Compare commits

...

128 Commits

Author SHA1 Message Date
UpstreamData
ade3cd6fee version: bump version number 2022-11-14 09:56:18 -07:00
UpstreamData
28dc3ccb84 version: bump version number 2022-11-14 09:51:34 -07:00
UpstreamData
6ca8582ec3 bug: improve robustness of identifying new versions of bosminer running BOSer. 2022-11-14 09:49:19 -07:00
UpstreamData
74b4aeb44a version: bump version number 2022-11-14 09:14:28 -07:00
UpstreamData
9c7ab5ac57 bug: fix support for new miners that return a BOSer string in version data. 2022-11-14 09:10:18 -07:00
Upstream Data
65ecf1fea2 version: bump version number 2022-11-13 19:36:25 -07:00
Upstream Data
44142c658b format: remove print statements 2022-11-13 19:36:07 -07:00
Upstream Data
25a205ce6c version: bump version number 2022-11-13 19:27:55 -07:00
Upstream Data
25094084cf bug: clarify uppercasing data from __get_model_from_graphql() 2022-11-13 19:27:32 -07:00
Upstream Data
4eac601153 version: bump version number 2022-11-13 19:17:43 -07:00
Upstream Data
f0d8d66b9b bug: fix not handling errors in send_graphql_query 2022-11-13 19:16:48 -07:00
Upstream Data
cfa550f8c0 version: bump version number 2022-11-13 19:07:56 -07:00
Arceris
91f6a5bf41 bug: catch TypeError on temps 2022-11-13 19:06:52 -07:00
Upstream Data
464bd6be65 version: bump version number. 2022-11-13 18:58:31 -07:00
Upstream Data
031d7e2186 bug: fix missing catching IndexError 2022-11-13 18:58:05 -07:00
Upstream Data
126b0d124c version: bump version number 2022-11-13 18:54:46 -07:00
Upstream Data
81c84a3e8f bug: fix a bug with no try except blocks on pools. 2022-11-13 18:54:26 -07:00
Upstream Data
406d5bd549 version: bump version number 2022-11-13 18:21:18 -07:00
Upstream Data
cd5fe09fd9 bug: fix wrong formatting for multiple error handling 2022-11-13 18:20:15 -07:00
Upstream Data
766fc4efed bug: fix check light not responding properly from graphQL. 2022-11-13 17:06:35 -07:00
Upstream Data
b70fed40c8 bug: add try except statements to get_data to fix bugs with getting data from graphql. 2022-11-13 17:03:41 -07:00
Upstream Data
255d98fd08 bug: fix some issues with validating data from graphql properly 2022-11-13 16:48:22 -07:00
Upstream Data
78304631f7 bug: fix errors not registering properly on braiins OS miners 2022-11-13 16:34:26 -07:00
Upstream Data
ab230844fc bug: fix an issue with chip count not receiving properly in some cases. 2022-11-13 16:31:41 -07:00
Upstream Data
8a58cb9fd3 format: remove extra print 2022-11-13 16:28:36 -07:00
Upstream Data
70b6ed73dc bug: fix manual return from __get_devdetails_and_version 2022-11-13 16:27:42 -07:00
Upstream Data
d2400bf44e bug: fix identifying stock as bosminer. 2022-11-13 16:22:57 -07:00
Upstream Data
db780fe876 version: bump version number 2022-11-13 15:14:16 -07:00
Upstream Data
cd84ae828a bug: fix a bug with some missing temperatures in graphql. 2022-11-13 15:13:56 -07:00
Upstream Data
970d5d1031 version: bump version number 2022-11-13 14:39:23 -07:00
Upstream Data
da0e327ec7 feature: add support for graphQL for BOS miners which may be having API issues, such as BBB. 2022-11-13 14:38:44 -07:00
Upstream Data
4c84a8d572 bug: ensure bosminer check_light backwards compatibility 2022-11-13 11:06:28 -07:00
Upstream Data
1cfd895deb feature: improve speed of bosminer check_light by 10x using graphql. 2022-11-13 11:01:22 -07:00
Upstream Data
d0da08eb10 version: bump version number 2022-11-12 14:51:30 -07:00
Upstream Data
ee4f2cd87d bug: fix win error check not being cross platform 2022-11-12 14:51:10 -07:00
UpstreamData
8a6577c8aa docs: Update README.md. 2022-11-10 14:20:41 -07:00
UpstreamData
ed5eae4187 format: add isort to dev requirements. 2022-11-10 14:17:31 -07:00
UpstreamData
f3b25027ad format: add isort to pre-commit and sort imports. 2022-11-10 14:15:45 -07:00
UpstreamData
228daecbbf version: bump version number 2022-11-08 15:09:32 -07:00
UpstreamData
81aa569d07 feature: rework whatsminer error codes to be able to allow an arbitrary slot number to be returned from the BTMiner API. 2022-11-08 15:08:47 -07:00
upstreamdata
aba8d7b8b9 bug: fix an issue with get data on unknown miners. 2022-11-08 13:38:19 -07:00
Upstream Data
449403bc57 version: bump version number. 2022-11-07 20:35:51 -07:00
Upstream Data
997a1bbe31 feature: add allow_warning to get_data to allow suppressing warnings. 2022-11-07 20:35:23 -07:00
Arceris
2c7e0e3efe Merge pull request #24 from UpstreamData/crc
Merge in branch refactoring the hashboard data
2022-11-07 19:25:06 -07:00
UpstreamData
6051a46654 docs: update docs to show cls.fields() 2022-11-07 12:54:40 -07:00
UpstreamData
c7847d2789 feature: add fields classmethod to all dataclasses to show what the fields are in each class. 2022-11-07 12:53:01 -07:00
UpstreamData
215af72a6b feature: add 0 as error_code for bos errors and X19 errors to make them consistent with the other types of errors. 2022-11-07 12:49:01 -07:00
UpstreamData
b33586f8eb feature: add fields() method to MinerData dataclass. 2022-11-07 10:51:34 -07:00
UpstreamData
8db81d9b3c format: remove prints. 2022-11-07 10:18:50 -07:00
UpstreamData
cf3b2fedf4 bug: fix a missed key step in btminer get errors. 2022-11-07 10:18:22 -07:00
UpstreamData
81db6d8e28 format: remove print in tunerstatus. 2022-11-07 10:11:08 -07:00
UpstreamData
d47e59d057 bug: fix a bug with older versions of BOSMiner having tuner status in a different place. 2022-11-07 10:09:56 -07:00
UpstreamData
8388d2f4ac bug: change MinerData().total_chips from reduce to sum, as reduce was causing issues when instantiating MinerData with a hashboard list that is empty. 2022-11-07 09:37:22 -07:00
UpstreamData
f5ec513ce0 bug: fix BMMiner warning when getting data from X19. 2022-11-07 09:07:32 -07:00
UpstreamData
8975842f0b bug: fix for some BMMiner models which report no boards causing an error when getting data. 2022-11-07 08:52:24 -07:00
Upstream Data
c6ca1df112 bug: fix some issues with get data on X19 from CGMiner on Vnish to BMMiner on stock. 2022-11-05 10:15:18 -06:00
Upstream Data
92f58a9682 bug: fix fan count and ideal chips for M34S+. 2022-11-05 10:04:17 -06:00
Upstream Data
5dc19dbd78 feature: add support for avalonminers using new hashboard model. 2022-11-05 10:03:02 -06:00
Upstream Data
b0fc11bcc1 bug: fix M34S+ not having ideal board count. 2022-11-05 09:50:03 -06:00
Upstream Data
23ebe460a4 format: Reformat some files. 2022-11-05 09:47:25 -06:00
Upstream Data
92db8c8161 format: Improve T9 formatting with get data on hiveon. 2022-11-05 09:44:38 -06:00
Upstream Data
d9b522734a format: reformat T3H+ get_data to improve error handling and checking of data. 2022-11-05 09:40:56 -06:00
Colin Crossman
e02457f0b2 bug: fix import for HashBoard 2022-11-04 21:55:44 -06:00
Colin Crossman
104b4c18c2 format: remove vestigial elements of btminer 2022-11-04 21:28:59 -06:00
Colin Crossman
1970f8c924 feature: add innosilicon support for new HashBoard format. 2022-11-04 21:28:33 -06:00
Colin Crossman
e431a06ac5 feature: add cgminer support for new HashBoard structure 2022-11-04 21:28:07 -06:00
Upstream Data
6da9b58088 bug: fix missing initialization variables on HashBoard when being called from within get_data on bos, T9, and btminer. 2022-11-04 20:26:28 -06:00
Upstream Data
3108443041 feature: add bosminer support for new HashBoard format. 2022-11-04 20:22:07 -06:00
Upstream Data
37ebb9e6c3 feature: add T9 support for new HashBoard format. 2022-11-04 20:10:50 -06:00
Upstream Data
6c2e0f59b1 feature: add the remainder of the backwards compatible MinerData board related parts that were lost in converting to HashBoard dataclass. 2022-11-04 20:00:03 -06:00
Upstream Data
6eb9cdce90 feature: add left_chips, center_chips, and right_chips to MinerData as properties that compute the correct board position from hashboards. 2022-11-04 19:55:13 -06:00
Upstream Data
e96d9447d1 format: reformat btminer.py 2022-11-04 19:44:49 -06:00
Upstream Data
d3cca11322 feature: add new HashBoard data structure to bmminer, and add ideal_hashboards to BaseMiner. 2022-11-04 19:44:19 -06:00
Colin Crossman
86155db455 Initial modifications to account for differential hashboard counts
Now storing the hasboard data as an array of datastructs. Not fully fleshed out yet, but this is where I'm going.
2022-11-04 15:48:30 -06:00
UpstreamData
5b078b4b27 feature: add support for M34S+ and M34S+ VE10 2022-11-04 13:12:28 -06:00
UpstreamData
9672dd6873 bug: fix warnings when new whatsminer API commands don't exist. 2022-11-02 10:32:33 -06:00
Upstream Data
1587f65196 version: bump version number 2022-11-01 22:52:56 -06:00
Upstream Data
5ff10c0cdd bug: fix the v2.0.4 whatsminer error codes bug to work with new versions where it was fixed. 2022-11-01 22:15:13 -06:00
UpstreamData
aa0a028564 Update .gitignore 2022-11-01 14:17:07 -06:00
UpstreamData
82f6d2f274 version: bump version number
resolves #22
2022-11-01 12:59:55 -06:00
UpstreamData
833de3ab43 feature: add support for whatsminer API v2.0.4, which moves the location of error codes to a new function (that happens to have very incorrect JSON) 2022-11-01 12:57:33 -06:00
UpstreamData
08d273c7c1 version: bump version number 2022-11-01 08:14:56 -06:00
UpstreamData
aac7598187 bug: Fix whatsminers not reporting error codes due to self.api.get_psu() causing a failed multicommand when used with older miners. 2022-11-01 08:14:06 -06:00
UpstreamData
5c0ac4e665 feature: added the ability to call btminer privileged commands as their own function, and abstracted out most of the complexity. 2022-10-31 09:46:28 -06:00
Upstream Data
7bd5e49412 bug: fix for btminer get_data() for cases where arbitrary amounts of errors with no error information are returned from the api to have them not be added to the error list. 2022-10-30 21:17:26 -06:00
Upstream Data
53ff3c5f79 bug: fix _load_api_data() raising an error when api data is an arbitrarily large size and overflows the receive buffer by parsing out the last incomplete item and repairing the json. 2022-10-30 21:14:14 -06:00
Upstream Data
36ae6e5272 docs: add M5X to nav directory 2022-10-21 18:43:13 -06:00
Upstream Data
fb3dffb216 docs: add relative paths to supported miner types to fix read the docs not going to the correct page. 2022-10-21 18:39:15 -06:00
Colin Crossman
ff6a6d2ec6 Add support for M50, forgot to update this file 2022-10-21 18:13:54 -06:00
Colin Crossman
f4bbc2c3e5 Add support for M50
Received M50 units, added support for them.
2022-10-21 18:12:05 -06:00
UpstreamData
4dbfdbe29c bump version number 2022-10-12 15:50:12 -06:00
UpstreamData
5e2a18f91e add fan_psu to MinerData, only works for whatsminers. 2022-10-12 15:42:27 -06:00
UpstreamData
3363bdc592 add MinerData().as_csv() to documentation. 2022-10-04 14:44:02 -06:00
UpstreamData
08180a2d59 bump version number 2022-10-04 08:30:26 -06:00
UpstreamData
1a64ff4038 add support for whatsminer in miner listener, and fix space in MinerData.as_csv() 2022-10-04 08:28:24 -06:00
UpstreamData
8ad90a6abb bump version number 2022-10-03 13:52:04 -06:00
UpstreamData
8cdd5ff015 improve MinerData().as_csv()` 2022-10-03 13:51:44 -06:00
UpstreamData
a08f434e1f bump version number 2022-10-03 13:43:08 -06:00
UpstreamData
9acd6d2fea add MinerData().as_csv() 2022-10-03 13:42:47 -06:00
UpstreamData
0a1cdea2e3 bump version number 2022-09-30 16:01:28 -06:00
UpstreamData
73b1a0493c Add the ability to add MinerData together and sum them, as well as compute division and floor division on them. 2022-09-30 16:00:45 -06:00
UpstreamData
d3a5517fa9 bump version number 2022-09-29 14:10:19 -06:00
UpstreamData
ad42251ee9 add S19 XP to docs 2022-09-29 14:09:26 -06:00
Arceris
0670938ed3 Add in S19 XP spec, and also make an adjustment to BMMiner so it won't fail when hashboards are missing (#20)
* Add in spec for S19 XP

* Adjust BMMiner to account for missing hashboards
2022-09-29 14:03:53 -06:00
UpstreamData
3e96889976 improve poetry setup instructions and improve documentation 2022-09-28 12:54:04 -06:00
UpstreamData
5a7b43ad74 Update README.md 2022-09-28 11:46:56 -06:00
UpstreamData
08c4863a2e improve poetry setup and install instructions 2022-09-28 11:39:25 -06:00
UpstreamData
4dbab75cf4 bump version number 2022-09-28 10:20:17 -06:00
UpstreamData
a90ad3ba6e fix incorrect chip count on M31S+ V30 2022-09-28 10:19:56 -06:00
UpstreamData
98a94ce4a6 bump version number 2022-09-28 10:06:53 -06:00
UpstreamData
f0a8b6e1c7 add support for whatsminer M31S+ V30 2022-09-28 10:05:28 -06:00
UpstreamData
e07bd3bffb bump version number 2022-09-26 13:08:27 -06:00
UpstreamData
dcce944390 Fix a bug where older version of bosminer return excessive hashboard error information 2022-09-26 12:27:37 -06:00
UpstreamData
03ecd118a3 add support for M31S+ V60 and V90 2022-09-26 11:51:47 -06:00
UpstreamData
97c0331762 bump version number 2022-09-26 11:33:42 -06:00
UpstreamData
eda9804dea add support for some new whatsminers types, M31S+ v40 and v80, and improve documentation of supported types. 2022-09-26 11:32:55 -06:00
UpstreamData
e94c81ce44 improve miner network functionality 2022-09-26 09:15:37 -06:00
UpstreamData
c95c58138e bump version number 2022-09-22 10:07:31 -06:00
UpstreamData
03c93b4de1 added pause_mining and resume_mining to all miners, added get_errors to whatsminers, and improved get_errors type hinting 2022-09-22 10:06:27 -06:00
UpstreamData
ff0d15c365 bump version number 2022-09-22 09:06:51 -06:00
UpstreamData
eadcb76d31 add stop_mining and resume_mining for X19 devices 2022-09-22 09:06:22 -06:00
UpstreamData
b7ce9288f8 bump version number 2022-09-13 09:53:03 -06:00
UpstreamData
e077a099d9 add global Innosilicon password option to settings 2022-09-13 09:52:33 -06:00
UpstreamData
8542acfb01 improve documentation 2022-09-13 09:11:15 -06:00
UpstreamData
0d80ce5a0e bump version number 2022-09-12 15:28:22 -06:00
UpstreamData
ddcafe0f2b finish abstracting BaseMiner by implementing get_data() as abstract 2022-09-12 15:27:51 -06:00
UpstreamData
ea195b34db update tests and add code coverage with coverage, although coverage is not required 2022-09-12 15:18:00 -06:00
UpstreamData
7377cb0d26 refactor some classes into their own files and fill base __init__.py with imports 2022-09-12 15:15:13 -06:00
112 changed files with 3684 additions and 1209 deletions

17
.coveragerc Normal file
View File

@@ -0,0 +1,17 @@
[report]
exclude_lines =
# Skip @abstractmethod
@abstractmethod
@abc.abstractmethod
# Don't complain if tests don't hit defensive assertion code:
raise AssertionError
raise NotImplementedError
# Don't complain about missing debug-only code:
def __repr__
if self\.debug
# Don't complain if non-runnable code isn't run:
if 0:
if __name__ == .__main__.:

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ pyvenv.cfg
.env/ .env/
bin/ bin/
lib/ lib/
.idea/

View File

@@ -9,6 +9,12 @@ repos:
rev: 22.6.0 rev: 22.6.0
hooks: hooks:
- id: black - id: black
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: isort
name: isort (python)
- repo: local - repo: local
hooks: hooks:
- id: unittest - id: unittest

View File

@@ -7,47 +7,33 @@
[![Read the Docs](https://img.shields.io/readthedocs/pyasic)](https://pyasic.readthedocs.io/en/latest/) [![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) [![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) [![CodeFactor Grade](https://img.shields.io/codefactor/grade/github/UpstreamData/pyasic)](https://www.codefactor.io/repository/github/upstreamdata/pyasic)
## Supported Miners ## Documentation and Supported Miners
Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/) Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/).
## Documentation Supported miners are listed in the docs, [here](https://pyasic.readthedocs.io/en/latest/miners/supported_types/).
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/)
## Usage ## Installation
You can install pyasic directly from pip with the command `pip install pyasic`.
### Standard Usage 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).
You can install pyasic directly from pip with the command `pip install pyasic`
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library here -> (https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing) ## Developer Setup
It is highly reccommended that you contribute to this project through [`pyasic-super`](https://github.com/UpstreamData/pyasic-super) using its submodules. This allows testing in conjunction with other `pyasic` related programs.
### 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
##### Note: If you are trying to interface with Whatsminers, there is a bug in the way they are interacted with on Windows, so to fix that you need to change the event loop policy using this code:
```python
# need to import these 2 libraries, you need asyncio anyway so make sure you have sys imported
import sys
import asyncio
# if the computer is windows, set the event loop policy to a WindowsSelector policy
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
```
##### It is likely a good idea to use this code in your program anyway to be preventative.
<br> <br>
To write your own custom programs with this repo, you have many options. This repo uses poetry for dependencies, which can be installed by following the guide on their website [here](https://python-poetry.org/docs/#installation).
It is recommended that you explore the files in this repo to familiarize yourself with them, try starting with the miners module and going from there. After you have poetry installed, run `poetry install --with dev`, or `poetry install --with dev,docs` if you want to include packages required for documentation.
There are 2 main ways to get a miner and it's functions via scanning or via the MinerFactory. Finally, initialize pre-commit hooks with `poetry run pre-commit install`.
### Documentation Testing
Testing the documentation can be done by running `poetry run mkdocs serve`, whcih will serve the documentation locally on port 8000.
## Interfacing with miners programmatically
There are 2 main ways to get a miner (and the functions attached to it), via scanning or via the `MinerFactory()`.
#### Scanning for miners #### Scanning for miners
```python ```python
@@ -56,11 +42,6 @@ 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())
# define asynchronous function to scan for miners # define asynchronous function to scan for miners
async def scan_and_get_data(): async def scan_and_get_data():
@@ -87,7 +68,6 @@ if __name__ == "__main__":
asyncio.run(scan_and_get_data()) asyncio.run(scan_and_get_data())
``` ```
</br>
#### Getting a miner if you know the IP #### Getting a miner if you know the IP
```python ```python
@@ -96,11 +76,6 @@ import sys
from pyasic.miners import get_miner 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())
# define asynchronous function to get miner and data # define asynchronous function to get miner and data
async def get_miner_data(miner_ip: str): async def get_miner_data(miner_ip: str):
@@ -127,11 +102,6 @@ import sys
from pyasic.miners import get_miner 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_api_commands(miner_ip: str): async def get_api_commands(miner_ip: str):
# Get the miner # Get the miner
@@ -155,11 +125,6 @@ import sys
from pyasic.miners import get_miner 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_api_commands(miner_ip: str): async def get_api_commands(miner_ip: str):
# Get the miner # Get the miner

View File

@@ -23,3 +23,11 @@
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
<br>
## Innosilicon Error Codes
::: pyasic.data.error_codes.InnosiliconError
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -101,3 +101,165 @@ async def gather_miner_data(): # define async scan function to allow awaiting
if __name__ == "__main__": if __name__ == "__main__":
asyncio.run(gather_miner_data()) asyncio.run(gather_miner_data())
``` ```
<br>
## Controlling miners via pyasic
Every miner class in pyasic must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
These functions are
[`check_light`](#check-light),
[`fault_light_off`](#fault-light-off),
[`fault_light_on`](#fault-light-on),
[`get_config`](#get-config),
[`get_data`](#get-data),
[`get_errors`](#get-errors),
[`get_hostname`](#get-hostname),
[`get_model`](#get-model),
[`reboot`](#reboot),
[`restart_backend`](#restart-backend), and
[`stop_mining`](#stop-mining), and
[`resume_mining`](#resume-mining), and
[`send_config`](#send-config).
<br>
### Check Light
::: pyasic.miners.BaseMiner.check_light
handler: python
options:
heading_level: 4
<br>
### Fault Light Off
::: pyasic.miners.BaseMiner.fault_light_off
handler: python
options:
heading_level: 4
<br>
### Fault Light On
::: pyasic.miners.BaseMiner.fault_light_on
handler: python
options:
heading_level: 4
<br>
### Get Config
::: pyasic.miners.BaseMiner.get_config
handler: python
options:
heading_level: 4
<br>
### Get Data
::: pyasic.miners.BaseMiner.get_data
handler: python
options:
heading_level: 4
<br>
### Get Errors
::: pyasic.miners.BaseMiner.get_errors
handler: python
options:
heading_level: 4
<br>
### Get Hostname
::: pyasic.miners.BaseMiner.get_hostname
handler: python
options:
heading_level: 4
<br>
### Get Model
::: pyasic.miners.BaseMiner.get_model
handler: python
options:
heading_level: 4
<br>
### Reboot
::: pyasic.miners.BaseMiner.reboot
handler: python
options:
heading_level: 4
<br>
### Restart Backend
::: pyasic.miners.BaseMiner.restart_backend
handler: python
options:
heading_level: 4
<br>
### Stop Mining
::: pyasic.miners.BaseMiner.stop_mining
handler: python
options:
heading_level: 4
<br>
### Resume Mining
::: pyasic.miners.BaseMiner.resume_mining
handler: python
options:
heading_level: 4
<br>
### Send Config
::: pyasic.miners.BaseMiner.send_config
handler: python
options:
heading_level: 4
<br>
## [`MinerConfig`][pyasic.config.MinerConfig] and [`MinerData`][pyasic.data.MinerData]
Pyasic implements a few dataclasses as helpers to make data return types consistent across different miners and miner APIs. The different fields of these dataclasses can all be viewed with the classmethod `cls.fields()`.
<br>
### [`MinerData`][pyasic.data.MinerData]
[`MinerData`][pyasic.data.MinerData] is a return from the [`get_data()`](#get-data) function, and is used to have a consistent dataset across all returns.
You can call [`MinerData.asdict()`][pyasic.data.MinerData.asdict] to get the dataclass as a dictionary, and there are many other helper functions contained in the class to convert to different data formats.
[`MinerData`][pyasic.data.MinerData] instances can also be added to each other to combine their data and can be divided by a number to divide all their data, allowing you to get average data from many miners by doing -
```python
from pyasic import MinerData
# examples of miner data
d1 = MinerData("192.168.1.1")
d2 = MinerData("192.168.1.2")
list_of_miner_data = [d1, d2]
average_data = sum(list_of_miner_data, start=MinerData("0.0.0.0"))/len(list_of_miner_data)
```
<br>
### [`MinerConfig`][pyasic.config.MinerConfig]
[`MinerConfig`][pyasic.config.MinerConfig] is pyasic's way to represent a configuration file from a miner.
It is the return from [`get_config()`](#get-config).
Each miner has a unique way to convert the [`MinerConfig`][pyasic.config.MinerConfig] to their specific type, there are helper functions in the class.
In most cases these helper functions should not be used, as [`send_config()`](#send-config) takes a [`MinerConfig`][pyasic.config.MinerConfig] and will do the conversion to the right type for you.

View File

@@ -43,6 +43,14 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## S19 XP
::: pyasic.miners.antminer.bmminer.X19.S19_XP.BMMinerS19XP
handler: python
options:
show_root_heading: false
heading_level: 4
## T19 ## T19
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19 ::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19

View File

@@ -4,93 +4,237 @@
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. 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: ##### pyasic currently supports the following miners and subtypes:
* Braiins OS+ Devices: <style>
* X19 Series: details {
* [S19][pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19] margin:0px;
* [S19 Pro][pyasic.miners.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro] padding-top:0px;
* [S19j][pyasic.miners.antminer.bosminer.X19.S19j.BOSMinerS19j] padding-bottom:0px;
* [S19j Pro][pyasic.miners.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro] }
* [T19][pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19] </style>
* X17 Series: <details style="margin:0px; padding-top:0px; padding-bottom:0px;">
* [S17][pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17] <summary>Braiins OS+ Devices:</summary>
* [S17+][pyasic.miners.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus] <ul>
* [S17 Pro][pyasic.miners.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro] <details>
* [S17e][pyasic.miners.antminer.bosminer.X17.S17e.BOSMinerS17e] <summary>X19 Series:</summary>
* [T17][pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17] <ul>
* [T17+][pyasic.miners.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus] <li><a href="../antminer/X19#s19-bos">S19</a></li>
* [T17e][pyasic.miners.antminer.bosminer.X17.T17e.BOSMinerT17e] <li><a href="../antminer/X19#s19-pro-bos">S19 Pro</a></li>
* X9 Series: <li><a href="../antminer/X19#s19j-bos">S19j</a></li>
* [S9][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9] <li><a href="../antminer/X19#s19j-pro-bos">S19j Pro</a></li>
* [S9i][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9] <li><a href="../antminer/X19#t19-bos">T19</a></li>
* [S9j][pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9] </ul>
* Stock Firmware Whatsminers: </details>
* M3X Series: <details>
* [M30S][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S]: <summary>X17 Series:</summary>
* [VE10][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10] <ul>
* [VG20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20] <li><a href="../antminer/X17#s17-bos">S17</a></li>
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20] <li><a href="../antminer/X17#s17-plus-bos">S17+</a></li>
* [V50][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50] <li><a href="../antminer/X17#s17-pro-bos">S17 Pro</a></li>
* [M30S+][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlus]: <li><a href="../antminer/X17#s17e-bos">S17e</a></li>
* [VF20][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20] <li><a href="../antminer/X17#t17-bos">T17</a></li>
* [VE40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40] <li><a href="../antminer/X17#t17-plus-bos">T17+</a></li>
* [VG60][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60] <li><a href="../antminer/X17#t17e-bos">T17e</a></li>
* [M30S++][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus]: </ul>
* [VG30][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30] </details>
* [VG40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40] <details>
* [VH60][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH60] <summary>X9 Series:</summary>
* [M31S][pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S] <ul>
* [M31S+][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus]: <li><a href="../antminer/X9#s9-bos">S9</a></li>
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20] <li><a href="../antminer/X9#s9-bos">S9i</a></li>
* [M32][pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32] <li><a href="../antminer/X9#s9-bos">S9j</a></li>
* [V20][pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V20] </ul>
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S] </details>
* M2X Series: </ul>
* [M20][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20]: </details>
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10] <details>
* [M20S][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S]: <summary>Stock Firmware Whatsminers:</summary>
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10] <ul>
* [V20][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20] <details>
* [M20S+][pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus] <summary>M5X Series:</summary>
* [M21][pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21] <ul>
* [M21S][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S]: <details>
* [V20][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20] <summary><a href="../whatsminer/M5X/#m50">M50</a></summary>
* [V60][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60] <ul>
* [M21S+][pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus] <li><a href="../whatsminer/M5X/#m50vh50">VH50</a></li>
* Stock Firmware Antminers: </ul>
* X19 Series: </details>
* [S19][pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19] </ul>
* [S19 Pro][pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro] </details>
* [S19a][pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a] <details>
* [S19j][pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j] <summary>M3X Series:</summary>
* [S19j Pro][pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro] <ul>
* [T19][pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19] <details>
* X17 Series: <summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
* [S17][pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17] <ul>
* [S17+][pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus] <li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
* [S17 Pro][pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro] <li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
* [S17e][pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e] <li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
* [T17][pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17] <li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
* [T17+][pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus] </ul>
* [T17e][pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e] </details>
* X9 Series: <details>
* [S9][pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9] <summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
* [S9i][pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i] <ul>
* [T9][pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9] <li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
* Stock Firmware Avalonminers: <li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
* A7X Series: <li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
* [A721][pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721] </ul>
* [A741][pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741] </details>
* [A761][pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761] <details>
* A8X Series: <summary><a href="../whatsminer/M3X/#m30s_2">M30S++</a></summary>
* [A821][pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821] <ul>
* [A841][pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841] <li><a href="../whatsminer/M3X/#m30svg30">VG30</a></li>
* [A851][pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851] <li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
* A9X Series: <li><a href="../whatsminer/M3X/#m30svh60">VH60</a></li>
* [A921][pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921] </ul>
* A10X Series: </details>
* [A1026][pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026] <details>
* [A1047][pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047] <summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
* [A1066][pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066] </details>
* Stock Firmware Innosilicon Miners: <details>
* T3X Series: <summary><a href="../whatsminer/M3X/#m31s_1">M31S+</a></summary>
* [T3H+][pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus] <ul>
<li><a href="../whatsminer/M3X/#m31sve20">VE20</a></li>
<li><a href="../whatsminer/M3X/#m31sv30">V30</a></li>
<li><a href="../whatsminer/M3X/#m31sv40">V40</a></li>
<li><a href="../whatsminer/M3X/#m31sv60">V60</a></li>
<li><a href="../whatsminer/M3X/#m31sv80">V80</a></li>
<li><a href="../whatsminer/M3X/#m31sv90">V90</a></li>
</ul>
</details>
<details>
<summary><a href="../whatsminer/M3X/#m32">M32</a></summary>
<ul>
<li><a href="../whatsminer/M3X/#m32v20">V20</a></li>
</ul>
</details>
<details>
<summary><a href="../whatsminer/M3X/#m32s">M32S</a></summary>
</details>
<details>
<summary><a href="../whatsminer/M3X/#m34s">M34S+</a></summary>
<ul>
<li><a href="../whatsminer/M3X/#m34sve10">VE10</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>M2X Series:</summary>
<ul>
<details>
<summary><a href="../whatsminer/M2X/#m20">M20</a></summary>
<ul>
<li><a href="../whatsminer/M2X/#m20v10">V10</a></li>
</ul>
</details>
<details>
<summary><a href="../whatsminer/M2X/#m20s">M20S</a></summary>
<ul>
<li><a href="../whatsminer/M2X/#m20sv10">V10</a></li>
<li><a href="../whatsminer/M2X/#m20sv20">V20</a></li>
</ul>
</details>
<details>
<summary><a href="../whatsminer/M2X/#m20s_1">M20S+</a></summary>
</details>
<details>
<summary><a href="../whatsminer/M2X/#m21">M21</a></summary>
</details>
<details>
<summary><a href="../whatsminer/M2X/#m21s">M21S</a></summary>
<ul>
<li><a href="../whatsminer/M2X/#m21sv20">V20</a></li>
<li><a href="../whatsminer/M2X/#m21sv60">V60</a></li>
</ul>
</details>
<details>
<summary><a href="../whatsminer/M2X/#m21s_1">M21S+</a></summary>
</details>
</ul>
</details>
</ul>
</details>
<details>
<summary>Stock Firmware Antminers:</summary>
<ul>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="../antminer/X19/#s19">S19</a></li>
<li><a href="../antminer/X19/#s19-pro">S19 Pro</a></li>
<li><a href="../antminer/X19/#s19a">S19a</a></li>
<li><a href="../antminer/X19/#s19j">S19j</a></li>
<li><a href="../antminer/X19/#s19j-pro">S19j Pro</a></li>
<li><a href="../antminer/X19/#s19-xp">S19 XP</a></li>
<li><a href="../antminer/X19/#t19">T19</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="../antminer/X17/#s17">S17</a></li>
<li><a href="../antminer/X17/#s17_1">S17+</a></li>
<li><a href="../antminer/X17/#s17-pro">S17 Pro</a></li>
<li><a href="../antminer/X17/#s17e">S17e</a></li>
<li><a href="../antminer/X17/#t17">T17</a></li>
<li><a href="../antminer/X17/#t17_1">T17+</a></li>
<li><a href="../antminer/X17/#t17e">T17e</a></li>
</ul>
</details>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="../antminer/X9/#s9">S9</a></li>
<li><a href="../antminer/X9/#s9i">S9i</a></li>
<li><a href="../antminer/X9/#t9">T9</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>Stock Firmware Avalonminers:</summary>
<ul>
<details>
<summary>A7X Series:</summary>
<ul>
<li><a href="../avalonminer/A7X/#a721">A721</a></li>
<li><a href="../avalonminer/A7X/#a741">A741</a></li>
<li><a href="../avalonminer/A7X/#a761">A761</a></li>
</ul>
</details>
<details>
<summary>A8X Series:</summary>
<ul>
<li><a href="../avalonminer/A8X/#a821">A821</a></li>
<li><a href="../avalonminer/A8X/#a841">A841</a></li>
<li><a href="../avalonminer/A8X/#a851">A851</a></li>
</ul>
</details>
<details>
<summary>A9X Series:</summary>
<ul>
<li><a href="../avalonminer/A9X/#a921">A921</a></li>
</ul>
</details>
<details>
<summary>A10X Series:</summary>
<ul>
<li><a href="../avalonminer/A10X/#a1026">A1026</a></li>
<li><a href="../avalonminer/A10X/#a1047">A1047</a></li>
<li><a href="../avalonminer/A10X/#a1066">A1066</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>Stock Firmware Innosilicon Miners:</summary>
<ul>
<details>
<summary>T3X Series:</summary>
<ul>
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li>
</ul>
</details>
</ul>
</details>

View File

@@ -130,6 +130,46 @@
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M31S+V30
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+V40
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV40
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+V60
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV60
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+V80
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV80
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+V90
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV90
handler: python
options:
show_root_heading: false
heading_level: 4
## M32 ## M32
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32 ::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32
@@ -153,3 +193,19 @@
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## M34S+
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlus
handler: python
options:
show_root_heading: false
heading_level: 4
## M34S+VE10
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlusVE10
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -0,0 +1,18 @@
# pyasic
## M5X Models
## M50
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50
handler: python
options:
show_root_heading: false
heading_level: 4
## M50VH50
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -11,7 +11,6 @@ nav:
- BTMiner: "miners/backends/btminer.md" - BTMiner: "miners/backends/btminer.md"
- CGMiner: "miners/backends/cgminer.md" - CGMiner: "miners/backends/cgminer.md"
- Hiveon: "miners/backends/hiveon.md" - Hiveon: "miners/backends/hiveon.md"
- Classes: - Classes:
- Antminer X9: "miners/antminer/X9.md" - Antminer X9: "miners/antminer/X9.md"
- Antminer X17: "miners/antminer/X17.md" - Antminer X17: "miners/antminer/X17.md"
@@ -22,15 +21,14 @@ nav:
- Avalon 10X: "miners/avalonminer/A10X.md" - Avalon 10X: "miners/avalonminer/A10X.md"
- Whatsminer M2X: "miners/whatsminer/M2X.md" - Whatsminer M2X: "miners/whatsminer/M2X.md"
- Whatsminer M3X: "miners/whatsminer/M3X.md" - Whatsminer M3X: "miners/whatsminer/M3X.md"
- Whatsminer M5X: "miners/whatsminer/M5X.md"
- Innosilicon T3X: "miners/innosilicon/T3X.md" - Innosilicon T3X: "miners/innosilicon/T3X.md"
- Network: - Network:
- Miner Network: "network/miner_network.md" - Miner Network: "network/miner_network.md"
- Miner Network Range: "network/miner_network_range.md" - Miner Network Range: "network/miner_network_range.md"
- Data: - Dataclasses:
- Miner Data: "data/miner_data.md" - Miner Data: "data/miner_data.md"
- Error Codes: "data/error_codes.md" - Error Codes: "data/error_codes.md"
- Config:
- Miner Config: "config/miner_config.md" - Miner Config: "config/miner_config.md"
- Advanced: - Advanced:
- Miner APIs: - Miner APIs:

647
poetry.lock generated
View File

@@ -11,8 +11,8 @@ idna = ">=2.8"
sniffio = ">=1.1" sniffio = ">=1.1"
[package.extras] [package.extras]
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"] test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
trio = ["trio (>=0.16)"] trio = ["trio (>=0.16)"]
[[package]] [[package]]
@@ -38,7 +38,7 @@ pywin32 = ["pywin32 (>=227)"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2022.6.15" version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main" category = "main"
optional = false optional = false
@@ -55,9 +55,36 @@ python-versions = "*"
[package.dependencies] [package.dependencies]
pycparser = "*" pycparser = "*"
[[package]]
name = "cfgv"
version = "3.3.1"
description = "Validate configuration and produce human readable error messages."
category = "dev"
optional = false
python-versions = ">=3.6.1"
[[package]]
name = "click"
version = "8.1.3"
description = "Composable command line interface toolkit"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
colorama = {version = "*", markers = "platform_system == \"Windows\""}
[[package]]
name = "colorama"
version = "0.4.5"
description = "Cross-platform colored terminal text."
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package]] [[package]]
name = "cryptography" name = "cryptography"
version = "37.0.4" version = "38.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main" category = "main"
optional = false optional = false
@@ -68,11 +95,56 @@ cffi = ">=1.12"
[package.extras] [package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
sdist = ["setuptools_rust (>=0.11.4)"] sdist = ["setuptools-rust (>=0.11.4)"]
ssh = ["bcrypt (>=3.1.5)"] ssh = ["bcrypt (>=3.1.5)"]
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
[[package]]
name = "distlib"
version = "0.3.6"
description = "Distribution utilities"
category = "dev"
optional = false
python-versions = "*"
[[package]]
name = "filelock"
version = "3.8.0"
description = "A platform independent file lock."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "ghp-import"
version = "2.1.0"
description = "Copy your docs directly to the gh-pages branch."
category = "dev"
optional = false
python-versions = "*"
[package.dependencies]
python-dateutil = ">=2.8.1"
[package.extras]
dev = ["flake8", "markdown", "twine", "wheel"]
[[package]]
name = "griffe"
version = "0.22.2"
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
async = ["aiofiles (>=0.7,<1.0)"]
[[package]] [[package]]
name = "h11" name = "h11"
@@ -115,19 +187,181 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*" sniffio = "*"
[package.extras] [package.extras]
brotli = ["brotlicffi", "brotli"] brotli = ["brotli", "brotlicffi"]
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"] cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
http2 = ["h2 (>=3,<5)"] http2 = ["h2 (>=3,<5)"]
socks = ["socksio (>=1.0.0,<2.0.0)"] socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "identify"
version = "2.5.5"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
license = ["ukkonen"]
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.3" version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
[[package]]
name = "importlib-metadata"
version = "4.12.0"
description = "Read metadata from Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
zipp = ">=0.5"
[package.extras]
docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
perf = ["ipython"]
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
[[package]]
name = "Jinja2"
version = "3.1.2"
description = "A very fast and expressive template engine."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
MarkupSafe = ">=2.0"
[package.extras]
i18n = ["Babel (>=2.7)"]
[[package]]
name = "Markdown"
version = "3.3.7"
description = "Python implementation of Markdown."
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
[package.extras]
testing = ["coverage", "pyyaml"]
[[package]]
name = "MarkupSafe"
version = "2.1.1"
description = "Safely add untrusted strings to HTML/XML markup."
category = "dev"
optional = false
python-versions = ">=3.7"
[[package]]
name = "mergedeep"
version = "1.3.4"
description = "A deep merge function for 🐍."
category = "dev"
optional = false
python-versions = ">=3.6"
[[package]]
name = "mkdocs"
version = "1.4.0"
description = "Project documentation with Markdown."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
click = ">=7.0"
ghp-import = ">=1.0"
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
Jinja2 = ">=2.11.1"
Markdown = ">=3.2.1,<3.4"
mergedeep = ">=1.3.4"
packaging = ">=20.5"
PyYAML = ">=5.1"
pyyaml-env-tag = ">=0.1"
watchdog = ">=2.0"
[package.extras]
i18n = ["babel (>=2.9.0)"]
[[package]]
name = "mkdocs-autorefs"
version = "0.4.1"
description = "Automatically link across pages in MkDocs."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
Markdown = ">=3.3"
mkdocs = ">=1.1"
[[package]]
name = "mkdocstrings"
version = "0.19.0"
description = "Automatic documentation from sources, for MkDocs."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
Jinja2 = ">=2.11.1"
Markdown = ">=3.3"
MarkupSafe = ">=1.1"
mkdocs = ">=1.2"
mkdocs-autorefs = ">=0.3.1"
mkdocstrings-python = {version = ">=0.5.2", optional = true, markers = "extra == \"python\""}
pymdown-extensions = ">=6.3"
[package.extras]
crystal = ["mkdocstrings-crystal (>=0.3.4)"]
python = ["mkdocstrings-python (>=0.5.2)"]
python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
[[package]]
name = "mkdocstrings-python"
version = "0.7.1"
description = "A Python handler for mkdocstrings."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
griffe = ">=0.11.1"
mkdocstrings = ">=0.19"
[[package]]
name = "nodeenv"
version = "1.7.0"
description = "Node.js virtual environment builder"
category = "dev"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
[package.dependencies]
setuptools = "*"
[[package]]
name = "packaging"
version = "21.3"
description = "Core utilities for Python packages"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
[[package]] [[package]]
name = "passlib" name = "passlib"
version = "1.7.4" version = "1.7.4"
@@ -139,9 +373,37 @@ python-versions = "*"
[package.extras] [package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"] argon2 = ["argon2-cffi (>=18.2.0)"]
bcrypt = ["bcrypt (>=3.1.0)"] bcrypt = ["bcrypt (>=3.1.0)"]
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"] build_docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
totp = ["cryptography"] totp = ["cryptography"]
[[package]]
name = "platformdirs"
version = "2.5.2"
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
[[package]]
name = "pre-commit"
version = "2.20.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
cfgv = ">=2.0.0"
identify = ">=1.0.0"
nodeenv = ">=0.11.1"
pyyaml = ">=5.1"
toml = "*"
virtualenv = ">=20.0.8"
[[package]] [[package]]
name = "pyaml" name = "pyaml"
version = "21.10.1" version = "21.10.1"
@@ -162,13 +424,57 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[package]] [[package]]
name = "pyyaml" name = "pymdown-extensions"
version = "9.6"
description = "Extension pack for Python Markdown."
category = "dev"
optional = false
python-versions = ">=3.7"
[package.dependencies]
markdown = ">=3.2"
[[package]]
name = "pyparsing"
version = "3.0.9"
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
category = "dev"
optional = false
python-versions = ">=3.6.8"
[package.extras]
diagrams = ["jinja2", "railroad-diagrams"]
[[package]]
name = "python-dateutil"
version = "2.8.2"
description = "Extensions to the standard Python datetime module"
category = "dev"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
[package.dependencies]
six = ">=1.5"
[[package]]
name = "PyYAML"
version = "6.0" version = "6.0"
description = "YAML parser and emitter for Python" description = "YAML parser and emitter for Python"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
[[package]]
name = "pyyaml_env_tag"
version = "0.1"
description = "A custom YAML tag for referencing environment variables in YAML files. "
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
pyyaml = "*"
[[package]] [[package]]
name = "rfc3986" name = "rfc3986"
version = "1.5.0" version = "1.5.0"
@@ -183,13 +489,34 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
[package.extras] [package.extras]
idna2008 = ["idna"] idna2008 = ["idna"]
[[package]]
name = "setuptools"
version = "65.4.1"
description = "Easily download, build, install, upgrade, and uninstall Python packages"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]]
name = "six"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
category = "dev"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.2.0" version = "1.3.0"
description = "Sniff out which async library your code is running under" description = "Sniff out which async library your code is running under"
category = "main" category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.7"
[[package]] [[package]]
name = "toml" name = "toml"
@@ -207,10 +534,50 @@ category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
[[package]]
name = "virtualenv"
version = "20.16.5"
description = "Virtual Python Environment builder"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.dependencies]
distlib = ">=0.3.5,<1"
filelock = ">=3.4.1,<4"
platformdirs = ">=2.4,<3"
[package.extras]
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
[[package]]
name = "watchdog"
version = "2.1.9"
description = "Filesystem events monitoring"
category = "dev"
optional = false
python-versions = ">=3.6"
[package.extras]
watchmedo = ["PyYAML (>=3.10)"]
[[package]]
name = "zipp"
version = "3.8.1"
description = "Backport of pathlib-compatible object wrapper for zip files"
category = "dev"
optional = false
python-versions = ">=3.7"
[package.extras]
docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
[metadata] [metadata]
lock-version = "1.1" lock-version = "1.1"
python-versions = "^3.9" python-versions = "^3.9"
content-hash = "8d93eafd928d7fed4b0a00d13e46982c2d4310c37acb2faec7e7a477b3f35e9c" content-hash = "98c3026a6f27c29c0357bbfa07166d6d8b604a869f3a802adc3bb3610f86964c"
[metadata.files] [metadata.files]
anyio = [ anyio = [
@@ -222,8 +589,8 @@ asyncssh = [
{file = "asyncssh-2.12.0.tar.gz", hash = "sha256:274101322c4b941823aeed8e1ab6e7be5191686c6db2d2bd35afeba30505e780"}, {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.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"}, {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
] ]
cffi = [ cffi = [
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
@@ -291,29 +658,61 @@ cffi = [
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
] ]
cfgv = [
{file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"},
{file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"},
]
click = [
{file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"},
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
]
colorama = [
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
]
cryptography = [ cryptography = [
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, {file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, {file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, {file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, {file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, {file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, {file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, {file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, {file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, {file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, {file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, {file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
{file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
{file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
]
distlib = [
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
]
filelock = [
{file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
{file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
]
ghp-import = [
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
]
griffe = [
{file = "griffe-0.22.2-py3-none-any.whl", hash = "sha256:cea5415ac6a92f4a22638e3f1f2e661402bac09fb8e8266936d67185a7e0d0fb"},
{file = "griffe-0.22.2.tar.gz", hash = "sha256:1408e336a4155392bbd81eed9f2f44bf144e71b9c664e905630affe83bbc088e"},
] ]
h11 = [ h11 = [
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"}, {file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
@@ -327,14 +726,108 @@ httpx = [
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"}, {file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"}, {file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
] ]
identify = [
{file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"},
{file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"},
]
idna = [ idna = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
]
importlib-metadata = [
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
]
Jinja2 = [
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
{file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
]
Markdown = [
{file = "Markdown-3.3.7-py3-none-any.whl", hash = "sha256:f5da449a6e1c989a4cea2631aa8ee67caa5a2ef855d551c88f9e309f4634c621"},
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
]
MarkupSafe = [
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
]
mergedeep = [
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
]
mkdocs = [
{file = "mkdocs-1.4.0-py3-none-any.whl", hash = "sha256:ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa"},
{file = "mkdocs-1.4.0.tar.gz", hash = "sha256:e5549a22d59e7cb230d6a791edd2c3d06690908454c0af82edc31b35d57e3069"},
]
mkdocs-autorefs = [
{file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
]
mkdocstrings = [
{file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"},
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
]
mkdocstrings-python = [
{file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
{file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
]
nodeenv = [
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
]
packaging = [
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
] ]
passlib = [ passlib = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"}, {file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"}, {file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
] ]
platformdirs = [
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
]
pre-commit = [
{file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
{file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
]
pyaml = [ pyaml = [
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"}, {file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
{file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"}, {file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
@@ -343,7 +836,19 @@ pycparser = [
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
] ]
pyyaml = [ pymdown-extensions = [
{file = "pymdown_extensions-9.6-py3-none-any.whl", hash = "sha256:1e36490adc7bfcef1fdb21bb0306e93af99cff8ec2db199bd17e3bf009768c11"},
{file = "pymdown_extensions-9.6.tar.gz", hash = "sha256:b956b806439bbff10f726103a941266beb03fbe99f897c7d5e774d7170339ad9"},
]
pyparsing = [
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
]
python-dateutil = [
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
{file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"},
]
PyYAML = [
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
@@ -351,6 +856,13 @@ pyyaml = [
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
{file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"},
{file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"},
{file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"},
{file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"},
{file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"},
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
@@ -378,13 +890,25 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
] ]
pyyaml_env_tag = [
{file = "pyyaml_env_tag-0.1-py3-none-any.whl", hash = "sha256:af31106dec8a4d68c60207c1886031cbf839b68aa7abccdb19868200532c2069"},
{file = "pyyaml_env_tag-0.1.tar.gz", hash = "sha256:70092675bda14fdec33b31ba77e7543de9ddc88f2e5b99160396572d11525bdb"},
]
rfc3986 = [ rfc3986 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"}, {file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"}, {file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
] ]
setuptools = [
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
]
six = [
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
sniffio = [ sniffio = [
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"}, {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"}, {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
] ]
toml = [ toml = [
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
@@ -394,3 +918,38 @@ typing-extensions = [
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"}, {file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"}, {file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
] ]
virtualenv = [
{file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"},
{file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
]
watchdog = [
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
{file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
{file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
{file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
{file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
{file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
{file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
{file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
{file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
{file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
{file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
{file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
{file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
{file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
]
zipp = [
{file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
{file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
]

View File

@@ -13,39 +13,14 @@
# limitations under the License. # limitations under the License.
import asyncio import asyncio
import json
import ipaddress import ipaddress
import warnings import json
import logging import logging
import re
import warnings
from typing import Union from typing import Union
from pyasic.errors import APIError, APIWarning
class APIError(Exception):
def __init__(self, *args):
if args:
self.message = args[0]
else:
self.message = None
def __str__(self):
if self.message:
return f"{self.message}"
else:
return "Incorrect API parameters."
class APIWarning(Warning):
def __init__(self, *args):
if args:
self.message = args[0]
else:
self.message = None
def __str__(self):
if self.message:
return f"{self.message}"
else:
return "Incorrect API parameters."
class BaseMinerAPI: class BaseMinerAPI:
@@ -72,8 +47,8 @@ class BaseMinerAPI:
# each function in self # each function in self
dir(self) dir(self)
if callable(getattr(self, func)) and if callable(getattr(self, func)) and
# no __ methods # no __ or _ methods
not func.startswith("__") and not func.startswith("__") and not func.startswith("_") and
# remove all functions that are in this base class # remove all functions that are in this base class
func func
not in [ not in [
@@ -97,81 +72,42 @@ If you are sure you want to use this command please use API.send_command("{comma
) )
return return_commands return return_commands
async def multicommand( async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
self, *commands: str, ignore_x19_error: bool = False
) -> dict:
"""Creates and sends multiple commands as one command to the miner. """Creates and sends multiple commands as one command to the miner.
Parameters: Parameters:
*commands: The commands to send as a multicommand to the miner. *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. allow_warning: A boolean to supress APIWarnings.
""" """
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}") logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail # make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands) commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2" # standard multicommand format is "command1+command2"
# doesnt work for S19 which uses the backup _x19_multicommand # doesn't work for S19 which uses the backup _x19_multicommand
command = "+".join(commands) command = "+".join(commands)
try: try:
data = await self.send_command(command, x19_command=ignore_x19_error) data = await self.send_command(command, allow_warning=allow_warning)
except APIError: except APIError:
logging.debug(f"{self.ip}: Handling X19 multicommand.") return {}
data = await self._x19_multicommand(*command.split("+"))
logging.debug(f"{self.ip}: Received multicommand data.") logging.debug(f"{self.ip}: Received multicommand data.")
return data return data
async def _x19_multicommand(self, *commands): async def _send_bytes(self, data: bytes) -> bytes:
data = None
try:
data = {}
# send all commands individually
for cmd in commands:
data[cmd] = []
data[cmd].append(await self.send_command(cmd, x19_command=True))
except APIError as e:
raise APIError(e)
except Exception as e:
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
return data
async def send_command(
self,
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
x19_command: bool = False,
) -> dict:
"""Send an API command to the miner and return the result.
Parameters:
command: The command to sent to the miner.
parameters: Any additional parameters to be sent with the command.
ignore_errors: Whether or not to raise APIError when the command returns an error.
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
Returns:
The return data from the API command parsed from JSON into a dict.
"""
try: 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)
# handle OSError 121 # handle OSError 121
except OSError as e: except OSError as e:
if e.winerror == "121": if getattr(e, "winerror") == "121":
logging.warning("Semaphore Timeout has Expired.") logging.warning("Semaphore Timeout has Expired.")
return {} return b"{}"
# create the command
cmd = {"command": command}
if parameters:
cmd["parameter"] = parameters
# send the command # send the command
writer.write(json.dumps(cmd).encode("utf-8")) writer.write(data)
await writer.drain() await writer.drain()
# instantiate data # instantiate data
data = b"" ret_data = b""
# loop to receive all the data # loop to receive all the data
try: try:
@@ -179,27 +115,60 @@ If you are sure you want to use this command please use API.send_command("{comma
d = await reader.read(4096) d = await reader.read(4096)
if not d: if not d:
break break
data += d ret_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)
# close the connection # close the connection
writer.close() writer.close()
await writer.wait_closed() await writer.wait_closed()
return ret_data
async def send_command(
self,
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
allow_warning: bool = True,
) -> dict:
"""Send an API command to the miner and return the result.
Parameters:
command: The command to sent to the miner.
parameters: Any additional parameters to be sent with the command.
ignore_errors: Whether to raise APIError when the command returns an error.
allow_warning: Whether to warn if the command fails.
Returns:
The return data from the API command parsed from JSON into a dict.
"""
# create the command
cmd = {"command": command}
if parameters:
cmd["parameter"] = parameters
# send the command
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
data = self._load_api_data(data)
# check for if the user wants to allow errors to return # 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 allow_warning:
logging.warning(f"{self.ip}: API Command Error: {validation[1]}") logging.warning(
f"{self.ip}: API Command Error: {command}: {validation[1]}"
)
raise APIError(validation[1]) raise APIError(validation[1])
return data return data
async def send_privileged_command(self, *args, **kwargs) -> dict:
return await self.send_command(*args, **kwargs)
@staticmethod @staticmethod
def _validate_command_output(data: dict) -> tuple: def _validate_command_output(data: dict) -> tuple:
# check if the data returned is correct or an error # check if the data returned is correct or an error
@@ -230,31 +199,39 @@ If you are sure you want to use this command please use API.send_command("{comma
@staticmethod @staticmethod
def _load_api_data(data: bytes) -> dict: def _load_api_data(data: bytes) -> dict:
str_data = None str_data = None
# some json from the API returns with a null byte (\x00) on the end
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode("utf-8")[:-1]
else:
# no null byte
str_data = data.decode("utf-8")
# fix an error with a btminer return having an extra comma that breaks json.loads()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()
str_data = str_data.replace("\n", "")
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
str_data = str_data.replace("}{", "},{")
# fix an error with a bmminer return having a specific comma that breaks json.loads()
str_data = str_data.replace("[,{", "[{")
# fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("inf", "0")
str_data = str_data.replace("nan", "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):
str_data = f"{{{str_data[1:]}"
# try to fix an error with overflowing the receive buffer
# this can happen in cases such as bugged btminers returning arbitrary length error info with 100s of errors.
if not str_data.endswith("}"):
str_data = ",".join(str_data.split(",")[:-1]) + "}"
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
str_data = str_data.replace("[", "{").replace("]", "}")
# parse the json
try: try:
# some json from the API returns with a null byte (\x00) on the end
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode("utf-8")[:-1]
else:
# no null byte
str_data = data.decode("utf-8")
# fix an error with a btminer return having an extra comma that breaks json.loads()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()
str_data = str_data.replace("\n", "")
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
str_data = str_data.replace("}{", "},{")
# fix an error with a bmminer return having a specific comma that breaks json.loads()
str_data = str_data.replace("[,{", "[{")
# fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("inf", "0")
str_data = str_data.replace("nan", "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):
str_data = f"{{{str_data[1:]}"
# parse the json
parsed_data = json.loads(str_data) parsed_data = json.loads(str_data)
# handle bad json
except json.decoder.JSONDecodeError as e: except json.decoder.JSONDecodeError as e:
raise APIError(f"Decode Error {e}: {str_data}") raise APIError(f"Decode Error {e}: {str_data}")
return parsed_data return parsed_data

View File

@@ -11,8 +11,9 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
from pyasic.API import BaseMinerAPI from pyasic.API import APIError, BaseMinerAPI
class BMMinerAPI(BaseMinerAPI): class BMMinerAPI(BaseMinerAPI):
@@ -36,6 +37,37 @@ class BMMinerAPI(BaseMinerAPI):
def __init__(self, ip: str, port: int = 4028) -> None: def __init__(self, ip: str, port: int = 4028) -> None:
super().__init__(ip, port) super().__init__(ip, port)
async def multicommand(
self, *commands: str, allow_warning: bool = True
) -> dict:
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
# doesn't work for S19 which uses the backup _x19_multicommand
command = "+".join(commands)
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
logging.debug(f"{self.ip}: Handling X19 multicommand.")
data = await self._x19_multicommand(*command.split("+"))
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _x19_multicommand(self, *commands):
data = None
try:
data = {}
# send all commands individually
for cmd in commands:
data[cmd] = []
data[cmd].append(await self.send_command(cmd, allow_warning=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 version(self) -> dict: async def version(self) -> dict:
"""Get miner version info. """Get miner version info.
<details> <details>

View File

@@ -13,21 +13,21 @@
# limitations under the License. # limitations under the License.
import asyncio import asyncio
import re
import json
import hashlib
import binascii
import base64 import base64
import binascii
import hashlib
import json
import logging import logging
import re
from typing import Union from typing import Union
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 passlib.handlers.md5_crypt import md5_crypt
from pyasic.API import BaseMinerAPI, APIError from pyasic.API import BaseMinerAPI
from pyasic.errors import APIError
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
### IMPORTANT ### ### IMPORTANT ###
# you need to change the password of the miners using the Whatsminer # you need to change the password of the miners using the Whatsminer
# tool, then you can set them back to admin with this tool, but they # tool, then you can set them back to admin with this tool, but they
@@ -186,57 +186,24 @@ class BTMinerAPI(BaseMinerAPI):
self.pwd = pwd self.pwd = pwd
self.current_token = None self.current_token = None
async def send_command( async def send_privileged_command(
self, self, command: Union[str, bytes], ignore_errors: bool = False, **kwargs
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
**kwargs,
) -> dict: ) -> dict:
# check if command is a string command = {"cmd": command}
# if its bytes its encoded and needs to be sent raw for kwarg in kwargs:
if isinstance(command, str): if kwargs[kwarg]:
# if it is a string, put it into the standard command format command[kwarg] = kwargs[kwarg]
command = json.dumps({"command": command}).encode("utf-8")
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
# handle OSError 121
except OSError as e:
if e.winerror == "121":
print("Semaphore Timeout has Expired.")
return {}
# send the command token_data = await self.get_token()
writer.write(command) enc_command = create_privileged_cmd(token_data, command)
await writer.drain()
# instantiate data
data = b""
# loop to receive all the data
try:
while True:
d = await reader.read(4096)
if not d:
break
data += d
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
data = await self._send_bytes(enc_command)
data = self._load_api_data(data) data = self._load_api_data(data)
# close the connection try:
writer.close() data = parse_btminer_priviledge_data(self.current_token, data)
await writer.wait_closed() except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
# check if the returned data is encoded
if "enc" in data.keys():
# try to parse the encoded data
try:
data = parse_btminer_priviledge_data(self.current_token, data)
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
if not ignore_errors: if not ignore_errors:
# if it fails to validate, it is likely an error # if it fails to validate, it is likely an error
@@ -319,46 +286,18 @@ class BTMinerAPI(BaseMinerAPI):
A dict from the API to confirm the pools were updated. A dict from the API to confirm the pools were updated.
</details> </details>
""" """
# get the token and password from the miner return await self.send_privileged_command(
token_data = await self.get_token() "update_pools",
pool1=pool_1,
# parse pool data worker1=worker_1,
if not pool_1: passwd1=passwd_1,
raise APIError("No pools set.") pool2=pool_2,
elif pool_2 and pool_3: worker2=worker_2,
command = { passwd2=passwd_2,
"cmd": "update_pools", pool3=pool_3,
"pool1": pool_1, worker3=worker_3,
"worker1": worker_1, passwd3=passwd_3,
"passwd1": passwd_1, )
"pool2": pool_2,
"worker2": worker_2,
"passwd2": passwd_2,
"pool3": pool_3,
"worker3": worker_3,
"passwd3": passwd_3,
}
elif pool_2:
command = {
"cmd": "update_pools",
"pool1": pool_1,
"worker1": worker_1,
"passwd1": passwd_1,
"pool2": pool_2,
"worker2": worker_2,
"passwd2": passwd_2,
}
else:
command = {
"cmd": "update_pools",
"pool1": pool_1,
"worker1": worker_1,
"passwd1": passwd_1,
}
# encode the command with the token data
enc_command = create_privileged_cmd(token_data, command)
# send the command
return await self.send_command(enc_command)
async def restart(self) -> dict: async def restart(self) -> dict:
"""Restart BTMiner using the API. """Restart BTMiner using the API.
@@ -372,10 +311,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the restart. A reply informing of the restart.
</details> </details>
""" """
command = {"cmd": "restart_btminer"} return await self.send_privileged_command("restart_btminer")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def power_off(self, respbefore: bool = True) -> dict: async def power_off(self, respbefore: bool = True) -> dict:
"""Power off the miner using the API. """Power off the miner using the API.
@@ -392,12 +328,8 @@ class BTMinerAPI(BaseMinerAPI):
</details> </details>
""" """
if respbefore: if respbefore:
command = {"cmd": "power_off", "respbefore": "true"} return await self.send_privileged_command("power_off", respbefore="true")
else: return await self.send_privileged_command("power_off", respbefore="false")
command = {"cmd": "power_off", "respbefore": "false"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def power_on(self) -> dict: async def power_on(self) -> dict:
"""Power on the miner using the API. """Power on the miner using the API.
@@ -412,10 +344,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of powering on. A reply informing of the status of powering on.
</details> </details>
""" """
command = {"cmd": "power_on"} return await self.send_privileged_command("power_on")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def reset_led(self) -> dict: async def reset_led(self) -> dict:
"""Reset the LED on the miner using the API. """Reset the LED on the miner using the API.
@@ -430,10 +359,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of resetting the LED. A reply informing of the status of resetting the LED.
</details> </details>
""" """
command = {"cmd": "set_led", "param": "auto"} return await self.set_led(auto=True)
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def set_led( async def set_led(
self, self,
@@ -461,19 +387,11 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting the LED. A reply informing of the status of setting the LED.
</details> </details>
""" """
if not auto: if auto:
command = { return await self.send_privileged_command("set_led", param=auto)
"cmd": "set_led", return await self.send_privileged_command(
"color": color, "set_led", color=color, period=period, duration=duration, start=start
"period": period, )
"duration": duration,
"start": start,
}
else:
command = {"cmd": "set_led", "param": "auto"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command, ignore_errors=True)
async def set_low_power(self) -> dict: 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.
@@ -488,10 +406,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting low power mode. A reply informing of the status of setting low power mode.
</details> </details>
""" """
command = {"cmd": "set_low_power"} return await self.send_privileged_command("set_low_power")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def update_firmware(self): # noqa - static async def update_firmware(self): # noqa - static
"""Not implemented.""" """Not implemented."""
@@ -509,10 +424,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of the reboot. A reply informing of the status of the reboot.
</details> </details>
""" """
command = {"cmd": "reboot"} return await self.send_privileged_command("reboot")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def factory_reset(self) -> dict: async def factory_reset(self) -> dict:
"""Reset the miner to factory defaults. """Reset the miner to factory defaults.
@@ -524,10 +436,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of the reset. A reply informing of the status of the reset.
</details> </details>
""" """
command = {"cmd": "factory_reset"} return await self.send_privileged_command("factory_reset")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict: async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
"""Update the admin user's password. """Update the admin user's password.
@@ -554,11 +463,10 @@ class BTMinerAPI(BaseMinerAPI):
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}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
try: try:
data = await self.send_command(enc_command) data = await self.send_privileged_command(
"update_pwd", old=old_pwd, new=new_pwd
)
except APIError as e: except APIError as e:
raise e raise e
self.pwd = new_pwd self.pwd = new_pwd
@@ -587,10 +495,9 @@ class BTMinerAPI(BaseMinerAPI):
f"range. Please set a % between -10 and " f"range. Please set a % between -10 and "
f"100" f"100"
) )
command = {"cmd": "set_target_freq", "percent": str(percent)} return await self.send_privileged_command(
token_data = await self.get_token() "set_target_freq", percent=str(percent)
enc_command = create_privileged_cmd(token_data, command) )
return await self.send_command(enc_command)
async def enable_fast_boot(self) -> dict: async def enable_fast_boot(self) -> dict:
"""Turn on fast boot. """Turn on fast boot.
@@ -606,10 +513,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of enabling fast boot. A reply informing of the status of enabling fast boot.
</details> </details>
""" """
command = {"cmd": "enable_btminer_fast_boot"} return await self.send_privileged_command("enable_btminer_fast_boot")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def disable_fast_boot(self) -> dict: async def disable_fast_boot(self) -> dict:
"""Turn off fast boot. """Turn off fast boot.
@@ -625,10 +529,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of disabling fast boot. A reply informing of the status of disabling fast boot.
</details> </details>
""" """
command = {"cmd": "disable_btminer_fast_boot"} return await self.send_privileged_command("disable_btminer_fast_boot")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def enable_web_pools(self) -> dict: async def enable_web_pools(self) -> dict:
"""Turn on web pool updates. """Turn on web pool updates.
@@ -644,10 +545,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of enabling web pools. A reply informing of the status of enabling web pools.
</details> </details>
""" """
command = {"cmd": "enable_web_pools"} return await self.send_privileged_command("enable_web_pools")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def disable_web_pools(self) -> dict: async def disable_web_pools(self) -> dict:
"""Turn off web pool updates. """Turn off web pool updates.
@@ -663,10 +561,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of disabling web pools. A reply informing of the status of disabling web pools.
</details> </details>
""" """
command = {"cmd": "disable_web_pools"} return await self.send_privileged_command("disable_web_pools")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def set_hostname(self, hostname: str) -> dict: async def set_hostname(self, hostname: str) -> dict:
"""Set the hostname of the miner. """Set the hostname of the miner.
@@ -684,10 +579,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting the hostname. A reply informing of the status of setting the hostname.
</details> </details>
""" """
command = {"cmd": "set_hostname", "hostname": hostname} return await self.send_privileged_command("set_hostname", hostname=hostname)
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def set_power_pct(self, percent: int) -> dict: async def set_power_pct(self, percent: int) -> dict:
"""Set the power percentage of the miner. """Set the power percentage of the miner.
@@ -712,10 +604,7 @@ class BTMinerAPI(BaseMinerAPI):
f"range. Please set a % between 0 and " f"range. Please set a % between 0 and "
f"100" f"100"
) )
command = {"cmd": "set_power_pct", "percent": str(percent)} return await self.send_privileged_command("set_power_pct", percent=str(percent))
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def pre_power_on(self, complete: bool, msg: str) -> dict: 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.
@@ -746,13 +635,12 @@ class BTMinerAPI(BaseMinerAPI):
'"adjust continue"]' '"adjust continue"]'
) )
if complete: if complete:
complete = "true" return await self.send_privileged_command(
else: "pre_power_on", complete="true", msg=msg
complete = "false" )
command = {"cmd": "pre_power_on", "complete": complete, "msg": msg} return await self.send_privileged_command(
token_data = await self.get_token() "pre_power_on", complete="false", msg=msg
enc_command = create_privileged_cmd(token_data, command) )
return await self.send_command(enc_command)
#### END privileged COMMANDS #### #### END privileged COMMANDS ####
@@ -878,4 +766,18 @@ class BTMinerAPI(BaseMinerAPI):
General miner info. General miner info.
</details> </details>
""" """
return await self.send_command("get_miner_info") return await self.send_command("get_miner_info", allow_warning=False)
async def get_error_code(self) -> dict:
"""Get a list of error codes from the miner.
<details>
<summary>Expand</summary>
Get a list of error codes from the miner. Replaced `summary` as the location of error codes with API version 2.0.4.
Returns:
A list of error codes on the miner.
</details>
"""
return await self.send_command("get_error_code", allow_warning=False)

View File

@@ -12,7 +12,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.API import BaseMinerAPI import logging
from pyasic.API import APIError, BaseMinerAPI
class CGMinerAPI(BaseMinerAPI): class CGMinerAPI(BaseMinerAPI):
@@ -36,6 +38,37 @@ class CGMinerAPI(BaseMinerAPI):
def __init__(self, ip: str, port: int = 4028): def __init__(self, ip: str, port: int = 4028):
super().__init__(ip, port) super().__init__(ip, port)
async def multicommand(
self, *commands: str, allow_warning: bool = True
) -> dict:
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
# make sure we can actually run each command, otherwise they will fail
commands = self._check_commands(*commands)
# standard multicommand format is "command1+command2"
# doesnt work for S19 which uses the backup _x19_multicommand
command = "+".join(commands)
try:
data = await self.send_command(command, allow_warning=allow_warning)
except APIError:
logging.debug(f"{self.ip}: Handling X19 multicommand.")
data = await self._x19_multicommand(*command.split("+"))
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _x19_multicommand(self, *commands):
data = None
try:
data = {}
# send all commands individually
for cmd in commands:
data[cmd] = []
data[cmd].append(await self.send_command(cmd, allow_warning=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 version(self) -> dict: async def version(self) -> dict:
"""Get miner version info. """Get miner version info.
<details> <details>

View File

@@ -11,3 +11,45 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.API.bmminer import BMMinerAPI
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.API.btminer import BTMinerAPI
from pyasic.API.cgminer import CGMinerAPI
from pyasic.API.unknown import UnknownAPI
from pyasic.config import MinerConfig
from pyasic.data import (
BraiinsOSError,
InnosiliconError,
MinerData,
WhatsminerError,
X19Error,
)
from pyasic.errors import APIError, APIWarning
from pyasic.miners import get_miner
from pyasic.miners.base import AnyMiner
from pyasic.miners.miner_factory import MinerFactory
from pyasic.miners.miner_listener import MinerListener
from pyasic.network import MinerNetwork
from pyasic.settings import PyasicSettings
__all__ = [
"BMMinerAPI",
"BOSMinerAPI",
"BTMinerAPI",
"CGMinerAPI",
"UnknownAPI",
"MinerConfig",
"MinerData",
"BraiinsOSError",
"InnosiliconError",
"WhatsminerError",
"X19Error",
"APIError",
"APIWarning",
"get_miner",
"AnyMiner",
"MinerFactory",
"MinerListener",
"MinerNetwork",
"PyasicSettings",
]

View File

@@ -12,15 +12,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from dataclasses import dataclass, asdict import json
from typing import Literal, List
import random import random
import string import string
import time
from dataclasses import asdict, dataclass, fields
from typing import List, Literal
import toml import toml
import yaml import yaml
import json
import time
@dataclass @dataclass
@@ -37,6 +37,10 @@ class _Pool:
username: str = "" username: str = ""
password: str = "" password: str = ""
@classmethod
def fields(cls):
return fields(cls)
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.
@@ -136,6 +140,10 @@ class _PoolGroup:
group_name: str = None group_name: str = None
pools: List[_Pool] = None pools: List[_Pool] = None
@classmethod
def fields(cls):
return fields(cls)
def __post_init__(self): def __post_init__(self):
if not self.group_name: if not self.group_name:
self.group_name = "".join( self.group_name = "".join(
@@ -247,7 +255,7 @@ class MinerConfig:
temp_mode: Literal["auto", "manual", "disabled"] = "auto" temp_mode: Literal["auto", "manual", "disabled"] = "auto"
temp_target: float = 70.0 temp_target: float = 70.0
temp_hot: float = 80.0 temp_hot: float = 80.0
temp_dangerous: float = 10.0 temp_dangerous: float = 100.0
minimum_fans: int = None minimum_fans: int = None
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
@@ -263,6 +271,10 @@ class MinerConfig:
dps_shutdown_enabled: bool = None dps_shutdown_enabled: bool = None
dps_shutdown_duration: float = None dps_shutdown_duration: float = None
@classmethod
def fields(cls):
return fields(cls)
def as_dict(self) -> dict: 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)
@@ -299,6 +311,10 @@ class MinerConfig:
self.temp_mode = "manual" self.temp_mode = "manual"
if data.get("bitmain-fan-pwm"): if data.get("bitmain-fan-pwm"):
self.fan_speed = int(data["bitmain-fan-pwm"]) self.fan_speed = int(data["bitmain-fan-pwm"])
elif key == "bitmain-work-mode":
if data[key]:
if data[key] == 1:
self.autotuning_wattage = 0
elif key == "fan_control": elif key == "fan_control":
for _key in data[key].keys(): for _key in data[key].keys():
if _key == "min_fans": if _key == "min_fans":
@@ -409,7 +425,10 @@ class MinerConfig:
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix), "pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
"bitmain-fan-ctrl": False, "bitmain-fan-ctrl": False,
"bitmain-fan-pwn": 100, "bitmain-fan-pwn": 100,
"miner-mode": 0, # Normal Mode
} }
if self.autotuning_wattage == 0:
cfg["miner-mode"] = 1 # Sleep Mode
if not self.temp_mode == "auto": if not self.temp_mode == "auto":
cfg["bitmain-fan-ctrl"] = True cfg["bitmain-fan-ctrl"] = True

View File

@@ -12,13 +12,26 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from typing import Union, List import copy
from dataclasses import dataclass, field, asdict
from datetime import datetime, timezone
import time
import json import json
import time
from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone
from functools import reduce
from typing import List, Union
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
@dataclass
class HashBoard:
slot: int = 0
hashrate: float = 0.0
temp: int = -1
chip_temp: int = -1
chips: int = 0
expected_chips: int = 0
missing: bool = True
@dataclass @dataclass
@@ -48,6 +61,7 @@ class MinerData:
fan_2: The speed of the second fan as an int. fan_2: The speed of the second fan as an int.
fan_3: The speed of the third fan as an int. fan_3: The speed of the third fan as an int.
fan_4: The speed of the fourth fan as an int. fan_4: The speed of the fourth fan as an int.
fan_psu: The speed of the PSU on the fan if the miner collects it.
left_chips: The number of chips online in the left board as an int. left_chips: The number of chips online in the left board as an int.
center_chips: The number of chips online in the left board as an int. center_chips: The number of chips online in the left board as an int.
right_chips: The number of chips online in the left board as an int. right_chips: The number of chips online in the left board as an int.
@@ -71,26 +85,29 @@ 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 hashboards: List[HashBoard] = field(default_factory=list)
center_board_hashrate: float = 0.0 ideal_hashboards: int = 1
right_board_hashrate: float = 0.0 left_board_hashrate: float = field(init=False)
center_board_hashrate: float = field(init=False)
right_board_hashrate: float = field(init=False)
temperature_avg: int = field(init=False) temperature_avg: int = field(init=False)
env_temp: float = 0.0 env_temp: float = -1.0
left_board_temp: int = 0 left_board_temp: int = field(init=False)
left_board_chip_temp: int = 0 left_board_chip_temp: int = field(init=False)
center_board_temp: int = 0 center_board_temp: int = field(init=False)
center_board_chip_temp: int = 0 center_board_chip_temp: int = field(init=False)
right_board_temp: int = 0 right_board_temp: int = field(init=False)
right_board_chip_temp: int = 0 right_board_chip_temp: int = field(init=False)
wattage: int = 0 wattage: int = -1
wattage_limit: int = 0 wattage_limit: int = -1
fan_1: int = -1 fan_1: int = -1
fan_2: int = -1 fan_2: int = -1
fan_3: int = -1 fan_3: int = -1
fan_4: int = -1 fan_4: int = -1
left_chips: int = 0 fan_psu: int = -1
center_chips: int = 0 left_chips: int = field(init=False)
right_chips: int = 0 center_chips: int = field(init=False)
right_chips: int = field(init=False)
total_chips: int = field(init=False) total_chips: int = field(init=False)
ideal_chips: int = 1 ideal_chips: int = 1
percent_ideal: float = field(init=False) percent_ideal: float = field(init=False)
@@ -106,6 +123,11 @@ class MinerData:
fault_light: Union[bool, None] = None fault_light: Union[bool, None] = None
efficiency: int = field(init=False) efficiency: int = field(init=False)
@classmethod
def fields(cls):
return [f.name for f in fields(cls)]
def __post_init__(self): def __post_init__(self):
self.datetime = datetime.now(timezone.utc).astimezone() self.datetime = datetime.now(timezone.utc).astimezone()
@@ -121,14 +143,187 @@ class MinerData:
def __iter__(self): def __iter__(self):
return iter([item for item in self.asdict()]) return iter([item for item in self.asdict()])
def __truediv__(self, other):
return self // other
def __floordiv__(self, other):
cp = copy.deepcopy(self)
for key in self:
item = getattr(self, key)
if isinstance(item, int):
setattr(cp, key, item // other)
if isinstance(item, float):
setattr(cp, key, round(item / other, 2))
return cp
def __add__(self, other):
if not isinstance(other, MinerData):
raise TypeError("Cannot add MinerData to non MinerData type.")
cp = copy.deepcopy(self)
for key in self:
item = getattr(self, key)
other_item = getattr(other, key)
if item is None:
item = 0
if other_item is None:
other_item = 0
if isinstance(item, int):
setattr(cp, key, item + other_item)
if isinstance(item, float):
setattr(cp, key, item + other_item)
if isinstance(item, str):
setattr(cp, key, "")
if isinstance(item, list):
setattr(cp, key, item + other_item)
if isinstance(item, bool):
setattr(cp, key, item & other_item)
return cp
@property @property
def total_chips(self): # noqa - Skip PyCharm inspection def total_chips(self): # noqa - Skip PyCharm inspection
return self.right_chips + self.center_chips + self.left_chips return sum([hb.chips for hb in self.hashboards])
@total_chips.setter @total_chips.setter
def total_chips(self, val): def total_chips(self, val):
pass pass
@property
def left_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3]:
return self.hashboards[0].chips
return 0
@left_chips.setter
def left_chips(self, val):
pass
@property
def center_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].chips
if len(self.hashboards) == 3:
return self.hashboards[1].chips
return 0
@center_chips.setter
def center_chips(self, val):
pass
@property
def right_chips(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].chips
if len(self.hashboards) == 3:
return self.hashboards[2].chips
return 0
@right_chips.setter
def right_chips(self, val):
pass
@property
def left_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3]:
return self.hashboards[0].hashrate
return 0
@left_board_hashrate.setter
def left_board_hashrate(self, val):
pass
@property
def center_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].hashrate
if len(self.hashboards) == 3:
return self.hashboards[1].hashrate
return 0
@center_board_hashrate.setter
def center_board_hashrate(self, val):
pass
@property
def right_board_hashrate(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].hashrate
if len(self.hashboards) == 3:
return self.hashboards[2].hashrate
return 0
@right_board_hashrate.setter
def right_board_hashrate(self, val):
pass
@property
def left_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3]:
return self.hashboards[0].temp
return 0
@left_board_temp.setter
def left_board_temp(self, val):
pass
@property
def center_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].temp
if len(self.hashboards) == 3:
return self.hashboards[1].temp
return 0
@center_board_temp.setter
def center_board_temp(self, val):
pass
@property
def right_board_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].temp
if len(self.hashboards) == 3:
return self.hashboards[2].temp
return 0
@right_board_temp.setter
def right_board_temp(self, val):
pass
@property
def left_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) in [2, 3]:
return self.hashboards[0].chip_temp
return 0
@left_board_chip_temp.setter
def left_board_chip_temp(self, val):
pass
@property
def center_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 1:
return self.hashboards[0].chip_temp
if len(self.hashboards) == 3:
return self.hashboards[1].chip_temp
return 0
@center_board_chip_temp.setter
def center_board_chip_temp(self, val):
pass
@property
def right_board_chip_temp(self): # noqa - Skip PyCharm inspection
if len(self.hashboards) == 2:
return self.hashboards[1].chip_temp
if len(self.hashboards) == 3:
return self.hashboards[2].chip_temp
return 0
@right_board_chip_temp.setter
def right_board_chip_temp(self, val):
pass
@property @property
def nominal(self): # noqa - Skip PyCharm inspection def nominal(self): # noqa - Skip PyCharm inspection
return self.ideal_chips == self.total_chips return self.ideal_chips == self.total_chips
@@ -149,13 +344,9 @@ class MinerData:
def temperature_avg(self): # noqa - Skip PyCharm inspection def temperature_avg(self): # noqa - Skip PyCharm inspection
total_temp = 0 total_temp = 0
temp_count = 0 temp_count = 0
for temp in [ for hb in self.hashboards:
self.left_board_chip_temp, if hb.temp and not hb.temp == -1:
self.center_board_chip_temp, total_temp += hb.temp
self.right_board_chip_temp,
]:
if temp and not temp == 0:
total_temp += temp
temp_count += 1 temp_count += 1
if not temp_count > 0: if not temp_count > 0:
return 0 return 0
@@ -175,15 +366,48 @@ class MinerData:
def efficiency(self, val): def efficiency(self, val):
pass pass
def asdict(self): def asdict(self) -> dict:
"""Get this dataclass as a dictionary.
Returns:
A dictionary version of this class.
"""
return asdict(self) return asdict(self)
def as_json(self): def as_json(self) -> str:
"""Get this dataclass as JSON.
Returns:
A JSON version of this class.
"""
data = self.asdict() data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple()))) data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data) return json.dumps(data)
def as_influxdb(self, measurement_name: str = "miner_data"): def as_csv(self) -> str:
"""Get this dataclass as CSV.
Returns:
A CSV version of this class with no headers.
"""
data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
errs = []
for error in data["errors"]:
errs.append(error["error_message"])
data["errors"] = "; ".join(errs)
data_list = [str(data[item]) for item in data]
return ",".join(data_list)
def as_influxdb(self, measurement_name: str = "miner_data") -> str:
"""Get this dataclass as [influxdb line protocol](https://docs.influxdata.com/influxdb/v2.4/reference/syntax/line-protocol/).
Parameters:
measurement_name: The name of the measurement to insert into in influxdb.
Returns:
A influxdb line protocol version of this class.
"""
tag_data = [measurement_name] tag_data = [measurement_name]
field_data = [] field_data = []

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from dataclasses import dataclass, asdict from dataclasses import asdict, dataclass, fields
@dataclass @dataclass
@@ -21,9 +21,15 @@ class X19Error:
Attributes: Attributes:
error_message: The error message as a string. error_message: The error message as a string.
error_code: The error code as an int. 0 if the message is not assigned a code.
""" """
error_message: str error_message: str
error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self): def asdict(self):
return asdict(self) return asdict(self)

View File

@@ -12,7 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .whatsminer import WhatsminerError from typing import TypeVar
from .bos import BraiinsOSError from .bos import BraiinsOSError
from .X19 import X19Error
from .innosilicon import InnosiliconError from .innosilicon import InnosiliconError
from .whatsminer import WhatsminerError
from .X19 import X19Error
MinerErrorData = TypeVar(
"MinerErrorData", WhatsminerError, BraiinsOSError, X19Error, InnosiliconError
)

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from dataclasses import dataclass, asdict from dataclasses import asdict, dataclass, fields
@dataclass @dataclass
@@ -21,9 +21,16 @@ class BraiinsOSError:
Attributes: Attributes:
error_message: The error message as a string. error_message: The error message as a string.
error_code: The error code as an int. 0 if the message is not assigned a code.
""" """
error_message: str error_message: str
error_code: int = 0
@classmethod
def fields(cls):
return fields(cls)
def asdict(self): def asdict(self):
return asdict(self) return asdict(self)

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from dataclasses import dataclass, field, asdict from dataclasses import asdict, dataclass, field, fields
@dataclass @dataclass
@@ -27,6 +27,11 @@ class InnosiliconError:
error_code: int error_code: int
error_message: str = field(init=False) error_message: str = field(init=False)
@classmethod
def fields(cls):
return fields(cls)
@property @property
def error_message(self): # noqa - Skip PyCharm inspection def error_message(self): # noqa - Skip PyCharm inspection
if self.error_code in ERROR_CODES: if self.error_code in ERROR_CODES:

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from dataclasses import dataclass, field, asdict from dataclasses import asdict, dataclass, field, fields
@dataclass @dataclass
@@ -27,11 +27,25 @@ class WhatsminerError:
error_code: int error_code: int
error_message: str = field(init=False) error_message: str = field(init=False)
@classmethod
def fields(cls):
return fields(cls)
@property @property
def error_message(self): # noqa - Skip PyCharm inspection def error_message(self): # noqa - Skip PyCharm inspection
if self.error_code in ERROR_CODES: err_type = int(str(self.error_code)[:-2])
return ERROR_CODES[self.error_code] err_subtype = int(str(self.error_code)[-2:-1])
return "Unknown error type." err_value = int(str(self.error_code)[-1:])
try:
select_err_subtype = ERROR_CODES[err_type][err_subtype]
if err_value in select_err_subtype:
return select_err_subtype[err_value]
elif "n" in select_err_subtype:
return select_err_subtype["n"].replace("{n}", str(err_value)) # noqa: picks up `select_err_subtype["n"]` as not being numeric?
else:
return "Unknown error type."
except KeyError:
return "Unknown error type."
@error_message.setter @error_message.setter
def error_message(self, val): def error_message(self, val):
@@ -42,131 +56,158 @@ class WhatsminerError:
ERROR_CODES = { ERROR_CODES = {
110: "Intake fan speed error.", 1: { # Fan error
111: "Exhaust fan speed error.", 1: { # Fan speed error of 1000+
120: "Intake fan speed error. Fan speed deviates by more than 2000.", 0: "Intake fan speed error.",
121: "Exhaust fan speed error. Fan speed deviates by more than 2000.", 1: "Exhaust fan speed error.",
130: "Intake fan speed error. Fan speed deviates by more than 3000.", },
131: "Exhaust fan speed error. Fan speed deviates by more than 3000.", 2: { # Fan speed error of 2000+
140: "Fan speed too high.", 0: "Intake fan speed error. Fan speed deviates by more than 2000.",
200: "Power probing error. No power found.", 1: "Exhaust fan speed error. Fan speed deviates by more than 2000.",
201: "Power supply and configuration file don't match.", },
202: "Power output voltage error.", 3: { # Fan speed error of 3000+
203: "Power protecting due to high environment temperature.", 0: "Intake fan speed error. Fan speed deviates by more than 3000.",
204: "Power current protecting due to high environment temperature.", 1: "Exhaust fan speed error. Fan speed deviates by more than 3000.",
205: "Power current error.", },
206: "Power input low voltage error.", 4: {0: "Fan speed too high."}, # High speed
207: "Power input current protecting due to bad power input.", },
210: "Power error.", 2: { # Power error
213: "Power input voltage and current do not match power output.", 0: {
216: "Power remained unchanged for a long time.", 0: "Power probing error. No power found.",
217: "Power set enable error.", 1: "Power supply and configuration file don't match.",
218: "Power input voltage is lower than 230V for high power mode.", 2: "Power output voltage error.",
233: "Power output high temperature protection error.", 3: "Power protecting due to high environment temperature.",
234: "Power output high temperature protection error.", 4: "Power current protecting due to high environment temperature.",
235: "Power output high temperature protection error.", 5: "Power current error.",
236: "Power output high current protection error.", 6: "Power input low voltage error.",
237: "Power output high current protection error.", 7: "Power input current protecting due to bad power input.",
238: "Power output high current protection error.", },
239: "Power output high voltage protection error.", 1: {
240: "Power output low voltage protection error.", 0: "Power error.",
241: "Power output current imbalance error.", 3: "Power input voltage and current do not match power output.",
243: "Power input high temperature protection error.", 6: "Power remained unchanged for a long time.",
244: "Power input high temperature protection error.", 7: "Power set enable error.",
245: "Power input high temperature protection error.", 8: "Power input voltage is lower than 230V for high power mode.",
246: "Power input high voltage protection error.", },
247: "Power input high voltage protection error.", 3: {
248: "Power input high current protection error.", 3: "Power output high temperature protection error.",
249: "Power input high current protection error.", 4: "Power output high temperature protection error.",
250: "Power input low voltage protection error.", 5: "Power output high temperature protection error.",
251: "Power input low voltage protection error.", 6: "Power output high current protection error.",
253: "Power supply fan error.", 7: "Power output high current protection error.",
254: "Power supply fan error.", 8: "Power output high current protection error.",
255: "Power output high power protection error.", 9: "Power output high voltage protection error.",
256: "Power output high power protection error.", },
257: "Input over current protection of power supply on primary side.", 4: {
263: "Power communication warning.", 0: "Power output low voltage protection error.",
264: "Power communication error.", 1: "Power output current imbalance error.",
267: "Power watchdog protection.", 3: "Power input high temperature protection error.",
268: "Power output high current protection.", 4: "Power input high temperature protection error.",
269: "Power input high current protection.", 5: "Power input high temperature protection error.",
270: "Power input high voltage protection.", 6: "Power input high voltage protection error.",
271: "Power input low voltage protection.", 7: "Power input high voltage protection error.",
272: "Excessive power supply output warning.", 8: "Power input high current protection error.",
273: "Power input too high warning.", 9: "Power input high current protection error.",
274: "Power fan warning.", },
275: "Power high temperature warning.", 5: {
300: "Right board temperature sensor detection error.", 0: "Power input low voltage protection error.",
301: "Center board temperature sensor detection error.", 1: "Power input low voltage protection error.",
302: "Left board temperature sensor detection error.", 3: "Power supply fan error.",
320: "Right board temperature reading error.", 4: "Power supply fan error.",
321: "Center board temperature reading error.", 5: "Power output high power protection error.",
322: "Left board temperature reading error.", 6: "Power output high power protection error.",
329: "Control board temperature sensor communication error.", 7: "Input over current protection of power supply on primary side.",
350: "Right board temperature protecting.", },
351: "Center board temperature protecting.", 6: {
352: "Left board temperature protecting.", 3: "Power communication warning.",
360: "Hashboard high temperature error.", 4: "Power communication error.",
410: "Right board eeprom detection error.", 7: "Power watchdog protection.",
411: "Center board eeprom detection error.", 8: "Power output high current protection.",
412: "Left board eeprom detection error.", 9: "Power input high current protection.",
420: "Right board eeprom parsing error.", },
421: "Center board eeprom parsing error.", 7: {
422: "Left board eeprom parsing error.", 0: "Power input high voltage protection.",
430: "Right board chip bin type error.", 1: "Power input low voltage protection.",
431: "Center board chip bin type error.", 2: "Excessive power supply output warning.",
432: "Left board chip bin type error.", 3: "Power input too high warning.",
440: "Right board eeprom chip number X error.", 4: "Power fan warning.",
441: "Center board eeprom chip number X error.", 5: "Power high temperature warning.",
442: "Left board eeprom chip number X error.", },
450: "Right board eeprom xfer error.", },
451: "Center board eeprom xfer error.", 3: { # temperature error
452: "Left board eeprom xfer error.", 0: { # sensor detection error
510: "Right board miner type error.", "n": "Slot {n} temperature sensor detection error."
511: "Center board miner type error.", },
512: "Left board miner type error.", 2: { # temperature reading error
520: "Right board bin type error.", "n": "Slot {n} temperature reading error.",
521: "Center board bin type error.", 9: "Control board temperature sensor communication error.",
522: "Left board bin type error.", },
530: "Right board not found.", 5: {"n": "Slot {n} temperature protecting."}, # temperature protection
531: "Center board not found.", 6: {0: "Hashboard high temperature error."}, # high temp
532: "Left board not found.", },
540: "Right board error reading chip id.", 4: { # EEPROM error
541: "Center board error reading chip id.", 1: {"n": "Slot {n} eeprom detection error."}, # EEPROM detection error
542: "Left board error reading chip id.", 2: {"n": "Slot {n} eeprom parsing error."}, # EEPROM parsing error
550: "Right board has bad chips.", 3: {"n": "Slot {n} chip bin type error."}, # chip bin error
551: "Center board has bad chips.", 4: {"n": "Slot {n} eeprom chip number X error."}, # EEPROM chip number error
552: "Left board has bad chips.", 5: {"n": "Slot {n} eeprom xfer error."}, # EEPROM xfer error
560: "Right board loss of balance error.", },
561: "Center board loss of balance error.", 5: { # hashboard error
562: "Left board loss of balance error.", 1: {"n": "Slot {n} miner type error."}, # board miner type error
600: "Environment temperature is too high.", 2: {"n": "Slot {n} bin type error."}, # chip bin type error
610: "Environment temperature is too high for high performance mode.", 3: {"n": "Slot {n} not found."}, # board not found error
701: "Control board no support chip.", 4: {"n": "Slot {n} error reading chip id."}, # reading chip id error
710: "Control board rebooted as an exception.", 5: {"n": "Slot {n} has bad chips."}, # board has bad chips error
712: "Control board rebooted as an exception.", 6: {"n": "Slot {n} loss of balance error."}, # loss of balance error
800: "CGMiner checksum error.", },
801: "System monitor checksum error.", 6: { # env temp error
802: "Remote daemon checksum error.", 0: {0: "Environment temperature is too high."}, # normal env temp error
2010: "All pools are disabled.", 1: { # high power env temp error
2020: "Pool 0 connection failed.", 0: "Environment temperature is too high for high performance mode."
2021: "Pool 1 connection failed.", },
2022: "Pool 2 connection failed.", },
2023: "Pool 3 connection failed.", 7: { # control board error
2030: "High rejection rate on pool.", 0: {1: "Control board no support chip."},
2040: "The pool does not support asicboost mode.", 1: {
2310: "Hashrate is too low.", 0: "Control board rebooted as an exception.",
2320: "Hashrate is too low.", 2: "Control board rebooted as an exception.",
2340: "Hashrate loss is too high.", },
2350: "Hashrate loss is too high.", },
5070: "Right hashboard water velocity is abnormal.", 8: { # checksum error
5071: "Center hashboard water velocity is abnormal.", 0: {
5072: "Left hashboard water velocity is abnormal.", 0: "CGMiner checksum error.",
5110: "Right hashboard frequency up timeout.", 1: "System monitor checksum error.",
5111: "Center hashboard frequency up timeout.", 2: "Remote daemon checksum error.",
5112: "Left hashboard frequency up timeout.", }
8410: "Software version error.", },
100001: "/antiv/signature illegal.", 20: { # pool error
100002: "/antiv/dig/init.d illegal.", 1: {0: "All pools are disabled."}, # all disabled error
100003: "/antiv/dig/pf_partial.dig illegal.", 2: {"n": "Pool {n} connection failed."}, # pool connection failed error
3: {0: "High rejection rate on pool."}, # rejection rate error
4: { # asicboost not supported error
0: "The pool does not support asicboost mode."
},
},
23: { # hashrate error
1: {0: "Hashrate is too low."},
2: {0: "Hashrate is too low."},
3: {0: "Hashrate loss is too high."},
4: {0: "Hashrate loss is too high."},
},
50: { # water velocity error
7: {"n": "Slot {n} water velocity is abnormal."}, # abnormal water velocity
},
51: { # frequency error
7: {"n": "Slot {n} frequency up timeout."}, # frequency up timeout
},
84: {
1: {0: "Software version error."},
},
1000: {
0: {
1: "/antiv/signature illegal.",
2: "/antiv/dig/init.d illegal.",
3: "/antiv/dig/pf_partial.dig illegal.",
},
},
} }

41
pyasic/errors/__init__.py Normal file
View File

@@ -0,0 +1,41 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class APIError(Exception):
def __init__(self, *args):
if args:
self.message = args[0]
else:
self.message = None
def __str__(self):
if self.message:
return f"{self.message}"
else:
return "Incorrect API parameters."
class APIWarning(Warning):
def __init__(self, *args):
if args:
self.message = args[0]
else:
self.message = None
def __str__(self):
if self.message:
return f"{self.message}"
else:
return "Incorrect API parameters."

View File

@@ -13,6 +13,7 @@
# limitations under the License. # limitations under the License.
import logging import logging
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings

View File

@@ -15,9 +15,10 @@
import ipaddress import ipaddress
from typing import Union from typing import Union
from pyasic.miners.base import BaseMiner, AnyMiner from pyasic.miners.base import AnyMiner, BaseMiner
from pyasic.miners.miner_factory import MinerFactory from pyasic.miners.miner_factory import MinerFactory
# abstracted version of get miner that is easier to access # abstracted version of get miner that is easier to access
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner: async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
return await MinerFactory().get_miner(ip) return await MinerFactory().get_miner(ip)

View File

@@ -14,15 +14,13 @@
import ipaddress import ipaddress
import logging import logging
from typing import Union from typing import List, Union
from pyasic.API.bmminer import BMMinerAPI from pyasic.API.bmminer import BMMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.data import MinerData
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
@@ -169,7 +167,7 @@ class BMMiner(BaseMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
return False return False
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
async def get_mac(self) -> str: async def get_mac(self) -> str:
@@ -178,13 +176,23 @@ class BMMiner(BaseMiner):
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
return False return False
async def get_data(self) -> MinerData: async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner. """Get data from the miner.
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. 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 * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1 board_offset = -1
fan_offset = -1 fan_offset = -1
@@ -212,7 +220,7 @@ class BMMiner(BaseMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_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", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -235,7 +243,7 @@ class BMMiner(BaseMiner):
if stats: if stats:
boards = stats.get("STATS") boards = stats.get("STATS")
if boards: if boards:
if len(boards) > 0: if len(boards) > 1:
for board_num in range(1, 16, 5): for board_num in range(1, 16, 5):
for _b_num in range(5): for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}") b = boards[1].get(f"chain_acn{board_num + _b_num}")
@@ -245,19 +253,38 @@ class BMMiner(BaseMiner):
if board_offset == -1: if board_offset == -1:
board_offset = 1 board_offset = 1
data.left_chips = boards[1].get(f"chain_acn{board_offset}") env_temp_list = []
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}") for i in range(board_offset, board_offset + self.ideal_hashboards):
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}") hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.nominal_chips
)
data.left_board_hashrate = round( chip_temp = boards[1].get(f"temp{i}")
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2 if chip_temp:
) hashboard.chip_temp = round(chip_temp)
data.center_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2 temp = boards[1].get(f"temp2_{i}")
) if temp:
data.right_board_hashrate = round( hashboard.temp = round(temp)
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
) hashrate = boards[1].get(f"chain_rate{i}")
if hashrate:
hashboard.hashrate = round(float(hashrate) / 1000, 2)
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
data.hashboards.append(hashboard)
if f"temp_pcb{i}" in boards[1].keys():
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
if not env_temp_list == []:
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
if stats: if stats:
temp = stats.get("STATS") temp = stats.get("STATS")
@@ -275,20 +302,6 @@ class BMMiner(BaseMiner):
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}") data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
) )
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
env_temp_list = []
for item in range(3):
board_temp = temp[1].get(f"temp{item + board_offset}")
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
setattr(data, f"{board_map[item]}_temp", board_temp)
if f"temp_pcb{item}" in temp[1].keys():
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
if not env_temp_list == []:
data.env_temp = sum(env_temp_list) / len(env_temp_list)
if pools: if pools:
pool_1 = None pool_1 = None
pool_2 = None pool_2 = None

View File

@@ -13,22 +13,19 @@
# limitations under the License. # limitations under the License.
import ipaddress import ipaddress
import logging
import json import json
from typing import Union import logging
from typing import List, Union
import toml import toml
import httpx
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.data.error_codes import BraiinsOSError
from pyasic.data import MinerData
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
@@ -69,6 +66,27 @@ class BOSMiner(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 send_graphql_query(self, query) -> Union[dict, None]:
url = f"http://{self.ip}/graphql"
try:
async with httpx.AsyncClient() as client:
_auth = await client.post(
url,
json={
"query": 'mutation{auth{login(username:"'
+ self.uname
+ '", password:"'
+ self.pwd
+ '"){__typename}}}'
},
)
d = await client.post(url, json={"query": query})
if d.status_code == 200:
return d.json()
except httpx.ReadError:
return None
return None
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.")
@@ -103,6 +121,20 @@ class BOSMiner(BaseMiner):
return True return True
return False return False
async def stop_mining(self) -> bool:
data = await self.api.pause()
if data.get("PAUSE"):
if data["PAUSE"][0]:
return True
return False
async def resume_mining(self) -> bool:
data = await self.api.resume()
if data.get("RESUME"):
if data["RESUME"][0]:
return True
return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboots power to the physical miner.""" """Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
@@ -138,6 +170,11 @@ class BOSMiner(BaseMiner):
""" """
if self.hostname: if self.hostname:
return self.hostname return self.hostname
# get hostname through GraphQL
if data := await self.send_graphql_query("{bos {hostname}}"):
self.hostname = data["data"]["bos"]["hostname"]
return self.hostname
try: try:
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
if conn is not None: if conn is not None:
@@ -201,9 +238,15 @@ class BOSMiner(BaseMiner):
if self.version: if self.version:
logging.debug(f"Found version for {self.ip}: {self.version}") logging.debug(f"Found version for {self.ip}: {self.version}")
return self.version return self.version
version_data = None
# try to get data from graphql
data = await self.send_graphql_query("{bos{info{version{full}}}}")
if data:
version_data = data["bos"]["info"]["version"]["full"]
# get output of bos version file if not version_data:
version_data = await self.send_ssh_command("cat /etc/bos_version") # try version data file
version_data = await self.send_ssh_command("cat /etc/bos_version")
# if we get the version data, parse it # if we get the version data, parse it
if version_data: if version_data:
@@ -234,6 +277,15 @@ class BOSMiner(BaseMiner):
async def check_light(self) -> bool: async def check_light(self) -> bool:
if self.light: if self.light:
return self.light return self.light
# get light through GraphQL
if data := await self.send_graphql_query("{bos {faultLight}}"):
try:
self.light = data["data"]["bos"]["faultLight"]
return self.light
except (TypeError, KeyError, ValueError, IndexError):
pass
# get light via ssh if that fails (10x slower)
data = ( data = (
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
).strip() ).strip()
@@ -242,7 +294,7 @@ class BOSMiner(BaseMiner):
self.light = True self.light = True
return self.light return self.light
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
tunerstatus = None tunerstatus = None
errors = [] errors = []
@@ -252,7 +304,10 @@ class BOSMiner(BaseMiner):
logging.warning(e) logging.warning(e)
if tunerstatus: if tunerstatus:
tuner = tunerstatus[0].get("TUNERSTATUS") try:
tuner = tunerstatus[0].get("TUNERSTATUS")
except KeyError:
tuner = tunerstatus.get("TUNERSTATUS")
if tuner: if tuner:
if len(tuner) > 0: if len(tuner) > 0:
chain_status = tuner[0].get("TunerChainStatus") chain_status = tuner[0].get("TunerChainStatus")
@@ -272,21 +327,34 @@ class BOSMiner(BaseMiner):
if board["Status"] not in [ if board["Status"] not in [
"Stable", "Stable",
"Testing performance profile", "Testing performance profile",
"Tuning individual chips"
]: ]:
_error = board["Status"] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
errors.append( errors.append(
BraiinsOSError(f"{board_map[_id]} {_error}") BraiinsOSError(f"{board_map[_id]} {_error}")
) )
return errors return errors
async def get_data(self) -> MinerData: async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner. """Get data from the miner.
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
""" """
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) d = await self._graphql_get_data()
if d:
return d
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
board_offset = -1 board_offset = -1
fan_offset = -1 fan_offset = -1
@@ -317,6 +385,7 @@ class BOSMiner(BaseMiner):
"devdetails", "devdetails",
"fans", "fans",
"devs", "devs",
allow_warning=allow_warning,
) )
except APIError as e: except APIError as e:
if str(e.message) == "Not ready": if str(e.message) == "Not ready":
@@ -347,14 +416,13 @@ class BOSMiner(BaseMiner):
temp = temps[0].get("TEMPS") temp = temps[0].get("TEMPS")
if temp: if temp:
if len(temp) > 0: if len(temp) > 0:
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"] offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
for board in temp: for board in temp:
_id = board["ID"] - offset _id = board["ID"] - offset
chip_temp = round(board["Chip"]) chip_temp = round(board["Chip"])
board_temp = round(board["Board"]) board_temp = round(board["Board"])
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp) data.hashboards[_id].chip_temp = chip_temp
setattr(data, f"{board_map[_id]}_temp", board_temp) data.hashboards[_id].temp = board_temp
if fans: if fans:
fan_data = fans[0].get("FANS") fan_data = fans[0].get("FANS")
@@ -437,7 +505,7 @@ class BOSMiner(BaseMiner):
"Stable", "Stable",
"Testing performance profile", "Testing performance profile",
]: ]:
_error = board["Status"] _error = board["Status"].split(" {")[0]
_error = _error[0].lower() + _error[1:] _error = _error[0].lower() + _error[1:]
data.errors.append( data.errors.append(
BraiinsOSError(f"{board_map[_id]} {_error}") BraiinsOSError(f"{board_map[_id]} {_error}")
@@ -447,27 +515,158 @@ class BOSMiner(BaseMiner):
boards = devdetails[0].get("DEVDETAILS") boards = devdetails[0].get("DEVDETAILS")
if boards: if boards:
if len(boards) > 0: if len(boards) > 0:
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"] offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
for board in boards: for board in boards:
_id = board["ID"] - offset _id = board["ID"] - offset
chips = board["Chips"] chips = board["Chips"]
setattr(data, board_map[_id], chips) data.hashboards[_id].chips = chips
if chips > 0:
data.hashboards[_id].missing = False
else:
data.hashboards[_id].missing = True
if devs: if devs:
boards = devs[0].get("DEVS") boards = devs[0].get("DEVS")
if boards: if boards:
if len(boards) > 0: 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"] offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
for board in boards: for board in boards:
_id = board["ID"] - offset _id = board["ID"] - offset
hashrate = round(board["MHS 1m"] / 1000000, 2) hashrate = round(board["MHS 1m"] / 1000000, 2)
setattr(data, board_map[_id], hashrate) data.hashboards[_id].hashrate = hashrate
return data
async def _graphql_get_data(self) -> Union[MinerData, None]:
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips, missing=True)
for i in range(self.ideal_hashboards)
],
)
query = "{bos {hostname}, bosminer{config{... on BosminerConfig{groups{pools{url, user}, strategy{... on QuotaStrategy {quota}}}}}, info{fans{name, rpm}, workSolver{realHashrate{mhs1M}, temperatures{degreesC}, power{limitW, approxConsumptionW}, childSolvers{name, realHashrate{mhs1M}, hwDetails{chips}, tuner{statusMessages}, temperatures{degreesC}}}}}}"
query_data = await self.send_graphql_query(query)
if not query_data:
return None
query_data = query_data["data"]
data.mac = await self.get_mac()
data.model = await self.get_model()
if query_data.get("bos"):
if query_data["bos"].get("hostname"):
data.hostname = query_data["bos"]["hostname"]
try:
if query_data["bosminer"]["info"]["workSolver"]["realHashrate"].get("mhs1M"):
data.hashrate = round(
query_data["bosminer"]["info"]["workSolver"]["realHashrate"]["mhs1M"]
/ 1000000,
2,
)
except (TypeError, KeyError, ValueError, IndexError):
pass
boards = None
if query_data.get("bosminer"):
if query_data["bosminer"].get("info"):
if query_data["bosminer"]["info"].get("workSolver"):
boards = query_data["bosminer"]["info"]["workSolver"].get("childSolvers")
if boards:
offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else int(boards[0]["name"])
for hb in boards:
_id = int(hb["name"]) - offset
board = data.hashboards[_id]
board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2)
temps = hb["temperatures"]
try:
if len(temps) > 0:
board.temp = round(hb["temperatures"][0]["degreesC"])
if len(temps) > 1:
board.chip_temp = round(hb["temperatures"][1]["degreesC"])
except (TypeError, KeyError, ValueError, IndexError):
pass
details = hb.get("hwDetails")
if details:
if chips := details["chips"]:
board.chips = chips
board.missing = False
tuner = hb.get("tuner")
if tuner:
if msg := tuner.get("statusMessages"):
if len(msg) > 0:
if hb["tuner"]["statusMessages"][0] not in [
"Stable",
"Testing performance profile",
"Tuning individual chips"
]:
data.errors.append(
BraiinsOSError(f"Slot {_id} {hb['tuner']['statusMessages'][0]}")
)
try:
data.wattage = query_data["bosminer"]["info"]["workSolver"]["power"]["approxConsumptionW"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.wattage_limit = query_data["bosminer"]["info"]["workSolver"]["power"]["limitW"]
except (TypeError, KeyError, ValueError, IndexError):
pass
for n in range(self.fan_count):
try:
setattr(data, f"fan_{n + 1}", query_data["bosminer"]["info"]["fans"][n]["rpm"])
except (TypeError, KeyError, ValueError, IndexError):
pass
groups = None
if query_data.get("bosminer"):
if query_data["bosminer"].get("config"):
groups = query_data["bosminer"]["config"].get("groups")
if groups:
if len(groups) == 1:
try:
data.pool_1_user = groups[0]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_user = groups[0]["pools"][1]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[0]["pools"][1]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
data.quota = 0
else:
try:
data.pool_1_user = groups[0]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_1_url = groups[0]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_user = groups[1]["pools"][0]["user"]
except (TypeError, KeyError, ValueError, IndexError):
pass
try:
data.pool_2_url = groups[1]["pools"][0]["url"]
except (TypeError, KeyError, ValueError, IndexError):
pass
if groups[0]["strategy"].get("quota"):
data.quota = groups[0]["strategy"]["quota"] + "/" + groups[1]["strategy"]["quota"]
data.fault_light = await self.check_light()
return data return data
async def get_mac(self): async def get_mac(self):

View File

@@ -12,14 +12,15 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
import ipaddress import ipaddress
from typing import Union import logging
from typing import List, Union
from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.bosminer import BOSMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.miners.base import BaseMiner
class BOSMinerOld(BaseMiner): class BOSMinerOld(BaseMiner):
@@ -76,7 +77,7 @@ class BOSMinerOld(BaseMiner):
async def get_config(self) -> None: async def get_config(self) -> None:
return None return None
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
@@ -94,5 +95,14 @@ class BOSMinerOld(BaseMiner):
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None return None
async def get_data(self) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -14,17 +14,14 @@
import ipaddress import ipaddress
import logging import logging
from typing import Union from typing import List, Union
from pyasic.API.btminer import BTMinerAPI from pyasic.API.btminer import BTMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.API import APIError
from pyasic.data import MinerData
from pyasic.data.error_codes import WhatsminerError
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
@@ -109,7 +106,6 @@ class BTMiner(BaseMiner):
if "Code" in data.keys(): if "Code" in data.keys():
if data["Code"] == 131: if data["Code"] == 131:
return True return True
print(data)
return False return False
async def check_light(self) -> bool: async def check_light(self) -> bool:
@@ -156,13 +152,61 @@ class BTMiner(BaseMiner):
return True return True
return False return False
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
return [] data = []
try:
err_data = await self.api.get_error_code()
if err_data:
if err_data.get("Msg"):
if err_data["Msg"].get("error_code"):
for err in err_data["Msg"]["error_code"]:
if isinstance(err, dict):
for code in err:
data.append(WhatsminerError(error_code=int(code)))
else:
data.append(WhatsminerError(error_code=int(err)))
except APIError:
summary_data = await self.api.summary()
if summary_data.get("SUMMARY"):
summary_data = summary_data["SUMMARY"]
if summary_data[0].get("Error Code Count"):
for i in range(summary_data[0]["Error Code Count"]):
if summary_data[0].get(f"Error Code {i}"):
if not summary_data[0][f"Error Code {i}"] == "":
data.append(
WhatsminerError(
error_code=summary_data[0][f"Error Code {i}"]
)
)
return data
async def reboot(self) -> bool: async def reboot(self) -> bool:
data = await self.api.reboot()
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
return False return False
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
data = await self.api.restart()
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
return False
async def stop_mining(self) -> bool:
data = await self.api.power_off(respbefore=True)
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
return False
async def resume_mining(self) -> bool:
data = await self.api.power_on()
if data.get("Msg"):
if data["Msg"] == "API command OK":
return True
return False return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
@@ -194,13 +238,17 @@ class BTMiner(BaseMiner):
cfg = cfg.from_api(pools["POOLS"]) cfg = cfg.from_api(pools["POOLS"])
return cfg return cfg
async def get_data(self) -> MinerData: async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner. """Get data from the miner.
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. 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 * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
mac = None mac = None
@@ -229,18 +277,25 @@ class BTMiner(BaseMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_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", allow_warning=allow_warning)
if miner_data: if miner_data:
break break
except APIError: except APIError:
pass pass
if not miner_data: if not miner_data:
return data return data
summary = miner_data.get("summary")[0] summary = miner_data.get("summary")[0]
devs = miner_data.get("devs")[0] devs = miner_data.get("devs")[0]
pools = miner_data.get("pools")[0] pools = miner_data.get("pools")[0]
try:
psu_data = await self.api.get_psu()
except APIError:
psu_data = None
try:
err_data = await self.api.get_error_code()
except APIError:
err_data = None
if summary: if summary:
summary_data = summary.get("SUMMARY") summary_data = summary.get("SUMMARY")
@@ -256,6 +311,9 @@ class BTMiner(BaseMiner):
if summary_data[0].get("Power Limit"): if summary_data[0].get("Power Limit"):
wattage_limit = summary_data[0]["Power Limit"] wattage_limit = summary_data[0]["Power Limit"]
if summary_data[0].get("Power Fanspeed"):
data.fan_psu = summary_data[0]["Power Fanspeed"]
data.fan_1 = summary_data[0]["Fan Speed In"] data.fan_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"]
@@ -275,39 +333,47 @@ class BTMiner(BaseMiner):
if summary_data[0].get("Error Code Count"): if summary_data[0].get("Error Code Count"):
for i in range(summary_data[0]["Error Code Count"]): for i in range(summary_data[0]["Error Code Count"]):
if summary_data[0].get(f"Error Code {i}"): if summary_data[0].get(f"Error Code {i}"):
data.errors.append( if not summary_data[0][f"Error Code {i}"] == "":
WhatsminerError( data.errors.append(
error_code=summary_data[0][f"Error Code {i}"] WhatsminerError(
error_code=summary_data[0][
f"Error Code {i}"
]
)
) )
if psu_data:
psu = psu_data.get("Msg")
if psu:
if psu.get("fan_speed"):
data.fan_psu = psu["fan_speed"]
if err_data:
if err_data.get("Msg"):
if err_data["Msg"].get("error_code"):
for err in err_data["Msg"]["error_code"]:
if isinstance(err, dict):
for code in err:
data.errors.append(
WhatsminerError(error_code=int(code))
) )
else:
data.errors.append(WhatsminerError(error_code=int(err)))
if devs: if devs:
temp_data = devs.get("DEVS") dev_data = devs.get("DEVS")
if temp_data: if dev_data:
board_map = {0: "left_board", 1: "center_board", 2: "right_board"} for board in dev_data:
for board in temp_data: temp_board = HashBoard(
_id = board["ASC"] slot=board["ASC"],
chip_temp = round(board["Chip Temp Avg"]) chip_temp=round(board["Chip Temp Avg"]),
board_temp = round(board["Temperature"]) temp=round(board["Temperature"]),
hashrate = round(board["MHS 1m"] / 1000000, 2) hashrate=round(board["MHS 1m"] / 1000000, 2),
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp) chips=board["Effective Chips"],
setattr(data, f"{board_map[_id]}_temp", board_temp) missing=False if board["Effective Chips"] > 0 else True,
setattr(data, f"{board_map[_id]}_hashrate", hashrate) expected_chips=self.nominal_chips,
)
if devs: data.hashboards.append(temp_board)
boards = devs.get("DEVS")
if boards:
if len(boards) > 0:
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
if "ID" in boards[0].keys():
id_key = "ID"
else:
id_key = "ASC"
offset = boards[0][id_key]
for board in boards:
_id = board[id_key] - offset
chips = board["Effective Chips"]
setattr(data, board_map[_id], chips)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -14,16 +14,14 @@
import ipaddress import ipaddress
import logging import logging
from typing import Union from typing import List, Union
from pyasic.API.cgminer import CGMinerAPI from pyasic.API.cgminer import CGMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.API import APIError
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.data import MinerData from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError
from pyasic.miners.base import BaseMiner
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
@@ -118,8 +116,7 @@ class CGMiner(BaseMiner):
return True return True
return False return False
async def start_cgminer(self) -> None: async def resume_mining(self) -> bool:
"""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',
@@ -128,9 +125,9 @@ class CGMiner(BaseMiner):
] ]
commands = ";".join(commands) commands = ";".join(commands)
await self.send_ssh_command(commands) await self.send_ssh_command(commands)
return True
async def stop_cgminer(self) -> None: async def stop_mining(self) -> bool:
"""Restart cgminer hashing process."""
commands = [ commands = [
"mkdir -p /etc/tmp/", "mkdir -p /etc/tmp/",
'echo "" > /etc/tmp/root', 'echo "" > /etc/tmp/root',
@@ -139,6 +136,7 @@ class CGMiner(BaseMiner):
] ]
commands = ";".join(commands) commands = ";".join(commands)
await self.send_ssh_command(commands) await self.send_ssh_command(commands)
return True
async def get_config(self) -> str: async def get_config(self) -> str:
"""Gets the config for the miner and sets it as `self.config`. """Gets the config for the miner and sets it as `self.config`.
@@ -163,7 +161,7 @@ class CGMiner(BaseMiner):
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
return False return False
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
@@ -172,13 +170,17 @@ class CGMiner(BaseMiner):
async def get_mac(self) -> str: async def get_mac(self) -> str:
return "00:00:00:00:00:00" return "00:00:00:00:00:00"
async def get_data(self) -> MinerData: async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner. """Get data from the miner.
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. 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 * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1 board_offset = -1
fan_offset = -1 fan_offset = -1
@@ -201,7 +203,7 @@ class CGMiner(BaseMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_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", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -234,19 +236,38 @@ class CGMiner(BaseMiner):
if board_offset == -1: if board_offset == -1:
board_offset = 1 board_offset = 1
data.left_chips = boards[1].get(f"chain_acn{board_offset}") env_temp_list = []
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}") for i in range(board_offset, board_offset + self.ideal_hashboards):
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}") hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.nominal_chips
)
data.left_board_hashrate = round( chip_temp = boards[1].get(f"temp{i}")
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2 if chip_temp:
) hashboard.chip_temp = round(chip_temp)
data.center_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2 temp = boards[1].get(f"temp2_{i}")
) if temp:
data.right_board_hashrate = round( hashboard.temp = round(temp)
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
) hashrate = boards[1].get(f"chain_rate{i}")
if hashrate:
hashboard.hashrate = round(float(hashrate) / 1000, 2)
chips = boards[1].get(f"chain_acn{i}")
if chips:
hashboard.chips = chips
hashboard.missing = False
if (not chips) or (not chips > 0):
hashboard.missing = True
data.hashboards.append(hashboard)
if f"temp_pcb{i}" in boards[1].keys():
env_temp = boards[1][f"temp_pcb{i}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
if not env_temp_list == []:
data.env_temp = round(sum(env_temp_list) / len(env_temp_list))
if stats: if stats:
temp = stats.get("STATS") temp = stats.get("STATS")
@@ -264,19 +285,6 @@ class CGMiner(BaseMiner):
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}") data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
) )
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
env_temp_list = []
for item in range(3):
board_temp = temp[1].get(f"temp{item + board_offset}")
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
setattr(data, f"{board_map[item]}_temp", board_temp)
if f"temp_pcb{item}" in temp[1].keys():
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
data.env_temp = sum(env_temp_list) / len(env_temp_list)
if pools: if pools:
pool_1 = None pool_1 = None
pool_2 = None pool_2 = None

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BMMiner
import ipaddress import ipaddress
from pyasic.miners._backends import BMMiner
class Hiveon(BMMiner): class Hiveon(BMMiner):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -14,5 +14,5 @@
from .antminer import * from .antminer import *
from .avalonminer import * from .avalonminer import *
from .whatsminer import *
from .innosilicon import * from .innosilicon import *
from .whatsminer import *

View File

@@ -16,7 +16,6 @@ from .S17 import S17
from .S17_Plus import S17Plus from .S17_Plus import S17Plus
from .S17_Pro import S17Pro from .S17_Pro import S17Pro
from .S17e import S17e from .S17e import S17e
from .T17 import T17 from .T17 import T17
from .T17_Plus import T17Plus from .T17_Plus import T17Plus
from .T17e import T17e from .T17e import T17e

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 S19XP(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "S19 XP"
self.nominal_chips = 110
self.fan_count = 4

View File

@@ -14,10 +14,8 @@
from .S19 import S19 from .S19 import S19
from .S19_Pro import S19Pro from .S19_Pro import S19Pro
from .S19_XP import S19XP
from .S19a import S19a
from .S19j import S19j from .S19j import S19j
from .S19j_Pro import S19jPro from .S19j_Pro import S19jPro
from .S19a import S19a
from .T19 import T19 from .T19 import T19

View File

@@ -15,7 +15,6 @@
from .M20 import M20, M20V10 from .M20 import M20, M20V10
from .M20S import M20S, M20SV10, M20SV20 from .M20S import M20S, M20SV10, M20SV20
from .M20S_Plus import M20SPlus from .M20S_Plus import M20SPlus
from .M21 import M21 from .M21 import M21
from .M21S import M21S, M21SV20, M21SV60 from .M21S import M21S, M21SV20, M21SV60
from .M21S_Plus import M21SPlus from .M21S_Plus import M21SPlus

View File

@@ -31,3 +31,48 @@ class M31SPlusVE20(BaseMiner):
self.model = "M31S+ VE20" self.model = "M31S+ VE20"
self.nominal_chips = 78 self.nominal_chips = 78
self.fan_count = 2 self.fan_count = 2
class M31SPlusV30(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S+ V30"
self.nominal_chips = 117
self.fan_count = 2
class M31SPlusV40(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S+ V40"
self.nominal_chips = 123
self.fan_count = 2
class M31SPlusV60(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S+ V60"
self.nominal_chips = 156
self.fan_count = 2
class M31SPlusV80(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S+ V80"
self.nominal_chips = 129
self.fan_count = 2
class M31SPlusV90(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M31S+ V90"
self.nominal_chips = 117
self.fan_count = 2

View File

@@ -0,0 +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.
from pyasic.miners.base import BaseMiner
class M34SPlus(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M34S+"
self.ideal_hashboards = 4
self.nominal_chips = 116
self.fan_count = 0
class M34SPlusVE10(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M34S+ VE10"
self.ideal_hashboards = 4
self.nominal_chips = 116
self.fan_count = 0

View File

@@ -12,17 +12,24 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .M30S import M30S, M30SVE10, M30SVE20, M30SVG20, M30SV50 from .M30S import M30S, M30SV50, M30SVE10, M30SVE20, M30SVG20
from .M30S_Plus import M30SPlus, M30SPlusVG60, M30SPlusVE40, M30SPlusVF20 from .M30S_Plus import M30SPlus, M30SPlusVE40, M30SPlusVF20, M30SPlusVG60
from .M30S_Plus_Plus import ( from .M30S_Plus_Plus import (
M30SPlusPlus, M30SPlusPlus,
M30SPlusPlusVG30, M30SPlusPlusVG30,
M30SPlusPlusVG40, M30SPlusPlusVG40,
M30SPlusPlusVH60, M30SPlusPlusVH60,
) )
from .M31S import M31S from .M31S import M31S
from .M31S_Plus import M31SPlus, M31SPlusVE20 from .M31S_Plus import (
M31SPlus,
M31SPlusV30,
M31SPlusV40,
M31SPlusV60,
M31SPlusV80,
M31SPlusV90,
M31SPlusVE20,
)
from .M32 import M32, M32V20 from .M32 import M32, M32V20
from .M32S import M32S from .M32S import M32S
from .M34S_Plus import M34SPlus, M34SPlusVE10

View File

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

View File

@@ -12,9 +12,4 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import unittest from .M50 import M50, M50VH50
from pyasic.tests.miners_tests import MinersTest
from pyasic.tests.network_tests import NetworkTest
if __name__ == "__main__":
unittest.main()

View File

@@ -14,3 +14,4 @@
from .M2X import * from .M2X import *
from .M3X import * from .M3X import *
from .M5X import *

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import S17 # noqa - Ignore access to _module from pyasic.miners._types import S17 # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerS17(BMMinerX17, S17): class BMMinerS17(BMMinerX17, S17):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import S17Plus # noqa - Ignore access to _module from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerS17Plus(BMMinerX17, S17Plus): class BMMinerS17Plus(BMMinerX17, S17Plus):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import S17Pro # noqa - Ignore access to _module from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerS17Pro(BMMinerX17, S17Pro): class BMMinerS17Pro(BMMinerX17, S17Pro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import S17e # noqa - Ignore access to _module from pyasic.miners._types import S17e # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerS17e(BMMinerX17, S17e): class BMMinerS17e(BMMinerX17, S17e):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import T17 # noqa - Ignore access to _module from pyasic.miners._types import T17 # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerT17(BMMinerX17, T17): class BMMinerT17(BMMinerX17, T17):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import T17Plus # noqa - Ignore access to _module from pyasic.miners._types import T17Plus # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerT17Plus(BMMinerX17, T17Plus): class BMMinerT17Plus(BMMinerX17, T17Plus):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X17 import BMMinerX17
from pyasic.miners._types import T17e # noqa - Ignore access to _module from pyasic.miners._types import T17e # noqa - Ignore access to _module
from .X17 import BMMinerX17
class BMMinerT17e(BMMinerX17, T17e): class BMMinerT17e(BMMinerX17, T17e):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,12 +12,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module from typing import Union
from pyasic.settings import PyasicSettings
import httpx import httpx
from typing import Union
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class BMMinerX17(BMMiner): class BMMinerX17(BMMiner):

View File

@@ -16,7 +16,6 @@ from .S17 import BMMinerS17
from .S17_Plus import BMMinerS17Plus from .S17_Plus import BMMinerS17Plus
from .S17_Pro import BMMinerS17Pro from .S17_Pro import BMMinerS17Pro
from .S17e import BMMinerS17e from .S17e import BMMinerS17e
from .T17 import BMMinerT17 from .T17 import BMMinerT17
from .T17_Plus import BMMinerT17Plus from .T17_Plus import BMMinerT17Plus
from .T17e import BMMinerT17e from .T17e import BMMinerT17e

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import S19 # noqa - Ignore access to _module from pyasic.miners._types import S19 # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19(BMMinerX19, S19): class BMMinerS19(BMMinerX19, S19):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import S19Pro # noqa - Ignore access to _module from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19Pro(BMMinerX19, S19Pro): class BMMinerS19Pro(BMMinerX19, S19Pro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -0,0 +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.
from pyasic.miners._types import S19XP # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19XP(BMMinerX19, S19XP):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import S19a # noqa - Ignore access to _module from pyasic.miners._types import S19a # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19a(BMMinerX19, S19a): class BMMinerS19a(BMMinerX19, S19a):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import S19j # noqa - Ignore access to _module from pyasic.miners._types import S19j # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19j(BMMinerX19, S19j): class BMMinerS19j(BMMinerX19, S19j):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import S19jPro # noqa - Ignore access to _module from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerS19jPro(BMMinerX19, S19jPro): class BMMinerS19jPro(BMMinerX19, S19jPro):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .X19 import BMMinerX19
from pyasic.miners._types import T19 # noqa - Ignore access to _module from pyasic.miners._types import T19 # noqa - Ignore access to _module
from .X19 import BMMinerX19
class BMMinerT19(BMMinerX19, T19): class BMMinerT19(BMMinerX19, T19):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,16 +12,16 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module import asyncio
import json
from pyasic.config import MinerConfig from typing import List, Union
from pyasic.data.error_codes import X19Error
from pyasic.settings import PyasicSettings
import httpx import httpx
import json
import asyncio from pyasic.config import MinerConfig
from typing import Union, List from pyasic.data.error_codes import MinerErrorData, X19Error
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class BMMinerX19(BMMiner): class BMMinerX19(BMMiner):
@@ -132,7 +132,7 @@ class BMMinerX19(BMMiner):
return True return True
return False return False
async def get_errors(self) -> List[X19Error]: async def get_errors(self) -> List[MinerErrorData]:
errors = [] errors = []
url = f"http://{self.ip}/cgi-bin/summary.cgi" url = f"http://{self.ip}/cgi-bin/summary.cgi"
auth = httpx.DigestAuth(self.uname, self.pwd) auth = httpx.DigestAuth(self.uname, self.pwd)
@@ -146,3 +146,15 @@ class BMMinerX19(BMMiner):
if not item["status"] == "s": if not item["status"] == "s":
errors.append(X19Error(item["msg"])) errors.append(X19Error(item["msg"]))
return errors return errors
async def stop_mining(self) -> bool:
cfg = await self.get_config()
cfg.autotuning_wattage = 0
await self.send_config(cfg)
return True
async def resume_mining(self) -> bool:
cfg = await self.get_config()
cfg.autotuning_wattage = 1
await self.send_config(cfg)
return True

View File

@@ -14,10 +14,8 @@
from .S19 import BMMinerS19 from .S19 import BMMinerS19
from .S19_Pro import BMMinerS19Pro from .S19_Pro import BMMinerS19Pro
from .S19_XP import BMMinerS19XP
from .S19a import BMMinerS19a
from .S19j import BMMinerS19j from .S19j import BMMinerS19j
from .S19j_Pro import BMMinerS19jPro from .S19j_Pro import BMMinerS19jPro
from .S19a import BMMinerS19a
from .T19 import BMMinerT19 from .T19 import BMMinerT19

View File

@@ -16,7 +16,6 @@ from .S17 import BOSMinerS17
from .S17_Plus import BOSMinerS17Plus from .S17_Plus import BOSMinerS17Plus
from .S17_Pro import BOSMinerS17Pro from .S17_Pro import BOSMinerS17Pro
from .S17e import BOSMinerS17e from .S17e import BOSMinerS17e
from .T17 import BOSMinerT17 from .T17 import BOSMinerT17
from .T17_Plus import BOSMinerT17Plus from .T17_Plus import BOSMinerT17Plus
from .T17e import BOSMinerT17e from .T17e import BOSMinerT17e

View File

@@ -14,8 +14,6 @@
from .S19 import BOSMinerS19 from .S19 import BOSMinerS19
from .S19_Pro import BOSMinerS19Pro from .S19_Pro import BOSMinerS19Pro
from .S19j import BOSMinerS19j from .S19j import BOSMinerS19j
from .S19j_Pro import BOSMinerS19jPro from .S19j_Pro import BOSMinerS19jPro
from .T19 import BOSMinerT19 from .T19 import BOSMinerT19

View File

@@ -16,7 +16,6 @@ from .S17 import CGMinerS17
from .S17_Plus import CGMinerS17Plus from .S17_Plus import CGMinerS17Plus
from .S17_Pro import CGMinerS17Pro from .S17_Pro import CGMinerS17Pro
from .S17e import CGMinerS17e from .S17e import CGMinerS17e
from .T17 import CGMinerT17 from .T17 import CGMinerT17
from .T17_Plus import CGMinerT17Plus from .T17_Plus import CGMinerT17Plus
from .T17e import CGMinerT17e from .T17e import CGMinerT17e

View File

@@ -14,8 +14,6 @@
from .S19 import CGMinerS19 from .S19 import CGMinerS19
from .S19_Pro import CGMinerS19Pro from .S19_Pro import CGMinerS19Pro
from .S19j import CGMinerS19j from .S19j import CGMinerS19j
from .S19j_Pro import CGMinerS19jPro from .S19j_Pro import CGMinerS19jPro
from .T19 import CGMinerT19 from .T19 import CGMinerT19

View File

@@ -13,3 +13,4 @@
# limitations under the License. # limitations under the License.
from .S9 import CGMinerS9 from .S9 import CGMinerS9
from .T9 import CGMinerT9

View File

@@ -12,10 +12,9 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.data import HashBoard, MinerData
from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module
from pyasic.miners._types import T9 # noqa - Ignore access to _module from pyasic.miners._types import T9 # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
@@ -33,13 +32,17 @@ class HiveonT9(Hiveon, T9):
) )
return mac return mac
async def get_data(self) -> MinerData: async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner. """Get data from the miner.
Returns: Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. 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 * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1 board_offset = -1
fan_offset = -1 fan_offset = -1
@@ -67,7 +70,7 @@ class HiveonT9(Hiveon, T9):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_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", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -97,14 +100,17 @@ class HiveonT9(Hiveon, T9):
) )
board_map = { board_map = {
"left": [2, 9, 10], 0: [2, 9, 10],
"center": [3, 11, 12], 1: [3, 11, 12],
"right": [4, 13, 14], 2: [4, 13, 14],
} }
env_temp_list = [] env_temp_list = []
for board in board_map.keys(): for board in board_map.keys():
hashboard = HashBoard(
slot=board, expected_chips=self.nominal_chips
)
chips = 0 chips = 0
hashrate = 0 hashrate = 0
chip_temp = 0 chip_temp = 0
@@ -121,10 +127,15 @@ class HiveonT9(Hiveon, T9):
hashrate += boards[1][f"chain_rate{chipset}"] hashrate += boards[1][f"chain_rate{chipset}"]
chips += boards[1][f"chain_acn{chipset}"] chips += boards[1][f"chain_acn{chipset}"]
setattr(data, f"{board}_chips", chips) hashboard.hashrate = hashrate
setattr(data, f"{board}_board_hashrate", hashrate) hashboard.chips = chips
setattr(data, f"{board}_board_temp", board_temp) hashboard.temp = board_temp
setattr(data, f"{board}_board_chip_temp", chip_temp) hashboard.chip_temp = chip_temp
hashboard.missing = True
if chips and chips > 0:
hashboard.missing = False
data.hashboards.append(hashboard)
if not env_temp_list == []: if not env_temp_list == []:
data.env_temp = sum(env_temp_list) / len(env_temp_list) data.env_temp = sum(env_temp_list) / len(env_temp_list)

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A10X import CGMinerA10X
from pyasic.miners._types import Avalon1026 # noqa - Ignore access to _module from pyasic.miners._types import Avalon1026 # noqa - Ignore access to _module
from .A10X import CGMinerA10X
class CGMinerAvalon1026(CGMinerA10X, Avalon1026): class CGMinerAvalon1026(CGMinerA10X, Avalon1026):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A10X import CGMinerA10X
from pyasic.miners._types import Avalon1047 # noqa - Ignore access to _module from pyasic.miners._types import Avalon1047 # noqa - Ignore access to _module
from .A10X import CGMinerA10X
class CGMinerAvalon1047(CGMinerA10X, Avalon1047): class CGMinerAvalon1047(CGMinerA10X, Avalon1047):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A10X import CGMinerA10X
from pyasic.miners._types import Avalon1066 # noqa - Ignore access to _module from pyasic.miners._types import Avalon1066 # noqa - Ignore access to _module
from .A10X import CGMinerA10X
class CGMinerAvalon1066(CGMinerA10X, Avalon1066): class CGMinerAvalon1066(CGMinerA10X, Avalon1066):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,13 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
import logging import logging
import re
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class CGMinerA10X(CGMiner): class CGMinerA10X(CGMiner):
@@ -51,6 +51,12 @@ class CGMinerA10X(CGMiner):
return True return True
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config.""" """Configures miner with yaml config."""
raise NotImplementedError raise NotImplementedError
@@ -74,8 +80,16 @@ class CGMinerA10X(CGMiner):
) )
return mac return mac
async def get_data(self): async def get_data(self, allow_warning: bool = True):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model() model = await self.get_model()
mac = None mac = None
@@ -86,7 +100,7 @@ class CGMinerA10X(CGMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_data_retries): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats" "version", "summary", "pools", "stats", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -139,27 +153,20 @@ class CGMinerA10X(CGMiner):
f"fan_{fan+1}", f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]), int(raw_data[f"Fan{fan+1}"]),
) )
if "MTmax" in raw_data.keys(): for board in range(self.ideal_hashboards):
data.left_board_chip_temp = int(raw_data["MTmax"][0]) chip_temp = raw_data.get("MTmax")
data.center_board_chip_temp = int(raw_data["MTmax"][1]) if chip_temp:
data.right_board_chip_temp = int(raw_data["MTmax"][2]) data.hashboards[board].chip_temp = chip_temp[board]
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data: temp = raw_data.get("MTavg")
data.left_chips = len( if temp:
[item for item in raw_data["PVT_T0"] if not item == "0"] data.hashboards[board].temp = temp[board]
)
if "PVT_T1" in raw_data: chips = raw_data.get(f"PVT_T{board}")
data.center_chips = len( if chips:
[item for item in raw_data["PVT_T1"] if not item == "0"] data.hashboards[board].chips = len(
) [item for item in chips if not item == "0"]
if "PVT_T2" in raw_data: )
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A7X import CGMinerA7X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon721 # noqa - Ignore access to _module from pyasic.miners._types import Avalon721 # noqa - Ignore access to _module
from .A7X import CGMinerA7X # noqa - Ignore access to _module
class CGMinerAvalon721(CGMinerA7X, Avalon721): class CGMinerAvalon721(CGMinerA7X, Avalon721):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A7X import CGMinerA7X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon741 # noqa - Ignore access to _module from pyasic.miners._types import Avalon741 # noqa - Ignore access to _module
from .A7X import CGMinerA7X # noqa - Ignore access to _module
class CGMinerAvalon741(CGMinerA7X, Avalon741): class CGMinerAvalon741(CGMinerA7X, Avalon741):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A7X import CGMinerA7X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon761 # noqa - Ignore access to _module from pyasic.miners._types import Avalon761 # noqa - Ignore access to _module
from .A7X import CGMinerA7X # noqa - Ignore access to _module
class CGMinerAvalon761(CGMinerA7X, Avalon761): class CGMinerAvalon761(CGMinerA7X, Avalon761):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,13 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
import logging import logging
import re
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class CGMinerA7X(CGMiner): class CGMinerA7X(CGMiner):
@@ -51,6 +51,12 @@ class CGMinerA7X(CGMiner):
return True return True
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config.""" """Configures miner with yaml config."""
raise NotImplementedError raise NotImplementedError
@@ -74,8 +80,16 @@ class CGMinerA7X(CGMiner):
) )
return mac return mac
async def get_data(self): async def get_data(self, allow_warning: bool = True):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model() model = await self.get_model()
mac = None mac = None
@@ -86,7 +100,7 @@ class CGMinerA7X(CGMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_data_retries): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats" "version", "summary", "pools", "stats", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -139,27 +153,20 @@ class CGMinerA7X(CGMiner):
f"fan_{fan+1}", f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]), int(raw_data[f"Fan{fan+1}"]),
) )
if "MTmax" in raw_data.keys(): for board in range(self.ideal_hashboards):
data.left_board_chip_temp = int(raw_data["MTmax"][0]) chip_temp = raw_data.get("MTmax")
data.center_board_chip_temp = int(raw_data["MTmax"][1]) if chip_temp:
data.right_board_chip_temp = int(raw_data["MTmax"][2]) data.hashboards[board].chip_temp = chip_temp[board]
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data: temp = raw_data.get("MTavg")
data.left_chips = len( if temp:
[item for item in raw_data["PVT_T0"] if not item == "0"] data.hashboards[board].temp = temp[board]
)
if "PVT_T1" in raw_data: chips = raw_data.get(f"PVT_T{board}")
data.center_chips = len( if chips:
[item for item in raw_data["PVT_T1"] if not item == "0"] data.hashboards[board].chips = len(
) [item for item in chips if not item == "0"]
if "PVT_T2" in raw_data: )
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A8X import CGMinerA8X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon821 # noqa - Ignore access to _module from pyasic.miners._types import Avalon821 # noqa - Ignore access to _module
from .A8X import CGMinerA8X # noqa - Ignore access to _module
class CGMinerAvalon821(CGMinerA8X, Avalon821): class CGMinerAvalon821(CGMinerA8X, Avalon821):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A8X import CGMinerA8X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon841 # noqa - Ignore access to _module from pyasic.miners._types import Avalon841 # noqa - Ignore access to _module
from .A8X import CGMinerA8X # noqa - Ignore access to _module
class CGMinerAvalon841(CGMinerA8X, Avalon841): class CGMinerAvalon841(CGMinerA8X, Avalon841):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,9 +12,10 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from .A8X import CGMinerA8X # noqa - Ignore access to _module
from pyasic.miners._types import Avalon851 # noqa - Ignore access to _module from pyasic.miners._types import Avalon851 # noqa - Ignore access to _module
from .A8X import CGMinerA8X # noqa - Ignore access to _module
class CGMinerAvalon851(CGMinerA8X, Avalon851): class CGMinerAvalon851(CGMinerA8X, Avalon851):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:

View File

@@ -12,13 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
import logging import logging
import re
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class CGMinerA8X(CGMiner): class CGMinerA8X(CGMiner):
@@ -51,6 +51,12 @@ class CGMinerA8X(CGMiner):
return True return True
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config.""" """Configures miner with yaml config."""
raise NotImplementedError raise NotImplementedError
@@ -74,8 +80,16 @@ class CGMinerA8X(CGMiner):
) )
return mac return mac
async def get_data(self): async def get_data(self, allow_warning: bool = True):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model() model = await self.get_model()
mac = None mac = None
@@ -86,7 +100,7 @@ class CGMinerA8X(CGMiner):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_data_retries): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats" "version", "summary", "pools", "stats", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -139,27 +153,20 @@ class CGMinerA8X(CGMiner):
f"fan_{fan+1}", f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]), int(raw_data[f"Fan{fan+1}"]),
) )
if "MTmax" in raw_data.keys(): for board in range(self.ideal_hashboards):
data.left_board_chip_temp = int(raw_data["MTmax"][0]) chip_temp = raw_data.get("MTmax")
data.center_board_chip_temp = int(raw_data["MTmax"][1]) if chip_temp:
data.right_board_chip_temp = int(raw_data["MTmax"][2]) data.hashboards[board].chip_temp = chip_temp[board]
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data: temp = raw_data.get("MTavg")
data.left_chips = len( if temp:
[item for item in raw_data["PVT_T0"] if not item == "0"] data.hashboards[board].temp = temp[board]
)
if "PVT_T1" in raw_data: chips = raw_data.get(f"PVT_T{board}")
data.center_chips = len( if chips:
[item for item in raw_data["PVT_T1"] if not item == "0"] data.hashboards[board].chips = len(
) [item for item in chips if not item == "0"]
if "PVT_T2" in raw_data: )
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -12,14 +12,14 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import logging
import re
from pyasic.config import MinerConfig
from pyasic.data import HashBoard, MinerData
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.settings import PyasicSettings from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
import logging
class CGMinerAvalon921(CGMiner, Avalon921): class CGMinerAvalon921(CGMiner, Avalon921):
@@ -52,6 +52,12 @@ class CGMinerAvalon921(CGMiner, Avalon921):
return True return True
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Configures miner with yaml config.""" """Configures miner with yaml config."""
raise NotImplementedError raise NotImplementedError
@@ -75,8 +81,16 @@ class CGMinerAvalon921(CGMiner, Avalon921):
) )
return mac return mac
async def get_data(self): async def get_data(self, allow_warning: bool = True):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
hashboards=[
HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
model = await self.get_model() model = await self.get_model()
mac = None mac = None
@@ -89,7 +103,7 @@ class CGMinerAvalon921(CGMiner, Avalon921):
miner_data = None miner_data = None
for i in range(PyasicSettings().miner_get_data_retries): for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand( miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats" "version", "summary", "pools", "stats", allow_warning=allow_warning
) )
if miner_data: if miner_data:
break break
@@ -140,27 +154,20 @@ class CGMinerAvalon921(CGMiner, Avalon921):
f"fan_{fan+1}", f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]), int(raw_data[f"Fan{fan+1}"]),
) )
if "MTmax" in raw_data.keys(): for board in range(self.ideal_hashboards):
data.left_board_chip_temp = int(raw_data["MTmax"][0]) chip_temp = raw_data.get("MTmax")
data.center_board_chip_temp = int(raw_data["MTmax"][1]) if chip_temp:
data.right_board_chip_temp = int(raw_data["MTmax"][2]) data.hashboards[board].chip_temp = chip_temp[board]
if "MTavg" in raw_data.keys():
data.left_board_temp = int(raw_data["MTavg"][0])
data.center_board_temp = int(raw_data["MTavg"][1])
data.right_board_temp = int(raw_data["MTavg"][2])
if "PVT_T0" in raw_data: temp = raw_data.get("MTavg")
data.left_chips = len( if temp:
[item for item in raw_data["PVT_T0"] if not item == "0"] data.hashboards[board].temp = temp[board]
)
if "PVT_T1" in raw_data: chips = raw_data.get(f"PVT_T{board}")
data.center_chips = len( if chips:
[item for item in raw_data["PVT_T1"] if not item == "0"] data.hashboards[board].chips = len(
) [item for item in chips if not item == "0"]
if "PVT_T2" in raw_data: )
data.right_chips = len(
[item for item in raw_data["PVT_T2"] if not item == "0"]
)
if pools: if pools:
pool_1 = None pool_1 = None

View File

@@ -12,14 +12,22 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import asyncssh
import logging
import ipaddress import ipaddress
import logging
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import TypeVar from typing import List, TypeVar, Union
import asyncssh
from pyasic.data import MinerData
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import MinerData
from pyasic.data.error_codes import (
BraiinsOSError,
InnosiliconError,
MinerErrorData,
WhatsminerError,
X19Error,
)
class BaseMiner(ABC): class BaseMiner(ABC):
@@ -36,6 +44,7 @@ class BaseMiner(ABC):
self.version = None self.version = None
self.fan_count = 2 self.fan_count = 2
self.config = None self.config = None
self.ideal_hashboards = 3
def __new__(cls, *args, **kwargs): def __new__(cls, *args, **kwargs):
if cls is BaseMiner: if cls is BaseMiner:
@@ -43,7 +52,7 @@ class BaseMiner(ABC):
return object.__new__(cls) return object.__new__(cls)
def __repr__(self): def __repr__(self):
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}" 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): def __lt__(self, other):
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip) return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
@@ -85,58 +94,130 @@ class BaseMiner(ABC):
@abstractmethod @abstractmethod
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Turn the fault light of the miner on and return success as a boolean.
Returns:
A boolean value of the success of turning the light on.
"""
pass pass
@abstractmethod @abstractmethod
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
pass """Turn the fault light of the miner off and return success as a boolean.
# async def send_file(self, src, dest): Returns:
# async with (await self._get_ssh_connection()) as conn: A boolean value of the success of turning the light off.
# await asyncssh.scp(src, (conn, dest)) """
pass
@abstractmethod @abstractmethod
async def check_light(self) -> bool: async def check_light(self) -> bool:
pass """Check the status and return on or off as a boolean.
# @abstractmethod Returns:
async def get_board_info(self): A boolean value where `True` represents on and `False` represents off.
return None """
pass
@abstractmethod @abstractmethod
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
Returns:
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
"""
pass pass
@abstractmethod @abstractmethod
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
"""Get the hostname of the miner and return it as a string.
Returns:
A string representing the hostname of the miner.
"""
pass pass
@abstractmethod @abstractmethod
async def get_model(self) -> str: async def get_model(self) -> str:
"""Get the model of the miner and return it as a string.
Returns:
A string representing the model of the miner.
"""
pass pass
@abstractmethod @abstractmethod
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboot the miner and return success as a boolean.
Returns:
A boolean value of the success of rebooting the miner.
"""
pass pass
@abstractmethod @abstractmethod
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
Returns:
A boolean value of the success of restarting the mining process.
"""
pass pass
@abstractmethod @abstractmethod
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Set the mining configuration of the miner.
Parameters:
config: A [`MinerConfig`][pyasic.config.MinerConfig] containing the mining config you want to switch the miner to.
user_suffix: A suffix to append to the username when sending to the miner.
"""
return None return None
@abstractmethod @abstractmethod
async def get_mac(self) -> str: async def get_mac(self) -> str:
"""Get the MAC address of the miner and return it as a string.
Returns:
A string representing the MAC address of the miner.
"""
pass pass
@abstractmethod @abstractmethod
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
"""Get a list of the errors the miner is experiencing.
Returns:
A list of error classes representing different errors.
"""
pass pass
async def get_data(self) -> MinerData: @abstractmethod
async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing data from the miner.
"""
return MinerData(ip=str(self.ip)) return MinerData(ip=str(self.ip))
@abstractmethod
async def stop_mining(self) -> bool:
"""Stop the mining process of the miner.
Returns:
A boolean value of the success of stopping the mining process.
"""
pass
@abstractmethod
async def resume_mining(self) -> bool:
"""Stop the mining process of the miner.
Returns:
A boolean value of the success of resuming the mining process.
"""
pass
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner) AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

@@ -12,18 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module import logging
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module import warnings
from pyasic.data import MinerData from typing import List, Union
from pyasic.data.error_codes import InnosiliconError
from pyasic.settings import PyasicSettings
from pyasic.config import MinerConfig
from pyasic.API import APIError
import httpx import httpx
import warnings
from typing import Union from pyasic.config import MinerConfig
import logging from pyasic.data import HashBoard, MinerData
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.errors import APIError
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
from pyasic.settings import PyasicSettings
class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus): class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
@@ -31,7 +32,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
self.uname = "admin" self.uname = "admin"
self.pwd = "admin" self.pwd = PyasicSettings().global_innosilicon_password
self.jwt = None self.jwt = None
async def auth(self): async def auth(self):
@@ -142,7 +143,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
"updatePools", data=config.as_inno(user_suffix=user_suffix) "updatePools", data=config.as_inno(user_suffix=user_suffix)
) )
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
errors = [] errors = []
try: try:
data = await self.send_web_command("getErrorDetail") data = await self.send_web_command("getErrorDetail")
@@ -157,13 +158,16 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
errors.append(InnosiliconError(error_code=err)) errors.append(InnosiliconError(error_code=err))
return errors return errors
async def get_data(self) -> MinerData: async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner. data = MinerData(
ip=str(self.ip),
Returns: ideal_chips=self.nominal_chips * self.ideal_hashboards,
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data. ideal_hashboards=self.ideal_hashboards,
""" hashboards=[
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3) HashBoard(slot=i, expected_chips=self.nominal_chips)
for i in range(self.ideal_hashboards)
],
)
board_offset = -1 board_offset = -1
fan_offset = -1 fan_offset = -1
@@ -184,7 +188,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
all_data = None all_data = None
for i in range(PyasicSettings().miner_get_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", allow_warning=allow_warning
) )
if miner_data: if miner_data:
@@ -220,32 +224,31 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if stats: if stats:
stats = stats[0] stats = stats[0]
if stats.get("STATS"): if stats.get("STATS"):
board_map = {0: "left", 1: "center", 2: "right"}
for idx, board in enumerate(stats["STATS"]): for idx, board in enumerate(stats["STATS"]):
data.hashboards[idx].missing = True
chips = board.get("Num active chips") chips = board.get("Num active chips")
if chips: if chips:
setattr(data, f"{board_map[idx]}_chips", chips) data.hashboards[idx].chips = chips
temp = board.get("Temp") if chips > 0:
if temp: data.hashboards[idx].missing = False
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
if all_data: if all_data:
if all_data.get("chain"): if all_data.get("chain"):
board_map = {0: "left", 1: "center", 2: "right"}
for idx, board in enumerate(all_data["chain"]): for idx, board in enumerate(all_data["chain"]):
temp = board.get("Temp max") temp = board.get("Temp min")
if temp: if temp:
setattr(data, f"{board_map[idx]}_board_chip_temp", temp) data.hashboards[idx].temp = round(temp)
temp_board = board.get("Temp min")
if temp_board: hashrate = board.get("Hash Rate H")
setattr(data, f"{board_map[idx]}_board_temp", temp_board) if hashrate:
hr = board.get("Hash Rate H") data.hashboards[idx].hashrate = round(
if hr: hashrate / 1000000000000, 2
setattr(
data,
f"{board_map[idx]}_board_hashrate",
round(hr / 1000000000000, 2),
) )
chip_temp = board.get("Temp max")
if chip_temp:
data.hashboards[idx].chip_temp = round(chip_temp)
if all_data.get("fansSpeed"): if all_data.get("fansSpeed"):
speed = round((all_data["fansSpeed"] * 6000) / 100) speed = round((all_data["fansSpeed"] * 6000) / 100)
for fan in range(self.fan_count): for fan in range(self.fan_count):

View File

@@ -12,36 +12,32 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from typing import Tuple, List, Union
from collections.abc import AsyncIterable
from pyasic.miners.base import AnyMiner
import httpx
from pyasic.miners.antminer import *
from pyasic.miners.avalonminer import *
from pyasic.miners.whatsminer import *
from pyasic.miners.innosilicon import *
from pyasic.miners._backends.cgminer import CGMiner # noqa - Ignore _module import
from pyasic.miners._backends.bmminer import BMMiner # noqa - Ignore _module import
from pyasic.miners._backends.bosminer import BOSMiner # noqa - Ignore _module import
from pyasic.miners._backends.btminer import BTMiner # noqa - Ignore _module import
from pyasic.miners._backends.bosminer_old import ( # noqa - Ignore _module import
BOSMinerOld,
)
from pyasic.miners.unknown import UnknownMiner
from pyasic.API import APIError
import asyncio import asyncio
import ipaddress import ipaddress
import json import json
import logging import logging
from collections.abc import AsyncIterable
from pyasic.settings import PyasicSettings from typing import List, Tuple, Union
import asyncssh import asyncssh
import httpx
from pyasic.errors import APIError
from pyasic.miners._backends.bmminer import BMMiner # noqa - Ignore _module import
from pyasic.miners._backends.bosminer import BOSMiner # noqa - Ignore _module import
from pyasic.miners._backends.bosminer_old import ( # noqa - Ignore _module import
BOSMinerOld,
)
from pyasic.miners._backends.btminer import BTMiner # noqa - Ignore _module import
from pyasic.miners._backends.cgminer import CGMiner # noqa - Ignore _module import
from pyasic.miners.antminer import *
from pyasic.miners.avalonminer import *
from pyasic.miners.base import AnyMiner
from pyasic.miners.innosilicon import *
from pyasic.miners.unknown import UnknownMiner
from pyasic.miners.whatsminer import *
from pyasic.misc import Singleton
from pyasic.settings import PyasicSettings
MINER_CLASSES = { MINER_CLASSES = {
"ANTMINER S9": { "ANTMINER S9": {
@@ -59,6 +55,7 @@ MINER_CLASSES = {
"Default": BMMinerT9, "Default": BMMinerT9,
"BMMiner": BMMinerT9, "BMMiner": BMMinerT9,
"Hiveon": HiveonT9, "Hiveon": HiveonT9,
"CGMiner": CGMinerT9,
}, },
"ANTMINER S17": { "ANTMINER S17": {
"Default": BMMinerS17, "Default": BMMinerS17,
@@ -126,6 +123,10 @@ MINER_CLASSES = {
"BMMiner": BMMinerS19jPro, "BMMiner": BMMinerS19jPro,
"CGMiner": CGMinerS19jPro, "CGMiner": CGMinerS19jPro,
}, },
"ANTMINER S19 XP": {
"Default": BMMinerS19XP,
"BMMiner": BMMinerS19XP,
},
"ANTMINER S19A": { "ANTMINER S19A": {
"Default": BMMinerS19a, "Default": BMMinerS19a,
"BMMiner": BMMinerS19a, "BMMiner": BMMinerS19a,
@@ -194,6 +195,11 @@ MINER_CLASSES = {
"Default": BTMinerM31SPlus, "Default": BTMinerM31SPlus,
"BTMiner": BTMinerM31SPlus, "BTMiner": BTMinerM31SPlus,
"E20": BTMinerM31SPlusVE20, "E20": BTMinerM31SPlusVE20,
"30": BTMinerM31SPlusV30,
"40": BTMinerM31SPlusV40,
"60": BTMinerM31SPlusV60,
"80": BTMinerM31SPlusV80,
"90": BTMinerM31SPlusV90,
}, },
"M32S": { "M32S": {
"Default": BTMinerM32S, "Default": BTMinerM32S,
@@ -204,6 +210,16 @@ MINER_CLASSES = {
"BTMiner": BTMinerM32, "BTMiner": BTMinerM32,
"20": BTMinerM32V20, "20": BTMinerM32V20,
}, },
"M34S+": {
"Default": BTMinerM34SPlus,
"BTMiner": BTMinerM34SPlus,
"E10": BTMinerM34SPlusVE10,
},
"M50": {
"Default": BTMinerM50,
"BTMiner": BTMinerM50,
"H50": BTMinerM50VH50,
},
"AVALONMINER 721": { "AVALONMINER 721": {
"Default": CGMinerAvalon721, "Default": CGMinerAvalon721,
"CGMiner": CGMinerAvalon721, "CGMiner": CGMinerAvalon721,
@@ -252,15 +268,6 @@ MINER_CLASSES = {
} }
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MinerFactory(metaclass=Singleton): class MinerFactory(metaclass=Singleton):
"""A factory to handle identification and selection of the proper class of miner""" """A factory to handle identification and selection of the proper class of miner"""
@@ -330,7 +337,6 @@ class MinerFactory(metaclass=Singleton):
break break
except asyncio.TimeoutError: except asyncio.TimeoutError:
logging.warning(f"{ip}: Get Miner Timed Out") logging.warning(f"{ip}: Get Miner Timed Out")
miner = self._select_miner_from_classes(ip, model, api, ver) miner = self._select_miner_from_classes(ip, model, api, ver)
# save the miner to the cache at its IP if its not unknown # save the miner to the cache at its IP if its not unknown
@@ -405,7 +411,9 @@ class MinerFactory(metaclass=Singleton):
# miner refused connection on API port, we wont be able to get data this way # miner refused connection on API port, we wont be able to get data this way
# try ssh # try ssh
try: try:
_model = await self.__get_model_from_ssh(ip) _model = await self.__get_model_from_graphql(ip)
if not _model:
_model = await self.__get_model_from_ssh(ip)
if _model: if _model:
model = _model model = _model
api = "BOSMiner+" api = "BOSMiner+"
@@ -433,15 +441,42 @@ class MinerFactory(metaclass=Singleton):
break break
except KeyError: except KeyError:
continue continue
try:
if devdetails[0]["STATUS"][0]["Msg"]:
model = await self.__get_model_from_graphql(ip)
if model:
api = "BOSMiner+"
except (KeyError, TypeError, ValueError, IndexError):
pass
if not model: if not model:
# braiins OS bug check just in case # braiins OS bug check just in case
if "s9" in devdetails["STATUS"][0]["Description"]: if "s9" in devdetails["STATUS"][0]["Description"]:
model = "ANTMINER S9" model = "ANTMINER S9"
if "s17" in version["STATUS"][0]["Description"]: if "s17" in version["STATUS"][0]["Description"]:
model = "ANTMINER S17" model = "ANTMINER S17"
if not api:
if "boser" in version["STATUS"][0]["Description"]:
api = "BOSMiner+"
else:
try:
_model = await self.__get_model_from_graphql(ip)
if _model:
model = _model
api = "BOSMiner+"
except (KeyError, TypeError, ValueError, IndexError):
pass
# if we have version we can get API type from here # if we have version we can get API type from here
if version: if version:
try:
if version[0]["STATUS"][0]["Msg"]:
model = await self.__get_model_from_graphql(ip)
if model:
api = "BOSMiner+"
return model, api, ver
except (KeyError, TypeError, ValueError, IndexError):
pass
if "VERSION" in version: if "VERSION" in version:
api_types = ["BMMiner", "CGMiner", "BTMiner"] api_types = ["BMMiner", "CGMiner", "BTMiner"]
# check basic API types, BOSMiner needs a special check # check basic API types, BOSMiner needs a special check
@@ -457,6 +492,8 @@ class MinerFactory(metaclass=Singleton):
api = "BOSMiner+" api = "BOSMiner+"
if "BOSminer+" in version["VERSION"][0]: if "BOSminer+" in version["VERSION"][0]:
api = "BOSMiner+" api = "BOSMiner+"
if any("BOSer" in string for string in version["VERSION"][0]):
api = "BOSMiner+"
# check for avalonminers # check for avalonminers
for _version_key in ["PROD", "MODEL"]: for _version_key in ["PROD", "MODEL"]:
@@ -520,7 +557,6 @@ class MinerFactory(metaclass=Singleton):
# don't need "Bitmain", just "ANTMINER XX" as model # don't need "Bitmain", just "ANTMINER XX" as model
if "BITMAIN " in model: if "BITMAIN " in model:
model = model.replace("BITMAIN ", "") model = model.replace("BITMAIN ", "")
return model, api, ver return model, api, ver
async def __get_devdetails_and_version( async def __get_devdetails_and_version(
@@ -533,6 +569,11 @@ class MinerFactory(metaclass=Singleton):
# validate success # validate success
validation = await self._validate_command(data) validation = await self._validate_command(data)
if not validation[0]: if not validation[0]:
try:
if data["version"][0]["STATUS"][0]["Msg"] == "Disconnected":
return data["devdetails"], data["version"]
except KeyError:
pass
raise APIError(validation[1]) raise APIError(validation[1])
# copy each part of the main command to devdetails and version # copy each part of the main command to devdetails and version
devdetails = data["devdetails"][0] devdetails = data["devdetails"][0]
@@ -580,6 +621,16 @@ class MinerFactory(metaclass=Singleton):
model = "ANTMINER S17" model = "ANTMINER S17"
return model return model
@staticmethod
async def __get_model_from_graphql(ip: ipaddress.ip_address) -> Union[str, None]:
model = None
url = f"http://{ip}/graphql"
async with httpx.AsyncClient() as client:
d = await client.post(url, json={"query": "{bosminer {info{modelName}}}"})
if d.status_code == 200:
model = (d.json()["data"]["bosminer"]["info"]["modelName"]).upper()
return model
@staticmethod @staticmethod
async def __get_system_info_from_web(ip) -> dict: async def __get_system_info_from_web(ip) -> dict:
url = f"http://{ip}/cgi-bin/get_system_info.cgi" url = f"http://{ip}/cgi-bin/get_system_info.cgi"
@@ -637,9 +688,7 @@ class MinerFactory(metaclass=Singleton):
else: else:
# make sure the command succeeded # make sure the command succeeded
if data["STATUS"][0]["STATUS"] not in ("S", "I"): if data["STATUS"][0]["STATUS"] not in ("S", "I"):
# this is an error return False, data["STATUS"][0]["Msg"]
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
return False, data["STATUS"][0]["Msg"]
return True, None return True, None
@staticmethod @staticmethod

View File

@@ -14,14 +14,7 @@
import asyncio import asyncio
from pyasic.misc import Singleton
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class _MinerListener: class _MinerListener:
@@ -34,7 +27,13 @@ class _MinerListener:
def datagram_received(self, data, _addr): def datagram_received(self, data, _addr):
m = data.decode() m = data.decode()
ip, mac = m.split(",") if "," in m:
ip, mac = m.split(",")
else:
d = m[:-1].split("MAC")
ip = d[0][3:]
mac = d[1][1:]
new_miner = {"IP": ip, "MAC": mac.upper()} new_miner = {"IP": ip, "MAC": mac.upper()}
MinerListener().new_miner = new_miner MinerListener().new_miner = new_miner
@@ -53,9 +52,12 @@ class MinerListener(metaclass=Singleton):
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
transport, protocol = await loop.create_datagram_endpoint( transport_14235, protocol_14235 = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 14235) # noqa lambda: _MinerListener(), local_addr=("0.0.0.0", 14235) # noqa
) )
transport_8888, protocol_8888 = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 8888) # noqa
)
while True: while True:
if self.new_miner: if self.new_miner:
@@ -63,7 +65,8 @@ class MinerListener(metaclass=Singleton):
self.found_miners.append(self.new_miner) self.found_miners.append(self.new_miner)
self.new_miner = None self.new_miner = None
if self.stop: if self.stop:
transport.close() transport_14235.close()
transport_8888.close()
break break
await asyncio.sleep(0) await asyncio.sleep(0)

View File

@@ -12,9 +12,13 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
from typing import List
from pyasic.API.unknown import UnknownAPI from pyasic.API.unknown import UnknownAPI
from pyasic.miners.base import BaseMiner
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import MinerData
from pyasic.data.error_codes import MinerErrorData
from pyasic.miners.base import BaseMiner
class UnknownMiner(BaseMiner): class UnknownMiner(BaseMiner):
@@ -47,7 +51,7 @@ class UnknownMiner(BaseMiner):
async def get_config(self) -> None: async def get_config(self) -> None:
return None return None
async def get_errors(self) -> list: async def get_errors(self) -> List[MinerErrorData]:
return [] return []
async def get_mac(self) -> str: async def get_mac(self) -> str:
@@ -59,5 +63,14 @@ class UnknownMiner(BaseMiner):
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None return None
async def get_data(self, allow_warning: bool = False) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -13,10 +13,7 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( # noqa - Ignore access to _module from pyasic.miners._types import M20, M20V10 # noqa - Ignore access to _module
M20,
M20V10,
)
class BTMinerM20(BTMiner, M20): class BTMinerM20(BTMiner, M20):

View File

@@ -13,11 +13,11 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( from pyasic.miners._types import ( # noqa - Ignore access to _module
M21S, M21S,
M21SV20, M21SV20,
M21SV60, M21SV60,
) # noqa - Ignore access to _module )
class BTMinerM21S(BTMiner, M21S): class BTMinerM21S(BTMiner, M21S):

View File

@@ -15,7 +15,6 @@
from .M20 import BTMinerM20, BTMinerM20V10 from .M20 import BTMinerM20, BTMinerM20V10
from .M20S import BTMinerM20S, BTMinerM20SV10, BTMinerM20SV20 from .M20S import BTMinerM20S, BTMinerM20SV10, BTMinerM20SV20
from .M20S_Plus import BTMinerM20SPlus from .M20S_Plus import BTMinerM20SPlus
from .M21 import BTMinerM21 from .M21 import BTMinerM21
from .M21S import BTMinerM21S, BTMinerM21SV20, BTMinerM21SV60 from .M21S import BTMinerM21S, BTMinerM21SV20, BTMinerM21SV60
from .M21S_Plus import BTMinerM21SPlus from .M21S_Plus import BTMinerM21SPlus

View File

@@ -13,13 +13,13 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( from pyasic.miners._types import ( # noqa - Ignore access to _module
M30S, M30S,
M30SV50, M30SV50,
M30SVG20,
M30SVE20,
M30SVE10, M30SVE10,
) # noqa - Ignore access to _module M30SVE20,
M30SVG20,
)
class BTMinerM30S(BTMiner, M30S): class BTMinerM30S(BTMiner, M30S):

View File

@@ -13,12 +13,12 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( from pyasic.miners._types import ( # noqa - Ignore access to _module
M30SPlus, M30SPlus,
M30SPlusVE40, M30SPlusVE40,
M30SPlusVF20, M30SPlusVF20,
M30SPlusVG60, M30SPlusVG60,
) # noqa - Ignore access to _module )
class BTMinerM30SPlus(BTMiner, M30SPlus): class BTMinerM30SPlus(BTMiner, M30SPlus):

View File

@@ -15,8 +15,8 @@
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( # noqa - Ignore access to _module from pyasic.miners._types import ( # noqa - Ignore access to _module
M30SPlusPlus, M30SPlusPlus,
M30SPlusPlusVG40,
M30SPlusPlusVG30, M30SPlusPlusVG30,
M30SPlusPlusVG40,
M30SPlusPlusVH60, M30SPlusPlusVH60,
) )

View File

@@ -13,10 +13,15 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( from pyasic.miners._types import ( # noqa - Ignore access to _module
M31SPlus, M31SPlus,
M31SPlusV30,
M31SPlusV40,
M31SPlusV60,
M31SPlusV80,
M31SPlusV90,
M31SPlusVE20, M31SPlusVE20,
) # noqa - Ignore access to _module )
class BTMinerM31SPlus(BTMiner, M31SPlus): class BTMinerM31SPlus(BTMiner, M31SPlus):
@@ -29,3 +34,33 @@ class BTMinerM31SPlusVE20(BTMiner, M31SPlusVE20):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
super().__init__(ip) super().__init__(ip)
self.ip = ip self.ip = ip
class BTMinerM31SPlusV30(BTMiner, M31SPlusV30):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SPlusV40(BTMiner, M31SPlusV40):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SPlusV60(BTMiner, M31SPlusV60):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SPlusV80(BTMiner, M31SPlusV80):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SPlusV90(BTMiner, M31SPlusV90):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -13,9 +13,7 @@
# limitations under the License. # limitations under the License.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( from pyasic.miners._types import M32S # noqa - Ignore access to _module
M32S,
) # noqa - Ignore access to _module
class BTMinerM32S(BTMiner, M32S): class BTMinerM32S(BTMiner, M32S):

View File

@@ -0,0 +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.
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import ( # noqa - Ignore access to _module
M34SPlus,
M34SPlusVE10,
)
class BTMinerM34SPlus(BTMiner, M34SPlus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM34SPlusVE10(BTMiner, M34SPlusVE10):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -14,26 +14,33 @@
from .M30S import ( from .M30S import (
BTMinerM30S, BTMinerM30S,
BTMinerM30SV50,
BTMinerM30SVE10, BTMinerM30SVE10,
BTMinerM30SVE20, BTMinerM30SVE20,
BTMinerM30SVG20, BTMinerM30SVG20,
BTMinerM30SV50,
) )
from .M30S_Plus import ( from .M30S_Plus import (
BTMinerM30SPlus, BTMinerM30SPlus,
BTMinerM30SPlusVF20,
BTMinerM30SPlusVE40, BTMinerM30SPlusVE40,
BTMinerM30SPlusVF20,
BTMinerM30SPlusVG60, BTMinerM30SPlusVG60,
) )
from .M30S_Plus_Plus import ( from .M30S_Plus_Plus import (
BTMinerM30SPlusPlus, BTMinerM30SPlusPlus,
BTMinerM30SPlusPlusVG40,
BTMinerM30SPlusPlusVG30, BTMinerM30SPlusPlusVG30,
BTMinerM30SPlusPlusVG40,
BTMinerM30SPlusPlusVH60, BTMinerM30SPlusPlusVH60,
) )
from .M31S import BTMinerM31S from .M31S import BTMinerM31S
from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20 from .M31S_Plus import (
BTMinerM31SPlus,
BTMinerM31SPlusV30,
BTMinerM31SPlusV40,
BTMinerM31SPlusV60,
BTMinerM31SPlusV80,
BTMinerM31SPlusV90,
BTMinerM31SPlusVE20,
)
from .M32 import BTMinerM32, BTMinerM32V20 from .M32 import BTMinerM32, BTMinerM32V20
from .M32S import BTMinerM32S from .M32S import BTMinerM32S
from .M34S_Plus import BTMinerM34SPlus, BTMinerM34SPlusVE10

View File

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

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 .M50 import BTMinerM50, BTMinerM50VH50

View File

@@ -14,3 +14,4 @@
from .M2X import * from .M2X import *
from .M3X import * from .M3X import *
from .M5X import *

22
pyasic/misc/__init__.py Normal file
View File

@@ -0,0 +1,22 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]

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