Compare commits
109 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2e1d2f2fd | ||
|
|
dd205c0f06 | ||
|
|
79e247c0cf | ||
|
|
836d045b65 | ||
|
|
2d029b65e6 | ||
|
|
993b7efeef | ||
|
|
b7a81097a4 | ||
|
|
651bef8203 | ||
|
|
a9e5c99ab2 | ||
|
|
30216fdd5b | ||
|
|
341cc13d83 | ||
|
|
05a4ae6f04 | ||
|
|
5971d9fd83 | ||
|
|
3968f2275c | ||
|
|
84344ca883 | ||
|
|
59b80254eb | ||
|
|
06fdb19e0b | ||
|
|
75222e8cd2 | ||
|
|
5a067d60e7 | ||
|
|
2fbc4fcb4a | ||
|
|
13fe60504a | ||
|
|
cdc52c3605 | ||
|
|
a9db097355 | ||
|
|
ec5557cf88 | ||
|
|
a8ea84d2f3 | ||
|
|
58d369eedf | ||
|
|
f8f9dd7070 | ||
|
|
01b72e1ee6 | ||
|
|
e367e630b8 | ||
|
|
385cca6fc0 | ||
|
|
75a3a466a3 | ||
|
|
4bf08dbfe6 | ||
|
|
833d061315 | ||
|
|
a85558278d | ||
|
|
bf5087b06d | ||
|
|
ec58d13bae | ||
|
|
75993564ab | ||
|
|
f3ea169dec | ||
|
|
39b3fe5c25 | ||
|
|
5c79c6cb0c | ||
|
|
bab4261bed | ||
|
|
e1d5c89388 | ||
|
|
5f6c8cca18 | ||
|
|
39db14b002 | ||
|
|
3be04c678b | ||
|
|
099ec35a8f | ||
|
|
113dfb9170 | ||
|
|
8d19e0ebbb | ||
|
|
ec064eba65 | ||
|
|
3451127761 | ||
|
|
b3be52ca77 | ||
|
|
b6ec6caa72 | ||
|
|
8e81e18622 | ||
|
|
6ea26e0e19 | ||
|
|
be446f94c1 | ||
|
|
3d94e30f22 | ||
|
|
d5a7ff3a46 | ||
|
|
bbfa97632d | ||
|
|
ecf0ce22d6 | ||
|
|
d56da007a5 | ||
|
|
2c86b2da7e | ||
|
|
c73b1ceb07 | ||
|
|
a320c8967d | ||
|
|
e21e340f60 | ||
|
|
f63d8f4b91 | ||
|
|
ba6a1606b6 | ||
|
|
51b0c0456f | ||
|
|
04bd03b496 | ||
|
|
bc5764c8ff | ||
|
|
1f2e066f4c | ||
|
|
74c457a694 | ||
|
|
4a1c53dfd7 | ||
|
|
81b77f8768 | ||
|
|
3b127b6862 | ||
|
|
2815d2ba11 | ||
|
|
1ff20fc6f0 | ||
|
|
797c847055 | ||
|
|
65c7f2f66f | ||
|
|
445d621590 | ||
|
|
d39ecfd6b4 | ||
|
|
36663471fb | ||
|
|
80293ac52f | ||
|
|
70b45f40f5 | ||
|
|
a511fabd9c | ||
|
|
8bc8f6f178 | ||
|
|
b790ad58a7 | ||
|
|
354ab793a2 | ||
|
|
59346d641f | ||
|
|
11d770771b | ||
|
|
1b6db7ed45 | ||
|
|
55c4e10fae | ||
|
|
77c06dad61 | ||
|
|
68d250d2f2 | ||
|
|
094a17ac68 | ||
|
|
dbcdeaa3de | ||
|
|
872cac811a | ||
|
|
d324c2fee9 | ||
|
|
577e8df612 | ||
|
|
4b54cf67ba | ||
|
|
0e00fe3114 | ||
|
|
15d1dc5bb6 | ||
|
|
2af0003843 | ||
|
|
3c227be170 | ||
|
|
e889780bad | ||
|
|
cc3d4fa805 | ||
|
|
743823f66e | ||
|
|
227e1e2d2d | ||
|
|
d6c8ff0910 | ||
|
|
bd20e051b0 |
@@ -1,16 +1,16 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
rev: v4.5.0
|
||||
hooks:
|
||||
- id: trailing-whitespace
|
||||
- id: check-yaml
|
||||
- id: check-added-large-files
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 22.6.0
|
||||
rev: 24.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
- repo: https://github.com/pycqa/isort
|
||||
rev: 5.10.1
|
||||
rev: 5.13.2
|
||||
hooks:
|
||||
- id: isort
|
||||
name: isort (python)
|
||||
|
||||
31
README.md
31
README.md
@@ -19,6 +19,37 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
|
||||
|
||||
[Click here to view supported miner types](https://docs.pyasic.org/en/latest/miners/supported_types/)
|
||||
|
||||
---
|
||||
## Installation
|
||||
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
||||
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
||||
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
||||
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
||||
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
##### Installing `pyasic`
|
||||
|
||||
`python -m pip install .` or `poetry install`
|
||||
|
||||
##### Additional Developer Setup
|
||||
```
|
||||
poetry install --with dev
|
||||
pre-commit install
|
||||
```
|
||||
|
||||
---
|
||||
## Getting started
|
||||
|
||||
|
||||
@@ -47,8 +47,8 @@ def backend_str(backend: MinerTypes) -> str:
|
||||
return "Stock Firmware Goldshells"
|
||||
case MinerTypes.LUX_OS:
|
||||
return "LuxOS Firmware Miners"
|
||||
case MinerTypes.EPIC:
|
||||
return "ePIC Firmware Miners"
|
||||
case MinerTypes.MARATHON:
|
||||
return "Mara Firmware Miners"
|
||||
|
||||
|
||||
def create_url_str(mtype: str):
|
||||
|
||||
@@ -18,6 +18,31 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
|
||||
|
||||
[Click here to view supported miner types](miners/supported_types.md)
|
||||
|
||||
---
|
||||
## Installation
|
||||
|
||||
It is recommended to install `pyasic` in a [virtual environment](https://realpython.com/python-virtual-environments-a-primer/#what-other-popular-options-exist-aside-from-venv) to isolate it from the rest of your system. Options include:
|
||||
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default
|
||||
|
||||
```
|
||||
poetry install
|
||||
```
|
||||
|
||||
- [venv](https://docs.python.org/3/library/venv.html): included in Python standard library but has fewer features than other options
|
||||
- [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv): [pyenv](https://github.com/pyenv/pyenv) plugin for managing virtualenvs
|
||||
|
||||
```
|
||||
pyenv install <python version number>
|
||||
pyenv virtualenv <python version number> <env name>
|
||||
pyenv activate <env name>
|
||||
```
|
||||
|
||||
- [conda](https://docs.conda.io/en/latest/)
|
||||
|
||||
##### Installing `pyasic`
|
||||
|
||||
`python -m pip install .` or `poetry install`
|
||||
|
||||
---
|
||||
## Getting started
|
||||
---
|
||||
@@ -236,6 +261,7 @@ settings.update("default_antminer_password", "my_pwd")
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"antminer_mining_mode_as_str": False,
|
||||
"default_whatsminer_password": "admin",
|
||||
"default_innosilicon_password": "admin",
|
||||
"default_antminer_password": "root",
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Hydro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Hydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro Hydro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProHydro
|
||||
handler: python
|
||||
@@ -92,6 +99,20 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro+ Hydro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlusHydro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19K Pro
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19KPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19
|
||||
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||
handler: python
|
||||
@@ -155,8 +176,8 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
|
||||
## S19j Pro No PIC
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -169,6 +190,20 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ No PIC
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlusNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro No PIC
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19kProNoPIC
|
||||
handler: python
|
||||
@@ -302,3 +337,94 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro+ (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jProPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19k Pro (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19kPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T19 (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X19.T19.LUXMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j No PIC (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19jNoPIC
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19j Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19 XP (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19K Pro (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X19.S19.MaraS19KPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,6 +1,27 @@
|
||||
# pyasic
|
||||
## X21 Models
|
||||
|
||||
## S21
|
||||
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21
|
||||
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21
|
||||
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
|
||||
handler: python
|
||||
@@ -8,3 +29,31 @@
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (ePIC)
|
||||
::: pyasic.miners.antminer.epic.X21.T21.ePICT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (LuxOS)
|
||||
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S21 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X21.S21.MaraS21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## T21 (MaraFW)
|
||||
::: pyasic.miners.antminer.marathon.X21.T21.MaraT21
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
[`MinerFactory`][pyasic.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
[`MinerFactory`][pyasic.miners.factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||
|
||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||
|
||||
[`MinerFactory`][pyasic.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
[`MinerFactory`][pyasic.miners.factory.MinerFactory] also keeps a cache, which can be cleared if needed with `pyasic.miner_factory.clear_cached_miners()`.
|
||||
|
||||
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||
|
||||
@@ -32,5 +32,5 @@ Finally, there is functionality to get multiple miners without using `asyncio.ga
|
||||
heading_level: 4
|
||||
|
||||
[`AnyMiner`][pyasic.miners.base.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner],
|
||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||
|
||||
@@ -78,10 +78,20 @@ details {
|
||||
<li><a href="../antminer/X19#s19-xp">S19 XP</a></li>
|
||||
<li><a href="../antminer/X19#s19a">S19a</a></li>
|
||||
<li><a href="../antminer/X19#s19a-pro">S19a Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19-hydro">S19 Hydro</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-hydro">S19 Pro Hydro</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro_1-hydro">S19 Pro+ Hydro</a></li>
|
||||
<li><a href="../antminer/X19#s19k-pro">S19K Pro</a></li>
|
||||
<li><a href="../antminer/X19#t19">T19</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21">S21</a></li>
|
||||
<li><a href="../antminer/X21#t21">T21</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
@@ -444,13 +454,21 @@ details {
|
||||
<li><a href="../antminer/X19#s19j">S19j</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic">S19j No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro">S19j Pro</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-no-pic">S19j Pro No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro_1">S19j Pro+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro_1">S19j Pro+</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro_1-no-pic">S19j Pro+ No PIC</a></li>
|
||||
<li><a href="../antminer/X19#s19k-pro-no-pic">S19k Pro No PIC</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>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21">S21</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
@@ -510,12 +528,14 @@ details {
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21-epic">S21 (ePIC)</a></li>
|
||||
<li><a href="../antminer/X21#t21-epic">T21 (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>blockminer Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../blockminer/blockminer#blockminer-520i-epic">BlockMiner 520i (ePIC)</a></li>
|
||||
<li><a href="../blockminer/blockminer#blockminer-720i-epic">BlockMiner 720i (ePIC)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -544,6 +564,18 @@ details {
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-luxos">S19 (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-luxos">S19 Pro (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-luxos">S19j Pro (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro_1-luxos">S19j Pro+ (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19k-pro-luxos">S19k Pro (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp-luxos">S19 XP (LuxOS)</a></li>
|
||||
<li><a href="../antminer/X19#t19-luxos">T19 (LuxOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21-luxos">S21 (LuxOS)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -575,3 +607,27 @@ details {
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Mara Firmware Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19#s19-marafw">S19 (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19-pro-marafw">S19 Pro (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-marafw">S19j (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-no-pic-marafw">S19j No PIC (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19j-pro-marafw">S19j Pro (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19-xp-marafw">S19 XP (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X19#s19k-pro-marafw">S19K Pro (MaraFW)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X21 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X21#s21-marafw">S21 (MaraFW)</a></li>
|
||||
<li><a href="../antminer/X21#t21-marafw">T21 (MaraFW)</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
@@ -1,10 +1,10 @@
|
||||
# pyasic
|
||||
## Miner APIs
|
||||
Each miner has a unique API that is used to communicate with it.
|
||||
## Miner RPC APIs
|
||||
Each miner has a unique RPC API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.rpc`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
All RPC API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
|
||||
@@ -12,6 +12,7 @@ Settings options:
|
||||
- `factory_get_timeout`
|
||||
- `get_data_retries`
|
||||
- `api_function_timeout`
|
||||
- `antminer_mining_mode_as_str`
|
||||
- `default_whatsminer_password`
|
||||
- `default_innosilicon_password`
|
||||
- `default_antminer_password`
|
||||
|
||||
14
docs/web/antminer.md
Normal file
14
docs/web/antminer.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# pyasic
|
||||
## AntminerModernWebAPI
|
||||
::: pyasic.web.antminer.AntminerModernWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## AntminerOldWebAPI
|
||||
::: pyasic.web.antminer.AntminerOldWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
27
docs/web/api.md
Normal file
27
docs/web/api.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# pyasic
|
||||
## Miner Web APIs
|
||||
Each miner has a unique Web API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.base.BaseMiner] may have an API linked to it as `Miner.web`.
|
||||
|
||||
All API implementations inherit from [`BaseWebAPI`][pyasic.web.BaseWebAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseWebAPI`][pyasic.web.BaseWebAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
Use these instead -
|
||||
|
||||
#### [AntminerModerNWebAPI][pyasic.web.antminer.AntminerModernWebAPI]
|
||||
#### [AntminerOldWebAPI][pyasic.web.antminer.AntminerOldWebAPI]
|
||||
#### [AuradineWebAPI][pyasic.web.auradine.AuradineWebAPI]
|
||||
#### [ePICWebAPI][pyasic.web.epic.ePICWebAPI]
|
||||
#### [GoldshellWebAPI][pyasic.web.goldshell.GoldshellWebAPI]
|
||||
#### [InnosiliconWebAPI][pyasic.web.innosilicon.InnosiliconWebAPI]
|
||||
#### [MaraWebAPI][pyasic.web.marathon.MaraWebAPI]
|
||||
#### [VNishWebAPI][pyasic.web.vnish.VNishWebAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseWebAPI
|
||||
::: pyasic.web.BaseWebAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
7
docs/web/auradine.md
Normal file
7
docs/web/auradine.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## AuradineWebAPI
|
||||
::: pyasic.web.auradine.AuradineWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/epic.md
Normal file
7
docs/web/epic.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## ePICWebAPI
|
||||
::: pyasic.web.epic.ePICWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/goldshell.md
Normal file
7
docs/web/goldshell.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## GoldshellWebAPI
|
||||
::: pyasic.web.goldshell.GoldshellWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/innosilicon.md
Normal file
7
docs/web/innosilicon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## InnosiliconWebAPI
|
||||
::: pyasic.web.innosilicon.InnosiliconWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/marathon.md
Normal file
7
docs/web/marathon.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## MaraWebAPI
|
||||
::: pyasic.web.marathon.MaraWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/web/vnish.md
Normal file
7
docs/web/vnish.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## VNishWebAPI
|
||||
::: pyasic.web.vnish.VNishWebAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
14
mkdocs.yml
14
mkdocs.yml
@@ -22,6 +22,15 @@ nav:
|
||||
- CGMiner: "rpc/cgminer.md"
|
||||
- LUXMiner: "rpc/luxminer.md"
|
||||
- Unknown: "rpc/unknown.md"
|
||||
- Web APIs:
|
||||
- Intro: "web/api.md"
|
||||
- Antminer: "web/antminer.md"
|
||||
- Auradine: "web/auradine.md"
|
||||
- ePIC: "web/epic.md"
|
||||
- Goldshell: "web/goldshell.md"
|
||||
- Innosilicon: "web/innosilicon.md"
|
||||
- Marathon: "web/marathon.md"
|
||||
- VNish: "web/vnish.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
@@ -50,10 +59,15 @@ nav:
|
||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Whatsminer M6X: "miners/whatsminer/M6X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell X5: "miners/goldshell/X5.md"
|
||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||
- Goldshell XBox: "miners/goldshell/XBox.md"
|
||||
- Auradine AD: "miners/auradine/AD.md"
|
||||
- Auradine AI: "miners/auradine/AI.md"
|
||||
- Auradine AT: "miners/auradine/AT.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Settings:
|
||||
- Settings: "settings/settings.md"
|
||||
|
||||
@@ -25,6 +25,9 @@ from pyasic.misc import merge_dicts
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
"""Represents the configuration for a miner including pool configuration,
|
||||
fan mode, temperature settings, mining mode, and power scaling."""
|
||||
|
||||
pools: PoolConfig = field(default_factory=PoolConfig.default)
|
||||
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default)
|
||||
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
|
||||
@@ -34,9 +37,11 @@ class MinerConfig:
|
||||
)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Converts the MinerConfig object to a dictionary."""
|
||||
return asdict(self)
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Antminers."""
|
||||
return {
|
||||
**self.fan_mode.as_am_modern(),
|
||||
"freq-level": "100",
|
||||
@@ -47,6 +52,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers."""
|
||||
return {
|
||||
**self.fan_mode.as_wm(),
|
||||
**self.mining_mode.as_wm(),
|
||||
@@ -56,6 +62,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_am_old(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for old versions of Antminers."""
|
||||
return {
|
||||
**self.fan_mode.as_am_old(),
|
||||
**self.mining_mode.as_am_old(),
|
||||
@@ -65,6 +72,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Goldshell miners."""
|
||||
return {
|
||||
**self.fan_mode.as_goldshell(),
|
||||
**self.mining_mode.as_goldshell(),
|
||||
@@ -74,6 +82,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Avalonminers."""
|
||||
return {
|
||||
**self.fan_mode.as_avalon(),
|
||||
**self.mining_mode.as_avalon(),
|
||||
@@ -83,6 +92,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Innosilicon miners."""
|
||||
return {
|
||||
**self.fan_mode.as_inno(),
|
||||
**self.mining_mode.as_inno(),
|
||||
@@ -92,6 +102,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the bosminer.toml format."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||
**self.mining_mode.as_bosminer(),
|
||||
@@ -100,6 +111,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
""" "Generates the configuration in the format suitable for BOSer."""
|
||||
return {
|
||||
**self.fan_mode.as_boser(),
|
||||
**self.temperature.as_boser(),
|
||||
@@ -109,6 +121,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for ePIC miners."""
|
||||
return {
|
||||
**merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
|
||||
**self.mining_mode.as_epic(),
|
||||
@@ -117,6 +130,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Auradine miners."""
|
||||
return {
|
||||
**self.fan_mode.as_auradine(),
|
||||
**self.temperature.as_auradine(),
|
||||
@@ -125,8 +139,18 @@ class MinerConfig:
|
||||
**self.power_scaling.as_auradine(),
|
||||
}
|
||||
|
||||
def as_mara(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_mara(),
|
||||
**self.temperature.as_mara(),
|
||||
**self.mining_mode.as_mara(),
|
||||
**self.pools.as_mara(user_suffix=user_suffix),
|
||||
**self.power_scaling.as_mara(),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from a dictionary."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_dict(dict_conf.get("pools")),
|
||||
mining_mode=MiningModeConfig.from_dict(dict_conf.get("mining_mode")),
|
||||
@@ -137,10 +161,12 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from API pool data."""
|
||||
return cls(pools=PoolConfig.from_api(api_pools))
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_am_modern(web_conf),
|
||||
mining_mode=MiningModeConfig.from_am_modern(web_conf),
|
||||
@@ -149,18 +175,22 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
|
||||
return cls.from_am_modern(web_conf)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
|
||||
return cls(pools=PoolConfig.from_inno(web_pools))
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from the bosminer.toml file, same as the `as_bosminer` dumps a dict for writing to that file as toml."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_bosminer(toml_conf),
|
||||
mining_mode=MiningModeConfig.from_bosminer(toml_conf),
|
||||
@@ -171,6 +201,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from gRPC configuration for BOSer."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_boser(grpc_miner_conf),
|
||||
mining_mode=MiningModeConfig.from_boser(grpc_miner_conf),
|
||||
@@ -181,6 +212,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for ePIC miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_epic(web_conf),
|
||||
fan_mode=FanModeConfig.from_epic(web_conf),
|
||||
@@ -190,6 +222,7 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web settings for VNish miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_vnish(web_settings),
|
||||
fan_mode=FanModeConfig.from_vnish(web_settings),
|
||||
@@ -199,8 +232,17 @@ class MinerConfig:
|
||||
|
||||
@classmethod
|
||||
def from_auradine(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for Auradine miners."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_api(web_conf["pools"]),
|
||||
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
|
||||
mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_miner_config: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_mara(web_miner_config),
|
||||
fan_mode=FanModeConfig.from_mara(web_miner_config),
|
||||
mining_mode=MiningModeConfig.from_mara(web_miner_config),
|
||||
)
|
||||
|
||||
@@ -57,6 +57,9 @@ class MinerConfigOption(Enum):
|
||||
def as_auradine(self) -> dict:
|
||||
return self.value.as_auradine()
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return self.value.as_mara()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@@ -106,3 +109,6 @@ class MinerConfigValue:
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {}
|
||||
|
||||
@@ -44,11 +44,21 @@ class FanModeNormal(MinerConfigValue):
|
||||
cls_conf["minimum_speed"] = web_cooling_settings["fan_min_duty"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_fan_conf: dict):
|
||||
cls_conf = {}
|
||||
if toml_fan_conf.get("min_fans") is not None:
|
||||
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "auto"}}
|
||||
return {
|
||||
"temp_control": {"mode": "auto"},
|
||||
"fan_control": {"min_fans": self.minimum_fans},
|
||||
}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
return {
|
||||
@@ -61,6 +71,15 @@ class FanModeNormal(MinerConfigValue):
|
||||
}
|
||||
}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"general-config": {"environment-profile": "AirCooling"},
|
||||
"advance-config": {
|
||||
"override-fan-control": False,
|
||||
"fan-fixed-percent": 0,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeManual(MinerConfigValue):
|
||||
@@ -96,7 +115,7 @@ class FanModeManual(MinerConfigValue):
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": str(self.speed)}
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
@@ -110,6 +129,15 @@ class FanModeManual(MinerConfigValue):
|
||||
def as_epic(self) -> dict:
|
||||
return {"fans": {"Manual": {"speed": self.speed}}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"general-config": {"environment-profile": "AirCooling"},
|
||||
"advance-config": {
|
||||
"override-fan-control": True,
|
||||
"fan-fixed-percent": self.speed,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
@@ -120,14 +148,19 @@ class FanModeImmersion(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwn": "0"}
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"temp_control": {"mode": "disabled"}}
|
||||
return {
|
||||
"fan_control": {"min_fans": 0},
|
||||
}
|
||||
|
||||
def as_auradine(self) -> dict:
|
||||
return {"fan": {"percentage": 0}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {"general-config": {"environment-profile": "OilImmersionCooling"}}
|
||||
|
||||
|
||||
class FanModeConfig(MinerConfigOption):
|
||||
normal = FanModeNormal
|
||||
@@ -156,7 +189,10 @@ class FanModeConfig(MinerConfigOption):
|
||||
if web_conf.get("bitmain-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["bitmain-fan-ctrl"]
|
||||
if fan_manual:
|
||||
return cls.manual(speed=web_conf["bitmain-fan-pwm"])
|
||||
speed = int(web_conf["bitmain-fan-pwm"])
|
||||
if speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(speed=speed)
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
@@ -175,20 +211,28 @@ class FanModeConfig(MinerConfigOption):
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
if toml_conf.get("temp_control") is None:
|
||||
return cls.default()
|
||||
if toml_conf["temp_control"].get("mode") is None:
|
||||
return cls.default()
|
||||
|
||||
try:
|
||||
mode = toml_conf["temp_control"]["mode"]
|
||||
fan_config = toml_conf.get("fan_control", {})
|
||||
if mode == "auto":
|
||||
return cls.normal()
|
||||
return cls.normal().from_bosminer(fan_config)
|
||||
elif mode == "manual":
|
||||
if toml_conf.get("fan_control"):
|
||||
return cls.manual().from_bosminer(toml_conf["fan_control"])
|
||||
return cls.manual().from_bosminer(fan_config)
|
||||
return cls.manual()
|
||||
elif mode == "disabled":
|
||||
return cls.immersion()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
min_fans = toml_conf["fan_control"]["min_fans"]
|
||||
except KeyError:
|
||||
return cls.default()
|
||||
|
||||
if min_fans == 0:
|
||||
return cls.immersion()
|
||||
return cls.normal(minimum_fans=min_fans)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict):
|
||||
@@ -232,4 +276,18 @@ class FanModeConfig(MinerConfigOption):
|
||||
fan_1_target = fan_data["Target"]
|
||||
return cls.manual(speed=round((fan_1_target / fan_1_max) * 100))
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict):
|
||||
try:
|
||||
mode = web_config["general-config"]["environment-profile"]
|
||||
if mode == "AirCooling":
|
||||
if web_config["advance-config"]["override-fan-control"]:
|
||||
return cls.manual(web_config["advance-config"]["fan-fixed-percent"])
|
||||
return cls.normal()
|
||||
return cls.immersion()
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@@ -17,6 +17,7 @@ from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
HashrateTargetMode,
|
||||
@@ -39,7 +40,9 @@ class MiningModeNormal(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
@@ -53,6 +56,13 @@ class MiningModeNormal(MinerConfigValue):
|
||||
def as_goldshell(self) -> dict:
|
||||
return {"settings": {"level": 0}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Stock",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
@@ -63,7 +73,9 @@ class MiningModeSleep(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
@@ -77,6 +89,13 @@ class MiningModeSleep(MinerConfigValue):
|
||||
def as_goldshell(self) -> dict:
|
||||
return {"settings": {"level": 3}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Sleep",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
@@ -87,7 +106,9 @@ class MiningModeLPM(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
@@ -108,7 +129,9 @@ class MiningModeHPM(MinerConfigValue):
|
||||
return cls()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
@@ -117,6 +140,7 @@ class MiningModeHPM(MinerConfigValue):
|
||||
return {"mode": {"mode": "turbo"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class StandardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
@@ -124,20 +148,22 @@ class StandardTuneAlgo(MinerConfigValue):
|
||||
return VOptAlgo().as_epic()
|
||||
|
||||
|
||||
@dataclass
|
||||
class VOptAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
mode: str = field(init=False, default="voltage_optimizer")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "VoltageOptimizer"
|
||||
|
||||
|
||||
class ChipTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
mode: str = field(init=False, default="chip_tune")
|
||||
|
||||
def as_epic(self) -> str:
|
||||
return "ChipTune"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TunerAlgo(MinerConfigOption):
|
||||
standard = StandardTuneAlgo
|
||||
voltage_optimizer = VOptAlgo
|
||||
@@ -147,6 +173,16 @@ class TunerAlgo(MinerConfigOption):
|
||||
def default(cls):
|
||||
return cls.standard()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().from_dict(dict_conf)
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
@@ -160,12 +196,14 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
if dict_conf.get("power"):
|
||||
cls_conf["power"] = dict_conf["power"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = dict_conf["algo"]
|
||||
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
|
||||
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
if self.power is not None:
|
||||
@@ -173,7 +211,10 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
return {}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {"autotuning": {"enabled": True, "psu_power_limit": self.power}}
|
||||
conf = {"enabled": True, "mode": "power_target"}
|
||||
if self.power is not None:
|
||||
conf["power_target"] = self.power
|
||||
return {"autotuning": conf}
|
||||
|
||||
def as_boser(self) -> dict:
|
||||
return {
|
||||
@@ -192,6 +233,17 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
def as_auradine(self) -> dict:
|
||||
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Auto",
|
||||
"concorde": {
|
||||
"mode-select": "PowerTarget",
|
||||
"power-target": self.power,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
@@ -201,10 +253,24 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
|
||||
return cls(dict_conf.get("hashrate"))
|
||||
cls_conf = {}
|
||||
if dict_conf.get("hashrate"):
|
||||
cls_conf["hashrate"] = dict_conf["hashrate"]
|
||||
if dict_conf.get("algo"):
|
||||
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
|
||||
|
||||
return cls(**cls_conf)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
conf = {"enabled": True, "mode": "hashrate_target"}
|
||||
if self.hashrate is not None:
|
||||
conf["hashrate_target"] = self.hashrate
|
||||
return {"autotuning": conf}
|
||||
|
||||
def as_boser(self) -> dict:
|
||||
return {
|
||||
@@ -228,6 +294,17 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
def as_epic(self) -> dict:
|
||||
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Auto",
|
||||
"concorde": {
|
||||
"mode-select": "Hashrate",
|
||||
"hash-target": self.hashrate,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
@@ -239,7 +316,9 @@ class ManualBoardSettings(MinerConfigValue):
|
||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -259,7 +338,9 @@ class MiningModeManual(MinerConfigValue):
|
||||
)
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
if settings.get("antminer_mining_mode_as_str", False):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
|
||||
@@ -275,6 +356,17 @@ class MiningModeManual(MinerConfigValue):
|
||||
}
|
||||
return cls(global_freq=freq, global_volt=voltage, boards=boards)
|
||||
|
||||
def as_mara(self) -> dict:
|
||||
return {
|
||||
"mode": {
|
||||
"work-mode-selector": "Fixed",
|
||||
"fixed": {
|
||||
"frequency": str(self.global_freq),
|
||||
"voltage": self.global_volt,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class MiningModeConfig(MinerConfigOption):
|
||||
normal = MiningModeNormal
|
||||
@@ -423,3 +515,28 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.power_tuning(mode_data["Power"])
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict):
|
||||
try:
|
||||
mode = web_config["mode"]["work-mode-selector"]
|
||||
if mode == "Fixed":
|
||||
fixed_conf = web_config["mode"]["fixed"]
|
||||
return cls.manual(
|
||||
global_freq=int(fixed_conf["frequency"]),
|
||||
global_volt=fixed_conf["voltage"],
|
||||
)
|
||||
elif mode == "Stock":
|
||||
return cls.normal()
|
||||
elif mode == "Sleep":
|
||||
return cls.sleep()
|
||||
elif mode == "Auto":
|
||||
auto_conf = web_config["mode"]["concorde"]
|
||||
auto_mode = auto_conf["mode-select"]
|
||||
if auto_mode == "Hashrate":
|
||||
return cls.hashrate_tuning(hashrate=auto_conf["hash-target"])
|
||||
elif auto_mode == "PowerTarget":
|
||||
return cls.power_tuning(power=auto_conf["power-target"])
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@@ -118,6 +118,15 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
return {"pool": self.url, "login": self.user, "password": self.password}
|
||||
|
||||
def as_mara(self, user_suffix: str = None) -> dict:
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix}",
|
||||
"pass": self.password,
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
||||
return cls(
|
||||
@@ -177,6 +186,14 @@ class Pool(MinerConfigValue):
|
||||
password=grpc_pool["password"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"],
|
||||
user=web_pool["user"],
|
||||
password=web_pool["pass"],
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolGroup(MinerConfigValue):
|
||||
@@ -264,9 +281,12 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_auradine(self, user_suffix: str = None) -> list:
|
||||
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_epic(self, user_suffix: str = None) -> dict:
|
||||
def as_epic(self, user_suffix: str = None) -> list:
|
||||
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_mara(self, user_suffix: str = None) -> list:
|
||||
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
@@ -336,6 +356,10 @@ class PoolGroup(MinerConfigValue):
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolConfig(MinerConfigValue):
|
||||
@@ -427,6 +451,11 @@ class PoolConfig(MinerConfigValue):
|
||||
}
|
||||
}
|
||||
|
||||
def as_mara(self, user_suffix: str = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
|
||||
return {"pools": []}
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
@@ -481,3 +510,7 @@ class PoolConfig(MinerConfigValue):
|
||||
)
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
||||
|
||||
@@ -114,6 +114,8 @@ class PowerScalingEnabled(MinerConfigValue):
|
||||
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
|
||||
power_step = power_scaling_conf.get("power_step")
|
||||
min_power = power_scaling_conf.get("min_psu_power_limit")
|
||||
if min_power is None:
|
||||
min_power = power_scaling_conf.get("min_power_target")
|
||||
sd_mode = PowerScalingShutdown.from_bosminer(power_scaling_conf)
|
||||
|
||||
return cls(
|
||||
@@ -138,12 +140,12 @@ class PowerScalingEnabled(MinerConfigValue):
|
||||
if self.power_step is not None:
|
||||
cfg["power_step"] = self.power_step
|
||||
if self.minimum_power is not None:
|
||||
cfg["min_psu_power_limit"] = self.minimum_power
|
||||
cfg["min_power_target"] = self.minimum_power
|
||||
|
||||
if self.shutdown_enabled is not None:
|
||||
cfg = {**cfg, **self.shutdown_enabled.as_bosminer()}
|
||||
|
||||
return {"power_scaling": cfg}
|
||||
return {"performance_scaling": cfg}
|
||||
|
||||
def as_boser(self) -> dict:
|
||||
return {
|
||||
@@ -189,6 +191,8 @@ class PowerScalingConfig(MinerConfigOption):
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict):
|
||||
power_scaling = toml_conf.get("power_scaling")
|
||||
if power_scaling is None:
|
||||
power_scaling = toml_conf.get("performance_scaling")
|
||||
if power_scaling is not None:
|
||||
enabled = power_scaling.get("enabled")
|
||||
if enabled is not None:
|
||||
|
||||
@@ -38,6 +38,8 @@ class TemperatureConfig(MinerConfigValue):
|
||||
temp_cfg["hot_temp"] = self.hot
|
||||
if self.danger is not None:
|
||||
temp_cfg["dangerous_temp"] = self.danger
|
||||
if len(temp_cfg) == 0:
|
||||
return {}
|
||||
return {"temp_control": temp_cfg}
|
||||
|
||||
def as_epic(self) -> dict:
|
||||
@@ -67,6 +69,7 @@ class TemperatureConfig(MinerConfigValue):
|
||||
hot=temp_control.get("hot_temp"),
|
||||
danger=temp_control.get("dangerous_temp"),
|
||||
)
|
||||
return cls()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
||||
|
||||
@@ -94,7 +94,14 @@ class MinerData:
|
||||
percent_expected_wattage: float = field(init=False)
|
||||
nominal: bool = field(init=False)
|
||||
config: MinerConfig = None
|
||||
errors: List[Union[WhatsminerError, BraiinsOSError, X19Error, InnosiliconError]] = field(default_factory=list)
|
||||
errors: List[
|
||||
Union[
|
||||
WhatsminerError,
|
||||
BraiinsOSError,
|
||||
X19Error,
|
||||
InnosiliconError,
|
||||
]
|
||||
] = field(default_factory=list)
|
||||
fault_light: Union[bool, None] = None
|
||||
efficiency: int = field(init=False)
|
||||
is_mining: bool = True
|
||||
|
||||
@@ -20,4 +20,5 @@ from .cgminer import *
|
||||
from .epic import *
|
||||
from .hiveon import *
|
||||
from .luxos import *
|
||||
from .marathon import *
|
||||
from .vnish import *
|
||||
|
||||
@@ -21,6 +21,7 @@ from pyasic.miners.models import (
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19Hydro,
|
||||
S19i,
|
||||
S19j,
|
||||
S19jNoPIC,
|
||||
@@ -29,6 +30,8 @@ from pyasic.miners.models import (
|
||||
S19Pro,
|
||||
S19ProHydro,
|
||||
S19ProPlus,
|
||||
S19ProPlusHydro,
|
||||
S19KPro,
|
||||
)
|
||||
|
||||
|
||||
@@ -82,3 +85,15 @@ class BMMinerS19L(AntminerModern, S19L):
|
||||
|
||||
class BMMinerS19ProHydro(AntminerModern, S19ProHydro):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19Hydro(AntminerModern, S19Hydro):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19ProPlusHydro(AntminerModern, S19ProPlusHydro):
|
||||
pass
|
||||
|
||||
|
||||
class BMMinerS19KPro(AntminerModern, S19KPro):
|
||||
pass
|
||||
|
||||
@@ -18,6 +18,7 @@ from .S19 import (
|
||||
BMMinerS19,
|
||||
BMMinerS19a,
|
||||
BMMinerS19aPro,
|
||||
BMMinerS19Hydro,
|
||||
BMMinerS19i,
|
||||
BMMinerS19j,
|
||||
BMMinerS19jNoPIC,
|
||||
@@ -27,6 +28,8 @@ from .S19 import (
|
||||
BMMinerS19Pro,
|
||||
BMMinerS19ProHydro,
|
||||
BMMinerS19ProPlus,
|
||||
BMMinerS19ProPlusHydro,
|
||||
BMMinerS19XP,
|
||||
BMMinerS19KPro,
|
||||
)
|
||||
from .T19 import BMMinerT19
|
||||
|
||||
22
pyasic/miners/antminer/bmminer/X21/S21.py
Normal file
22
pyasic/miners/antminer/bmminer/X21/S21.py
Normal 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 pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.models import S21
|
||||
|
||||
|
||||
class BMMinerS21(AntminerModern, S21):
|
||||
pass
|
||||
22
pyasic/miners/antminer/bmminer/X21/T21.py
Normal file
22
pyasic/miners/antminer/bmminer/X21/T21.py
Normal 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 pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.models import T21
|
||||
|
||||
|
||||
class BMMinerT21(AntminerModern, T21):
|
||||
pass
|
||||
17
pyasic/miners/antminer/bmminer/X21/__init__.py
Normal file
17
pyasic/miners/antminer/bmminer/X21/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .S21 import BMMinerS21
|
||||
from .T21 import BMMinerT21
|
||||
@@ -18,3 +18,4 @@ from .X7 import *
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
from .X21 import *
|
||||
|
||||
@@ -23,7 +23,9 @@ from pyasic.miners.models import (
|
||||
S19j,
|
||||
S19jNoPIC,
|
||||
S19jPro,
|
||||
S19jProNoPIC,
|
||||
S19jProPlus,
|
||||
S19jProPlusNoPIC,
|
||||
S19kProNoPIC,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
@@ -58,6 +60,10 @@ class BOSMinerS19jPro(BOSer, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS19jProNoPIC(BOSer, S19jProNoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS19kProNoPIC(BOSer, S19kProNoPIC):
|
||||
pass
|
||||
|
||||
@@ -70,5 +76,9 @@ class BOSMinerS19jProPlus(BOSer, S19jProPlus):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS19jProPlusNoPIC(BOSer, S19jProPlusNoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class BOSMinerS19XP(BOSer, S19XP):
|
||||
pass
|
||||
|
||||
@@ -21,7 +21,9 @@ from .S19 import (
|
||||
BOSMinerS19j,
|
||||
BOSMinerS19jNoPIC,
|
||||
BOSMinerS19jPro,
|
||||
BOSMinerS19jProNoPIC,
|
||||
BOSMinerS19jProPlus,
|
||||
BOSMinerS19jProPlusNoPIC,
|
||||
BOSMinerS19kProNoPIC,
|
||||
BOSMinerS19Plus,
|
||||
BOSMinerS19Pro,
|
||||
|
||||
22
pyasic/miners/antminer/bosminer/X21/S21.py
Normal file
22
pyasic/miners/antminer/bosminer/X21/S21.py
Normal 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 pyasic.miners.backends import BOSer
|
||||
from pyasic.miners.models import S21
|
||||
|
||||
|
||||
class BOSMinerS21(BOSer, S21):
|
||||
pass
|
||||
17
pyasic/miners/antminer/bosminer/X21/__init__.py
Normal file
17
pyasic/miners/antminer/bosminer/X21/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import BOSMinerS21
|
||||
@@ -17,3 +17,4 @@
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
from .X21 import *
|
||||
|
||||
22
pyasic/miners/antminer/epic/X21/T21.py
Normal file
22
pyasic/miners/antminer/epic/X21/T21.py
Normal 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 pyasic.miners.backends import ePIC
|
||||
from pyasic.miners.models import T21
|
||||
|
||||
|
||||
class ePICT21(ePIC, T21):
|
||||
pass
|
||||
@@ -17,3 +17,7 @@
|
||||
from .S21 import (
|
||||
ePICS21,
|
||||
)
|
||||
|
||||
from .T21 import (
|
||||
ePICT21,
|
||||
)
|
||||
|
||||
@@ -15,8 +15,28 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import LUXMiner
|
||||
from pyasic.miners.models import S19
|
||||
from pyasic.miners.models import S19, S19XP, S19jPro, S19jProPlus, S19kPro, S19Pro
|
||||
|
||||
|
||||
class LUXMinerS19(LUXMiner, S19):
|
||||
pass
|
||||
|
||||
|
||||
class LUXMinerS19Pro(LUXMiner, S19Pro):
|
||||
pass
|
||||
|
||||
|
||||
class LUXMinerS19jPro(LUXMiner, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class LUXMinerS19jProPlus(LUXMiner, S19jProPlus):
|
||||
pass
|
||||
|
||||
|
||||
class LUXMinerS19kPro(LUXMiner, S19kPro):
|
||||
pass
|
||||
|
||||
|
||||
class LUXMinerS19XP(LUXMiner, S19XP):
|
||||
pass
|
||||
|
||||
22
pyasic/miners/antminer/luxos/X19/T19.py
Normal file
22
pyasic/miners/antminer/luxos/X19/T19.py
Normal 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 pyasic.miners.backends import LUXMiner
|
||||
from pyasic.miners.models import T19
|
||||
|
||||
|
||||
class LUXMinerT19(LUXMiner, T19):
|
||||
pass
|
||||
@@ -14,4 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S19 import LUXMinerS19
|
||||
from .S19 import (
|
||||
LUXMinerS19,
|
||||
LUXMinerS19jPro,
|
||||
LUXMinerS19jProPlus,
|
||||
LUXMinerS19kPro,
|
||||
LUXMinerS19Pro,
|
||||
LUXMinerS19XP,
|
||||
)
|
||||
from .T19 import LUXMinerT19
|
||||
|
||||
22
pyasic/miners/antminer/luxos/X21/S21.py
Normal file
22
pyasic/miners/antminer/luxos/X21/S21.py
Normal 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 pyasic.miners.backends import LUXMiner
|
||||
from pyasic.miners.models import S21
|
||||
|
||||
|
||||
class LUXMinerS21(LUXMiner, S21):
|
||||
pass
|
||||
17
pyasic/miners/antminer/luxos/X21/__init__.py
Normal file
17
pyasic/miners/antminer/luxos/X21/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .S21 import LUXMinerS21
|
||||
@@ -16,3 +16,4 @@
|
||||
|
||||
from .X9 import *
|
||||
from .X19 import *
|
||||
from .X21 import *
|
||||
|
||||
46
pyasic/miners/antminer/marathon/X19/S19.py
Normal file
46
pyasic/miners/antminer/marathon/X19/S19.py
Normal file
@@ -0,0 +1,46 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# 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 MaraMiner
|
||||
from pyasic.miners.models import S19, S19XP, S19j, S19jNoPIC, S19jPro, S19KPro, S19Pro
|
||||
|
||||
|
||||
class MaraS19(MaraMiner, S19):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19Pro(MaraMiner, S19Pro):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19XP(MaraMiner, S19XP):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19j(MaraMiner, S19j):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19jNoPIC(MaraMiner, S19jNoPIC):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19jPro(MaraMiner, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class MaraS19KPro(MaraMiner, S19KPro):
|
||||
pass
|
||||
9
pyasic/miners/antminer/marathon/X19/__init__.py
Normal file
9
pyasic/miners/antminer/marathon/X19/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .S19 import (
|
||||
MaraS19,
|
||||
MaraS19j,
|
||||
MaraS19jNoPIC,
|
||||
MaraS19jPro,
|
||||
MaraS19KPro,
|
||||
MaraS19Pro,
|
||||
MaraS19XP,
|
||||
)
|
||||
22
pyasic/miners/antminer/marathon/X21/S21.py
Normal file
22
pyasic/miners/antminer/marathon/X21/S21.py
Normal 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 pyasic.miners.backends import MaraMiner
|
||||
from pyasic.miners.models import S21
|
||||
|
||||
|
||||
class MaraS21(MaraMiner, S21):
|
||||
pass
|
||||
22
pyasic/miners/antminer/marathon/X21/T21.py
Normal file
22
pyasic/miners/antminer/marathon/X21/T21.py
Normal 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 pyasic.miners.backends import MaraMiner
|
||||
from pyasic.miners.models import T21
|
||||
|
||||
|
||||
class MaraT21(MaraMiner, T21):
|
||||
pass
|
||||
2
pyasic/miners/antminer/marathon/X21/__init__.py
Normal file
2
pyasic/miners/antminer/marathon/X21/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .S21 import MaraS21
|
||||
from .T21 import MaraT21
|
||||
2
pyasic/miners/antminer/marathon/__init__.py
Normal file
2
pyasic/miners/antminer/marathon/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .X19 import *
|
||||
from .X21 import *
|
||||
@@ -26,5 +26,6 @@ from .goldshell import GoldshellMiner
|
||||
from .hiveon import Hiveon
|
||||
from .innosilicon import Innosilicon
|
||||
from .luxminer import LUXMiner
|
||||
from .marathon import MaraMiner
|
||||
from .vnish import VNish
|
||||
from .whatsminer import M2X, M3X, M5X, M6X
|
||||
|
||||
@@ -22,6 +22,7 @@ from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
DataLocations,
|
||||
@@ -29,6 +30,7 @@ from pyasic.miners.data import (
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc.antminer import AntminerRPCAPI
|
||||
from pyasic.ssh.antminer import AntminerModernSSH
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
@@ -88,6 +90,9 @@ class AntminerModern(BMMiner):
|
||||
_web_cls = AntminerModernWebAPI
|
||||
web: AntminerModernWebAPI
|
||||
|
||||
_rpc_cls = AntminerRPCAPI
|
||||
web: AntminerRPCAPI
|
||||
|
||||
_ssh_cls = AntminerModernSSH
|
||||
ssh: AntminerModernSSH
|
||||
|
||||
@@ -207,7 +212,7 @@ class AntminerModern(BMMiner):
|
||||
]
|
||||
|
||||
try:
|
||||
rpc_stats = await self.rpc.send_command("stats", new_api=True)
|
||||
rpc_stats = await self.rpc.stats(new_api=True)
|
||||
except APIError:
|
||||
return hashboards
|
||||
|
||||
|
||||
@@ -176,21 +176,25 @@ class BOSMiner(BaseMiner):
|
||||
self.config = cfg
|
||||
except toml.TomlDecodeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
except TypeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
print(config)
|
||||
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
|
||||
|
||||
toml_conf = toml.dumps(
|
||||
{
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"version": "2.0",
|
||||
"generator": "pyasic",
|
||||
"model": f"{self.make.replace('Miner', 'miner')} {self.raw_model.replace('j', 'J')}",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
**config.as_bosminer(user_suffix=user_suffix),
|
||||
**parsed_cfg,
|
||||
}
|
||||
)
|
||||
try:
|
||||
@@ -200,9 +204,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async with conn:
|
||||
await conn.run("/etc/init.d/bosminer stop")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml")
|
||||
await conn.run("/etc/init.d/bosminer start")
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
@@ -641,6 +643,8 @@ class BOSer(BaseMiner):
|
||||
_web_cls = BOSerWebAPI
|
||||
web: BOSerWebAPI
|
||||
|
||||
firmware = "BOS+"
|
||||
|
||||
data_locations = BOSER_DATA_LOC
|
||||
|
||||
supports_autotuning = True
|
||||
|
||||
@@ -74,6 +74,10 @@ EPIC_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -313,8 +317,18 @@ class ePIC(BaseMiner):
|
||||
hb_list[hb["Index"]].temp = hb["Temperature"]
|
||||
return hb_list
|
||||
|
||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
if web_summary is not None:
|
||||
try:
|
||||
op_state = web_summary["Status"]["Operating State"]
|
||||
return not op_state == "Idling"
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||
if web_summary is None:
|
||||
|
||||
298
pyasic/miners/backends/marathon.py
Normal file
298
pyasic/miners/backends/marathon.py
Normal file
@@ -0,0 +1,298 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import MinerConfig
|
||||
from pyasic.config import MiningModeConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.misc import merge_dicts
|
||||
from pyasic.web.marathon import MaraWebAPI
|
||||
|
||||
MARA_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_overview", "overview")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_overview", "overview")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_network_config", "network_config")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_hashboards", "hashboards")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit",
|
||||
[WebAPICommand("web_miner_config", "miner_config")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_fans", "fans")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_locate_miner", "locate_miner")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_brief", "brief")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class MaraMiner(BaseMiner):
|
||||
_web_cls = MaraWebAPI
|
||||
web: MaraWebAPI
|
||||
|
||||
data_locations = MARA_DATA_LOC
|
||||
|
||||
firmware = "MaraFW"
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
res = await self.web.set_locate_miner(blinking=False)
|
||||
return res.get("blinking") is False
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
res = await self.web.set_locate_miner(blinking=True)
|
||||
return res.get("blinking") is True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_config()
|
||||
if data:
|
||||
self.config = MinerConfig.from_mara(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
data = await self.web.get_miner_config()
|
||||
cfg_data = config.as_mara(user_suffix=user_suffix)
|
||||
merged_cfg = merge_dicts(data, cfg_data)
|
||||
await self.web.set_miner_config(**merged_cfg)
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.mining_mode = MiningModeConfig.power_tuning(wattage)
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
data = await self.web.get_miner_config()
|
||||
data["mode"]["work-mode-selector"] = "Sleep"
|
||||
await self.web.set_miner_config(**data)
|
||||
return True
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
data = await self.web.get_miner_config()
|
||||
data["mode"]["work-mode-selector"] = "Auto"
|
||||
await self.web.set_miner_config(**data)
|
||||
return True
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
await self.web.reboot()
|
||||
return True
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
await self.web.reload()
|
||||
return True
|
||||
|
||||
async def _get_wattage(self, web_brief: dict = None) -> Optional[int]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return round(web_brief["power_consumption_estimated"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self, web_brief: dict = None) -> Optional[bool]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return web_brief["status"] == "Mining"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_brief: dict = None) -> Optional[int]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return web_brief["elapsed"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_hashboards: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if web_hashboards is None:
|
||||
try:
|
||||
web_hashboards = await self.web.hashboards()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_hashboards is not None:
|
||||
try:
|
||||
for hb in web_hashboards["hashboards"]:
|
||||
idx = hb["index"]
|
||||
hashboards[idx].hashrate = round(hb["hashrate_average"] / 1000, 2)
|
||||
hashboards[idx].temp = round(
|
||||
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"]), 2
|
||||
)
|
||||
hashboards[idx].chip_temp = round(
|
||||
sum(hb["temperature_chip"]) / len(hb["temperature_chip"]), 2
|
||||
)
|
||||
hashboards[idx].chips = hb["asic_num"]
|
||||
hashboards[idx].serial_number = hb["serial_number"]
|
||||
hashboards[idx].missing = False
|
||||
except LookupError:
|
||||
pass
|
||||
return hashboards
|
||||
|
||||
async def _get_mac(self, web_overview: dict = None) -> Optional[str]:
|
||||
if web_overview is None:
|
||||
try:
|
||||
web_overview = await self.web.overview()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_overview is not None:
|
||||
try:
|
||||
return web_overview["mac"].upper()
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_overview: dict = None) -> Optional[str]:
|
||||
if web_overview is None:
|
||||
try:
|
||||
web_overview = await self.web.overview()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_overview is not None:
|
||||
try:
|
||||
return web_overview["version_firmware"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hostname(self, web_network_config: dict = None) -> Optional[str]:
|
||||
if web_network_config is None:
|
||||
try:
|
||||
web_network_config = await self.web.get_network_config()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_network_config is not None:
|
||||
try:
|
||||
return web_network_config["hostname"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, web_brief: dict = None) -> Optional[float]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return round(web_brief["hashrate_realtime"], 2)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_fans: dict = None) -> List[Fan]:
|
||||
if web_fans is None:
|
||||
try:
|
||||
web_fans = await self.web.fans()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_fans is not None:
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(web_fans["fans"][n]["current_speed"]))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_fault_light(self, web_locate_miner: dict = None) -> bool:
|
||||
if web_locate_miner is None:
|
||||
try:
|
||||
web_locate_miner = await self.web.get_locate_miner()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_locate_miner is not None:
|
||||
try:
|
||||
return web_locate_miner["blinking"]
|
||||
except LookupError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def _get_expected_hashrate(self, web_brief: dict = None) -> Optional[float]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return round(web_brief["hashrate_ideal"] / 1000, 2)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(
|
||||
self, web_miner_config: dict = None
|
||||
) -> Optional[float]:
|
||||
if web_miner_config is None:
|
||||
try:
|
||||
web_miner_config = await self.web.get_miner_config()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_miner_config is not None:
|
||||
try:
|
||||
return web_miner_config["mode"]["concorde"]["power-target"]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -74,6 +74,10 @@ VNISH_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("rpc_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -84,6 +88,8 @@ class VNish(BMMiner):
|
||||
_web_cls = VNishWebAPI
|
||||
web: VNishWebAPI
|
||||
|
||||
supports_shutdown = True
|
||||
|
||||
firmware = "VNish"
|
||||
|
||||
data_locations = VNISH_DATA_LOC
|
||||
@@ -125,7 +131,13 @@ class VNish(BMMiner):
|
||||
return False
|
||||
|
||||
async def _get_mac(self, web_summary: dict = None) -> str:
|
||||
if web_summary is None:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
mac = web_summary["system"]["network_status"]["mac"]
|
||||
return mac
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
web_info = await self.web.info()
|
||||
|
||||
if web_info is not None:
|
||||
@@ -135,13 +147,6 @@ class VNish(BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
mac = web_summary["system"]["network_status"]["mac"]
|
||||
return mac
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hostname(self, web_summary: dict = None) -> str:
|
||||
if web_summary is None:
|
||||
web_info = await self.web.info()
|
||||
@@ -214,6 +219,20 @@ class VNish(BMMiner):
|
||||
except LookupError:
|
||||
return fw_ver
|
||||
|
||||
async def _is_mining(self, web_summary: dict = None) -> Optional[bool]:
|
||||
if web_summary is None:
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
is_mining = not web_summary["miner"]["miner_status"]["miner_state"] in [
|
||||
"stopped",
|
||||
"shutting-down",
|
||||
]
|
||||
return is_mining
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
try:
|
||||
web_settings = await self.web.settings()
|
||||
|
||||
@@ -16,4 +16,5 @@
|
||||
|
||||
from .blockminer import (
|
||||
ePICBlockMiner520i,
|
||||
ePICBlockMiner720i,
|
||||
)
|
||||
|
||||
@@ -16,7 +16,12 @@
|
||||
|
||||
from pyasic.miners.backends import ePIC
|
||||
from pyasic.miners.models import BlockMiner520i
|
||||
from pyasic.miners.models import BlockMiner720i
|
||||
|
||||
|
||||
class ePICBlockMiner520i(ePIC, BlockMiner520i):
|
||||
pass
|
||||
|
||||
|
||||
class ePICBlockMiner720i(ePIC, BlockMiner720i):
|
||||
pass
|
||||
|
||||
@@ -29,23 +29,11 @@ from pyasic import settings
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.antminer import *
|
||||
from pyasic.miners.auradine import *
|
||||
from pyasic.miners.blockminer import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
from pyasic.miners.backends import (
|
||||
Auradine,
|
||||
AvalonMiner,
|
||||
BMMiner,
|
||||
BOSMiner,
|
||||
BTMiner,
|
||||
GoldshellMiner,
|
||||
Hiveon,
|
||||
Innosilicon,
|
||||
LUXMiner,
|
||||
VNish,
|
||||
ePIC,
|
||||
)
|
||||
from pyasic.miners.backends import *
|
||||
from pyasic.miners.backends.unknown import UnknownMiner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.blockminer import *
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
from pyasic.miners.makes import *
|
||||
@@ -64,6 +52,7 @@ class MinerTypes(enum.Enum):
|
||||
LUX_OS = 8
|
||||
EPIC = 9
|
||||
AURADINE = 10
|
||||
MARATHON = 11
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -99,8 +88,13 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19 XP": BMMinerS19XP,
|
||||
"ANTMINER S19A": BMMinerS19a,
|
||||
"ANTMINER S19A PRO": BMMinerS19aPro,
|
||||
"ANTMINER S19 HYDRO": BMMinerS19Hydro,
|
||||
"ANTMINER S19 PRO HYD.": BMMinerS19ProHydro,
|
||||
"ANTMINER S19 PRO+ HYD.": BMMinerS19ProPlusHydro,
|
||||
"ANTMINER S19K PRO": BMMinerS19KPro,
|
||||
"ANTMINER T19": BMMinerT19,
|
||||
"ANTMINER S21": BMMinerS21,
|
||||
"ANTMINER T21": BMMinerT21,
|
||||
},
|
||||
MinerTypes.WHATSMINER: {
|
||||
None: type("WhatsminerUnknown", (BTMiner, WhatsMinerMake), {}),
|
||||
@@ -365,11 +359,14 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19J": BOSMinerS19j,
|
||||
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
|
||||
"ANTMINER S19J PRO": BOSMinerS19jPro,
|
||||
"ANTMINER S19J PRO NOPIC": BOSMinerS19jPro,
|
||||
"ANTMINER S19J PRO NOPIC": BOSMinerS19jProNoPIC,
|
||||
"ANTMINER S19J PRO+": BOSMinerS19jProPlus,
|
||||
"ANTMINER S19J PRO PLUS": BOSMinerS19jProPlus,
|
||||
"ANTMINER S19J PRO PLUS NOPIC": BOSMinerS19jProPlusNoPIC,
|
||||
"ANTMINER S19K PRO NOPIC": BOSMinerS19kProNoPIC,
|
||||
"ANTMINER S19 XP": BOSMinerS19XP,
|
||||
"ANTMINER T19": BOSMinerT19,
|
||||
"ANTMINER S21": BOSMinerS21,
|
||||
},
|
||||
MinerTypes.VNISH: {
|
||||
None: VNish,
|
||||
@@ -397,7 +394,9 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19K PRO": ePICS19kPro,
|
||||
"ANTMINER S19 XP": ePICS19XP,
|
||||
"ANTMINER S21": ePICS21,
|
||||
"ANTMINER T21": ePICT21,
|
||||
"BLOCKMINER 520I": ePICBlockMiner520i,
|
||||
"BLOCKMINER 720I": ePICBlockMiner720i,
|
||||
},
|
||||
MinerTypes.HIVEON: {
|
||||
None: Hiveon,
|
||||
@@ -407,9 +406,16 @@ MINER_CLASSES = {
|
||||
None: LUXMiner,
|
||||
"ANTMINER S9": LUXMinerS9,
|
||||
"ANTMINER S19": LUXMinerS19,
|
||||
"ANTMINER S19 PRO": LUXMinerS19Pro,
|
||||
"ANTMINER S19J PRO": LUXMinerS19jPro,
|
||||
"ANTMINER S19J PRO+": LUXMinerS19jProPlus,
|
||||
"ANTMINER S19K PRO": LUXMinerS19kPro,
|
||||
"ANTMINER S19 XP": LUXMinerS19XP,
|
||||
"ANTMINER T19": LUXMinerT19,
|
||||
"ANTMINER S21": LUXMinerS21,
|
||||
},
|
||||
MinerTypes.AURADINE: {
|
||||
None: type("GoldshellUnknown", (Auradine, AuradineMake), {}),
|
||||
None: type("AuradineUnknown", (Auradine, AuradineMake), {}),
|
||||
"AT1500": AuradineFluxAT1500,
|
||||
"AT2860": AuradineFluxAT2860,
|
||||
"AT2880": AuradineFluxAT2880,
|
||||
@@ -418,6 +424,18 @@ MINER_CLASSES = {
|
||||
"AD2500": AuradineFluxAD2500,
|
||||
"AD3500": AuradineFluxAD3500,
|
||||
},
|
||||
MinerTypes.MARATHON: {
|
||||
None: MaraMiner,
|
||||
"ANTMINER S19": MaraS19,
|
||||
"ANTMINER S19 PRO": MaraS19Pro,
|
||||
"ANTMINER S19J": MaraS19j,
|
||||
"ANTMINER S19J88NOPIC": MaraS19jNoPIC,
|
||||
"ANTMINER S19J PRO": MaraS19jPro,
|
||||
"ANTMINER S19 XP": MaraS19XP,
|
||||
"ANTMINER S19K PRO": MaraS19KPro,
|
||||
"ANTMINER S21": MaraS21,
|
||||
"ANTMINER T21": MaraT21,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -493,6 +511,7 @@ class MinerFactory:
|
||||
MinerTypes.HIVEON: self.get_miner_model_hiveon,
|
||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||
MinerTypes.AURADINE: self.get_miner_model_auradine,
|
||||
MinerTypes.MARATHON: self.get_miner_model_marathon,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
@@ -522,15 +541,11 @@ class MinerFactory:
|
||||
return await concurrent_get_first_result(tasks, lambda x: x is not None)
|
||||
|
||||
async def _get_miner_web(self, ip: str) -> MinerTypes | None:
|
||||
tasks = []
|
||||
try:
|
||||
urls = [f"http://{ip}/", f"https://{ip}/"]
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(verify=False)
|
||||
) as session:
|
||||
tasks = [
|
||||
asyncio.create_task(self._web_ping(session, url)) for url in urls
|
||||
]
|
||||
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
|
||||
|
||||
text, resp = await concurrent_get_first_result(
|
||||
tasks,
|
||||
@@ -538,14 +553,14 @@ class MinerFactory:
|
||||
and self._parse_web_type(x[0], x[1]) is not None,
|
||||
)
|
||||
if text is not None:
|
||||
return self._parse_web_type(text, resp)
|
||||
except asyncio.CancelledError:
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
try:
|
||||
await t
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
mtype = self._parse_web_type(text, resp)
|
||||
if mtype == MinerTypes.ANTMINER:
|
||||
# could still be mara
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
res = await self.send_web_command(ip, "/kaonsu/v1/brief", auth=auth)
|
||||
if res is not None:
|
||||
mtype = MinerTypes.MARATHON
|
||||
return mtype
|
||||
|
||||
@staticmethod
|
||||
async def _web_ping(
|
||||
@@ -593,12 +608,8 @@ class MinerFactory:
|
||||
return MinerTypes.AURADINE
|
||||
|
||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||
tasks = []
|
||||
try:
|
||||
commands = ["version", "devdetails"]
|
||||
tasks = [
|
||||
asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands
|
||||
]
|
||||
tasks = [asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands]
|
||||
|
||||
data = await concurrent_get_first_result(
|
||||
tasks,
|
||||
@@ -607,13 +618,6 @@ class MinerFactory:
|
||||
if data is not None:
|
||||
d = self._parse_socket_type(data)
|
||||
return d
|
||||
except asyncio.CancelledError:
|
||||
for t in tasks:
|
||||
t.cancel()
|
||||
try:
|
||||
await t
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
async def _socket_ping(ip: str, cmd: str) -> str | None:
|
||||
@@ -674,6 +678,8 @@ class MinerFactory:
|
||||
return MinerTypes.HIVEON
|
||||
if "LUXMINER" in upper_data:
|
||||
return MinerTypes.LUX_OS
|
||||
if "KAONSU" in upper_data:
|
||||
return MinerTypes.MARATHON
|
||||
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
|
||||
return MinerTypes.ANTMINER
|
||||
if (
|
||||
@@ -904,10 +910,11 @@ class MinerFactory:
|
||||
async def get_miner_model_braiins_os(self, ip: str) -> str | None:
|
||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||
try:
|
||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace(
|
||||
"Bitmain ", ""
|
||||
miner_model = (
|
||||
sock_json_data["DEVDETAILS"][0]["Model"]
|
||||
.replace("Bitmain ", "")
|
||||
.replace("S19XP", "S19 XP")
|
||||
)
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
@@ -920,7 +927,9 @@ class MinerFactory:
|
||||
)
|
||||
if d.status_code == 200:
|
||||
json_data = d.json()
|
||||
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
|
||||
miner_model = json_data["data"]["bosminer"]["info"][
|
||||
"modelName"
|
||||
].replace("S19XP", "S19 XP")
|
||||
return miner_model
|
||||
except (httpx.HTTPError, LookupError):
|
||||
pass
|
||||
@@ -983,6 +992,21 @@ class MinerFactory:
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def get_miner_model_marathon(self, ip: str) -> str | None:
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
web_json_data = await self.send_web_command(
|
||||
ip, "/kaonsu/v1/overview", auth=auth
|
||||
)
|
||||
|
||||
try:
|
||||
miner_model = web_json_data["model"]
|
||||
if miner_model == "":
|
||||
return None
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
|
||||
@@ -21,22 +21,33 @@ class MinerListenerProtocol(asyncio.Protocol):
|
||||
def __init__(self):
|
||||
self.responses = {}
|
||||
self.transport = None
|
||||
self.new_miner = None
|
||||
|
||||
async def get_new_miner(self):
|
||||
try:
|
||||
while self.new_miner is None:
|
||||
await asyncio.sleep(0)
|
||||
return self.new_miner
|
||||
finally:
|
||||
self.new_miner = None
|
||||
|
||||
def connection_made(self, transport):
|
||||
self.transport = transport
|
||||
|
||||
@staticmethod
|
||||
def datagram_received(data, _addr):
|
||||
def datagram_received(self, data, _addr):
|
||||
if data == b"OK\x00\x00\x00\x00\x00\x00\x00\x00":
|
||||
return
|
||||
m = data.decode()
|
||||
if "," in m:
|
||||
ip, mac = m.split(",")
|
||||
if "/" in ip:
|
||||
ip = ip.replace("[", "").split("/")[0]
|
||||
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
|
||||
self.new_miner = {"IP": ip, "MAC": mac.upper()}
|
||||
|
||||
def connection_lost(self, _):
|
||||
pass
|
||||
@@ -45,32 +56,32 @@ class MinerListenerProtocol(asyncio.Protocol):
|
||||
class MinerListener:
|
||||
def __init__(self, bind_addr: str = "0.0.0.0"):
|
||||
self.found_miners = []
|
||||
self.new_miner = None
|
||||
self.stop = False
|
||||
self.stop = asyncio.Event()
|
||||
self.bind_addr = bind_addr
|
||||
|
||||
async def listen(self):
|
||||
self.stop = False
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
transport_14235, _ = await loop.create_datagram_endpoint(
|
||||
transport_14235, protocol_14235 = await loop.create_datagram_endpoint(
|
||||
MinerListenerProtocol, local_addr=(self.bind_addr, 14235)
|
||||
)
|
||||
transport_8888, _ = await loop.create_datagram_endpoint(
|
||||
transport_8888, protocol_8888 = await loop.create_datagram_endpoint(
|
||||
MinerListenerProtocol, local_addr=(self.bind_addr, 8888)
|
||||
)
|
||||
try:
|
||||
while not self.stop.is_set():
|
||||
tasks = [
|
||||
asyncio.create_task(protocol_14235.get_new_miner()),
|
||||
asyncio.create_task(protocol_8888.get_new_miner()),
|
||||
]
|
||||
await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
|
||||
for t in tasks:
|
||||
if t.done():
|
||||
yield t.result()
|
||||
|
||||
while True:
|
||||
if self.new_miner:
|
||||
yield self.new_miner
|
||||
self.found_miners.append(self.new_miner)
|
||||
self.new_miner = None
|
||||
if self.stop:
|
||||
finally:
|
||||
transport_14235.close()
|
||||
transport_8888.close()
|
||||
break
|
||||
await asyncio.sleep(0)
|
||||
|
||||
async def cancel(self):
|
||||
self.stop = True
|
||||
|
||||
@@ -89,12 +89,24 @@ class S19jPro(AntMinerMake):
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jProNoPIC(AntMinerMake):
|
||||
raw_model = "S19j Pro No PIC"
|
||||
expected_chips = 126
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jProPlus(AntMinerMake):
|
||||
raw_model = "S19j Pro+"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jProPlusNoPIC(AntMinerMake):
|
||||
raw_model = "S19j Pro+ No PIC"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19kPro(AntMinerMake):
|
||||
raw_model = "S19k Pro"
|
||||
expected_chips = 77
|
||||
@@ -113,8 +125,28 @@ class S19kProNoPIC(AntMinerMake):
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19Hydro(AntMinerMake):
|
||||
raw_model = "S19 Hydro"
|
||||
expected_chips = 104
|
||||
expected_hashboards = 4
|
||||
expected_fans = 0
|
||||
|
||||
|
||||
class S19ProHydro(AntMinerMake):
|
||||
raw_model = "S19 Pro Hydro"
|
||||
expected_chips = 180
|
||||
expected_hashboards = 4
|
||||
expected_fans = 0
|
||||
|
||||
|
||||
class S19ProPlusHydro(AntMinerMake):
|
||||
raw_model = "S19 Pro+ Hydro"
|
||||
expected_chips = 180
|
||||
expected_hashboards = 4
|
||||
expected_fans = 0
|
||||
|
||||
|
||||
class S19KPro(AntMinerMake):
|
||||
raw_model = "S19K Pro"
|
||||
expected_chips = 77
|
||||
expected_fans = 4
|
||||
|
||||
@@ -20,17 +20,22 @@ from .S19 import (
|
||||
S19XP,
|
||||
S19a,
|
||||
S19aPro,
|
||||
S19Hydro,
|
||||
S19i,
|
||||
S19j,
|
||||
S19jNoPIC,
|
||||
S19jPro,
|
||||
S19jProNoPIC,
|
||||
S19jProPlus,
|
||||
S19jProPlusNoPIC,
|
||||
S19kPro,
|
||||
S19KPro,
|
||||
S19kProNoPIC,
|
||||
S19NoPIC,
|
||||
S19Plus,
|
||||
S19Pro,
|
||||
S19ProHydro,
|
||||
S19ProPlus,
|
||||
S19ProPlusHydro,
|
||||
)
|
||||
from .T19 import T19
|
||||
|
||||
23
pyasic/miners/models/antminer/X21/T21.py
Normal file
23
pyasic/miners/models/antminer/X21/T21.py
Normal file
@@ -0,0 +1,23 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class T21(AntMinerMake):
|
||||
raw_model = "T21"
|
||||
expected_chips = 108
|
||||
expected_fans = 4
|
||||
@@ -17,3 +17,6 @@
|
||||
from .S21 import (
|
||||
S21,
|
||||
)
|
||||
from .T21 import (
|
||||
T21,
|
||||
)
|
||||
|
||||
@@ -5,3 +5,9 @@ class BlockMiner520i(ePICMake):
|
||||
raw_model = "BlockMiner 520i"
|
||||
expected_chips = 124
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class BlockMiner720i(ePICMake):
|
||||
raw_model = "BlockMiner 720i"
|
||||
expected_chips = 180
|
||||
expected_fans = 4
|
||||
|
||||
@@ -32,6 +32,10 @@ class MinerNetwork:
|
||||
|
||||
def __init__(self, hosts: List[ipaddress.IPv4Address]):
|
||||
self.hosts = hosts
|
||||
semaphore_limit = settings.get("network_scan_semaphore", 255)
|
||||
if semaphore_limit is None:
|
||||
semaphore_limit = 255
|
||||
self.semaphore = asyncio.Semaphore(semaphore_limit)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.hosts)
|
||||
@@ -153,8 +157,16 @@ class MinerNetwork:
|
||||
except TimeoutError:
|
||||
yield None
|
||||
|
||||
async def ping_and_get_miner(
|
||||
self, ip: ipaddress.ip_address
|
||||
) -> Union[None, AnyMiner]:
|
||||
if settings.get("network_scan_semaphore") is None:
|
||||
return await self._ping_and_get_miner(ip)
|
||||
async with self.semaphore:
|
||||
return await self._ping_and_get_miner(ip)
|
||||
|
||||
@staticmethod
|
||||
async def ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
|
||||
async def _ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
|
||||
try:
|
||||
return await ping_and_get_miner(ip)
|
||||
except ConnectionRefusedError:
|
||||
|
||||
36
pyasic/rpc/antminer.py
Normal file
36
pyasic/rpc/antminer.py
Normal file
@@ -0,0 +1,36 @@
|
||||
from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
|
||||
|
||||
class AntminerRPCAPI(BMMinerRPCAPI):
|
||||
async def stats(self, new_api: bool = False) -> dict:
|
||||
if new_api:
|
||||
return await self.send_command("stats", new_api=True)
|
||||
return await super().stats()
|
||||
|
||||
async def rate(self):
|
||||
return await self.send_command("rate", new_api=True)
|
||||
|
||||
async def pools(self, new_api: bool = False):
|
||||
if new_api:
|
||||
return await self.send_command("pools", new_api=True)
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def reload(self):
|
||||
return await self.send_command("reload", new_api=True)
|
||||
|
||||
async def summary(self, new_api: bool = False):
|
||||
if new_api:
|
||||
return await self.send_command("summary", new_api=True)
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def warning(self):
|
||||
return await self.send_command("warning", new_api=True)
|
||||
|
||||
async def new_api_pools(self):
|
||||
return await self.pools(new_api=True)
|
||||
|
||||
async def new_api_stats(self):
|
||||
return await self.stats(new_api=True)
|
||||
|
||||
async def new_api_summary(self):
|
||||
return await self.summary(new_api=True)
|
||||
@@ -24,10 +24,12 @@ from httpx import AsyncHTTPTransport
|
||||
_settings = { # defaults
|
||||
"network_ping_retries": 1,
|
||||
"network_ping_timeout": 3,
|
||||
"network_scan_semaphore": None,
|
||||
"factory_get_retries": 1,
|
||||
"factory_get_timeout": 3,
|
||||
"get_data_retries": 1,
|
||||
"api_function_timeout": 5,
|
||||
"antminer_mining_mode_as_str": False,
|
||||
"default_whatsminer_rpc_password": "admin",
|
||||
"default_innosilicon_web_password": "admin",
|
||||
"default_antminer_web_password": "root",
|
||||
@@ -46,6 +48,9 @@ _settings = { # defaults
|
||||
ssl_cxt = httpx.create_ssl_context()
|
||||
|
||||
|
||||
# this function configures socket options like SO_LINGER and returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
|
||||
# using those options.
|
||||
# SO_LINGER controls what happens when you close a socket with unsent data - it allows specifying linger time for the data to be sent.
|
||||
def transport(verify: Union[str, bool, SSLContext] = ssl_cxt):
|
||||
l_onoff = 1
|
||||
l_linger = get("so_linger_time", 1000)
|
||||
|
||||
@@ -3,6 +3,13 @@ from pyasic.ssh.base import BaseSSH
|
||||
|
||||
|
||||
class AntminerModernSSH(BaseSSH):
|
||||
"""
|
||||
Initialize an AntminerModernSSH instance.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address of the Antminer device.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str):
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_antminer_ssh_password", "root")
|
||||
|
||||
@@ -4,32 +4,92 @@ from pyasic.ssh.base import BaseSSH
|
||||
|
||||
class BOSMinerSSH(BaseSSH):
|
||||
def __init__(self, ip: str):
|
||||
"""
|
||||
Initialize a BOSMinerSSH instance.
|
||||
|
||||
Args:
|
||||
ip (str): The IP address of the BOSMiner device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.pwd = settings.get("default_bosminer_ssh_password", "root")
|
||||
|
||||
async def get_board_info(self):
|
||||
"""
|
||||
Retrieve information about the BOSMiner board.
|
||||
|
||||
Returns:
|
||||
str: Information about the BOSMiner board.
|
||||
"""
|
||||
return await self.send_command("bosminer model -d")
|
||||
|
||||
async def fault_light_on(self):
|
||||
"""
|
||||
Turn on the fault light of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after turning on the fault light.
|
||||
"""
|
||||
return await self.send_command("miner fault_light on")
|
||||
|
||||
async def fault_light_off(self):
|
||||
"""
|
||||
Turn off the fault light of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after turning off the fault light.
|
||||
"""
|
||||
return await self.send_command("miner fault_light off")
|
||||
|
||||
async def restart_bosminer(self):
|
||||
"""
|
||||
Restart the BOSMiner service on the device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after restarting the BOSMiner service.
|
||||
"""
|
||||
return await self.send_command("/etc/init.d/bosminer restart")
|
||||
|
||||
async def reboot(self):
|
||||
"""
|
||||
Reboot the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Confirmation message after initiating the reboot process.
|
||||
"""
|
||||
return await self.send_command("/sbin/reboot")
|
||||
|
||||
async def get_config_file(self):
|
||||
"""
|
||||
Retrieve the configuration file of BOSMiner.
|
||||
|
||||
Returns:
|
||||
str: Content of the BOSMiner configuration file.
|
||||
"""
|
||||
return await self.send_command("cat /etc/bosminer.toml")
|
||||
|
||||
async def get_network_config(self):
|
||||
"""
|
||||
Retrieve the network configuration of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Content of the network configuration file.
|
||||
"""
|
||||
return await self.send_command("cat /etc/config/network")
|
||||
|
||||
async def get_hostname(self):
|
||||
"""
|
||||
Retrieve the hostname of the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Hostname of the BOSMiner device.
|
||||
"""
|
||||
return await self.send_command("cat /proc/sys/kernel/hostname")
|
||||
|
||||
async def get_led_status(self):
|
||||
"""
|
||||
Retrieve the status of the LED on the BOSMiner device.
|
||||
|
||||
Returns:
|
||||
str: Status of the LED.
|
||||
"""
|
||||
return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .base import BaseWebAPI
|
||||
|
||||
from .antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
from .auradine import AuradineWebAPI
|
||||
from .braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
|
||||
@@ -27,6 +27,11 @@ from pyasic.web.base import BaseWebAPI
|
||||
|
||||
class AntminerModernWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initialize the modern Antminer API client with a specific IP address.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Antminer device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "root"
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
@@ -39,6 +44,18 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Antminer device using HTTP digest authentication.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The CGI command to send.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
privileged (bool): If set to True, requires elevated privileges.
|
||||
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device or an empty dictionary if an error occurs.
|
||||
"""
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
@@ -66,6 +83,16 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously.
|
||||
|
||||
Args:
|
||||
*commands (str): Multiple command strings to be executed.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of all commands executed.
|
||||
"""
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
tasks = [
|
||||
asyncio.create_task(self._handle_multicommand(client, command))
|
||||
@@ -83,6 +110,15 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
async def _handle_multicommand(
|
||||
self, client: httpx.AsyncClient, command: str
|
||||
) -> dict:
|
||||
"""Helper function for handling individual commands in a multicommand execution.
|
||||
|
||||
Args:
|
||||
client (httpx.AsyncClient): The HTTP client to use for the request.
|
||||
command (str): The command to be executed.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the response of the executed command.
|
||||
"""
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
|
||||
try:
|
||||
@@ -100,29 +136,75 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
return {command: {}}
|
||||
|
||||
async def get_miner_conf(self) -> dict:
|
||||
"""Retrieve the miner configuration from the Antminer device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_miner_conf")
|
||||
|
||||
async def set_miner_conf(self, conf: dict) -> dict:
|
||||
"""Set the configuration for the miner.
|
||||
|
||||
Args:
|
||||
conf (dict): A dictionary of configuration settings to apply to the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the configuration.
|
||||
"""
|
||||
return await self.send_command("set_miner_conf", **conf)
|
||||
|
||||
async def blink(self, blink: bool) -> dict:
|
||||
"""Control the blinking of the LED on the miner device.
|
||||
|
||||
Args:
|
||||
blink (bool): True to start blinking, False to stop.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after the command execution.
|
||||
"""
|
||||
if blink:
|
||||
return await self.send_command("blink", blink="true")
|
||||
return await self.send_command("blink", blink="false")
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device confirming the reboot command.
|
||||
"""
|
||||
return await self.send_command("reboot")
|
||||
|
||||
async def get_system_info(self) -> dict:
|
||||
"""Retrieve system information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing system information of the miner.
|
||||
"""
|
||||
return await self.send_command("get_system_info")
|
||||
|
||||
async def get_network_info(self) -> dict:
|
||||
"""Retrieve network configuration information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the network configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_network_info")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get a summary of the miner's status and performance.
|
||||
|
||||
Returns:
|
||||
dict: A summary of the miner's current operational status.
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def get_blink_status(self) -> dict:
|
||||
"""Check the status of the LED blinking on the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating whether the LED is currently blinking.
|
||||
"""
|
||||
return await self.send_command("get_blink_status")
|
||||
|
||||
async def set_network_conf(
|
||||
@@ -134,6 +216,19 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
hostname: str,
|
||||
protocol: int,
|
||||
) -> dict:
|
||||
"""Set the network configuration of the miner.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the device.
|
||||
dns (str): DNS server IP address.
|
||||
gateway (str): Gateway IP address.
|
||||
subnet_mask (str): Network subnet mask.
|
||||
hostname (str): Hostname of the device.
|
||||
protocol (int): Network protocol used.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the network configuration.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"set_network_conf",
|
||||
ipAddress=ip,
|
||||
@@ -147,6 +242,11 @@ class AntminerModernWebAPI(BaseWebAPI):
|
||||
|
||||
class AntminerOldWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initialize the old Antminer API client with a specific IP address.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Antminer device.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "root"
|
||||
self.pwd = settings.get("default_antminer_web_password", "root")
|
||||
@@ -159,6 +259,18 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Antminer device using HTTP digest authentication.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The CGI command to send.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
privileged (bool): If set to True, requires elevated privileges.
|
||||
**parameters: Arbitrary keyword arguments to be sent as parameters in the request.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device or an empty dictionary if an error occurs.
|
||||
"""
|
||||
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
@@ -184,6 +296,16 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously.
|
||||
|
||||
Args:
|
||||
*commands (str): Multiple command strings to be executed.
|
||||
ignore_errors (bool): If True, ignore any HTTP errors.
|
||||
allow_warning (bool): If True, proceed with warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of all commands executed.
|
||||
"""
|
||||
data = {k: None for k in commands}
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
@@ -203,30 +325,81 @@ class AntminerOldWebAPI(BaseWebAPI):
|
||||
return data
|
||||
|
||||
async def get_system_info(self) -> dict:
|
||||
"""Retrieve system information from the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing system information of the miner.
|
||||
"""
|
||||
return await self.send_command("get_system_info")
|
||||
|
||||
async def blink(self, blink: bool) -> dict:
|
||||
"""Control the blinking of the LED on the miner device.
|
||||
|
||||
Args:
|
||||
blink (bool): True to start blinking, False to stop.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after the command execution.
|
||||
"""
|
||||
if blink:
|
||||
return await self.send_command("blink", action="startBlink")
|
||||
return await self.send_command("blink", action="stopBlink")
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device confirming the reboot command.
|
||||
"""
|
||||
return await self.send_command("reboot")
|
||||
|
||||
async def get_blink_status(self) -> dict:
|
||||
"""Check the status of the LED blinking on the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating whether the LED is currently blinking.
|
||||
"""
|
||||
return await self.send_command("blink", action="onPageLoaded")
|
||||
|
||||
async def get_miner_conf(self) -> dict:
|
||||
"""Retrieve the miner configuration from the Antminer device.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current configuration of the miner.
|
||||
"""
|
||||
return await self.send_command("get_miner_conf")
|
||||
|
||||
async def set_miner_conf(self, conf: dict) -> dict:
|
||||
"""Set the configuration for the miner.
|
||||
|
||||
Args:
|
||||
conf (dict): A dictionary of configuration settings to apply to the miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary response from the device after setting the configuration.
|
||||
"""
|
||||
return await self.send_command("set_miner_conf", **conf)
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Retrieve detailed statistical data of the mining operation.
|
||||
|
||||
Returns:
|
||||
dict: Detailed statistics of the miner's operation.
|
||||
"""
|
||||
return await self.send_command("miner_stats")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get a summary of the miner's status and performance.
|
||||
|
||||
Returns:
|
||||
dict: A summary of the miner's current operational status.
|
||||
"""
|
||||
return await self.send_command("miner_summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Retrieve current pool information associated with the miner.
|
||||
|
||||
Returns:
|
||||
dict: Information about the mining pools configured in the miner.
|
||||
"""
|
||||
return await self.send_command("miner_pools")
|
||||
|
||||
@@ -30,6 +30,11 @@ from pyasic.web.base import BaseWebAPI
|
||||
|
||||
class AuradineWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
"""Initializes the API client for interacting with Auradine mining devices.
|
||||
|
||||
Args:
|
||||
ip (str): IP address of the Auradine miner.
|
||||
"""
|
||||
super().__init__(ip)
|
||||
self.username = "admin"
|
||||
self.pwd = settings.get("default_auradine_web_password", "admin")
|
||||
@@ -37,6 +42,11 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
self.token = None
|
||||
|
||||
async def auth(self) -> str | None:
|
||||
"""Authenticate and retrieve a web token from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
str | None: A token if authentication is successful, None otherwise.
|
||||
"""
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
try:
|
||||
auth = await client.post(
|
||||
@@ -65,6 +75,18 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
"""Send a command to the Auradine miner, handling authentication and retries.
|
||||
|
||||
Args:
|
||||
command (str | bytes): The specific command to execute.
|
||||
ignore_errors (bool): Whether to ignore HTTP errors.
|
||||
allow_warning (bool): Whether to proceed with warnings.
|
||||
privileged (bool): Whether the command requires privileged access.
|
||||
**parameters: Additional parameters for the command.
|
||||
|
||||
Returns:
|
||||
dict: The JSON response from the device.
|
||||
"""
|
||||
post = privileged or not parameters == {}
|
||||
if not parameters == {}:
|
||||
parameters["command"] = command
|
||||
@@ -102,6 +124,16 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously on the Auradine miner.
|
||||
|
||||
Args:
|
||||
*commands (str): Commands to execute.
|
||||
ignore_errors (bool): Whether to ignore errors during command execution.
|
||||
allow_warning (bool): Whether to proceed despite warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing responses for all commands executed.
|
||||
"""
|
||||
tasks = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
@@ -124,52 +156,157 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
return data
|
||||
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Perform a factory reset on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the reset operation.
|
||||
"""
|
||||
return await self.send_command("factory-reset", privileged=True)
|
||||
|
||||
async def get_fan(self) -> dict:
|
||||
"""Retrieve the current fan status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current fan status.
|
||||
"""
|
||||
return await self.send_command("fan")
|
||||
|
||||
async def set_fan(self, fan: int, speed_pct: int) -> dict:
|
||||
"""Set the speed of a specific fan on the Auradine miner.
|
||||
|
||||
Args:
|
||||
fan (int): The index of the fan to control.
|
||||
speed_pct (int): The speed percentage to set for the fan.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation.
|
||||
"""
|
||||
return await self.send_command("fan", index=fan, percentage=speed_pct)
|
||||
|
||||
async def firmware_upgrade(self, url: str = None, version: str = "latest") -> dict:
|
||||
"""Upgrade the firmware of the Auradine miner.
|
||||
|
||||
Args:
|
||||
url (str, optional): The URL to download the firmware from.
|
||||
version (str, optional): The version of the firmware to upgrade to, defaults to 'latest'.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the firmware upgrade.
|
||||
"""
|
||||
if url is not None:
|
||||
return await self.send_command("firmware-upgrade", url=url)
|
||||
return await self.send_command("firmware-upgrade", version=version)
|
||||
|
||||
async def get_frequency(self) -> dict:
|
||||
"""Retrieve the current frequency settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the frequency settings.
|
||||
"""
|
||||
return await self.send_command("frequency")
|
||||
|
||||
async def set_frequency(self, board: int, frequency: float) -> dict:
|
||||
"""Set the frequency for a specific board on the Auradine miner.
|
||||
|
||||
Args:
|
||||
board (int): The index of the board to configure.
|
||||
frequency (float): The frequency in MHz to set for the board.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the frequency.
|
||||
"""
|
||||
return await self.send_command("frequency", board=board, frequency=frequency)
|
||||
|
||||
async def ipreport(self) -> dict:
|
||||
"""Generate an IP report for the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the IP report details.
|
||||
"""
|
||||
return await self.send_command("ipreport")
|
||||
|
||||
async def get_led(self) -> dict:
|
||||
"""Retrieve the current LED status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current status of the LED settings.
|
||||
"""
|
||||
return await self.send_command("led")
|
||||
|
||||
async def set_led(self, code: int) -> dict:
|
||||
"""Set the LED code on the Auradine miner.
|
||||
|
||||
Args:
|
||||
code (int): The code that determines the LED behavior.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the operation.
|
||||
"""
|
||||
return await self.send_command("led", code=code)
|
||||
|
||||
async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str) -> dict:
|
||||
"""Set custom LED configurations including messages.
|
||||
|
||||
Args:
|
||||
code (int): The LED code to set.
|
||||
led_1 (int): The first LED indicator number.
|
||||
led_2 (int): The second LED indicator number.
|
||||
msg (str): The message to display or represent with LEDs.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the custom LED configuration.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"led", code=code, led1=led_1, led2=led_2, msg=msg
|
||||
)
|
||||
|
||||
async def get_mode(self) -> dict:
|
||||
"""Retrieve the current operational mode of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current mode settings.
|
||||
"""
|
||||
return await self.send_command("mode")
|
||||
|
||||
async def set_mode(self, **kwargs) -> dict:
|
||||
"""Set the operational mode of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Mode settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the mode setting operation.
|
||||
"""
|
||||
return await self.send_command("mode", **kwargs)
|
||||
|
||||
async def get_network(self) -> dict:
|
||||
"""Retrieve the network configuration settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the network configuration details.
|
||||
"""
|
||||
return await self.send_command("network")
|
||||
|
||||
async def set_network(self, **kwargs) -> dict:
|
||||
"""Set the network configuration of the Auradine miner.
|
||||
|
||||
Args:
|
||||
**kwargs: Network settings specified as keyword arguments.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the network configuration.
|
||||
"""
|
||||
return await self.send_command("network", **kwargs)
|
||||
|
||||
async def password(self, password: str) -> dict:
|
||||
"""Change the password used for accessing the Auradine miner.
|
||||
|
||||
Args:
|
||||
password (str): The new password to set.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the password change operation.
|
||||
"""
|
||||
res = await self.send_command(
|
||||
"password", user=self.username, old=self.pwd, new=password
|
||||
)
|
||||
@@ -177,43 +314,129 @@ class AuradineWebAPI(BaseWebAPI):
|
||||
return res
|
||||
|
||||
async def get_psu(self) -> dict:
|
||||
"""Retrieve the status of the power supply unit (PSU) from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the PSU status.
|
||||
"""
|
||||
return await self.send_command("psu")
|
||||
|
||||
async def set_psu(self, voltage: float) -> dict:
|
||||
"""Set the voltage for the power supply unit of the Auradine miner.
|
||||
|
||||
Args:
|
||||
voltage (float): The voltage level to set for the PSU.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the PSU voltage.
|
||||
"""
|
||||
return await self.send_command("psu", voltage=voltage)
|
||||
|
||||
async def get_register(self) -> dict:
|
||||
"""Retrieve registration information from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the registration details.
|
||||
"""
|
||||
return await self.send_command("register")
|
||||
|
||||
async def set_register(self, company: str) -> dict:
|
||||
"""Set the registration information for the Auradine miner.
|
||||
|
||||
Args:
|
||||
company (str): The company name to register the miner under.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the registration operation.
|
||||
"""
|
||||
return await self.send_command("register", parameter=company)
|
||||
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the reboot operation.
|
||||
"""
|
||||
return await self.send_command("restart", privileged=True)
|
||||
|
||||
async def restart_gcminer(self) -> dict:
|
||||
"""Restart the GCMiner application on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the GCMiner restart operation.
|
||||
"""
|
||||
return await self.send_command("restart", parameter="gcminer")
|
||||
|
||||
async def restart_api_server(self) -> dict:
|
||||
"""Restart the API server on the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the API server restart operation.
|
||||
"""
|
||||
return await self.send_command("restart", parameter="api-server")
|
||||
|
||||
async def temperature(self) -> dict:
|
||||
"""Retrieve the current temperature readings from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing temperature data.
|
||||
"""
|
||||
return await self.send_command("temperature")
|
||||
|
||||
async def timedate(self, ntp: str, timezone: str) -> dict:
|
||||
"""Set the time and date settings for the Auradine miner.
|
||||
|
||||
Args:
|
||||
ntp (str): The NTP server to use for time synchronization.
|
||||
timezone (str): The timezone setting.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of setting the time and date.
|
||||
"""
|
||||
return await self.send_command("timedate", ntp=ntp, timezone=timezone)
|
||||
|
||||
async def get_token(self) -> dict:
|
||||
"""Retrieve the current authentication token for the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the authentication token.
|
||||
"""
|
||||
return await self.send_command("token", user=self.username, password=self.pwd)
|
||||
|
||||
async def update_pools(self, pools: list[dict]) -> dict:
|
||||
"""Update the mining pools configuration on the Auradine miner.
|
||||
|
||||
Args:
|
||||
pools (list[dict]): A list of dictionaries, each representing a pool configuration.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the update operation.
|
||||
"""
|
||||
return await self.send_command("updatepools", pools=pools)
|
||||
|
||||
async def voltage(self) -> dict:
|
||||
"""Retrieve the voltage settings of the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the voltage details.
|
||||
"""
|
||||
return await self.send_command("voltage")
|
||||
|
||||
async def get_ztp(self) -> dict:
|
||||
"""Retrieve the zero-touch provisioning status from the Auradine miner.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the ZTP status.
|
||||
"""
|
||||
return await self.send_command("ztp")
|
||||
|
||||
async def set_ztp(self, enable: bool) -> dict:
|
||||
"""Enable or disable zero-touch provisioning (ZTP) on the Auradine miner.
|
||||
|
||||
Args:
|
||||
enable (bool): True to enable ZTP, False to disable.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary indicating the result of the ZTP setting operation.
|
||||
"""
|
||||
return await self.send_command("ztp", parameter="on" if enable else "off")
|
||||
|
||||
147
pyasic/web/marathon.py
Normal file
147
pyasic/web/marathon.py
Normal file
@@ -0,0 +1,147 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.web.base import BaseWebAPI
|
||||
|
||||
|
||||
class MaraWebAPI(BaseWebAPI):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
tasks = [
|
||||
asyncio.create_task(self._handle_multicommand(client, command))
|
||||
for command in commands
|
||||
]
|
||||
all_data = await asyncio.gather(*tasks)
|
||||
|
||||
data = {}
|
||||
for item in all_data:
|
||||
data.update(item)
|
||||
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _handle_multicommand(
|
||||
self, client: httpx.AsyncClient, command: str
|
||||
) -> dict:
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
|
||||
try:
|
||||
url = f"http://{self.ip}:{self.port}/kaonsu/v1/{command}"
|
||||
ret = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if ret.status_code == 200:
|
||||
try:
|
||||
json_data = ret.json()
|
||||
return {command: json_data}
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
return {command: {}}
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: str | bytes,
|
||||
ignore_errors: bool = False,
|
||||
allow_warning: bool = True,
|
||||
privileged: bool = False,
|
||||
**parameters: Any,
|
||||
) -> dict:
|
||||
url = f"http://{self.ip}:{self.port}/kaonsu/v1/{command}"
|
||||
auth = httpx.DigestAuth(self.username, self.pwd)
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(),
|
||||
) as client:
|
||||
if parameters:
|
||||
data = await client.post(
|
||||
url,
|
||||
auth=auth,
|
||||
timeout=settings.get("api_function_timeout", 3),
|
||||
json=parameters,
|
||||
)
|
||||
else:
|
||||
data = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def brief(self):
|
||||
return await self.send_command("brief")
|
||||
|
||||
async def ping(self):
|
||||
return await self.send_command("ping")
|
||||
|
||||
async def get_locate_miner(self):
|
||||
return await self.send_command("locate_miner")
|
||||
|
||||
async def set_locate_miner(self, blinking: bool):
|
||||
return await self.send_command("locate_miner", blinking=blinking)
|
||||
|
||||
async def reboot(self):
|
||||
return await self.send_command("maintenance", type="reboot")
|
||||
|
||||
async def reset(self):
|
||||
return await self.send_command("maintenance", type="reset")
|
||||
|
||||
async def reload(self):
|
||||
return await self.send_command("maintenance", type="reload")
|
||||
|
||||
async def set_password(self, new_pwd: str):
|
||||
return await self.send_command(
|
||||
"maintenance",
|
||||
type="passwd",
|
||||
params={"curPwd": self.pwd, "confirmPwd": self.pwd, "newPwd": new_pwd},
|
||||
)
|
||||
|
||||
async def get_network_config(self):
|
||||
return await self.send_command("network_config")
|
||||
|
||||
async def set_network_config(self, **params):
|
||||
return await self.send_command("network_config", **params)
|
||||
|
||||
async def get_miner_config(self):
|
||||
return await self.send_command("miner_config")
|
||||
|
||||
async def set_miner_config(self, **params):
|
||||
return await self.send_command("miner_config", **params)
|
||||
|
||||
async def fans(self):
|
||||
return await self.send_command("fans")
|
||||
|
||||
async def log(self):
|
||||
return await self.send_command("log")
|
||||
|
||||
async def overview(self):
|
||||
return await self.send_command("overview")
|
||||
|
||||
async def connections(self):
|
||||
return await self.send_command("connections")
|
||||
|
||||
async def controlboard_info(self):
|
||||
return await self.send_command("controlboard_info")
|
||||
|
||||
async def event_chart(self):
|
||||
return await self.send_command("event_chart")
|
||||
|
||||
async def hashboards(self):
|
||||
return await self.send_command("hashboards")
|
||||
|
||||
async def pools(self):
|
||||
return await self.send_command("pools")
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "pyasic"
|
||||
version = "0.54.0"
|
||||
version = "0.55.3"
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||
repository = "https://github.com/UpstreamData/pyasic"
|
||||
@@ -9,7 +9,7 @@ readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
httpx = "^0.26.0"
|
||||
httpx = ">=0.26.0"
|
||||
asyncssh = "^2.14.2"
|
||||
passlib = "^1.7.4"
|
||||
pyaml = "^23.12.0"
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from tests.api_tests import *
|
||||
from tests.rpc_tests import *
|
||||
from tests.config_tests import TestConfig
|
||||
from tests.miners_tests import MinersTest
|
||||
from tests.network_tests import NetworkTest
|
||||
|
||||
@@ -72,7 +72,11 @@ class TestConfig(unittest.TestCase):
|
||||
},
|
||||
"fan_mode": {"mode": "manual", "speed": 90, "minimum_fans": 2},
|
||||
"temperature": {"target": 70, "hot": None, "danger": 120},
|
||||
"mining_mode": {"mode": "power_tuning", "power": 3000, "algo": {}},
|
||||
"mining_mode": {
|
||||
"mode": "power_tuning",
|
||||
"power": 3000,
|
||||
"algo": {"mode": "standard"},
|
||||
},
|
||||
"power_scaling": {
|
||||
"mode": "enabled",
|
||||
"power_step": 100,
|
||||
@@ -98,7 +102,11 @@ class TestConfig(unittest.TestCase):
|
||||
"dangerous_temp": 120,
|
||||
},
|
||||
"fan_control": {"min_fans": 2, "speed": 90},
|
||||
"autotuning": {"enabled": True, "psu_power_limit": 3000},
|
||||
"autotuning": {
|
||||
"enabled": True,
|
||||
"mode": "power_target",
|
||||
"power_target": 3000,
|
||||
},
|
||||
"group": [
|
||||
{
|
||||
"name": "W91Q1L",
|
||||
@@ -112,10 +120,10 @@ class TestConfig(unittest.TestCase):
|
||||
"quota": 1,
|
||||
}
|
||||
],
|
||||
"power_scaling": {
|
||||
"performance_scaling": {
|
||||
"enabled": True,
|
||||
"power_step": 100,
|
||||
"min_psu_power_limit": 2000,
|
||||
"min_power_target": 2000,
|
||||
"shutdown_enabled": True,
|
||||
"shutdown_duration": 3,
|
||||
},
|
||||
@@ -128,9 +136,9 @@ class TestConfig(unittest.TestCase):
|
||||
def test_am_modern_serialize(self):
|
||||
correct_config = {
|
||||
"bitmain-fan-ctrl": True,
|
||||
"bitmain-fan-pwn": "90",
|
||||
"bitmain-fan-pwm": "90",
|
||||
"freq-level": "100",
|
||||
"miner-mode": "0",
|
||||
"miner-mode": 0,
|
||||
"pools": [
|
||||
{
|
||||
"url": "stratum+tcp://stratum.test.io:3333",
|
||||
|
||||
75
tests/config_tests/fans.py
Normal file
75
tests/config_tests/fans.py
Normal file
@@ -0,0 +1,75 @@
|
||||
import unittest
|
||||
|
||||
from pyasic.config import FanModeConfig
|
||||
|
||||
|
||||
class TestFanConfig(unittest.TestCase):
|
||||
def test_serialize_and_deserialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
dict_conf = conf.as_dict()
|
||||
self.assertEqual(conf, FanModeConfig.from_dict(dict_conf))
|
||||
|
||||
def test_bosminer_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of bosminer fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
bos_conf = conf.as_bosminer()
|
||||
self.assertEqual(conf, FanModeConfig.from_bosminer(bos_conf))
|
||||
|
||||
def test_am_modern_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of antminer modern fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
am_conf = conf.as_am_modern()
|
||||
self.assertEqual(conf, FanModeConfig.from_am_modern(am_conf))
|
||||
|
||||
def test_epic_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of epic fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
epic_conf = conf.as_epic()
|
||||
self.assertEqual(conf, FanModeConfig.from_epic(epic_conf))
|
||||
|
||||
def test_vnish_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of vnish fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
vnish_conf = conf.as_vnish()
|
||||
self.assertEqual(conf, FanModeConfig.from_vnish(vnish_conf))
|
||||
|
||||
def test_auradine_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of auradine fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
aur_conf = conf.as_auradine()
|
||||
self.assertEqual(conf, FanModeConfig.from_auradine(aur_conf))
|
||||
|
||||
def test_boser_deserialize_and_serialize(self):
|
||||
for fan_mode in FanModeConfig:
|
||||
with self.subTest(
|
||||
msg=f"Test serialization and deserialization of boser fan config",
|
||||
fan_mode=fan_mode,
|
||||
):
|
||||
conf = fan_mode()
|
||||
boser_conf = conf.as_boser()
|
||||
self.assertEqual(conf, FanModeConfig.from_boser(boser_conf))
|
||||
@@ -24,6 +24,7 @@ from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
from pyasic.rpc.gcminer import GCMinerRPCAPI
|
||||
from pyasic.rpc.luxminer import LUXMinerRPCAPI
|
||||
|
||||
|
||||
@@ -159,6 +160,12 @@ class TestCGMinerAPI(TestAPIBase):
|
||||
self.api_str = "CGMiner"
|
||||
|
||||
|
||||
class TestGCMinerRPCAPI(TestAPIBase):
|
||||
def setUpData(self):
|
||||
self.api = GCMinerRPCAPI(self.ip)
|
||||
self.api_str = "GCMiner"
|
||||
|
||||
|
||||
class TestLuxOSAPI(TestAPIBase):
|
||||
def setUpData(self):
|
||||
self.api = LUXMinerRPCAPI(self.ip)
|
||||
Reference in New Issue
Block a user