Compare commits

...

70 Commits

Author SHA1 Message Date
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
52 changed files with 1887 additions and 706 deletions

1
.gitignore vendored
View File

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

View File

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

View File

@@ -230,7 +230,7 @@ These functions are
## [`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.
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>
@@ -240,6 +240,20 @@ Pyasic implements a few dataclasses as helpers to make data return types consist
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]

View File

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

View File

@@ -4,37 +4,44 @@
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:
<details>
<style>
details {
margin:0px;
padding-top:0px;
padding-bottom:0px;
}
</style>
<details style="margin:0px; padding-top:0px; padding-bottom:0px;">
<summary>Braiins OS+ Devices:</summary>
<ul>
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="/miners/antminer/X19#s19-bos">S19</a></li>
<li><a href="/miners/antminer/X19#s19-pro-bos">S19 Pro</a></li>
<li><a href="/miners/antminer/X19#s19j-bos">S19j</a></li>
<li><a href="/miners/antminer/X19#s19j-pro-bos">S19j Pro</a></li>
<li><a href="/miners/antminer/X19#t19-bos">T19</a></li>
<li><a href="../antminer/X19#s19-bos">S19</a></li>
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro</a></li>
<li><a href="../antminer/X19#s19j-bos">S19j</a></li>
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro</a></li>
<li><a href="../antminer/X19#t19-bos">T19</a></li>
</ul>
</details>
<details>
<summary>X17 Series:</summary>
<ul>
<li><a href="/miners/antminer/X17#s17-bos">S17</a></li>
<li><a href="/miners/antminer/X17#s17-plus-bos">S17+</a></li>
<li><a href="/miners/antminer/X17#s17-pro-bos">S17 Pro</a></li>
<li><a href="/miners/antminer/X17#s17e-bos">S17e</a></li>
<li><a href="/miners/antminer/X17#t17-bos">T17</a></li>
<li><a href="/miners/antminer/X17#t17-plus-bos">T17+</a></li>
<li><a href="/miners/antminer/X17#t17e-bos">T17e</a></li>
<li><a href="../antminer/X17#s17-bos">S17</a></li>
<li><a href="../antminer/X17#s17-plus-bos">S17+</a></li>
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro</a></li>
<li><a href="../antminer/X17#s17e-bos">S17e</a></li>
<li><a href="../antminer/X17#t17-bos">T17</a></li>
<li><a href="../antminer/X17#t17-plus-bos">T17+</a></li>
<li><a href="../antminer/X17#t17e-bos">T17e</a></li>
</ul>
</details>
<details>
<summary>X9 Series:</summary>
<ul>
<li><a href="/miners/antminer/X9#s9-bos">S9</a></li>
<li><a href="/miners/antminer/X9#s9-bos">S9i</a></li>
<li><a href="/miners/antminer/X9#s9-bos">S9j</a></li>
<li><a href="../antminer/X9#s9-bos">S9</a></li>
<li><a href="../antminer/X9#s9-bos">S9i</a></li>
<li><a href="../antminer/X9#s9-bos">S9j</a></li>
</ul>
</details>
</ul>
@@ -42,55 +49,73 @@ Supported miner types are here on this list. If your miner (or miner version) i
<details>
<summary>Stock Firmware Whatsminers:</summary>
<ul>
<details>
<summary>M5X Series:</summary>
<ul>
<details>
<summary><a href="../whatsminer/M5X/#m50">M50</a></summary>
<ul>
<li><a href="../whatsminer/M5X/#m50vh50">VH50</a></li>
</ul>
</details>
</ul>
</details>
<details>
<summary>M3X Series:</summary>
<ul>
<details>
<summary><a href="/miners/whatsminer/M3X/#m30s">M30S</a></summary>
<summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
<ul>
<li><a href="/miners/whatsminer/M3X/#m30sve10">VE10</a></li>
<li><a href="/miners/whatsminer/M3X/#m30svg20">VG20</a></li>
<li><a href="/miners/whatsminer/M3X/#m30sve20">VE20</a></li>
<li><a href="/miners/whatsminer/M3X/#m30sv50">V50</a></li>
<li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
<li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
<li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
<li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M3X/#m30s_1">M30S+</a></summary>
<summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
<ul>
<li><a href="/miners/whatsminer/M3X/#m30svf20">VF20</a></li>
<li><a href="/miners/whatsminer/M3X/#m30sve40">VE40</a></li>
<li><a href="/miners/whatsminer/M3X/#m30svg60">VG60</a></li>
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M3X/#m30s_2">M30S++</a></summary>
<summary><a href="../whatsminer/M3X/#m30s_2">M30S++</a></summary>
<ul>
<li><a href="/miners/whatsminer/M3X/#m30svg30">VG30</a></li>
<li><a href="/miners/whatsminer/M3X/#m30svg40">VG40</a></li>
<li><a href="/miners/whatsminer/M3X/#m30svh60">VH60</a></li>
<li><a href="../whatsminer/M3X/#m30svg30">VG30</a></li>
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
<li><a href="../whatsminer/M3X/#m30svh60">VH60</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M3X/#m31s">M31S</a></summary>
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
</details>
<details>
<summary><a href="/miners/whatsminer/M3X/#m31s_1">M31S+</a></summary>
<summary><a href="../whatsminer/M3X/#m31s_1">M31S+</a></summary>
<ul>
<li><a href="/miners/whatsminer/M3X/#m31sve20">VE20</a></li>
<li><a href="/miners/whatsminer/M3X/#m31sv40">V40</a></li>
<li><a href="/miners/whatsminer/M3X/#m31sv60">V60</a></li>
<li><a href="/miners/whatsminer/M3X/#m31sv80">V80</a></li>
<li><a href="/miners/whatsminer/M3X/#m31sv90">V90</a></li>
<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="/miners/whatsminer/M3X/#m32">M32</a></summary>
<summary><a href="../whatsminer/M3X/#m32">M32</a></summary>
<ul>
<li><a href="/miners/whatsminer/M3X/#m32v20">V20</a></li>
<li><a href="../whatsminer/M3X/#m32v20">V20</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M3X/#m32s">M32S</a></summary>
<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>
@@ -98,33 +123,33 @@ Supported miner types are here on this list. If your miner (or miner version) i
<summary>M2X Series:</summary>
<ul>
<details>
<summary><a href="/miners/whatsminer/M2X/#m20">M20</a></summary>
<summary><a href="../whatsminer/M2X/#m20">M20</a></summary>
<ul>
<li><a href="/miners/whatsminer/M2X/#m20v10">V10</a></li>
<li><a href="../whatsminer/M2X/#m20v10">V10</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M2X/#m20s">M20S</a></summary>
<summary><a href="../whatsminer/M2X/#m20s">M20S</a></summary>
<ul>
<li><a href="/miners/whatsminer/M2X/#m20sv10">V10</a></li>
<li><a href="/miners/whatsminer/M2X/#m20sv20">V20</a></li>
<li><a href="../whatsminer/M2X/#m20sv10">V10</a></li>
<li><a href="../whatsminer/M2X/#m20sv20">V20</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M2X/#m20s_1">M20S+</a></summary>
<summary><a href="../whatsminer/M2X/#m20s_1">M20S+</a></summary>
</details>
<details>
<summary><a href="/miners/whatsminer/M2X/#m21">M21</a></summary>
<summary><a href="../whatsminer/M2X/#m21">M21</a></summary>
</details>
<details>
<summary><a href="/miners/whatsminer/M2X/#m21s">M21S</a></summary>
<summary><a href="../whatsminer/M2X/#m21s">M21S</a></summary>
<ul>
<li><a href="/miners/whatsminer/M2X/#m21sv20">V20</a></li>
<li><a href="/miners/whatsminer/M2X/#m21sv60">V60</a></li>
<li><a href="../whatsminer/M2X/#m21sv20">V20</a></li>
<li><a href="../whatsminer/M2X/#m21sv60">V60</a></li>
</ul>
</details>
<details>
<summary><a href="/miners/whatsminer/M2X/#m21s_1">M21S+</a></summary>
<summary><a href="../whatsminer/M2X/#m21s_1">M21S+</a></summary>
</details>
</ul>
</details>
@@ -136,32 +161,33 @@ Supported miner types are here on this list. If your miner (or miner version) i
<details>
<summary>X19 Series:</summary>
<ul>
<li><a href="/miners/antminer/X19/#s19">S19</a></li>
<li><a href="/miners/antminer/X19/#s19-pro">S19 Pro</a></li>
<li><a href="/miners/antminer/X19/#s19a">S19a</a></li>
<li><a href="/miners/antminer/X19/#s19j">S19j</a></li>
<li><a href="/miners/antminer/X19/#s19j-pro">S19j Pro</a></li>
<li><a href="/miners/antminer/X19/#t19">T19</a></li>
<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="/miners/antminer/X17/#s17">S17</a></li>
<li><a href="/miners/antminer/X17/#s17_1">S17+</a></li>
<li><a href="/miners/antminer/X17/#s17-pro">S17 Pro</a></li>
<li><a href="/miners/antminer/X17/#s17e">S17e</a></li>
<li><a href="/miners/antminer/X17/#t17">T17</a></li>
<li><a href="/miners/antminer/X17/#t17_1">T17+</a></li>
<li><a href="/miners/antminer/X17/#t17e">T17e</a></li>
<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="/miners/antminer/X9/#s9">S9</a></li>
<li><a href="/miners/antminer/X9/#s9i">S9i</a></li>
<li><a href="/miners/antminer/X9/#t9">T9</a></li>
<li><a href="../antminer/X9/#s9">S9</a></li>
<li><a href="../antminer/X9/#s9i">S9i</a></li>
<li><a href="../antminer/X9/#t9">T9</a></li>
</ul>
</details>
</ul>
@@ -172,31 +198,31 @@ Supported miner types are here on this list. If your miner (or miner version) i
<details>
<summary>A7X Series:</summary>
<ul>
<li><a href="/miners/avalonminer/A7X/#a721">A721</a></li>
<li><a href="/miners/avalonminer/A7X/#a741">A741</a></li>
<li><a href="/miners/avalonminer/A7X/#a761">A761</a></li>
<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="/miners/avalonminer/A8X/#a821">A821</a></li>
<li><a href="/miners/avalonminer/A8X/#a841">A841</a></li>
<li><a href="/miners/avalonminer/A8X/#a851">A851</a></li>
<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="/miners/avalonminer/A9X/#a921">A921</a></li>
<li><a href="../avalonminer/A9X/#a921">A921</a></li>
</ul>
</details>
<details>
<summary>A10X Series:</summary>
<ul>
<li><a href="/miners/avalonminer/A10X/#a1026">A1026</a></li>
<li><a href="/miners/avalonminer/A10X/#a1047">A1047</a></li>
<li><a href="/miners/avalonminer/A10X/#a1066">A1066</a></li>
<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>
@@ -207,7 +233,7 @@ Supported miner types are here on this list. If your miner (or miner version) i
<details>
<summary>T3X Series:</summary>
<ul>
<li><a href="/miners/innosilicon/T3X/#t3h">T3H+</a></li>
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li>
</ul>
</details>
</ul>

View File

@@ -130,6 +130,14 @@
show_root_heading: false
heading_level: 4
## M31S+V30
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
## M31S+V40
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV40
@@ -185,3 +193,19 @@
options:
show_root_heading: false
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

@@ -21,6 +21,7 @@ nav:
- Avalon 10X: "miners/avalonminer/A10X.md"
- Whatsminer M2X: "miners/whatsminer/M2X.md"
- Whatsminer M3X: "miners/whatsminer/M3X.md"
- Whatsminer M5X: "miners/whatsminer/M5X.md"
- Innosilicon T3X: "miners/innosilicon/T3X.md"
- Network:
- Miner Network: "network/miner_network.md"

647
poetry.lock generated
View File

@@ -11,8 +11,8 @@ idna = ">=2.8"
sniffio = ">=1.1"
[package.extras]
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
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)"]
[[package]]
@@ -38,7 +38,7 @@ pywin32 = ["pywin32 (>=227)"]
[[package]]
name = "certifi"
version = "2022.6.15"
version = "2022.9.24"
description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false
@@ -55,9 +55,36 @@ python-versions = "*"
[package.dependencies]
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]]
name = "cryptography"
version = "37.0.4"
version = "38.0.1"
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
category = "main"
optional = false
@@ -68,11 +95,56 @@ cffi = ">=1.12"
[package.extras]
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
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)"]
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]]
name = "h11"
@@ -115,19 +187,181 @@ rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
sniffio = "*"
[package.extras]
brotli = ["brotlicffi", "brotli"]
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
brotli = ["brotli", "brotlicffi"]
cli = ["click (>=8.0.0,<9.0.0)", "pygments (>=2.0.0,<3.0.0)", "rich (>=10,<13)"]
http2 = ["h2 (>=3,<5)"]
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]]
name = "idna"
version = "3.3"
version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false
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]]
name = "passlib"
version = "1.7.4"
@@ -139,9 +373,37 @@ python-versions = "*"
[package.extras]
argon2 = ["argon2-cffi (>=18.2.0)"]
bcrypt = ["bcrypt (>=3.1.0)"]
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"]
build_docs = ["cloud-sptheme (>=1.10.1)", "sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)"]
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]]
name = "pyaml"
version = "21.10.1"
@@ -162,13 +424,57 @@ optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
[[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"
description = "YAML parser and emitter for Python"
category = "main"
optional = false
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]]
name = "rfc3986"
version = "1.5.0"
@@ -183,13 +489,34 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
[package.extras]
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]]
name = "sniffio"
version = "1.2.0"
version = "1.3.0"
description = "Sniff out which async library your code is running under"
category = "main"
optional = false
python-versions = ">=3.5"
python-versions = ">=3.7"
[[package]]
name = "toml"
@@ -207,10 +534,50 @@ category = "main"
optional = false
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]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "8d93eafd928d7fed4b0a00d13e46982c2d4310c37acb2faec7e7a477b3f35e9c"
content-hash = "98c3026a6f27c29c0357bbfa07166d6d8b604a869f3a802adc3bb3610f86964c"
[metadata.files]
anyio = [
@@ -222,8 +589,8 @@ asyncssh = [
{file = "asyncssh-2.12.0.tar.gz", hash = "sha256:274101322c4b941823aeed8e1ab6e7be5191686c6db2d2bd35afeba30505e780"},
]
certifi = [
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
]
cffi = [
{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.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 = [
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
{file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
{file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
{file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
{file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
{file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
{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 = [
{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.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 = [
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{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 = [
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
{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 = [
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
{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.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_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
{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-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
{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-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"},
@@ -378,13 +890,25 @@ pyyaml = [
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
{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 = [
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
{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 = [
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
toml = [
{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.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

@@ -18,6 +18,7 @@ import ipaddress
import warnings
import logging
from typing import Union
import re
from pyasic.errors import APIError, APIWarning
@@ -46,8 +47,8 @@ class BaseMinerAPI:
# each function in self
dir(self)
if callable(getattr(self, func)) and
# no __ methods
not func.startswith("__") and
# no __ or _ methods
not func.startswith("__") and not func.startswith("_") and
# remove all functions that are in this base class
func
not in [
@@ -71,61 +72,27 @@ If you are sure you want to use this command please use API.send_command("{comma
)
return return_commands
async def multicommand(
self, *commands: str, ignore_x19_error: bool = False
) -> dict:
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
"""Creates and sends multiple commands as one command to the miner.
Parameters:
*commands: The commands to send as a multicommand to the miner.
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]}")
# 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
# doesn't work for S19 which uses the backup _x19_multicommand
command = "+".join(commands)
try:
data = await self.send_command(command, x19_command=ignore_x19_error)
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("+"))
return {}
logging.debug(f"{self.ip}: Received multicommand data.")
return data
async def _x19_multicommand(self, *commands):
data = None
try:
data = {}
# send all commands individually
for cmd in commands:
data[cmd] = []
data[cmd].append(await self.send_command(cmd, x19_command=True))
except APIError as e:
raise APIError(e)
except Exception as e:
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
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.
"""
async def _send_bytes(self, data: bytes) -> bytes:
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
@@ -133,19 +100,14 @@ If you are sure you want to use this command please use API.send_command("{comma
except OSError as e:
if e.winerror == "121":
logging.warning("Semaphore Timeout has Expired.")
return {}
# create the command
cmd = {"command": command}
if parameters:
cmd["parameter"] = parameters
return b"{}"
# send the command
writer.write(json.dumps(cmd).encode("utf-8"))
writer.write(data)
await writer.drain()
# instantiate data
data = b""
ret_data = b""
# loop to receive all the data
try:
@@ -153,27 +115,60 @@ If you are sure you want to use this command please use API.send_command("{comma
d = await reader.read(4096)
if not d:
break
data += d
ret_data += d
except Exception as e:
logging.warning(f"{self.ip}: API Command Error: - {e}")
data = self._load_api_data(data)
# close the connection
writer.close()
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
if not ignore_errors:
# validate the command succeeded
validation = self._validate_command_output(data)
if not validation[0]:
if not x19_command:
logging.warning(f"{self.ip}: API Command Error: {validation[1]}")
if allow_warning:
logging.warning(
f"{self.ip}: API Command Error: {command}: {validation[1]}"
)
raise APIError(validation[1])
return data
async def send_privileged_command(self, *args, **kwargs) -> dict:
return await self.send_command(*args, **kwargs)
@staticmethod
def _validate_command_output(data: dict) -> tuple:
# check if the data returned is correct or an error
@@ -204,31 +199,39 @@ If you are sure you want to use this command please use API.send_command("{comma
@staticmethod
def _load_api_data(data: bytes) -> dict:
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:
# some json from the API returns with a null byte (\x00) on the end
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode("utf-8")[:-1]
else:
# no null byte
str_data = data.decode("utf-8")
# fix an error with a btminer return having an extra comma that breaks json.loads()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()
str_data = str_data.replace("\n", "")
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
str_data = str_data.replace("}{", "},{")
# fix an error with a bmminer return having a specific comma that breaks json.loads()
str_data = str_data.replace("[,{", "[{")
# fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("inf", "0")
str_data = str_data.replace("nan", "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):
str_data = f"{{{str_data[1:]}"
# parse the json
parsed_data = json.loads(str_data)
# handle bad json
except json.decoder.JSONDecodeError as e:
raise APIError(f"Decode Error {e}: {str_data}")
return parsed_data

View File

@@ -11,8 +11,10 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
from pyasic.API import BaseMinerAPI
from pyasic.API import APIError
class BMMinerAPI(BaseMinerAPI):
@@ -36,6 +38,37 @@ class BMMinerAPI(BaseMinerAPI):
def __init__(self, ip: str, port: int = 4028) -> None:
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:
"""Get miner version info.
<details>

View File

@@ -187,57 +187,24 @@ class BTMinerAPI(BaseMinerAPI):
self.pwd = pwd
self.current_token = None
async def send_command(
self,
command: Union[str, bytes],
parameters: Union[str, int, bool] = None,
ignore_errors: bool = False,
**kwargs,
async def send_privileged_command(
self, command: Union[str, bytes], ignore_errors: bool = False, **kwargs
) -> dict:
# check if command is a string
# if its bytes its encoded and needs to be sent raw
if isinstance(command, str):
# if it is a string, put it into the standard command format
command = json.dumps({"command": command}).encode("utf-8")
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
# handle OSError 121
except OSError as e:
if e.winerror == "121":
print("Semaphore Timeout has Expired.")
return {}
command = {"cmd": command}
for kwarg in kwargs:
if kwargs[kwarg]:
command[kwarg] = kwargs[kwarg]
# send the command
writer.write(command)
await writer.drain()
# instantiate data
data = b""
# loop to receive all the data
try:
while True:
d = await reader.read(4096)
if not d:
break
data += d
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
data = await self._send_bytes(enc_command)
data = self._load_api_data(data)
# close the connection
writer.close()
await writer.wait_closed()
# check if the returned data is encoded
if "enc" in data.keys():
# try to parse the encoded data
try:
data = parse_btminer_priviledge_data(self.current_token, data)
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
try:
data = parse_btminer_priviledge_data(self.current_token, data)
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
if not ignore_errors:
# if it fails to validate, it is likely an error
@@ -320,46 +287,18 @@ class BTMinerAPI(BaseMinerAPI):
A dict from the API to confirm the pools were updated.
</details>
"""
# get the token and password from the miner
token_data = await self.get_token()
# parse pool data
if not pool_1:
raise APIError("No pools set.")
elif pool_2 and pool_3:
command = {
"cmd": "update_pools",
"pool1": pool_1,
"worker1": worker_1,
"passwd1": passwd_1,
"pool2": pool_2,
"worker2": worker_2,
"passwd2": passwd_2,
"pool3": pool_3,
"worker3": worker_3,
"passwd3": passwd_3,
}
elif pool_2:
command = {
"cmd": "update_pools",
"pool1": pool_1,
"worker1": worker_1,
"passwd1": passwd_1,
"pool2": pool_2,
"worker2": worker_2,
"passwd2": passwd_2,
}
else:
command = {
"cmd": "update_pools",
"pool1": pool_1,
"worker1": worker_1,
"passwd1": passwd_1,
}
# encode the command with the token data
enc_command = create_privileged_cmd(token_data, command)
# send the command
return await self.send_command(enc_command)
return await self.send_privileged_command(
"update_pools",
pool1=pool_1,
worker1=worker_1,
passwd1=passwd_1,
pool2=pool_2,
worker2=worker_2,
passwd2=passwd_2,
pool3=pool_3,
worker3=worker_3,
passwd3=passwd_3,
)
async def restart(self) -> dict:
"""Restart BTMiner using the API.
@@ -373,10 +312,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the restart.
</details>
"""
command = {"cmd": "restart_btminer"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("restart_btminer")
async def power_off(self, respbefore: bool = True) -> dict:
"""Power off the miner using the API.
@@ -393,12 +329,8 @@ class BTMinerAPI(BaseMinerAPI):
</details>
"""
if respbefore:
command = {"cmd": "power_off", "respbefore": "true"}
else:
command = {"cmd": "power_off", "respbefore": "false"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("power_off", respbefore="true")
return await self.send_privileged_command("power_off", respbefore="false")
async def power_on(self) -> dict:
"""Power on the miner using the API.
@@ -413,10 +345,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of powering on.
</details>
"""
command = {"cmd": "power_on"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("power_on")
async def reset_led(self) -> dict:
"""Reset the LED on the miner using the API.
@@ -431,10 +360,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of resetting the LED.
</details>
"""
command = {"cmd": "set_led", "param": "auto"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.set_led(auto=True)
async def set_led(
self,
@@ -462,19 +388,11 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting the LED.
</details>
"""
if not auto:
command = {
"cmd": "set_led",
"color": color,
"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)
if auto:
return await self.send_privileged_command("set_led", param=auto)
return await self.send_privileged_command(
"set_led", color=color, period=period, duration=duration, start=start
)
async def set_low_power(self) -> dict:
"""Set low power mode on the miner using the API.
@@ -489,10 +407,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting low power mode.
</details>
"""
command = {"cmd": "set_low_power"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("set_low_power")
async def update_firmware(self): # noqa - static
"""Not implemented."""
@@ -510,10 +425,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of the reboot.
</details>
"""
command = {"cmd": "reboot"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("reboot")
async def factory_reset(self) -> dict:
"""Reset the miner to factory defaults.
@@ -525,10 +437,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of the reset.
</details>
"""
command = {"cmd": "factory_reset"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("factory_reset")
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
"""Update the admin user's password.
@@ -555,11 +464,10 @@ class BTMinerAPI(BaseMinerAPI):
f"New password too long, the max length is 8. "
f"Password size: {len(new_pwd.encode('utf-8'))}"
)
command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
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:
raise e
self.pwd = new_pwd
@@ -588,10 +496,9 @@ class BTMinerAPI(BaseMinerAPI):
f"range. Please set a % between -10 and "
f"100"
)
command = {"cmd": "set_target_freq", "percent": str(percent)}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command(
"set_target_freq", percent=str(percent)
)
async def enable_fast_boot(self) -> dict:
"""Turn on fast boot.
@@ -607,10 +514,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of enabling fast boot.
</details>
"""
command = {"cmd": "enable_btminer_fast_boot"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("enable_btminer_fast_boot")
async def disable_fast_boot(self) -> dict:
"""Turn off fast boot.
@@ -626,10 +530,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of disabling fast boot.
</details>
"""
command = {"cmd": "disable_btminer_fast_boot"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("disable_btminer_fast_boot")
async def enable_web_pools(self) -> dict:
"""Turn on web pool updates.
@@ -645,10 +546,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of enabling web pools.
</details>
"""
command = {"cmd": "enable_web_pools"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("enable_web_pools")
async def disable_web_pools(self) -> dict:
"""Turn off web pool updates.
@@ -664,10 +562,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of disabling web pools.
</details>
"""
command = {"cmd": "disable_web_pools"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("disable_web_pools")
async def set_hostname(self, hostname: str) -> dict:
"""Set the hostname of the miner.
@@ -685,10 +580,7 @@ class BTMinerAPI(BaseMinerAPI):
A reply informing of the status of setting the hostname.
</details>
"""
command = {"cmd": "set_hostname", "hostname": hostname}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("set_hostname", hostname=hostname)
async def set_power_pct(self, percent: int) -> dict:
"""Set the power percentage of the miner.
@@ -713,10 +605,7 @@ class BTMinerAPI(BaseMinerAPI):
f"range. Please set a % between 0 and "
f"100"
)
command = {"cmd": "set_power_pct", "percent": str(percent)}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command("set_power_pct", percent=str(percent))
async def pre_power_on(self, complete: bool, msg: str) -> dict:
"""Configure or check status of pre power on.
@@ -747,13 +636,12 @@ class BTMinerAPI(BaseMinerAPI):
'"adjust continue"]'
)
if complete:
complete = "true"
else:
complete = "false"
command = {"cmd": "pre_power_on", "complete": complete, "msg": msg}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
return await self.send_privileged_command(
"pre_power_on", complete="true", msg=msg
)
return await self.send_privileged_command(
"pre_power_on", complete="false", msg=msg
)
#### END privileged COMMANDS ####
@@ -879,4 +767,18 @@ class BTMinerAPI(BaseMinerAPI):
General miner info.
</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
# limitations under the License.
from pyasic.API import BaseMinerAPI
from pyasic.API import BaseMinerAPI, APIError
import logging
class CGMinerAPI(BaseMinerAPI):
@@ -36,6 +38,37 @@ class CGMinerAPI(BaseMinerAPI):
def __init__(self, ip: str, port: int = 4028):
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:
"""Get miner version info.
<details>

View File

@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from dataclasses import dataclass, asdict
from dataclasses import dataclass, asdict, fields
from typing import Literal, List
import random
import string
@@ -37,6 +37,10 @@ class _Pool:
username: str = ""
password: str = ""
@classmethod
def fields(cls):
return fields(cls)
def from_dict(self, data: dict):
"""Convert raw pool data as a dict to usable data and save it to this class.
@@ -136,6 +140,10 @@ class _PoolGroup:
group_name: str = None
pools: List[_Pool] = None
@classmethod
def fields(cls):
return fields(cls)
def __post_init__(self):
if not self.group_name:
self.group_name = "".join(
@@ -263,6 +271,10 @@ class MinerConfig:
dps_shutdown_enabled: bool = None
dps_shutdown_duration: float = None
@classmethod
def fields(cls):
return fields(cls)
def as_dict(self) -> dict:
"""Convert the data in this class to a dict."""
data_dict = asdict(self)

View File

@@ -13,14 +13,27 @@
# limitations under the License.
from typing import Union, List
from dataclasses import dataclass, field, asdict
from dataclasses import dataclass, field, asdict, fields
from datetime import datetime, timezone
from functools import reduce
import time
import json
import copy
from .error_codes import X19Error, WhatsminerError, BraiinsOSError, InnosiliconError
@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
class MinerData:
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
@@ -48,6 +61,7 @@ class MinerData:
fan_2: The speed of the second fan as an int.
fan_3: The speed of the third fan as an int.
fan_4: The speed of the fourth fan as an int.
fan_psu: The speed of the PSU on the fan if the miner collects it.
left_chips: The number of chips online in the left board as an int.
center_chips: The number of chips online in the left board as an int.
right_chips: The number of chips online in the left board as an int.
@@ -71,26 +85,29 @@ class MinerData:
model: str = "Unknown"
hostname: str = "Unknown"
hashrate: float = 0
left_board_hashrate: float = 0.0
center_board_hashrate: float = 0.0
right_board_hashrate: float = 0.0
hashboards: List[HashBoard] = field(default_factory=list)
ideal_hashboards: int = 1
left_board_hashrate: float = field(init=False)
center_board_hashrate: float = field(init=False)
right_board_hashrate: float = field(init=False)
temperature_avg: int = field(init=False)
env_temp: float = 0.0
left_board_temp: int = 0
left_board_chip_temp: int = 0
center_board_temp: int = 0
center_board_chip_temp: int = 0
right_board_temp: int = 0
right_board_chip_temp: int = 0
wattage: int = 0
wattage_limit: int = 0
env_temp: float = -1.0
left_board_temp: int = field(init=False)
left_board_chip_temp: int = field(init=False)
center_board_temp: int = field(init=False)
center_board_chip_temp: int = field(init=False)
right_board_temp: int = field(init=False)
right_board_chip_temp: int = field(init=False)
wattage: int = -1
wattage_limit: int = -1
fan_1: int = -1
fan_2: int = -1
fan_3: int = -1
fan_4: int = -1
left_chips: int = 0
center_chips: int = 0
right_chips: int = 0
fan_psu: int = -1
left_chips: int = field(init=False)
center_chips: int = field(init=False)
right_chips: int = field(init=False)
total_chips: int = field(init=False)
ideal_chips: int = 1
percent_ideal: float = field(init=False)
@@ -106,6 +123,11 @@ class MinerData:
fault_light: Union[bool, None] = None
efficiency: int = field(init=False)
@classmethod
def fields(cls):
return [f.name for f in fields(cls)]
def __post_init__(self):
self.datetime = datetime.now(timezone.utc).astimezone()
@@ -121,14 +143,187 @@ class MinerData:
def __iter__(self):
return iter([item for item in self.asdict()])
def __truediv__(self, other):
return self // other
def __floordiv__(self, other):
cp = copy.deepcopy(self)
for key in self:
item = getattr(self, key)
if isinstance(item, int):
setattr(cp, key, item // other)
if isinstance(item, float):
setattr(cp, key, round(item / other, 2))
return cp
def __add__(self, other):
if not isinstance(other, MinerData):
raise TypeError("Cannot add MinerData to non MinerData type.")
cp = copy.deepcopy(self)
for key in self:
item = getattr(self, key)
other_item = getattr(other, key)
if item is None:
item = 0
if other_item is None:
other_item = 0
if isinstance(item, int):
setattr(cp, key, item + other_item)
if isinstance(item, float):
setattr(cp, key, item + other_item)
if isinstance(item, str):
setattr(cp, key, "")
if isinstance(item, list):
setattr(cp, key, item + other_item)
if isinstance(item, bool):
setattr(cp, key, item & other_item)
return cp
@property
def total_chips(self): # noqa - Skip PyCharm inspection
return self.right_chips + self.center_chips + self.left_chips
return sum([hb.chips for hb in self.hashboards])
@total_chips.setter
def total_chips(self, val):
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
def nominal(self): # noqa - Skip PyCharm inspection
return self.ideal_chips == self.total_chips
@@ -149,13 +344,9 @@ class MinerData:
def temperature_avg(self): # noqa - Skip PyCharm inspection
total_temp = 0
temp_count = 0
for temp in [
self.left_board_chip_temp,
self.center_board_chip_temp,
self.right_board_chip_temp,
]:
if temp and not temp == 0:
total_temp += temp
for hb in self.hashboards:
if hb.temp and not hb.temp == -1:
total_temp += hb.temp
temp_count += 1
if not temp_count > 0:
return 0
@@ -193,6 +384,21 @@ class MinerData:
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data)
def as_csv(self) -> str:
"""Get this dataclass as CSV.
Returns:
A CSV version of this class with no headers.
"""
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/).

View File

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

View File

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

View File

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

View File

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

View File

@@ -20,7 +20,7 @@ from typing import Union, List
from pyasic.API.bmminer import BMMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.config import MinerConfig
from pyasic.data.error_codes import MinerErrorData
@@ -185,13 +185,17 @@ class BMMiner(BaseMiner):
async def resume_mining(self) -> bool:
return False
async def get_data(self) -> MinerData:
async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1
fan_offset = -1
@@ -219,7 +223,7 @@ class BMMiner(BaseMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True
"summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -242,7 +246,7 @@ class BMMiner(BaseMiner):
if stats:
boards = stats.get("STATS")
if boards:
if len(boards) > 0:
if len(boards) > 1:
for board_num in range(1, 16, 5):
for _b_num in range(5):
b = boards[1].get(f"chain_acn{board_num + _b_num}")
@@ -252,19 +256,38 @@ class BMMiner(BaseMiner):
if board_offset == -1:
board_offset = 1
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
env_temp_list = []
for i in range(board_offset, board_offset + self.ideal_hashboards):
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.nominal_chips
)
data.left_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
)
data.center_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
)
data.right_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
)
chip_temp = boards[1].get(f"temp{i}")
if chip_temp:
hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}")
if temp:
hashboard.temp = round(temp)
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:
temp = stats.get("STATS")
@@ -282,20 +305,6 @@ class BMMiner(BaseMiner):
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:
pool_1 = None
pool_2 = None

View File

@@ -25,7 +25,7 @@ from pyasic.API.bosminer import BOSMinerAPI
from pyasic.errors import APIError
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.config import MinerConfig
@@ -266,7 +266,10 @@ class BOSMiner(BaseMiner):
logging.warning(e)
if tunerstatus:
tuner = tunerstatus[0].get("TUNERSTATUS")
try:
tuner = tunerstatus[0].get("TUNERSTATUS")
except KeyError:
tuner = tunerstatus.get("TUNERSTATUS")
if tuner:
if len(tuner) > 0:
chain_status = tuner[0].get("TunerChainStatus")
@@ -294,13 +297,21 @@ class BOSMiner(BaseMiner):
)
return errors
async def get_data(self) -> MinerData:
async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * 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
fan_offset = -1
@@ -331,6 +342,7 @@ class BOSMiner(BaseMiner):
"devdetails",
"fans",
"devs",
allow_warning=allow_warning
)
except APIError as e:
if str(e.message) == "Not ready":
@@ -361,14 +373,13 @@ class BOSMiner(BaseMiner):
temp = temps[0].get("TEMPS")
if temp:
if len(temp) > 0:
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
for board in temp:
_id = board["ID"] - offset
chip_temp = round(board["Chip"])
board_temp = round(board["Board"])
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
setattr(data, f"{board_map[_id]}_temp", board_temp)
data.hashboards[_id].chip_temp = chip_temp
data.hashboards[_id].temp = board_temp
if fans:
fan_data = fans[0].get("FANS")
@@ -461,27 +472,25 @@ class BOSMiner(BaseMiner):
boards = devdetails[0].get("DEVDETAILS")
if boards:
if len(boards) > 0:
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
for board in boards:
_id = board["ID"] - offset
chips = board["Chips"]
setattr(data, board_map[_id], chips)
data.hashboards[_id].chips = chips
if chips > 0:
data.hashboards[_id].missing = False
else:
data.hashboards[_id].missing = True
if devs:
boards = devs[0].get("DEVS")
if boards:
if len(boards) > 0:
board_map = {
0: "left_board_hashrate",
1: "center_board_hashrate",
2: "right_board_hashrate",
}
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
for board in boards:
_id = board["ID"] - offset
hashrate = round(board["MHS 1m"] / 1000000, 2)
setattr(data, board_map[_id], hashrate)
data.hashboards[_id].hashrate = hashrate
return data
async def get_mac(self):

View File

@@ -21,7 +21,7 @@ from pyasic.API.btminer import BTMinerAPI
from pyasic.miners.base import BaseMiner
from pyasic.errors import APIError
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.data.error_codes import WhatsminerError, MinerErrorData
from pyasic.config import MinerConfig
@@ -109,7 +109,6 @@ class BTMiner(BaseMiner):
if "Code" in data.keys():
if data["Code"] == 131:
return True
print(data)
return False
async def check_light(self) -> bool:
@@ -158,14 +157,31 @@ class BTMiner(BaseMiner):
async def get_errors(self) -> List[MinerErrorData]:
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}"]
)
)
summary_data = await self.api.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}"):
data.append(
WhatsminerError(error_code=summary_data[0][f"Error Code {i}"])
)
return data
async def reboot(self) -> bool:
@@ -225,13 +241,17 @@ class BTMiner(BaseMiner):
cfg = cfg.from_api(pools["POOLS"])
return cfg
async def get_data(self) -> MinerData:
async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
mac = None
@@ -260,18 +280,25 @@ class BTMiner(BaseMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
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:
break
except APIError:
pass
if not miner_data:
return data
summary = miner_data.get("summary")[0]
devs = miner_data.get("devs")[0]
pools = miner_data.get("pools")[0]
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:
summary_data = summary.get("SUMMARY")
@@ -287,6 +314,9 @@ class BTMiner(BaseMiner):
if summary_data[0].get("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_2 = summary_data[0]["Fan Speed Out"]
@@ -306,39 +336,47 @@ class BTMiner(BaseMiner):
if summary_data[0].get("Error Code Count"):
for i in range(summary_data[0]["Error Code Count"]):
if summary_data[0].get(f"Error Code {i}"):
data.errors.append(
WhatsminerError(
error_code=summary_data[0][f"Error Code {i}"]
if not summary_data[0][f"Error Code {i}"] == "":
data.errors.append(
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:
temp_data = devs.get("DEVS")
if temp_data:
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
for board in temp_data:
_id = board["ASC"]
chip_temp = round(board["Chip Temp Avg"])
board_temp = round(board["Temperature"])
hashrate = round(board["MHS 1m"] / 1000000, 2)
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
setattr(data, f"{board_map[_id]}_temp", board_temp)
setattr(data, f"{board_map[_id]}_hashrate", hashrate)
if devs:
boards = devs.get("DEVS")
if boards:
if len(boards) > 0:
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
if "ID" in boards[0].keys():
id_key = "ID"
else:
id_key = "ASC"
offset = boards[0][id_key]
for board in boards:
_id = board[id_key] - offset
chips = board["Effective Chips"]
setattr(data, board_map[_id], chips)
dev_data = devs.get("DEVS")
if dev_data:
for board in dev_data:
temp_board = HashBoard(
slot=board["ASC"],
chip_temp=round(board["Chip Temp Avg"]),
temp=round(board["Temperature"]),
hashrate=round(board["MHS 1m"] / 1000000, 2),
chips=board["Effective Chips"],
missing=False if board["Effective Chips"] > 0 else True,
expected_chips=self.nominal_chips,
)
data.hashboards.append(temp_board)
if pools:
pool_1 = None

View File

@@ -22,7 +22,7 @@ from pyasic.miners.base import BaseMiner
from pyasic.errors import APIError
from pyasic.config import MinerConfig
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.settings import PyasicSettings
@@ -173,13 +173,17 @@ class CGMiner(BaseMiner):
async def get_mac(self) -> str:
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.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1
fan_offset = -1
@@ -202,7 +206,7 @@ class CGMiner(BaseMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True
"summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -235,19 +239,38 @@ class CGMiner(BaseMiner):
if board_offset == -1:
board_offset = 1
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
env_temp_list = []
for i in range(board_offset, board_offset + self.ideal_hashboards):
hashboard = HashBoard(
slot=i - board_offset, expected_chips=self.nominal_chips
)
data.left_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset}")) / 1000, 2
)
data.center_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+1}")) / 1000, 2
)
data.right_board_hashrate = round(
float(boards[1].get(f"chain_rate{board_offset+2}")) / 1000, 2
)
chip_temp = boards[1].get(f"temp{i}")
if chip_temp:
hashboard.chip_temp = round(chip_temp)
temp = boards[1].get(f"temp2_{i}")
if temp:
hashboard.temp = round(temp)
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:
temp = stats.get("STATS")
@@ -265,19 +288,6 @@ class CGMiner(BaseMiner):
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
)
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
env_temp_list = []
for item in range(3):
board_temp = temp[1].get(f"temp{item + board_offset}")
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
setattr(data, f"{board_map[item]}_temp", board_temp)
if f"temp_pcb{item}" in temp[1].keys():
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
if not env_temp == 0:
env_temp_list.append(int(env_temp))
data.env_temp = sum(env_temp_list) / len(env_temp_list)
if pools:
pool_1 = None
pool_2 = None

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

@@ -18,6 +18,8 @@ from .S19_Pro import S19Pro
from .S19j import S19j
from .S19j_Pro import S19jPro
from .S19_XP import S19XP
from .S19a import S19a
from .T19 import T19

View File

@@ -33,6 +33,15 @@ class M31SPlusVE20(BaseMiner):
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__()

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

@@ -25,6 +25,7 @@ from .M31S import M31S
from .M31S_Plus import (
M31SPlus,
M31SPlusVE20,
M31SPlusV30,
M31SPlusV40,
M31SPlusV80,
M31SPlusV60,
@@ -33,3 +34,4 @@ from .M31S_Plus import (
from .M32 import M32, M32V20
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

@@ -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 M50, M50VH50

View File

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

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

View File

@@ -18,6 +18,8 @@ from .S19_Pro import BMMinerS19Pro
from .S19j import BMMinerS19j
from .S19j_Pro import BMMinerS19jPro
from .S19_XP import BMMinerS19XP
from .S19a import BMMinerS19a
from .T19 import BMMinerT19

View File

@@ -15,7 +15,7 @@
from pyasic.miners._backends import Hiveon # noqa - Ignore access to _module
from pyasic.miners._types import T9 # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.settings import PyasicSettings
@@ -33,13 +33,17 @@ class HiveonT9(Hiveon, T9):
)
return mac
async def get_data(self) -> MinerData:
async def get_data(self, allow_warning: bool = False) -> MinerData:
"""Get data from the miner.
Returns:
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
"""
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
data = MinerData(
ip=str(self.ip),
ideal_chips=self.nominal_chips * self.ideal_hashboards,
ideal_hashboards=self.ideal_hashboards,
)
board_offset = -1
fan_offset = -1
@@ -67,7 +71,7 @@ class HiveonT9(Hiveon, T9):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True
"summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -97,14 +101,17 @@ class HiveonT9(Hiveon, T9):
)
board_map = {
"left": [2, 9, 10],
"center": [3, 11, 12],
"right": [4, 13, 14],
0: [2, 9, 10],
1: [3, 11, 12],
2: [4, 13, 14],
}
env_temp_list = []
for board in board_map.keys():
hashboard = HashBoard(
slot=board, expected_chips=self.nominal_chips
)
chips = 0
hashrate = 0
chip_temp = 0
@@ -121,10 +128,15 @@ class HiveonT9(Hiveon, T9):
hashrate += boards[1][f"chain_rate{chipset}"]
chips += boards[1][f"chain_acn{chipset}"]
setattr(data, f"{board}_chips", chips)
setattr(data, f"{board}_board_hashrate", hashrate)
setattr(data, f"{board}_board_temp", board_temp)
setattr(data, f"{board}_board_chip_temp", chip_temp)
hashboard.hashrate = hashrate
hashboard.chips = chips
hashboard.temp = board_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 == []:
data.env_temp = sum(env_temp_list) / len(env_temp_list)

View File

@@ -14,7 +14,7 @@
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
@@ -80,8 +80,16 @@ class CGMinerA10X(CGMiner):
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
async def get_data(self, allow_warning: bool = True):
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()
mac = None
@@ -92,7 +100,7 @@ class CGMinerA10X(CGMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
"version", "summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -145,27 +153,20 @@ class CGMinerA10X(CGMiner):
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
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])
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
data.hashboards[board].chip_temp = chip_temp[board]
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] 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"]
)
temp = raw_data.get("MTavg")
if temp:
data.hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
data.hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
if pools:
pool_1 = None

View File

@@ -14,7 +14,7 @@
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
@@ -80,8 +80,16 @@ class CGMinerA7X(CGMiner):
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
async def get_data(self, allow_warning: bool = True):
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()
mac = None
@@ -92,7 +100,7 @@ class CGMinerA7X(CGMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
"version", "summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -145,27 +153,20 @@ class CGMinerA7X(CGMiner):
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
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])
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
data.hashboards[board].chip_temp = chip_temp[board]
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] 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"]
)
temp = raw_data.get("MTavg")
if temp:
data.hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
data.hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
if pools:
pool_1 = None

View File

@@ -14,7 +14,7 @@
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
@@ -80,8 +80,16 @@ class CGMinerA8X(CGMiner):
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
async def get_data(self, allow_warning: bool = True):
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()
mac = None
@@ -92,7 +100,7 @@ class CGMinerA8X(CGMiner):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
"version", "summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -145,27 +153,20 @@ class CGMinerA8X(CGMiner):
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
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])
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
data.hashboards[board].chip_temp = chip_temp[board]
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] 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"]
)
temp = raw_data.get("MTavg")
if temp:
data.hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
data.hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
if pools:
pool_1 = None

View File

@@ -15,7 +15,7 @@
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.settings import PyasicSettings
import re
from pyasic.config import MinerConfig
@@ -81,8 +81,16 @@ class CGMinerAvalon921(CGMiner, Avalon921):
)
return mac
async def get_data(self):
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
async def get_data(self, allow_warning: bool = True):
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()
mac = None
@@ -95,7 +103,7 @@ class CGMinerAvalon921(CGMiner, Avalon921):
miner_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"version", "summary", "pools", "stats"
"version", "summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
break
@@ -146,27 +154,20 @@ class CGMinerAvalon921(CGMiner, Avalon921):
f"fan_{fan+1}",
int(raw_data[f"Fan{fan+1}"]),
)
if "MTmax" in raw_data.keys():
data.left_board_chip_temp = int(raw_data["MTmax"][0])
data.center_board_chip_temp = int(raw_data["MTmax"][1])
data.right_board_chip_temp = int(raw_data["MTmax"][2])
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])
for board in range(self.ideal_hashboards):
chip_temp = raw_data.get("MTmax")
if chip_temp:
data.hashboards[board].chip_temp = chip_temp[board]
if "PVT_T0" in raw_data:
data.left_chips = len(
[item for item in raw_data["PVT_T0"] if not item == "0"]
)
if "PVT_T1" in raw_data:
data.center_chips = len(
[item for item in raw_data["PVT_T1"] 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"]
)
temp = raw_data.get("MTavg")
if temp:
data.hashboards[board].temp = temp[board]
chips = raw_data.get(f"PVT_T{board}")
if chips:
data.hashboards[board].chips = len(
[item for item in chips if not item == "0"]
)
if pools:
pool_1 = None

View File

@@ -43,6 +43,7 @@ class BaseMiner(ABC):
self.version = None
self.fan_count = 2
self.config = None
self.ideal_hashboards = 3
def __new__(cls, *args, **kwargs):
if cls is BaseMiner:
@@ -191,7 +192,7 @@ class BaseMiner(ABC):
pass
@abstractmethod
async def get_data(self) -> MinerData:
async def get_data(self, allow_warning: bool = True) -> MinerData:
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
Returns:

View File

@@ -14,7 +14,7 @@
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
from pyasic.miners._types import InnosiliconT3HPlus # noqa - Ignore access to _module
from pyasic.data import MinerData
from pyasic.data import MinerData, HashBoard
from pyasic.data.error_codes import InnosiliconError, MinerErrorData
from pyasic.settings import PyasicSettings
from pyasic.config import MinerConfig
@@ -157,8 +157,16 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
errors.append(InnosiliconError(error_code=err))
return errors
async def get_data(self) -> MinerData:
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
async def get_data(self, allow_warning: bool = False) -> MinerData:
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
fan_offset = -1
@@ -179,7 +187,7 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
all_data = None
for i in range(PyasicSettings().miner_get_data_retries):
miner_data = await self.api.multicommand(
"summary", "pools", "stats", ignore_x19_error=True
"summary", "pools", "stats", allow_warning=allow_warning
)
if miner_data:
@@ -215,32 +223,31 @@ class CGMinerInnosiliconT3HPlus(CGMiner, InnosiliconT3HPlus):
if stats:
stats = stats[0]
if stats.get("STATS"):
board_map = {0: "left", 1: "center", 2: "right"}
for idx, board in enumerate(stats["STATS"]):
data.hashboards[idx].missing = True
chips = board.get("Num active chips")
if chips:
setattr(data, f"{board_map[idx]}_chips", chips)
temp = board.get("Temp")
if temp:
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
data.hashboards[idx].chips = chips
if chips > 0:
data.hashboards[idx].missing = False
if all_data:
if all_data.get("chain"):
board_map = {0: "left", 1: "center", 2: "right"}
for idx, board in enumerate(all_data["chain"]):
temp = board.get("Temp max")
temp = board.get("Temp min")
if temp:
setattr(data, f"{board_map[idx]}_board_chip_temp", temp)
temp_board = board.get("Temp min")
if temp_board:
setattr(data, f"{board_map[idx]}_board_temp", temp_board)
hr = board.get("Hash Rate H")
if hr:
setattr(
data,
f"{board_map[idx]}_board_hashrate",
round(hr / 1000000000000, 2),
data.hashboards[idx].temp = round(temp)
hashrate = board.get("Hash Rate H")
if hashrate:
data.hashboards[idx].hashrate = round(
hashrate / 1000000000000, 2
)
chip_temp = board.get("Temp max")
if chip_temp:
data.hashboards[idx].chip_temp = round(chip_temp)
if all_data.get("fansSpeed"):
speed = round((all_data["fansSpeed"] * 6000) / 100)
for fan in range(self.fan_count):

View File

@@ -129,6 +129,10 @@ MINER_CLASSES = {
"BMMiner": BMMinerS19jPro,
"CGMiner": CGMinerS19jPro,
},
"ANTMINER S19 XP": {
"Default": BMMinerS19XP,
"BMMiner": BMMinerS19XP,
},
"ANTMINER S19A": {
"Default": BMMinerS19a,
"BMMiner": BMMinerS19a,
@@ -197,6 +201,7 @@ MINER_CLASSES = {
"Default": BTMinerM31SPlus,
"BTMiner": BTMinerM31SPlus,
"E20": BTMinerM31SPlusVE20,
"30": BTMinerM31SPlusV30,
"40": BTMinerM31SPlusV40,
"60": BTMinerM31SPlusV60,
"80": BTMinerM31SPlusV80,
@@ -211,6 +216,16 @@ MINER_CLASSES = {
"BTMiner": BTMinerM32,
"20": BTMinerM32V20,
},
"M34S+": {
"Default": BTMinerM34SPlus,
"BTMiner": BTMinerM34SPlus,
"E10": BTMinerM34SPlusVE10,
},
"M50": {
"Default": BTMinerM50,
"BTMiner": BTMinerM50,
"H50": BTMinerM50VH50,
},
"AVALONMINER 721": {
"Default": CGMinerAvalon721,
"CGMiner": CGMinerAvalon721,

View File

@@ -27,7 +27,13 @@ class _MinerListener:
def datagram_received(self, data, _addr):
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()}
MinerListener().new_miner = new_miner
@@ -46,9 +52,12 @@ class MinerListener(metaclass=Singleton):
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
)
transport_8888, protocol_8888 = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 8888) # noqa
)
while True:
if self.new_miner:
@@ -56,7 +65,8 @@ class MinerListener(metaclass=Singleton):
self.found_miners.append(self.new_miner)
self.new_miner = None
if self.stop:
transport.close()
transport_14235.close()
transport_8888.close()
break
await asyncio.sleep(0)

View File

@@ -72,5 +72,5 @@ class UnknownMiner(BaseMiner):
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def get_data(self) -> MinerData:
async def get_data(self, _) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -16,6 +16,7 @@ from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
from pyasic.miners._types import (
M31SPlus,
M31SPlusVE20,
M31SPlusV30,
M31SPlusV40,
M31SPlusV60,
M31SPlusV80,
@@ -35,6 +36,12 @@ class BTMinerM31SPlusVE20(BTMiner, M31SPlusVE20):
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)

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 (
M34SPlus,
M34SPlusVE10,
) # noqa - Ignore access to _module
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

@@ -36,6 +36,7 @@ from .M31S import BTMinerM31S
from .M31S_Plus import (
BTMinerM31SPlus,
BTMinerM31SPlusVE20,
BTMinerM31SPlusV30,
BTMinerM31SPlusV40,
BTMinerM31SPlusV60,
BTMinerM31SPlusV80,
@@ -44,3 +45,4 @@ from .M31S_Plus import (
from .M32 import BTMinerM32, BTMinerM32V20
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,18 @@
# Copyright 2022 Upstream Data Inc
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .M50 import (
BTMinerM50,
BTMinerM50VH50,
)

View File

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

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
version = "0.17.8"
version = "0.20.0"
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic"
@@ -15,7 +15,19 @@ passlib = "^1.7.4"
pyaml = "^21.10.1"
toml = "^0.10.2"
[tool.poetry.dev-dependencies]
[tool.poetry.group.dev]
optional = true
[tool.poetry.group.dev.dependencies]
pre-commit = "^2.20.0"
[tool.poetry.group.docs]
optional = true
[tool.poetry.group.docs.dependencies]
mkdocs = "^1.4.0"
mkdocstrings = {extras = ["python"], version = "^0.19.0"}
[build-system]
requires = ["poetry-core>=1.0.0"]

View File

@@ -1,2 +0,0 @@
-r requirements.txt
pre-commit

Binary file not shown.