Compare commits

...

109 Commits

Author SHA1 Message Date
Upstream Data
e2e1d2f2fd version: bump version number. 2024-05-06 14:43:19 -06:00
Upstream Data
dd205c0f06 feature: use semaphore for scanning. 2024-05-06 14:43:03 -06:00
Upstream Data
79e247c0cf version: bump version number. 2024-05-03 11:19:50 -06:00
Upstream Data
836d045b65 bug: fix bosminer sometimes not being able to set config due to temp settings not being set. 2024-05-03 11:19:33 -06:00
Upstream Data
2d029b65e6 version: bump version number. 2024-05-03 08:31:39 -06:00
Upstream Data
993b7efeef bug: fix bosminer.toml writing to use only ssh, no sftp. 2024-05-03 08:30:51 -06:00
Upstream Data
b7a81097a4 version: bump version number. 2024-04-30 15:50:37 -06:00
Upstream Data
651bef8203 bug: round mara fw wattage. 2024-04-30 14:43:35 -06:00
Upstream Data
a9e5c99ab2 bug: fix mara fw expected hashrate scaling. 2024-04-30 14:42:43 -06:00
Upstream Data
30216fdd5b bug: fix mara fw hashrate scaling. 2024-04-30 14:32:43 -06:00
Upstream Data
341cc13d83 bug: fix no firmware type for BOSer. 2024-04-30 13:12:01 -06:00
Upstream Data
05a4ae6f04 bug: reformat, and fix miner listener. 2024-04-30 11:50:21 -06:00
Brett Rowan
5971d9fd83 Merge pull request #125 from UpstreamData/dev_marafw
feature: Add maraFW support.
2024-04-30 10:01:35 -06:00
Brett Rowan
3968f2275c Merge branch 'master' into dev_marafw 2024-04-30 10:01:28 -06:00
Upstream Data
84344ca883 feature: make mara discover more consistent. 2024-04-30 09:11:23 -06:00
1e9abhi1e10
59b80254eb remove trailing whitespace 2024-04-30 09:11:23 -06:00
1e9abhi1e10
06fdb19e0b Add tess in config_tests 2024-04-30 09:11:23 -06:00
Upstream Data
75222e8cd2 docs: update docs generation to fix marathon miners. 2024-04-30 09:11:23 -06:00
Upstream Data
5a067d60e7 bug: remove some excepts that were catching propagated cancellations. 2024-04-30 09:11:21 -06:00
Upstream Data
2fbc4fcb4a version: bump version number. 2024-04-30 09:11:18 -06:00
Upstream Data
13fe60504a bug: fix bad naming of S19J Pro Plus NOPIC. 2024-04-30 09:11:18 -06:00
Upstream Data
cdc52c3605 docs: update docs. 2024-04-30 09:11:17 -06:00
Upstream Data
a9db097355 version: bump version number. 2024-04-30 09:11:17 -06:00
Upstream Data
ec5557cf88 feature: Add support for S19jPro+ No PIC. 2024-04-30 09:11:17 -06:00
Upstream Data
a8ea84d2f3 version: bump version number. 2024-04-30 09:11:17 -06:00
Upstream Data
58d369eedf bug: fix disabling fans when using immersion mode. 2024-04-30 09:11:17 -06:00
Abhishek Patidar
f8f9dd7070 Add tests in rpc_tests (#128) 2024-04-30 09:11:17 -06:00
Upstream Data
01b72e1ee6 version: bump version number. 2024-04-30 09:11:16 -06:00
Upstream Data
e367e630b8 bug: fix bosminer fan control for 2.0 versions. 2024-04-30 09:11:16 -06:00
upstreamdata
385cca6fc0 docs: update index and readme. 2024-04-30 09:11:16 -06:00
upstreamdata
75a3a466a3 docs: update documentation to include new web docstrings. 2024-04-30 09:11:16 -06:00
Harsh Singh Rawat
4bf08dbfe6 docs: add docstring for antminer and auradine in web (#127)
* docs: add docstring for antminer and auradine in web

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring of the fucntion multicommand

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

---------

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>
2024-04-30 09:11:16 -06:00
Abhishek Patidar
833d061315 docs: Add documentation for config (#121)
* docs: Add documentation for `config`

* Fix styling

* Add documentation for classmethods

* fixes some docstring
2024-04-30 09:11:16 -06:00
Abhishek Patidar
a85558278d docs: Add documentation for ssh (#126) 2024-04-30 09:11:15 -06:00
Upstream Data
bf5087b06d Merge remote-tracking branch 'origin/master' 2024-04-29 09:14:59 -06:00
Upstream Data
ec58d13bae docs: update docs generation to fix marathon miners. 2024-04-29 09:14:47 -06:00
Brett Rowan
75993564ab Merge pull request #131 from 1e9abhi1e10/config_tests 2024-04-27 12:16:30 -06:00
1e9abhi1e10
f3ea169dec remove trailing whitespace 2024-04-27 15:47:08 +05:30
1e9abhi1e10
39b3fe5c25 Add tess in config_tests 2024-04-27 15:40:03 +05:30
Upstream Data
5c79c6cb0c bug: remove some excepts that were catching propagated cancellations. 2024-04-26 13:00:16 -06:00
Upstream Data
bab4261bed version: bump version number. 2024-04-24 10:39:34 -06:00
Upstream Data
e1d5c89388 bug: fix bad naming of S19J Pro Plus NOPIC. 2024-04-24 10:37:07 -06:00
Upstream Data
5f6c8cca18 docs: update docs. 2024-04-24 10:04:46 -06:00
Upstream Data
39db14b002 version: bump version number. 2024-04-24 10:04:13 -06:00
Upstream Data
3be04c678b feature: Add support for S19jPro+ No PIC. 2024-04-24 10:03:36 -06:00
Upstream Data
099ec35a8f version: bump version number. 2024-04-22 12:28:20 -06:00
Upstream Data
113dfb9170 bug: fix disabling fans when using immersion mode. 2024-04-22 12:27:44 -06:00
Abhishek Patidar
8d19e0ebbb Add tests in rpc_tests (#128) 2024-04-22 09:30:20 -06:00
Upstream Data
ec064eba65 version: bump version number. 2024-04-22 08:58:28 -06:00
Upstream Data
3451127761 bug: fix bosminer fan control for 2.0 versions. 2024-04-22 08:58:06 -06:00
upstreamdata
b3be52ca77 docs: update index and readme. 2024-04-19 21:43:22 -06:00
upstreamdata
b6ec6caa72 docs: update documentation to include new web docstrings. 2024-04-19 21:41:17 -06:00
Harsh Singh Rawat
8e81e18622 docs: add docstring for antminer and auradine in web (#127)
* docs: add docstring for antminer and auradine in web

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

* Remove Trailing whitespace from the docstring of the fucntion multicommand

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>

---------

Signed-off-by: PAINxNAGATO <harshrawat349@gmail.com>
2024-04-19 15:14:01 -06:00
Abhishek Patidar
6ea26e0e19 docs: Add documentation for config (#121)
* docs: Add documentation for `config`

* Fix styling

* Add documentation for classmethods

* fixes some docstring
2024-04-19 15:05:12 -06:00
Abhishek Patidar
be446f94c1 docs: Add documentation for ssh (#126) 2024-04-19 15:03:51 -06:00
Upstream Data
3d94e30f22 feature: Add wattage settings, reboot, restart backend, stop mining, and start mining. 2024-04-12 15:20:09 -06:00
Upstream Data
d5a7ff3a46 feature: add wattage limit data and set wattage to mara. 2024-04-12 15:15:23 -06:00
Upstream Data
bbfa97632d feature: add send_config for mara miners. 2024-04-12 14:56:18 -06:00
Upstream Data
ecf0ce22d6 feature: add mara config parsing. 2024-04-12 14:28:22 -06:00
Upstream Data
d56da007a5 feature: make mara discover more consistent. 2024-04-12 13:40:10 -06:00
Upstream Data
2c86b2da7e feature: Add mara fw light functions. 2024-04-12 13:16:55 -06:00
Upstream Data
c73b1ceb07 feature: update mara fw data locations. 2024-04-12 13:09:23 -06:00
Upstream Data
a320c8967d feature: remove some mara support in order to get rid of Antminer function calls. 2024-04-12 10:19:24 -06:00
Upstream Data
e21e340f60 feature: add mara miner models. 2024-04-12 10:02:13 -06:00
Upstream Data
f63d8f4b91 feature: add really basic MaraFW support. 2024-04-12 09:55:45 -06:00
Upstream Data
ba6a1606b6 version: bump version number. 2024-04-12 08:48:52 -06:00
Upstream Data
51b0c0456f bug: fix parsing of tuner algo modes into json. 2024-04-12 08:47:40 -06:00
Upstream Data
04bd03b496 version: bump version number. 2024-04-11 09:31:00 -06:00
Upstream Data
bc5764c8ff feature: add stock support for T21. 2024-04-11 09:30:24 -06:00
Brett Rowan
1f2e066f4c Merge pull request #122 from jpcomps/fix_is_mining
feature: add is_mining for ePIC, add T21, add BlockMiner 720i
2024-04-11 09:28:18 -06:00
Upstream Data
74c457a694 version: bump version number. 2024-04-11 09:23:55 -06:00
Upstream Data
4a1c53dfd7 bug: fix some issues with config on bosminer. 2024-04-11 09:23:33 -06:00
John-Paul Compagnone
81b77f8768 cleaner code 2024-04-11 11:06:45 -04:00
John-Paul Compagnone
3b127b6862 fix is_mining for ePIC 2024-04-11 09:34:40 -04:00
Upstream Data
2815d2ba11 version: bump version number. 2024-04-08 12:15:38 -06:00
Upstream Data
1ff20fc6f0 feature: add bosminer S21 support. 2024-04-08 12:15:18 -06:00
Brett Rowan
797c847055 version: bump version number. 2024-04-07 17:17:17 -06:00
Brett Rowan
65c7f2f66f bug: fix vnish shutting-down handling. 2024-04-07 17:16:37 -06:00
Brett Rowan
445d621590 version: bump version number. 2024-04-07 17:04:10 -06:00
Brett Rowan
d39ecfd6b4 feature: add is_mining for vnish. 2024-04-07 17:03:28 -06:00
Brett Rowan
36663471fb version: bump version number. 2024-04-06 12:34:43 -06:00
Brett Rowan
80293ac52f bug: fix incorrect model for XP. 2024-04-06 12:34:14 -06:00
UpstreamData
70b45f40f5 docs: update dev setup. 2024-03-29 11:19:17 -06:00
UpstreamData
a511fabd9c Update README.md with dev docs. 2024-03-29 11:17:39 -06:00
Jim Burtoft
8bc8f6f178 Comment on some code I didn't understand initially (#118) 2024-03-28 21:31:37 -06:00
Brett Rowan
b790ad58a7 version: bump version number. 2024-03-23 20:27:42 -06:00
Brett Rowan
354ab793a2 bug: fix vnish MAC not working on some versions. 2024-03-23 20:25:26 -06:00
Upstream Data
59346d641f version: bump version number. 2024-03-22 15:12:35 -06:00
Upstream Data
11d770771b feature: fix vnish not having the shutdown flag. 2024-03-22 15:12:20 -06:00
Upstream Data
1b6db7ed45 feature: add antminer new API. 2024-03-22 13:42:10 -06:00
Upstream Data
55c4e10fae tests: Update tests, and fix some bugs. 2024-03-22 13:19:44 -06:00
Upstream Data
77c06dad61 version: bump version number. 2024-03-19 14:24:54 -06:00
Upstream Data
68d250d2f2 bug: fix K pro naming. 2024-03-19 14:24:31 -06:00
Upstream Data
094a17ac68 version: bump version number. 2024-03-19 14:17:21 -06:00
Upstream Data
dbcdeaa3de feature: add support for S19K Pro. 2024-03-19 14:16:57 -06:00
Upstream Data
872cac811a version: bump version number. 2024-03-19 12:41:57 -06:00
Upstream Data
d324c2fee9 feature: add stock S21 support. 2024-03-19 12:41:37 -06:00
JP Compagnone
577e8df612 Merge branch 'UpstreamData:master' into master 2024-03-14 16:58:12 -04:00
Upstream Data
4b54cf67ba version: bump version number. 2024-03-12 16:16:40 -06:00
Upstream Data
0e00fe3114 feature: add antminer mode as str setting. 2024-03-12 16:16:15 -06:00
wilfredallyn
15d1dc5bb6 feature: add install docs (#117) 2024-03-10 09:09:06 -06:00
Brett Rowan
2af0003843 version: bump version number. 2024-03-06 21:56:10 -07:00
Brett Rowan
3c227be170 bug: update httpx to use compatible versioning. 2024-03-06 21:53:14 -07:00
Brett Rowan
e889780bad version: bump version number. 2024-03-03 11:16:16 -07:00
Brett Rowan
cc3d4fa805 feature: add S19 Hydro and S19 Pro+ Hydro. 2024-03-03 11:15:44 -07:00
John-Paul Compagnone
743823f66e add Blockminer 720i and Antminer T21 support 2024-02-25 17:44:20 -05:00
Brett Rowan
227e1e2d2d version: bump version number. 2024-02-25 13:10:11 -07:00
Brett Rowan
d6c8ff0910 feature: add support for more S21 LUXOS. 2024-02-25 13:08:25 -07:00
Brett Rowan
bd20e051b0 feature: add support for more LuxOS models. 2024-02-25 13:02:52 -07:00
82 changed files with 2377 additions and 163 deletions

View File

@@ -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)

View File

@@ -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

View File

@@ -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):

View File

@@ -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",

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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>

View File

@@ -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`.

View File

@@ -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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,7 @@
# pyasic
## VNishWebAPI
::: pyasic.web.vnish.VNishWebAPI
handler: python
options:
show_root_heading: false
heading_level: 4

View File

@@ -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"

View File

@@ -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),
)

View File

@@ -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 {}

View File

@@ -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()

View File

@@ -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()

View File

@@ -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"])])

View File

@@ -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:

View File

@@ -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":

View File

@@ -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

View File

@@ -20,4 +20,5 @@ from .cgminer import *
from .epic import *
from .hiveon import *
from .luxos import *
from .marathon import *
from .vnish import *

View File

@@ -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

View File

@@ -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

View File

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

View File

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

View 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

View File

@@ -18,3 +18,4 @@ from .X7 import *
from .X9 import *
from .X17 import *
from .X19 import *
from .X21 import *

View File

@@ -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

View File

@@ -21,7 +21,9 @@ from .S19 import (
BOSMinerS19j,
BOSMinerS19jNoPIC,
BOSMinerS19jPro,
BOSMinerS19jProNoPIC,
BOSMinerS19jProPlus,
BOSMinerS19jProPlusNoPIC,
BOSMinerS19kProNoPIC,
BOSMinerS19Plus,
BOSMinerS19Pro,

View File

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

View 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

View File

@@ -17,3 +17,4 @@
from .X9 import *
from .X17 import *
from .X19 import *
from .X21 import *

View File

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

View File

@@ -17,3 +17,7 @@
from .S21 import (
ePICS21,
)
from .T21 import (
ePICT21,
)

View File

@@ -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

View File

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

View File

@@ -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

View File

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

View 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

View File

@@ -16,3 +16,4 @@
from .X9 import *
from .X19 import *
from .X21 import *

View 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

View File

@@ -0,0 +1,9 @@
from .S19 import (
MaraS19,
MaraS19j,
MaraS19jNoPIC,
MaraS19jPro,
MaraS19KPro,
MaraS19Pro,
MaraS19XP,
)

View File

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

View File

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

View File

@@ -0,0 +1,2 @@
from .S21 import MaraS21
from .T21 import MaraT21

View File

@@ -0,0 +1,2 @@
from .X19 import *
from .X21 import *

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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:

View 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

View File

@@ -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()

View File

@@ -16,4 +16,5 @@
from .blockminer import (
ePICBlockMiner520i,
ePICBlockMiner720i,
)

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

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

View File

@@ -17,3 +17,6 @@
from .S21 import (
S21,
)
from .T21 import (
T21,
)

View File

@@ -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

View File

@@ -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
View 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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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")

View File

@@ -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

View File

@@ -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")

View File

@@ -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
View 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")

View File

@@ -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"

View File

@@ -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

View File

@@ -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",

View 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))

View File

@@ -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)