Compare commits

..

137 Commits

Author SHA1 Message Date
UpstreamData
24b66de971 bump version number 2022-09-06 11:18:34 -06:00
UpstreamData
62d664a14c strip file output when checking for fault light in bosminer 2022-09-06 11:18:17 -06:00
UpstreamData
03b9a90f68 bump version number 2022-09-06 11:02:05 -06:00
UpstreamData
fefe0324b9 fix a bug with checking miner fault lights in bosminer 2022-09-06 11:01:42 -06:00
UpstreamData
62b14a78b7 bump version number 2022-09-06 10:49:16 -06:00
UpstreamData
0ff505bbb4 add support for innosilicon T3H+ 2022-09-06 10:48:04 -06:00
UpstreamData
b6c8c930a2 bump version number 2022-08-30 16:01:48 -06:00
UpstreamData
903bb93c4e add check_light() for bosminers by checking if delay_on exists in the Red LED directory 2022-08-30 10:51:22 -06:00
UpstreamData
59667cf104 bump version number 2022-08-29 09:53:01 -06:00
UpstreamData
3fd1b41bec add support for whatsminer VH60 2022-08-29 09:52:35 -06:00
UpstreamData
6569107f64 bump version number 2022-08-29 09:07:02 -06:00
UpstreamData
9d746a6dcb add errors to MinerData().as_influxdb() 2022-08-29 09:06:20 -06:00
UpstreamData
fce4c07c32 bump version number 2022-08-25 15:34:28 -06:00
UpstreamData
094857758a update MinerData().as_influxdb() to include properties. 2022-08-25 15:34:04 -06:00
UpstreamData
2a49b89849 bump version number 2022-08-25 13:09:19 -06:00
UpstreamData
4ecd135734 fix a bug with incorrect types in miner data 2022-08-25 13:09:03 -06:00
UpstreamData
836defc216 bump version number 2022-08-25 13:04:06 -06:00
UpstreamData
f8f777b5b5 fix tag data to be escaped properly 2022-08-25 13:03:46 -06:00
UpstreamData
b15e0a7363 bump version number 2022-08-25 12:50:55 -06:00
UpstreamData
5c1d06f743 attempt to fix a bug with influx db miner data 2022-08-25 12:50:30 -06:00
UpstreamData
51de56feb3 bump version number 2022-08-25 12:43:05 -06:00
UpstreamData
256a4ac909 fix boolean bug in miner data 2022-08-25 12:39:57 -06:00
UpstreamData
09800c8ad2 bump version number 2022-08-24 15:18:04 -06:00
UpstreamData
83a7d8c60f add MinerData().as_json() 2022-08-24 15:17:36 -06:00
UpstreamData
ee2698be50 update poetry.lock 2022-08-24 13:56:05 -06:00
UpstreamData
dc43087b0d bump version number 2022-08-24 13:51:25 -06:00
UpstreamData
4fa3511725 add float support to MinerData().as_influx() 2022-08-24 13:50:55 -06:00
UpstreamData
4b9ae70424 bump version number 2022-08-24 13:35:48 -06:00
UpstreamData
74ebffb4fc add MinerData().as_influx() to write miner data as influx db line format data. 2022-08-24 13:34:57 -06:00
UpstreamData
54206da449 add getitem and setitem methods to MinerData 2022-08-24 10:02:51 -06:00
UpstreamData
dd54ff7ee4 bump version number 2022-08-22 15:19:08 -06:00
UpstreamData
abef0c3d59 add efficiency to MinerData 2022-08-22 14:50:49 -06:00
UpstreamData
957c9a3678 Refactor MinerFactory._get_miner_type(), move BaseMiner to its own file, and improve interface of miner.send_config() (#17) 2022-08-22 14:10:37 -06:00
Arceris
50ccfec1b3 Add a check in _parse_type_from_version (#16) 2022-08-18 10:12:19 -06:00
UpstreamData
8e7d6751e2 update docs to include all currently supported miners including BOS devices. 2022-08-12 12:30:29 -06:00
UpstreamData
b77c4173c6 update supported miners in docs and add link to it in README.md 2022-08-12 12:22:49 -06:00
UpstreamData
4941cffb70 fix a bad character in requirements.txt caused by pre-commit 2022-08-12 12:14:12 -06:00
UpstreamData
81d5d23189 Revert "Revert "attempt to improve the readability of miner_factory.py""
This reverts commit 9da5a836ce.
2022-08-11 15:22:00 -06:00
UpstreamData
9da5a836ce Revert "attempt to improve the readability of miner_factory.py"
This reverts commit c9a536fc60.
2022-08-11 15:16:42 -06:00
UpstreamData
c9a536fc60 attempt to improve the readability of miner_factory.py 2022-08-11 15:12:45 -06:00
UpstreamData
fa172b56b0 bump version number 2022-08-11 15:11:09 -06:00
UpstreamData
ee45f2342e improve how the fault light looks on whatsminers 2022-08-11 11:58:42 -06:00
UpstreamData
1f59ef025d bump version number 2022-08-10 16:26:24 -06:00
UpstreamData
d6a153144f remove print statement from btminer configuration 2022-08-10 16:25:54 -06:00
UpstreamData
99001e2e13 added the ability to configure whatsminer via API 2022-08-10 16:21:47 -06:00
UpstreamData
92b847656e add light functions for btminer, and add a way to reset to admin password for btminers to allow unlocking of priviledged API. 2022-08-10 15:31:42 -06:00
UpstreamData
a41525e828 bump version number 2022-08-10 11:18:44 -06:00
UpstreamData
5e9588cc56 add M32V20 2022-08-10 11:17:12 -06:00
UpstreamData
b8239703c1 move M32 to separate file. 2022-08-10 11:14:06 -06:00
Colin Crossman
5d49135b59 Add hooks for M32 (not S) 2022-08-10 11:06:54 -06:00
UpstreamData
3a5a76080b add pre-commit hooks 2022-08-10 09:57:31 -06:00
UpstreamData
f23e10d629 add better hiveon support and improve T9 functionality. 2022-08-10 09:04:01 -06:00
UpstreamData
b7d4891140 bump version number 2022-08-09 11:12:10 -06:00
UpstreamData
5f5cbd9060 add support for setting X19 web passwords and X17 web passwords. 2022-08-09 11:04:08 -06:00
UpstreamData
8379359caf update documentation and make BaseMiner and BaseMinerAPI unable to be instantiated directly. Add more unittests for miners. 2022-08-08 13:19:59 -06:00
UpstreamData
62238192ce bump version number 2022-08-05 16:34:00 -06:00
UpstreamData
1997003643 fix a bug with whatsminer crashing if hitting a S19 condition 2022-08-05 16:33:40 -06:00
UpstreamData
3a81844898 bump version number 2022-08-05 12:12:25 -06:00
UpstreamData
0ac80fb205 fix a bug with vnish miner identification 2022-08-05 12:12:10 -06:00
UpstreamData
9494018c12 bump version number 2022-08-05 12:08:05 -06:00
UpstreamData
0bc86c98c5 add support for some X19 models running vnish to be able to get miner type from them 2022-08-05 12:07:33 -06:00
UpstreamData
f0d69c9ca7 bump version number 2022-08-05 10:23:22 -06:00
UpstreamData
b81590bd2e add support for X19 miner errors codes shown on their dashboard 2022-08-05 10:23:03 -06:00
UpstreamData
a53e01df6f bump version number 2022-08-02 08:19:03 -06:00
UpstreamData
f63e063954 fix a bug with not capitalizing BITMAIN for a model check 2022-08-02 08:18:46 -06:00
upstreamdata
9cbaf7076a bump version number 2022-07-28 12:29:24 -06:00
upstreamdata
daa5ac5870 fixed a bug with capitalization of "Pro" in antminer models 2022-07-28 12:28:55 -06:00
upstreamdata
0b8c08016b bump version number. 2022-07-27 23:48:42 -06:00
upstreamdata
8c768d351b fix a bug with braiinsOS+ miners which return with a capital letter in their model instead of a lowercase letter. 2022-07-27 23:44:19 -06:00
UpstreamData
c9e7fa2629 bump version number 2022-07-22 13:05:13 -06:00
UpstreamData
9d3f2b5968 add support for M20 versions and update docs 2022-07-22 13:04:47 -06:00
UpstreamData
283e3d5e11 bump version number 2022-07-21 08:43:30 -06:00
UpstreamData
add4b575c2 update shields and improve typing and handling of fault light checks 2022-07-21 08:42:35 -06:00
UpstreamData
af2f1e9ad5 misc docs changes 2022-07-20 14:54:59 -06:00
UpstreamData
8258320a7b fix a bug with avalonminer imports and bump version number 2022-07-20 14:42:33 -06:00
UpstreamData
a5dc7f485b bump version number 2022-07-20 14:37:04 -06:00
UpstreamData
025b5bf6f0 improved avalonminer handler and added fault light to get_data 2022-07-20 14:36:13 -06:00
UpstreamData
3d3064d78e improve some type hinting compatibility 2022-07-20 11:19:13 -06:00
UpstreamData
2e3991355b Update README.md 2022-07-20 10:05:23 -06:00
UpstreamData
73a4cf5834 bump version number 2022-07-19 16:16:56 -06:00
UpstreamData
b120064e80 fixed a bug with miner factory not handling ConnectionRefused errors properly 2022-07-19 16:16:36 -06:00
UpstreamData
3ec833e700 add copyright license, using Apache 2.0 license 2022-07-19 15:43:24 -06:00
UpstreamData
29aeea1194 bump version number 2022-07-19 13:02:55 -06:00
UpstreamData
994d53ae3b removed arbitrary scan thread limitation dividing 2022-07-19 13:01:39 -06:00
UpstreamData
a95333eb1c removed arbitrary scan thread limitation dividing 2022-07-19 13:01:28 -06:00
UpstreamData
c5f2d71791 improved the speed of scanning by only checking secondary ports if the 4028 connection is refused 2022-07-19 13:00:15 -06:00
UpstreamData
26ae6ebfb2 bump version number 2022-07-19 11:19:32 -06:00
UpstreamData
e65cb0573d update miner factory to handle some types of stock fw S9s 2022-07-19 11:18:55 -06:00
UpstreamData
f8590b0c5f improve more typing 2022-07-18 14:46:17 -06:00
UpstreamData
43b4992cee improve logging and some documentation 2022-07-18 14:38:54 -06:00
UpstreamData
98e2cfae84 bump version number 2022-07-18 12:05:44 -06:00
UpstreamData
cb01c1a8ee update network to scan fast even if some miners are not responding properly 2022-07-18 12:05:22 -06:00
UpstreamData
36a273ec2b bump version number 2022-07-18 11:45:14 -06:00
UpstreamData
6a0dc03b9d update to a better way to handle settings 2022-07-18 11:44:22 -06:00
UpstreamData
ce7b006c8f bump version number 2022-07-18 11:23:15 -06:00
UpstreamData
88cc05bcea handle for BraiinsOS miners that dont have bosminer running for some reason 2022-07-18 11:21:40 -06:00
UpstreamData
ae749f4a90 add additional scan ports as backups in case 4028 doesn't respond 2022-07-18 10:03:39 -06:00
UpstreamData
36b30a2cdd added supported miners to the docs 2022-07-14 16:32:43 -06:00
UpstreamData
ae9f103578 bump version number 2022-07-14 11:49:34 -06:00
UpstreamData
13b583b739 fixed some bugs and added support for M20Sv10 and 20 2022-07-14 11:39:55 -06:00
UpstreamData
aaf0d7fa75 bump version number 2022-07-14 09:47:36 -06:00
UpstreamData
a8cbb6394e fix a bug with ints being passed to miner network 2022-07-14 09:45:21 -06:00
UpstreamData
ca6980b1ad update documentation and add docs from config 2022-07-13 16:17:08 -06:00
UpstreamData
c6c87a864d fix pyproject.toml 2022-07-13 14:29:06 -06:00
UpstreamData
ed17a0f436 update documentation and bump version number 2022-07-13 14:25:12 -06:00
UpstreamData
36fead3dd1 Update README.md 2022-07-13 11:57:33 -06:00
UpstreamData
ecb16c10ca Update README.md 2022-07-13 11:56:28 -06:00
UpstreamData
a540db3246 update docs requirements 2022-07-13 11:28:12 -06:00
UpstreamData
81a2f99fbf add jinja 2 to docs requirements to fix mkdocs bug 2022-07-13 11:22:00 -06:00
UpstreamData
1dd9f742ad Revert "dealete readthedocs.yaml"
This reverts commit 7bd6a0f136.
2022-07-13 11:18:56 -06:00
UpstreamData
7bd6a0f136 dealete readthedocs.yaml 2022-07-13 11:17:53 -06:00
UpstreamData
7297f12e88 update readthedocs info 2022-07-13 11:15:34 -06:00
UpstreamData
0e009c3a16 add readthedocs info 2022-07-13 11:12:29 -06:00
UpstreamData
95b0cc364b add miner data documentation 2022-07-13 11:08:12 -06:00
UpstreamData
2dcc4f0cfc add docs for miner factory and miner network 2022-07-13 10:52:42 -06:00
UpstreamData
d7e9498018 add docs for the rest of the APIs 2022-07-13 10:11:05 -06:00
UpstreamData
0324a21e79 add bmminer docs 2022-07-13 09:13:21 -06:00
UpstreamData
5700bd1c9c start adding some basic documentation 2022-07-12 16:25:05 -06:00
UpstreamData
abc6494f18 bump version number 2022-07-12 11:58:59 -06:00
UpstreamData
5de8fc064e fix a bug with hashrate parsing on braiins os devices with kh hashrates 2022-07-12 11:58:21 -06:00
UpstreamData
c9d620105b add support for braiins OS errors 2022-07-12 11:55:42 -06:00
UpstreamData
5d6fc5b26d add support for whatsminer error codes in get_data() 2022-07-12 11:41:38 -06:00
UpstreamData
6bd319355d bump version number 2022-07-12 10:26:25 -06:00
UpstreamData
31827e7dd1 fix a bug with old versions of bosminer returning not ready from fans 2022-07-12 10:25:48 -06:00
UpstreamData
26961a5d8c bump version number 2022-07-11 15:02:45 -06:00
UpstreamData
2ff09a3765 add support for getting hashrates from each board for bosminer, bmminer, and btminer 2022-07-11 15:02:04 -06:00
UpstreamData
18c26adbb6 remove dockerignore 2022-07-11 13:23:19 -06:00
UpstreamData
4bfafabe9d bump version number 2022-07-11 10:45:34 -06:00
UpstreamData
19e6ed90ec update README.md 2022-07-11 10:28:18 -06:00
UpstreamData
eca60d1eae improved whatsminer power limit handling 2022-07-11 10:15:55 -06:00
UpstreamData
8b8a592308 bump version number to 0.9.3 2022-07-11 08:29:36 -06:00
UpstreamData
c3de4188d6 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	pyproject.toml
2022-07-11 08:25:36 -06:00
UpstreamData
490138fd1a bump version number 2022-07-11 08:25:24 -06:00
UpstreamData
f566b7fcb9 bump version number 2022-07-11 08:23:04 -06:00
upstreamdata
7fb4237e51 update publish workflow to use correct secret name 2022-07-07 15:44:28 -06:00
upstreamdata
eeffdecde1 update publish workflow 2022-07-07 15:34:26 -06:00
UpstreamData
477a411c87 Create python-publish.yml 2022-07-07 15:12:38 -06:00
233 changed files with 8350 additions and 3578 deletions

View File

@@ -1,8 +0,0 @@
# Ignore VENV
venv
# Ignore builds
build
# Ignore github files
.github

22
.github/workflows/python-publish.yml vendored Normal file
View File

@@ -0,0 +1,22 @@
name: PyPI
on:
push:
tags:
- "v*.*.*"
paths-ignore:
- '**.md'
- 'docs/**'
- 'docsrc/**'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Publish GH release
uses: softprops/action-gh-release@v0.1.14
- name: Build using poetry and publish to PyPi
uses: JRubics/poetry-publish@v1.11
with:
pypi_token: ${{ secrets.PYPI_API_KEY }}

21
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,21 @@
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: 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]

20
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,20 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-20.04
tools:
python: "3.9"
mkdocs:
configuration: mkdocs.yml
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/requirements.txt

176
LICENSE.txt Normal file
View 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

314
README.md
View File

@@ -2,18 +2,34 @@
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.* *A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pypi](https://img.shields.io/pypi/v/pyasic.svg)](https://pypi.org/project/pyasic/)
[![python](https://img.shields.io/pypi/pyversions/pyasic.svg)](https://pypi.org/project/pyasic/)
[![Read the Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/)
[![GitHub](https://img.shields.io/github/license/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/UpstreamData/pyasic)](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
## Supported Miners
Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/)
## Documentation
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/)
## Usage ## Usage
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.
You can also use poetry by initializing and running ```poetry install``` ### Standard Usage
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 the GUI applications 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-dev.txt``` on Windows or ```pip3 install -r requirements-dev.txt``` on Mac or UNIX if the first command fails.
You can also use poetry by initializing and running ```poetry install```, and you will have to install `pre-commit` (`pip install pre-commit`).
Finally, initialize pre-commit hooks with `pre-commit install`
### Interfacing with miners programmatically ### Interfacing with miners programmatically
<br>
##### 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: ##### 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 ```python
# need to import these 2 libraries, you need asyncio anyway so make sure you have sys imported # need to import these 2 libraries, you need asyncio anyway so make sure you have sys imported
import sys import sys
@@ -31,228 +47,130 @@ To write your own custom programs with this repo, you have many options.
It is recommended that you explore the files in this repo to familiarize yourself with them, try starting with the miners module and going from there. 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.
A basic script to find all miners on the network and get the hashrate from them looks like this - There are 2 main ways to get a miner and it's functions via scanning or via the MinerFactory.
#### Scanning for miners
```python ```python
import asyncio import asyncio
import sys
from pyasic.network import MinerNetwork 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())
async def get_hashrate():
# Miner Network class allows for easy scanning of a network # define asynchronous function to scan for miners
# Give it any IP on a network and it will find the whole subnet async def scan_and_get_data():
# It can also be passed a subnet mask: # Define network range to be used for scanning
# miner_network = MinerNetwork('192.168.1.55', mask=23) # This can take a list of IPs, a constructor string, or an IP and subnet mask
miner_network = MinerNetwork('192.168.1.1') # The standard mask is /24, and you can pass any IP address in the subnet
# Miner Network scan function returns Miner classes for all miners found net = MinerNetwork("192.168.1.69", mask=24)
miners = await miner_network.scan_network_for_miners() # Scan the network for miners
# Each miner will return with its own set of functions, and an API class instance # 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] tasks = [miner.get_data() for miner in miners]
# Gather all tasks asynchronously and run them # Gather all tasks asynchronously and run them
data = await asyncio.gather(*tasks) data = await asyncio.gather(*tasks)
# now we have a list of MinerData, and can get .hashrate
print([item.hashrate for item in data])
# 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__': if __name__ == "__main__":
asyncio.new_event_loop().run_until_complete(get_hashrate()) asyncio.run(scan_and_get_data())
``` ```
<br>
You can also create your own miner without scanning if you know the IP:
</br>
#### Getting a miner if you know the IP
```python ```python
import asyncio import asyncio
import ipaddress import sys
from pyasic.miners.miner_factory import Mine~~~~rFactory
from pyasic.miners import get_miner
# Fix whatsminer bug
# if the computer is windows, set the event loop policy to a WindowsSelector policy
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
async def get_miner_hashrate(ip: str): # define asynchronous function to get miner and data
# Instantiate a Miner Factory to generate miners from their IP async def get_miner_data(miner_ip: str):
miner_factory = MinerFactory() # Use MinerFactory to get miner
# Make the string IP into an IP address # MinerFactory is a singleton, so we can just get the instance in place
miner_ip = ipaddress.ip_address(ip) miner = await get_miner(miner_ip)
# Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip) # Get data from the miner
# Get the API data
data = await miner.get_data() data = await miner.get_data()
# print out hashrate
print(data.hashrate)
if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(
get_miner_hashrate(str("192.168.1.69")))
```
Now that you know that, lets move on to some common API functions that you might want to use.
### Common commands:
* Get the data used by the config utility, this includes pool data, wattage use, temperature, hashrate, etc:
* All the data from below commands and more are returned from this in a consistent dataclass. Check out the `MinerData` class in `/data/__init__.py` for more information.
```python
import asyncio
import ipaddress
from pyasic.miners.miner_factory import MinerFactory
async def get_miner_pool_data(ip: str):
# Instantiate a Miner Factory to generate miners from their IP
miner_factory = MinerFactory()
# Make the string IP into an IP address
miner_ip = ipaddress.ip_address(ip)
# Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip)
# Get the data
data = await miner.get_data()
print(data) print(data)
if __name__ == "__main__":
if __name__ == '__main__': asyncio.run(get_miner_data("192.168.1.69"))
asyncio.new_event_loop().run_until_complete(
get_miner_pool_data(str("192.168.1.69")))
``` ```
### Advanced data gathering
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
#### List available API commands
```python
import asyncio
import sys
from pyasic.miners import get_miner
# 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())
* Getting pool data: async def get_api_commands(miner_ip: str):
# Get the miner
miner = await get_miner(miner_ip)
# List all available commands
print(miner.api.get_commands())
if __name__ == "__main__":
asyncio.run(get_api_commands("192.168.1.69"))
```
#### Use miner API commands to gather data
The miner API commands will raise an `APIError` if they fail with a bad status code, to bypass this you must send them manually by using `miner.api.send_command(command, ignore_errors=True)`
```python ```python
import asyncio import asyncio
import ipaddress import sys
from pyasic.miners.miner_factory import MinerFactory
from pyasic.miners import get_miner
# Fix whatsminer bug
# if the computer is windows, set the event loop policy to a WindowsSelector policy
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
async def get_miner_pool_data(ip: str): async def get_api_commands(miner_ip: str):
# Instantiate a Miner Factory to generate miners from their IP # Get the miner
miner_factory = MinerFactory() miner = await get_miner(miner_ip)
# Make the string IP into an IP address
miner_ip = ipaddress.ip_address(ip) # Run the devdetails command
# Wait for the factory to return the miner # This is equivalent to await miner.api.send_command("devdetails")
miner = await miner_factory.get_miner(miner_ip) devdetails: dict = await miner.api.devdetails()
# Get the API data print(devdetails)
pools = await miner.api.pools()
# safe_parse_api_data parses the data from a miner API
# It will raise an APIError (from API import APIError) if there is a problem
data = pools["POOLS"]
# parse further from here to get all the pool info you want.
# each pool is on a different index eg:
# data[0] is pool 1
# data[1] is pool 2
# etc
print(data)
if __name__ == '__main__': if __name__ == "__main__":
asyncio.new_event_loop().run_until_complete( asyncio.run(get_api_commands("192.168.1.69"))
get_miner_pool_data(str("192.168.1.69")))
```
* Getting temperature data:
This one is a bit tougher, lots of miners do this a different way, you might need to experiment a bit to find what works for you.
BraiinsOS uses the "temps" command, Whatsminers has it in "devs", Avalonminers put it in "stats" as well as some other miners,
but the spot I like to try first is in "summary".
A pretty good example of really trying to make this robust is in ```cfg_util.func.miners``` in the ```get_formatted_data()``` function.
```python
import asyncio
import ipaddress
from pyasic.miners.miner_factory import MinerFactory
async def get_miner_temperature_data(ip: str):
# Instantiate a Miner Factory to generate miners from their IP
miner_factory = MinerFactory()
# Make the string IP into an IP address
miner_ip = ipaddress.ip_address(ip)
# Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip)
# Get the API data
summary = await miner.api.summary()
data = summary['SUMMARY'][0]["Temperature"]
print(data)
if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(
get_miner_temperature_data(str("192.168.1.69")))
```
* Getting power data:
How about data on the power usage of the miner? This one only works for Whatsminers and BraiinsOS for now, and the Braiins one just uses the tuning setting, but its good enough for basic uses.
```python
import asyncio
import ipaddress
from pyasic.miners.miner_factory import MinerFactory
async def get_miner_power_data(ip: str):
data = None
# Instantiate a Miner Factory to generate miners from their IP
miner_factory = MinerFactory()
# Make the string IP into an IP address
miner_ip = ipaddress.ip_address(ip)
# Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip)
# check if this can be sent the "tunerstatus" command, BraiinsOS only
if "tunerstatus" in miner.api.get_commands():
# send the command
tunerstatus = await miner.api.tunerstatus()
# parse the return
data = tunerstatus['TUNERSTATUS'][0]["PowerLimit"]
else:
# send the command
# whatsminers have the power info in summary
summary = await miner.api.summary()
# parse the return
data = summary['SUMMARY'][0]["Power"]
if data:
print(data)
if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(
get_miner_power_data(str("192.168.1.69")))
```
* Multicommands:
Multicommands make it much easier to get many types of data all at once. The multicommand function will also remove any commands that your API can't handle automatically.
How about we get the current pool user and hashrate in 1 command?
```python
import asyncio
import ipaddress
from pyasic.miners.miner_factory import MinerFactory
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate_and_pool(ip: str):
# Instantiate a Miner Factory to generate miners from their IP
miner_factory = MinerFactory()
# Make the string IP into an IP address
miner_ip = ipaddress.ip_address(ip)
# Wait for the factory to return the miner
miner = await miner_factory.get_miner(miner_ip)
# Get the API data
api_data = await miner.api.multicommand("pools", "summary")
if "pools" in api_data.keys():
user = api_data["pools"][0]["POOLS"][0]["User"]
print(user)
if "summary" in api_data.keys():
hashrate = api_data["summary"][0]["SUMMARY"][0]["MHS av"]
print(hashrate)
if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(
get_miner_hashrate_and_pool(str("192.168.1.9")))
``` ```

25
docs/API/api.md Normal file
View File

@@ -0,0 +1,25 @@
# pyasic
## Miner APIs
Each miner has a unique API that is used to communicate with it.
Each of these API types has commands that differ between them, and some commands have data that others do not.
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
Use these instead -
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
<br>
## BaseMinerAPI
::: pyasic.API.BaseMinerAPI
handler: python
options:
heading_level: 4

7
docs/API/bmminer.md Normal file
View File

@@ -0,0 +1,7 @@
# pyasic
## BMMinerAPI
::: pyasic.API.bmminer.BMMinerAPI
handler: python
options:
show_root_heading: false
heading_level: 4

7
docs/API/bosminer.md Normal file
View File

@@ -0,0 +1,7 @@
# pyasic
## BOSMinerAPI
::: pyasic.API.bosminer.BOSMinerAPI
handler: python
options:
show_root_heading: false
heading_level: 4

7
docs/API/btminer.md Normal file
View File

@@ -0,0 +1,7 @@
# pyasic
## BTMinerAPI
::: pyasic.API.btminer.BTMinerAPI
handler: python
options:
show_root_heading: false
heading_level: 4

7
docs/API/cgminer.md Normal file
View File

@@ -0,0 +1,7 @@
# pyasic
## CGMinerAPI
::: pyasic.API.cgminer.CGMinerAPI
handler: python
options:
show_root_heading: false
heading_level: 4

7
docs/API/unknown.md Normal file
View File

@@ -0,0 +1,7 @@
# pyasic
## UnknownAPI
::: pyasic.API.unknown.UnknownAPI
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,24 @@
# pyasic
## Miner Config
::: pyasic.config.MinerConfig
handler: python
options:
show_root_heading: false
heading_level: 4
## Pool Groups
::: pyasic.config._PoolGroup
handler: python
options:
show_root_heading: false
heading_level: 4
## Pools
::: pyasic.config._Pool
handler: python
options:
show_root_heading: false
heading_level: 4

25
docs/data/error_codes.md Normal file
View File

@@ -0,0 +1,25 @@
# 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

8
docs/data/miner_data.md Normal file
View File

@@ -0,0 +1,8 @@
# pyasic
## Miner Data
::: pyasic.data.MinerData
handler: python
options:
show_root_heading: false
heading_level: 4

103
docs/index.md Normal file
View File

@@ -0,0 +1,103 @@
# pyasic
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![pypi](https://img.shields.io/pypi/v/pyasic.svg)](https://pypi.org/project/pyasic/)
[![python](https://img.shields.io/pypi/pyversions/pyasic.svg)](https://pypi.org/project/pyasic/)
[![Read the Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/)
[![GitHub](https://img.shields.io/github/license/UpstreamData/pyasic)](https://github.com/UpstreamData/pyasic/blob/master/LICENSE.txt)
[![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/UpstreamData/pyasic)](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.
[Supported Miner Types](miners/supported_types.md)
Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
<br>
## Scanning for miners
To scan for miners in pyasic, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
The command [`MinerNetwork().scan_network_for_miners()`][pyasic.network.MinerNetwork.scan_network_for_miners] returns a list that contains any miners found.
```python
import asyncio # asyncio for handling the async part
from pyasic.network import MinerNetwork # miner network handles the scanning
async def scan_miners(): # define async scan function to allow awaiting
# create a miner network
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
network = MinerNetwork("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__":
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.
```python
import asyncio # asyncio for handling the async part
from pyasic.miners.miner_factory import MinerFactory # miner factory handles miners 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")
print(miner_1, miner_2)
if __name__ == "__main__":
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
```
<br>
## Getting data from miners
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called `get_data()`.
This function will return a instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
```python
import asyncio
from pyasic.miners.miner_factory import MinerFactory
async def gather_miner_data():
miner = await MinerFactory().get_miner("192.168.1.75")
miner_data = await miner.get_data()
print(miner_data) # all data from the dataclass
print(miner_data.hashrate) # hashrate of the miner in TH/s
if __name__ == "__main__":
asyncio.run(gather_miner_data())
```
You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.
```python
import asyncio # asyncio for handling the async part
from pyasic.network import MinerNetwork # miner network handles the scanning
async def gather_miner_data(): # define async scan function to allow awaiting
network = MinerNetwork("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__":
asyncio.run(gather_miner_data())
```

117
docs/miners/antminer/X17.md Normal file
View File

@@ -0,0 +1,117 @@
# pyasic
## X17 Models
## S17
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
handler: python
options:
show_root_heading: false
heading_level: 4
## S17+
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## S17 Pro
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## S17e
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
handler: python
options:
show_root_heading: false
heading_level: 4
## T17
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
handler: python
options:
show_root_heading: false
heading_level: 4
## T17+
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## T17e
::: pyasic.miners.antminer.bmminer.X17.T17e.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_Plus.BOSMinerS17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## S17 Pro (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## S17e (BOS)
::: pyasic.miners.antminer.bosminer.X17.S17e.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_Plus.BOSMinerT17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
## T17e (BOS)
::: pyasic.miners.antminer.bosminer.X17.T17e.BOSMinerT17e
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,94 @@
# pyasic
## X19 Models
## S19
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
handler: python
options:
show_root_heading: false
heading_level: 4
## S19 Pro
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
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.S19j.BMMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
## S19j Pro
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
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_Pro.BOSMinerS19Pro
handler: python
options:
show_root_heading: false
heading_level: 4
## S19j (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19j.BOSMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
## S19j Pro (BOS)
::: pyasic.miners.antminer.bosminer.X19.S19j_Pro.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

View File

@@ -0,0 +1,36 @@
# pyasic
## X9 Models
## X9 (BOS)
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
handler: python
options:
show_root_heading: false
heading_level: 4
## S9
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
handler: python
options:
show_root_heading: false
heading_level: 4
## S9i
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
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

View File

@@ -0,0 +1,26 @@
# pyasic
## A10X Models
## A1026
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
handler: python
options:
show_root_heading: false
heading_level: 4
## A1047
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
handler: python
options:
show_root_heading: false
heading_level: 4
## A1066
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,26 @@
# pyasic
## A7X Models
## A721
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
handler: python
options:
show_root_heading: false
heading_level: 4
## A741
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
handler: python
options:
show_root_heading: false
heading_level: 4
## A761
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,26 @@
# pyasic
## A8X Models
## A821
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
handler: python
options:
show_root_heading: false
heading_level: 4
## A841
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
handler: python
options:
show_root_heading: false
heading_level: 4
## A851
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,10 @@
# pyasic
## A9X Models
## A921
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## BMMiner Backend
::: pyasic.miners._backends.bmminer.BMMiner
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## BOSMiner Backend
::: pyasic.miners._backends.bosminer.BOSMiner
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## BTMiner Backend
::: pyasic.miners._backends.btminer.BTMiner
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## CGMiner Backend
::: pyasic.miners._backends.cgminer.CGMiner
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## Hiveon Backend
::: pyasic.miners._backends.hiveon.Hiveon
handler: python
options:
show_root_heading: false
heading_level: 4

10
docs/miners/base_miner.md Normal file
View 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

View File

@@ -0,0 +1,10 @@
# pyasic
## T3X Models
## T3H+
::: pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,20 @@
# pyasic
## Miner Factory
::: pyasic.miners.miner_factory.MinerFactory
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.

View File

@@ -0,0 +1,96 @@
# pyasic
## Supported Miners
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
##### pyasic currently supports the following miners and subtypes:
* Braiins OS+ Devices:
* X19 Series:
* [S19][pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19]
* [S19 Pro][pyasic.miners.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro]
* [S19j][pyasic.miners.antminer.bosminer.X19.S19j.BOSMinerS19j]
* [S19j Pro][pyasic.miners.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro]
* [T19][pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19]
* X17 Series:
* [S17][pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17]
* [S17+][pyasic.miners.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus]
* [S17 Pro][pyasic.miners.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro]
* [S17e][pyasic.miners.antminer.bosminer.X17.S17e.BOSMinerS17e]
* [T17][pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17]
* [T17+][pyasic.miners.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus]
* [T17e][pyasic.miners.antminer.bosminer.X17.T17e.BOSMinerT17e]
* X9 Series:
* [S9][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9]
* [S9i][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9]
* [S9j][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9]
* 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]
* [VH60][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH60]
* [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]
* [M32][pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32]
* [V20][pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V20]
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S]
* M2X Series:
* [M20][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20]:
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10]
* [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]
* Stock Firmware Innosilicon Miners:
* T3X Series:
* [T3H+][pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus]

View File

@@ -0,0 +1,91 @@
# pyasic
## M2X Models
## M20
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20
handler: python
options:
show_root_heading: false
heading_level: 4
## M20V10
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
handler: python
options:
show_root_heading: false
heading_level: 4
## M20S
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
handler: python
options:
show_root_heading: false
heading_level: 4
## M20SV10
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
handler: python
options:
show_root_heading: false
heading_level: 4
## M20SV20
::: 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
handler: python
options:
show_root_heading: false
heading_level: 4
## M21
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
handler: python
options:
show_root_heading: false
heading_level: 4
## M21S
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
handler: python
options:
show_root_heading: false
heading_level: 4
## M21SV20
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
handler: python
options:
show_root_heading: false
heading_level: 4
## M21SV60
::: 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
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,155 @@
# pyasic
## M3X Models
## M30S
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S
handler: python
options:
show_root_heading: false
heading_level: 4
## M30SVE10
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10
handler: python
options:
show_root_heading: false
heading_level: 4
## M30SVG20
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
handler: python
options:
show_root_heading: false
heading_level: 4
## M30SVE20
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20
handler: python
options:
show_root_heading: false
heading_level: 4
## M30SV50
::: 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
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+VE40
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
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
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
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40
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
## M31S
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus
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
## M32
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32
handler: python
options:
show_root_heading: false
heading_level: 4
## M32V20
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V20
handler: python
options:
show_root_heading: false
heading_level: 4
## M32S
::: pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,8 @@
# pyasic
## Miner Network
::: pyasic.network.MinerNetwork
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,12 @@
# pyasic
## Miner Network Range
[`MinerNetworkRange`][pyasic.network.net_range.MinerNetworkRange] is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
::: pyasic.network.net_range.MinerNetworkRange
handler: python
options:
show_root_heading: false
heading_level: 4

3
docs/requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
jinja2<3.1.0
mkdocs
mkdocstrings[python]

49
mkdocs.yml Normal file
View File

@@ -0,0 +1,49 @@
site_name: pyasic
repo_url: https://github.com/UpstreamData/pyasic
nav:
- Introduction: "index.md"
- Miners:
- Supported Miners: "miners/supported_types.md"
- Miner Factory: "miners/miner_factory.md"
- Backends:
- BMMiner: "miners/backends/bmminer.md"
- BOSMiner: "miners/backends/bosminer.md"
- BTMiner: "miners/backends/btminer.md"
- CGMiner: "miners/backends/cgminer.md"
- Hiveon: "miners/backends/hiveon.md"
- Classes:
- Antminer X9: "miners/antminer/X9.md"
- Antminer X17: "miners/antminer/X17.md"
- Antminer X19: "miners/antminer/X19.md"
- Avalon 7X: "miners/avalonminer/A7X.md"
- Avalon 8X: "miners/avalonminer/A8X.md"
- Avalon 9X: "miners/avalonminer/A9X.md"
- Avalon 10X: "miners/avalonminer/A10X.md"
- Whatsminer M2X: "miners/whatsminer/M2X.md"
- Whatsminer M3X: "miners/whatsminer/M3X.md"
- Innosilicon T3X: "miners/innosilicon/T3X.md"
- Network:
- Miner Network: "network/miner_network.md"
- Miner Network Range: "network/miner_network_range.md"
- Data:
- Miner Data: "data/miner_data.md"
- Error Codes: "data/error_codes.md"
- Config:
- Miner Config: "config/miner_config.md"
- Advanced:
- Miner APIs:
- Intro: "API/api.md"
- BMMiner: "API/bmminer.md"
- BOSMiner: "API/bosminer.md"
- BTMiner: "API/btminer.md"
- CGMiner: "API/cgminer.md"
- Unknown: "API/unknown.md"
- Base Miner: "miners/base_miner.md"
plugins:
- mkdocstrings
- search

6
poetry.lock generated
View File

@@ -17,7 +17,7 @@ trio = ["trio (>=0.16)"]
[[package]] [[package]]
name = "asyncssh" name = "asyncssh"
version = "2.11.0" version = "2.12.0"
description = "AsyncSSH: Asynchronous SSHv2 client and server library" description = "AsyncSSH: Asynchronous SSHv2 client and server library"
category = "main" category = "main"
optional = false optional = false
@@ -218,8 +218,8 @@ anyio = [
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"}, {file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
] ]
asyncssh = [ asyncssh = [
{file = "asyncssh-2.11.0-py3-none-any.whl", hash = "sha256:7302348cbd54c58d3259da17f13e77912de1b005e366b15c8b183d948c8a91a8"}, {file = "asyncssh-2.12.0-py3-none-any.whl", hash = "sha256:6841c4242c606fd51188c974ec2f4887efeec67ecdfa5b84140711dacd985ab3"},
{file = "asyncssh-2.11.0.tar.gz", hash = "sha256:59c36ce77ba9dda8dd57ad875776e7105ddb1fa851bc039bb3aeadeac4f67b56"}, {file = "asyncssh-2.12.0.tar.gz", hash = "sha256:274101322c4b941823aeed8e1ab6e7be5191686c6db2d2bd35afeba30505e780"},
] ]
certifi = [ certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"}, {file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},

View File

@@ -1,8 +1,23 @@
# 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 asyncio
import json import json
import ipaddress import ipaddress
import warnings import warnings
import logging import logging
from typing import Union
class APIError(Exception): class APIError(Exception):
@@ -40,8 +55,17 @@ class BaseMinerAPI:
# ip address of the miner # ip address of the miner
self.ip = ipaddress.ip_address(ip) 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 get_commands(self) -> list: def get_commands(self) -> list:
"""Get a list of command accessible to a specific type of API on the miner.""" """Get a list of command accessible to a specific type of API on the miner.
Returns:
A list of all API commands that the miner supports.
"""
return [ return [
func func
for func in for func in
@@ -59,51 +83,75 @@ class BaseMinerAPI:
] ]
] ]
def _check_commands(self, *commands):
allowed_commands = self.get_commands()
return_commands = []
for command in [*commands]:
if command in allowed_commands:
return_commands.append(command)
else:
warnings.warn(
f"""Removing incorrect command: {command}
If you are sure you want to use this command please use API.send_command("{command}", ignore_errors=True) instead.""",
APIWarning,
)
return return_commands
async def multicommand( async def multicommand(
self, *commands: str, ignore_x19_error: bool = False self, *commands: str, ignore_x19_error: bool = False
) -> dict: ) -> dict:
"""Creates and sends multiple commands as one command to the miner.""" """Creates and sends multiple commands as one command to the miner.
Parameters:
*commands: The commands to send as a multicommand to the miner.
ignore_x19_error: Whether or not to ignore errors raised by x19 miners when using the "+" delimited style.
"""
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}") logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# split the commands into a proper list # make sure we can actually run each command, otherwise they will fail
user_commands = [*commands] commands = self._check_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" # standard multicommand format is "command1+command2"
# doesnt work for S19 which is dealt with in the send command function # doesnt work for S19 which uses the backup _x19_multicommand
command = "+".join(commands) command = "+".join(commands)
data = None
try: try:
data = await self.send_command(command, x19_command=ignore_x19_error) data = await self.send_command(command, x19_command=ignore_x19_error)
except APIError: except APIError:
try: logging.debug(f"{self.ip}: Handling X19 multicommand.")
data = {} data = await self._x19_multicommand(*command.split("+"))
# S19 handler, try again logging.debug(f"{self.ip}: Received multicommand data.")
for cmd in command.split("+"): return data
data[cmd] = []
data[cmd].append(await self.send_command(cmd)) async def _x19_multicommand(self, *commands):
except APIError as e: data = None
raise APIError(e) try:
except Exception as e: data = {}
logging.warning(f"{self.ip}: API Multicommand Error: {e}") # send all commands individually
if data: for cmd in commands:
logging.debug(f"{self.ip}: Received multicommand data.") data[cmd] = []
return data data[cmd].append(await self.send_command(cmd, x19_command=True))
except APIError as e:
raise APIError(e)
except Exception as e:
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
return data
async def send_command( async def send_command(
self, self,
command: str or bytes, command: Union[str, bytes],
parameters: str or int or bool = None, parameters: Union[str, int, bool] = None,
ignore_errors: bool = False, ignore_errors: bool = False,
x19_command: bool = False, x19_command: bool = False,
) -> dict: ) -> dict:
"""Send an API command to the miner and return the result.""" """Send an API command to the miner and return the result.
Parameters:
command: The command to sent to the miner.
parameters: Any additional parameters to be sent with the command.
ignore_errors: Whether or not to raise APIError when the command returns an error.
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
Returns:
The return data from the API command parsed from JSON into a dict.
"""
try: try:
# get reader and writer streams # get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port) reader, writer = await asyncio.open_connection(str(self.ip), self.port)
@@ -115,7 +163,7 @@ If you are sure you want to use this command please use API.send_command("{item}
# create the command # create the command
cmd = {"command": command} cmd = {"command": command}
if parameters is not None: if parameters:
cmd["parameter"] = parameters cmd["parameter"] = parameters
# send the command # send the command
@@ -133,9 +181,9 @@ If you are sure you want to use this command please use API.send_command("{item}
break break
data += d data += d
except Exception as e: except Exception as e:
logging.warning(f"{self.ip}: API Command Error: {e}") logging.warning(f"{self.ip}: API Command Error: - {e}")
data = self.load_api_data(data) data = self._load_api_data(data)
# close the connection # close the connection
writer.close() writer.close()
@@ -144,7 +192,7 @@ If you are sure you want to use this command please use API.send_command("{item}
# check for if the user wants to allow errors to return # check for if the user wants to allow errors to return
if not ignore_errors: if not ignore_errors:
# validate the command succeeded # validate the command succeeded
validation = self.validate_command_output(data) validation = self._validate_command_output(data)
if not validation[0]: if not validation[0]:
if not x19_command: if not x19_command:
logging.warning(f"{self.ip}: API Command Error: {validation[1]}") logging.warning(f"{self.ip}: API Command Error: {validation[1]}")
@@ -153,8 +201,7 @@ If you are sure you want to use this command please use API.send_command("{item}
return data return data
@staticmethod @staticmethod
def validate_command_output(data: dict) -> tuple: def _validate_command_output(data: dict) -> tuple:
"""Check if the returned command output is correctly formatted."""
# check if the data returned is correct or an error # check if the data returned is correct or an error
# if status isn't a key, it is a multicommand # if status isn't a key, it is a multicommand
if "STATUS" not in data.keys(): if "STATUS" not in data.keys():
@@ -181,8 +228,7 @@ If you are sure you want to use this command please use API.send_command("{item}
return True, None return True, None
@staticmethod @staticmethod
def load_api_data(data: bytes) -> dict: def _load_api_data(data: bytes) -> dict:
"""Convert API data from JSON to dict"""
str_data = None str_data = None
try: try:
# some json from the API returns with a null byte (\x00) on the end # some json from the API returns with a null byte (\x00) on the end

View File

@@ -1,3 +1,17 @@
# 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 from pyasic.API import BaseMinerAPI
@@ -6,17 +20,17 @@ class BMMinerAPI(BaseMinerAPI):
Each method corresponds to an API command in BMMiner. Each method corresponds to an API command in BMMiner.
BMMiner API documentation: [BMMiner API documentation](https://github.com/jameshilliard/bmminer/blob/master/API-README)
https://github.com/jameshilliard/bmminer/blob/master/API-README
This class abstracts use of the BMMiner API, as well as the This class abstracts use of the BMMiner API, as well as the
methods for sending commands to it. The self.send_command() methods for sending commands to it. The `self.send_command()`
function handles sending a command to the miner asynchronously, and function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which as such is the base for many of the functions in this class, which
rely on it to send the command for them. rely on it to send the command for them.
:param ip: The IP of the miner to reference the API on. Parameters:
:param port: The port to reference the API on. Default is 4028. ip: The IP of the miner to reference the API on.
port: The port to reference the API on. Default is 4028.
""" """
def __init__(self, ip: str, port: int = 4028) -> None: def __init__(self, ip: str, port: int = 4028) -> None:
@@ -24,58 +38,81 @@ class BMMinerAPI(BaseMinerAPI):
async def version(self) -> dict: async def version(self) -> dict:
"""Get miner version info. """Get miner version info.
<details>
<summary>Expand</summary>
:return: Miner version information. Returns:
Miner version information.
</details>
""" """
return await self.send_command("version") return await self.send_command("version")
async def config(self) -> dict: async def config(self) -> dict:
"""Get some basic configuration info. """Get some basic configuration info.
<details>
<summary>Expand</summary>
:return: Some miner configuration information: Returns:
ASC Count <- the number of ASCs ## Some miner configuration information:
PGA Count <- the number of PGAs * ASC Count <- the number of ASCs
Pool Count <- the number of Pools * PGA Count <- the number of PGAs
Strategy <- the current pool strategy * Pool Count <- the number of Pools
Log Interval <- the interval of logging * Strategy <- the current pool strategy
Device Code <- list of compiled device drivers * Log Interval <- the interval of logging
OS <- the current operating system * Device Code <- list of compiled device drivers
Failover-Only <- failover-only setting * OS <- the current operating system
Scan Time <- scan-time setting * Failover-Only <- failover-only setting
Queue <- queue setting * Scan Time <- scan-time setting
Expiry <- expiry setting * Queue <- queue setting
* Expiry <- expiry setting
</details>
""" """
return await self.send_command("config") return await self.send_command("config")
async def summary(self) -> dict: async def summary(self) -> dict:
"""Get the status summary of the miner. """Get the status summary of the miner.
<details>
<summary>Expand</summary>
:return: The status summary of the miner. Returns:
The status summary of the miner.
</details>
""" """
return await self.send_command("summary") return await self.send_command("summary")
async def pools(self) -> dict: async def pools(self) -> dict:
"""Get pool information. """Get pool information.
<details>
<summary>Expand</summary>
:return: Miner pool information. Returns:
Miner pool information.
</details>
""" """
return await self.send_command("pools") return await self.send_command("pools")
async def devs(self) -> dict: async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details. """Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
return await self.send_command("devs") return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict: async def edevs(self, old: bool = False) -> dict:
"""Get data on each PGA/ASC with their details, ignoring """Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
blacklisted and zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago old: Include zombie devices that became zombies less than 'old' seconds ago
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
if old: if old:
return await self.send_command("edevs", parameters=old) return await self.send_command("edevs", parameters=old)
@@ -84,46 +121,70 @@ class BMMinerAPI(BaseMinerAPI):
async def pga(self, n: int) -> dict: async def pga(self, n: int) -> dict:
"""Get data from PGA n. """Get data from PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA number to get data from. Parameters:
n: The PGA number to get data from.
:return: Data on the PGA n. Returns:
Data on the PGA n.
</details>
""" """
return await self.send_command("pga", parameters=n) return await self.send_command("pga", parameters=n)
async def pgacount(self) -> dict: async def pgacount(self) -> dict:
"""Get data fon all PGAs. """Get data fon all PGAs.
<details>
<summary>Expand</summary>
:return: Data on the PGAs connected. Returns:
Data on the PGAs connected.
</details>
""" """
return await self.send_command("pgacount") return await self.send_command("pgacount")
async def switchpool(self, n: int) -> dict: async def switchpool(self, n: int) -> dict:
"""Switch pools to pool n. """Switch pools to pool n.
<details>
<summary>Expand</summary>
:param n: The pool to switch to. Parameters:
n: The pool to switch to.
:return: A confirmation of switching to pool n. Returns:
A confirmation of switching to pool n.
</details>
""" """
return await self.send_command("switchpool", parameters=n) return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict: async def enablepool(self, n: int) -> dict:
"""Enable pool n. """Enable pool n.
<details>
<summary>Expand</summary>
:param n: The pool to enable. Parameters:
n: The pool to enable.
:return: A confirmation of enabling pool n. Returns:
A confirmation of enabling pool n.
</details>
""" """
return await self.send_command("enablepool", parameters=n) return await self.send_command("enablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict: async def addpool(self, url: str, username: str, password: str) -> dict:
"""Add a pool to the miner. """Add a pool to the miner.
<details>
<summary>Expand</summary>
:param url: The URL of the new pool to add. Parameters:
:param username: The users username on the new pool. url: The URL of the new pool to add.
:param password: The worker password on the new pool. username: The users username on the new pool.
password: The worker password on the new pool.
:return: A confirmation of adding the pool. Returns:
A confirmation of adding the pool.
</details>
""" """
return await self.send_command( return await self.send_command(
"addpool", parameters=f"{url},{username},{password}" "addpool", parameters=f"{url},{username},{password}"
@@ -131,48 +192,73 @@ class BMMinerAPI(BaseMinerAPI):
async def poolpriority(self, *n: int) -> dict: async def poolpriority(self, *n: int) -> dict:
"""Set pool priority. """Set pool priority.
<details>
<summary>Expand</summary>
:param n: Pools in order of priority. Parameters:
*n: Pools in order of priority.
:return: A confirmation of setting pool priority. Returns:
A confirmation of setting pool priority.
</details>
""" """
pools = f"{','.join([str(item) for item in n])}" pools = f"{','.join([str(item) for item in n])}"
return await self.send_command("poolpriority", parameters=pools) return await self.send_command("poolpriority", parameters=pools)
async def poolquota(self, n: int, q: int) -> dict: async def poolquota(self, n: int, q: int) -> dict:
"""Set pool quota. """Set pool quota.
<details>
<summary>Expand</summary>
:param n: Pool number to set quota on. Parameters:
:param q: Quota to set the pool to. n: Pool number to set quota on.
q: Quota to set the pool to.
:return: A confirmation of setting pool quota. Returns:
A confirmation of setting pool quota.
</details>
""" """
return await self.send_command("poolquota", parameters=f"{n},{q}") return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict: async def disablepool(self, n: int) -> dict:
"""Disable a pool. """Disable a pool.
<details>
<summary>Expand</summary>
:param n: Pool to disable. Parameters:
n: Pool to disable.
:return: A confirmation of diabling the pool. Returns:
A confirmation of diabling the pool.
</details>
""" """
return await self.send_command("disablepool", parameters=n) return await self.send_command("disablepool", parameters=n)
async def removepool(self, n: int) -> dict: async def removepool(self, n: int) -> dict:
"""Remove a pool. """Remove a pool.
<details>
<summary>Expand</summary>
:param n: Pool to remove. Parameters:
n: Pool to remove.
:return: A confirmation of removing the pool. Returns:
A confirmation of removing the pool.
</details>
""" """
return await self.send_command("removepool", parameters=n) return await self.send_command("removepool", parameters=n)
async def save(self, filename: str = None) -> dict: async def save(self, filename: str = None) -> dict:
"""Save the config. """Save the config.
<details>
<summary>Expand</summary>
:param filename: Filename to save the config as. Parameters:
filename: Filename to save the config as.
:return: A confirmation of saving the config. Returns:
A confirmation of saving the config.
</details>
""" """
if filename: if filename:
return await self.send_command("save", parameters=filename) return await self.send_command("save", parameters=filename)
@@ -181,83 +267,123 @@ class BMMinerAPI(BaseMinerAPI):
async def quit(self) -> dict: async def quit(self) -> dict:
"""Quit BMMiner. """Quit BMMiner.
<details>
<summary>Expand</summary>
:return: A single "BYE" before BMMiner quits. Returns:
A single "BYE" before BMMiner quits.
</details>
""" """
return await self.send_command("quit") return await self.send_command("quit")
async def notify(self) -> dict: async def notify(self) -> dict:
"""Notify the user of past errors. """Notify the user of past errors.
<details>
<summary>Expand</summary>
:return: The last status and count of each devices problem(s). Returns:
The last status and count of each devices problem(s).
</details>
""" """
return await self.send_command("notify") return await self.send_command("notify")
async def privileged(self) -> dict: async def privileged(self) -> dict:
"""Check if you have privileged access. """Check if you have privileged access.
<details>
<summary>Expand</summary>
:return: The STATUS section with an error if you have no Returns:
privileged access, or success if you have privileged access. 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") return await self.send_command("privileged")
async def pgaenable(self, n: int) -> dict: async def pgaenable(self, n: int) -> dict:
"""Enable PGA n. """Enable PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to enable. Parameters:
n: The PGA to enable.
:return: A confirmation of enabling PGA n. Returns:
A confirmation of enabling PGA n.
</details>
""" """
return await self.send_command("pgaenable", parameters=n) return await self.send_command("pgaenable", parameters=n)
async def pgadisable(self, n: int) -> dict: async def pgadisable(self, n: int) -> dict:
"""Disable PGA n. """Disable PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to disable. Parameters:
n: The PGA to disable.
:return: A confirmation of disabling PGA n. Returns:
A confirmation of disabling PGA n.
</details>
""" """
return await self.send_command("pgadisable", parameters=n) return await self.send_command("pgadisable", parameters=n)
async def pgaidentify(self, n: int) -> dict: async def pgaidentify(self, n: int) -> dict:
"""Identify PGA n. """Identify PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to identify. Parameters:
n: The PGA to identify.
:return: A confirmation of identifying PGA n. Returns:
A confirmation of identifying PGA n.
</details>
""" """
return await self.send_command("pgaidentify", parameters=n) return await self.send_command("pgaidentify", parameters=n)
async def devdetails(self) -> dict: async def devdetails(self) -> dict:
"""Get data on all devices with their static details. """Get data on all devices with their static details.
<details>
<summary>Expand</summary>
:return: Data on all devices with their static details. Returns:
Data on all devices with their static details.
</details>
""" """
return await self.send_command("devdetails") return await self.send_command("devdetails")
async def restart(self) -> dict: async def restart(self) -> dict:
"""Restart BMMiner using the API. """Restart BMMiner using the API.
<details>
<summary>Expand</summary>
:return: A reply informing of the restart. Returns:
A reply informing of the restart.
</details>
""" """
return await self.send_command("restart") return await self.send_command("restart")
async def stats(self) -> dict: async def stats(self) -> dict:
"""Get stats of each device/pool with more than 1 getwork. """Get stats of each device/pool with more than 1 getwork.
<details>
<summary>Expand</summary>
:return: Stats of each device/pool with more than 1 getwork. Returns:
Stats of each device/pool with more than 1 getwork.
</details>
""" """
return await self.send_command("stats") return await self.send_command("stats")
async def estats(self, old: bool = False) -> dict: async def estats(self, old: bool = False) -> dict:
"""Get stats of each device/pool with more than 1 getwork, """Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
ignoring zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago. old: Include zombie devices that became zombies less than 'old' seconds ago.
:return: Stats of each device/pool with more than 1 getwork, Returns:
ignoring zombie devices. Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
</details>
""" """
if old: if old:
return await self.send_command("estats", parameters=old) return await self.send_command("estats", parameters=old)
@@ -266,92 +392,126 @@ class BMMinerAPI(BaseMinerAPI):
async def check(self, command: str) -> dict: async def check(self, command: str) -> dict:
"""Check if the command command exists in BMMiner. """Check if the command command exists in BMMiner.
<details>
<summary>Expand</summary>
:param command: The command to check. Parameters:
command: The command to check.
:return: Information about a command: Returns:
Exists (Y/N) <- the command exists in this version ## Information about a command:
Access (Y/N) <- you have access to use the 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) return await self.send_command("check", parameters=command)
async def failover_only(self, failover: bool) -> dict: async def failover_only(self, failover: bool) -> dict:
"""Set failover-only. """Set failover-only.
<details>
<summary>Expand</summary>
Parameters:
failover: What to set failover-only to.
:param failover: What to set failover-only to. Returns:
Confirmation of setting failover-only.
:return: Confirmation of setting failover-only. </details>
""" """
return await self.send_command("failover-only", parameters=failover) return await self.send_command("failover-only", parameters=failover)
async def coin(self) -> dict: async def coin(self) -> dict:
"""Get information on the current coin. """Get information on the current coin.
<details>
<summary>Expand</summary>
:return: Information about the current coin being mined: Returns:
Hash Method <- the hashing algorithm ## Information about the current coin being mined:
Current Block Time <- blocktime as a float, 0 means none * Hash Method <- the hashing algorithm
Current Block Hash <- the hash of the current block, blank * Current Block Time <- blocktime as a float, 0 means none
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 * LP <- whether LP is in use on at least 1 pool
Network Difficulty: the current network difficulty * Network Difficulty: the current network difficulty
</details>
""" """
return await self.send_command("coin") return await self.send_command("coin")
async def debug(self, setting: str) -> dict: async def debug(self, setting: str) -> dict:
"""Set a debug setting. """Set a debug setting.
<details>
<summary>Expand</summary>
:param setting: Which setting to switch to. Options are: Parameters:
Silent, setting: Which setting to switch to.
Quiet, ## Options are:
Verbose, * Silent
Debug, * Quiet
RPCProto, * Verbose
PerDevice, * Debug
WorkTime, * RPCProto
Normal. * PerDevice
* WorkTime
* Normal
:return: Data on which debug setting was enabled or disabled. Returns:
Data on which debug setting was enabled or disabled.
</details>
""" """
return await self.send_command("debug", parameters=setting) return await self.send_command("debug", parameters=setting)
async def setconfig(self, name: str, n: int) -> dict: async def setconfig(self, name: str, n: int) -> dict:
"""Set config of name to value n. """Set config of name to value n.
<details>
<summary>Expand</summary>
:param name: The name of the config setting to set. Options are: Parameters:
queue, name: The name of the config setting to set.
scantime, ## Options are:
expiry. * queue
:param n: The value to set the 'name' setting to. * scantime
* expiry
n: The value to set the 'name' setting to.
:return: The results of setting config of name to n. Returns:
The results of setting config of name to n.
</details>
""" """
return await self.send_command("setconfig", parameters=f"{name},{n}") return await self.send_command("setconfig", parameters=f"{name},{n}")
async def usbstats(self) -> dict: async def usbstats(self) -> dict:
"""Get stats of all USB devices except ztex. """Get stats of all USB devices except ztex.
<details>
<summary>Expand</summary>
:return: The stats of all USB devices except ztex. Returns:
The stats of all USB devices except ztex.
</details>
""" """
return await self.send_command("usbstats") return await self.send_command("usbstats")
async def pgaset(self, n: int, opt: str, val: int = None) -> dict: async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
"""Set PGA option opt to val on PGA n. """Set PGA option opt to val on PGA n.
<details>
<summary>Expand</summary>
Options: Options:
```
MMQ - MMQ -
opt: clock opt: clock
val: 160 - 230 (multiple of 2) val: 160 - 230 (multiple of 2)
CMR - CMR -
opt: clock opt: clock
val: 100 - 220 val: 100 - 220
```
:param n: The PGA to set the options on. Parameters:
:param opt: The option to set. Setting this to 'help' n: The PGA to set the options on.
returns a help message. opt: The option to set. Setting this to 'help' returns a help message.
:param val: The value to set the option to. val: The value to set the option to.
:return: Confirmation of setting PGA n with opt[,val]. Returns:
Confirmation of setting PGA n with opt[,val].
</details>
""" """
if val: if val:
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}") return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
@@ -360,75 +520,108 @@ class BMMinerAPI(BaseMinerAPI):
async def zero(self, which: str, summary: bool) -> dict: async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device. """Zero a device.
<details>
<summary>Expand</summary>
:param which: Which device to zero. Parameters:
Setting this to 'all' zeros all devices. 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.
Setting this to 'bestshare' zeros only the bestshare values summary: Whether or not to show a full summary.
for each pool and global.
:param summary: Whether or not to show a full summary.
:return: the STATUS section with info on the zero and optional Returns:
summary. the STATUS section with info on the zero and optional summary.
</details>
""" """
return await self.send_command("zero", parameters=f"{which},{summary}") return await self.send_command("zero", parameters=f"{which},{summary}")
async def hotplug(self, n: int) -> dict: async def hotplug(self, n: int) -> dict:
"""Enable hotplug. """Enable hotplug.
<details>
<summary>Expand</summary>
:param n: The device number to set hotplug on. Parameters:
n: The device number to set hotplug on.
:return: Information on hotplug status. Returns:
Information on hotplug status.
</details>
""" """
return await self.send_command("hotplug", parameters=n) return await self.send_command("hotplug", parameters=n)
async def asc(self, n: int) -> dict: async def asc(self, n: int) -> dict:
"""Get data for ASC device n. """Get data for ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to get data for. Parameters:
n: The device to get data for.
:return: The data for ASC device n. Returns:
The data for ASC device n.
</details>
""" """
return await self.send_command("asc", parameters=n) return await self.send_command("asc", parameters=n)
async def ascenable(self, n: int) -> dict: async def ascenable(self, n: int) -> dict:
"""Enable ASC device n. """Enable ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to enable. Parameters:
n: The device to enable.
:return: Confirmation of enabling ASC device n. Returns:
Confirmation of enabling ASC device n.
</details>
""" """
return await self.send_command("ascenable", parameters=n) return await self.send_command("ascenable", parameters=n)
async def ascdisable(self, n: int) -> dict: async def ascdisable(self, n: int) -> dict:
"""Disable ASC device n. """Disable ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to disable. Parameters:
n: The device to disable.
:return: Confirmation of disabling ASC device n. Returns:
Confirmation of disabling ASC device n.
</details>
""" """
return await self.send_command("ascdisable", parameters=n) return await self.send_command("ascdisable", parameters=n)
async def ascidentify(self, n: int) -> dict: async def ascidentify(self, n: int) -> dict:
"""Identify ASC device n. """Identify ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to identify. Parameters:
n: The device to identify.
:return: Confirmation of identifying ASC device n. Returns:
Confirmation of identifying ASC device n.
</details>
""" """
return await self.send_command("ascidentify", parameters=n) return await self.send_command("ascidentify", parameters=n)
async def asccount(self) -> dict: async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info. """Get data on the number of ASC devices and their info.
<details>
<summary>Expand</summary>
:return: Data on all ASC devices. Returns:
Data on all ASC devices.
</details>
""" """
return await self.send_command("asccount") return await self.send_command("asccount")
async def ascset(self, n: int, opt: str, val: int = None) -> dict: async def ascset(self, n: int, opt: str, val: int = None) -> dict:
"""Set ASC n option opt to value val. """Set ASC n option opt to value val.
<details>
<summary>Expand</summary>
Sets an option on the ASC n to a value. Allowed options are: Sets an option on the ASC n to a value. Allowed options are:
```
AVA+BTB - AVA+BTB -
opt: freq opt: freq
val: 256 - 1024 (chip frequency) val: 256 - 1024 (chip frequency)
@@ -462,14 +655,16 @@ class BMMinerAPI(BaseMinerAPI):
opt: clock opt: clock
val: 0 - 15 val: 0 - 15
```
Parameters:
n: The ASC to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
:param n: The ASC to set the options on. Returns:
:param opt: The option to set. Setting this to 'help' returns a Confirmation of setting option opt to value val.
help message. </details>
:param val: The value to set the option to.
:return: Confirmation of setting option opt to value val.
""" """
if val: if val:
return await self.send_command("ascset", parameters=f"{n},{opt},{val}") return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
@@ -478,14 +673,22 @@ class BMMinerAPI(BaseMinerAPI):
async def lcd(self) -> dict: async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner. """Get a general all-in-one status summary of the miner.
<details>
<summary>Expand</summary>
:return: An all-in-one status summary of the miner. Returns:
An all-in-one status summary of the miner.
</details>
""" """
return await self.send_command("lcd") return await self.send_command("lcd")
async def lockstats(self) -> dict: async def lockstats(self) -> dict:
"""Write lockstats to STDERR. """Write lockstats to STDERR.
<details>
<summary>Expand</summary>
:return: The result of writing the lock stats to STDERR. Returns:
The result of writing the lock stats to STDERR.
</details>
""" """
return await self.send_command("lockstats") return await self.send_command("lockstats")

View File

@@ -1,3 +1,17 @@
# 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 from pyasic.API import BaseMinerAPI
@@ -6,60 +20,80 @@ class BOSMinerAPI(BaseMinerAPI):
Each method corresponds to an API command in BOSMiner. Each method corresponds to an API command in BOSMiner.
BOSMiner API documentation: [BOSMiner API documentation](https://docs.braiins.com/os/plus-en/Development/1_api.html)
https://docs.braiins.com/os/plus-en/Development/1_api.html
This class abstracts use of the BOSMiner API, as well as the This class abstracts use of the BOSMiner API, as well as the
methods for sending commands to it. The self.send_command() methods for sending commands to it. The `self.send_command()`
function handles sending a command to the miner asynchronously, and function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which as such is the base for many of the functions in this class, which
rely on it to send the command for them. rely on it to send the command for them.
:param ip: The IP of the miner to reference the API on. Parameters:
:param port: The port to reference the API on. Default is 4028. 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, port=4028): def __init__(self, ip: str, port: int = 4028):
super().__init__(ip, port) super().__init__(ip, port)
async def asccount(self) -> dict: async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info. """Get data on the number of ASC devices and their info.
<details>
<summary>Expand</summary>
:return: Data on all ASC devices. Returns:
Data on all ASC devices.
</details>
""" """
return await self.send_command("asccount") return await self.send_command("asccount")
async def asc(self, n: int) -> dict: async def asc(self, n: int) -> dict:
"""Get data for ASC device n. """Get data for ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to get data for. Parameters:
n: The device to get data for.
:return: The data for ASC device n. Returns:
The data for ASC device n.
</details>
""" """
return await self.send_command("asc", parameters=n) return await self.send_command("asc", parameters=n)
async def devdetails(self) -> dict: async def devdetails(self) -> dict:
"""Get data on all devices with their static details. """Get data on all devices with their static details.
<details>
<summary>Expand</summary>
:return: Data on all devices with their static details. Returns:
Data on all devices with their static details.
</details>
""" """
return await self.send_command("devdetails") return await self.send_command("devdetails")
async def devs(self) -> dict: async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details. """Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
return await self.send_command("devs") return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict: async def edevs(self, old: bool = False) -> dict:
"""Get data on each PGA/ASC with their details, ignoring """Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
blacklisted and zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago old: Include zombie devices that became zombies less than 'old' seconds ago
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
if old: if old:
return await self.send_command("edevs", parameters="old") return await self.send_command("edevs", parameters="old")
@@ -69,40 +103,62 @@ class BOSMinerAPI(BaseMinerAPI):
async def pools(self) -> dict: async def pools(self) -> dict:
"""Get pool information. """Get pool information.
:return: Miner pool information. <details>
<summary>Expand</summary>
Returns:
Miner pool information.
</details>
""" """
return await self.send_command("pools") return await self.send_command("pools")
async def summary(self) -> dict: async def summary(self) -> dict:
"""Get the status summary of the miner. """Get the status summary of the miner.
:return: The status summary of the miner. <details>
<summary>Expand</summary>
Returns:
The status summary of the miner.
</details>
""" """
return await self.send_command("summary") return await self.send_command("summary")
async def stats(self) -> dict: async def stats(self) -> dict:
"""Get stats of each device/pool with more than 1 getwork. """Get stats of each device/pool with more than 1 getwork.
:return: 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") return await self.send_command("stats")
async def version(self) -> dict: async def version(self) -> dict:
"""Get miner version info. """Get miner version info.
:return: Miner version information. <details>
<summary>Expand</summary>
Returns:
Miner version information.
</details>
""" """
return await self.send_command("version") return await self.send_command("version")
async def estats(self, old: bool = False) -> dict: async def estats(self, old: bool = False) -> dict:
"""Get stats of each device/pool with more than 1 getwork, """Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
ignoring zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago. old: Include zombie devices that became zombies less than 'old' seconds ago.
:return: Stats of each device/pool with more than 1 getwork, Returns:
ignoring zombie devices. Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
</details>
""" """
if old: if old:
return await self.send_command("estats", parameters=old) return await self.send_command("estats", parameters=old)
@@ -111,98 +167,109 @@ class BOSMinerAPI(BaseMinerAPI):
async def check(self, command: str) -> dict: async def check(self, command: str) -> dict:
"""Check if the command command exists in BOSMiner. """Check if the command command exists in BOSMiner.
<details>
<summary>Expand</summary>
:param command: The command to check. Parameters:
command: The command to check.
:return: Information about a command: Returns:
Exists (Y/N) <- the command exists in this version ## Information about a command:
Access (Y/N) <- you have access to use the 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) return await self.send_command("check", parameters=command)
async def coin(self) -> dict: async def coin(self) -> dict:
"""Get information on the current coin. """Get information on the current coin.
<details>
<summary>Expand</summary>
:return: Information about the current coin being mined: Returns:
Hash Method <- the hashing algorithm ## Information about the current coin being mined:
Current Block Time <- blocktime as a float, 0 means none * Hash Method <- the hashing algorithm
Current Block Hash <- the hash of the current block, blank * Current Block Time <- blocktime as a float, 0 means none
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 * LP <- whether LP is in use on at least 1 pool
Network Difficulty: the current network difficulty * Network Difficulty: the current network difficulty
</details>
""" """
return await self.send_command("coin") return await self.send_command("coin")
async def lcd(self) -> dict: async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner. """Get a general all-in-one status summary of the miner.
<details>
<summary>Expand</summary>
:return: An all-in-one status summary of the miner. Returns:
An all-in-one status summary of the miner.
</details>
""" """
return await self.send_command("lcd") return await self.send_command("lcd")
async def switchpool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future
raise NotImplementedError
# return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future
raise NotImplementedError
# return await self.send_command("enablepool", parameters=n)
async def disablepool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future
raise NotImplementedError
# return await self.send_command("disablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict:
# BOS has not implemented this yet, they will in the future
raise NotImplementedError
# return await self.send_command("addpool", parameters=f"{url},{username},{password}")
async def removepool(self, n: int) -> dict:
# BOS has not implemented this yet, they will in the future
raise NotImplementedError
# return await self.send_command("removepool", parameters=n)
async def fans(self) -> dict: async def fans(self) -> dict:
"""Get fan data. """Get fan data.
<details>
<summary>Expand</summary>
:return: Data on the fans of the miner. Returns:
Data on the fans of the miner.
</details>
""" """
return await self.send_command("fans") return await self.send_command("fans")
async def tempctrl(self) -> dict: async def tempctrl(self) -> dict:
"""Get temperature control data. """Get temperature control data.
<details>
<summary>Expand</summary>
:return: Data about the temp control settings of the miner. Returns:
Data about the temp control settings of the miner.
</details>
""" """
return await self.send_command("tempctrl") return await self.send_command("tempctrl")
async def temps(self) -> dict: async def temps(self) -> dict:
"""Get temperature data. """Get temperature data.
<details>
<summary>Expand</summary>
:return: Data on the temps of the miner. Returns:
Data on the temps of the miner.
</details>
""" """
return await self.send_command("temps") return await self.send_command("temps")
async def tunerstatus(self) -> dict: async def tunerstatus(self) -> dict:
"""Get tuner status data """Get tuner status data
<details>
<summary>Expand</summary>
:return: Data on the status of autotuning. Returns:
Data on the status of autotuning.
</details>
""" """
return await self.send_command("tunerstatus") return await self.send_command("tunerstatus")
async def pause(self) -> dict: async def pause(self) -> dict:
"""Pause mining. """Pause mining.
<details>
<summary>Expand</summary>
:return: Confirmation of pausing mining. Returns:
Confirmation of pausing mining.
</details>
""" """
return await self.send_command("pause") return await self.send_command("pause")
async def resume(self) -> dict: async def resume(self) -> dict:
"""Resume mining. """Resume mining.
<details>
<summary>Expand</summary>
:return: Confirmation of resuming mining. Returns:
Confirmation of resuming mining.
</details>
""" """
return await self.send_command("resume") return await self.send_command("resume")

View File

@@ -1,3 +1,17 @@
# 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 asyncio
import re import re
import json import json
@@ -5,12 +19,13 @@ import hashlib
import binascii import binascii
import base64 import base64
import logging import logging
from typing import Union
from passlib.handlers.md5_crypt import md5_crypt from passlib.handlers.md5_crypt import md5_crypt
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from pyasic.API import BaseMinerAPI, APIError from pyasic.API import BaseMinerAPI, APIError
from pyasic.settings import WHATSMINER_PWD from pyasic.settings import PyasicSettings
### IMPORTANT ### ### IMPORTANT ###
@@ -29,9 +44,12 @@ def _crypt(word: str, salt: str) -> str:
'\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a '\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a
ValueError is raised. ValueError is raised.
:param word: The word to be encrypted. Parameters:
:param salt: The salt to encrypt the word. word: The word to be encrypted.
:return: An MD5 hash of the word with the salt. salt: The salt to encrypt the word.
Returns:
An MD5 hash of the word with the salt.
""" """
# compile a standard format for the salt # compile a standard format for the salt
standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$") standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$")
@@ -50,11 +68,11 @@ def _crypt(word: str, salt: str) -> str:
def _add_to_16(string: str) -> bytes: def _add_to_16(string: str) -> bytes:
"""Add null bytes to a string until the length is a multiple 16 """Add null bytes to a string until the length is a multiple 16
:param string: The string to lengthen to a multiple of 16 and Parameters:
encode. string: The string to lengthen to a multiple of 16 and encode.
:return: The input string as bytes with a multiple of 16 as the Returns:
length. The input string as bytes with a multiple of 16 as the length.
""" """
while len(string) % 16 != 0: while len(string) % 16 != 0:
string += "\0" string += "\0"
@@ -67,10 +85,12 @@ def parse_btminer_priviledge_data(token_data: dict, data: dict):
Parses data from the BTMiner privileged API using the the token Parses data from the BTMiner privileged API using the the token
from the API in an AES format. from the API in an AES format.
:param token_data: The token information from self.get_token(). Parameters:
:param data: The data to parse, returned from the API. token_data: The token information from self.get_token().
data: The data to parse, returned from the API.
:return: A decoded dict version of the privileged command output. Returns:
A decoded dict version of the privileged command output.
""" """
# get the encoded data from the dict # get the encoded data from the dict
enc_data = data["enc"] enc_data = data["enc"]
@@ -97,10 +117,12 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
command as a dict of {'command': cmd}, with cmd being any command command as a dict of {'command': cmd}, with cmd being any command
that the miner API accepts. that the miner API accepts.
:param token_data: The token information from self.get_token(). Parameters:
:param command: The command to turn into a privileged command. token_data: The token information from self.get_token().
command: The command to turn into a privileged command.
:return: The encrypted privileged command to be sent to the miner. Returns:
The encrypted privileged command to be sent to the miner.
""" """
# add token to command # add token to command
command["token"] = token_data["host_sign"] command["token"] = token_data["host_sign"]
@@ -132,7 +154,7 @@ class BTMinerAPI(BaseMinerAPI):
Each method corresponds to an API command in BMMiner. Each method corresponds to an API command in BMMiner.
This class abstracts use of the BTMiner API, as well as the This class abstracts use of the BTMiner API, as well as the
methods for sending commands to it. The self.send_command() methods for sending commands to it. The `self.send_command()`
function handles sending a command to the miner asynchronously, and function handles sending a command to the miner asynchronously, and
as such is the base for many of the functions in this class, which as such is the base for many of the functions in this class, which
rely on it to send the command for them. rely on it to send the command for them.
@@ -148,36 +170,29 @@ class BTMinerAPI(BaseMinerAPI):
this automatically for you and will decode the output to look like this automatically for you and will decode the output to look like
a normal output from a miner API. a normal output from a miner API.
:param ip: The IP of the miner to reference the API on. Parameters:
:param port: The port to reference the API on. Default is 4028. ip: The IP of the miner to reference the API on.
:param pwd: The admin password of the miner. Default is admin. port: The port to reference the API on. Default is 4028.
pwd: The admin password of the miner. Default is admin.
""" """
def __init__(self, ip, port=4028, pwd: str = WHATSMINER_PWD): def __init__(
self,
ip: str,
port: int = 4028,
pwd: str = PyasicSettings().global_whatsminer_password,
):
super().__init__(ip, port) super().__init__(ip, port)
self.admin_pwd = pwd self.pwd = pwd
self.current_token = None self.current_token = None
async def send_command( async def send_command(
self, self,
command: str or bytes, command: Union[str, bytes],
parameters: str or int or bool = None, parameters: Union[str, int, bool] = None,
ignore_errors: bool = False, ignore_errors: bool = False,
**kwargs, **kwargs,
) -> dict: ) -> dict:
"""Send a command to the miner API.
Send a command using an asynchronous connection, load the data,
parse encoded data if needed, and return the result.
:param command: The command to send to the miner.
:param parameters: Parameters to pass to the command.
:param ignore_errors: Ignore the E (Error) status code from the
API.
:return: The data received from the API after sending the
command.
"""
# check if command is a string # check if command is a string
# if its bytes its encoded and needs to be sent raw # if its bytes its encoded and needs to be sent raw
if isinstance(command, str): if isinstance(command, str):
@@ -209,7 +224,7 @@ class BTMinerAPI(BaseMinerAPI):
except Exception as e: except Exception as e:
logging.info(f"{str(self.ip)}: {e}") logging.info(f"{str(self.ip)}: {e}")
data = self.load_api_data(data) data = self._load_api_data(data)
# close the connection # close the connection
writer.close() writer.close()
@@ -225,24 +240,27 @@ class BTMinerAPI(BaseMinerAPI):
if not ignore_errors: if not ignore_errors:
# if it fails to validate, it is likely an error # if it fails to validate, it is likely an error
validation = self.validate_command_output(data) validation = self._validate_command_output(data)
if not validation[0]: if not validation[0]:
raise APIError(validation[1]) raise APIError(validation[1])
# return the parsed json as a dict # return the parsed json as a dict
return data return data
async def get_token(self): async def get_token(self) -> dict:
"""Gets token information from the API. """Gets token information from the API.
<details>
<summary>Expand</summary>
:return: An encoded token and md5 password, which are used Returns:
for the privileged API. An encoded token and md5 password, which are used for the privileged API.
</details>
""" """
# get the token # get the token
data = await self.send_command("get_token") data = await self.send_command("get_token")
# encrypt the admin password with the salt # 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("$") pwd = pwd.split("$")
# take the 4th item from the pwd split # take the 4th item from the pwd split
@@ -278,22 +296,28 @@ class BTMinerAPI(BaseMinerAPI):
pool_3: str = None, pool_3: str = None,
worker_3: str = None, worker_3: str = None,
passwd_3: str = None, passwd_3: str = None,
): ) -> dict:
"""Update the pools of the miner using the API. """Update the pools of the miner using the API.
<details>
<summary>Expand</summary>
Update the pools of the miner using the API, only works after Update the pools of the miner using the API, only works after
changing the password of the miner using the Whatsminer tool. changing the password of the miner using the Whatsminer tool.
:param pool_1: The URL to update pool 1 to. Parameters:
:param worker_1: The worker name for pool 1 to update to. pool_1: The URL to update pool 1 to.
:param passwd_1: The password for pool 1 to update to. worker_1: The worker name for pool 1 to update to.
:param pool_2: The URL to update pool 2 to. passwd_1: The password for pool 1 to update to.
:param worker_2: The worker name for pool 2 to update to. pool_2: The URL to update pool 2 to.
:param passwd_2: The password for pool 2 to update to. worker_2: The worker name for pool 2 to update to.
:param pool_3: The URL to update pool 3 to. passwd_2: The password for pool 2 to update to.
:param worker_3: The worker name for pool 3 to update to. pool_3: The URL to update pool 3 to.
:param passwd_3: The password for pool 3 to update to. worker_3: The worker name for pool 3 to update to.
:return: A dict from the API to confirm the pools were updated. passwd_3: The password for pool 3 to update to.
Returns:
A dict from the API to confirm the pools were updated.
</details>
""" """
# get the token and password from the miner # get the token and password from the miner
token_data = await self.get_token() token_data = await self.get_token()
@@ -336,27 +360,36 @@ class BTMinerAPI(BaseMinerAPI):
# send the command # send the command
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def restart(self): async def restart(self) -> dict:
"""Restart BTMiner using the API. """Restart BTMiner using the API.
<details>
<summary>Expand</summary>
Restart BTMiner using the API, only works after changing Restart BTMiner using the API, only works after changing
the password of the miner using the Whatsminer tool. the password of the miner using the Whatsminer tool.
:return: A reply informing of the restart. Returns:
A reply informing of the restart.
</details>
""" """
command = {"cmd": "restart_btminer"} command = {"cmd": "restart_btminer"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def power_off(self, respbefore: bool = True): async def power_off(self, respbefore: bool = True) -> dict:
"""Power off the miner using the API. """Power off the miner using the API.
<details>
<summary>Expand</summary>
Power off the miner using the API, only works after changing Power off the miner using the API, only works after changing
the password of the miner using the Whatsminer tool. the password of the miner using the Whatsminer tool.
:param respbefore: Whether to respond before powering off. Parameters:
:return: A reply informing of the status of powering off. respbefore: Whether to respond before powering off.
Returns:
A reply informing of the status of powering off.
</details>
""" """
if respbefore: if respbefore:
command = {"cmd": "power_off", "respbefore": "true"} command = {"cmd": "power_off", "respbefore": "true"}
@@ -366,26 +399,36 @@ class BTMinerAPI(BaseMinerAPI):
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def power_on(self): async def power_on(self) -> dict:
"""Power on the miner using the API. """Power on the miner using the API.
<details>
<summary>Expand</summary>
Power on the miner using the API, only works after changing Power on the miner using the API, only works after changing
the password of the miner using the Whatsminer tool. the password of the miner using the Whatsminer tool.
:return: A reply informing of the status of powering on. Returns:
A reply informing of the status of powering on.
</details>
""" """
command = {"cmd": "power_on"} command = {"cmd": "power_on"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def reset_led(self): async def reset_led(self) -> dict:
"""Reset the LED on the miner using the API. """Reset the LED on the miner using the API.
<details>
<summary>Expand</summary>
Reset the LED on the miner using the API, only works after Reset the LED on the miner using the API, only works after
changing the password of the miner using the Whatsminer tool. changing the password of the miner using the Whatsminer tool.
:return: A reply informing of the status of resetting the LED. Returns:
A reply informing of the status of resetting the LED.
</details>
""" """
command = {"cmd": "set_led", "param": "auto"} command = {"cmd": "set_led", "param": "auto"}
token_data = await self.get_token() token_data = await self.get_token()
@@ -394,41 +437,56 @@ class BTMinerAPI(BaseMinerAPI):
async def set_led( async def set_led(
self, self,
auto: bool = True,
color: str = "red", color: str = "red",
period: int = 2000, period: int = 60,
duration: int = 1000, duration: int = 20,
start: int = 0, start: int = 0,
): ) -> dict:
"""Set the LED on the miner using the API. """Set the LED on the miner using the API.
<details>
<summary>Expand</summary>
Set the LED on the miner using the API, only works after Set the LED on the miner using the API, only works after
changing the password of the miner using the Whatsminer tool. changing the password of the miner using the Whatsminer tool.
:param color: The LED color to set, either 'red' or 'green'. Parameters:
:param period: The flash cycle in ms. auto: Whether or not to reset the LED to auto mode.
:param duration: LED on time in the cycle in ms. color: The LED color to set, either 'red' or 'green'.
:param start: LED on time offset in the cycle in ms. period: The flash cycle in ms.
:return: A reply informing of the status of setting the LED. 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 = { if not auto:
"cmd": "set_led", command = {
"color": color, "cmd": "set_led",
"period": period, "color": color,
"duration": duration, "period": period,
"start": start, "duration": duration,
} "start": start,
}
else:
command = {"cmd": "set_led", "param": "auto"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command, ignore_errors=True)
async def set_low_power(self): async def set_low_power(self) -> dict:
"""Set low power mode on the miner using the API. """Set low power mode on the miner using the API.
<details>
<summary>Expand</summary>
Set low power mode on the miner using the API, only works after Set low power mode on the miner using the API, only works after
changing the password of the miner using the Whatsminer tool. changing the password of the miner using the Whatsminer tool.
:return: A reply informing of the status of setting low power Returns:
mode.
A reply informing of the status of setting low power mode.
</details>
""" """
command = {"cmd": "set_low_power"} command = {"cmd": "set_low_power"}
token_data = await self.get_token() token_data = await self.get_token()
@@ -436,67 +494,95 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def update_firmware(self): # noqa - static async def update_firmware(self): # noqa - static
"""Not implemented."""
# to be determined if this will be added later # to be determined if this will be added later
# requires a file stream in bytes # requires a file stream in bytes
return NotImplementedError return NotImplementedError
async def reboot(self): async def reboot(self) -> dict:
"""Reboot the miner using the API. """Reboot the miner using the API.
<details>
<summary>Expand</summary>
:return: A reply informing of the status of the reboot. Returns:
A reply informing of the status of the reboot.
</details>
""" """
command = {"cmd": "reboot"} command = {"cmd": "reboot"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def factory_reset(self): async def factory_reset(self) -> dict:
"""Reset the miner to factory defaults. """Reset the miner to factory defaults.
<details>
<summary>Expand</summary>
:return: A reply informing of the status of the reset. Returns:
A reply informing of the status of the reset.
</details>
""" """
command = {"cmd": "factory_reset"} command = {"cmd": "factory_reset"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def update_pwd(self, old_pwd: str, new_pwd: str): async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
"""Update the admin user's password. """Update the admin user's password.
<details>
<summary>Expand</summary>
Update the admin user's password, only works after changing the Update the admin user's password, only works after changing the
password of the miner using the Whatsminer tool. New password password of the miner using the Whatsminer tool. New password
has a max length of 8 bytes, using letters, numbers, and has a max length of 8 bytes, using letters, numbers, and
underscores. underscores.
:param old_pwd: The old admin password. Parameters:
:param new_pwd: The new password to set. old_pwd: The old admin password.
:return: A reply informing of the status of setting the new_pwd: The new password to set.
password. Returns:
A reply informing of the status of setting the password.
""" """
self.pwd = old_pwd
# check if password length is greater than 8 bytes # check if password length is greater than 8 bytes
if len(new_pwd.encode("utf-8")) > 8: if len(new_pwd.encode("utf-8")) > 8:
return APIError( raise APIError(
f"New password too long, the max length is 8. " f"New password too long, the max length is 8. "
f"Password size: {len(new_pwd.encode('utf-8'))}" f"Password size: {len(new_pwd.encode('utf-8'))}"
) )
command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd} command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) try:
data = await self.send_command(enc_command)
except APIError as e:
raise e
self.pwd = new_pwd
return data
async def set_target_freq(self, percent: int): async def set_target_freq(self, percent: int) -> dict:
"""Update the target frequency. """Update the target frequency.
<details>
<summary>Expand</summary>
Update the target frequency, only works after changing the Update the target frequency, only works after changing the
password of the miner using the Whatsminer tool. The new password of the miner using the Whatsminer tool. The new
frequency must be between -10% and 100%. frequency must be between -10% and 100%.
:param percent: The frequency % to set. Parameters:
:return: A reply informing of the status of setting the percent: The frequency % to set.
frequency. Returns:
A reply informing of the status of setting the frequency.
</details>
""" """
if not -10 < percent < 100: if not -10 < percent < 100:
return APIError( raise APIError(
f"Frequency % is outside of the allowed " f"Frequency % is outside of the allowed "
f"range. Please set a % between -10 and " f"range. Please set a % between -10 and "
f"100" f"100"
@@ -506,88 +592,122 @@ class BTMinerAPI(BaseMinerAPI):
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def enable_fast_boot(self): async def enable_fast_boot(self) -> dict:
"""Turn on fast boot. """Turn on fast boot.
<details>
<summary>Expand</summary>
Turn on fast boot, only works after changing the password of Turn on fast boot, only works after changing the password of
the miner using the Whatsminer tool. the miner using the Whatsminer tool.
:return: A reply informing of the status of enabling fast boot. Returns:
A reply informing of the status of enabling fast boot.
</details>
""" """
command = {"cmd": "enable_btminer_fast_boot"} command = {"cmd": "enable_btminer_fast_boot"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def disable_fast_boot(self): async def disable_fast_boot(self) -> dict:
"""Turn off fast boot. """Turn off fast boot.
<details>
<summary>Expand</summary>
Turn off fast boot, only works after changing the password of Turn off fast boot, only works after changing the password of
the miner using the Whatsminer tool. the miner using the Whatsminer tool.
:return: A reply informing of the status of disabling fast boot. Returns:
A reply informing of the status of disabling fast boot.
</details>
""" """
command = {"cmd": "disable_btminer_fast_boot"} command = {"cmd": "disable_btminer_fast_boot"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def enable_web_pools(self): async def enable_web_pools(self) -> dict:
"""Turn on web pool updates. """Turn on web pool updates.
<details>
<summary>Expand</summary>
Turn on web pool updates, only works after changing the Turn on web pool updates, only works after changing the
password of the miner using the Whatsminer tool. password of the miner using the Whatsminer tool.
:return: A reply informing of the status of enabling web pools. Returns:
A reply informing of the status of enabling web pools.
</details>
""" """
command = {"cmd": "enable_web_pools"} command = {"cmd": "enable_web_pools"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def disable_web_pools(self): async def disable_web_pools(self) -> dict:
"""Turn off web pool updates. """Turn off web pool updates.
<details>
<summary>Expand</summary>
Turn off web pool updates, only works after changing the Turn off web pool updates, only works after changing the
password of the miner using the Whatsminer tool. password of the miner using the Whatsminer tool.
:return: A reply informing of the status of disabling web Returns:
pools.
A reply informing of the status of disabling web pools.
</details>
""" """
command = {"cmd": "disable_web_pools"} command = {"cmd": "disable_web_pools"}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def set_hostname(self, hostname: str): async def set_hostname(self, hostname: str) -> dict:
"""Set the hostname of the miner. """Set the hostname of the miner.
<details>
<summary>Expand</summary>
Set the hostname of the miner, only works after changing the Set the hostname of the miner, only works after changing the
password of the miner using the Whatsminer tool. password of the miner using the Whatsminer tool.
Parameters:
hostname: The new hostname to use.
Returns:
:param hostname: The new hostname to use. A reply informing of the status of setting the hostname.
:return: A reply informing of the status of setting the </details>
hostname.
""" """
command = {"cmd": "set_hostname", "hostname": hostname} command = {"cmd": "set_hostname", "hostname": hostname}
token_data = await self.get_token() token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def set_power_pct(self, percent: int): async def set_power_pct(self, percent: int) -> dict:
"""Set the power percentage of the miner. """Set the power percentage of the miner.
<details>
<summary>Expand</summary>
Set the power percentage of the miner, only works after changing Set the power percentage of the miner, only works after changing
the password of the miner using the Whatsminer tool. the password of the miner using the Whatsminer tool.
:param percent: The power percentage to set. Parameters:
:return: A reply informing of the status of setting the percent: The power percentage to set.
power percentage. Returns:
A reply informing of the status of setting the power percentage.
</details>
""" """
if not 0 < percent < 100: if not 0 < percent < 100:
return APIError( raise APIError(
f"Power PCT % is outside of the allowed " f"Power PCT % is outside of the allowed "
f"range. Please set a % between 0 and " f"range. Please set a % between 0 and "
f"100" f"100"
@@ -597,22 +717,29 @@ class BTMinerAPI(BaseMinerAPI):
enc_command = create_privileged_cmd(token_data, command) enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command) return await self.send_command(enc_command)
async def pre_power_on(self, complete: bool, msg: str): async def pre_power_on(self, complete: bool, msg: str) -> dict:
"""Configure or check status of pre power on. """Configure or check status of pre power on.
<details>
<summary>Expand</summary>
Configure or check status of pre power on, only works after Configure or check status of pre power on, only works after
changing the password of the miner using the Whatsminer tool. changing the password of the miner using the Whatsminer tool.
:param complete: check whether pre power on is complete. Parameters:
:param msg: the message to check. complete: check whether pre power on is complete.
"wait for adjust temp" or msg: ## the message to check.
"adjust complete" or * `wait for adjust temp`
"adjust continue" * `adjust complete`
:return: A reply informing of the status of pre power on. * `adjust continue`
Returns:
A reply informing of the status of pre power on.
</details>
""" """
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue": if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
return APIError( raise APIError(
"Message is incorrect, please choose one of " "Message is incorrect, please choose one of "
'["wait for adjust temp", ' '["wait for adjust temp", '
'"adjust complete", ' '"adjust complete", '
@@ -629,77 +756,126 @@ class BTMinerAPI(BaseMinerAPI):
#### END privileged COMMANDS #### #### END privileged COMMANDS ####
async def summary(self): async def summary(self) -> dict:
"""Get the summary status from the miner. """Get the summary status from the miner.
<details>
<summary>Expand</summary>
:return: Summary status of the miner. Returns:
Summary status of the miner.
</details>
""" """
return await self.send_command("summary") return await self.send_command("summary")
async def pools(self): async def pools(self) -> dict:
"""Get the pool status from the miner. """Get the pool status from the miner.
<details>
<summary>Expand</summary>
:return: Pool status of the miner. Returns:
Pool status of the miner.
</details>
""" """
return await self.send_command("pools") return await self.send_command("pools")
async def devs(self): async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details. """Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
return await self.send_command("devs") return await self.send_command("devs")
async def edevs(self): async def edevs(self) -> dict:
"""Get data on each PGA/ASC with their details, ignoring """Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
blacklisted and zombie devices. <details>
<summary>Expand</summary>
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
return await self.send_command("edevs") return await self.send_command("edevs")
async def devdetails(self): async def devdetails(self) -> dict:
"""Get data on all devices with their static details. """Get data on all devices with their static details.
<details>
<summary>Expand</summary>
:return: Data on all devices with their static details. Returns:
Data on all devices with their static details.
</details>
""" """
return await self.send_command("devdetails") return await self.send_command("devdetails")
async def get_psu(self): async def get_psu(self) -> dict:
"""Get data on the PSU and power information. """Get data on the PSU and power information.
<details>
<summary>Expand</summary>
:return: Data on the PSU and power information. Returns:
Data on the PSU and power information.
</details>
""" """
return await self.send_command("get_psu") return await self.send_command("get_psu")
async def version(self): async def version(self) -> dict:
"""Get version data for the miner. """Get version data for the miner. Wraps `self.get_version()`.
<details>
<summary>Expand</summary>
Get version data for the miner. This calls another function, Get version data for the miner. This calls another function,
self.get_version(), but is named version to stay consistent self.get_version(), but is named version to stay consistent
with the other miner APIs. with the other miner APIs.
:return: Version data for the miner. Returns:
Version data for the miner.
</details>
""" """
return await self.get_version() return await self.get_version()
async def get_version(self): async def get_version(self) -> dict:
"""Get version data for the miner. """Get version data for the miner.
<details>
<summary>Expand</summary>
:return: Version data for the miner. Returns:
Version data for the miner.
</details>
""" """
return await self.send_command("get_version") return await self.send_command("get_version")
async def status(self): async def status(self) -> dict:
"""Get BTMiner status and firmware version. """Get BTMiner status and firmware version.
<details>
<summary>Expand</summary>
:return: BTMiner status and firmware version. Returns:
BTMiner status and firmware version.
</details>
""" """
return await self.send_command("status") return await self.send_command("status")
async def get_miner_info(self): async def get_miner_info(self) -> dict:
"""Get general miner info. """Get general miner info.
<details>
<summary>Expand</summary>
:return: General miner info. Returns:
General miner info.
</details>
""" """
return await self.send_command("get_miner_info") return await self.send_command("get_miner_info")

View File

@@ -1,3 +1,17 @@
# 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 from pyasic.API import BaseMinerAPI
@@ -6,8 +20,7 @@ class CGMinerAPI(BaseMinerAPI):
Each method corresponds to an API command in GGMiner. Each method corresponds to an API command in GGMiner.
CGMiner API documentation: [CGMiner API documentation](https://github.com/ckolivas/cgminer/blob/master/API-README)
https://github.com/ckolivas/cgminer/blob/master/API-README
This class abstracts use of the CGMiner API, as well as the This class abstracts use of the CGMiner API, as well as the
methods for sending commands to it. The self.send_command() methods for sending commands to it. The self.send_command()
@@ -15,111 +28,163 @@ class CGMinerAPI(BaseMinerAPI):
as such is the base for many of the functions in this class, which as such is the base for many of the functions in this class, which
rely on it to send the command for them. rely on it to send the command for them.
:param ip: The IP of the miner to reference the API on. Parameters:
:param port: The port to reference the API on. Default is 4028. 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, port=4028): def __init__(self, ip: str, port: int = 4028):
super().__init__(ip, port) super().__init__(ip, port)
async def version(self) -> dict: async def version(self) -> dict:
"""Get miner version info. """Get miner version info.
<details>
<summary>Expand</summary>
:return: Miner version information. Returns:
Miner version information.
</details>
""" """
return await self.send_command("version") return await self.send_command("version")
async def config(self) -> dict: async def config(self) -> dict:
"""Get some basic configuration info. """Get some basic configuration info.
<details>
<summary>Expand</summary>
:return: Some miner configuration information: Returns:
ASC Count <- the number of ASCs ## Some miner configuration information:
PGA Count <- the number of PGAs * ASC Count <- the number of ASCs
Pool Count <- the number of Pools * PGA Count <- the number of PGAs
Strategy <- the current pool strategy * Pool Count <- the number of Pools
Log Interval <- the interval of logging * Strategy <- the current pool strategy
Device Code <- list of compiled device drivers * Log Interval <- the interval of logging
OS <- the current operating system * 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") return await self.send_command("config")
async def summary(self) -> dict: async def summary(self) -> dict:
"""Get the status summary of the miner. """Get the status summary of the miner.
<details>
<summary>Expand</summary>
:return: The status summary of the miner. Returns:
The status summary of the miner.
</details>
""" """
return await self.send_command("summary") return await self.send_command("summary")
async def pools(self) -> dict: async def pools(self) -> dict:
"""Get pool information. """Get pool information.
<details>
<summary>Expand</summary>
:return: Miner pool information. Returns:
Miner pool information.
</details>
""" """
return await self.send_command("pools") return await self.send_command("pools")
async def devs(self) -> dict: async def devs(self) -> dict:
"""Get data on each PGA/ASC with their details. """Get data on each PGA/ASC with their details.
<details>
<summary>Expand</summary>
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
return await self.send_command("devs") return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict: async def edevs(self, old: bool = False) -> dict:
"""Get data on each PGA/ASC with their details, ignoring """Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
blacklisted and zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago old: Include zombie devices that became zombies less than 'old' seconds ago
:return: Data on each PGA/ASC with their details. Returns:
Data on each PGA/ASC with their details.
</details>
""" """
if old: if old:
return await self.send_command("edevs", parameters="old") return await self.send_command("edevs", parameters=old)
else: else:
return await self.send_command("edevs") return await self.send_command("edevs")
async def pga(self, n: int) -> dict: async def pga(self, n: int) -> dict:
"""Get data from PGA n. """Get data from PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA number to get data from. Parameters:
n: The PGA number to get data from.
:return: Data on the PGA n. Returns:
Data on the PGA n.
</details>
""" """
return await self.send_command("pga", parameters=n) return await self.send_command("pga", parameters=n)
async def pgacount(self) -> dict: async def pgacount(self) -> dict:
"""Get data fon all PGAs. """Get data fon all PGAs.
<details>
<summary>Expand</summary>
:return: Data on the PGAs connected. Returns:
Data on the PGAs connected.
</details>
""" """
return await self.send_command("pgacount") return await self.send_command("pgacount")
async def switchpool(self, n: int) -> dict: async def switchpool(self, n: int) -> dict:
"""Switch pools to pool n. """Switch pools to pool n.
<details>
<summary>Expand</summary>
:param n: The pool to switch to. Parameters:
n: The pool to switch to.
:return: A confirmation of switching to pool n. Returns:
A confirmation of switching to pool n.
</details>
""" """
return await self.send_command("switchpool", parameters=n) return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict: async def enablepool(self, n: int) -> dict:
"""Enable pool n. """Enable pool n.
<details>
<summary>Expand</summary>
:param n: The pool to enable. Parameters:
n: The pool to enable.
:return: A confirmation of enabling pool n. Returns:
A confirmation of enabling pool n.
</details>
""" """
return await self.send_command("enablepool", parameters=n) return await self.send_command("enablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict: async def addpool(self, url: str, username: str, password: str) -> dict:
"""Add a pool to the miner. """Add a pool to the miner.
<details>
<summary>Expand</summary>
:param url: The URL of the new pool to add. Parameters:
:param username: The users username on the new pool. url: The URL of the new pool to add.
:param password: The worker password on the new pool. username: The users username on the new pool.
password: The worker password on the new pool.
:return: A confirmation of adding the pool. Returns:
A confirmation of adding the pool.
</details>
""" """
return await self.send_command( return await self.send_command(
"addpool", parameters=f"{url},{username},{password}" "addpool", parameters=f"{url},{username},{password}"
@@ -127,48 +192,73 @@ class CGMinerAPI(BaseMinerAPI):
async def poolpriority(self, *n: int) -> dict: async def poolpriority(self, *n: int) -> dict:
"""Set pool priority. """Set pool priority.
<details>
<summary>Expand</summary>
:param n: Pools in order of priority. Parameters:
*n: Pools in order of priority.
:return: A confirmation of setting pool priority. Returns:
A confirmation of setting pool priority.
</details>
""" """
pools = f"{','.join([str(item) for item in n])}" pools = f"{','.join([str(item) for item in n])}"
return await self.send_command("poolpriority", parameters=pools) return await self.send_command("poolpriority", parameters=pools)
async def poolquota(self, n: int, q: int) -> dict: async def poolquota(self, n: int, q: int) -> dict:
"""Set pool quota. """Set pool quota.
<details>
<summary>Expand</summary>
:param n: Pool number to set quota on. Parameters:
:param q: Quota to set the pool to. n: Pool number to set quota on.
q: Quota to set the pool to.
:return: A confirmation of setting pool quota. Returns:
A confirmation of setting pool quota.
</details>
""" """
return await self.send_command("poolquota", parameters=f"{n},{q}") return await self.send_command("poolquota", parameters=f"{n},{q}")
async def disablepool(self, n: int) -> dict: async def disablepool(self, n: int) -> dict:
"""Disable a pool. """Disable a pool.
<details>
<summary>Expand</summary>
:param n: Pool to disable. Parameters:
n: Pool to disable.
:return: A confirmation of diabling the pool. Returns:
A confirmation of diabling the pool.
</details>
""" """
return await self.send_command("disablepool", parameters=n) return await self.send_command("disablepool", parameters=n)
async def removepool(self, n: int) -> dict: async def removepool(self, n: int) -> dict:
"""Remove a pool. """Remove a pool.
<details>
<summary>Expand</summary>
:param n: Pool to remove. Parameters:
n: Pool to remove.
:return: A confirmation of removing the pool. Returns:
A confirmation of removing the pool.
</details>
""" """
return await self.send_command("removepool", parameters=n) return await self.send_command("removepool", parameters=n)
async def save(self, filename: str = None) -> dict: async def save(self, filename: str = None) -> dict:
"""Save the config. """Save the config.
<details>
<summary>Expand</summary>
:param filename: Filename to save the config as. Parameters:
filename: Filename to save the config as.
:return: A confirmation of saving the config. Returns:
A confirmation of saving the config.
</details>
""" """
if filename: if filename:
return await self.send_command("save", parameters=filename) return await self.send_command("save", parameters=filename)
@@ -177,83 +267,123 @@ class CGMinerAPI(BaseMinerAPI):
async def quit(self) -> dict: async def quit(self) -> dict:
"""Quit CGMiner. """Quit CGMiner.
<details>
<summary>Expand</summary>
:return: A single "BYE" before CGMiner quits. Returns:
A single "BYE" before CGMiner quits.
</details>
""" """
return await self.send_command("quit") return await self.send_command("quit")
async def notify(self) -> dict: async def notify(self) -> dict:
"""Notify the user of past errors. """Notify the user of past errors.
<details>
<summary>Expand</summary>
:return: The last status and count of each devices problem(s). Returns:
The last status and count of each devices problem(s).
</details>
""" """
return await self.send_command("notify") return await self.send_command("notify")
async def privileged(self) -> dict: async def privileged(self) -> dict:
"""Check if you have privileged access. """Check if you have privileged access.
<details>
<summary>Expand</summary>
:return: The STATUS section with an error if you have no Returns:
privileged access, or success if you have privileged access. 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") return await self.send_command("privileged")
async def pgaenable(self, n: int) -> dict: async def pgaenable(self, n: int) -> dict:
"""Enable PGA n. """Enable PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to enable. Parameters:
n: The PGA to enable.
:return: A confirmation of enabling PGA n. Returns:
A confirmation of enabling PGA n.
</details>
""" """
return await self.send_command("pgaenable", parameters=n) return await self.send_command("pgaenable", parameters=n)
async def pgadisable(self, n: int) -> dict: async def pgadisable(self, n: int) -> dict:
"""Disable PGA n. """Disable PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to disable. Parameters:
n: The PGA to disable.
:return: A confirmation of disabling PGA n. Returns:
A confirmation of disabling PGA n.
</details>
""" """
return await self.send_command("pgadisable", parameters=n) return await self.send_command("pgadisable", parameters=n)
async def pgaidentify(self, n: int) -> dict: async def pgaidentify(self, n: int) -> dict:
"""Identify PGA n. """Identify PGA n.
<details>
<summary>Expand</summary>
:param n: The PGA to identify. Parameters:
n: The PGA to identify.
:return: A confirmation of identifying PGA n. Returns:
A confirmation of identifying PGA n.
</details>
""" """
return await self.send_command("pgaidentify", parameters=n) return await self.send_command("pgaidentify", parameters=n)
async def devdetails(self) -> dict: async def devdetails(self) -> dict:
"""Get data on all devices with their static details. """Get data on all devices with their static details.
<details>
<summary>Expand</summary>
:return: Data on all devices with their static details. Returns:
Data on all devices with their static details.
</details>
""" """
return await self.send_command("devdetails") return await self.send_command("devdetails")
async def restart(self) -> dict: async def restart(self) -> dict:
"""Restart CGMiner using the API. """Restart CGMiner using the API.
<details>
<summary>Expand</summary>
:return: A reply informing of the restart. Returns:
A reply informing of the restart.
</details>
""" """
return await self.send_command("restart") return await self.send_command("restart")
async def stats(self) -> dict: async def stats(self) -> dict:
"""Get stats of each device/pool with more than 1 getwork. """Get stats of each device/pool with more than 1 getwork.
<details>
<summary>Expand</summary>
:return: Stats of each device/pool with more than 1 getwork. Returns:
Stats of each device/pool with more than 1 getwork.
</details>
""" """
return await self.send_command("stats") return await self.send_command("stats")
async def estats(self, old: bool = False) -> dict: async def estats(self, old: bool = False) -> dict:
"""Get stats of each device/pool with more than 1 getwork, """Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
ignoring zombie devices. <details>
<summary>Expand</summary>
:param old: Include zombie devices that became zombies less Parameters:
than 'old' seconds ago. old: Include zombie devices that became zombies less than 'old' seconds ago.
:return: Stats of each device/pool with more than 1 getwork, Returns:
ignoring zombie devices. Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
</details>
""" """
if old: if old:
return await self.send_command("estats", parameters=old) return await self.send_command("estats", parameters=old)
@@ -262,92 +392,126 @@ class CGMinerAPI(BaseMinerAPI):
async def check(self, command: str) -> dict: async def check(self, command: str) -> dict:
"""Check if the command command exists in CGMiner. """Check if the command command exists in CGMiner.
<details>
<summary>Expand</summary>
:param command: The command to check. Parameters:
command: The command to check.
:return: Information about a command: Returns:
Exists (Y/N) <- the command exists in this version ## Information about a command:
Access (Y/N) <- you have access to use the 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) return await self.send_command("check", parameters=command)
async def failover_only(self, failover: bool) -> dict: async def failover_only(self, failover: bool) -> dict:
"""Set failover-only. """Set failover-only.
<details>
<summary>Expand</summary>
Parameters:
failover: What to set failover-only to.
:param failover: What to set failover-only to. Returns:
Confirmation of setting failover-only.
:return: Confirmation of setting failover-only. </details>
""" """
return await self.send_command("failover-only", parameters=failover) return await self.send_command("failover-only", parameters=failover)
async def coin(self) -> dict: async def coin(self) -> dict:
"""Get information on the current coin. """Get information on the current coin.
<details>
<summary>Expand</summary>
:return: Information about the current coin being mined: Returns:
Hash Method <- the hashing algorithm ## Information about the current coin being mined:
Current Block Time <- blocktime as a float, 0 means none * Hash Method <- the hashing algorithm
Current Block Hash <- the hash of the current block, blank * Current Block Time <- blocktime as a float, 0 means none
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 * LP <- whether LP is in use on at least 1 pool
Network Difficulty: the current network difficulty * Network Difficulty: the current network difficulty
</details>
""" """
return await self.send_command("coin") return await self.send_command("coin")
async def debug(self, setting: str) -> dict: async def debug(self, setting: str) -> dict:
"""Set a debug setting. """Set a debug setting.
<details>
<summary>Expand</summary>
:param setting: Which setting to switch to. Options are: Parameters:
Silent, setting: Which setting to switch to.
Quiet, ## Options are:
Verbose, * Silent
Debug, * Quiet
RPCProto, * Verbose
PerDevice, * Debug
WorkTime, * RPCProto
Normal. * PerDevice
* WorkTime
* Normal
:return: Data on which debug setting was enabled or disabled. Returns:
Data on which debug setting was enabled or disabled.
</details>
""" """
return await self.send_command("debug", parameters=setting) return await self.send_command("debug", parameters=setting)
async def setconfig(self, name: str, n: int) -> dict: async def setconfig(self, name: str, n: int) -> dict:
"""Set config of name to value n. """Set config of name to value n.
<details>
<summary>Expand</summary>
:param name: The name of the config setting to set. Options are: Parameters:
queue, name: The name of the config setting to set.
scantime, ## Options are:
expiry. * queue
:param n: The value to set the 'name' setting to. * scantime
* expiry
n: The value to set the 'name' setting to.
:return: The results of setting config of name to n. Returns:
The results of setting config of name to n.
</details>
""" """
return await self.send_command("setconfig", parameters=f"{name},{n}") return await self.send_command("setconfig", parameters=f"{name},{n}")
async def usbstats(self) -> dict: async def usbstats(self) -> dict:
"""Get stats of all USB devices except ztex. """Get stats of all USB devices except ztex.
<details>
<summary>Expand</summary>
:return: The stats of all USB devices except ztex. Returns:
The stats of all USB devices except ztex.
</details>
""" """
return await self.send_command("usbstats") return await self.send_command("usbstats")
async def pgaset(self, n: int, opt: str, val: int = None) -> dict: async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
"""Set PGA option opt to val on PGA n. """Set PGA option opt to val on PGA n.
<details>
<summary>Expand</summary>
Options: Options:
```
MMQ - MMQ -
opt: clock opt: clock
val: 160 - 230 (multiple of 2) val: 160 - 230 (multiple of 2)
CMR - CMR -
opt: clock opt: clock
val: 100 - 220 val: 100 - 220
```
:param n: The PGA to set the options on. Parameters:
:param opt: The option to set. Setting this to 'help' n: The PGA to set the options on.
returns a help message. opt: The option to set. Setting this to 'help' returns a help message.
:param val: The value to set the option to. val: The value to set the option to.
:return: Confirmation of setting PGA n with opt[,val]. Returns:
Confirmation of setting PGA n with opt[,val].
</details>
""" """
if val: if val:
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}") return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
@@ -356,75 +520,108 @@ class CGMinerAPI(BaseMinerAPI):
async def zero(self, which: str, summary: bool) -> dict: async def zero(self, which: str, summary: bool) -> dict:
"""Zero a device. """Zero a device.
<details>
<summary>Expand</summary>
:param which: Which device to zero. Parameters:
Setting this to 'all' zeros all devices. 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.
Setting this to 'bestshare' zeros only the bestshare values summary: Whether or not to show a full summary.
for each pool and global.
:param summary: Whether or not to show a full summary.
:return: the STATUS section with info on the zero and optional Returns:
summary. the STATUS section with info on the zero and optional summary.
</details>
""" """
return await self.send_command("zero", parameters=f"{which},{summary}") return await self.send_command("zero", parameters=f"{which},{summary}")
async def hotplug(self, n: int) -> dict: async def hotplug(self, n: int) -> dict:
"""Enable hotplug. """Enable hotplug.
<details>
<summary>Expand</summary>
:param n: The device number to set hotplug on. Parameters:
n: The device number to set hotplug on.
:return: Information on hotplug status. Returns:
Information on hotplug status.
</details>
""" """
return await self.send_command("hotplug", parameters=n) return await self.send_command("hotplug", parameters=n)
async def asc(self, n: int) -> dict: async def asc(self, n: int) -> dict:
"""Get data for ASC device n. """Get data for ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to get data for. Parameters:
n: The device to get data for.
:return: The data for ASC device n. Returns:
The data for ASC device n.
</details>
""" """
return await self.send_command("asc", parameters=n) return await self.send_command("asc", parameters=n)
async def ascenable(self, n: int) -> dict: async def ascenable(self, n: int) -> dict:
"""Enable ASC device n. """Enable ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to enable. Parameters:
n: The device to enable.
:return: Confirmation of enabling ASC device n. Returns:
Confirmation of enabling ASC device n.
</details>
""" """
return await self.send_command("ascenable", parameters=n) return await self.send_command("ascenable", parameters=n)
async def ascdisable(self, n: int) -> dict: async def ascdisable(self, n: int) -> dict:
"""Disable ASC device n. """Disable ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to disable. Parameters:
n: The device to disable.
:return: Confirmation of disabling ASC device n. Returns:
Confirmation of disabling ASC device n.
</details>
""" """
return await self.send_command("ascdisable", parameters=n) return await self.send_command("ascdisable", parameters=n)
async def ascidentify(self, n: int) -> dict: async def ascidentify(self, n: int) -> dict:
"""Identify ASC device n. """Identify ASC device n.
<details>
<summary>Expand</summary>
:param n: The device to identify. Parameters:
n: The device to identify.
:return: Confirmation of identifying ASC device n. Returns:
Confirmation of identifying ASC device n.
</details>
""" """
return await self.send_command("ascidentify", parameters=n) return await self.send_command("ascidentify", parameters=n)
async def asccount(self) -> dict: async def asccount(self) -> dict:
"""Get data on the number of ASC devices and their info. """Get data on the number of ASC devices and their info.
<details>
<summary>Expand</summary>
:return: Data on all ASC devices. Returns:
Data on all ASC devices.
</details>
""" """
return await self.send_command("asccount") return await self.send_command("asccount")
async def ascset(self, n: int, opt: str, val: int = None) -> dict: async def ascset(self, n: int, opt: str, val: int = None) -> dict:
"""Set ASC n option opt to value val. """Set ASC n option opt to value val.
<details>
<summary>Expand</summary>
Sets an option on the ASC n to a value. Allowed options are: Sets an option on the ASC n to a value. Allowed options are:
```
AVA+BTB - AVA+BTB -
opt: freq opt: freq
val: 256 - 1024 (chip frequency) val: 256 - 1024 (chip frequency)
@@ -458,14 +655,16 @@ class CGMinerAPI(BaseMinerAPI):
opt: clock opt: clock
val: 0 - 15 val: 0 - 15
```
Parameters:
n: The ASC to set the options on.
opt: The option to set. Setting this to 'help' returns a help message.
val: The value to set the option to.
:param n: The ASC to set the options on. Returns:
:param opt: The option to set. Setting this to 'help' returns a Confirmation of setting option opt to value val.
help message. </details>
:param val: The value to set the option to.
:return: Confirmation of setting option opt to value val.
""" """
if val: if val:
return await self.send_command("ascset", parameters=f"{n},{opt},{val}") return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
@@ -474,14 +673,22 @@ class CGMinerAPI(BaseMinerAPI):
async def lcd(self) -> dict: async def lcd(self) -> dict:
"""Get a general all-in-one status summary of the miner. """Get a general all-in-one status summary of the miner.
<details>
<summary>Expand</summary>
:return: An all-in-one status summary of the miner. Returns:
An all-in-one status summary of the miner.
</details>
""" """
return await self.send_command("lcd") return await self.send_command("lcd")
async def lockstats(self) -> dict: async def lockstats(self) -> dict:
"""Write lockstats to STDERR. """Write lockstats to STDERR.
<details>
<summary>Expand</summary>
:return: The result of writing the lock stats to STDERR. Returns:
The result of writing the lock stats to STDERR.
</details>
""" """
return await self.send_command("lockstats") return await self.send_command("lockstats")

View File

@@ -1,3 +1,17 @@
# 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 from pyasic.API import BaseMinerAPI

View File

@@ -0,0 +1,13 @@
# 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.

View File

@@ -1,5 +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 dataclasses import dataclass, asdict from dataclasses import dataclass, asdict
from typing import List, Literal from typing import Literal, List
import random import random
import string import string
@@ -13,9 +27,10 @@ import time
class _Pool: class _Pool:
"""A dataclass for pool information. """A dataclass for pool information.
:param url: URL of the pool. Attributes:
:param username: Username on the pool. url: URL of the pool.
:param password: Worker password on the pool. username: Username on the pool.
password: Worker password on the pool.
""" """
url: str = "" url: str = ""
@@ -25,7 +40,8 @@ class _Pool:
def from_dict(self, data: dict): def from_dict(self, data: dict):
"""Convert raw pool data as a dict to usable data and save it to this class. """Convert raw pool data as a dict to usable data and save it to this class.
:param data: The raw config data to convert. Parameters:
data: The raw config data to convert.
""" """
for key in data.keys(): for key in data.keys():
if key == "url": if key == "url":
@@ -36,10 +52,11 @@ class _Pool:
self.password = data[key] self.password = data[key]
return self return self
def as_x19(self, user_suffix: str = None): def as_wm(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an X19 device. """Convert the data in this class to a dict usable by an Whatsminer device.
:param user_suffix: The suffix to append to username. Parameters:
user_suffix: The suffix to append to username.
""" """
username = self.username username = self.username
if user_suffix: if user_suffix:
@@ -48,7 +65,42 @@ class _Pool:
pool = {"url": self.url, "user": username, "pass": self.password} pool = {"url": self.url, "user": username, "pass": self.password}
return pool return pool
def as_avalon(self, user_suffix: str = None): def as_x19(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an X19 device.
Parameters:
user_suffix: The suffix to append to username.
"""
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "pass": self.password}
return pool
def as_inno(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an Innosilicon device.
Parameters:
user_suffix: The suffix to append to username.
"""
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {
f"Pool": self.url,
f"UserName": username,
f"Password": self.password,
}
return pool
def as_avalon(self, user_suffix: str = None) -> str:
"""Convert the data in this class to a string usable by an Avalonminer device.
Parameters:
user_suffix: The suffix to append to username.
"""
username = self.username username = self.username
if user_suffix: if user_suffix:
username = f"{username}{user_suffix}" username = f"{username}{user_suffix}"
@@ -56,10 +108,11 @@ class _Pool:
pool = ",".join([self.url, username, self.password]) pool = ",".join([self.url, username, self.password])
return pool return pool
def as_bos(self, user_suffix: str = None): def as_bos(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an BOSMiner device. """Convert the data in this class to a dict usable by an BOSMiner device.
:param user_suffix: The suffix to append to username. Parameters:
user_suffix: The suffix to append to username.
""" """
username = self.username username = self.username
if user_suffix: if user_suffix:
@@ -73,9 +126,10 @@ class _Pool:
class _PoolGroup: class _PoolGroup:
"""A dataclass for pool group information. """A dataclass for pool group information.
:param quota: The group quota. Attributes:
:param group_name: The name of the pool group. quota: The group quota.
:param pools: A list of pools in this group. group_name: The name of the pool group.
pools: A list of pools in this group.
""" """
quota: int = 1 quota: int = 1
@@ -91,7 +145,8 @@ class _PoolGroup:
def from_dict(self, data: dict): def from_dict(self, data: dict):
"""Convert raw pool group data as a dict to usable data and save it to this class. """Convert raw pool group data as a dict to usable data and save it to this class.
:param data: The raw config data to convert. Parameters:
data: The raw config data to convert.
""" """
pools = [] pools = []
for key in data.keys(): for key in data.keys():
@@ -105,24 +160,57 @@ class _PoolGroup:
self.pools = pools self.pools = pools
return self return self
def as_x19(self, user_suffix: str = None): def as_x19(self, user_suffix: str = None) -> List[dict]:
"""Convert the data in this class to a dict usable by an X19 device. """Convert the data in this class to a list usable by an X19 device.
:param user_suffix: The suffix to append to username. Parameters:
user_suffix: The suffix to append to username.
""" """
pools = [] pools = []
for pool in self.pools[:3]: for pool in self.pools[:3]:
pools.append(pool.as_x19(user_suffix=user_suffix)) pools.append(pool.as_x19(user_suffix=user_suffix))
return pools return pools
def as_avalon(self, user_suffix: str = None): def as_inno(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a list usable by an Innosilicon device.
Parameters:
user_suffix: The suffix to append to username.
"""
pools = {}
for idx, pool in enumerate(self.pools[:3]):
pool_data = pool.as_inno(user_suffix=user_suffix)
for key in pool_data:
pools[f"{key}{idx+1}"] = pool_data[key]
return pools
def as_wm(self, user_suffix: str = None) -> List[dict]:
"""Convert the data in this class to a list usable by an Whatsminer device.
Parameters:
user_suffix: The suffix to append to username.
"""
pools = []
for pool in self.pools[:3]:
pools.append(pool.as_wm(user_suffix=user_suffix))
while len(pools) < 3:
pools.append({"url": None, "user": None, "pass": None})
return pools
def as_avalon(self, user_suffix: str = None) -> str:
"""Convert the data in this class to a dict usable by an Avalonminer device.
Parameters:
user_suffix: The suffix to append to username.
"""
pool = self.pools[0].as_avalon(user_suffix=user_suffix) pool = self.pools[0].as_avalon(user_suffix=user_suffix)
return pool return pool
def as_bos(self, user_suffix: str = None): def as_bos(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a dict usable by an BOSMiner device. """Convert the data in this class to a dict usable by an BOSMiner device.
:param user_suffix: The suffix to append to username. Parameters:
user_suffix: The suffix to append to username.
""" """
group = { group = {
"name": self.group_name, "name": self.group_name,
@@ -136,21 +224,22 @@ class _PoolGroup:
class MinerConfig: class MinerConfig:
"""A dataclass for miner configuration information. """A dataclass for miner configuration information.
:param pool_groups: A list of pool groups in this config. Attributes:
:param temp_mode: The temperature control mode. pool_groups: A list of pool groups in this config.
:param temp_target: The target temp. temp_mode: The temperature control mode.
:param temp_hot: The hot temp (100% fans). temp_target: The target temp.
:param temp_dangerous: The dangerous temp (shutdown). temp_hot: The hot temp (100% fans).
:param minimum_fans: The minimum numbers of fans to run the miner. temp_dangerous: The dangerous temp (shutdown).
:param fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual"). minimum_fans: The minimum numbers of fans to run the miner.
:param asicboost: Whether or not to enable asicboost. fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
:param autotuning_enabled: Whether or not to enable autotuning. asicboost: Whether or not to enable asicboost.
:param autotuning_wattage: The wattage to use when autotuning. autotuning_enabled: Whether or not to enable autotuning.
:param dps_enabled: Whether or not to enable dynamic power scaling. autotuning_wattage: The wattage to use when autotuning.
:param dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp. dps_enabled: Whether or not to enable dynamic power scaling.
:param dps_min_power: The minimum power to reduce autotuning to. dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
:param dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached. dps_min_power: The minimum power to reduce autotuning to.
:param dps_shutdown_duration: The amount of time to shutdown for (in hours). dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
dps_shutdown_duration: The amount of time to shutdown for (in hours).
""" """
pool_groups: List[_PoolGroup] = None pool_groups: List[_PoolGroup] = None
@@ -174,27 +263,28 @@ class MinerConfig:
dps_shutdown_enabled: bool = None dps_shutdown_enabled: bool = None
dps_shutdown_duration: float = None dps_shutdown_duration: float = None
def as_dict(self): def as_dict(self) -> dict:
"""Convert the data in this class to a dict.""" """Convert the data in this class to a dict."""
data_dict = asdict(self) data_dict = asdict(self)
for key in asdict(self).keys(): for key in asdict(self).keys():
if data_dict[key] is None: if data_dict[key] is None:
del data_dict[key] del data_dict[key]
return data_dict return data_dict
def as_toml(self): def as_toml(self) -> str:
"""Convert the data in this class to toml.""" """Convert the data in this class to toml."""
return toml.dumps(self.as_dict()) return toml.dumps(self.as_dict())
def as_yaml(self): def as_yaml(self) -> str:
"""Convert the data in this class to yaml.""" """Convert the data in this class to yaml."""
return yaml.dump(self.as_dict(), sort_keys=False) return yaml.dump(self.as_dict(), sort_keys=False)
def from_raw(self, data: dict): def from_raw(self, data: dict):
"""Convert raw config data as a dict to usable data and save it to this class. """Convert raw config data as a dict to usable data and save it to this class.
This should be able to handle any raw config file from any miner supported by pyasic.
:param data: The raw config data to convert. Parameters:
data: The raw config data to convert.
""" """
pool_groups = [] pool_groups = []
for key in data.keys(): for key in data.keys():
@@ -253,10 +343,20 @@ class MinerConfig:
self.pool_groups = pool_groups self.pool_groups = pool_groups
return self return self
def from_api(self, pools: list):
_pools = []
for pool in pools:
url = pool.get("URL")
user = pool.get("User")
_pools.append({"url": url, "user": user, "pass": "123"})
self.pool_groups = [_PoolGroup().from_dict({"pools": _pools})]
return self
def from_dict(self, data: dict): def from_dict(self, data: dict):
"""Convert an output dict of this class back into usable data and save it to this class. """Convert an output dict of this class back into usable data and save it to this class.
:param data: The raw config data to convert. Parameters:
data: The dict config data to convert.
""" """
pool_groups = [] pool_groups = []
for group in data["pool_groups"]: for group in data["pool_groups"]:
@@ -270,21 +370,40 @@ class MinerConfig:
def from_toml(self, data: str): def from_toml(self, data: str):
"""Convert output toml of this class back into usable data and save it to this class. """Convert output toml of this class back into usable data and save it to this class.
:param data: The raw config data to convert. Parameters:
data: The toml config data to convert.
""" """
return self.from_dict(toml.loads(data)) return self.from_dict(toml.loads(data))
def from_yaml(self, data: str): def from_yaml(self, data: str):
"""Convert output yaml of this class back into usable data and save it to this class. """Convert output yaml of this class back into usable data and save it to this class.
:param data: The raw config data to convert. Parameters:
data: The yaml config data to convert.
""" """
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader)) return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
def as_wm(self, user_suffix: str = None) -> List[dict]:
"""Convert the data in this class to a config usable by an Whatsminer device.
Parameters:
user_suffix: The suffix to append to username.
"""
return self.pool_groups[0].as_wm(user_suffix=user_suffix)
def as_inno(self, user_suffix: str = None) -> dict:
"""Convert the data in this class to a config usable by an Innosilicon device.
Parameters:
user_suffix: The suffix to append to username.
"""
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
def as_x19(self, user_suffix: str = None) -> str: def as_x19(self, user_suffix: str = None) -> str:
"""Convert the data in this class to a config usable by an X19 device. """Convert the data in this class to a config usable by an X19 device.
:param user_suffix: The suffix to append to username. Parameters:
user_suffix: The suffix to append to username.
""" """
cfg = { cfg = {
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix), "pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
@@ -301,14 +420,20 @@ class MinerConfig:
return json.dumps(cfg) return json.dumps(cfg)
def as_avalon(self, user_suffix: str = None) -> str: def as_avalon(self, user_suffix: str = None) -> str:
cfg = self.pool_groups[0].as_avalon() """Convert the data in this class to a config usable by an Avalonminer device.
Parameters:
user_suffix: The suffix to append to username.
"""
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
return cfg return cfg
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str: def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
"""Convert the data in this class to a config usable by an BOSMiner device. """Convert the data in this class to a config usable by an BOSMiner device.
:param model: The model of the miner to be used in the format portion of the config. Parameters:
:param user_suffix: The suffix to append to username. model: The model of the miner to be used in the format portion of the config.
user_suffix: The suffix to append to username.
""" """
cfg = { cfg = {
"format": { "format": {

View File

@@ -1,37 +1,68 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from typing import Union, List
from dataclasses import dataclass, field, asdict from dataclasses import dataclass, field, asdict
from datetime import datetime from datetime import datetime, timezone
import time
import json
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError
@dataclass @dataclass
class MinerData: class MinerData:
"""A Dataclass to standardize data returned from miners (specifically AnyMiner().get_data()) """A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
:param ip: The IP of the miner as a str. Attributes:
:param datetime: The time and date this data was generated. ip: The IP of the miner as a str.
:param model: The model of the miner as a str. datetime: The time and date this data was generated.
:param hostname: The network hostname of the miner as a str. model: The model of the miner as a str.
:param hashrate: The hashrate of the miner in TH/s as a int. hostname: The network hostname of the miner as a str.
:param left_board_temp: The temp of the left PCB as an int. hashrate: The hashrate of the miner in TH/s as a float.
:param left_board_chip_temp: The temp of the left board chips as an int. left_board_hashrate: The hashrate of the left board of the miner in TH/s as a float.
:param center_board_temp: The temp of the center PCB as an int. center_board_hashrate: The hashrate of the center board of the miner in TH/s as a float.
:param center_board_chip_temp: The temp of the center board chips as an int. right_board_hashrate: The hashrate of the right board of the miner in TH/s as a float.
:param right_board_temp: The temp of the right PCB as an int. temperature_avg: The average temperature across the boards. Calculated automatically.
:param right_board_chip_temp: The temp of the right board chips as an int. env_temp: The environment temps as a float.
:param wattage: Current power draw of the miner as an int. left_board_temp: The temp of the left PCB as an int.
:param wattage_limit: Power limit of the miner as an int. left_board_chip_temp: The temp of the left board chips as an int.
:param fan_1: The speed of the first fan as an int. center_board_temp: The temp of the center PCB as an int.
:param fan_2: The speed of the second fan as an int. center_board_chip_temp: The temp of the center board chips as an int.
:param fan_3: The speed of the third fan as an int. right_board_temp: The temp of the right PCB as an int.
:param fan_4: The speed of the fourth fan as an int. right_board_chip_temp: The temp of the right board chips as an int.
:param left_chips: The number of chips online in the left board as an int. wattage: Current power draw of the miner as an int.
:param center_chips: The number of chips online in the left board as an int. wattage_limit: Power limit of the miner as an int.
:param right_chips: The number of chips online in the left board as an int. fan_1: The speed of the first fan as an int.
:param ideal_chips: The ideal number of chips in the miner as an int. fan_2: The speed of the second fan as an int.
:param pool_split: The pool split as a str. fan_3: The speed of the third fan as an int.
:param pool_1_url: The first pool url on the miner as a str. fan_4: The speed of the fourth fan as an int.
:param pool_1_user: The first pool user on the miner as a str. left_chips: The number of chips online in the left board as an int.
:param pool_2_url: The second pool url on the miner as a str. center_chips: The number of chips online in the left board as an int.
:param pool_2_user: The second pool user on the miner as a str. right_chips: The number of chips online in the left board as an int.
total_chips: The total number of chips on all boards. Calculated automatically.
ideal_chips: The ideal number of chips in the miner as an int.
percent_ideal: The percent of total chips out of the ideal count. Calculated automatically.
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 or not the fault light is on as a boolean.
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
""" """
ip: str ip: str
@@ -40,8 +71,11 @@ class MinerData:
model: str = "Unknown" model: str = "Unknown"
hostname: str = "Unknown" hostname: str = "Unknown"
hashrate: float = 0 hashrate: float = 0
left_board_hashrate: float = 0.0
center_board_hashrate: float = 0.0
right_board_hashrate: float = 0.0
temperature_avg: int = field(init=False) temperature_avg: int = field(init=False)
env_temp: float = 0 env_temp: float = 0.0
left_board_temp: int = 0 left_board_temp: int = 0
left_board_chip_temp: int = 0 left_board_chip_temp: int = 0
center_board_temp: int = 0 center_board_temp: int = 0
@@ -66,9 +100,26 @@ class MinerData:
pool_1_user: str = "Unknown" pool_1_user: str = "Unknown"
pool_2_url: str = "" pool_2_url: str = ""
pool_2_user: str = "" pool_2_user: str = ""
errors: List[
Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]
] = field(default_factory=list)
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
def __post_init__(self): 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()])
@property @property
def total_chips(self): # noqa - Skip PyCharm inspection def total_chips(self): # noqa - Skip PyCharm inspection
@@ -114,5 +165,55 @@ class MinerData:
def temperature_avg(self, val): def temperature_avg(self, val):
pass pass
@property
def efficiency(self): # noqa - Skip PyCharm inspection
if self.hashrate == 0:
return 0
return round(self.wattage / self.hashrate)
@efficiency.setter
def efficiency(self, val):
pass
def asdict(self): def asdict(self):
return asdict(self) return asdict(self)
def as_json(self):
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data)
def as_influxdb(self, measurement_name: str = "miner_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
if isinstance(self[attribute], str):
field_data.append(f'{attribute}="{self[attribute]}"')
continue
if isinstance(self[attribute], bool):
field_data.append(f"{attribute}={str(self[attribute]).lower()}")
continue
if isinstance(self[attribute], int):
field_data.append(f"{attribute}={self[attribute]}")
continue
if isinstance(self[attribute], float):
field_data.append(f"{attribute}={self[attribute]}")
continue
if attribute == "fault_light" and not self[attribute]:
field_data.append(f"{attribute}=false")
continue
if attribute == "errors":
for idx, item in enumerate(self[attribute]):
field_data.append(f'error_{idx+1}="{item.error_message}"')
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])

View File

@@ -0,0 +1,29 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass, asdict
@dataclass
class X19Error:
"""A Dataclass to handle error codes of X19 miners.
Attributes:
error_message: The error message as a string.
"""
error_message: str
def asdict(self):
return asdict(self)

View File

@@ -0,0 +1,18 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .whatsminer import WhatsminerError
from .bos import BraiinsOSError
from .X19 import X19Error
from .innosilicon import InnosiliconError

View File

@@ -0,0 +1,29 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass, asdict
@dataclass
class BraiinsOSError:
"""A Dataclass to handle error codes of BraiinsOS+ miners.
Attributes:
error_message: The error message as a string.
"""
error_message: str
def asdict(self):
return asdict(self)

View File

@@ -0,0 +1,65 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass, field, asdict
@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)
@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.",
}

View File

@@ -0,0 +1,172 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass, field, asdict
@dataclass
class WhatsminerError:
"""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)
@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 = {
110: "Intake fan speed error.",
111: "Exhaust fan speed error.",
120: "Intake fan speed error. Fan speed deviates by more than 2000.",
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
130: "Intake fan speed error. Fan speed deviates by more than 3000.",
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
140: "Fan speed too high.",
200: "Power probing error. No power found.",
201: "Power supply and configuration file don't match.",
202: "Power output voltage error.",
203: "Power protecting due to high environment temperature.",
204: "Power current protecting due to high environment temperature.",
205: "Power current error.",
206: "Power input low voltage error.",
207: "Power input current protecting due to bad power input.",
210: "Power error.",
213: "Power input voltage and current do not match power output.",
216: "Power remained unchanged for a long time.",
217: "Power set enable error.",
218: "Power input voltage is lower than 230V for high power mode.",
233: "Power output high temperature protection error.",
234: "Power output high temperature protection error.",
235: "Power output high temperature protection error.",
236: "Power output high current protection error.",
237: "Power output high current protection error.",
238: "Power output high current protection error.",
239: "Power output high voltage protection error.",
240: "Power output low voltage protection error.",
241: "Power output current imbalance error.",
243: "Power input high temperature protection error.",
244: "Power input high temperature protection error.",
245: "Power input high temperature protection error.",
246: "Power input high voltage protection error.",
247: "Power input high voltage protection error.",
248: "Power input high current protection error.",
249: "Power input high current protection error.",
250: "Power input low voltage protection error.",
251: "Power input low voltage protection error.",
253: "Power supply fan error.",
254: "Power supply fan error.",
255: "Power output high power protection error.",
256: "Power output high power protection error.",
257: "Input over current protection of power supply on primary side.",
263: "Power communication warning.",
264: "Power communication error.",
267: "Power watchdog protection.",
268: "Power output high current protection.",
269: "Power input high current protection.",
270: "Power input high voltage protection.",
271: "Power input low voltage protection.",
272: "Excessive power supply output warning.",
273: "Power input too high warning.",
274: "Power fan warning.",
275: "Power high temperature warning.",
300: "Right board temperature sensor detection error.",
301: "Center board temperature sensor detection error.",
302: "Left board temperature sensor detection error.",
320: "Right board temperature reading error.",
321: "Center board temperature reading error.",
322: "Left board temperature reading error.",
329: "Control board temperature sensor communication error.",
350: "Right board temperature protecting.",
351: "Center board temperature protecting.",
352: "Left board temperature protecting.",
360: "Hashboard high temperature error.",
410: "Right board eeprom detection error.",
411: "Center board eeprom detection error.",
412: "Left board eeprom detection error.",
420: "Right board eeprom parsing error.",
421: "Center board eeprom parsing error.",
422: "Left board eeprom parsing error.",
430: "Right board chip bin type error.",
431: "Center board chip bin type error.",
432: "Left board chip bin type error.",
440: "Right board eeprom chip number X error.",
441: "Center board eeprom chip number X error.",
442: "Left board eeprom chip number X error.",
450: "Right board eeprom xfer error.",
451: "Center board eeprom xfer error.",
452: "Left board eeprom xfer error.",
510: "Right board miner type error.",
511: "Center board miner type error.",
512: "Left board miner type error.",
520: "Right board bin type error.",
521: "Center board bin type error.",
522: "Left board bin type error.",
530: "Right board not found.",
531: "Center board not found.",
532: "Left board not found.",
540: "Right board error reading chip id.",
541: "Center board error reading chip id.",
542: "Left board error reading chip id.",
550: "Right board has bad chips.",
551: "Center board has bad chips.",
552: "Left board has bad chips.",
560: "Right board loss of balance error.",
561: "Center board loss of balance error.",
562: "Left board loss of balance error.",
600: "Environment temperature is too high.",
610: "Environment temperature is too high for high performance mode.",
701: "Control board no support chip.",
710: "Control board rebooted as an exception.",
712: "Control board rebooted as an exception.",
800: "CGMiner checksum error.",
801: "System monitor checksum error.",
802: "Remote daemon checksum error.",
2010: "All pools are disabled.",
2020: "Pool 0 connection failed.",
2021: "Pool 1 connection failed.",
2022: "Pool 2 connection failed.",
2023: "Pool 3 connection failed.",
2030: "High rejection rate on pool.",
2040: "The pool does not support asicboost mode.",
2310: "Hashrate is too low.",
2320: "Hashrate is too low.",
2340: "Hashrate loss is too high.",
2350: "Hashrate loss is too high.",
5070: "Right hashboard water velocity is abnormal.",
5071: "Center hashboard water velocity is abnormal.",
5072: "Left hashboard water velocity is abnormal.",
5110: "Right hashboard frequency up timeout.",
5111: "Center hashboard frequency up timeout.",
5112: "Left hashboard frequency up timeout.",
8410: "Software version error.",
100001: "/antiv/signature illegal.",
100002: "/antiv/dig/init.d illegal.",
100003: "/antiv/dig/pf_partial.dig illegal.",
}

View File

@@ -1,9 +1,23 @@
# 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 logging
from pyasic.settings import DEBUG, LOGFILE from pyasic.settings import PyasicSettings
def init_logger(): def init_logger():
if LOGFILE: if PyasicSettings().logfile:
logging.basicConfig( logging.basicConfig(
filename="logfile.txt", filename="logfile.txt",
filemode="a", filemode="a",
@@ -18,7 +32,7 @@ def init_logger():
_logger = logging.getLogger() _logger = logging.getLogger()
if DEBUG: if PyasicSettings().debug:
_logger.setLevel(logging.DEBUG) _logger.setLevel(logging.DEBUG)
logging.getLogger("asyncssh").setLevel(logging.DEBUG) logging.getLogger("asyncssh").setLevel(logging.DEBUG)
else: else:

View File

@@ -1,104 +1,23 @@
import asyncssh # Copyright 2022 Upstream Data Inc
import logging #
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ipaddress import ipaddress
from typing import Union
from pyasic.data import MinerData from pyasic.miners.base import BaseMiner, AnyMiner
from pyasic.miners.miner_factory import MinerFactory
# abstracted version of get miner that is easier to access
class BaseMiner: async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
def __init__(self, *args) -> None: return await MinerFactory().get_miner(ip)
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))

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .bmminer import BMMiner from .bmminer import BMMiner
from .bosminer import BOSMiner from .bosminer import BOSMiner
from .btminer import BTMiner from .btminer import BTMiner

View File

@@ -1,16 +1,34 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ipaddress import ipaddress
import logging import logging
from typing import Union
from pyasic.API.bmminer import BMMinerAPI from pyasic.API.bmminer import BMMinerAPI
from pyasic.miners import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.data import MinerData from pyasic.data import MinerData
from pyasic.config import MinerConfig
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES from pyasic.settings import PyasicSettings
class BMMiner(BaseMiner): class BMMiner(BaseMiner):
"""Base handler for BMMiner based miners."""
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ipaddress.ip_address(ip) self.ip = ipaddress.ip_address(ip)
@@ -19,10 +37,11 @@ class BMMiner(BaseMiner):
self.uname = "root" self.uname = "root"
self.pwd = "admin" self.pwd = "admin"
async def get_model(self) -> str or None: async def get_model(self) -> Union[str, None]:
"""Get miner model. """Get miner model.
:return: Miner model or None. Returns:
Miner model or None.
""" """
# check if model is cached # check if model is cached
if self.model: if self.model:
@@ -46,7 +65,8 @@ class BMMiner(BaseMiner):
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
"""Get miner hostname. """Get miner hostname.
:return: The hostname of the miner as a string or "?" Returns:
The hostname of the miner as a string or "?"
""" """
if self.hostname: if self.hostname:
return self.hostname return self.hostname
@@ -72,12 +92,14 @@ class BMMiner(BaseMiner):
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return "?" return "?"
async def send_ssh_command(self, cmd: str) -> str or None: async def send_ssh_command(self, cmd: str) -> Union[str, None]:
"""Send a command to the miner over ssh. """Send a command to the miner over ssh.
:param cmd: The command to run. Parameters:
cmd: The command to run.
:return: Result of the command or None. Returns:
Result of the command or None.
""" """
result = None result = None
@@ -101,10 +123,11 @@ class BMMiner(BaseMiner):
# return the result, either command output or None # return the result, either command output or None
return result return result
async def get_config(self) -> list or None: async def get_config(self) -> Union[list, None]:
"""Get the pool configuration of the miner. """Get the pool configuration of the miner.
:return: Pool config data or None. Returns:
Pool config data or None.
""" """
# get pool data # get pool data
pools = await self.api.pools() pools = await self.api.pools()
@@ -120,6 +143,11 @@ class BMMiner(BaseMiner):
return pool_data return pool_data
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboot the miner.
Returns:
The result of rebooting the miner.
"""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
_ret = await self.send_ssh_command("reboot") _ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
@@ -127,7 +155,35 @@ class BMMiner(BaseMiner):
return True return True
return False return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def check_light(self) -> bool:
if not self.light:
self.light = False
return self.light
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def get_errors(self) -> list:
return []
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def restart_backend(self) -> bool:
return False
async def get_data(self) -> MinerData: 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) data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
board_offset = -1 board_offset = -1
@@ -136,6 +192,7 @@ class BMMiner(BaseMiner):
model = await self.get_model() model = await self.get_model()
hostname = await self.get_hostname() hostname = await self.get_hostname()
mac = await self.get_mac() mac = await self.get_mac()
errors = await self.get_errors()
if model: if model:
data.model = model data.model = model
@@ -146,8 +203,14 @@ class BMMiner(BaseMiner):
if mac: if mac:
data.mac = mac data.mac = mac
if errors:
for error in errors:
data.errors.append(error)
data.fault_light = await self.check_light()
miner_data = None miner_data = None
for i in range(DATA_RETRIES): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True "summary", "pools", "stats", ignore_x19_error=True
) )
@@ -186,6 +249,16 @@ class BMMiner(BaseMiner):
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}") 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.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: if stats:
temp = stats.get("STATS") temp = stats.get("STATS")
if temp: if temp:
@@ -213,7 +286,8 @@ class BMMiner(BaseMiner):
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0] env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
if not env_temp == 0: if not env_temp == 0:
env_temp_list.append(int(env_temp)) env_temp_list.append(int(env_temp))
data.env_temp = sum(env_temp_list) / len(env_temp_list) if not env_temp_list == []:
data.env_temp = sum(env_temp_list) / len(env_temp_list)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -1,19 +1,35 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ipaddress import ipaddress
import logging import logging
import json import json
from typing import Union
import toml import toml
from pyasic.miners import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.bosminer import BOSMinerAPI
from pyasic.API import APIError from pyasic.API import APIError
from pyasic.data.error_codes import BraiinsOSError
from pyasic.data import MinerData from pyasic.data import MinerData
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES from pyasic.settings import PyasicSettings
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
@@ -26,10 +42,11 @@ class BOSMiner(BaseMiner):
self.pwd = "admin" self.pwd = "admin"
self.config = None self.config = None
async def send_ssh_command(self, cmd: str) -> str or None: async def send_ssh_command(self, cmd: str) -> Union[str, None]:
"""Send a command to the miner over ssh. """Send a command to the miner over ssh.
:return: Result of the command or None. Returns:
Result of the command or None.
""" """
result = None result = None
@@ -55,11 +72,11 @@ class BOSMiner(BaseMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner.""" """Sends command to turn on fault light on the miner."""
logging.debug(f"{self}: Sending fault_light on command.") logging.debug(f"{self}: Sending fault_light on command.")
self.light = True
_ret = await self.send_ssh_command("miner fault_light on") _ret = await self.send_ssh_command("miner fault_light on")
logging.debug(f"{self}: fault_light on command completed.") logging.debug(f"{self}: fault_light on command completed.")
if isinstance(_ret, str): if isinstance(_ret, str):
return True self.light = True
return self.light
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
@@ -69,10 +86,12 @@ class BOSMiner(BaseMiner):
_ret = await self.send_ssh_command("miner fault_light off") _ret = await self.send_ssh_command("miner fault_light off")
logging.debug(f"{self}: fault_light off command completed.") logging.debug(f"{self}: fault_light off command completed.")
if isinstance(_ret, str): if isinstance(_ret, str):
self.light = False
return True return True
return False return False
async def restart_backend(self) -> bool: 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() return await self.restart_bosminer()
async def restart_bosminer(self) -> bool: async def restart_bosminer(self) -> bool:
@@ -93,7 +112,12 @@ class BOSMiner(BaseMiner):
return True return True
return False return False
async def get_config(self) -> None: async def get_config(self) -> MinerConfig:
"""Gets the config for the miner and sets it as `self.config`.
Returns:
The config from `self.config`.
"""
logging.debug(f"{self}: Getting config.") logging.debug(f"{self}: Getting config.")
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
logging.debug(f"{self}: Opening SFTP connection.") logging.debug(f"{self}: Opening SFTP connection.")
@@ -109,7 +133,8 @@ class BOSMiner(BaseMiner):
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
"""Get miner hostname. """Get miner hostname.
:return: The hostname of the miner as a string or "?" Returns:
The hostname of the miner as a string or "?"
""" """
if self.hostname: if self.hostname:
return self.hostname return self.hostname
@@ -128,10 +153,11 @@ class BOSMiner(BaseMiner):
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return "?" return "?"
async def get_model(self) -> str or None: async def get_model(self) -> Union[str, None]:
"""Get miner model. """Get miner model.
:return: Miner model or None. Returns:
Miner model or None.
""" """
# check if model is cached # check if model is cached
if self.model: if self.model:
@@ -165,10 +191,11 @@ class BOSMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}") logging.warning(f"Failed to get model for miner: {self}")
return None return None
async def get_version(self): async def get_version(self) -> Union[str, None]:
"""Get miner firmware version. """Get miner firmware version.
:return: Miner firmware version or None. Returns:
Miner firmware version or None.
""" """
# check if version is cached # check if version is cached
if self.version: if self.version:
@@ -188,82 +215,77 @@ class BOSMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}") logging.warning(f"Failed to get model for miner: {self}")
return None return None
async def send_config(self, yaml_config, ip_user: bool = False) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config.""" """Configures miner with yaml config."""
logging.debug(f"{self}: Sending config.") logging.debug(f"{self}: Sending config.")
if ip_user: toml_conf = config.as_bos(
suffix = str(self.ip).split(".")[-1] model=self.model.replace(" (BOS)", ""), user_suffix=user_suffix
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: async with (await self._get_ssh_connection()) as conn:
await conn.run("/etc/init.d/bosminer stop")
logging.debug(f"{self}: Opening SFTP connection.") logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp: async with conn.start_sftp_client() as sftp:
logging.debug(f"{self}: Opening config file.") logging.debug(f"{self}: Opening config file.")
async with sftp.open("/etc/bosminer.toml", "w+") as file: async with sftp.open("/etc/bosminer.toml", "w+") as file:
await file.write(toml_conf) await file.write(toml_conf)
logging.debug(f"{self}: Restarting BOSMiner") logging.debug(f"{self}: Restarting BOSMiner")
await conn.run("/etc/init.d/bosminer restart") await conn.run("/etc/init.d/bosminer start")
async def get_board_info(self) -> dict: async def check_light(self) -> bool:
"""Gets data on each board and chain in the miner.""" if self.light:
logging.debug(f"{self}: Getting board info.") return self.light
devdetails = await self.api.devdetails() data = (
if not devdetails.get("DEVDETAILS"): await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
print("devdetails error", devdetails) ).strip()
return {0: [], 1: [], 2: []} self.light = False
devs = devdetails["DEVDETAILS"] if data == "50":
boards = {} self.light = True
offset = devs[0]["ID"] return self.light
for board in devs:
boards[board["ID"] - offset] = []
if not board["Chips"] == self.nominal_chips:
nominal = False
else:
nominal = True
boards[board["ID"] - offset].append(
{
"chain": board["ID"] - offset,
"chip_count": board["Chips"],
"chip_status": "o" * board["Chips"],
"nominal": nominal,
}
)
logging.debug(f"Found board data for {self}: {boards}")
return boards
async def get_bad_boards(self) -> dict: async def get_errors(self) -> list:
"""Checks for and provides list of non working boards.""" tunerstatus = None
boards = await self.get_board_info() errors = []
bad_boards = {}
for board in boards.keys():
for chain in boards[board]:
if not chain["chip_count"] == 63:
if board not in bad_boards.keys():
bad_boards[board] = []
bad_boards[board].append(chain)
return bad_boards
async def check_good_boards(self) -> str: try:
"""Checks for and provides list for working boards.""" tunerstatus = await self.api.tunerstatus()
devs = await self.api.devdetails() except Exception as e:
bad = 0 logging.warning(e)
chains = devs["DEVDETAILS"]
for chain in chains: if tunerstatus:
if chain["Chips"] == 0: tuner = tunerstatus[0].get("TUNERSTATUS")
bad += 1 if tuner:
if not bad > 0: if len(tuner) > 0:
return str(self.ip) 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:]
errors.append(
BraiinsOSError(f"{board_map[_id]} {_error}")
)
return errors
async def get_data(self) -> MinerData: 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) data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
board_offset = -1 board_offset = -1
@@ -282,16 +304,24 @@ class BOSMiner(BaseMiner):
if mac: if mac:
data.mac = mac data.mac = mac
data.fault_light = await self.check_light()
miner_data = None miner_data = None
for i in range(DATA_RETRIES): for i in range(PyasicSettings().miner_get_data_retries):
try: try:
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"summary", "temps", "tunerstatus", "pools", "devdetails", "fans" "summary",
"temps",
"tunerstatus",
"pools",
"devdetails",
"fans",
"devs",
) )
except APIError as e: except APIError as e:
if str(e.message) == "Not ready": if str(e.message) == "Not ready":
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"summary", "tunerstatus", "pools", "fans" "summary", "tunerstatus", "pools", "devs"
) )
if miner_data: if miner_data:
break break
@@ -302,6 +332,7 @@ class BOSMiner(BaseMiner):
tunerstatus = miner_data.get("tunerstatus") tunerstatus = miner_data.get("tunerstatus")
pools = miner_data.get("pools") pools = miner_data.get("pools")
devdetails = miner_data.get("devdetails") devdetails = miner_data.get("devdetails")
devs = miner_data.get("devs")
fans = miner_data.get("fans") fans = miner_data.get("fans")
if summary: if summary:
@@ -388,6 +419,30 @@ class BOSMiner(BaseMiner):
if wattage: if wattage:
data.wattage = 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: if devdetails:
boards = devdetails[0].get("DEVDETAILS") boards = devdetails[0].get("DEVDETAILS")
if boards: if boards:
@@ -399,6 +454,20 @@ class BOSMiner(BaseMiner):
chips = board["Chips"] chips = board["Chips"]
setattr(data, board_map[_id], 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 return data
async def get_mac(self): async def get_mac(self):

View File

@@ -1,9 +1,25 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging import logging
import ipaddress import ipaddress
from typing import Union
from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.bosminer import BOSMinerAPI
from pyasic.miners import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.config import MinerConfig
class BOSMinerOld(BaseMiner): class BOSMinerOld(BaseMiner):
@@ -15,7 +31,7 @@ class BOSMinerOld(BaseMiner):
self.uname = "root" self.uname = "root"
self.pwd = "admin" self.pwd = "admin"
async def send_ssh_command(self, cmd: str) -> str or None: async def send_ssh_command(self, cmd: str) -> Union[str, None]:
"""Send a command to the miner over ssh. """Send a command to the miner over ssh.
:return: Result of the command or None. :return: Result of the command or None.
@@ -44,7 +60,39 @@ class BOSMinerOld(BaseMiner):
# return the result, either command output or None # return the result, either command output or None
return str(result) return str(result)
async def update_to_plus(self): async def update_to_plus(self):
result = await self.send_ssh_command("opkg update && opkg install bos_plus") result = await self.send_ssh_command("opkg update && opkg install bos_plus")
return result return result
async def check_light(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def fault_light_off(self) -> bool:
return False
async def get_config(self) -> None:
return None
async def get_errors(self) -> list:
return []
async def get_hostname(self) -> str:
return "?"
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def get_model(self) -> str:
return "S9"
async def reboot(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None

View File

@@ -1,14 +1,31 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ipaddress import ipaddress
import logging import logging
from typing import Union
from pyasic.API.btminer import BTMinerAPI from pyasic.API.btminer import BTMinerAPI
from pyasic.miners import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.API import APIError from pyasic.API import APIError
from pyasic.data import MinerData from pyasic.data import MinerData
from pyasic.data.error_codes import WhatsminerError
from pyasic.config import MinerConfig
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES from pyasic.settings import PyasicSettings
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
@@ -18,7 +35,12 @@ class BTMiner(BaseMiner):
self.api = BTMinerAPI(ip) self.api = BTMinerAPI(ip)
self.api_type = "BTMiner" self.api_type = "BTMiner"
async def get_model(self): async def get_model(self) -> Union[str, None]:
"""Get miner model.
Returns:
Miner model or None.
"""
if self.model: if self.model:
logging.debug(f"Found model for {self.ip}: {self.model}") logging.debug(f"Found model for {self.ip}: {self.model}")
return self.model return self.model
@@ -30,7 +52,12 @@ class BTMiner(BaseMiner):
logging.warning(f"Failed to get model for miner: {self}") logging.warning(f"Failed to get model for miner: {self}")
return None return None
async def get_hostname(self) -> str or 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: if self.hostname:
return self.hostname return self.hostname
try: try:
@@ -47,38 +74,12 @@ class BTMiner(BaseMiner):
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return None return None
async def get_board_info(self) -> dict: async def get_mac(self) -> str:
"""Gets data on each board and chain in the miner.""" """Get the mac address of the miner.
logging.debug(f"{self}: Getting board info.")
devs = await self.api.devs()
if not devs.get("DEVS"):
print("devs error", devs)
return {0: [], 1: [], 2: []}
devs = devs["DEVS"]
boards = {}
offset = devs[0]["ID"]
for board in devs:
boards[board["ID"] - offset] = []
if "Effective Chips" in board.keys():
if not board["Effective Chips"] in self.nominal_chips:
nominal = False
else:
nominal = True
boards[board["ID"] - offset].append(
{
"chain": board["ID"] - offset,
"chip_count": board["Effective Chips"],
"chip_status": "o" * board["Effective Chips"],
"nominal": nominal,
}
)
else:
logging.warning(f"Incorrect board data from {self}: {board}")
print(board)
logging.debug(f"Found board data for {self}: {boards}")
return boards
async def get_mac(self): Returns:
The mac address of the miner as a string.
"""
mac = "" mac = ""
data = await self.api.summary() data = await self.api.summary()
if data: if data:
@@ -99,7 +100,106 @@ class BTMiner(BaseMiner):
return str(mac).upper() return str(mac).upper()
async def get_data(self): async def _reset_api_pwd_to_admin(self, pwd: str):
try:
data = await self.api.update_pwd(pwd, "admin")
except APIError:
return False
if data:
if "Code" in data.keys():
if data["Code"] == 131:
return True
print(data)
return False
async def check_light(self) -> bool:
data = None
try:
data = await self.api.get_miner_info()
except APIError:
if not self.light:
self.light = False
if data:
if "Msg" in data.keys():
if "ledstat" in data["Msg"].keys():
if not data["Msg"]["ledstat"] == "auto":
self.light = True
if data["Msg"]["ledstat"] == "auto":
self.light = False
return self.light
async def fault_light_off(self) -> bool:
try:
data = await self.api.set_led(auto=True)
except APIError:
return False
if data:
if "Code" in data.keys():
if data["Code"] == 131:
self.light = False
return True
return False
async def fault_light_on(self) -> bool:
try:
data = await self.api.set_led(auto=False)
await self.api.set_led(
auto=False, color="green", start=0, period=1, duration=0
)
except APIError:
return False
if data:
if "Code" in data.keys():
if data["Code"] == 131:
self.light = True
return True
return False
async def get_errors(self) -> list:
return []
async def reboot(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
conf = config.as_wm(user_suffix=user_suffix)
await self.api.update_pools(
conf[0]["url"],
conf[0]["user"],
conf[0]["pass"],
conf[1]["url"],
conf[1]["user"],
conf[1]["pass"],
conf[2]["url"],
conf[2]["user"],
conf[2]["pass"],
)
async def get_config(self) -> MinerConfig:
pools = None
cfg = MinerConfig()
try:
pools = await self.api.pools()
except APIError as e:
logging.warning(e)
if pools:
if "POOLS" in pools.keys():
cfg = cfg.from_api(pools["POOLS"])
return cfg
async def get_data(self) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
mac = None mac = None
@@ -124,8 +224,10 @@ class BTMiner(BaseMiner):
if hostname: if hostname:
data.hostname = hostname data.hostname = hostname
data.fault_light = await self.check_light()
miner_data = None miner_data = None
for i in range(DATA_RETRIES): for i in range(PyasicSettings().miner_get_data_retries):
try: try:
miner_data = await self.api.multicommand("summary", "devs", "pools") miner_data = await self.api.multicommand("summary", "devs", "pools")
if miner_data: if miner_data:
@@ -144,12 +246,16 @@ class BTMiner(BaseMiner):
summary_data = summary.get("SUMMARY") summary_data = summary.get("SUMMARY")
if summary_data: if summary_data:
if len(summary_data) > 0: if len(summary_data) > 0:
wattage_limit = None
if summary_data[0].get("MAC"): if summary_data[0].get("MAC"):
mac = summary_data[0]["MAC"] mac = summary_data[0]["MAC"]
if summary_data[0].get("Env Temp"): if summary_data[0].get("Env Temp"):
data.env_temp = summary_data[0]["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_1 = summary_data[0]["Fan Speed In"]
data.fan_2 = summary_data[0]["Fan Speed Out"] data.fan_2 = summary_data[0]["Fan Speed Out"]
@@ -160,7 +266,20 @@ class BTMiner(BaseMiner):
wattage = summary_data[0].get("Power") wattage = summary_data[0].get("Power")
if wattage: if wattage:
data.wattage = round(wattage) data.wattage = round(wattage)
data.wattage_limit = 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: if devs:
temp_data = devs.get("DEVS") temp_data = devs.get("DEVS")
@@ -170,8 +289,10 @@ class BTMiner(BaseMiner):
_id = board["ASC"] _id = board["ASC"]
chip_temp = round(board["Chip Temp Avg"]) chip_temp = round(board["Chip Temp Avg"])
board_temp = round(board["Temperature"]) 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]}_chip_temp", chip_temp)
setattr(data, f"{board_map[_id]}_temp", board_temp) setattr(data, f"{board_map[_id]}_temp", board_temp)
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
if devs: if devs:
boards = devs.get("DEVS") boards = devs.get("DEVS")

View File

@@ -1,14 +1,30 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ipaddress import ipaddress
import logging import logging
from typing import Union
from pyasic.API.cgminer import CGMinerAPI from pyasic.API.cgminer import CGMinerAPI
from pyasic.miners import BaseMiner from pyasic.miners.base import BaseMiner
from pyasic.API import APIError from pyasic.API import APIError
from pyasic.config import MinerConfig
from pyasic.data import MinerData from pyasic.data import MinerData
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES from pyasic.settings import PyasicSettings
class CGMiner(BaseMiner): class CGMiner(BaseMiner):
@@ -21,7 +37,12 @@ class CGMiner(BaseMiner):
self.pwd = "admin" self.pwd = "admin"
self.config = None self.config = None
async def get_model(self): async def get_model(self) -> Union[str, None]:
"""Get miner model.
Returns:
Miner model or None.
"""
if self.model: if self.model:
return self.model return self.model
try: try:
@@ -33,7 +54,12 @@ class CGMiner(BaseMiner):
return self.model return self.model
return None return None
async def get_hostname(self) -> str or 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: if self.hostname:
return self.hostname return self.hostname
try: try:
@@ -48,7 +74,15 @@ class CGMiner(BaseMiner):
except Exception: except Exception:
return None return None
async def send_ssh_command(self, cmd): 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 result = None
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
for i in range(3): for i in range(3):
@@ -63,9 +97,11 @@ class CGMiner(BaseMiner):
return result return result
async def restart_backend(self) -> bool: 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() return await self.restart_cgminer()
async def restart_cgminer(self) -> bool: async def restart_cgminer(self) -> bool:
"""Restart cgminer hashing process."""
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"] commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands) commands = ";".join(commands)
_ret = await self.send_ssh_command(commands) _ret = await self.send_ssh_command(commands)
@@ -74,6 +110,7 @@ class CGMiner(BaseMiner):
return False return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
_ret = await self.send_ssh_command("reboot") _ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
@@ -82,6 +119,7 @@ class CGMiner(BaseMiner):
return False return False
async def start_cgminer(self) -> None: async def start_cgminer(self) -> None:
"""Start cgminer hashing process."""
commands = [ commands = [
"mkdir -p /etc/tmp/", "mkdir -p /etc/tmp/",
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root', 'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
@@ -92,6 +130,7 @@ class CGMiner(BaseMiner):
await self.send_ssh_command(commands) await self.send_ssh_command(commands)
async def stop_cgminer(self) -> None: async def stop_cgminer(self) -> None:
"""Restart cgminer hashing process."""
commands = [ commands = [
"mkdir -p /etc/tmp/", "mkdir -p /etc/tmp/",
'echo "" > /etc/tmp/root', 'echo "" > /etc/tmp/root',
@@ -101,16 +140,49 @@ class CGMiner(BaseMiner):
commands = ";".join(commands) commands = ";".join(commands)
await self.send_ssh_command(commands) await self.send_ssh_command(commands)
async def get_config(self) -> None: 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: async with (await self._get_ssh_connection()) as conn:
command = "cat /etc/config/cgminer" command = "cat /etc/config/cgminer"
result = await conn.run(command, check=True) result = await conn.run(command, check=True)
self.config = result.stdout self.config = result.stdout
print(str(self.config)) return self.config
async def get_data(self): async def check_light(self) -> bool:
if not self.light:
self.light = False
return self.light
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def get_errors(self) -> list:
return []
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def get_mac(self) -> str:
return "00:00:00:00:00:00"
async def get_data(self) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
board_offset = -1
fan_offset = -1
model = await self.get_model() model = await self.get_model()
hostname = await self.get_hostname() hostname = await self.get_hostname()
mac = await self.get_mac() mac = await self.get_mac()
@@ -124,9 +196,13 @@ class CGMiner(BaseMiner):
if mac: if mac:
data.mac = mac data.mac = mac
data.fault_light = await self.check_light()
miner_data = None miner_data = None
for i in range(DATA_RETRIES): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand("summary", "pools", "stats") miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True
)
if miner_data: if miner_data:
break break
@@ -141,25 +217,65 @@ class CGMiner(BaseMiner):
hr = summary.get("SUMMARY") hr = summary.get("SUMMARY")
if hr: if hr:
if len(hr) > 0: if len(hr) > 0:
hr = hr[0].get("GHS 1m") hr = hr[0].get("GHS av")
if hr: if hr:
data.hashrate = round(hr / 1000, 2) 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: if stats:
temp = stats.get("STATS") temp = stats.get("STATS")
if temp: if temp:
if len(temp) > 1: if len(temp) > 1:
data.fan_1 = temp[1].get("fan1") for fan_num in range(1, 8, 4):
data.fan_2 = temp[1].get("fan2") for _f_num in range(4):
data.fan_3 = temp[1].get("fan3") f = temp[1].get(f"fan{fan_num + _f_num}")
data.fan_4 = temp[1].get("fan4") 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 = {1: "left_board", 2: "center_board", 3: "right_board"} board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
for item in range(1, 4): env_temp_list = []
board_temp = temp[1].get(f"temp{item}") for item in range(3):
chip_temp = temp[1].get(f"temp2_{item}") 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]}_chip_temp", chip_temp)
setattr(data, f"{board_map[item]}_temp", board_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: if pools:
pool_1 = None pool_1 = None
@@ -173,16 +289,19 @@ class CGMiner(BaseMiner):
if not pool_1_user: if not pool_1_user:
pool_1_user = pool.get("User") pool_1_user = pool.get("User")
pool_1 = pool["URL"] pool_1 = pool["URL"]
pool_1_quota = pool["Quota"] if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
elif not pool_2_user: elif not pool_2_user:
pool_2_user = pool.get("User") pool_2_user = pool.get("User")
pool_2 = pool["URL"] pool_2 = pool["URL"]
pool_2_quota = pool["Quota"] if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
if not pool.get("User") == pool_1_user: if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"): if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User") pool_2_user = pool.get("User")
pool_2 = pool["URL"] pool_2 = pool["URL"]
pool_2_quota = pool["Quota"] if pool.get("Quota"):
pool_2_quota = pool.get("Quota")
if pool_2_user and not pool_2_user == pool_1_user: if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}" quota = f"{pool_1_quota}/{pool_2_quota}"

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners._backends import BMMiner from pyasic.miners._backends import BMMiner
import ipaddress import ipaddress

View File

@@ -1,3 +1,18 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .antminer import * from .antminer import *
from .avalonminer import * from .avalonminer import *
from .whatsminer import * from .whatsminer import *
from .innosilicon import *

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S17(BaseMiner): class S17(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S17Plus(BaseMiner): class S17Plus(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S17Pro(BaseMiner): class S17Pro(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S17e(BaseMiner): class S17e(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class T17(BaseMiner): class T17(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class T17Plus(BaseMiner): class T17Plus(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class T17e(BaseMiner): class T17e(BaseMiner):

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .S17 import S17 from .S17 import S17
from .S17_Plus import S17Plus from .S17_Plus import S17Plus
from .S17_Pro import S17Pro from .S17_Pro import S17Pro

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S19(BaseMiner): class S19(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S19Pro(BaseMiner): class S19Pro(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S19a(BaseMiner): class S19a(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S19j(BaseMiner): class S19j(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S19jPro(BaseMiner): class S19jPro(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class T19(BaseMiner): class T19(BaseMiner):

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .S19 import S19 from .S19 import S19
from .S19_Pro import S19Pro from .S19_Pro import S19Pro

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S9(BaseMiner): class S9(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class S9i(BaseMiner): class S9i(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class T9(BaseMiner): class T9(BaseMiner):
@@ -6,5 +20,5 @@ class T9(BaseMiner):
super().__init__() super().__init__()
self.ip = ip self.ip = ip
self.model = "T9" self.model = "T9"
self.nominal_chips = 57 self.nominal_chips = 54
self.fan_count = 2 self.fan_count = 2

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .S9 import S9 from .S9 import S9
from .S9i import S9i from .S9i import S9i
from .T9 import T9 from .T9 import T9

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .X9 import * from .X9 import *
from .X17 import * from .X17 import *
from .X19 import * from .X19 import *

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon1026(BaseMiner): class Avalon1026(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon1047(BaseMiner): class Avalon1047(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon1066(BaseMiner): class Avalon1066(BaseMiner):

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .A1026 import Avalon1026 from .A1026 import Avalon1026
from .A1047 import Avalon1047 from .A1047 import Avalon1047
from .A1066 import Avalon1066 from .A1066 import Avalon1066

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon721(BaseMiner): class Avalon721(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon741(BaseMiner): class Avalon741(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon761(BaseMiner): class Avalon761(BaseMiner):

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .A721 import Avalon721 from .A721 import Avalon721
from .A741 import Avalon741 from .A741 import Avalon741
from .A761 import Avalon761 from .A761 import Avalon761

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon821(BaseMiner): class Avalon821(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon841(BaseMiner): class Avalon841(BaseMiner):

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon851(BaseMiner): class Avalon851(BaseMiner):

View File

@@ -1,3 +1,17 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .A821 import Avalon821 from .A821 import Avalon821
from .A841 import Avalon841 from .A841 import Avalon841
from .A851 import Avalon851 from .A851 import Avalon851

View File

@@ -1,4 +1,18 @@
from pyasic.miners import BaseMiner # Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class Avalon921(BaseMiner): class Avalon921(BaseMiner):

View File

@@ -1 +1,15 @@
# 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 .A921 import Avalon921 from .A921 import Avalon921

View File

@@ -1,3 +1,17 @@
# 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 .A7X import * from .A7X import *
from .A8X import * from .A8X import *
from .A9X import * from .A9X import *

View File

@@ -0,0 +1,24 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from pyasic.miners.base import BaseMiner
class InnosiliconT3HPlus(BaseMiner):
def __init__(self, ip: str) -> None:
super().__init__()
self.ip = ip
self.model = "T3H+"
self.nominal_chips = 114
self.fan_count = 4

View File

@@ -0,0 +1,15 @@
# 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 .T3H_Plus import InnosiliconT3HPlus

Some files were not shown because too many files have changed in this diff Show More