Compare commits
74 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f4054bf38 | ||
|
|
cd52d3aeaf | ||
|
|
66c9b3663e | ||
|
|
5f0e1da938 | ||
|
|
2bd031b33d | ||
|
|
e2f07818cc | ||
|
|
75056cfff5 | ||
|
|
7fbcb0dbd2 | ||
|
|
7329aeace2 | ||
|
|
e8c3953106 | ||
|
|
a1a7562bdb | ||
|
|
b2726c77a0 | ||
|
|
68299fa54d | ||
|
|
1466039e08 | ||
|
|
0aa72c6c85 | ||
|
|
24134a5991 | ||
|
|
038208efa6 | ||
|
|
7e319b79df | ||
|
|
a0fdec3ce5 | ||
|
|
1a8928de18 | ||
|
|
bce0058930 | ||
|
|
850656fce4 | ||
|
|
8bb35d6d7c | ||
|
|
e85f06dbc2 | ||
|
|
a566801316 | ||
|
|
e1a9cc5d19 | ||
|
|
27bb06de2b | ||
|
|
debd4d2d4d | ||
|
|
56ad6cbc6f | ||
|
|
3fa54213bf | ||
|
|
076958ec0e | ||
|
|
5319089ebe | ||
|
|
76a77b51e8 | ||
|
|
b099ff45d2 | ||
|
|
9bc3cc221a | ||
|
|
6418c2e102 | ||
|
|
aa9f3b2c45 | ||
|
|
bb1c98f061 | ||
|
|
d984431fe5 | ||
|
|
f1e4feb91e | ||
|
|
90c8986900 | ||
|
|
5457ae6cd5 | ||
|
|
aa3d105fcb | ||
|
|
77f59f6db6 | ||
|
|
3fa0d96fbb | ||
|
|
e55477a8b8 | ||
|
|
7d5744ae28 | ||
|
|
d4500be10c | ||
|
|
7ef2540133 | ||
|
|
1ea4f4e124 | ||
|
|
a8a0e4a5fe | ||
|
|
5f2f6e01da | ||
|
|
41b4c23d45 | ||
|
|
b4687f18fd | ||
|
|
2437421005 | ||
|
|
40ebc2773f | ||
|
|
b8ae238d23 | ||
|
|
2920639b70 | ||
|
|
bd9144b3de | ||
|
|
8f7a67d4dc | ||
|
|
a62bea33a7 | ||
|
|
406bcd179c | ||
|
|
aa87ef7d71 | ||
|
|
ec88fbf6aa | ||
|
|
e23c86a944 | ||
|
|
b81276cb19 | ||
|
|
8dcc72b1bb | ||
|
|
540572356f | ||
|
|
83035a869b | ||
|
|
4c104a59ff | ||
|
|
e708ae3728 | ||
|
|
a4352816ee | ||
|
|
336bd9c002 | ||
|
|
e3c917efde |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,3 +7,4 @@ pyvenv.cfg
|
|||||||
bin/
|
bin/
|
||||||
lib/
|
lib/
|
||||||
.idea/
|
.idea/
|
||||||
|
.vs/
|
||||||
@@ -5,13 +5,13 @@ ci:
|
|||||||
- generate-docs
|
- generate-docs
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/python-poetry/poetry
|
- repo: https://github.com/python-poetry/poetry
|
||||||
rev: 2.1.1
|
rev: 2.2.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: poetry-check
|
- id: poetry-check
|
||||||
- id: poetry-lock
|
- id: poetry-lock
|
||||||
- id: poetry-install
|
- id: poetry-install
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v5.0.0
|
rev: v6.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: trailing-whitespace
|
- id: trailing-whitespace
|
||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
@@ -22,16 +22,24 @@ repos:
|
|||||||
name: check-yaml for other YAML files
|
name: check-yaml for other YAML files
|
||||||
exclude: ^mkdocs\.yml$
|
exclude: ^mkdocs\.yml$
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
rev: 25.1.0
|
rev: v0.13.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: ruff-check
|
||||||
- repo: https://github.com/pycqa/isort
|
args: [--fix]
|
||||||
rev: 6.0.1
|
- id: ruff-format
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.18.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: mypy
|
||||||
name: isort (python)
|
additional_dependencies:
|
||||||
|
[
|
||||||
|
betterproto==2.0.0b7,
|
||||||
|
httpx==0.28.1,
|
||||||
|
types-aiofiles==24.1.0.20250822,
|
||||||
|
types-passlib==1.7.7.20250602,
|
||||||
|
pydantic==2.11.9,
|
||||||
|
]
|
||||||
- repo: local
|
- repo: local
|
||||||
hooks:
|
hooks:
|
||||||
- id: unittest
|
- id: unittest
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Welcome to `pyasic`! `pyasic` uses an asynchronous method of communicating with
|
|||||||
## Installation
|
## 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:
|
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
|
- [pypoetry](https://python-poetry.org/): the reccommended way, since pyasic already uses it by default. Use version 2.0+
|
||||||
|
|
||||||
```
|
```
|
||||||
poetry install
|
poetry install
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import asyncio
|
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
|
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
|
||||||
|
|
||||||
@@ -128,7 +128,7 @@ BACKEND_TYPE_CLOSER = """
|
|||||||
</ul>
|
</ul>
|
||||||
</details>"""
|
</details>"""
|
||||||
|
|
||||||
m_data = {}
|
m_data: dict[str, dict[str, list[type[Any]]]] = {}
|
||||||
done = []
|
done = []
|
||||||
|
|
||||||
for m in MINER_CLASSES:
|
for m in MINER_CLASSES:
|
||||||
|
|||||||
@@ -157,6 +157,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19j Pro+ (Stock)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [x] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jProPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19j XP (Stock)
|
## S19j XP (Stock)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -170,6 +183,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19j+ (Stock)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [x] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19j No PIC (Stock)
|
## S19j No PIC (Stock)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -287,6 +313,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19 XP Hydro (BOS+)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19XPHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19+ (BOS+)
|
## S19+ (BOS+)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -443,6 +482,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19 Hydro (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19Hydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19 Pro (VNish)
|
## S19 Pro (VNish)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -456,6 +508,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19 Pro A (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19ProA
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19 Pro Hydro (VNish)
|
## S19 Pro Hydro (VNish)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -469,6 +534,32 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19 XP (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19XP
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
## S19 XP Hydro (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X19.S19.VNishS19XPHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S19a (VNish)
|
## S19a (VNish)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -759,7 +850,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19
|
||||||
@@ -772,7 +863,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19Pro
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19Pro
|
||||||
@@ -785,7 +876,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19XP
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19XP
|
||||||
@@ -798,7 +889,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jPro
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jPro
|
||||||
@@ -811,7 +902,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jProPlus
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19jProPlus
|
||||||
@@ -824,7 +915,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19kPro
|
::: pyasic.miners.antminer.luxos.X19.S19.LUXMinerS19kPro
|
||||||
@@ -837,7 +928,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X19.T19.LUXMinerT19
|
::: pyasic.miners.antminer.luxos.X19.T19.LUXMinerT19
|
||||||
|
|||||||
@@ -53,6 +53,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21+ Hydro (Stock)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [x] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21PlusHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## T21 (Stock)
|
## T21 (Stock)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -79,6 +92,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21 Hydro (BOS+)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Hydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S21 Pro (BOS+)
|
## S21 Pro (BOS+)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -92,6 +118,32 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21+ (BOS+)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21+ Hydro (BOS+)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21PlusHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## T21 (BOS+)
|
## T21 (BOS+)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -118,6 +170,58 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21 Hydro (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Hydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21 Pro (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21+ (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
## S21+ Hydro (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X21.S21.VNishS21PlusHydro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## T21 (VNish)
|
## T21 (VNish)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
@@ -174,7 +278,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
|
::: pyasic.miners.antminer.luxos.X21.S21.LUXMinerS21
|
||||||
@@ -183,6 +287,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## T21 (LuxOS)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.luxos.X21.T21.LUXMinerT21
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## S21 (MaraFW)
|
## S21 (MaraFW)
|
||||||
|
|
||||||
- [ ] Shutdowns
|
- [ ] Shutdowns
|
||||||
|
|||||||
@@ -73,7 +73,7 @@
|
|||||||
- [x] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.vnish.X3.L3.VnishL3Plus
|
::: pyasic.miners.antminer.vnish.X3.L3.VNishL3Plus
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
- [x] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.vnish.X7.L7.VnishL7
|
::: pyasic.miners.antminer.vnish.X7.L7.VNishL7
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -105,6 +105,19 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## L9 (VNish)
|
||||||
|
|
||||||
|
- [x] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [x] Setpoints
|
||||||
|
- [x] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.vnish.X9.L9.VNishL9
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## T9 (Hive)
|
## T9 (Hive)
|
||||||
|
|
||||||
- [ ] Shutdowns
|
- [ ] Shutdowns
|
||||||
@@ -122,7 +135,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [x] Setpoints
|
||||||
- [x] Presets
|
- [x] Presets
|
||||||
|
|
||||||
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9
|
::: pyasic.miners.antminer.luxos.X9.S9.LUXMinerS9
|
||||||
|
|||||||
16
docs/miners/avalonminer/Q.md
Normal file
16
docs/miners/avalonminer/Q.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## Q Models
|
||||||
|
|
||||||
|
## Avalon Q Home (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.Q.Q.CGMinerAvalonQHome
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## nano Models
|
## nano Models
|
||||||
|
|
||||||
|
## Avalon Nano 3s (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.nano.nano3.CGMinerAvalonNano3s
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## Avalon Nano 3 (Stock)
|
## Avalon Nano 3 (Stock)
|
||||||
|
|
||||||
- [ ] Shutdowns
|
- [ ] Shutdowns
|
||||||
|
|||||||
@@ -1,6 +1,19 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## DGX Models
|
## DGX Models
|
||||||
|
|
||||||
|
## DG1 (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
## DG1+ (Stock)
|
## DG1+ (Stock)
|
||||||
|
|
||||||
- [ ] Shutdowns
|
- [ ] Shutdowns
|
||||||
@@ -14,3 +27,16 @@
|
|||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 0
|
heading_level: 0
|
||||||
|
|
||||||
|
## DG1Home (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.elphapex.daoge.DGX.DG1.ElphapexDG1Home
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
|
|||||||
16
docs/miners/goldshell/Byte.md
Normal file
16
docs/miners/goldshell/Byte.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## byte Models
|
||||||
|
|
||||||
|
## Byte (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.goldshell.bfgminer.byte.byte.GoldshellByte
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
16
docs/miners/goldshell/MiniDoge.md
Normal file
16
docs/miners/goldshell/MiniDoge.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## Mini Doge Models
|
||||||
|
|
||||||
|
## Mini Doge (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.goldshell.bfgminer.MiniDoge.MiniDoge.GoldshellMiniDoge
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
16
docs/miners/goldshell/mini_doge.md
Normal file
16
docs/miners/goldshell/mini_doge.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## mini_doge Models
|
||||||
|
|
||||||
|
## Mini Doge (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.goldshell.bfgminer.mini_doge.mini_doge.GoldshellMiniDoge
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
16
docs/miners/iceriver/ALX.md
Normal file
16
docs/miners/iceriver/ALX.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# pyasic
|
||||||
|
## ALX Models
|
||||||
|
|
||||||
|
## AL3 (Stock)
|
||||||
|
|
||||||
|
- [ ] Shutdowns
|
||||||
|
- [ ] Power Modes
|
||||||
|
- [ ] Setpoints
|
||||||
|
- [ ] Presets
|
||||||
|
|
||||||
|
::: pyasic.miners.iceriver.iceminer.ALX.AL3.IceRiverAL3
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 0
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>X7 Series:</summary>
|
<summary>X7 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
|
<li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li>
|
<li><a href="../antminer/X7#l7-stock">L7 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X7#k7-stock">K7 (Stock)</a></li>
|
<li><a href="../antminer/X7#k7-stock">K7 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X7#d7-stock">D7 (Stock)</a></li>
|
<li><a href="../antminer/X7#d7-stock">D7 (Stock)</a></li>
|
||||||
@@ -53,6 +54,7 @@ details {
|
|||||||
<li><a href="../antminer/X9#s9j-stock">S9j (Stock)</a></li>
|
<li><a href="../antminer/X9#s9j-stock">S9j (Stock)</a></li>
|
||||||
<li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li>
|
<li><a href="../antminer/X9#t9-stock">T9 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
|
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X9#l9-stock">L9 (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -86,6 +88,8 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19j-no-pic-stock">S19j No PIC (Stock)</a></li>
|
<li><a href="../antminer/X19#s19j-no-pic-stock">S19j No PIC (Stock)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-pro_1-stock">S19 Pro+ (Stock)</a></li>
|
<li><a href="../antminer/X19#s19-pro_1-stock">S19 Pro+ (Stock)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro-stock">S19j Pro (Stock)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-stock">S19j Pro (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19j_1-stock">S19j+ (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19j-pro_1-stock">S19j Pro+ (Stock)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-xp-stock">S19 XP (Stock)</a></li>
|
<li><a href="../antminer/X19#s19-xp-stock">S19 XP (Stock)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-stock">S19a (Stock)</a></li>
|
<li><a href="../antminer/X19#s19a-stock">S19a (Stock)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-pro-stock">S19a Pro (Stock)</a></li>
|
<li><a href="../antminer/X19#s19a-pro-stock">S19a Pro (Stock)</a></li>
|
||||||
@@ -104,6 +108,8 @@ details {
|
|||||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-stock">S21 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#s21_1-stock">S21+ (Stock)</a></li>
|
<li><a href="../antminer/X21#s21_1-stock">S21+ (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-hydro-stock">S21+ Hydro (Stock)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-hydro-stock">S21+ Hydro (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-pro-stock">S21 Pro (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
<li><a href="../antminer/X21#t21-stock">T21 (Stock)</a></li>
|
||||||
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
|
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
|
||||||
@@ -553,6 +559,7 @@ details {
|
|||||||
<summary>nano Series:</summary>
|
<summary>nano Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
|
<li><a href="../avalonminer/nano#avalon-nano-3-stock">Avalon Nano 3 (Stock)</a></li>
|
||||||
|
<li><a href="../avalonminer/nano#avalon-nano-3s-stock">Avalon Nano 3s (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -561,6 +568,12 @@ details {
|
|||||||
<li><a href="../avalonminer/A15X#avalon-1566-stock">Avalon 1566 (Stock)</a></li>
|
<li><a href="../avalonminer/A15X#avalon-1566-stock">Avalon 1566 (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Q Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../avalonminer/Q#avalon-q-home-stock">Avalon Q Home (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -611,6 +624,18 @@ details {
|
|||||||
<li><a href="../goldshell/XBox#kd-box-pro-stock">KD Box Pro (Stock)</a></li>
|
<li><a href="../goldshell/XBox#kd-box-pro-stock">KD Box Pro (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>byte Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../goldshell/byte#byte-stock">Byte (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>mini_doge Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../goldshell/mini_doge#mini-doge-stock">Mini Doge (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -654,6 +679,7 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19-xp-bos_1">S19 XP (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-pro_1-hydro-bos_1">S19 Pro+ Hydro (BOS+)</a></li>
|
<li><a href="../antminer/X19#s19-pro_1-hydro-bos_1">S19 Pro+ Hydro (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
<li><a href="../antminer/X19#t19-bos_1">T19 (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-xp-hydro-bos_1">S19 XP Hydro (BOS+)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -661,6 +687,9 @@ details {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
|
<li><a href="../antminer/X21#s21-bos_1">S21 (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X21#s21-pro-bos_1">S21 Pro (BOS+)</a></li>
|
<li><a href="../antminer/X21#s21-pro-bos_1">S21 Pro (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-bos_1">S21+ (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-hydro-bos_1">S21+ Hydro (BOS+)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-hydro-bos_1">S21 Hydro (BOS+)</a></li>
|
||||||
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
|
<li><a href="../antminer/X21#t21-bos_1">T21 (BOS+)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -689,6 +718,12 @@ details {
|
|||||||
<li><a href="../antminer/X7#l7-vnish">L7 (VNish)</a></li>
|
<li><a href="../antminer/X7#l7-vnish">L7 (VNish)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>X9 Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../antminer/X9#l9-vnish">L9 (VNish)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X17 Series:</summary>
|
<summary>X17 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -704,11 +739,16 @@ details {
|
|||||||
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19-pro-vnish">S19 Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
|
<li><a href="../antminer/X19#s19j-vnish">S19j (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19i-vnish">S19i (VNish)</a></li>
|
<li><a href="../antminer/X19#s19i-vnish">S19i (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-xp-vnish">S19 XP (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-xp-hydro-vnish">S19 XP Hydro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19j-pro-vnish">S19j Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
|
<li><a href="../antminer/X19#s19a-vnish">S19a (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-hydro-vnish">S19 Hydro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro-a-vnish">S19 Pro A (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#s19k-pro-vnish">S19k Pro (VNish)</a></li>
|
<li><a href="../antminer/X19#s19k-pro-vnish">S19k Pro (VNish)</a></li>
|
||||||
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
|
||||||
@@ -719,6 +759,10 @@ details {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#t21-vnish">T21 (VNish)</a></li>
|
<li><a href="../antminer/X21#t21-vnish">T21 (VNish)</a></li>
|
||||||
<li><a href="../antminer/X21#s21-vnish">S21 (VNish)</a></li>
|
<li><a href="../antminer/X21#s21-vnish">S21 (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-vnish">S21+ (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21_1-hydro-vnish">S21+ Hydro (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-pro-vnish">S21 Pro (VNish)</a></li>
|
||||||
|
<li><a href="../antminer/X21#s21-hydro-vnish">S21 Hydro (VNish)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -814,6 +858,7 @@ details {
|
|||||||
<summary>X21 Series:</summary>
|
<summary>X21 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../antminer/X21#s21-luxos">S21 (LuxOS)</a></li>
|
<li><a href="../antminer/X21#s21-luxos">S21 (LuxOS)</a></li>
|
||||||
|
<li><a href="../antminer/X21#t21-luxos">T21 (LuxOS)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -912,6 +957,12 @@ details {
|
|||||||
<li><a href="../iceriver/KSX#ks5m-stock">KS5M (Stock)</a></li>
|
<li><a href="../iceriver/KSX#ks5m-stock">KS5M (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>ALX Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../iceriver/ALX#al3-stock">AL3 (Stock)</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
@@ -943,6 +994,8 @@ details {
|
|||||||
<summary>DGX Series:</summary>
|
<summary>DGX Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="../elphapex/DGX#dg1_1-stock">DG1+ (Stock)</a></li>
|
<li><a href="../elphapex/DGX#dg1_1-stock">DG1+ (Stock)</a></li>
|
||||||
|
<li><a href="../elphapex/DGX#dg1-stock">DG1 (Stock)</a></li>
|
||||||
|
<li><a href="../elphapex/DGX#dg1home-stock">DG1Home (Stock)</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK40
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK50
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK60
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL20
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL30
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL40
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL50
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL60
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ40
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ60
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK10
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK30
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL10
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL20
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL30
|
||||||
@@ -291,7 +291,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||||
@@ -330,7 +330,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||||
@@ -343,7 +343,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||||
@@ -369,7 +369,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ40
|
||||||
@@ -408,7 +408,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ50
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK10
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK20
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK30
|
||||||
@@ -460,7 +460,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK50
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK60
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK70
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK70
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK80
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK80
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL20
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL30
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
|
||||||
@@ -551,7 +551,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||||
@@ -577,7 +577,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||||
@@ -590,7 +590,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||||
@@ -603,7 +603,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||||
@@ -616,7 +616,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||||
@@ -629,7 +629,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||||
@@ -642,7 +642,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||||
@@ -668,7 +668,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH90
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH90
|
||||||
@@ -681,7 +681,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||||
@@ -694,7 +694,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||||
@@ -707,7 +707,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ40
|
||||||
@@ -733,7 +733,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ60
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ60
|
||||||
@@ -746,7 +746,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK40
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK40
|
||||||
@@ -759,7 +759,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK50
|
||||||
@@ -772,7 +772,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M52S_Plus_Plus.BTMinerM52SPlusPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M5X.M52S_Plus_Plus.BTMinerM52SPlusPlusVL10
|
||||||
@@ -785,7 +785,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M52S.BTMinerM52SVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M52S.BTMinerM52SVK30
|
||||||
@@ -798,7 +798,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53H.BTMinerM53HVH10
|
::: pyasic.miners.whatsminer.btminer.M5X.M53H.BTMinerM53HVH10
|
||||||
@@ -811,7 +811,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK10
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK10
|
||||||
@@ -824,7 +824,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK20
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK20
|
||||||
@@ -837,7 +837,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK30
|
||||||
@@ -850,7 +850,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK50
|
||||||
@@ -863,7 +863,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL10
|
||||||
@@ -876,7 +876,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL30
|
||||||
@@ -889,7 +889,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||||
@@ -902,7 +902,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ40
|
||||||
@@ -915,7 +915,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ50
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ50
|
||||||
@@ -928,7 +928,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVK30
|
||||||
@@ -941,7 +941,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH20
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH20
|
||||||
@@ -954,7 +954,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||||
@@ -967,7 +967,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ30
|
||||||
@@ -980,7 +980,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ40
|
||||||
@@ -993,7 +993,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVK30
|
||||||
@@ -1006,7 +1006,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||||
@@ -1019,7 +1019,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH40
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH40
|
||||||
@@ -1032,7 +1032,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH50
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH50
|
||||||
@@ -1045,7 +1045,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK30
|
||||||
@@ -1058,7 +1058,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK60
|
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK60
|
||||||
@@ -1071,7 +1071,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVK30
|
||||||
@@ -1084,7 +1084,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL30
|
||||||
@@ -1097,7 +1097,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL40
|
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL40
|
||||||
@@ -1110,7 +1110,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK10
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK10
|
||||||
@@ -1123,7 +1123,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK30
|
||||||
@@ -1136,7 +1136,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK40
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK40
|
||||||
@@ -1149,7 +1149,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK50
|
||||||
@@ -1162,7 +1162,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
||||||
@@ -1175,7 +1175,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK30
|
||||||
@@ -1188,7 +1188,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK40
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK40
|
||||||
@@ -1201,7 +1201,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK50
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK50
|
||||||
@@ -1214,7 +1214,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
||||||
@@ -1227,7 +1227,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ30
|
||||||
@@ -1240,7 +1240,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ40
|
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ40
|
||||||
@@ -1253,7 +1253,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
||||||
@@ -1266,7 +1266,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL30
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL40
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK30
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK40
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK50
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK50
|
||||||
@@ -70,7 +70,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK60
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK60
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK70
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK70
|
||||||
@@ -96,7 +96,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL10
|
||||||
@@ -109,7 +109,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL30
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL40
|
||||||
@@ -135,7 +135,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL50
|
||||||
@@ -148,7 +148,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL60
|
||||||
@@ -161,7 +161,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK10
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK10
|
||||||
@@ -174,7 +174,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK20
|
||||||
@@ -187,7 +187,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK30
|
||||||
@@ -200,7 +200,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK40
|
||||||
@@ -213,7 +213,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL10
|
||||||
@@ -226,7 +226,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL20
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL30
|
||||||
@@ -252,7 +252,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL40
|
||||||
@@ -265,7 +265,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL50
|
||||||
@@ -278,7 +278,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL60
|
||||||
@@ -291,7 +291,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL70
|
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL70
|
||||||
@@ -304,7 +304,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK10
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK10
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK20
|
||||||
@@ -330,7 +330,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK30
|
||||||
@@ -343,7 +343,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK40
|
||||||
@@ -356,7 +356,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK6A
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK6A
|
||||||
@@ -369,7 +369,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL10
|
||||||
@@ -382,7 +382,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL20
|
||||||
@@ -395,7 +395,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL30
|
||||||
@@ -408,7 +408,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL40
|
||||||
@@ -421,7 +421,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL50
|
||||||
@@ -434,7 +434,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61S_Plus.BTMinerM61SPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M61S_Plus.BTMinerM61SPlusVL30
|
||||||
@@ -447,7 +447,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL10
|
||||||
@@ -460,7 +460,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL20
|
||||||
@@ -473,7 +473,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL30
|
||||||
@@ -486,7 +486,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK10
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK10
|
||||||
@@ -499,7 +499,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK20
|
||||||
@@ -512,7 +512,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK30
|
||||||
@@ -525,7 +525,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK40
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK40
|
||||||
@@ -538,7 +538,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL10
|
||||||
@@ -551,7 +551,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL30
|
||||||
@@ -564,7 +564,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL40
|
||||||
@@ -577,7 +577,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL50
|
||||||
@@ -590,7 +590,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL60
|
||||||
@@ -603,7 +603,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M62S_Plus.BTMinerM62SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M62S_Plus.BTMinerM62SPlusVK30
|
||||||
@@ -616,7 +616,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus_Plus.BTMinerM63SPlusPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus_Plus.BTMinerM63SPlusPlusVL20
|
||||||
@@ -629,7 +629,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVK30
|
||||||
@@ -642,7 +642,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL10
|
||||||
@@ -655,7 +655,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL20
|
||||||
@@ -668,7 +668,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL30
|
||||||
@@ -681,7 +681,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL50
|
||||||
@@ -694,7 +694,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK10
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK10
|
||||||
@@ -707,7 +707,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK20
|
||||||
@@ -720,7 +720,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK30
|
||||||
@@ -733,7 +733,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK60
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK60
|
||||||
@@ -746,7 +746,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL10
|
||||||
@@ -759,7 +759,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL50
|
||||||
@@ -772,7 +772,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL60
|
||||||
@@ -785,7 +785,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK10
|
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK10
|
||||||
@@ -798,7 +798,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK20
|
||||||
@@ -811,7 +811,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK30
|
||||||
@@ -824,7 +824,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL10
|
||||||
@@ -837,7 +837,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL30
|
||||||
@@ -850,7 +850,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M64S.BTMinerM64SVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M64S.BTMinerM64SVL30
|
||||||
@@ -863,7 +863,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL30
|
||||||
@@ -876,7 +876,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL40
|
||||||
@@ -889,7 +889,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M65S_Plus.BTMinerM65SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M65S_Plus.BTMinerM65SPlusVK30
|
||||||
@@ -902,7 +902,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVK20
|
||||||
@@ -915,7 +915,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVL60
|
||||||
@@ -928,7 +928,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus_Plus.BTMinerM66SPlusPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus_Plus.BTMinerM66SPlusPlusVL20
|
||||||
@@ -941,7 +941,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVK30
|
||||||
@@ -954,7 +954,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL10
|
||||||
@@ -967,7 +967,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL20
|
||||||
@@ -980,7 +980,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL30
|
||||||
@@ -993,7 +993,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL40
|
||||||
@@ -1006,7 +1006,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL60
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL60
|
||||||
@@ -1019,7 +1019,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK20
|
||||||
@@ -1032,7 +1032,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK30
|
||||||
@@ -1045,7 +1045,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK40
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK40
|
||||||
@@ -1058,7 +1058,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK50
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK50
|
||||||
@@ -1071,7 +1071,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK60
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK60
|
||||||
@@ -1084,7 +1084,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL10
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL10
|
||||||
@@ -1097,7 +1097,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL20
|
||||||
@@ -1110,7 +1110,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL30
|
||||||
@@ -1123,7 +1123,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL40
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL40
|
||||||
@@ -1136,7 +1136,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL50
|
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL50
|
||||||
@@ -1149,7 +1149,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK20
|
||||||
@@ -1162,7 +1162,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK30
|
||||||
@@ -1175,7 +1175,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL20
|
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL20
|
||||||
@@ -1188,7 +1188,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL30
|
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL30
|
||||||
@@ -1201,7 +1201,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M6X.M67S.BTMinerM67SVK30
|
::: pyasic.miners.whatsminer.btminer.M6X.M67S.BTMinerM67SVK30
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
- [x] Shutdowns
|
- [x] Shutdowns
|
||||||
- [x] Power Modes
|
- [x] Power Modes
|
||||||
- [x] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.whatsminer.btminer.M7X.M70.BTMinerM70VM30
|
::: pyasic.miners.whatsminer.btminer.M7X.M70.BTMinerM70VM30
|
||||||
|
|||||||
@@ -97,6 +97,8 @@ nav:
|
|||||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||||
- Innosilicon A11X: "miners/innosilicon/A11X.md"
|
- Innosilicon A11X: "miners/innosilicon/A11X.md"
|
||||||
|
- Goldshell Byte: "miners/goldshell/Byte.md"
|
||||||
|
- Goldshell Mini Doge: "miners/goldshell/MiniDoge.md"
|
||||||
- Goldshell X5: "miners/goldshell/X5.md"
|
- Goldshell X5: "miners/goldshell/X5.md"
|
||||||
- Goldshell XMax: "miners/goldshell/XMax.md"
|
- Goldshell XMax: "miners/goldshell/XMax.md"
|
||||||
- Goldshell XBox: "miners/goldshell/XBox.md"
|
- Goldshell XBox: "miners/goldshell/XBox.md"
|
||||||
|
|||||||
1148
poetry.lock
generated
1148
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -14,10 +14,41 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from pyasic.config.fans import FanMode, FanModeConfig, FanModeNormal
|
from pyasic.config.fans import (
|
||||||
from pyasic.config.mining import MiningMode, MiningModeConfig
|
FanModeConfig,
|
||||||
|
FanModeImmersion,
|
||||||
|
FanModeManual,
|
||||||
|
FanModeNormal,
|
||||||
|
)
|
||||||
|
from pyasic.config.mining import (
|
||||||
|
MiningModeConfig,
|
||||||
|
MiningModeHashrateTune,
|
||||||
|
MiningModeHPM,
|
||||||
|
MiningModeLPM,
|
||||||
|
MiningModeManual,
|
||||||
|
MiningModeNormal,
|
||||||
|
MiningModePowerTune,
|
||||||
|
MiningModePreset,
|
||||||
|
MiningModeSleep,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Type aliases for config field types
|
||||||
|
FanModeType = FanModeNormal | FanModeManual | FanModeImmersion | FanModeConfig
|
||||||
|
MiningModeType = (
|
||||||
|
MiningModeNormal
|
||||||
|
| MiningModeHPM
|
||||||
|
| MiningModeLPM
|
||||||
|
| MiningModeSleep
|
||||||
|
| MiningModeManual
|
||||||
|
| MiningModePowerTune
|
||||||
|
| MiningModeHashrateTune
|
||||||
|
| MiningModePreset
|
||||||
|
| MiningModeConfig
|
||||||
|
)
|
||||||
from pyasic.config.mining.scaling import ScalingConfig
|
from pyasic.config.mining.scaling import ScalingConfig
|
||||||
from pyasic.config.pools import PoolConfig
|
from pyasic.config.pools import PoolConfig
|
||||||
from pyasic.config.temperature import TemperatureConfig
|
from pyasic.config.temperature import TemperatureConfig
|
||||||
@@ -32,11 +63,11 @@ class MinerConfig(BaseModel):
|
|||||||
arbitrary_types_allowed = True
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
pools: PoolConfig = Field(default_factory=PoolConfig.default)
|
pools: PoolConfig = Field(default_factory=PoolConfig.default)
|
||||||
fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
|
fan_mode: FanModeType = Field(default_factory=FanModeConfig.default)
|
||||||
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
|
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
|
||||||
mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
|
mining_mode: MiningModeType = Field(default_factory=MiningModeConfig.default)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item: str) -> Any:
|
||||||
try:
|
try:
|
||||||
return getattr(self, item)
|
return getattr(self, item)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -85,6 +116,13 @@ class MinerConfig(BaseModel):
|
|||||||
**self.temperature.as_wm(),
|
**self.temperature.as_wm(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||||
|
"""Generates the configuration in the format suitable for Whatsminers running BTMiner V3."""
|
||||||
|
return {
|
||||||
|
"set.miner.pools": self.pools.as_btminer_v3(),
|
||||||
|
**self.mining_mode.as_btminer_v3(),
|
||||||
|
}
|
||||||
|
|
||||||
def as_am_old(self, user_suffix: str | None = None) -> dict:
|
def as_am_old(self, user_suffix: str | None = None) -> dict:
|
||||||
"""Generates the configuration in the format suitable for old versions of Antminers."""
|
"""Generates the configuration in the format suitable for old versions of Antminers."""
|
||||||
return {
|
return {
|
||||||
@@ -247,6 +285,16 @@ class MinerConfig(BaseModel):
|
|||||||
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_goldshell_list(cls, web_conf: list) -> "MinerConfig":
|
||||||
|
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
|
||||||
|
return cls(pools=PoolConfig.from_goldshell(web_conf))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_goldshell_byte(cls, web_conf: list) -> "MinerConfig":
|
||||||
|
"""Constructs a MinerConfig object from web configuration for Goldshell Byte miners."""
|
||||||
|
return cls(pools=PoolConfig.from_goldshell_byte(web_conf))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
def from_inno(cls, web_pools: list) -> "MinerConfig":
|
||||||
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
|
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
|
||||||
@@ -283,13 +331,17 @@ class MinerConfig(BaseModel):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings: dict, web_presets: list[dict]) -> "MinerConfig":
|
def from_vnish(
|
||||||
|
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
|
||||||
|
) -> "MinerConfig":
|
||||||
"""Constructs a MinerConfig object from web settings for VNish miners."""
|
"""Constructs a MinerConfig object from web settings for VNish miners."""
|
||||||
return cls(
|
return cls(
|
||||||
pools=PoolConfig.from_vnish(web_settings),
|
pools=PoolConfig.from_vnish(web_settings),
|
||||||
fan_mode=FanModeConfig.from_vnish(web_settings),
|
fan_mode=FanModeConfig.from_vnish(web_settings),
|
||||||
temperature=TemperatureConfig.from_vnish(web_settings),
|
temperature=TemperatureConfig.from_vnish(web_settings),
|
||||||
mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
|
mining_mode=MiningModeConfig.from_vnish(
|
||||||
|
web_settings, web_presets, web_perf_summary
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -346,3 +398,14 @@ class MinerConfig(BaseModel):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
|
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
|
||||||
return cls.from_am_modern(*args, **kwargs)
|
return cls.from_am_modern(*args, **kwargs)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_btminer_v3(
|
||||||
|
cls, rpc_pools: dict, rpc_settings: dict, rpc_device_info: dict
|
||||||
|
) -> "MinerConfig":
|
||||||
|
return cls(
|
||||||
|
pools=PoolConfig.from_btminer_v3(rpc_pools=rpc_pools["msg"]),
|
||||||
|
mining_mode=MiningModeConfig.from_btminer_v3(
|
||||||
|
rpc_device_info=rpc_device_info, rpc_settings=rpc_settings
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@@ -89,58 +90,61 @@ class MinerConfigOption(Enum):
|
|||||||
|
|
||||||
class MinerConfigValue(BaseModel):
|
class MinerConfigValue(BaseModel):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None):
|
def from_dict(cls, dict_conf: dict):
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
return self.model_dump()
|
return self.model_dump()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_hiveon_modern(self) -> dict:
|
def as_hiveon_modern(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_am_old(self) -> dict:
|
def as_am_old(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_wm(self) -> dict:
|
def as_wm(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_inno(self) -> dict:
|
def as_btminer_v3(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_goldshell(self) -> dict:
|
def as_inno(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_avalon(self) -> dict:
|
def as_goldshell(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_bosminer(self) -> dict:
|
def as_avalon(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_boser(self) -> dict:
|
def as_bosminer(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_epic(self) -> dict:
|
def as_boser(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_vnish(self) -> dict:
|
def as_epic(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_auradine(self) -> dict:
|
def as_vnish(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_mara(self) -> dict:
|
def as_auradine(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_espminer(self) -> dict:
|
def as_mara(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_luxos(self) -> dict:
|
def as_espminer(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def as_elphapex(self) -> dict:
|
def as_luxos(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def as_elphapex(self, *args: Any, **kwargs: Any) -> Any:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import TypeVar, Union
|
from typing import TypeVar
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ class FanModeNormal(MinerConfigValue):
|
|||||||
minimum_speed: int = 0
|
minimum_speed: int = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeNormal":
|
def from_dict(cls, dict_conf: dict) -> FanModeNormal:
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("minimum_fans") is not None:
|
if dict_conf.get("minimum_fans") is not None:
|
||||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||||
@@ -37,7 +37,7 @@ class FanModeNormal(MinerConfigValue):
|
|||||||
return cls(**cls_conf)
|
return cls(**cls_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeNormal":
|
def from_vnish(cls, web_cooling_settings: dict) -> FanModeNormal:
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if web_cooling_settings.get("fan_min_count") is not None:
|
if web_cooling_settings.get("fan_min_count") is not None:
|
||||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||||
@@ -112,7 +112,7 @@ class FanModeManual(MinerConfigValue):
|
|||||||
minimum_fans: int = 1
|
minimum_fans: int = 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeManual":
|
def from_dict(cls, dict_conf: dict) -> FanModeManual:
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("speed") is not None:
|
if dict_conf.get("speed") is not None:
|
||||||
cls_conf["speed"] = dict_conf["speed"]
|
cls_conf["speed"] = dict_conf["speed"]
|
||||||
@@ -121,7 +121,7 @@ class FanModeManual(MinerConfigValue):
|
|||||||
return cls(**cls_conf)
|
return cls(**cls_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_fan_conf: dict) -> "FanModeManual":
|
def from_bosminer(cls, toml_fan_conf: dict) -> FanModeManual:
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if toml_fan_conf.get("min_fans") is not None:
|
if toml_fan_conf.get("min_fans") is not None:
|
||||||
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
cls_conf["minimum_fans"] = toml_fan_conf["min_fans"]
|
||||||
@@ -130,7 +130,7 @@ class FanModeManual(MinerConfigValue):
|
|||||||
return cls(**cls_conf)
|
return cls(**cls_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeManual":
|
def from_vnish(cls, web_cooling_settings: dict) -> FanModeManual:
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if web_cooling_settings.get("fan_min_count") is not None:
|
if web_cooling_settings.get("fan_min_count") is not None:
|
||||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||||
@@ -191,7 +191,7 @@ class FanModeImmersion(MinerConfigValue):
|
|||||||
mode: str = Field(init=False, default="immersion")
|
mode: str = Field(init=False, default="immersion")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
|
def from_dict(cls, dict_conf: dict | None) -> FanModeImmersion:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -417,5 +417,5 @@ class FanModeConfig(MinerConfigOption):
|
|||||||
|
|
||||||
FanMode = TypeVar(
|
FanMode = TypeVar(
|
||||||
"FanMode",
|
"FanMode",
|
||||||
bound=Union[FanModeNormal, FanModeManual, FanModeImmersion],
|
bound=FanModeNormal | FanModeManual | FanModeImmersion,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from typing import TypeVar, Union
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
@@ -35,7 +35,14 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
|||||||
TunerPerformanceMode,
|
TunerPerformanceMode,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .algo import TunerAlgo, TunerAlgoType
|
from .algo import (
|
||||||
|
BoardTuneAlgo,
|
||||||
|
ChipTuneAlgo,
|
||||||
|
StandardTuneAlgo,
|
||||||
|
TunerAlgo,
|
||||||
|
TunerAlgoType,
|
||||||
|
VOptAlgo,
|
||||||
|
)
|
||||||
from .presets import MiningPreset
|
from .presets import MiningPreset
|
||||||
from .scaling import ScalingConfig
|
from .scaling import ScalingConfig
|
||||||
|
|
||||||
@@ -44,7 +51,7 @@ class MiningModeNormal(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="normal")
|
mode: str = field(init=False, default="normal")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeNormal:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -63,6 +70,9 @@ class MiningModeNormal(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
return {"set.miner.service": "start", "set.miner.power_mode": self.mode}
|
||||||
|
|
||||||
def as_auradine(self) -> dict:
|
def as_auradine(self) -> dict:
|
||||||
return {"mode": {"mode": self.mode}}
|
return {"mode": {"mode": self.mode}}
|
||||||
|
|
||||||
@@ -90,7 +100,7 @@ class MiningModeSleep(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="sleep")
|
mode: str = field(init=False, default="sleep")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeSleep:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -109,6 +119,9 @@ class MiningModeSleep(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
return {"set.miner.service": "stop"}
|
||||||
|
|
||||||
def as_auradine(self) -> dict:
|
def as_auradine(self) -> dict:
|
||||||
return {"mode": {"sleep": "on"}}
|
return {"mode": {"sleep": "on"}}
|
||||||
|
|
||||||
@@ -130,7 +143,7 @@ class MiningModeLPM(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="low")
|
mode: str = field(init=False, default="low")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeLPM:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -149,6 +162,9 @@ class MiningModeLPM(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
return {"set.miner.service": "start", "set.miner.power_mode": self.mode}
|
||||||
|
|
||||||
def as_auradine(self) -> dict:
|
def as_auradine(self) -> dict:
|
||||||
return {"mode": {"mode": "eco"}}
|
return {"mode": {"mode": "eco"}}
|
||||||
|
|
||||||
@@ -160,7 +176,7 @@ class MiningModeHPM(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="high")
|
mode: str = field(init=False, default="high")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeHPM:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -179,6 +195,9 @@ class MiningModeHPM(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
return {"set.miner.service": "start", "set.miner.power_mode": self.mode}
|
||||||
|
|
||||||
def as_auradine(self) -> dict:
|
def as_auradine(self) -> dict:
|
||||||
return {"mode": {"mode": "turbo"}}
|
return {"mode": {"mode": "turbo"}}
|
||||||
|
|
||||||
@@ -189,11 +208,15 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
|
|
||||||
mode: str = field(init=False, default="power_tuning")
|
mode: str = field(init=False, default="power_tuning")
|
||||||
power: int | None = None
|
power: int | None = None
|
||||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
algo: StandardTuneAlgo | VOptAlgo | BoardTuneAlgo | ChipTuneAlgo = field(
|
||||||
|
default_factory=TunerAlgo.default
|
||||||
|
)
|
||||||
scaling: ScalingConfig | None = None
|
scaling: ScalingConfig | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModePowerTune:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("power"):
|
if dict_conf.get("power"):
|
||||||
cls_conf["power"] = dict_conf["power"]
|
cls_conf["power"] = dict_conf["power"]
|
||||||
@@ -222,6 +245,9 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
return {"set.miner.service": "start", "set.miner.power_limit": self.power}
|
||||||
|
|
||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
tuning_cfg = {"enabled": True, "mode": "power_target"}
|
tuning_cfg = {"enabled": True, "mode": "power_target"}
|
||||||
if self.power is not None:
|
if self.power is not None:
|
||||||
@@ -230,25 +256,27 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
cfg = {"autotuning": tuning_cfg}
|
cfg = {"autotuning": tuning_cfg}
|
||||||
|
|
||||||
if self.scaling is not None:
|
if self.scaling is not None:
|
||||||
scaling_cfg = {"enabled": True}
|
scaling_cfg: dict[str, Any] = {"enabled": True}
|
||||||
if self.scaling.step is not None:
|
if self.scaling.step is not None:
|
||||||
scaling_cfg["power_step"] = self.scaling.step
|
scaling_cfg["power_step"] = self.scaling.step
|
||||||
if self.scaling.minimum is not None:
|
if self.scaling.minimum is not None:
|
||||||
scaling_cfg["min_power_target"] = self.scaling.minimum
|
scaling_cfg["min_power_target"] = self.scaling.minimum
|
||||||
if self.scaling.shutdown is not None:
|
if self.scaling.shutdown is not None:
|
||||||
scaling_cfg = {**scaling_cfg, **self.scaling.shutdown.as_bosminer()}
|
scaling_cfg.update(self.scaling.shutdown.as_bosminer())
|
||||||
cfg["performance_scaling"] = scaling_cfg
|
cfg["performance_scaling"] = scaling_cfg
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def as_boser(self) -> dict:
|
def as_boser(self) -> dict:
|
||||||
cfg = {
|
cfg: dict[str, Any] = {
|
||||||
"set_performance_mode": SetPerformanceModeRequest(
|
"set_performance_mode": SetPerformanceModeRequest(
|
||||||
save_action=SaveAction.SAVE_AND_APPLY,
|
save_action=SaveAction(SaveAction.SAVE_AND_APPLY),
|
||||||
mode=PerformanceMode(
|
mode=PerformanceMode(
|
||||||
tuner_mode=TunerPerformanceMode(
|
tuner_mode=TunerPerformanceMode(
|
||||||
power_target=PowerTargetMode(
|
power_target=PowerTargetMode(
|
||||||
power_target=Power(watt=self.power)
|
power_target=Power(watt=self.power)
|
||||||
|
if self.power is not None
|
||||||
|
else None # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
@@ -258,11 +286,15 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
sd_cfg = {}
|
sd_cfg = {}
|
||||||
if self.scaling.shutdown is not None:
|
if self.scaling.shutdown is not None:
|
||||||
sd_cfg = self.scaling.shutdown.as_boser()
|
sd_cfg = self.scaling.shutdown.as_boser()
|
||||||
power_target_kwargs = {"power_step": Power(self.scaling.step)}
|
power_target_kwargs: dict[str, Any] = {}
|
||||||
|
if self.scaling.step is not None:
|
||||||
|
power_target_kwargs["power_step"] = Power(watt=self.scaling.step)
|
||||||
if self.scaling.minimum is not None:
|
if self.scaling.minimum is not None:
|
||||||
power_target_kwargs["min_power_target"] = Power(self.scaling.minimum)
|
power_target_kwargs["min_power_target"] = Power(
|
||||||
|
watt=self.scaling.minimum
|
||||||
|
)
|
||||||
cfg["set_dps"] = SetDpsRequest(
|
cfg["set_dps"] = SetDpsRequest(
|
||||||
save_action=SaveAction.SAVE_AND_APPLY,
|
save_action=SaveAction(SaveAction.SAVE_AND_APPLY),
|
||||||
enable=True,
|
enable=True,
|
||||||
**sd_cfg,
|
**sd_cfg,
|
||||||
target=DpsTarget(power_target=DpsPowerTarget(**power_target_kwargs)),
|
target=DpsTarget(power_target=DpsPowerTarget(**power_target_kwargs)),
|
||||||
@@ -294,11 +326,15 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
|
|
||||||
mode: str = field(init=False, default="hashrate_tuning")
|
mode: str = field(init=False, default="hashrate_tuning")
|
||||||
hashrate: int | None = None
|
hashrate: int | None = None
|
||||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
algo: StandardTuneAlgo | VOptAlgo | BoardTuneAlgo | ChipTuneAlgo = field(
|
||||||
|
default_factory=TunerAlgo.default
|
||||||
|
)
|
||||||
scaling: ScalingConfig | None = None
|
scaling: ScalingConfig | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeHashrateTune:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("hashrate"):
|
if dict_conf.get("hashrate"):
|
||||||
cls_conf["hashrate"] = dict_conf["hashrate"]
|
cls_conf["hashrate"] = dict_conf["hashrate"]
|
||||||
@@ -328,16 +364,17 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
conf["hashrate_target"] = self.hashrate
|
conf["hashrate_target"] = self.hashrate
|
||||||
return {"autotuning": conf}
|
return {"autotuning": conf}
|
||||||
|
|
||||||
@property
|
|
||||||
def as_boser(self) -> dict:
|
def as_boser(self) -> dict:
|
||||||
cfg = {
|
cfg: dict[str, Any] = {
|
||||||
"set_performance_mode": SetPerformanceModeRequest(
|
"set_performance_mode": SetPerformanceModeRequest(
|
||||||
save_action=SaveAction.SAVE_AND_APPLY,
|
save_action=SaveAction(SaveAction.SAVE_AND_APPLY),
|
||||||
mode=PerformanceMode(
|
mode=PerformanceMode(
|
||||||
tuner_mode=TunerPerformanceMode(
|
tuner_mode=TunerPerformanceMode(
|
||||||
hashrate_target=HashrateTargetMode(
|
hashrate_target=HashrateTargetMode(
|
||||||
hashrate_target=TeraHashrate(
|
hashrate_target=TeraHashrate(
|
||||||
terahash_per_second=self.hashrate
|
terahash_per_second=float(self.hashrate)
|
||||||
|
if self.hashrate is not None
|
||||||
|
else None # type: ignore[arg-type]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -348,14 +385,21 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
sd_cfg = {}
|
sd_cfg = {}
|
||||||
if self.scaling.shutdown is not None:
|
if self.scaling.shutdown is not None:
|
||||||
sd_cfg = self.scaling.shutdown.as_boser()
|
sd_cfg = self.scaling.shutdown.as_boser()
|
||||||
|
hashrate_target_kwargs: dict[str, Any] = {}
|
||||||
|
if self.scaling.step is not None:
|
||||||
|
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
|
||||||
|
terahash_per_second=float(self.scaling.step)
|
||||||
|
)
|
||||||
|
if self.scaling.minimum is not None:
|
||||||
|
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
|
||||||
|
terahash_per_second=float(self.scaling.minimum)
|
||||||
|
)
|
||||||
cfg["set_dps"] = SetDpsRequest(
|
cfg["set_dps"] = SetDpsRequest(
|
||||||
|
save_action=SaveAction(SaveAction.SAVE_AND_APPLY),
|
||||||
enable=True,
|
enable=True,
|
||||||
**sd_cfg,
|
**sd_cfg,
|
||||||
target=DpsTarget(
|
target=DpsTarget(
|
||||||
hashrate_target=DpsHashrateTarget(
|
hashrate_target=DpsHashrateTarget(**hashrate_target_kwargs)
|
||||||
hashrate_step=TeraHashrate(self.scaling.step),
|
|
||||||
min_hashrate_target=TeraHashrate(self.scaling.minimum),
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -367,7 +411,11 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
def as_epic(self) -> dict:
|
def as_epic(self) -> dict:
|
||||||
mode = {
|
mode = {
|
||||||
"ptune": {
|
"ptune": {
|
||||||
"algo": self.algo.as_epic(),
|
"algo": (
|
||||||
|
self.algo.as_epic()
|
||||||
|
if hasattr(self.algo, "as_epic")
|
||||||
|
else TunerAlgo.default().as_epic()
|
||||||
|
),
|
||||||
"target": self.hashrate,
|
"target": self.hashrate,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,19 +452,25 @@ class MiningModePreset(MinerConfigValue):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(
|
def from_vnish(
|
||||||
cls, web_overclock_settings: dict, web_presets: list[dict]
|
cls,
|
||||||
) -> "MiningModePreset":
|
web_overclock_settings: dict,
|
||||||
active_preset = None
|
web_presets: list[dict],
|
||||||
for preset in web_presets:
|
web_perf_summary: dict,
|
||||||
if preset["name"] == web_overclock_settings["preset"]:
|
) -> MiningModePreset:
|
||||||
active_preset = preset
|
active_preset = web_perf_summary.get("current_preset")
|
||||||
|
|
||||||
|
if active_preset is None:
|
||||||
|
for preset in web_presets:
|
||||||
|
if preset["name"] == web_overclock_settings["preset"]:
|
||||||
|
active_preset = preset
|
||||||
|
|
||||||
return cls(
|
return cls(
|
||||||
active_preset=MiningPreset.from_vnish(active_preset),
|
active_preset=MiningPreset.from_vnish(active_preset or {}),
|
||||||
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
|
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> "MiningModePreset":
|
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> MiningModePreset:
|
||||||
active_preset = cls.get_active_preset_from_luxos(rpc_config, rpc_profiles)
|
active_preset = cls.get_active_preset_from_luxos(rpc_config, rpc_profiles)
|
||||||
return cls(
|
return cls(
|
||||||
active_preset=active_preset,
|
active_preset=active_preset,
|
||||||
@@ -434,7 +488,7 @@ class MiningModePreset(MinerConfigValue):
|
|||||||
for profile in rpc_profiles["PROFILES"]:
|
for profile in rpc_profiles["PROFILES"]:
|
||||||
if profile["Profile Name"] == active_profile:
|
if profile["Profile Name"] == active_profile:
|
||||||
active_preset = profile
|
active_preset = profile
|
||||||
return MiningPreset.from_luxos(active_preset)
|
return MiningPreset.from_luxos(active_preset or {})
|
||||||
|
|
||||||
|
|
||||||
class ManualBoardSettings(MinerConfigValue):
|
class ManualBoardSettings(MinerConfigValue):
|
||||||
@@ -442,7 +496,7 @@ class ManualBoardSettings(MinerConfigValue):
|
|||||||
volt: float
|
volt: float
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
|
def from_dict(cls, dict_conf: dict) -> ManualBoardSettings:
|
||||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -470,11 +524,15 @@ class MiningModeManual(MinerConfigValue):
|
|||||||
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
|
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
|
def from_dict(cls, dict_conf: dict) -> MiningModeManual:
|
||||||
return cls(
|
return cls(
|
||||||
global_freq=dict_conf["global_freq"],
|
global_freq=dict_conf["global_freq"],
|
||||||
global_volt=dict_conf["global_volt"],
|
global_volt=dict_conf["global_volt"],
|
||||||
boards={i: ManualBoardSettings.from_dict(dict_conf[i]) for i in dict_conf},
|
boards={
|
||||||
|
i: ManualBoardSettings.from_dict(dict_conf[i])
|
||||||
|
for i in dict_conf
|
||||||
|
if isinstance(i, int)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -498,7 +556,7 @@ class MiningModeManual(MinerConfigValue):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_overclock_settings: dict) -> "MiningModeManual":
|
def from_vnish(cls, web_overclock_settings: dict) -> MiningModeManual:
|
||||||
# will raise KeyError if it cant find the settings, values cannot be empty
|
# will raise KeyError if it cant find the settings, values cannot be empty
|
||||||
voltage = web_overclock_settings["globals"]["volt"]
|
voltage = web_overclock_settings["globals"]["volt"]
|
||||||
freq = web_overclock_settings["globals"]["freq"]
|
freq = web_overclock_settings["globals"]["freq"]
|
||||||
@@ -512,7 +570,7 @@ class MiningModeManual(MinerConfigValue):
|
|||||||
return cls(global_freq=freq, global_volt=voltage, boards=boards)
|
return cls(global_freq=freq, global_volt=voltage, boards=boards)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, epic_conf: dict) -> "MiningModeManual":
|
def from_epic(cls, epic_conf: dict) -> MiningModeManual:
|
||||||
voltage = 0
|
voltage = 0
|
||||||
freq = 0
|
freq = 0
|
||||||
if epic_conf.get("HwConfig") is not None:
|
if epic_conf.get("HwConfig") is not None:
|
||||||
@@ -552,11 +610,11 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
manual = MiningModeManual
|
manual = MiningModeManual
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default(cls):
|
def default(cls) -> MiningModeConfig:
|
||||||
return cls.normal()
|
return cls.normal()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None):
|
def from_dict(cls, dict_conf: dict | None) -> MiningModeConfig:
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@@ -564,12 +622,13 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
if mode is None:
|
if mode is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
cls_attr = getattr(cls, mode)
|
cls_attr = getattr(cls, mode, None)
|
||||||
if cls_attr is not None:
|
if cls_attr is not None:
|
||||||
return cls_attr().from_dict(dict_conf)
|
return cls_attr().from_dict(dict_conf)
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_conf: dict):
|
def from_am_modern(cls, web_conf: dict) -> MiningModeConfig:
|
||||||
if web_conf.get("bitmain-work-mode") is not None:
|
if web_conf.get("bitmain-work-mode") is not None:
|
||||||
work_mode = web_conf["bitmain-work-mode"]
|
work_mode = web_conf["bitmain-work-mode"]
|
||||||
if work_mode == "":
|
if work_mode == "":
|
||||||
@@ -583,7 +642,7 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hiveon_modern(cls, web_conf: dict):
|
def from_hiveon_modern(cls, web_conf: dict) -> MiningModeConfig:
|
||||||
if web_conf.get("bitmain-work-mode") is not None:
|
if web_conf.get("bitmain-work-mode") is not None:
|
||||||
work_mode = web_conf["bitmain-work-mode"]
|
work_mode = web_conf["bitmain-work-mode"]
|
||||||
if work_mode == "":
|
if work_mode == "":
|
||||||
@@ -597,7 +656,7 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_elphapex(cls, web_conf: dict):
|
def from_elphapex(cls, web_conf: dict) -> MiningModeConfig:
|
||||||
if web_conf.get("fc-work-mode") is not None:
|
if web_conf.get("fc-work-mode") is not None:
|
||||||
work_mode = web_conf["fc-work-mode"]
|
work_mode = web_conf["fc-work-mode"]
|
||||||
if work_mode == "":
|
if work_mode == "":
|
||||||
@@ -611,7 +670,7 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, web_conf: dict):
|
def from_epic(cls, web_conf: dict) -> MiningModeConfig:
|
||||||
try:
|
try:
|
||||||
tuner_running = web_conf["PerpetualTune"]["Running"]
|
tuner_running = web_conf["PerpetualTune"]["Running"]
|
||||||
if tuner_running:
|
if tuner_running:
|
||||||
@@ -650,12 +709,12 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
algo=TunerAlgo.chip_tune(),
|
algo=TunerAlgo.chip_tune(),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return MiningModeManual.from_epic(web_conf)
|
return cls.manual.from_epic(web_conf)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_conf: dict):
|
def from_bosminer(cls, toml_conf: dict) -> MiningModeConfig:
|
||||||
if toml_conf.get("autotuning") is None:
|
if toml_conf.get("autotuning") is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
autotuning_conf = toml_conf["autotuning"]
|
autotuning_conf = toml_conf["autotuning"]
|
||||||
@@ -695,19 +754,21 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
|
def from_vnish(
|
||||||
|
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
|
||||||
|
) -> MiningModeConfig:
|
||||||
try:
|
try:
|
||||||
mode_settings = web_settings["miner"]["overclock"]
|
mode_settings = web_settings["miner"]["overclock"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
if mode_settings["preset"] == "disabled":
|
if mode_settings["preset"] == "disabled":
|
||||||
return MiningModeManual.from_vnish(mode_settings)
|
return cls.manual.from_vnish(mode_settings, web_presets, web_perf_summary)
|
||||||
else:
|
else:
|
||||||
return MiningModePreset.from_vnish(mode_settings, web_presets)
|
return cls.preset.from_vnish(mode_settings, web_presets, web_perf_summary)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_miner_conf: dict):
|
def from_boser(cls, grpc_miner_conf: dict) -> MiningModeConfig:
|
||||||
try:
|
try:
|
||||||
tuner_conf = grpc_miner_conf["tuner"]
|
tuner_conf = grpc_miner_conf["tuner"]
|
||||||
if not tuner_conf.get("enabled", False):
|
if not tuner_conf.get("enabled", False):
|
||||||
@@ -753,7 +814,7 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_auradine(cls, web_mode: dict):
|
def from_auradine(cls, web_mode: dict) -> MiningModeConfig:
|
||||||
try:
|
try:
|
||||||
mode_data = web_mode["Mode"][0]
|
mode_data = web_mode["Mode"][0]
|
||||||
if mode_data.get("Sleep") == "on":
|
if mode_data.get("Sleep") == "on":
|
||||||
@@ -770,9 +831,33 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.power_tuning(power=mode_data["Power"])
|
return cls.power_tuning(power=mode_data["Power"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mara(cls, web_config: dict):
|
def from_btminer_v3(
|
||||||
|
cls, rpc_device_info: dict, rpc_settings: dict
|
||||||
|
) -> MiningModeConfig:
|
||||||
|
try:
|
||||||
|
is_mining = rpc_device_info["msg"]["miner"]["working"] == "true"
|
||||||
|
if not is_mining:
|
||||||
|
return cls.sleep()
|
||||||
|
power_limit = rpc_settings["msg"]["power-limit"]
|
||||||
|
if not power_limit == 0:
|
||||||
|
return cls.power_tuning(power=power_limit)
|
||||||
|
power_mode = rpc_settings["msg"]["power-mode"]
|
||||||
|
if power_mode == "normal":
|
||||||
|
return cls.normal()
|
||||||
|
if power_mode == "high":
|
||||||
|
return cls.high()
|
||||||
|
if power_mode == "low":
|
||||||
|
return cls.low()
|
||||||
|
|
||||||
|
except LookupError:
|
||||||
|
return cls.default()
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_mara(cls, web_config: dict) -> MiningModeConfig:
|
||||||
try:
|
try:
|
||||||
mode = web_config["mode"]["work-mode-selector"]
|
mode = web_config["mode"]["work-mode-selector"]
|
||||||
if mode == "Fixed":
|
if mode == "Fixed":
|
||||||
@@ -797,24 +882,26 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict):
|
def from_luxos(cls, rpc_config: dict, rpc_profiles: dict) -> MiningModeConfig:
|
||||||
preset_info = MiningModePreset.from_luxos(rpc_config, rpc_profiles)
|
preset_info = MiningModePreset.from_luxos(rpc_config, rpc_profiles)
|
||||||
return cls.preset(
|
return cls.preset(
|
||||||
active_preset=preset_info.active_preset,
|
active_preset=preset_info.active_preset,
|
||||||
available_presets=preset_info.available_presets,
|
available_presets=preset_info.available_presets,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def as_btminer_v3(self) -> dict:
|
||||||
|
"""Delegate to the default instance for btminer v3 configuration."""
|
||||||
|
return self.default().as_btminer_v3()
|
||||||
|
|
||||||
|
|
||||||
MiningMode = TypeVar(
|
MiningMode = TypeVar(
|
||||||
"MiningMode",
|
"MiningMode",
|
||||||
bound=Union[
|
bound=MiningModeNormal
|
||||||
MiningModeNormal,
|
| MiningModeHPM
|
||||||
MiningModeHPM,
|
| MiningModeLPM
|
||||||
MiningModeLPM,
|
| MiningModeSleep
|
||||||
MiningModeSleep,
|
| MiningModeManual
|
||||||
MiningModeManual,
|
| MiningModePowerTune
|
||||||
MiningModePowerTune,
|
| MiningModeHashrateTune
|
||||||
MiningModeHashrateTune,
|
| MiningModePreset,
|
||||||
MiningModePreset,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import field
|
from dataclasses import field
|
||||||
from typing import TypeVar, Union
|
from typing import Any, TypeVar
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
|
|
||||||
@@ -41,26 +41,26 @@ class TunerAlgo(MinerConfigOption):
|
|||||||
chip_tune = ChipTuneAlgo
|
chip_tune = ChipTuneAlgo
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default(cls) -> TunerAlgoType:
|
def default(cls) -> StandardTuneAlgo:
|
||||||
return cls.standard()
|
return cls.standard()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> TunerAlgoType:
|
def from_dict(
|
||||||
|
cls, dict_conf: dict[Any, Any] | None
|
||||||
|
) -> StandardTuneAlgo | VOptAlgo | BoardTuneAlgo | ChipTuneAlgo:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls.default()
|
||||||
mode = dict_conf.get("mode")
|
mode = dict_conf.get("mode")
|
||||||
if mode is None:
|
if mode is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
cls_attr = getattr(cls, mode)
|
cls_attr = getattr(cls, mode, None)
|
||||||
if cls_attr is not None:
|
if cls_attr is not None:
|
||||||
return cls_attr().from_dict(dict_conf)
|
return cls_attr().from_dict(dict_conf)
|
||||||
|
return cls.default()
|
||||||
|
|
||||||
|
|
||||||
TunerAlgoType = TypeVar(
|
TunerAlgoType = TypeVar(
|
||||||
"TunerAlgoType",
|
"TunerAlgoType",
|
||||||
bound=Union[
|
bound=StandardTuneAlgo | VOptAlgo | BoardTuneAlgo | ChipTuneAlgo,
|
||||||
StandardTuneAlgo,
|
|
||||||
VOptAlgo,
|
|
||||||
BoardTuneAlgo,
|
|
||||||
ChipTuneAlgo,
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ class ScalingShutdown(MinerConfigValue):
|
|||||||
duration: int | None = None
|
duration: int | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "ScalingShutdown":
|
def from_dict(cls, dict_conf: dict | None) -> ScalingShutdown:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
return cls(
|
return cls(
|
||||||
enabled=dict_conf.get("enabled", False), duration=dict_conf.get("duration")
|
enabled=dict_conf.get("enabled", False), duration=dict_conf.get("duration")
|
||||||
)
|
)
|
||||||
@@ -51,7 +53,7 @@ class ScalingShutdown(MinerConfigValue):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
cfg = {"shutdown_enabled": self.enabled}
|
cfg: dict[str, bool | int] = {"shutdown_enabled": self.enabled}
|
||||||
|
|
||||||
if self.duration is not None:
|
if self.duration is not None:
|
||||||
cfg["shutdown_duration"] = self.duration
|
cfg["shutdown_duration"] = self.duration
|
||||||
@@ -68,7 +70,9 @@ class ScalingConfig(MinerConfigValue):
|
|||||||
shutdown: ScalingShutdown | None = None
|
shutdown: ScalingShutdown | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "ScalingConfig":
|
def from_dict(cls, dict_conf: dict | None) -> ScalingConfig:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
cls_conf = {
|
cls_conf = {
|
||||||
"step": dict_conf.get("step"),
|
"step": dict_conf.get("step"),
|
||||||
"minimum": dict_conf.get("minimum"),
|
"minimum": dict_conf.get("minimum"),
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from typing import List
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import Field
|
from pydantic import Field
|
||||||
|
|
||||||
@@ -64,7 +64,17 @@ class Pool(MinerConfigValue):
|
|||||||
f"passwd_{idx}": self.password,
|
f"passwd_{idx}": self.password,
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_am_old(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||||
|
return {
|
||||||
|
"pool": self.url,
|
||||||
|
"worker": f"{self.user}{user_suffix or ''}",
|
||||||
|
"passwd": self.password,
|
||||||
|
}
|
||||||
|
|
||||||
|
def as_am_old(
|
||||||
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
|
idx = args[0] if args else kwargs.get("idx", 1)
|
||||||
return {
|
return {
|
||||||
f"_ant_pool{idx}url": self.url,
|
f"_ant_pool{idx}url": self.url,
|
||||||
f"_ant_pool{idx}user": f"{self.user}{user_suffix or ''}",
|
f"_ant_pool{idx}user": f"{self.user}{user_suffix or ''}",
|
||||||
@@ -81,7 +91,10 @@ class Pool(MinerConfigValue):
|
|||||||
def as_avalon(self, user_suffix: str | None = None) -> str:
|
def as_avalon(self, user_suffix: str | None = None) -> str:
|
||||||
return ",".join([self.url, f"{self.user}{user_suffix or ''}", self.password])
|
return ",".join([self.url, f"{self.user}{user_suffix or ''}", self.password])
|
||||||
|
|
||||||
def as_inno(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
def as_inno(
|
||||||
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
|
idx = args[0] if args else kwargs.get("idx", 1)
|
||||||
return {
|
return {
|
||||||
f"Pool{idx}": self.url,
|
f"Pool{idx}": self.url,
|
||||||
f"UserName{idx}": f"{self.user}{user_suffix or ''}",
|
f"UserName{idx}": f"{self.user}{user_suffix or ''}",
|
||||||
@@ -102,7 +115,7 @@ class Pool(MinerConfigValue):
|
|||||||
"pass": self.password,
|
"pass": self.password,
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_epic(self, user_suffix: str | None = None):
|
def as_epic(self, user_suffix: str | None = None) -> dict:
|
||||||
return {
|
return {
|
||||||
"pool": self.url,
|
"pool": self.url,
|
||||||
"login": f"{self.user}{user_suffix or ''}",
|
"login": f"{self.user}{user_suffix or ''}",
|
||||||
@@ -139,54 +152,60 @@ class Pool(MinerConfigValue):
|
|||||||
}
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
def from_dict(cls, dict_conf: dict | None) -> Pool:
|
||||||
|
if dict_conf is None:
|
||||||
|
raise ValueError("dict_conf cannot be None")
|
||||||
return cls(
|
return cls(
|
||||||
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, api_pool: dict) -> "Pool":
|
def from_api(cls, api_pool: dict) -> Pool:
|
||||||
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
|
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, api_pool: dict) -> "Pool":
|
def from_btminer_v3(cls, api_pool: dict) -> Pool:
|
||||||
|
return cls(url=api_pool["url"], user=api_pool["account"], password="x")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_epic(cls, api_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=api_pool["pool"], user=api_pool["login"], password=api_pool["password"]
|
url=api_pool["pool"], user=api_pool["login"], password=api_pool["password"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_pool: dict) -> "Pool":
|
def from_am_modern(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hiveon_modern(cls, web_pool: dict) -> "Pool":
|
def from_hiveon_modern(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_elphapex(cls, web_pool: dict) -> "Pool":
|
def from_elphapex(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: check if this is accurate, user/username, pass/password
|
# TODO: check if this is accurate, user/username, pass/password
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_goldshell(cls, web_pool: dict) -> "Pool":
|
def from_goldshell(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inno(cls, web_pool: dict) -> "Pool":
|
def from_inno(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_pool_conf: dict) -> "Pool":
|
def from_bosminer(cls, toml_pool_conf: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=toml_pool_conf["url"],
|
url=toml_pool_conf["url"],
|
||||||
user=toml_pool_conf["user"],
|
user=toml_pool_conf["user"],
|
||||||
@@ -194,7 +213,7 @@ class Pool(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_pool: dict) -> "Pool":
|
def from_vnish(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url="stratum+tcp://" + web_pool["url"],
|
url="stratum+tcp://" + web_pool["url"],
|
||||||
user=web_pool["user"],
|
user=web_pool["user"],
|
||||||
@@ -202,7 +221,7 @@ class Pool(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_pool: dict) -> "Pool":
|
def from_boser(cls, grpc_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=grpc_pool["url"],
|
url=grpc_pool["url"],
|
||||||
user=grpc_pool["user"],
|
user=grpc_pool["user"],
|
||||||
@@ -210,7 +229,7 @@ class Pool(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mara(cls, web_pool: dict) -> "Pool":
|
def from_mara(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["url"],
|
url=web_pool["url"],
|
||||||
user=web_pool["user"],
|
user=web_pool["user"],
|
||||||
@@ -218,7 +237,7 @@ class Pool(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_espminer(cls, web_system_info: dict) -> "Pool":
|
def from_espminer(cls, web_system_info: dict) -> Pool:
|
||||||
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
|
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
|
||||||
return cls(
|
return cls(
|
||||||
url=url,
|
url=url,
|
||||||
@@ -227,11 +246,11 @@ class Pool(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_luxos(cls, rpc_pools: dict) -> "Pool":
|
def from_luxos(cls, rpc_pools: dict) -> Pool:
|
||||||
return cls.from_api(rpc_pools)
|
return cls.from_api(rpc_pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_iceriver(cls, web_pool: dict) -> "Pool":
|
def from_iceriver(cls, web_pool: dict) -> Pool:
|
||||||
return cls(
|
return cls(
|
||||||
url=web_pool["addr"],
|
url=web_pool["addr"],
|
||||||
user=web_pool["user"],
|
user=web_pool["user"],
|
||||||
@@ -283,31 +302,32 @@ class PoolGroup(MinerConfigValue):
|
|||||||
idx += 1
|
idx += 1
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
def as_wm(self, *args: Any, user_suffix: str | None = None, **kwargs: Any) -> dict:
|
||||||
pools = {}
|
pools: dict[str, str] = {}
|
||||||
idx = 0
|
idx = 0
|
||||||
while idx < 3:
|
while idx < 3:
|
||||||
if len(self.pools) > idx:
|
if len(self.pools) > idx:
|
||||||
pools.update(
|
pools.update(**self.pools[idx].as_wm(idx + 1, user_suffix=user_suffix))
|
||||||
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
pools.update(**Pool(url="", user="", password="").as_wm(idx=idx + 1))
|
pools.update(**Pool(url="", user="", password="").as_wm(idx + 1))
|
||||||
idx += 1
|
idx += 1
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_am_old(self, user_suffix: str | None = None) -> dict:
|
def as_btminer_v3(self, user_suffix: str | None = None) -> list:
|
||||||
pools = {}
|
return [pool.as_btminer_v3(user_suffix) for pool in self.pools[:3]]
|
||||||
|
|
||||||
|
def as_am_old(
|
||||||
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
|
pools: dict[str, str] = {}
|
||||||
idx = 0
|
idx = 0
|
||||||
while idx < 3:
|
while idx < 3:
|
||||||
if len(self.pools) > idx:
|
if len(self.pools) > idx:
|
||||||
pools.update(
|
pools.update(
|
||||||
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
|
**self.pools[idx].as_am_old(idx + 1, user_suffix=user_suffix)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pools.update(
|
pools.update(**Pool(url="", user="", password="").as_am_old(idx + 1))
|
||||||
**Pool(url="", user="", password="").as_am_old(idx=idx + 1)
|
|
||||||
)
|
|
||||||
idx += 1
|
idx += 1
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
@@ -319,22 +339,24 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||||
return Pool(url="", user="", password="").as_avalon()
|
return Pool(url="", user="", password="").as_avalon()
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str | None = None) -> dict:
|
def as_inno(
|
||||||
pools = {}
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
|
pools: dict[str, str] = {}
|
||||||
idx = 0
|
idx = 0
|
||||||
while idx < 3:
|
while idx < 3:
|
||||||
if len(self.pools) > idx:
|
if len(self.pools) > idx:
|
||||||
pools.update(
|
pools.update(
|
||||||
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
|
**self.pools[idx].as_inno(idx + 1, user_suffix=user_suffix)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
pools.update(**Pool(url="", user="", password="").as_inno(idx=idx + 1))
|
pools.update(**Pool(url="", user="", password="").as_inno(idx + 1))
|
||||||
idx += 1
|
idx += 1
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_bosminer(self, user_suffix: str | None = None) -> dict:
|
def as_bosminer(self, user_suffix: str | None = None) -> dict:
|
||||||
if len(self.pools) > 0:
|
if len(self.pools) > 0:
|
||||||
conf = {
|
conf: dict[str, Any] = {
|
||||||
"name": self.name,
|
"name": self.name,
|
||||||
"pool": [
|
"pool": [
|
||||||
pool.as_bosminer(user_suffix=user_suffix) for pool in self.pools
|
pool.as_bosminer(user_suffix=user_suffix) for pool in self.pools
|
||||||
@@ -359,7 +381,7 @@ class PoolGroup(MinerConfigValue):
|
|||||||
|
|
||||||
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
|
def as_boser(self, user_suffix: str | None = None) -> PoolGroupConfiguration:
|
||||||
return PoolGroupConfiguration(
|
return PoolGroupConfiguration(
|
||||||
name=self.name,
|
name=self.name or "",
|
||||||
quota=Quota(value=self.quota),
|
quota=Quota(value=self.quota),
|
||||||
pools=[p.as_boser() for p in self.pools],
|
pools=[p.as_boser() for p in self.pools],
|
||||||
)
|
)
|
||||||
@@ -368,7 +390,10 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return {"pools": [p.as_vnish(user_suffix=user_suffix) for p in self.pools]}
|
return {"pools": [p.as_vnish(user_suffix=user_suffix) for p in self.pools]}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
def from_dict(cls, dict_conf: dict | None) -> PoolGroup:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
|
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
|
|
||||||
if dict_conf.get("quota") is not None:
|
if dict_conf.get("quota") is not None:
|
||||||
@@ -379,50 +404,57 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return cls(**cls_conf)
|
return cls(**cls_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, api_pool_list: list) -> "PoolGroup":
|
def from_api(cls, api_pool_list: list) -> PoolGroup:
|
||||||
pools = []
|
pools = []
|
||||||
for pool in api_pool_list:
|
for pool in api_pool_list:
|
||||||
pools.append(Pool.from_api(pool))
|
pools.append(Pool.from_api(pool))
|
||||||
return cls(pools=pools)
|
return cls(pools=pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, api_pool_list: list) -> "PoolGroup":
|
def from_btminer_v3(cls, api_pool_list: list) -> PoolGroup:
|
||||||
|
pools = []
|
||||||
|
for pool in api_pool_list:
|
||||||
|
pools.append(Pool.from_btminer_v3(pool))
|
||||||
|
return cls(pools=pools)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_epic(cls, api_pool_list: list) -> PoolGroup:
|
||||||
pools = []
|
pools = []
|
||||||
for pool in api_pool_list:
|
for pool in api_pool_list:
|
||||||
pools.append(Pool.from_epic(pool))
|
pools.append(Pool.from_epic(pool))
|
||||||
return cls(pools=pools)
|
return cls(pools=pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_pool_list: list) -> "PoolGroup":
|
def from_am_modern(cls, web_pool_list: list) -> PoolGroup:
|
||||||
pools = []
|
pools = []
|
||||||
for pool in web_pool_list:
|
for pool in web_pool_list:
|
||||||
pools.append(Pool.from_am_modern(pool))
|
pools.append(Pool.from_am_modern(pool))
|
||||||
return cls(pools=pools)
|
return cls(pools=pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hiveon_modern(cls, web_pool_list: list) -> "PoolGroup":
|
def from_hiveon_modern(cls, web_pool_list: list) -> PoolGroup:
|
||||||
pools = []
|
pools = []
|
||||||
for pool in web_pool_list:
|
for pool in web_pool_list:
|
||||||
pools.append(Pool.from_hiveon_modern(pool))
|
pools.append(Pool.from_hiveon_modern(pool))
|
||||||
return cls(pools=pools)
|
return cls(pools=pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_elphapex(cls, web_pool_list: list) -> "PoolGroup":
|
def from_elphapex(cls, web_pool_list: list) -> PoolGroup:
|
||||||
pools = []
|
pools = []
|
||||||
for pool in web_pool_list:
|
for pool in web_pool_list:
|
||||||
pools.append(Pool.from_elphapex(pool))
|
pools.append(Pool.from_elphapex(pool))
|
||||||
return cls(pools=pools)
|
return cls(pools=pools)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
def from_goldshell(cls, web_pools: list) -> PoolGroup:
|
||||||
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
|
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inno(cls, web_pools: list) -> "PoolGroup":
|
def from_inno(cls, web_pools: list) -> PoolGroup:
|
||||||
return cls(pools=[Pool.from_inno(p) for p in web_pools])
|
return cls(pools=[Pool.from_inno(p) for p in web_pools])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
|
def from_bosminer(cls, toml_group_conf: dict) -> PoolGroup:
|
||||||
if toml_group_conf.get("pool") is not None:
|
if toml_group_conf.get("pool") is not None:
|
||||||
return cls(
|
return cls(
|
||||||
name=toml_group_conf["name"],
|
name=toml_group_conf["name"],
|
||||||
@@ -432,13 +464,13 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
|
def from_vnish(cls, web_settings_pools: dict) -> PoolGroup:
|
||||||
return cls(
|
return cls(
|
||||||
pools=[Pool.from_vnish(p) for p in web_settings_pools if p["url"] != ""]
|
pools=[Pool.from_vnish(p) for p in web_settings_pools if p["url"] != ""]
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
|
def from_boser(cls, grpc_pool_group: dict) -> PoolGroup:
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
||||||
@@ -453,15 +485,15 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
|
def from_mara(cls, web_config_pools: dict) -> PoolGroup:
|
||||||
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_espminer(cls, web_system_info: dict) -> "PoolGroup":
|
def from_espminer(cls, web_system_info: dict) -> PoolGroup:
|
||||||
return cls(pools=[Pool.from_espminer(web_system_info)])
|
return cls(pools=[Pool.from_espminer(web_system_info)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolGroup":
|
def from_iceriver(cls, web_userpanel: dict) -> PoolGroup:
|
||||||
return cls(
|
return cls(
|
||||||
pools=[
|
pools=[
|
||||||
Pool.from_iceriver(web_pool)
|
Pool.from_iceriver(web_pool)
|
||||||
@@ -471,21 +503,21 @@ class PoolGroup(MinerConfigValue):
|
|||||||
|
|
||||||
|
|
||||||
class PoolConfig(MinerConfigValue):
|
class PoolConfig(MinerConfigValue):
|
||||||
groups: List[PoolGroup] = Field(default_factory=list)
|
groups: list[PoolGroup] = Field(default_factory=list)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def default(cls) -> "PoolConfig":
|
def default(cls) -> PoolConfig:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "PoolConfig":
|
def from_dict(cls, dict_conf: dict | None) -> PoolConfig:
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def simple(cls, pools: list[Pool | dict[str, str]]) -> "PoolConfig":
|
def simple(cls, pools: list[Pool | dict[str, str]]) -> PoolConfig:
|
||||||
group_pools = []
|
group_pools = []
|
||||||
for pool in pools:
|
for pool in pools:
|
||||||
if isinstance(pool, dict):
|
if isinstance(pool, dict):
|
||||||
@@ -508,12 +540,19 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
|
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
|
||||||
return {"pools": PoolGroup().as_elphapex()}
|
return {"pools": PoolGroup().as_elphapex()}
|
||||||
|
|
||||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
def as_wm(self, *args: Any, user_suffix: str | None = None, **kwargs: Any) -> dict:
|
||||||
if len(self.groups) > 0:
|
if len(self.groups) > 0:
|
||||||
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
|
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
|
||||||
return {"pools": PoolGroup().as_wm()}
|
return {"pools": PoolGroup().as_wm()}
|
||||||
|
|
||||||
def as_am_old(self, user_suffix: str | None = None) -> dict:
|
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
|
||||||
|
if len(self.groups) > 0:
|
||||||
|
return {"pools": self.groups[0].as_btminer_v3(user_suffix=user_suffix)}
|
||||||
|
return {"pools": PoolGroup().as_btminer_v3()}
|
||||||
|
|
||||||
|
def as_am_old(
|
||||||
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
if len(self.groups) > 0:
|
if len(self.groups) > 0:
|
||||||
return self.groups[0].as_am_old(user_suffix=user_suffix)
|
return self.groups[0].as_am_old(user_suffix=user_suffix)
|
||||||
return PoolGroup().as_am_old()
|
return PoolGroup().as_am_old()
|
||||||
@@ -528,7 +567,9 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)}
|
return {"pools": self.groups[0].as_avalon(user_suffix=user_suffix)}
|
||||||
return {"pools": PoolGroup().as_avalon()}
|
return {"pools": PoolGroup().as_avalon()}
|
||||||
|
|
||||||
def as_inno(self, user_suffix: str | None = None) -> dict:
|
def as_inno(
|
||||||
|
self, *args: Any, user_suffix: str | None = None, **kwargs: Any
|
||||||
|
) -> dict:
|
||||||
if len(self.groups) > 0:
|
if len(self.groups) > 0:
|
||||||
return self.groups[0].as_inno(user_suffix=user_suffix)
|
return self.groups[0].as_inno(user_suffix=user_suffix)
|
||||||
return PoolGroup().as_inno()
|
return PoolGroup().as_inno()
|
||||||
@@ -543,7 +584,7 @@ class PoolConfig(MinerConfigValue):
|
|||||||
def as_boser(self, user_suffix: str | None = None) -> dict:
|
def as_boser(self, user_suffix: str | None = None) -> dict:
|
||||||
return {
|
return {
|
||||||
"set_pool_groups": SetPoolGroupsRequest(
|
"set_pool_groups": SetPoolGroupsRequest(
|
||||||
save_action=SaveAction.SAVE_AND_APPLY,
|
save_action=SaveAction(SaveAction.SAVE_AND_APPLY),
|
||||||
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
pool_groups=[g.as_boser(user_suffix=user_suffix) for g in self.groups],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -589,7 +630,7 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return self.groups[0].as_vnish(user_suffix=user_suffix)
|
return self.groups[0].as_vnish(user_suffix=user_suffix)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
def from_api(cls, api_pools: dict) -> PoolConfig:
|
||||||
try:
|
try:
|
||||||
pool_data = api_pools["POOLS"]
|
pool_data = api_pools["POOLS"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -599,12 +640,22 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls(groups=[PoolGroup.from_api(pool_data)])
|
return cls(groups=[PoolGroup.from_api(pool_data)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, web_conf: dict) -> "PoolConfig":
|
def from_btminer_v3(cls, rpc_pools: dict) -> PoolConfig:
|
||||||
|
try:
|
||||||
|
pool_data = rpc_pools["pools"]
|
||||||
|
except KeyError:
|
||||||
|
return PoolConfig.default()
|
||||||
|
pool_data = sorted(pool_data, key=lambda x: int(x["id"]))
|
||||||
|
|
||||||
|
return cls(groups=[PoolGroup.from_btminer_v3(pool_data)])
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_epic(cls, web_conf: dict) -> PoolConfig:
|
||||||
pool_data = web_conf["StratumConfigs"]
|
pool_data = web_conf["StratumConfigs"]
|
||||||
return cls(groups=[PoolGroup.from_epic(pool_data)])
|
return cls(groups=[PoolGroup.from_epic(pool_data)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
def from_am_modern(cls, web_conf: dict) -> PoolConfig:
|
||||||
try:
|
try:
|
||||||
pool_data = web_conf["pools"]
|
pool_data = web_conf["pools"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -613,7 +664,7 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_hiveon_modern(cls, web_conf: dict) -> "PoolConfig":
|
def from_hiveon_modern(cls, web_conf: dict) -> PoolConfig:
|
||||||
try:
|
try:
|
||||||
pool_data = web_conf["pools"]
|
pool_data = web_conf["pools"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -622,35 +673,45 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls(groups=[PoolGroup.from_hiveon_modern(pool_data)])
|
return cls(groups=[PoolGroup.from_hiveon_modern(pool_data)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_elphapex(cls, web_conf: dict) -> "PoolConfig":
|
def from_elphapex(cls, web_conf: dict) -> PoolConfig:
|
||||||
pool_data = web_conf["pools"]
|
pool_data = web_conf["pools"]
|
||||||
|
|
||||||
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
|
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
def from_goldshell(cls, web_pools: list) -> PoolConfig:
|
||||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
def from_goldshell_byte(cls, web_pools: list) -> PoolConfig:
|
||||||
|
return cls(
|
||||||
|
groups=[
|
||||||
|
PoolGroup.from_goldshell(g["pools"])
|
||||||
|
for g in web_pools
|
||||||
|
if len(g["pools"]) > 0
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_inno(cls, web_pools: list) -> PoolConfig:
|
||||||
return cls(groups=[PoolGroup.from_inno(web_pools)])
|
return cls(groups=[PoolGroup.from_inno(web_pools)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
|
def from_bosminer(cls, toml_conf: dict) -> PoolConfig:
|
||||||
if toml_conf.get("group") is None:
|
if toml_conf.get("group") is None:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
|
def from_vnish(cls, web_settings: dict) -> PoolConfig:
|
||||||
try:
|
try:
|
||||||
return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
|
return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig":
|
def from_boser(cls, grpc_miner_conf: dict) -> PoolConfig:
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
groups=[
|
groups=[
|
||||||
@@ -662,19 +723,19 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mara(cls, web_config: dict) -> "PoolConfig":
|
def from_mara(cls, web_config: dict) -> PoolConfig:
|
||||||
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_espminer(cls, web_system_info: dict) -> "PoolConfig":
|
def from_espminer(cls, web_system_info: dict) -> PoolConfig:
|
||||||
return cls(groups=[PoolGroup.from_espminer(web_system_info)])
|
return cls(groups=[PoolGroup.from_espminer(web_system_info)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_iceriver(cls, web_userpanel: dict) -> "PoolConfig":
|
def from_iceriver(cls, web_userpanel: dict) -> PoolConfig:
|
||||||
return cls(groups=[PoolGroup.from_iceriver(web_userpanel)])
|
return cls(groups=[PoolGroup.from_iceriver(web_userpanel)])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_luxos(cls, rpc_groups: dict, rpc_pools: dict) -> "PoolConfig":
|
def from_luxos(cls, rpc_groups: dict, rpc_pools: dict) -> PoolConfig:
|
||||||
return cls(
|
return cls(
|
||||||
groups=[
|
groups=[
|
||||||
PoolGroup(
|
PoolGroup(
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return {"temp_control": temp_cfg}
|
return {"temp_control": temp_cfg}
|
||||||
|
|
||||||
def as_epic(self) -> dict:
|
def as_epic(self) -> dict:
|
||||||
temps_config = {"temps": {}, "fans": {"Auto": {}}}
|
temps_config: dict = {"temps": {}, "fans": {"Auto": {}}}
|
||||||
if self.target is not None:
|
if self.target is not None:
|
||||||
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
|
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
|
||||||
else:
|
else:
|
||||||
@@ -58,7 +58,9 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return {"misc": {"restart_temp": self.danger}}
|
return {"misc": {"restart_temp": self.danger}}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
|
def from_dict(cls, dict_conf: dict | None) -> TemperatureConfig:
|
||||||
|
if dict_conf is None:
|
||||||
|
return cls()
|
||||||
return cls(
|
return cls(
|
||||||
target=dict_conf.get("target"),
|
target=dict_conf.get("target"),
|
||||||
hot=dict_conf.get("hot"),
|
hot=dict_conf.get("hot"),
|
||||||
@@ -66,7 +68,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, toml_conf: dict) -> "TemperatureConfig":
|
def from_bosminer(cls, toml_conf: dict) -> TemperatureConfig:
|
||||||
temp_control = toml_conf.get("temp_control")
|
temp_control = toml_conf.get("temp_control")
|
||||||
if temp_control is not None:
|
if temp_control is not None:
|
||||||
return cls(
|
return cls(
|
||||||
@@ -77,7 +79,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
def from_epic(cls, web_conf: dict) -> TemperatureConfig:
|
||||||
try:
|
try:
|
||||||
dangerous_temp = web_conf["Misc"]["Critical Temp"]
|
dangerous_temp = web_conf["Misc"]["Critical Temp"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -95,7 +97,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
|
def from_vnish(cls, web_settings: dict) -> TemperatureConfig:
|
||||||
try:
|
try:
|
||||||
dangerous_temp = web_settings["misc"]["restart_temp"]
|
dangerous_temp = web_settings["misc"]["restart_temp"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -111,7 +113,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_miner_conf: dict) -> "TemperatureConfig":
|
def from_boser(cls, grpc_miner_conf: dict) -> TemperatureConfig:
|
||||||
try:
|
try:
|
||||||
temperature_conf = grpc_miner_conf["temperature"]
|
temperature_conf = grpc_miner_conf["temperature"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -142,7 +144,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_luxos(cls, rpc_tempctrl: dict) -> "TemperatureConfig":
|
def from_luxos(cls, rpc_tempctrl: dict) -> TemperatureConfig:
|
||||||
try:
|
try:
|
||||||
tempctrl_config = rpc_tempctrl["TEMPCTRL"][0]
|
tempctrl_config = rpc_tempctrl["TEMPCTRL"][0]
|
||||||
return cls(
|
return cls(
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import copy
|
import copy
|
||||||
import time
|
import time
|
||||||
|
from collections.abc import Callable
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
@@ -24,6 +25,7 @@ from pyasic.config import MinerConfig
|
|||||||
from pyasic.config.mining import MiningModePowerTune
|
from pyasic.config.mining import MiningModePowerTune
|
||||||
from pyasic.data.pools import PoolMetrics, Scheme
|
from pyasic.data.pools import PoolMetrics, Scheme
|
||||||
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
from pyasic.device.algorithm.hashrate import AlgoHashRateType
|
||||||
|
from pyasic.device.algorithm.hashrate.base import GenericHashrate
|
||||||
|
|
||||||
from .boards import HashBoard
|
from .boards import HashBoard
|
||||||
from .device import DeviceInfo
|
from .device import DeviceInfo
|
||||||
@@ -70,6 +72,7 @@ class MinerData(BaseModel):
|
|||||||
errors: A list of errors on the miner.
|
errors: A list of errors on the miner.
|
||||||
fault_light: Whether the fault light is on as a boolean.
|
fault_light: Whether the fault light is on as a boolean.
|
||||||
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
|
efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically.
|
||||||
|
efficiency_fract: Same as efficiency, but is not rounded to integer. Calculated automatically.
|
||||||
is_mining: Whether the miner is mining.
|
is_mining: Whether the miner is mining.
|
||||||
pools: A list of PoolMetrics instances, each representing metrics for a pool.
|
pools: A list of PoolMetrics instances, each representing metrics for a pool.
|
||||||
"""
|
"""
|
||||||
@@ -82,13 +85,16 @@ class MinerData(BaseModel):
|
|||||||
|
|
||||||
# about
|
# about
|
||||||
device_info: DeviceInfo | None = None
|
device_info: DeviceInfo | None = None
|
||||||
|
serial_number: str | None = None
|
||||||
mac: str | None = None
|
mac: str | None = None
|
||||||
api_ver: str | None = None
|
api_ver: str | None = None
|
||||||
fw_ver: str | None = None
|
fw_ver: str | None = None
|
||||||
hostname: str | None = None
|
hostname: str | None = None
|
||||||
|
|
||||||
# hashrate
|
# hashrate
|
||||||
raw_hashrate: AlgoHashRateType = Field(exclude=True, default=None, repr=False)
|
raw_hashrate: AlgoHashRateType | None = Field(
|
||||||
|
exclude=True, default=None, repr=False
|
||||||
|
)
|
||||||
|
|
||||||
# sticker
|
# sticker
|
||||||
sticker_hashrate: AlgoHashRateType | None = None
|
sticker_hashrate: AlgoHashRateType | None = None
|
||||||
@@ -192,7 +198,7 @@ class MinerData(BaseModel):
|
|||||||
setattr(cp, key, item & other_item)
|
setattr(cp, key, item & other_item)
|
||||||
return cp
|
return cp
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def hashrate(self) -> AlgoHashRateType | None:
|
def hashrate(self) -> AlgoHashRateType | None:
|
||||||
if len(self.hashboards) > 0:
|
if len(self.hashboards) > 0:
|
||||||
@@ -201,14 +207,24 @@ class MinerData(BaseModel):
|
|||||||
if item.hashrate is not None:
|
if item.hashrate is not None:
|
||||||
hr_data.append(item.hashrate)
|
hr_data.append(item.hashrate)
|
||||||
if len(hr_data) > 0:
|
if len(hr_data) > 0:
|
||||||
return sum(hr_data, start=self.device_info.algo.hashrate(rate=0))
|
if self.device_info is not None and self.device_info.algo is not None:
|
||||||
|
from pyasic.device.algorithm.hashrate.unit.base import GenericUnit
|
||||||
|
|
||||||
|
return sum(
|
||||||
|
hr_data,
|
||||||
|
start=self.device_info.algo.hashrate(
|
||||||
|
rate=0, unit=GenericUnit.H
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
return sum(hr_data, start=GenericHashrate(rate=0))
|
||||||
return self.raw_hashrate
|
return self.raw_hashrate
|
||||||
|
|
||||||
@hashrate.setter
|
@hashrate.setter
|
||||||
def hashrate(self, val):
|
def hashrate(self, val):
|
||||||
self.raw_hashrate = val
|
self.raw_hashrate = val
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def wattage_limit(self) -> int | None:
|
def wattage_limit(self) -> int | None:
|
||||||
if self.config is not None:
|
if self.config is not None:
|
||||||
@@ -220,7 +236,7 @@ class MinerData(BaseModel):
|
|||||||
def wattage_limit(self, val: int):
|
def wattage_limit(self, val: int):
|
||||||
self.raw_wattage_limit = val
|
self.raw_wattage_limit = val
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def total_chips(self) -> int | None:
|
def total_chips(self) -> int | None:
|
||||||
if len(self.hashboards) > 0:
|
if len(self.hashboards) > 0:
|
||||||
@@ -231,15 +247,16 @@ class MinerData(BaseModel):
|
|||||||
if len(chip_data) > 0:
|
if len(chip_data) > 0:
|
||||||
return sum(chip_data)
|
return sum(chip_data)
|
||||||
return None
|
return None
|
||||||
|
return 0
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def nominal(self) -> bool | None:
|
def nominal(self) -> bool | None:
|
||||||
if self.total_chips is None or self.expected_chips is None:
|
if self.total_chips is None or self.expected_chips is None:
|
||||||
return None
|
return None
|
||||||
return self.expected_chips == self.total_chips
|
return self.expected_chips == self.total_chips
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def percent_expected_chips(self) -> int | None:
|
def percent_expected_chips(self) -> int | None:
|
||||||
if self.total_chips is None or self.expected_chips is None:
|
if self.total_chips is None or self.expected_chips is None:
|
||||||
@@ -248,7 +265,7 @@ class MinerData(BaseModel):
|
|||||||
return 0
|
return 0
|
||||||
return round((self.total_chips / self.expected_chips) * 100)
|
return round((self.total_chips / self.expected_chips) * 100)
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def percent_expected_hashrate(self) -> int | None:
|
def percent_expected_hashrate(self) -> int | None:
|
||||||
if self.hashrate is None or self.expected_hashrate is None:
|
if self.hashrate is None or self.expected_hashrate is None:
|
||||||
@@ -258,7 +275,7 @@ class MinerData(BaseModel):
|
|||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def percent_expected_wattage(self) -> int | None:
|
def percent_expected_wattage(self) -> int | None:
|
||||||
if self.wattage_limit is None or self.wattage is None:
|
if self.wattage_limit is None or self.wattage is None:
|
||||||
@@ -268,10 +285,10 @@ class MinerData(BaseModel):
|
|||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def temperature_avg(self) -> int | None:
|
def temperature_avg(self) -> int | None:
|
||||||
total_temp = 0
|
total_temp: float = 0
|
||||||
temp_count = 0
|
temp_count = 0
|
||||||
for hb in self.hashboards:
|
for hb in self.hashboards:
|
||||||
if hb.temp is not None:
|
if hb.temp is not None:
|
||||||
@@ -281,49 +298,65 @@ class MinerData(BaseModel):
|
|||||||
return None
|
return None
|
||||||
return round(total_temp / temp_count)
|
return round(total_temp / temp_count)
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def efficiency(self) -> int | None:
|
def efficiency(self) -> int | None:
|
||||||
|
efficiency = self._efficiency(0)
|
||||||
|
if efficiency is None:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
return int(efficiency)
|
||||||
|
|
||||||
|
@computed_field # type: ignore[prop-decorator]
|
||||||
|
@property
|
||||||
|
def efficiency_fract(self) -> float | None:
|
||||||
|
return self._efficiency(2)
|
||||||
|
|
||||||
|
def _efficiency(self, ndigits: int) -> float | None:
|
||||||
if self.hashrate is None or self.wattage is None:
|
if self.hashrate is None or self.wattage is None:
|
||||||
return None
|
return None
|
||||||
try:
|
try:
|
||||||
return round(self.wattage / float(self.hashrate))
|
return round(self.wattage / float(self.hashrate), ndigits)
|
||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0
|
return 0.0
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def datetime(self) -> str:
|
def datetime(self) -> str:
|
||||||
return self.raw_datetime.isoformat()
|
return self.raw_datetime.isoformat()
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def timestamp(self) -> int:
|
def timestamp(self) -> int:
|
||||||
return int(time.mktime(self.raw_datetime.timetuple()))
|
return int(time.mktime(self.raw_datetime.timetuple()))
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def make(self) -> str | None:
|
def make(self) -> str | None:
|
||||||
if self.device_info.make is not None:
|
if self.device_info is not None and self.device_info.make is not None:
|
||||||
return str(self.device_info.make)
|
return str(self.device_info.make)
|
||||||
|
return ""
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def model(self) -> str | None:
|
def model(self) -> str | None:
|
||||||
if self.device_info.model is not None:
|
if self.device_info is not None and self.device_info.model is not None:
|
||||||
return str(self.device_info.model)
|
return str(self.device_info.model)
|
||||||
|
return ""
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def firmware(self) -> str | None:
|
def firmware(self) -> str | None:
|
||||||
if self.device_info.firmware is not None:
|
if self.device_info is not None and self.device_info.firmware is not None:
|
||||||
return str(self.device_info.firmware)
|
return str(self.device_info.firmware)
|
||||||
|
return ""
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def algo(self) -> str | None:
|
def algo(self) -> str | None:
|
||||||
if self.device_info.algo is not None:
|
if self.device_info is not None and self.device_info.algo is not None:
|
||||||
return str(self.device_info.algo)
|
return str(self.device_info.algo)
|
||||||
|
return ""
|
||||||
|
|
||||||
def keys(self) -> list:
|
def keys(self) -> list:
|
||||||
return list(self.model_fields.keys())
|
return list(self.model_fields.keys())
|
||||||
@@ -403,7 +436,8 @@ class MinerData(BaseModel):
|
|||||||
for dt in serialization_map_instance:
|
for dt in serialization_map_instance:
|
||||||
if item_serialized is None:
|
if item_serialized is None:
|
||||||
if isinstance(list_field_val, dt):
|
if isinstance(list_field_val, dt):
|
||||||
item_serialized = serialization_map_instance[dt](
|
func = serialization_map_instance[dt]
|
||||||
|
item_serialized = func(
|
||||||
f"{key}{level_delimiter}{idx}", list_field_val
|
f"{key}{level_delimiter}{idx}", list_field_val
|
||||||
)
|
)
|
||||||
if item_serialized is not None:
|
if item_serialized is not None:
|
||||||
@@ -447,11 +481,11 @@ class MinerData(BaseModel):
|
|||||||
"pools",
|
"pools",
|
||||||
]
|
]
|
||||||
|
|
||||||
serialization_map_instance = {
|
serialization_map_instance: dict[type, Callable[[str, Any], str | None]] = {
|
||||||
AlgoHashRateType: serialize_algo_hash_rate,
|
AlgoHashRateType: serialize_algo_hash_rate,
|
||||||
BaseMinerError: serialize_miner_error,
|
BaseMinerError: serialize_miner_error,
|
||||||
}
|
}
|
||||||
serialization_map = {
|
serialization_map: dict[type, Callable[[str, Any], str | None]] = {
|
||||||
int: serialize_int,
|
int: serialize_int,
|
||||||
float: serialize_float,
|
float: serialize_float,
|
||||||
str: serialize_str,
|
str: serialize_str,
|
||||||
@@ -485,9 +519,8 @@ class MinerData(BaseModel):
|
|||||||
for datatype in serialization_map_instance:
|
for datatype in serialization_map_instance:
|
||||||
if serialized is None:
|
if serialized is None:
|
||||||
if isinstance(field_val, datatype):
|
if isinstance(field_val, datatype):
|
||||||
serialized = serialization_map_instance[datatype](
|
func = serialization_map_instance[datatype]
|
||||||
field, field_val
|
serialized = func(field, field_val)
|
||||||
)
|
|
||||||
if serialized is not None:
|
if serialized is not None:
|
||||||
field_data.append(serialized)
|
field_data.append(serialized)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Callable
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
@@ -28,6 +29,8 @@ class HashBoard(BaseModel):
|
|||||||
Attributes:
|
Attributes:
|
||||||
slot: The slot of the board as an int.
|
slot: The slot of the board as an int.
|
||||||
hashrate: The hashrate of the board in TH/s as a float.
|
hashrate: The hashrate of the board in TH/s as a float.
|
||||||
|
inlet_temp: Inlet temperature for hydro asics as an int
|
||||||
|
outlet_temp: Outlet temperature for hydro asics as an int
|
||||||
temp: The temperature of the PCB as an int.
|
temp: The temperature of the PCB as an int.
|
||||||
chip_temp: The temperature of the chips as an int.
|
chip_temp: The temperature of the chips as an int.
|
||||||
chips: The chip count of the board as an int.
|
chips: The chip count of the board as an int.
|
||||||
@@ -41,6 +44,8 @@ class HashBoard(BaseModel):
|
|||||||
|
|
||||||
slot: int = 0
|
slot: int = 0
|
||||||
hashrate: AlgoHashRateType | None = None
|
hashrate: AlgoHashRateType | None = None
|
||||||
|
inlet_temp: float | None = None
|
||||||
|
outlet_temp: float | None = None
|
||||||
temp: float | None = None
|
temp: float | None = None
|
||||||
chip_temp: float | None = None
|
chip_temp: float | None = None
|
||||||
chips: int | None = None
|
chips: int | None = None
|
||||||
@@ -73,7 +78,6 @@ class HashBoard(BaseModel):
|
|||||||
raise KeyError(f"{item}")
|
raise KeyError(f"{item}")
|
||||||
|
|
||||||
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
||||||
|
|
||||||
def serialize_int(key: str, value: int) -> str:
|
def serialize_int(key: str, value: int) -> str:
|
||||||
return f"{key}={value}"
|
return f"{key}={value}"
|
||||||
|
|
||||||
@@ -86,7 +90,7 @@ class HashBoard(BaseModel):
|
|||||||
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
|
def serialize_algo_hash_rate(key: str, value: AlgoHashRateType) -> str:
|
||||||
return f"{key}={round(float(value), 2)}"
|
return f"{key}={round(float(value), 2)}"
|
||||||
|
|
||||||
def serialize_bool(key: str, value: bool):
|
def serialize_bool(key: str, value: bool) -> str:
|
||||||
return f"{key}={str(value).lower()}"
|
return f"{key}={str(value).lower()}"
|
||||||
|
|
||||||
serialization_map_instance = {
|
serialization_map_instance = {
|
||||||
@@ -113,8 +117,11 @@ class HashBoard(BaseModel):
|
|||||||
field_data = []
|
field_data = []
|
||||||
for field in include:
|
for field in include:
|
||||||
field_val = getattr(self, field)
|
field_val = getattr(self, field)
|
||||||
serialization_func = serialization_map.get(
|
serialization_func: Callable[[str, Any], str | None] = (
|
||||||
type(field_val), lambda _k, _v: None
|
serialization_map.get(
|
||||||
|
type(field_val),
|
||||||
|
lambda _k, _v: None, # type: ignore
|
||||||
|
)
|
||||||
)
|
)
|
||||||
serialized = serialization_func(
|
serialized = serialization_func(
|
||||||
f"{key_root}{level_delimiter}{field}", field_val
|
f"{key_root}{level_delimiter}{field}", field_val
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
|
|
||||||
class BaseMinerError(BaseModel):
|
class BaseMinerError(BaseModel):
|
||||||
|
error_code: int | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def fields(cls):
|
def fields(cls):
|
||||||
return list(cls.model_fields.keys())
|
return list(cls.model_fields.keys())
|
||||||
@@ -24,9 +26,13 @@ class BaseMinerError(BaseModel):
|
|||||||
field_data.append(
|
field_data.append(
|
||||||
f"{root_key}{level_delimiter}error_code={self.error_code}"
|
f"{root_key}{level_delimiter}error_code={self.error_code}"
|
||||||
)
|
)
|
||||||
if self.error_message is not None:
|
|
||||||
field_data.append(
|
# Check if error_message exists as an attribute (either regular or computed field)
|
||||||
f'{root_key}{level_delimiter}error_message="{self.error_message}"'
|
if hasattr(self, "error_message"):
|
||||||
)
|
error_message = getattr(self, "error_message")
|
||||||
|
if error_message is not None:
|
||||||
|
field_data.append(
|
||||||
|
f'{root_key}{level_delimiter}error_message="{error_message}"'
|
||||||
|
)
|
||||||
|
|
||||||
return ",".join(field_data)
|
return ",".join(field_data)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class InnosiliconError(BaseMinerError):
|
|||||||
|
|
||||||
error_code: int
|
error_code: int
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||||
if self.error_code in ERROR_CODES:
|
if self.error_code in ERROR_CODES:
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pydantic import computed_field
|
from pydantic import computed_field
|
||||||
|
|
||||||
from pyasic.data.error_codes.base import BaseMinerError
|
from pyasic.data.error_codes.base import BaseMinerError
|
||||||
@@ -28,50 +29,69 @@ class WhatsminerError(BaseMinerError):
|
|||||||
|
|
||||||
error_code: int
|
error_code: int
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||||
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
error_str = str(self.error_code)
|
||||||
err_type = int(str(self.error_code)[:2])
|
|
||||||
err_subtype = int(str(self.error_code)[2:3])
|
# Handle edge cases for short error codes
|
||||||
err_value = int(str(self.error_code)[3:])
|
if len(error_str) < 3:
|
||||||
|
return "Unknown error type."
|
||||||
|
|
||||||
|
if len(error_str) == 6 and not error_str[:1] == "1":
|
||||||
|
err_type = int(error_str[:2])
|
||||||
|
err_subtype = int(error_str[2:3])
|
||||||
|
err_value = int(error_str[3:])
|
||||||
else:
|
else:
|
||||||
err_type = int(str(self.error_code)[:-2])
|
err_type = int(error_str[:-2])
|
||||||
err_subtype = int(str(self.error_code)[-2:-1])
|
err_subtype = int(error_str[-2:-1])
|
||||||
err_value = int(str(self.error_code)[-1:])
|
err_value = int(error_str[-1:])
|
||||||
try:
|
try:
|
||||||
select_err_type = ERROR_CODES[err_type]
|
select_err_type = ERROR_CODES.get(err_type)
|
||||||
|
if select_err_type is None:
|
||||||
|
return "Unknown error type."
|
||||||
|
|
||||||
if err_subtype in select_err_type:
|
if err_subtype in select_err_type:
|
||||||
select_err_subtype = select_err_type[err_subtype]
|
select_err_subtype = select_err_type[err_subtype]
|
||||||
if err_value in select_err_subtype:
|
if isinstance(select_err_subtype, dict):
|
||||||
return select_err_subtype[err_value]
|
if err_value in select_err_subtype:
|
||||||
elif "n" in select_err_subtype:
|
result = select_err_subtype[err_value]
|
||||||
return select_err_subtype[
|
return str(result) if not isinstance(result, str) else result
|
||||||
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
elif "n" in select_err_subtype:
|
||||||
].replace("{n}", str(err_value))
|
template = select_err_subtype["n"]
|
||||||
|
if isinstance(template, str):
|
||||||
|
return template.replace("{n}", str(err_value))
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
else:
|
else:
|
||||||
return "Unknown error type."
|
return "Unknown error type."
|
||||||
elif "n" in select_err_type:
|
elif "n" in select_err_type:
|
||||||
select_err_subtype = select_err_type[
|
select_err_subtype = select_err_type["n"]
|
||||||
"n" # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
if isinstance(select_err_subtype, dict):
|
||||||
]
|
if err_value in select_err_subtype:
|
||||||
if err_value in select_err_subtype:
|
result = select_err_subtype[err_value]
|
||||||
return select_err_subtype[err_value]
|
return str(result) if not isinstance(result, str) else result
|
||||||
elif "c" in select_err_subtype:
|
elif "c" in select_err_subtype:
|
||||||
return (
|
template = select_err_subtype["c"]
|
||||||
select_err_subtype["c"]
|
if isinstance(template, str):
|
||||||
.replace( # noqa: picks up `select_err_subtype["n"]` as not being numeric?
|
return template.replace("{n}", str(err_subtype)).replace(
|
||||||
"{n}", str(err_subtype)
|
"{c}", str(err_value)
|
||||||
)
|
)
|
||||||
.replace("{c}", str(err_value))
|
else:
|
||||||
)
|
return "Unknown error type."
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
|
else:
|
||||||
|
return "Unknown error type."
|
||||||
else:
|
else:
|
||||||
return "Unknown error type."
|
return "Unknown error type."
|
||||||
except KeyError:
|
except (KeyError, TypeError):
|
||||||
return "Unknown error type."
|
return "Unknown error type."
|
||||||
|
|
||||||
|
|
||||||
ERROR_CODES = {
|
ERROR_CODES: dict[int, dict[int | str, str | dict[int | str, str]]] = {
|
||||||
1: { # Fan error
|
1: { # Fan error
|
||||||
0: {
|
0: {
|
||||||
0: "Fan unknown.",
|
0: "Fan unknown.",
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ class Fan(BaseModel):
|
|||||||
speed: The speed of the fan.
|
speed: The speed of the fan.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
speed: int = None
|
speed: int | None = None
|
||||||
|
|
||||||
def get(self, __key: str, default: Any = None):
|
def get(self, __key: str, default: Any = None):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
from collections.abc import Callable
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Optional
|
from typing import Any
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from pydantic import BaseModel, computed_field, model_serializer
|
from pydantic import BaseModel, computed_field, model_serializer
|
||||||
@@ -16,7 +17,7 @@ class PoolUrl(BaseModel):
|
|||||||
scheme: Scheme
|
scheme: Scheme
|
||||||
host: str
|
host: str
|
||||||
port: int
|
port: int
|
||||||
pubkey: Optional[str] = None
|
pubkey: str | None = None
|
||||||
|
|
||||||
@model_serializer
|
@model_serializer
|
||||||
def serialize(self):
|
def serialize(self):
|
||||||
@@ -39,6 +40,8 @@ class PoolUrl(BaseModel):
|
|||||||
scheme = Scheme.STRATUM_V1
|
scheme = Scheme.STRATUM_V1
|
||||||
host = parsed_url.hostname
|
host = parsed_url.hostname
|
||||||
port = parsed_url.port
|
port = parsed_url.port
|
||||||
|
if port is None:
|
||||||
|
return None
|
||||||
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
||||||
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
||||||
|
|
||||||
@@ -70,16 +73,20 @@ class PoolMetrics(BaseModel):
|
|||||||
index: int | None = None
|
index: int | None = None
|
||||||
user: str | None = None
|
user: str | None = None
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
|
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||||
"""Calculate and return the percentage of rejected shares"""
|
"""Calculate and return the percentage of rejected shares"""
|
||||||
|
if self.rejected is None or self.accepted is None:
|
||||||
|
return 0.0
|
||||||
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
|
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
|
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||||
"""Calculate and return the percentage of stale shares."""
|
"""Calculate and return the percentage of stale shares."""
|
||||||
|
if self.get_failures is None or self.accepted is None or self.rejected is None:
|
||||||
|
return 0.0
|
||||||
return self._calculate_percentage(
|
return self._calculate_percentage(
|
||||||
self.get_failures, self.accepted + self.rejected
|
self.get_failures, self.accepted + self.rejected
|
||||||
)
|
)
|
||||||
@@ -87,14 +94,11 @@ class PoolMetrics(BaseModel):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _calculate_percentage(value: int, total: int) -> float:
|
def _calculate_percentage(value: int, total: int) -> float:
|
||||||
"""Calculate the percentage."""
|
"""Calculate the percentage."""
|
||||||
if value is None or total is None:
|
|
||||||
return 0
|
|
||||||
if total == 0:
|
if total == 0:
|
||||||
return 0
|
return 0.0
|
||||||
return (value / total) * 100
|
return (value / total) * 100
|
||||||
|
|
||||||
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
|
||||||
|
|
||||||
def serialize_int(key: str, value: int) -> str:
|
def serialize_int(key: str, value: int) -> str:
|
||||||
return f"{key}={value}"
|
return f"{key}={value}"
|
||||||
|
|
||||||
@@ -104,13 +108,13 @@ class PoolMetrics(BaseModel):
|
|||||||
def serialize_str(key: str, value: str) -> str:
|
def serialize_str(key: str, value: str) -> str:
|
||||||
return f'{key}="{value}"'
|
return f'{key}="{value}"'
|
||||||
|
|
||||||
def serialize_pool_url(key: str, value: str) -> str:
|
def serialize_pool_url(key: str, value: PoolUrl) -> str:
|
||||||
return f'{key}="{str(value)}"'
|
return f'{key}="{str(value)}"'
|
||||||
|
|
||||||
def serialize_bool(key: str, value: bool):
|
def serialize_bool(key: str, value: bool) -> str:
|
||||||
return f"{key}={str(value).lower()}"
|
return f"{key}={str(value).lower()}"
|
||||||
|
|
||||||
serialization_map = {
|
serialization_map: dict[type, Callable[[str, Any], str]] = {
|
||||||
int: serialize_int,
|
int: serialize_int,
|
||||||
float: serialize_float,
|
float: serialize_float,
|
||||||
str: serialize_str,
|
str: serialize_str,
|
||||||
@@ -130,13 +134,14 @@ class PoolMetrics(BaseModel):
|
|||||||
field_data = []
|
field_data = []
|
||||||
for field in include:
|
for field in include:
|
||||||
field_val = getattr(self, field)
|
field_val = getattr(self, field)
|
||||||
serialization_func = serialization_map.get(
|
if field_val is None:
|
||||||
type(field_val), lambda _k, _v: None
|
continue
|
||||||
)
|
serialization_func = serialization_map.get(type(field_val))
|
||||||
serialized = serialization_func(
|
if serialization_func is not None:
|
||||||
f"{key_root}{level_delimiter}{field}", field_val
|
serialized = serialization_func(
|
||||||
)
|
f"{key_root}{level_delimiter}{field}", field_val
|
||||||
if serialized is not None:
|
)
|
||||||
field_data.append(serialized)
|
if serialized is not None:
|
||||||
|
field_data.append(serialized)
|
||||||
|
|
||||||
return ",".join(field_data)
|
return ",".join(field_data)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from .base import MinerAlgoType
|
from .base import MinerAlgoType
|
||||||
from .blake256 import Blake256Algo
|
from .blake256 import Blake256Algo
|
||||||
|
from .blockflow import BlockFlowAlgo
|
||||||
from .eaglesong import EaglesongAlgo
|
from .eaglesong import EaglesongAlgo
|
||||||
from .equihash import EquihashAlgo
|
from .equihash import EquihashAlgo
|
||||||
from .ethash import EtHashAlgo
|
from .ethash import EtHashAlgo
|
||||||
@@ -11,6 +12,7 @@ from .kheavyhash import KHeavyHashAlgo
|
|||||||
from .scrypt import ScryptAlgo
|
from .scrypt import ScryptAlgo
|
||||||
from .sha256 import SHA256Algo
|
from .sha256 import SHA256Algo
|
||||||
from .x11 import X11Algo
|
from .x11 import X11Algo
|
||||||
|
from .zksnark import ZkSnarkAlgo
|
||||||
|
|
||||||
|
|
||||||
class MinerAlgo:
|
class MinerAlgo:
|
||||||
@@ -24,3 +26,5 @@ class MinerAlgo:
|
|||||||
EAGLESONG = EaglesongAlgo
|
EAGLESONG = EaglesongAlgo
|
||||||
ETHASH = EtHashAlgo
|
ETHASH = EtHashAlgo
|
||||||
EQUIHASH = EquihashAlgo
|
EQUIHASH = EquihashAlgo
|
||||||
|
BLOCKFLOW = BlockFlowAlgo
|
||||||
|
ZKSNARK = ZkSnarkAlgo
|
||||||
|
|||||||
12
pyasic/device/algorithm/blockflow.py
Normal file
12
pyasic/device/algorithm/blockflow.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .base import MinerAlgoType
|
||||||
|
from .hashrate import BlockFlowHashRate
|
||||||
|
from .hashrate.unit import BlockFlowUnit
|
||||||
|
|
||||||
|
|
||||||
|
class BlockFlowAlgo(MinerAlgoType):
|
||||||
|
hashrate: type[BlockFlowHashRate] = BlockFlowHashRate
|
||||||
|
unit: type[BlockFlowUnit] = BlockFlowUnit
|
||||||
|
|
||||||
|
name = "BlockFlow"
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from .base import AlgoHashRateType
|
from .base import AlgoHashRateType
|
||||||
from .blake256 import Blake256HashRate
|
from .blake256 import Blake256HashRate
|
||||||
|
from .blockflow import BlockFlowHashRate
|
||||||
from .eaglesong import EaglesongHashRate
|
from .eaglesong import EaglesongHashRate
|
||||||
from .equihash import EquihashHashRate
|
from .equihash import EquihashHashRate
|
||||||
from .ethash import EtHashHashRate
|
from .ethash import EtHashHashRate
|
||||||
@@ -9,6 +10,7 @@ from .kheavyhash import KHeavyHashHashRate
|
|||||||
from .scrypt import ScryptHashRate
|
from .scrypt import ScryptHashRate
|
||||||
from .sha256 import SHA256HashRate
|
from .sha256 import SHA256HashRate
|
||||||
from .x11 import X11HashRate
|
from .x11 import X11HashRate
|
||||||
|
from .zksnark import ZkSnarkHashRate
|
||||||
|
|
||||||
|
|
||||||
class AlgoHashRate:
|
class AlgoHashRate:
|
||||||
@@ -22,3 +24,5 @@ class AlgoHashRate:
|
|||||||
EAGLESONG = EaglesongHashRate
|
EAGLESONG = EaglesongHashRate
|
||||||
ETHASH = EtHashHashRate
|
ETHASH = EtHashHashRate
|
||||||
EQUIHASH = EquihashHashRate
|
EQUIHASH = EquihashHashRate
|
||||||
|
BLOCKFLOW = BlockFlowHashRate
|
||||||
|
ZKSNARK = ZkSnarkHashRate
|
||||||
|
|||||||
@@ -1,23 +1,26 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
|
from typing import Generic, TypeVar
|
||||||
|
|
||||||
from pydantic import BaseModel, field_serializer
|
from pydantic import BaseModel, field_serializer
|
||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from .unit.base import AlgoHashRateUnitType, GenericUnit
|
from .unit.base import AlgoHashRateUnitType, GenericUnit
|
||||||
|
|
||||||
|
UnitType = TypeVar("UnitType", bound=AlgoHashRateUnitType)
|
||||||
|
|
||||||
class AlgoHashRateType(BaseModel, ABC):
|
|
||||||
unit: AlgoHashRateUnitType
|
class AlgoHashRateType(BaseModel, ABC, Generic[UnitType]):
|
||||||
|
unit: UnitType
|
||||||
rate: float
|
rate: float
|
||||||
|
|
||||||
@field_serializer("unit")
|
@field_serializer("unit")
|
||||||
def serialize_unit(self, unit: AlgoHashRateUnitType):
|
def serialize_unit(self, unit: UnitType):
|
||||||
return unit.model_dump()
|
return unit.model_dump()
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def into(self, other: "AlgoHashRateUnitType"):
|
def into(self, other: UnitType) -> Self:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def auto_unit(self):
|
def auto_unit(self):
|
||||||
@@ -46,7 +49,7 @@ class AlgoHashRateType(BaseModel, ABC):
|
|||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.rate} {str(self.unit)}"
|
return f"{self.rate} {str(self.unit)}"
|
||||||
|
|
||||||
def __round__(self, n: int = None):
|
def __round__(self, n: int | None = None):
|
||||||
return round(self.rate, n)
|
return round(self.rate, n)
|
||||||
|
|
||||||
def __add__(self, other: Self | int | float) -> Self:
|
def __add__(self, other: Self | int | float) -> Self:
|
||||||
@@ -85,11 +88,11 @@ class AlgoHashRateType(BaseModel, ABC):
|
|||||||
return self.__class__(rate=self.rate * other, unit=self.unit)
|
return self.__class__(rate=self.rate * other, unit=self.unit)
|
||||||
|
|
||||||
|
|
||||||
class GenericHashrate(AlgoHashRateType):
|
class GenericHashrate(AlgoHashRateType[GenericUnit]):
|
||||||
rate: float = 0
|
rate: float = 0
|
||||||
unit: GenericUnit = GenericUnit.H
|
unit: GenericUnit = GenericUnit.H
|
||||||
|
|
||||||
def into(self, other: GenericUnit):
|
def into(self, other: GenericUnit) -> Self:
|
||||||
return self.__class__(
|
return self.__class__(
|
||||||
rate=self.rate / (other.value / self.unit.value), unit=other
|
rate=self.rate / (other.value / self.unit.value), unit=other
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.blake256 import Blake256Unit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class Blake256HashRate(AlgoHashRateType):
|
class Blake256HashRate(AlgoHashRateType[Blake256Unit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: Blake256Unit = HashUnit.BLAKE256.default
|
unit: Blake256Unit = HashUnit.BLAKE256.default
|
||||||
|
|
||||||
|
|||||||
18
pyasic/device/algorithm/hashrate/blockflow.py
Normal file
18
pyasic/device/algorithm/hashrate/blockflow.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
|
||||||
|
from pyasic.device.algorithm.hashrate.unit.blockflow import BlockFlowUnit
|
||||||
|
|
||||||
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
|
class BlockFlowHashRate(AlgoHashRateType[BlockFlowUnit]):
|
||||||
|
rate: float
|
||||||
|
unit: BlockFlowUnit = HashUnit.BLOCKFLOW.default
|
||||||
|
|
||||||
|
def into(self, other: BlockFlowUnit) -> Self:
|
||||||
|
return self.__class__(
|
||||||
|
rate=self.rate / (other.value / self.unit.value), unit=other
|
||||||
|
)
|
||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.eaglesong import EaglesongUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class EaglesongHashRate(AlgoHashRateType):
|
class EaglesongHashRate(AlgoHashRateType[EaglesongUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: EaglesongUnit = HashUnit.EAGLESONG.default
|
unit: EaglesongUnit = HashUnit.EAGLESONG.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ from pyasic.device.algorithm.hashrate.unit.equihash import EquihashUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class EquihashHashRate(AlgoHashRateType):
|
class EquihashHashRate(AlgoHashRateType[EquihashUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: EquihashUnit = HashUnit.ETHASH.default
|
unit: EquihashUnit = HashUnit.EQUIHASH.default
|
||||||
|
|
||||||
def into(self, other: EquihashUnit) -> Self:
|
def into(self, other: EquihashUnit) -> Self:
|
||||||
return self.__class__(
|
return self.__class__(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.ethash import EtHashUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class EtHashHashRate(AlgoHashRateType):
|
class EtHashHashRate(AlgoHashRateType[EtHashUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: EtHashUnit = HashUnit.ETHASH.default
|
unit: EtHashUnit = HashUnit.ETHASH.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.handshake import HandshakeUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class HandshakeHashRate(AlgoHashRateType):
|
class HandshakeHashRate(AlgoHashRateType[HandshakeUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: HandshakeUnit = HashUnit.HANDSHAKE.default
|
unit: HandshakeUnit = HashUnit.HANDSHAKE.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.kadena import KadenaUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class KadenaHashRate(AlgoHashRateType):
|
class KadenaHashRate(AlgoHashRateType[KadenaUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: KadenaUnit = HashUnit.KADENA.default
|
unit: KadenaUnit = HashUnit.KADENA.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.kheavyhash import KHeavyHashUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class KHeavyHashHashRate(AlgoHashRateType):
|
class KHeavyHashHashRate(AlgoHashRateType[KHeavyHashUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: KHeavyHashUnit = HashUnit.KHEAVYHASH.default
|
unit: KHeavyHashUnit = HashUnit.KHEAVYHASH.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.scrypt import ScryptUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class ScryptHashRate(AlgoHashRateType):
|
class ScryptHashRate(AlgoHashRateType[ScryptUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: ScryptUnit = HashUnit.SCRYPT.default
|
unit: ScryptUnit = HashUnit.SCRYPT.default
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.sha256 import SHA256Unit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class SHA256HashRate(AlgoHashRateType):
|
class SHA256HashRate(AlgoHashRateType[SHA256Unit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: SHA256Unit = HashUnit.SHA256.default
|
unit: SHA256Unit = HashUnit.SHA256.default
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from .blake256 import Blake256Unit
|
from .blake256 import Blake256Unit
|
||||||
|
from .blockflow import BlockFlowUnit
|
||||||
from .eaglesong import EaglesongUnit
|
from .eaglesong import EaglesongUnit
|
||||||
from .equihash import EquihashUnit
|
from .equihash import EquihashUnit
|
||||||
from .ethash import EtHashUnit
|
from .ethash import EtHashUnit
|
||||||
@@ -8,6 +9,7 @@ from .kheavyhash import KHeavyHashUnit
|
|||||||
from .scrypt import ScryptUnit
|
from .scrypt import ScryptUnit
|
||||||
from .sha256 import SHA256Unit
|
from .sha256 import SHA256Unit
|
||||||
from .x11 import X11Unit
|
from .x11 import X11Unit
|
||||||
|
from .zksnark import ZkSnarkUnit
|
||||||
|
|
||||||
|
|
||||||
class HashUnit:
|
class HashUnit:
|
||||||
@@ -21,3 +23,5 @@ class HashUnit:
|
|||||||
EAGLESONG = EaglesongUnit
|
EAGLESONG = EaglesongUnit
|
||||||
ETHASH = EtHashUnit
|
ETHASH = EtHashUnit
|
||||||
EQUIHASH = EquihashUnit
|
EQUIHASH = EquihashUnit
|
||||||
|
BLOCKFLOW = BlockFlowUnit
|
||||||
|
ZKSNARK = ZkSnarkUnit
|
||||||
|
|||||||
@@ -2,54 +2,46 @@ from enum import IntEnum
|
|||||||
|
|
||||||
|
|
||||||
class AlgoHashRateUnitType(IntEnum):
|
class AlgoHashRateUnitType(IntEnum):
|
||||||
H: int
|
|
||||||
KH: int
|
|
||||||
MH: int
|
|
||||||
GH: int
|
|
||||||
TH: int
|
|
||||||
PH: int
|
|
||||||
EH: int
|
|
||||||
ZH: int
|
|
||||||
|
|
||||||
default: int
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
if self.value == self.H:
|
if hasattr(self.__class__, "H") and self.value == self.__class__.H:
|
||||||
return "H/s"
|
return "H/s"
|
||||||
if self.value == self.KH:
|
if hasattr(self.__class__, "KH") and self.value == self.__class__.KH:
|
||||||
return "KH/s"
|
return "KH/s"
|
||||||
if self.value == self.MH:
|
if hasattr(self.__class__, "MH") and self.value == self.__class__.MH:
|
||||||
return "MH/s"
|
return "MH/s"
|
||||||
if self.value == self.GH:
|
if hasattr(self.__class__, "GH") and self.value == self.__class__.GH:
|
||||||
return "GH/s"
|
return "GH/s"
|
||||||
if self.value == self.TH:
|
if hasattr(self.__class__, "TH") and self.value == self.__class__.TH:
|
||||||
return "TH/s"
|
return "TH/s"
|
||||||
if self.value == self.PH:
|
if hasattr(self.__class__, "PH") and self.value == self.__class__.PH:
|
||||||
return "PH/s"
|
return "PH/s"
|
||||||
if self.value == self.EH:
|
if hasattr(self.__class__, "EH") and self.value == self.__class__.EH:
|
||||||
return "EH/s"
|
return "EH/s"
|
||||||
if self.value == self.ZH:
|
if hasattr(self.__class__, "ZH") and self.value == self.__class__.ZH:
|
||||||
return "ZH/s"
|
return "ZH/s"
|
||||||
|
return ""
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_str(cls, value: str):
|
def from_str(cls, value: str):
|
||||||
if value == "H":
|
if value == "H" and hasattr(cls, "H"):
|
||||||
return cls.H
|
return cls.H
|
||||||
elif value == "KH":
|
elif value == "KH" and hasattr(cls, "KH"):
|
||||||
return cls.KH
|
return cls.KH
|
||||||
elif value == "MH":
|
elif value == "MH" and hasattr(cls, "MH"):
|
||||||
return cls.MH
|
return cls.MH
|
||||||
elif value == "GH":
|
elif value == "GH" and hasattr(cls, "GH"):
|
||||||
return cls.GH
|
return cls.GH
|
||||||
elif value == "TH":
|
elif value == "TH" and hasattr(cls, "TH"):
|
||||||
return cls.TH
|
return cls.TH
|
||||||
elif value == "PH":
|
elif value == "PH" and hasattr(cls, "PH"):
|
||||||
return cls.PH
|
return cls.PH
|
||||||
elif value == "EH":
|
elif value == "EH" and hasattr(cls, "EH"):
|
||||||
return cls.EH
|
return cls.EH
|
||||||
elif value == "ZH":
|
elif value == "ZH" and hasattr(cls, "ZH"):
|
||||||
return cls.ZH
|
return cls.ZH
|
||||||
return cls.default
|
if hasattr(cls, "default"):
|
||||||
|
return cls.default
|
||||||
|
return None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|||||||
16
pyasic/device/algorithm/hashrate/unit/blockflow.py
Normal file
16
pyasic/device/algorithm/hashrate/unit/blockflow.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .base import AlgoHashRateUnitType
|
||||||
|
|
||||||
|
|
||||||
|
class BlockFlowUnit(AlgoHashRateUnitType):
|
||||||
|
H = 1
|
||||||
|
KH = int(H) * 1000
|
||||||
|
MH = int(KH) * 1000
|
||||||
|
GH = int(MH) * 1000
|
||||||
|
TH = int(GH) * 1000
|
||||||
|
PH = int(TH) * 1000
|
||||||
|
EH = int(PH) * 1000
|
||||||
|
ZH = int(EH) * 1000
|
||||||
|
|
||||||
|
default = MH
|
||||||
16
pyasic/device/algorithm/hashrate/unit/zksnark.py
Normal file
16
pyasic/device/algorithm/hashrate/unit/zksnark.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .base import AlgoHashRateUnitType
|
||||||
|
|
||||||
|
|
||||||
|
class ZkSnarkUnit(AlgoHashRateUnitType):
|
||||||
|
H = 1
|
||||||
|
KH = int(H) * 1000
|
||||||
|
MH = int(KH) * 1000
|
||||||
|
GH = int(MH) * 1000
|
||||||
|
TH = int(GH) * 1000
|
||||||
|
PH = int(TH) * 1000
|
||||||
|
EH = int(PH) * 1000
|
||||||
|
ZH = int(EH) * 1000
|
||||||
|
|
||||||
|
default = GH
|
||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.x11 import X11Unit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class X11HashRate(AlgoHashRateType):
|
class X11HashRate(AlgoHashRateType[X11Unit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: X11Unit = HashUnit.X11.default
|
unit: X11Unit = HashUnit.X11.default
|
||||||
|
|
||||||
|
|||||||
18
pyasic/device/algorithm/hashrate/zksnark.py
Normal file
18
pyasic/device/algorithm/hashrate/zksnark.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
|
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
|
||||||
|
from pyasic.device.algorithm.hashrate.unit.zksnark import ZkSnarkUnit
|
||||||
|
|
||||||
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
|
class ZkSnarkHashRate(AlgoHashRateType[ZkSnarkUnit]):
|
||||||
|
rate: float
|
||||||
|
unit: ZkSnarkUnit = HashUnit.ZKSNARK.default
|
||||||
|
|
||||||
|
def into(self, other: ZkSnarkUnit) -> Self:
|
||||||
|
return self.__class__(
|
||||||
|
rate=self.rate / (other.value / self.unit.value), unit=other
|
||||||
|
)
|
||||||
12
pyasic/device/algorithm/zksnark.py
Normal file
12
pyasic/device/algorithm/zksnark.py
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from .base import MinerAlgoType
|
||||||
|
from .hashrate import ZkSnarkHashRate
|
||||||
|
from .hashrate.unit import ZkSnarkUnit
|
||||||
|
|
||||||
|
|
||||||
|
class ZkSnarkAlgo(MinerAlgoType):
|
||||||
|
hashrate: type[ZkSnarkHashRate] = ZkSnarkHashRate
|
||||||
|
unit: type[ZkSnarkUnit] = ZkSnarkUnit
|
||||||
|
|
||||||
|
name = "zkSNARK"
|
||||||
@@ -43,6 +43,7 @@ class AntminerModels(MinerModelType):
|
|||||||
S19jNoPIC = "S19j No PIC"
|
S19jNoPIC = "S19j No PIC"
|
||||||
S19ProPlus = "S19 Pro+"
|
S19ProPlus = "S19 Pro+"
|
||||||
S19jPro = "S19j Pro"
|
S19jPro = "S19j Pro"
|
||||||
|
S19jPlus = "S19j+"
|
||||||
S19jProNoPIC = "S19j Pro No PIC"
|
S19jProNoPIC = "S19j Pro No PIC"
|
||||||
S19jProPlus = "S19j Pro+"
|
S19jProPlus = "S19j Pro+"
|
||||||
S19jProPlusNoPIC = "S19j Pro+ No PIC"
|
S19jProPlusNoPIC = "S19j Pro+ No PIC"
|
||||||
@@ -54,14 +55,17 @@ class AntminerModels(MinerModelType):
|
|||||||
S19ProPlusHydro = "S19 Pro+ Hydro"
|
S19ProPlusHydro = "S19 Pro+ Hydro"
|
||||||
S19KPro = "S19K Pro"
|
S19KPro = "S19K Pro"
|
||||||
S19kPro = "S19k Pro"
|
S19kPro = "S19k Pro"
|
||||||
|
S19ProA = "S19 Pro A"
|
||||||
S19kProNoPIC = "S19k Pro No PIC"
|
S19kProNoPIC = "S19k Pro No PIC"
|
||||||
S19jXP = "S19j XP"
|
S19jXP = "S19j XP"
|
||||||
T19 = "T19"
|
T19 = "T19"
|
||||||
S21 = "S21"
|
S21 = "S21"
|
||||||
S21Plus = "S21+"
|
S21Plus = "S21+"
|
||||||
|
S21PlusHydro = "S21+ Hydro"
|
||||||
S21Pro = "S21 Pro"
|
S21Pro = "S21 Pro"
|
||||||
S21Hydro = "S21 Hydro"
|
S21Hydro = "S21 Hydro"
|
||||||
T21 = "T21"
|
T21 = "T21"
|
||||||
|
S19XPHydro = "S19 XP Hydro"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -220,6 +224,7 @@ class WhatsminerModels(MinerModelType):
|
|||||||
M31V20 = "M31 V20"
|
M31V20 = "M31 V20"
|
||||||
M32V10 = "M32 V10"
|
M32V10 = "M32 V10"
|
||||||
M32V20 = "M32 V20"
|
M32V20 = "M32 V20"
|
||||||
|
M32S = "M32S"
|
||||||
M33SPlusPlusVG40 = "M33S++ VG40"
|
M33SPlusPlusVG40 = "M33S++ VG40"
|
||||||
M33SPlusPlusVH20 = "M33S++ VH20"
|
M33SPlusPlusVH20 = "M33S++ VH20"
|
||||||
M33SPlusPlusVH30 = "M33S++ VH30"
|
M33SPlusPlusVH30 = "M33S++ VH30"
|
||||||
@@ -451,6 +456,8 @@ class AvalonminerModels(MinerModelType):
|
|||||||
Avalon1246 = "Avalon 1246"
|
Avalon1246 = "Avalon 1246"
|
||||||
Avalon1566 = "Avalon 1566"
|
Avalon1566 = "Avalon 1566"
|
||||||
AvalonNano3 = "Avalon Nano 3"
|
AvalonNano3 = "Avalon Nano 3"
|
||||||
|
AvalonNano3s = "Avalon Nano 3s"
|
||||||
|
AvalonQHome = "Avalon Q Home"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -473,6 +480,8 @@ class GoldshellModels(MinerModelType):
|
|||||||
KDMax = "KD Max"
|
KDMax = "KD Max"
|
||||||
KDBoxII = "KD Box II"
|
KDBoxII = "KD Box II"
|
||||||
KDBoxPro = "KD Box Pro"
|
KDBoxPro = "KD Box Pro"
|
||||||
|
Byte = "Byte"
|
||||||
|
MiniDoge = "Mini Doge"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -530,6 +539,7 @@ class IceRiverModels(MinerModelType):
|
|||||||
KS5 = "KS5"
|
KS5 = "KS5"
|
||||||
KS5L = "KS5L"
|
KS5L = "KS5L"
|
||||||
KS5M = "KS5M"
|
KS5M = "KS5M"
|
||||||
|
AL3 = "AL3"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -553,9 +563,17 @@ class BraiinsModels(MinerModelType):
|
|||||||
BMM100 = "BMM100"
|
BMM100 = "BMM100"
|
||||||
BMM101 = "BMM101"
|
BMM101 = "BMM101"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class ElphapexModels(MinerModelType):
|
class ElphapexModels(MinerModelType):
|
||||||
|
DG1 = "DG1"
|
||||||
DG1Plus = "DG1+"
|
DG1Plus = "DG1+"
|
||||||
|
DG1Home = "DG1Home"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
|
|||||||
@@ -1,352 +0,0 @@
|
|||||||
# ------------------------------------------------------------------------------
|
|
||||||
# Copyright 2022 Upstream Data Inc -
|
|
||||||
# -
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
|
||||||
# you may not use this file except in compliance with the License. -
|
|
||||||
# You may obtain a copy of the License at -
|
|
||||||
# -
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
|
||||||
# -
|
|
||||||
# Unless required by applicable law or agreed to in writing, software -
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
|
||||||
# See the License for the specific language governing permissions and -
|
|
||||||
# limitations under the License. -
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
from pyasic.errors import APIError
|
|
||||||
from pyasic.miners import AnyMiner
|
|
||||||
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
|
||||||
from pyasic.miners.device.models import (
|
|
||||||
S9,
|
|
||||||
S17,
|
|
||||||
T17,
|
|
||||||
S17e,
|
|
||||||
S17Plus,
|
|
||||||
S17Pro,
|
|
||||||
T17e,
|
|
||||||
T17Plus,
|
|
||||||
)
|
|
||||||
|
|
||||||
FAN_USAGE = 50 # 50 W per fan
|
|
||||||
|
|
||||||
|
|
||||||
class MinerLoadBalancer:
|
|
||||||
"""A load balancer for miners. Can be passed a list of `AnyMiner`, or a list of phases (lists of `AnyMiner`)."""
|
|
||||||
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
phases: Union[List[List[AnyMiner]], None] = None,
|
|
||||||
):
|
|
||||||
self.phases = [_MinerPhaseBalancer(phase) for phase in phases]
|
|
||||||
|
|
||||||
async def balance(self, wattage: int) -> int:
|
|
||||||
phase_wattage = wattage // len(self.phases)
|
|
||||||
setpoints = await asyncio.gather(
|
|
||||||
*[phase.get_balance_setpoints(phase_wattage) for phase in self.phases]
|
|
||||||
)
|
|
||||||
tasks = []
|
|
||||||
total_wattage = 0
|
|
||||||
for setpoint in setpoints:
|
|
||||||
wattage_set = 0
|
|
||||||
for miner in setpoint:
|
|
||||||
if setpoint[miner]["set"] == "on":
|
|
||||||
wattage_set += setpoint[miner]["max"]
|
|
||||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
|
||||||
elif setpoint[miner]["set"] == "off":
|
|
||||||
wattage_set += setpoint[miner]["min"]
|
|
||||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
|
||||||
else:
|
|
||||||
wattage_set += setpoint[miner]["set"]
|
|
||||||
tasks.append(
|
|
||||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
|
||||||
)
|
|
||||||
total_wattage += wattage_set
|
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
return total_wattage
|
|
||||||
|
|
||||||
|
|
||||||
class _MinerPhaseBalancer:
|
|
||||||
def __init__(self, miners: List[AnyMiner]):
|
|
||||||
self.miners = {
|
|
||||||
str(miner.ip): {
|
|
||||||
"miner": miner,
|
|
||||||
"set": 0,
|
|
||||||
"min": miner.expected_fans * FAN_USAGE,
|
|
||||||
}
|
|
||||||
for miner in miners
|
|
||||||
}
|
|
||||||
for miner in miners:
|
|
||||||
if (
|
|
||||||
isinstance(miner, BTMiner)
|
|
||||||
and not (miner.raw_model.startswith("M2") if miner.raw_model else True)
|
|
||||||
) or isinstance(miner, BOSMiner):
|
|
||||||
if isinstance(miner, S9):
|
|
||||||
self.miners[str(miner.ip)]["tune"] = True
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 1400
|
|
||||||
elif True in [
|
|
||||||
isinstance(miner, x)
|
|
||||||
for x in [S17, S17Plus, S17Pro, S17e, T17, T17Plus, T17e]
|
|
||||||
]:
|
|
||||||
self.miners[str(miner.ip)]["tune"] = True
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 2400
|
|
||||||
else:
|
|
||||||
self.miners[str(miner.ip)]["tune"] = True
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 3600
|
|
||||||
elif isinstance(miner, AntminerModern):
|
|
||||||
self.miners[str(miner.ip)]["tune"] = False
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 3600
|
|
||||||
elif isinstance(miner, BTMiner):
|
|
||||||
self.miners[str(miner.ip)]["tune"] = False
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 3600
|
|
||||||
if miner.raw_model:
|
|
||||||
if miner.raw_model.startswith("M2"):
|
|
||||||
self.miners[str(miner.ip)]["tune"] = False
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = True
|
|
||||||
self.miners[str(miner.ip)]["max"] = 2400
|
|
||||||
else:
|
|
||||||
self.miners[str(miner.ip)]["tune"] = False
|
|
||||||
self.miners[str(miner.ip)]["shutdown"] = False
|
|
||||||
self.miners[str(miner.ip)]["max"] = 3600
|
|
||||||
self.miners[str(miner.ip)]["min"] = 3600
|
|
||||||
|
|
||||||
async def balance(self, wattage: int) -> int:
|
|
||||||
setpoint = await self.get_balance_setpoints(wattage)
|
|
||||||
wattage_set = 0
|
|
||||||
tasks = []
|
|
||||||
for miner in setpoint:
|
|
||||||
if setpoint[miner]["set"] == "on":
|
|
||||||
wattage_set += setpoint[miner]["max"]
|
|
||||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
|
||||||
elif setpoint[miner]["set"] == "off":
|
|
||||||
wattage_set += setpoint[miner]["min"]
|
|
||||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
|
||||||
else:
|
|
||||||
wattage_set += setpoint[miner]["set"]
|
|
||||||
tasks.append(
|
|
||||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
|
||||||
)
|
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
return wattage_set
|
|
||||||
|
|
||||||
async def get_balance_setpoints(self, wattage: int) -> dict:
|
|
||||||
# gather data needed to optimize shutdown only miners
|
|
||||||
dp = ["hashrate", "wattage", "wattage_limit", "hashboards"]
|
|
||||||
data = await asyncio.gather(
|
|
||||||
*[
|
|
||||||
self.miners[miner]["miner"].get_data(data_to_get=dp)
|
|
||||||
for miner in self.miners
|
|
||||||
]
|
|
||||||
)
|
|
||||||
pct_expected_list = [d.percent_ideal for d in data]
|
|
||||||
pct_ideal = 0
|
|
||||||
if len(pct_expected_list) > 0:
|
|
||||||
pct_ideal = sum(pct_expected_list) / len(pct_expected_list)
|
|
||||||
|
|
||||||
wattage = round(wattage * 1 / (pct_ideal / 100))
|
|
||||||
|
|
||||||
for data_point in data:
|
|
||||||
if (not self.miners[data_point.ip]["tune"]) and (
|
|
||||||
not self.miners[data_point.ip]["shutdown"]
|
|
||||||
):
|
|
||||||
# cant do anything with it so need to find a semi-accurate power limit
|
|
||||||
if data_point.wattage_limit is not None:
|
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
|
||||||
elif data_point.wattage is not None:
|
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
|
||||||
|
|
||||||
max_tune_wattage = sum(
|
|
||||||
[miner["max"] for miner in self.miners.values() if miner["tune"]]
|
|
||||||
)
|
|
||||||
max_shutdown_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["max"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if (not miner["tune"]) and (miner["shutdown"])
|
|
||||||
]
|
|
||||||
)
|
|
||||||
max_other_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["max"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if (not miner["tune"]) and (not miner["shutdown"])
|
|
||||||
]
|
|
||||||
)
|
|
||||||
min_tune_wattage = sum(
|
|
||||||
[miner["min"] for miner in self.miners.values() if miner["tune"]]
|
|
||||||
)
|
|
||||||
min_shutdown_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["min"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if (not miner["tune"]) and (miner["shutdown"])
|
|
||||||
]
|
|
||||||
)
|
|
||||||
# min_other_wattage = sum(
|
|
||||||
# [
|
|
||||||
# miner["min"]
|
|
||||||
# for miner in self.miners.values()
|
|
||||||
# if (not miner["tune"]) and (not miner["shutdown"])
|
|
||||||
# ]
|
|
||||||
# )
|
|
||||||
|
|
||||||
# make sure wattage isnt set too high
|
|
||||||
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
|
||||||
raise APIError(
|
|
||||||
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
|
||||||
)
|
|
||||||
|
|
||||||
# should now know wattage limits and which can be tuned/shutdown
|
|
||||||
# check if 1/2 max of the miners which can be tuned is low enough
|
|
||||||
if (max_tune_wattage / 2) + max_shutdown_wattage + max_other_wattage < wattage:
|
|
||||||
useable_wattage = wattage - (max_other_wattage + max_shutdown_wattage)
|
|
||||||
useable_miners = len(
|
|
||||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
|
||||||
)
|
|
||||||
if not useable_miners == 0:
|
|
||||||
watts_per_miner = useable_wattage // useable_miners
|
|
||||||
# loop through and set useable miners to wattage
|
|
||||||
for miner in self.miners:
|
|
||||||
if (self.miners[miner]["set"] == 0) and (
|
|
||||||
self.miners[miner]["tune"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = watts_per_miner
|
|
||||||
elif self.miners[miner]["set"] == 0 and (
|
|
||||||
self.miners[miner]["shutdown"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = "on"
|
|
||||||
|
|
||||||
# check if shutting down miners will help
|
|
||||||
elif (
|
|
||||||
max_tune_wattage / 2
|
|
||||||
) + min_shutdown_wattage + max_other_wattage < wattage:
|
|
||||||
# tuneable inclusive since could be S9 BOS+ and S19 Stock, would rather shut down the S9, tuneable should always support shutdown
|
|
||||||
useable_wattage = wattage - (
|
|
||||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
|
||||||
)
|
|
||||||
for miner in sorted(
|
|
||||||
[miner for miner in self.miners.values() if miner["shutdown"]],
|
|
||||||
key=lambda x: x["max"],
|
|
||||||
reverse=True,
|
|
||||||
):
|
|
||||||
if miner["tune"]:
|
|
||||||
miner_min_watt_use = miner["max"] / 2
|
|
||||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
|
||||||
if useable_wattage < 0:
|
|
||||||
useable_wattage += miner_min_watt_use - miner["min"]
|
|
||||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
|
||||||
else:
|
|
||||||
miner_min_watt_use = miner["max"]
|
|
||||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
|
||||||
if useable_wattage < 0:
|
|
||||||
useable_wattage += miner_min_watt_use - miner["min"]
|
|
||||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
|
||||||
|
|
||||||
new_shutdown_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["max"] if miner["set"] == 0 else miner["min"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if miner["shutdown"] and not miner["tune"]
|
|
||||||
]
|
|
||||||
)
|
|
||||||
new_tune_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["min"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if miner["tune"] and miner["set"] == "off"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
useable_wattage = wattage - (
|
|
||||||
new_tune_wattage + max_other_wattage + new_shutdown_wattage
|
|
||||||
)
|
|
||||||
useable_miners = len(
|
|
||||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
|
||||||
)
|
|
||||||
|
|
||||||
if not useable_miners == 0:
|
|
||||||
watts_per_miner = useable_wattage // useable_miners
|
|
||||||
# loop through and set useable miners to wattage
|
|
||||||
for miner in self.miners:
|
|
||||||
if (self.miners[miner]["set"] == 0) and (
|
|
||||||
self.miners[miner]["tune"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = watts_per_miner
|
|
||||||
elif self.miners[miner]["set"] == 0 and (
|
|
||||||
self.miners[miner]["shutdown"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = "on"
|
|
||||||
|
|
||||||
# check if shutting down tuneable miners will do it
|
|
||||||
elif min_tune_wattage + min_shutdown_wattage + max_other_wattage < wattage:
|
|
||||||
# all miners that can be shutdown need to be
|
|
||||||
for miner in self.miners:
|
|
||||||
if (not self.miners[miner]["tune"]) and (
|
|
||||||
self.miners[miner]["shutdown"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = "off"
|
|
||||||
# calculate wattage usable by tuneable miners
|
|
||||||
useable_wattage = wattage - (
|
|
||||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
|
||||||
)
|
|
||||||
|
|
||||||
# loop through miners to see how much is actually useable
|
|
||||||
# sort the largest first
|
|
||||||
for miner in sorted(
|
|
||||||
[
|
|
||||||
miner
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if miner["tune"] and miner["shutdown"]
|
|
||||||
],
|
|
||||||
key=lambda x: x["max"],
|
|
||||||
reverse=True,
|
|
||||||
):
|
|
||||||
# add min to useable wattage since it was removed earlier, and remove 1/2 tuner max
|
|
||||||
useable_wattage -= (miner["max"] / 2) - miner["min"]
|
|
||||||
if useable_wattage < 0:
|
|
||||||
useable_wattage += (miner["max"] / 2) - miner["min"]
|
|
||||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
|
||||||
|
|
||||||
new_tune_wattage = sum(
|
|
||||||
[
|
|
||||||
miner["min"]
|
|
||||||
for miner in self.miners.values()
|
|
||||||
if miner["tune"] and miner["set"] == "off"
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
useable_wattage = wattage - (
|
|
||||||
new_tune_wattage + max_other_wattage + min_shutdown_wattage
|
|
||||||
)
|
|
||||||
useable_miners = len(
|
|
||||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
|
||||||
)
|
|
||||||
|
|
||||||
if not useable_miners == 0:
|
|
||||||
watts_per_miner = useable_wattage // useable_miners
|
|
||||||
# loop through and set useable miners to wattage
|
|
||||||
for miner in self.miners:
|
|
||||||
if (self.miners[miner]["set"] == 0) and (
|
|
||||||
self.miners[miner]["tune"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = watts_per_miner
|
|
||||||
elif self.miners[miner]["set"] == 0 and (
|
|
||||||
self.miners[miner]["shutdown"]
|
|
||||||
):
|
|
||||||
self.miners[miner]["set"] = "on"
|
|
||||||
else:
|
|
||||||
raise APIError(
|
|
||||||
f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W"
|
|
||||||
) # PhaseBalancingError(f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W")
|
|
||||||
|
|
||||||
return self.miners
|
|
||||||
@@ -25,7 +25,9 @@ from pyasic.miners.device.models import (
|
|||||||
S19i,
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jNoPIC,
|
S19jNoPIC,
|
||||||
|
S19jPlus,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
|
S19jProPlus,
|
||||||
S19jXP,
|
S19jXP,
|
||||||
S19KPro,
|
S19KPro,
|
||||||
S19Plus,
|
S19Plus,
|
||||||
@@ -80,6 +82,10 @@ class BMMinerS19jPro(AntminerModern, S19jPro):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19jPlus(AntminerModern, S19jPlus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS19L(AntminerModern, S19L):
|
class BMMinerS19L(AntminerModern, S19L):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -102,3 +108,7 @@ class BMMinerS19KPro(AntminerModern, S19KPro):
|
|||||||
|
|
||||||
class BMMinerS19jXP(AntminerModern, S19jXP):
|
class BMMinerS19jXP(AntminerModern, S19jXP):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS19jProPlus(AntminerModern, S19jProPlus):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ from .S19 import (
|
|||||||
BMMinerS19i,
|
BMMinerS19i,
|
||||||
BMMinerS19j,
|
BMMinerS19j,
|
||||||
BMMinerS19jNoPIC,
|
BMMinerS19jNoPIC,
|
||||||
|
BMMinerS19jPlus,
|
||||||
BMMinerS19jPro,
|
BMMinerS19jPro,
|
||||||
|
BMMinerS19jProPlus,
|
||||||
BMMinerS19jXP,
|
BMMinerS19jXP,
|
||||||
BMMinerS19KPro,
|
BMMinerS19KPro,
|
||||||
BMMinerS19L,
|
BMMinerS19L,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21Pro
|
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS21(AntminerModern, S21):
|
class BMMinerS21(AntminerModern, S21):
|
||||||
@@ -26,6 +26,10 @@ class BMMinerS21Plus(AntminerModern, S21Plus):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BMMinerS21PlusHydro(AntminerModern, S21PlusHydro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS21Pro(AntminerModern, S21Pro):
|
class BMMinerS21Pro(AntminerModern, S21Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -13,5 +13,11 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .S21 import BMMinerS21, BMMinerS21Hydro, BMMinerS21Plus, BMMinerS21Pro
|
from .S21 import (
|
||||||
|
BMMinerS21,
|
||||||
|
BMMinerS21Hydro,
|
||||||
|
BMMinerS21Plus,
|
||||||
|
BMMinerS21PlusHydro,
|
||||||
|
BMMinerS21Pro,
|
||||||
|
)
|
||||||
from .T21 import BMMinerT21
|
from .T21 import BMMinerT21
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from pyasic.miners.device.models import (
|
|||||||
S19Plus,
|
S19Plus,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
S19ProPlusHydro,
|
S19ProPlusHydro,
|
||||||
|
S19XPHydro,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -87,3 +88,7 @@ class BOSMinerS19XP(BOSer, S19XP):
|
|||||||
|
|
||||||
class BOSMinerS19ProPlusHydro(BOSer, S19ProPlusHydro):
|
class BOSMinerS19ProPlusHydro(BOSer, S19ProPlusHydro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS19XPHydro(BOSer, S19XPHydro):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -29,5 +29,6 @@ from .S19 import (
|
|||||||
BOSMinerS19Pro,
|
BOSMinerS19Pro,
|
||||||
BOSMinerS19ProPlusHydro,
|
BOSMinerS19ProPlusHydro,
|
||||||
BOSMinerS19XP,
|
BOSMinerS19XP,
|
||||||
|
BOSMinerS19XPHydro,
|
||||||
)
|
)
|
||||||
from .T19 import BOSMinerT19
|
from .T19 import BOSMinerT19
|
||||||
|
|||||||
@@ -15,12 +15,40 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSer
|
from pyasic.miners.backends import BOSer
|
||||||
from pyasic.miners.device.models import S21, S21Pro
|
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS21(BOSer, S21):
|
class BOSMinerS21(BOSer, S21):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS21Plus(BOSer, S21Plus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS21PlusHydro(BOSer, S21PlusHydro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS21Pro(BOSer, S21Pro):
|
class BOSMinerS21Pro(BOSer, S21Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BOSMinerS21Hydro(BOSer, S21Hydro):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -14,5 +14,11 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .S21 import BOSMinerS21, BOSMinerS21Pro
|
from .S21 import (
|
||||||
|
BOSMinerS21,
|
||||||
|
BOSMinerS21Hydro,
|
||||||
|
BOSMinerS21Plus,
|
||||||
|
BOSMinerS21PlusHydro,
|
||||||
|
BOSMinerS21Pro,
|
||||||
|
)
|
||||||
from .T21 import BOSMinerT21
|
from .T21 import BOSMinerT21
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.device.models import MinerModel
|
from pyasic.device.models import MinerModel, MinerModelType
|
||||||
from pyasic.miners.backends import ePIC
|
from pyasic.miners.backends import ePIC
|
||||||
from pyasic.miners.device.models import (
|
from pyasic.miners.device.models import (
|
||||||
S19,
|
S19,
|
||||||
@@ -56,12 +56,12 @@ class ePICS19XP(ePIC, S19XP):
|
|||||||
|
|
||||||
|
|
||||||
class ePICS19jProDual(ePIC, S19jPro):
|
class ePICS19jProDual(ePIC, S19jPro):
|
||||||
raw_model = MinerModel.EPIC.S19jProDual
|
raw_model: MinerModelType = MinerModel.EPIC.S19jProDual
|
||||||
expected_fans = S19jPro.expected_fans * 2
|
expected_fans = S19jPro.expected_fans * 2
|
||||||
expected_hashboards = S19jPro.expected_hashboards * 2
|
expected_hashboards = S19jPro.expected_hashboards * 2
|
||||||
|
|
||||||
|
|
||||||
class ePICS19kProDual(ePIC, S19kPro):
|
class ePICS19kProDual(ePIC, S19kPro):
|
||||||
raw_model = MinerModel.EPIC.S19kProDual
|
raw_model: MinerModelType = MinerModel.EPIC.S19kProDual
|
||||||
expected_fans = S19kPro.expected_fans * 2
|
expected_fans = S19kPro.expected_fans * 2
|
||||||
expected_hashboards = S19kPro.expected_hashboards * 2
|
expected_hashboards = S19kPro.expected_hashboards * 2
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.data import HashBoard
|
from pyasic.data import HashBoard
|
||||||
from pyasic.device.algorithm import AlgoHashRate, HashUnit
|
from pyasic.device.algorithm import AlgoHashRate, HashUnit
|
||||||
@@ -76,7 +75,7 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict | None = None) -> list[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=board, expected_chips=self.expected_chips)
|
HashBoard(slot=board, expected_chips=self.expected_chips)
|
||||||
for board in range(self.expected_hashboards)
|
for board in range(self.expected_hashboards)
|
||||||
@@ -84,7 +83,7 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
|
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -98,7 +97,7 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
hashrate = 0
|
hashrate = 0
|
||||||
chips = 0
|
chips = 0
|
||||||
for chipset in board_map[board]:
|
for chipset in board_map[board]:
|
||||||
if hashboards[board].chip_temp is None:
|
if hashboards[board].chip_temp is None and rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
hashboards[board].temp = rpc_stats["STATS"][1][f"temp{chipset}"]
|
hashboards[board].temp = rpc_stats["STATS"][1][f"temp{chipset}"]
|
||||||
hashboards[board].chip_temp = rpc_stats["STATS"][1][
|
hashboards[board].chip_temp = rpc_stats["STATS"][1][
|
||||||
@@ -108,11 +107,12 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
hashboards[board].missing = False
|
hashboards[board].missing = False
|
||||||
try:
|
if rpc_stats is not None:
|
||||||
hashrate += rpc_stats["STATS"][1][f"chain_rate{chipset}"]
|
try:
|
||||||
chips += rpc_stats["STATS"][1][f"chain_acn{chipset}"]
|
hashrate += rpc_stats["STATS"][1][f"chain_rate{chipset}"]
|
||||||
except (KeyError, IndexError):
|
chips += rpc_stats["STATS"][1][f"chain_acn{chipset}"]
|
||||||
pass
|
except (KeyError, IndexError):
|
||||||
|
pass
|
||||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
@@ -120,8 +120,8 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_stats: dict | None = None) -> float | None:
|
||||||
env_temp_list = []
|
env_temp_list: list[int] = []
|
||||||
board_map = {
|
board_map = {
|
||||||
0: [2, 9, 10],
|
0: [2, 9, 10],
|
||||||
1: [3, 11, 12],
|
1: [3, 11, 12],
|
||||||
@@ -144,3 +144,4 @@ class HiveonT9(HiveonOld, T9):
|
|||||||
|
|
||||||
if not env_temp_list == []:
|
if not env_temp_list == []:
|
||||||
return round(sum(env_temp_list) / len(env_temp_list))
|
return round(sum(env_temp_list) / len(env_temp_list))
|
||||||
|
return None
|
||||||
|
|||||||
22
pyasic/miners/antminer/luxos/X21/T21.py
Normal file
22
pyasic/miners/antminer/luxos/X21/T21.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import LUXMiner
|
||||||
|
from pyasic.miners.device.models import T21
|
||||||
|
|
||||||
|
|
||||||
|
class LUXMinerT21(LUXMiner, T21):
|
||||||
|
pass
|
||||||
@@ -15,3 +15,4 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .S21 import LUXMinerS21
|
from .S21 import LUXMinerS21
|
||||||
|
from .T21 import LUXMinerT21
|
||||||
|
|||||||
@@ -20,13 +20,16 @@ from pyasic.miners.device.models import (
|
|||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
S19aPro,
|
S19aPro,
|
||||||
|
S19Hydro,
|
||||||
S19i,
|
S19i,
|
||||||
S19j,
|
S19j,
|
||||||
S19jPro,
|
S19jPro,
|
||||||
S19kPro,
|
S19kPro,
|
||||||
S19NoPIC,
|
S19NoPIC,
|
||||||
S19Pro,
|
S19Pro,
|
||||||
|
S19ProA,
|
||||||
S19ProHydro,
|
S19ProHydro,
|
||||||
|
S19XPHydro,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -42,10 +45,18 @@ class VNishS19Pro(VNish, S19Pro):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS19Hydro(VNish, S19Hydro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VNishS19XP(VNish, S19XP):
|
class VNishS19XP(VNish, S19XP):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS19XPHydro(VNish, S19XPHydro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VNishS19a(VNish, S19a):
|
class VNishS19a(VNish, S19a):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -54,6 +65,10 @@ class VNishS19aPro(VNish, S19aPro):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS19ProA(VNish, S19ProA):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VNishS19i(VNish, S19i):
|
class VNishS19i(VNish, S19i):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,16 @@ from .S19 import (
|
|||||||
VNishS19,
|
VNishS19,
|
||||||
VNishS19a,
|
VNishS19a,
|
||||||
VNishS19aPro,
|
VNishS19aPro,
|
||||||
|
VNishS19Hydro,
|
||||||
VNishS19i,
|
VNishS19i,
|
||||||
VNishS19j,
|
VNishS19j,
|
||||||
VNishS19jPro,
|
VNishS19jPro,
|
||||||
VNishS19kPro,
|
VNishS19kPro,
|
||||||
VNishS19NoPIC,
|
VNishS19NoPIC,
|
||||||
VNishS19Pro,
|
VNishS19Pro,
|
||||||
|
VNishS19ProA,
|
||||||
VNishS19ProHydro,
|
VNishS19ProHydro,
|
||||||
VNishS19XP,
|
VNishS19XP,
|
||||||
|
VNishS19XPHydro,
|
||||||
)
|
)
|
||||||
from .T19 import VNishT19
|
from .T19 import VNishT19
|
||||||
|
|||||||
@@ -15,8 +15,24 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import VNish
|
from pyasic.miners.backends import VNish
|
||||||
from pyasic.miners.device.models import S21
|
from pyasic.miners.device.models import S21, S21Hydro, S21Plus, S21PlusHydro, S21Pro
|
||||||
|
|
||||||
|
|
||||||
class VNishS21(VNish, S21):
|
class VNishS21(VNish, S21):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS21Plus(VNish, S21Plus):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS21PlusHydro(VNish, S21PlusHydro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS21Pro(VNish, S21Pro):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class VNishS21Hydro(VNish, S21Hydro):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -14,5 +14,11 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .S21 import VNishS21
|
from .S21 import (
|
||||||
|
VNishS21,
|
||||||
|
VNishS21Hydro,
|
||||||
|
VNishS21Plus,
|
||||||
|
VNishS21PlusHydro,
|
||||||
|
VNishS21Pro,
|
||||||
|
)
|
||||||
from .T21 import VNishT21
|
from .T21 import VNishT21
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ from pyasic.miners.backends import VNish
|
|||||||
from pyasic.miners.device.models import L3Plus
|
from pyasic.miners.device.models import L3Plus
|
||||||
|
|
||||||
|
|
||||||
class VnishL3Plus(VNish, L3Plus):
|
class VNishL3Plus(VNish, L3Plus):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .L3 import VnishL3Plus
|
from .L3 import VNishL3Plus
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ from pyasic.miners.backends import VNish
|
|||||||
from pyasic.miners.device.models import L7
|
from pyasic.miners.device.models import L7
|
||||||
|
|
||||||
|
|
||||||
class VnishL7(VNish, L7):
|
class VNishL7(VNish, L7):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .L7 import VnishL7
|
from .L7 import VNishL7
|
||||||
|
|||||||
22
pyasic/miners/antminer/vnish/X9/L9.py
Normal file
22
pyasic/miners/antminer/vnish/X9/L9.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from pyasic.miners.backends import VNish
|
||||||
|
from pyasic.miners.device.models import L9
|
||||||
|
|
||||||
|
|
||||||
|
class VNishL9(VNish, L9):
|
||||||
|
pass
|
||||||
17
pyasic/miners/antminer/vnish/X9/__init__.py
Normal file
17
pyasic/miners/antminer/vnish/X9/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2022 Upstream Data Inc -
|
||||||
|
# -
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
|
# you may not use this file except in compliance with the License. -
|
||||||
|
# You may obtain a copy of the License at -
|
||||||
|
# -
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||||
|
# -
|
||||||
|
# Unless required by applicable law or agreed to in writing, software -
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||||
|
# See the License for the specific language governing permissions and -
|
||||||
|
# limitations under the License. -
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
from .L9 import VNishL9
|
||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
from .X3 import *
|
from .X3 import *
|
||||||
from .X7 import *
|
from .X7 import *
|
||||||
|
from .X9 import *
|
||||||
from .X17 import *
|
from .X17 import *
|
||||||
from .X19 import *
|
from .X19 import *
|
||||||
from .X21 import *
|
from .X21 import *
|
||||||
|
|||||||
22
pyasic/miners/avalonminer/cgminer/Q/Q.py
Normal file
22
pyasic/miners/avalonminer/cgminer/Q/Q.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# Copyright 2025 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 AvalonMiner
|
||||||
|
from pyasic.miners.device.models import AvalonQHome
|
||||||
|
|
||||||
|
|
||||||
|
class CGMinerAvalonQHome(AvalonMiner, AvalonQHome):
|
||||||
|
pass
|
||||||
1
pyasic/miners/avalonminer/cgminer/Q/__init__.py
Normal file
1
pyasic/miners/avalonminer/cgminer/Q/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .Q import CGMinerAvalonQHome
|
||||||
@@ -22,3 +22,4 @@ from .A11X import *
|
|||||||
from .A12X import *
|
from .A12X import *
|
||||||
from .A15X import *
|
from .A15X import *
|
||||||
from .nano import *
|
from .nano import *
|
||||||
|
from .Q import *
|
||||||
|
|||||||
@@ -14,4 +14,4 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from .nano3 import CGMinerAvalonNano3
|
from .nano3 import CGMinerAvalonNano3, CGMinerAvalonNano3s
|
||||||
|
|||||||
@@ -13,9 +13,11 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import Optional
|
from typing import Any
|
||||||
|
|
||||||
from pyasic import APIError
|
from pyasic import APIError
|
||||||
|
from pyasic.data.boards import HashBoard
|
||||||
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.miners.backends import AvalonMiner
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
@@ -24,7 +26,7 @@ from pyasic.miners.data import (
|
|||||||
RPCAPICommand,
|
RPCAPICommand,
|
||||||
WebAPICommand,
|
WebAPICommand,
|
||||||
)
|
)
|
||||||
from pyasic.miners.device.models import AvalonNano3
|
from pyasic.miners.device.models import AvalonNano3, AvalonNano3s
|
||||||
from pyasic.web.avalonminer import AvalonMinerWebAPI
|
from pyasic.web.avalonminer import AvalonMinerWebAPI
|
||||||
|
|
||||||
AVALON_NANO_DATA_LOC = DataLocations(
|
AVALON_NANO_DATA_LOC = DataLocations(
|
||||||
@@ -47,32 +49,89 @@ AVALON_NANO_DATA_LOC = DataLocations(
|
|||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate",
|
"_get_expected_hashrate",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp",
|
"_get_env_temp",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit",
|
"_get_wattage_limit",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage",
|
"_get_wattage",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans",
|
"_get_fans",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
AVALON_NANO3S_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
|
"_get_env_temp",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
|
"_get_wattage_limit",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
|
"_get_wattage",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
|
"_get_fault_light",
|
||||||
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime",
|
"_get_uptime",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
@@ -91,12 +150,12 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
|||||||
|
|
||||||
data_locations = AVALON_NANO_DATA_LOC
|
data_locations = AVALON_NANO_DATA_LOC
|
||||||
|
|
||||||
async def _get_mac(self, web_minerinfo: dict) -> Optional[dict]:
|
async def _get_mac(self, web_minerinfo: dict[Any, Any] | None = None) -> str | None:
|
||||||
if web_minerinfo is None:
|
if web_minerinfo is None:
|
||||||
try:
|
try:
|
||||||
web_minerinfo = await self.web.minerinfo()
|
web_minerinfo = await self.web.minerinfo()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_minerinfo is not None:
|
if web_minerinfo is not None:
|
||||||
try:
|
try:
|
||||||
@@ -105,3 +164,68 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
|||||||
return mac.upper()
|
return mac.upper()
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
|
||||||
|
data_locations = AVALON_NANO3S_DATA_LOC
|
||||||
|
|
||||||
|
async def _get_wattage(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
|
if rpc_estats is None:
|
||||||
|
try:
|
||||||
|
rpc_estats = await self.rpc.estats()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if rpc_estats is not None:
|
||||||
|
try:
|
||||||
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
|
return int(parsed_estats["PS"][6])
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_hashrate(
|
||||||
|
self, rpc_estats: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
|
if rpc_estats is None:
|
||||||
|
try:
|
||||||
|
rpc_estats = await self.rpc.estats()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if rpc_estats is not None:
|
||||||
|
try:
|
||||||
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
|
return self.algo.hashrate(
|
||||||
|
rate=float(parsed_estats["GHSspd"]), unit=self.algo.unit.GH
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_hashboards(self, rpc_estats: dict | None = None) -> list[HashBoard]:
|
||||||
|
hashboards = await AvalonMiner._get_hashboards(self, rpc_estats)
|
||||||
|
|
||||||
|
if rpc_estats is None:
|
||||||
|
try:
|
||||||
|
rpc_estats = await self.rpc.estats()
|
||||||
|
except APIError:
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
if rpc_estats is not None:
|
||||||
|
try:
|
||||||
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
for board in range(len(hashboards)):
|
||||||
|
try:
|
||||||
|
board_hr = parsed_estats["GHSspd"]
|
||||||
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
|
rate=float(board_hr), unit=self.algo.unit.GH
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return hashboards
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ from .bfgminer import BFGMiner
|
|||||||
from .bitaxe import BitAxe
|
from .bitaxe import BitAxe
|
||||||
from .bmminer import BMMiner
|
from .bmminer import BMMiner
|
||||||
from .braiins_os import BOSer, BOSMiner
|
from .braiins_os import BOSer, BOSMiner
|
||||||
from .btminer import BTMiner
|
from .btminer import BTMiner, BTMinerV2, BTMinerV3
|
||||||
from .cgminer import CGMiner
|
from .cgminer import CGMiner
|
||||||
from .elphapex import ElphapexMiner
|
from .elphapex import ElphapexMiner
|
||||||
from .epic import ePIC
|
from .epic import ePIC
|
||||||
|
|||||||
@@ -16,13 +16,12 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
from pyasic.data.error_codes import X19Error
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends.bmminer import BMMiner
|
from pyasic.miners.backends.bmminer import BMMiner
|
||||||
from pyasic.miners.backends.cgminer import CGMiner
|
from pyasic.miners.backends.cgminer import CGMiner
|
||||||
@@ -39,6 +38,10 @@ from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
|||||||
|
|
||||||
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
|
str(DataOptions.SERIAL_NUMBER): DataFunction(
|
||||||
|
"_get_serial_number",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
|
),
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac",
|
"_get_mac",
|
||||||
[WebAPICommand("web_get_system_info", "get_system_info")],
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
@@ -116,9 +119,11 @@ class AntminerModern(BMMiner):
|
|||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_am_modern(data)
|
self.config = MinerConfig.from_am_modern(data)
|
||||||
return self.config
|
return self.config or MinerConfig()
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
await self.web.set_miner_conf(config.as_am_modern(user_suffix=user_suffix))
|
await self.web.set_miner_conf(config.as_am_modern(user_suffix=user_suffix))
|
||||||
# if data:
|
# if data:
|
||||||
@@ -131,54 +136,77 @@ class AntminerModern(BMMiner):
|
|||||||
# break
|
# break
|
||||||
# await asyncio.sleep(1)
|
# await asyncio.sleep(1)
|
||||||
|
|
||||||
async def upgrade_firmware(self, file: Path, keep_settings: bool = True) -> str:
|
async def upgrade_firmware(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
file: str | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
version: str | None = None,
|
||||||
|
keep_settings: bool = True,
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the AntMiner device.
|
Upgrade the firmware of the AntMiner device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): Path to the firmware file.
|
file: Path to the firmware file as a string.
|
||||||
keep_settings (bool): Whether to keep the current settings after the update.
|
url: URL to download firmware from (not implemented).
|
||||||
|
version: Version to upgrade to (not implemented).
|
||||||
|
keep_settings: Whether to keep the current settings after the update.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Result of the upgrade process.
|
bool: True if upgrade was successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
if not file:
|
if not file:
|
||||||
raise ValueError("File location must be provided for firmware upgrade.")
|
logging.error("File location must be provided for firmware upgrade.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if url or version:
|
||||||
|
logging.warning(
|
||||||
|
"URL and version parameters are not implemented for Antminer."
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
file_path = Path(file)
|
||||||
|
|
||||||
|
if not hasattr(self.web, "update_firmware"):
|
||||||
|
logging.error(
|
||||||
|
"Firmware upgrade not supported via web API for this Antminer model."
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
result = await self.web.update_firmware(
|
result = await self.web.update_firmware(
|
||||||
file=file, keep_settings=keep_settings
|
file=file_path, keep_settings=keep_settings
|
||||||
)
|
)
|
||||||
|
|
||||||
if result.get("success"):
|
if result.get("success"):
|
||||||
logging.info(
|
logging.info(
|
||||||
"Firmware upgrade process completed successfully for AntMiner."
|
"Firmware upgrade process completed successfully for AntMiner."
|
||||||
)
|
)
|
||||||
return "Firmware upgrade completed successfully."
|
return True
|
||||||
else:
|
else:
|
||||||
error_message = result.get("message", "Unknown error")
|
error_message = result.get("message", "Unknown error")
|
||||||
logging.error(f"Firmware upgrade failed. Response: {error_message}")
|
logging.error(f"Firmware upgrade failed. Response: {error_message}")
|
||||||
return f"Firmware upgrade failed. Response: {error_message}"
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"An error occurred during the firmware upgrade process: {e}",
|
f"An error occurred during the firmware upgrade process: {e}",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
raise
|
return False
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
data = await self.web.blink(blink=True)
|
data = await self.web.blink(blink=True)
|
||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B000":
|
if data.get("code") == "B000":
|
||||||
self.light = True
|
self.light = True
|
||||||
return self.light
|
return self.light or False
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
data = await self.web.blink(blink=False)
|
data = await self.web.blink(blink=False)
|
||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B100":
|
if data.get("code") == "B100":
|
||||||
self.light = False
|
self.light = False
|
||||||
return self.light
|
return self.light or False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
@@ -198,7 +226,9 @@ class AntminerModern(BMMiner):
|
|||||||
await self.send_config(cfg)
|
await self.send_config(cfg)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_hostname(
|
||||||
|
self, web_get_system_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -210,8 +240,9 @@ class AntminerModern(BMMiner):
|
|||||||
return web_get_system_info["hostname"]
|
return web_get_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_get_system_info: dict | None = None) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -230,8 +261,11 @@ class AntminerModern(BMMiner):
|
|||||||
return data["macaddr"]
|
return data["macaddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
async def _get_errors( # type: ignore[override]
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> list[X19Error]:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -251,7 +285,7 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def _get_hashboards(self) -> List[HashBoard]:
|
async def _get_hashboards(self) -> list[HashBoard]: # type: ignore[override]
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -269,21 +303,53 @@ class AntminerModern(BMMiner):
|
|||||||
try:
|
try:
|
||||||
for board in rpc_stats["STATS"][0]["chain"]:
|
for board in rpc_stats["STATS"][0]["chain"]:
|
||||||
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
||||||
rate=board["rate_real"], unit=self.algo.unit.GH
|
rate=board["rate_real"],
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[board["index"]].chips = board["asic_num"]
|
hashboards[board["index"]].chips = board["asic_num"]
|
||||||
board_temp_data = list(
|
|
||||||
filter(lambda x: not x == 0, board["temp_pcb"])
|
if "S21+ Hyd" in self.model:
|
||||||
)
|
hashboards[board["index"]].inlet_temp = board["temp_pcb"][0]
|
||||||
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
hashboards[board["index"]].outlet_temp = board["temp_pcb"][2]
|
||||||
board_temp_data
|
hashboards[board["index"]].chip_temp = board["temp_pic"][0]
|
||||||
)
|
board_temp_data = list(
|
||||||
chip_temp_data = list(
|
filter(
|
||||||
filter(lambda x: not x == 0, board["temp_chip"])
|
lambda x: not x == 0,
|
||||||
)
|
[
|
||||||
hashboards[board["index"]].chip_temp = sum(chip_temp_data) / len(
|
board["temp_pic"][1],
|
||||||
chip_temp_data
|
board["temp_pic"][2],
|
||||||
)
|
board["temp_pic"][3],
|
||||||
|
board["temp_pcb"][1],
|
||||||
|
board["temp_pcb"][3],
|
||||||
|
],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
hashboards[board["index"]].temp = (
|
||||||
|
sum(board_temp_data) / len(board_temp_data)
|
||||||
|
if len(board_temp_data) > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
board_temp_data = list(
|
||||||
|
filter(lambda x: not x == 0, board["temp_pcb"])
|
||||||
|
)
|
||||||
|
hashboards[board["index"]].temp = (
|
||||||
|
sum(board_temp_data) / len(board_temp_data)
|
||||||
|
if len(board_temp_data) > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
chip_temp_data = list(
|
||||||
|
filter(lambda x: not x == 0, board["temp_chip"])
|
||||||
|
)
|
||||||
|
hashboards[board["index"]].chip_temp = (
|
||||||
|
sum(chip_temp_data) / len(chip_temp_data)
|
||||||
|
if len(chip_temp_data) > 0
|
||||||
|
else 0
|
||||||
|
)
|
||||||
|
|
||||||
hashboards[board["index"]].serial_number = board["sn"]
|
hashboards[board["index"]].serial_number = board["sn"]
|
||||||
hashboards[board["index"]].missing = False
|
hashboards[board["index"]].missing = False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -291,8 +357,8 @@ class AntminerModern(BMMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fault_light(
|
async def _get_fault_light(
|
||||||
self, web_get_blink_status: dict = None
|
self, web_get_blink_status: dict | None = None
|
||||||
) -> Optional[bool]:
|
) -> bool | None:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
@@ -310,8 +376,8 @@ class AntminerModern(BMMiner):
|
|||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_stats: dict = None
|
self, rpc_stats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -327,9 +393,26 @@ class AntminerModern(BMMiner):
|
|||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_serial_number(
|
||||||
|
self, web_get_system_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
|
if web_get_system_info is None:
|
||||||
|
try:
|
||||||
|
web_get_system_info = await self.web.get_system_info()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_get_system_info is not None:
|
||||||
|
try:
|
||||||
|
return web_get_system_info["serinum"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def set_static_ip(
|
async def set_static_ip(
|
||||||
self,
|
self,
|
||||||
@@ -337,10 +420,10 @@ class AntminerModern(BMMiner):
|
|||||||
dns: str,
|
dns: str,
|
||||||
gateway: str,
|
gateway: str,
|
||||||
subnet_mask: str = "255.255.255.0",
|
subnet_mask: str = "255.255.255.0",
|
||||||
hostname: str = None,
|
hostname: str | None = None,
|
||||||
):
|
):
|
||||||
if not hostname:
|
if not hostname:
|
||||||
hostname = await self.get_hostname()
|
hostname = await self.get_hostname() or ""
|
||||||
await self.web.set_network_conf(
|
await self.web.set_network_conf(
|
||||||
ip=ip,
|
ip=ip,
|
||||||
dns=dns,
|
dns=dns,
|
||||||
@@ -350,9 +433,9 @@ class AntminerModern(BMMiner):
|
|||||||
protocol=2,
|
protocol=2,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def set_dhcp(self, hostname: str = None):
|
async def set_dhcp(self, hostname: str | None = None):
|
||||||
if not hostname:
|
if not hostname:
|
||||||
hostname = await self.get_hostname()
|
hostname = await self.get_hostname() or ""
|
||||||
await self.web.set_network_conf(
|
await self.web.set_network_conf(
|
||||||
ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
|
ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
|
||||||
)
|
)
|
||||||
@@ -373,7 +456,7 @@ class AntminerModern(BMMiner):
|
|||||||
protocol=protocol,
|
protocol=protocol,
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_get_conf: dict | None = None) -> bool | None:
|
||||||
if web_get_conf is None:
|
if web_get_conf is None:
|
||||||
try:
|
try:
|
||||||
web_get_conf = await self.web.get_miner_conf()
|
web_get_conf = await self.web.get_miner_conf()
|
||||||
@@ -389,8 +472,9 @@ class AntminerModern(BMMiner):
|
|||||||
return False
|
return False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -402,8 +486,9 @@ class AntminerModern(BMMiner):
|
|||||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
@@ -492,19 +577,22 @@ class AntminerOld(CGMiner):
|
|||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_am_old(data)
|
self.config = MinerConfig.from_am_old(data)
|
||||||
return self.config
|
return self.config or MinerConfig()
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
|
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_mac(self) -> str | None:
|
||||||
try:
|
try:
|
||||||
data = await self.web.get_system_info()
|
data = await self.web.get_system_info()
|
||||||
if data:
|
if data:
|
||||||
return data["macaddr"]
|
return data["macaddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
# this should time out, after it does do a check
|
# this should time out, after it does do a check
|
||||||
@@ -516,7 +604,7 @@ class AntminerOld(CGMiner):
|
|||||||
self.light = True
|
self.light = True
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return self.light
|
return self.light or False
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
await self.web.blink(blink=False)
|
await self.web.blink(blink=False)
|
||||||
@@ -527,7 +615,7 @@ class AntminerOld(CGMiner):
|
|||||||
self.light = False
|
self.light = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return self.light
|
return self.light or False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
@@ -536,8 +624,8 @@ class AntminerOld(CGMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_fault_light(
|
async def _get_fault_light(
|
||||||
self, web_get_blink_status: dict = None
|
self, web_get_blink_status: dict | None = None
|
||||||
) -> Optional[bool]:
|
) -> bool | None:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
@@ -554,7 +642,9 @@ class AntminerOld(CGMiner):
|
|||||||
pass
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_hostname(
|
||||||
|
self, web_get_system_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -566,8 +656,9 @@ class AntminerOld(CGMiner):
|
|||||||
return web_get_system_info["hostname"]
|
return web_get_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_stats: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -592,16 +683,16 @@ class AntminerOld(CGMiner):
|
|||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans_data[fan].speed = rpc_stats["STATS"][1].get(
|
fans_data[fan].speed = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset + fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
hashboards = []
|
hashboards: list[HashBoard] = []
|
||||||
|
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
@@ -641,8 +732,11 @@ class AntminerOld(CGMiner):
|
|||||||
hashrate = boards[1].get(f"chain_rate{i}")
|
hashrate = boards[1].get(f"chain_rate{i}")
|
||||||
if hashrate:
|
if hashrate:
|
||||||
hashboard.hashrate = self.algo.hashrate(
|
hashboard.hashrate = self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.GH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
chips = boards[1].get(f"chain_acn{i}")
|
chips = boards[1].get(f"chain_acn{i}")
|
||||||
if chips:
|
if chips:
|
||||||
@@ -659,7 +753,7 @@ class AntminerOld(CGMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_get_conf: dict | None = None) -> bool | None:
|
||||||
if web_get_conf is None:
|
if web_get_conf is None:
|
||||||
try:
|
try:
|
||||||
web_get_conf = await self.web.get_miner_conf()
|
web_get_conf = await self.web.get_miner_conf()
|
||||||
@@ -684,7 +778,9 @@ class AntminerOld(CGMiner):
|
|||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
return None
|
||||||
|
|
||||||
|
async def _get_uptime(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -696,3 +792,4 @@ class AntminerOld(CGMiner):
|
|||||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|||||||
@@ -15,11 +15,10 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
@@ -187,7 +186,9 @@ class Auradine(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return MinerConfig()
|
return MinerConfig()
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
conf = config.as_auradine(user_suffix=user_suffix)
|
conf = config.as_auradine(user_suffix=user_suffix)
|
||||||
@@ -197,8 +198,8 @@ class Auradine(StockFirmware):
|
|||||||
async def upgrade_firmware(
|
async def upgrade_firmware(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
url: str = None,
|
url: str | None = None,
|
||||||
version: str = "latest",
|
version: str | None = "latest",
|
||||||
keep_settings: bool = False,
|
keep_settings: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
@@ -223,8 +224,12 @@ class Auradine(StockFirmware):
|
|||||||
|
|
||||||
if url:
|
if url:
|
||||||
result = await self.web.firmware_upgrade(url=url)
|
result = await self.web.firmware_upgrade(url=url)
|
||||||
else:
|
elif version:
|
||||||
result = await self.web.firmware_upgrade(version=version)
|
result = await self.web.firmware_upgrade(version=version)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Either URL or version must be provided for firmware upgrade."
|
||||||
|
)
|
||||||
|
|
||||||
if result.get("STATUS", [{}])[0].get("STATUS") == "S":
|
if result.get("STATUS", [{}])[0].get("STATUS") == "S":
|
||||||
logging.info("Firmware upgrade process completed successfully.")
|
logging.info("Firmware upgrade process completed successfully.")
|
||||||
@@ -245,7 +250,7 @@ class Auradine(StockFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, web_ipreport: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_ipreport: dict | None = None) -> str | None:
|
||||||
if web_ipreport is None:
|
if web_ipreport is None:
|
||||||
try:
|
try:
|
||||||
web_ipreport = await self.web.ipreport()
|
web_ipreport = await self.web.ipreport()
|
||||||
@@ -257,8 +262,9 @@ class Auradine(StockFirmware):
|
|||||||
return web_ipreport["IPReport"][0]["mac"].upper()
|
return web_ipreport["IPReport"][0]["mac"].upper()
|
||||||
except (LookupError, AttributeError):
|
except (LookupError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_ipreport: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_ipreport: dict | None = None) -> str | None:
|
||||||
if web_ipreport is None:
|
if web_ipreport is None:
|
||||||
try:
|
try:
|
||||||
web_ipreport = await self.web.ipreport()
|
web_ipreport = await self.web.ipreport()
|
||||||
@@ -270,8 +276,9 @@ class Auradine(StockFirmware):
|
|||||||
return web_ipreport["IPReport"][0]["version"]
|
return web_ipreport["IPReport"][0]["version"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hostname(self, web_ipreport: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_ipreport: dict | None = None) -> str | None:
|
||||||
if web_ipreport is None:
|
if web_ipreport is None:
|
||||||
try:
|
try:
|
||||||
web_ipreport = await self.web.ipreport()
|
web_ipreport = await self.web.ipreport()
|
||||||
@@ -283,8 +290,11 @@ class Auradine(StockFirmware):
|
|||||||
return web_ipreport["IPReport"][0]["hostname"]
|
return web_ipreport["IPReport"][0]["hostname"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -295,14 +305,15 @@ class Auradine(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 5s"]),
|
rate=float(rpc_summary["SUMMARY"][0]["MHS 5s"]),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, rpc_devs: dict = None, web_ipreport: dict = None
|
self, rpc_devs: dict | None = None, web_ipreport: dict | None = None
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -327,8 +338,11 @@ class Auradine(StockFirmware):
|
|||||||
for board in rpc_devs["DEVS"]:
|
for board in rpc_devs["DEVS"]:
|
||||||
b_id = board["ID"] - 1
|
b_id = board["ID"] - 1
|
||||||
hashboards[b_id].hashrate = self.algo.hashrate(
|
hashboards[b_id].hashrate = self.algo.hashrate(
|
||||||
rate=float(board["MHS 5s"]), unit=self.algo.unit.MH
|
rate=float(board["MHS 5s"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[b_id].temp = round(float(board["Temperature"]))
|
hashboards[b_id].temp = round(float(board["Temperature"]))
|
||||||
hashboards[b_id].missing = False
|
hashboards[b_id].missing = False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -344,7 +358,7 @@ class Auradine(StockFirmware):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(self, web_psu: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_psu: dict | None = None) -> int | None:
|
||||||
if web_psu is None:
|
if web_psu is None:
|
||||||
try:
|
try:
|
||||||
web_psu = await self.web.get_psu()
|
web_psu = await self.web.get_psu()
|
||||||
@@ -356,10 +370,11 @@ class Auradine(StockFirmware):
|
|||||||
return int(float(web_psu["PSU"][0]["PowerIn"].replace("W", "")))
|
return int(float(web_psu["PSU"][0]["PowerIn"].replace("W", "")))
|
||||||
except (LookupError, TypeError, ValueError):
|
except (LookupError, TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(
|
async def _get_wattage_limit(
|
||||||
self, web_mode: dict = None, web_psu: dict = None
|
self, web_mode: dict | None = None, web_psu: dict | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
if web_mode is None:
|
if web_mode is None:
|
||||||
try:
|
try:
|
||||||
web_mode = await self.web.get_mode()
|
web_mode = await self.web.get_mode()
|
||||||
@@ -383,8 +398,9 @@ class Auradine(StockFirmware):
|
|||||||
return int(float(web_psu["PSU"][0]["PoutMax"].replace("W", "")))
|
return int(float(web_psu["PSU"][0]["PoutMax"].replace("W", "")))
|
||||||
except (LookupError, TypeError, ValueError):
|
except (LookupError, TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, web_fan: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_fan: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -403,7 +419,7 @@ class Auradine(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_fault_light(self, web_led: dict = None) -> Optional[bool]:
|
async def _get_fault_light(self, web_led: dict | None = None) -> bool | None:
|
||||||
if web_led is None:
|
if web_led is None:
|
||||||
try:
|
try:
|
||||||
web_led = await self.web.get_led()
|
web_led = await self.web.get_led()
|
||||||
@@ -415,8 +431,9 @@ class Auradine(StockFirmware):
|
|||||||
return web_led["LED"][0]["Code"] == int(AuradineLEDCodes.LOCATE_MINER)
|
return web_led["LED"][0]["Code"] == int(AuradineLEDCodes.LOCATE_MINER)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _is_mining(self, web_mode: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_mode: dict | None = None) -> bool | None:
|
||||||
if web_mode is None:
|
if web_mode is None:
|
||||||
try:
|
try:
|
||||||
web_mode = await self.web.get_mode()
|
web_mode = await self.web.get_mode()
|
||||||
@@ -428,8 +445,9 @@ class Auradine(StockFirmware):
|
|||||||
return web_mode["Mode"][0]["Sleep"] == "off"
|
return web_mode["Mode"][0]["Sleep"] == "off"
|
||||||
except (LookupError, TypeError, ValueError):
|
except (LookupError, TypeError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -441,3 +459,4 @@ class Auradine(StockFirmware):
|
|||||||
return rpc_summary["SUMMARY"][0]["Elapsed"]
|
return rpc_summary["SUMMARY"][0]["Elapsed"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|||||||
@@ -13,15 +13,16 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import copy
|
||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
import time
|
||||||
|
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends.cgminer import CGMiner
|
from pyasic.miners.backends.cgminer import CGMiner
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
|
from pyasic.rpc.avalonminer import AvalonMinerRPCAPI
|
||||||
|
|
||||||
AVALON_DATA_LOC = DataLocations(
|
AVALON_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
@@ -43,31 +44,31 @@ AVALON_DATA_LOC = DataLocations(
|
|||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate",
|
"_get_expected_hashrate",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp",
|
"_get_env_temp",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit",
|
"_get_wattage_limit",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage",
|
"_get_wattage",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans",
|
"_get_fans",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
[RPCAPICommand("rpc_stats", "stats")],
|
[RPCAPICommand("rpc_estats", "estats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime",
|
"_get_uptime",
|
||||||
@@ -84,6 +85,9 @@ AVALON_DATA_LOC = DataLocations(
|
|||||||
class AvalonMiner(CGMiner):
|
class AvalonMiner(CGMiner):
|
||||||
"""Handler for Avalon Miners"""
|
"""Handler for Avalon Miners"""
|
||||||
|
|
||||||
|
_rpc_cls = AvalonMinerRPCAPI
|
||||||
|
rpc: AvalonMinerRPCAPI
|
||||||
|
|
||||||
data_locations = AVALON_DATA_LOC
|
data_locations = AVALON_DATA_LOC
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
@@ -114,7 +118,7 @@ class AvalonMiner(CGMiner):
|
|||||||
limit = 1
|
limit = 1
|
||||||
else:
|
else:
|
||||||
limit = 0
|
limit = 0
|
||||||
data = await self.rpc.ascset(0, "worklevel,set", 1)
|
data = await self.rpc.ascset(0, "worklevel,set", limit)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||||
@@ -134,51 +138,100 @@ class AvalonMiner(CGMiner):
|
|||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
# Shut off 5 seconds from now
|
||||||
|
timestamp = int(time.time()) + 5
|
||||||
|
data = await self.rpc.ascset(0, "softoff", f"1:{timestamp}")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if "success" in data["STATUS"][0]["Msg"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
# Shut off 5 seconds from now
|
||||||
|
timestamp = int(time.time()) + 5
|
||||||
|
data = await self.rpc.ascset(0, "softon", f"1:{timestamp}")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if "success" in data["STATUS"][0]["Msg"]:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_stats(stats):
|
def parse_estats(data):
|
||||||
_stats_items = re.findall(".+?\\[*?]", stats)
|
# Deep copy to preserve original structure
|
||||||
stats_items = []
|
new_data = copy.deepcopy(data)
|
||||||
stats_dict = {}
|
|
||||||
for item in _stats_items:
|
|
||||||
if ": " in item:
|
|
||||||
data = item.replace("]", "").split("[")
|
|
||||||
data_list = [i.split(": ") for i in data[1].strip().split(", ")]
|
|
||||||
data_dict = {}
|
|
||||||
try:
|
|
||||||
for key, val in [tuple(item) for item in data_list]:
|
|
||||||
data_dict[key] = val
|
|
||||||
except ValueError:
|
|
||||||
# --avalon args
|
|
||||||
for arg_item in data_list:
|
|
||||||
item_data = arg_item[0].split(" ")
|
|
||||||
for idx, val in enumerate(item_data):
|
|
||||||
if idx % 2 == 0 or idx == 0:
|
|
||||||
data_dict[val] = item_data[idx + 1]
|
|
||||||
|
|
||||||
raw_data = [data[0].strip(), data_dict]
|
def convert_value(val, key):
|
||||||
|
val = val.strip()
|
||||||
|
|
||||||
|
if key == "SYSTEMSTATU":
|
||||||
|
return val
|
||||||
|
|
||||||
|
if " " in val:
|
||||||
|
parts = val.split()
|
||||||
|
result = []
|
||||||
|
for part in parts:
|
||||||
|
if part.isdigit():
|
||||||
|
result.append(int(part))
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
result.append(float(part))
|
||||||
|
except ValueError:
|
||||||
|
result.append(part)
|
||||||
|
return result
|
||||||
else:
|
else:
|
||||||
raw_data = [
|
if val.isdigit():
|
||||||
value
|
return int(val)
|
||||||
for value in item.replace("[", " ")
|
try:
|
||||||
.replace("]", " ")
|
return float(val)
|
||||||
.split(" ")[:-1]
|
except ValueError:
|
||||||
if value != ""
|
return val
|
||||||
]
|
|
||||||
if len(raw_data) == 1:
|
|
||||||
raw_data.append("")
|
|
||||||
if raw_data[0] == "":
|
|
||||||
raw_data = raw_data[1:]
|
|
||||||
|
|
||||||
stats_dict[raw_data[0]] = raw_data[1:]
|
def parse_info_block(info_str):
|
||||||
stats_items.append(raw_data)
|
pattern = re.compile(r"(\w+)\[([^\]]*)\]")
|
||||||
|
return {
|
||||||
|
key: convert_value(val, key) for key, val in pattern.findall(info_str)
|
||||||
|
}
|
||||||
|
|
||||||
return stats_dict
|
for stat in new_data.get("STATS", []):
|
||||||
|
keys_to_replace = {}
|
||||||
|
|
||||||
|
for key, value in stat.items():
|
||||||
|
if "MM" in key:
|
||||||
|
# Normalize key by removing suffix after colon
|
||||||
|
norm_key = key.split(":")[0]
|
||||||
|
|
||||||
|
mm_data = value
|
||||||
|
if not isinstance(mm_data, str):
|
||||||
|
continue
|
||||||
|
if mm_data.startswith("'STATS':"):
|
||||||
|
mm_data = mm_data[len("'STATS':") :]
|
||||||
|
keys_to_replace[norm_key] = parse_info_block(mm_data)
|
||||||
|
|
||||||
|
elif key == "HBinfo":
|
||||||
|
match = re.search(r"'(\w+)':\{(.+)\}", value)
|
||||||
|
if match:
|
||||||
|
hb_key = match.group(1)
|
||||||
|
hb_data = match.group(2)
|
||||||
|
keys_to_replace[key] = {hb_key: parse_info_block(hb_data)}
|
||||||
|
|
||||||
|
# Remove old keys and insert parsed versions
|
||||||
|
for k in list(stat.keys()):
|
||||||
|
if "MM" in k or k == "HBinfo":
|
||||||
|
del stat[k]
|
||||||
|
stat.update(keys_to_replace)
|
||||||
|
|
||||||
|
return new_data
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_mac(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -195,23 +248,28 @@ class AvalonMiner(CGMiner):
|
|||||||
return mac
|
return mac
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_devs: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_devs is None:
|
if rpc_devs is None:
|
||||||
try:
|
try:
|
||||||
rpc_devs = await self.rpc.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_devs is not None:
|
if rpc_devs is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_devs["DEVS"][0]["MHS 1m"]), unit=self.algo.unit.MH
|
rate=float(rpc_devs["DEVS"][0]["MHS 1m"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (KeyError, IndexError, ValueError, TypeError):
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_estats: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -220,164 +278,216 @@ class AvalonMiner(CGMiner):
|
|||||||
for i in range(self.expected_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
for board in range(self.expected_hashboards):
|
for board in range(self.expected_hashboards):
|
||||||
try:
|
try:
|
||||||
hashboards[board].chip_temp = int(parsed_stats["MTmax"][board])
|
board_hr = parsed_estats["STATS"][0]["MM ID0"]["MGHS"]
|
||||||
|
if isinstance(board_hr, list):
|
||||||
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
|
rate=float(board_hr[board]),
|
||||||
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
|
rate=float(board_hr),
|
||||||
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
board_hr = parsed_stats["MGHS"][board]
|
hashboards[board].chip_temp = int(
|
||||||
hashboards[board].hashrate = self.algo.hashrate(
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
rate=float(board_hr), unit=self.algo.unit.GH
|
)
|
||||||
).into(self.algo.unit.default)
|
except (LookupError, TypeError):
|
||||||
except LookupError:
|
try:
|
||||||
pass
|
hashboards[board].chip_temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"].get(
|
||||||
|
"Tmax", parsed_estats["STATS"][0]["MM ID0"]["TMax"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
hashboards[board].temp = int(parsed_stats["MTavg"][board])
|
hashboards[board].temp = int(
|
||||||
except LookupError:
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
pass
|
)
|
||||||
|
except (LookupError, TypeError):
|
||||||
|
try:
|
||||||
|
hashboards[board].temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"].get(
|
||||||
|
"Tavg", parsed_estats["STATS"][0]["MM ID0"]["TAvg"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
chip_data = parsed_stats[f"PVT_T{board}"]
|
hashboards[board].inlet_temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"]["MTavg"][board]
|
||||||
|
)
|
||||||
|
except (LookupError, TypeError):
|
||||||
|
try:
|
||||||
|
hashboards[board].inlet_temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"]["HBITemp"]
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
hashboards[board].outlet_temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
|
)
|
||||||
|
except (LookupError, TypeError):
|
||||||
|
try:
|
||||||
|
hashboards[board].outlet_temp = int(
|
||||||
|
parsed_estats["STATS"][0]["MM ID0"]["HBOTemp"]
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
chip_data = parsed_estats["STATS"][0]["MM ID0"][f"PVT_T{board}"]
|
||||||
hashboards[board].missing = False
|
hashboards[board].missing = False
|
||||||
if chip_data:
|
if chip_data:
|
||||||
hashboards[board].chips = len(
|
hashboards[board].chips = len(
|
||||||
[item for item in chip_data if not item == "0"]
|
[item for item in chip_data if not item == "0"]
|
||||||
)
|
)
|
||||||
except LookupError:
|
except (LookupError, TypeError):
|
||||||
pass
|
try:
|
||||||
|
chip_data = parsed_estats["STATS"][0]["HBinfo"][f"HB{board}"][
|
||||||
|
f"PVT_T{board}"
|
||||||
|
]
|
||||||
|
hashboards[board].missing = False
|
||||||
|
if chip_data:
|
||||||
|
hashboards[board].chips = len(
|
||||||
|
[item for item in chip_data if not item == "0"]
|
||||||
|
)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_stats: dict = None
|
self, rpc_estats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(parsed_stats["GHSmm"][0]), unit=self.algo.unit.GH
|
rate=float(parsed_estats["GHSmm"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_estats: dict | None = None) -> float | None:
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
return float(parsed_estats["Temp"])
|
||||||
return float(parsed_stats["Temp"][0])
|
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if rpc_estats is not None:
|
||||||
|
try:
|
||||||
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
|
return int(parsed_estats["MPO"])
|
||||||
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_wattage(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
|
if rpc_estats is None:
|
||||||
|
try:
|
||||||
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
return int(parsed_estats["WALLPOWER"])
|
||||||
return int(parsed_stats["MPO"][0])
|
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_fans(self, rpc_estats: dict | None = None) -> list[Fan]:
|
||||||
if rpc_stats is None:
|
|
||||||
try:
|
|
||||||
rpc_stats = await self.rpc.stats()
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if rpc_stats is not None:
|
|
||||||
try:
|
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
|
||||||
return int(parsed_stats["WALLPOWER"][0])
|
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
|
||||||
except LookupError:
|
except LookupError:
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
try:
|
try:
|
||||||
fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"][0])
|
fans_data[fan].speed = int(parsed_estats[f"Fan{fan + 1}"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
async def _get_fault_light(self, rpc_stats: dict = None) -> Optional[bool]:
|
async def _get_fault_light(self, rpc_estats: dict | None = None) -> bool | None:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
if rpc_stats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if rpc_stats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
led = int(parsed_estats["Led"])
|
||||||
led = int(parsed_stats["Led"][0])
|
|
||||||
return True if led == 1 else False
|
return True if led == 1 else False
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
|
||||||
data = await self.rpc.ascset(0, "led", "1-255")
|
|
||||||
except APIError:
|
|
||||||
return False
|
|
||||||
try:
|
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set info: LED[1]":
|
|
||||||
return True
|
|
||||||
except LookupError:
|
|
||||||
pass
|
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -14,12 +14,10 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
from pyasic.miners.device.firmware import StockFirmware
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
@@ -72,7 +70,8 @@ class BFGMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
pools = await self.rpc.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
if self.config is not None:
|
||||||
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
@@ -81,7 +80,7 @@ class BFGMiner(StockFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -96,7 +95,7 @@ class BFGMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -111,7 +110,9 @@ class BFGMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
# get hr from API
|
# get hr from API
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
@@ -123,12 +124,15 @@ class BFGMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 20s"]),
|
rate=float(rpc_summary["SUMMARY"][0]["MHS 20s"]),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -172,8 +176,11 @@ class BFGMiner(StockFirmware):
|
|||||||
hashrate = boards[1].get(f"chain_rate{i}")
|
hashrate = boards[1].get(f"chain_rate{i}")
|
||||||
if hashrate:
|
if hashrate:
|
||||||
hashboard.hashrate = self.algo.hashrate(
|
hashboard.hashrate = self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.GH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
chips = boards[1].get(f"chain_acn{i}")
|
chips = boards[1].get(f"chain_acn{i}")
|
||||||
if chips:
|
if chips:
|
||||||
@@ -187,7 +194,7 @@ class BFGMiner(StockFirmware):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_stats: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -212,15 +219,15 @@ class BFGMiner(StockFirmware):
|
|||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans_data[fan] = rpc_stats["STATS"][1].get(
|
fans_data[fan] = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset + fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
|
fans = [Fan(speed=d) for d in fans_data if d is not None]
|
||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
@@ -251,8 +258,8 @@ class BFGMiner(StockFirmware):
|
|||||||
return pools_data
|
return pools_data
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_stats: dict = None
|
self, rpc_stats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
@@ -269,6 +276,7 @@ class BFGMiner(StockFirmware):
|
|||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|||||||
@@ -14,12 +14,11 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
from pyasic.miners.device.firmware import StockFirmware
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
@@ -76,7 +75,8 @@ class BMMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
pools = await self.rpc.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
if self.config is not None:
|
||||||
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
@@ -85,7 +85,7 @@ class BMMiner(StockFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -100,7 +100,7 @@ class BMMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -115,7 +115,9 @@ class BMMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
# get hr from API
|
# get hr from API
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
@@ -127,12 +129,15 @@ class BMMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||||
unit=self.algo.unit.GH,
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -189,8 +194,11 @@ class BMMiner(StockFirmware):
|
|||||||
hashrate = boards[1].get(f"chain_rate{i}")
|
hashrate = boards[1].get(f"chain_rate{i}")
|
||||||
if hashrate:
|
if hashrate:
|
||||||
hashboard.hashrate = self.algo.hashrate(
|
hashboard.hashrate = self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.GH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
chips = boards[1].get(f"chain_acn{i}")
|
chips = boards[1].get(f"chain_acn{i}")
|
||||||
if chips:
|
if chips:
|
||||||
@@ -204,7 +212,7 @@ class BMMiner(StockFirmware):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_stats: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -229,7 +237,7 @@ class BMMiner(StockFirmware):
|
|||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans[fan].speed = rpc_stats["STATS"][1].get(
|
fans[fan].speed = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset + fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -237,8 +245,8 @@ class BMMiner(StockFirmware):
|
|||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_stats: dict = None
|
self, rpc_stats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
@@ -255,11 +263,14 @@ class BMMiner(StockFirmware):
|
|||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -271,8 +282,9 @@ class BMMiner(StockFirmware):
|
|||||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
|
|||||||
@@ -16,8 +16,6 @@
|
|||||||
import base64
|
import base64
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
import tomli_w
|
import tomli_w
|
||||||
@@ -25,14 +23,14 @@ import tomli_w
|
|||||||
try:
|
try:
|
||||||
import tomllib
|
import tomllib
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import tomli as tomllib
|
import tomli as tomllib # type: ignore
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.config.mining import MiningModePowerTune
|
from pyasic.config.mining import MiningModePowerTune
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate, AlgoHashRateType
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
@@ -193,7 +191,9 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
|
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
|
parsed_cfg = config.as_bosminer(user_suffix=user_suffix)
|
||||||
|
|
||||||
@@ -202,21 +202,18 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
"format": {
|
"format": {
|
||||||
"version": "2.0",
|
"version": "2.0",
|
||||||
"generator": "pyasic",
|
"generator": "pyasic",
|
||||||
"model": f"{self.make.replace('Miner', 'miner')} {self.raw_model.replace('j', 'J')}",
|
"model": f"{self.make.replace('Miner', 'miner') if self.make else ''} {self.raw_model.replace('j', 'J') if self.raw_model else ''}",
|
||||||
"timestamp": int(time.time()),
|
"timestamp": int(time.time()),
|
||||||
},
|
},
|
||||||
**parsed_cfg,
|
**parsed_cfg,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
conn = await self.ssh._get_connection()
|
await self.ssh.send_command("/etc/init.d/bosminer stop")
|
||||||
except ConnectionError as e:
|
await self.ssh.send_command("echo '" + toml_conf + "' > /etc/bosminer.toml")
|
||||||
raise APIError("SSH connection failed when sending config.") from e
|
await self.ssh.send_command("/etc/init.d/bosminer start")
|
||||||
|
except Exception as e:
|
||||||
async with conn:
|
raise APIError("SSH command failed when sending config.") from e
|
||||||
await conn.run("/etc/init.d/bosminer stop")
|
|
||||||
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:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
@@ -285,12 +282,12 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
|
async def _get_mac(self, web_net_conf: dict | list | None = None) -> str | None:
|
||||||
if web_net_conf is None:
|
if web_net_conf is None:
|
||||||
try:
|
try:
|
||||||
web_net_conf = await self.web.get_net_conf()
|
web_net_conf = await self.web.get_net_conf()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if isinstance(web_net_conf, dict):
|
if isinstance(web_net_conf, dict):
|
||||||
if "admin/network/iface_status/lan" in web_net_conf.keys():
|
if "admin/network/iface_status/lan" in web_net_conf.keys():
|
||||||
@@ -301,17 +298,18 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
return web_net_conf[0]["macaddr"]
|
return web_net_conf[0]["macaddr"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
# could use ssh, but its slow and buggy
|
# could use ssh, but its slow and buggy
|
||||||
# result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
# result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||||
# if result:
|
# if result:
|
||||||
# return result.upper().strip()
|
# return result.upper().strip()
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
# Now get the API version
|
# Now get the API version
|
||||||
if rpc_version is not None:
|
if rpc_version is not None:
|
||||||
@@ -320,17 +318,20 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
rpc_ver = None
|
rpc_ver = None
|
||||||
self.api_ver = rpc_ver
|
self.api_ver = rpc_ver
|
||||||
self.rpc.rpc_ver = self.api_ver
|
self.rpc.rpc_ver = self.api_ver # type: ignore
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_bos_info: dict | None = None) -> str | None:
|
||||||
if web_bos_info is None:
|
if web_bos_info is None:
|
||||||
try:
|
try:
|
||||||
web_bos_info = await self.web.get_bos_info()
|
web_bos_info = await self.web.get_bos_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if web_bos_info is None:
|
||||||
|
return None
|
||||||
|
|
||||||
if isinstance(web_bos_info, dict):
|
if isinstance(web_bos_info, dict):
|
||||||
if "bos/info" in web_bos_info.keys():
|
if "bos/info" in web_bos_info.keys():
|
||||||
web_bos_info = web_bos_info["bos/info"]
|
web_bos_info = web_bos_info["bos/info"]
|
||||||
@@ -344,7 +345,7 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self) -> Union[str, None]:
|
async def _get_hostname(self) -> str | None:
|
||||||
try:
|
try:
|
||||||
hostname = (await self.ssh.get_hostname()).strip()
|
hostname = (await self.ssh.get_hostname()).strip()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -354,28 +355,31 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
return None
|
return None
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_summary is not None:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (KeyError, IndexError, ValueError, TypeError):
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self,
|
self,
|
||||||
rpc_temps: dict = None,
|
rpc_temps: dict | None = None,
|
||||||
rpc_devdetails: dict = None,
|
rpc_devdetails: dict | None = None,
|
||||||
rpc_devs: dict = None,
|
rpc_devs: dict | None = None,
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -440,19 +444,22 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
for board in rpc_devs["DEVS"]:
|
for board in rpc_devs["DEVS"]:
|
||||||
_id = board["ID"] - offset
|
_id = board["ID"] - offset
|
||||||
hashboards[_id].hashrate = self.algo.hashrate(
|
hashboards[_id].hashrate = self.algo.hashrate(
|
||||||
rate=float(board["MHS 1m"]), unit=self.algo.unit.MH
|
rate=float(board["MHS 1m"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_tunerstatus: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_tunerstatus: dict | None = None) -> int | None:
|
||||||
if rpc_tunerstatus is None:
|
if rpc_tunerstatus is None:
|
||||||
try:
|
try:
|
||||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_tunerstatus is not None:
|
if rpc_tunerstatus is not None:
|
||||||
try:
|
try:
|
||||||
@@ -461,21 +468,25 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
]
|
]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self, rpc_tunerstatus: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(
|
||||||
|
self, rpc_tunerstatus: dict | None = None
|
||||||
|
) -> int | None:
|
||||||
if rpc_tunerstatus is None:
|
if rpc_tunerstatus is None:
|
||||||
try:
|
try:
|
||||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_tunerstatus is not None:
|
if rpc_tunerstatus is not None:
|
||||||
try:
|
try:
|
||||||
return rpc_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
|
return rpc_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_fans: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -483,7 +494,7 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
try:
|
try:
|
||||||
rpc_fans = await self.rpc.fans()
|
rpc_fans = await self.rpc.fans()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return [Fan() for _ in range(self.expected_fans)]
|
||||||
|
|
||||||
if rpc_fans is not None:
|
if rpc_fans is not None:
|
||||||
fans = []
|
fans = []
|
||||||
@@ -495,12 +506,14 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
return fans
|
return fans
|
||||||
return [Fan() for _ in range(self.expected_fans)]
|
return [Fan() for _ in range(self.expected_fans)]
|
||||||
|
|
||||||
async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
|
async def _get_errors(
|
||||||
|
self, rpc_tunerstatus: dict | None = None
|
||||||
|
) -> list[MinerErrorData]:
|
||||||
if rpc_tunerstatus is None:
|
if rpc_tunerstatus is None:
|
||||||
try:
|
try:
|
||||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
if rpc_tunerstatus is not None:
|
if rpc_tunerstatus is not None:
|
||||||
errors = []
|
errors = []
|
||||||
@@ -523,9 +536,10 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
errors.append(
|
errors.append(
|
||||||
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
||||||
)
|
)
|
||||||
return errors
|
return errors # type: ignore
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
async def _get_fault_light(self) -> bool:
|
||||||
if self.light:
|
if self.light:
|
||||||
@@ -537,16 +551,16 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
self.light = True
|
self.light = True
|
||||||
return self.light
|
return self.light
|
||||||
except (TypeError, AttributeError):
|
except (TypeError, AttributeError):
|
||||||
return self.light
|
return self.light or False
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_devs: dict = None
|
self, rpc_devs: dict | None = None
|
||||||
) -> Optional[AlgoHashRateType]:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_devs is None:
|
if rpc_devs is None:
|
||||||
try:
|
try:
|
||||||
rpc_devs = await self.rpc.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_devs is not None:
|
if rpc_devs is not None:
|
||||||
try:
|
try:
|
||||||
@@ -559,52 +573,57 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
|
|
||||||
if len(hr_list) == 0:
|
if len(hr_list) == 0:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(0), unit=self.algo.unit.default
|
rate=float(0),
|
||||||
|
unit=self.algo.unit.default, # type: ignore
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(
|
rate=float(
|
||||||
(sum(hr_list) / len(hr_list)) * self.expected_hashboards
|
(sum(hr_list) / len(hr_list))
|
||||||
|
* (self.expected_hashboards or 1)
|
||||||
),
|
),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
|
async def _is_mining(self, rpc_devdetails: dict | None = None) -> bool | None:
|
||||||
if rpc_devdetails is None:
|
if rpc_devdetails is None:
|
||||||
try:
|
try:
|
||||||
rpc_devdetails = await self.rpc.send_command(
|
rpc_devdetails = await self.rpc.send_command(
|
||||||
"devdetails", ignore_errors=True, allow_warning=False
|
"devdetails", ignore_errors=True, allow_warning=False
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_devdetails is not None:
|
if rpc_devdetails is not None:
|
||||||
try:
|
try:
|
||||||
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_summary is not None:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
pools_data = []
|
pools_data = []
|
||||||
if rpc_pools is not None:
|
if rpc_pools is not None:
|
||||||
@@ -629,15 +648,25 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
pass
|
pass
|
||||||
return pools_data
|
return pools_data
|
||||||
|
|
||||||
async def upgrade_firmware(self, file: Path) -> str:
|
async def upgrade_firmware(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
file: str | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
version: str | None = None,
|
||||||
|
keep_settings: bool = True,
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the BOSMiner device.
|
Upgrade the firmware of the BOSMiner device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): The local file path of the firmware to be uploaded.
|
file: The local file path of the firmware to be uploaded.
|
||||||
|
url: URL of firmware to download (not used in this implementation).
|
||||||
|
version: Specific version to upgrade to (not used in this implementation).
|
||||||
|
keep_settings: Whether to keep current settings (not used in this implementation).
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Confirmation message after upgrading the firmware.
|
True if upgrade was successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logging.info("Starting firmware upgrade process.")
|
logging.info("Starting firmware upgrade process.")
|
||||||
@@ -659,24 +688,24 @@ class BOSMiner(BraiinsOSFirmware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Firmware upgrade process completed successfully.")
|
logging.info("Firmware upgrade process completed successfully.")
|
||||||
return "Firmware upgrade completed successfully."
|
return True
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
logging.error(f"File not found during the firmware upgrade process: {e}")
|
logging.error(f"File not found during the firmware upgrade process: {e}")
|
||||||
raise
|
return False
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"Validation error occurred during the firmware upgrade process: {e}"
|
f"Validation error occurred during the firmware upgrade process: {e}"
|
||||||
)
|
)
|
||||||
raise
|
return False
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.error(f"OS error occurred during the firmware upgrade process: {e}")
|
logging.error(f"OS error occurred during the firmware upgrade process: {e}")
|
||||||
raise
|
return False
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(
|
logging.error(
|
||||||
f"An unexpected error occurred during the firmware upgrade process: {e}",
|
f"An unexpected error occurred during the firmware upgrade process: {e}",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
raise
|
return False
|
||||||
|
|
||||||
|
|
||||||
BOSER_DATA_LOC = DataLocations(
|
BOSER_DATA_LOC = DataLocations(
|
||||||
@@ -805,7 +834,9 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
|
|
||||||
return MinerConfig.from_boser(grpc_conf)
|
return MinerConfig.from_boser(grpc_conf)
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
boser_cfg = config.as_boser(user_suffix=user_suffix)
|
boser_cfg = config.as_boser(user_suffix=user_suffix)
|
||||||
for key in boser_cfg:
|
for key in boser_cfg:
|
||||||
await self.web.send_command(key, message=boser_cfg[key])
|
await self.web.send_command(key, message=boser_cfg[key])
|
||||||
@@ -813,7 +844,8 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
result = await self.web.set_power_target(
|
result = await self.web.set_power_target(
|
||||||
wattage, save_action=SaveAction.SAVE_AND_FORCE_APPLY
|
wattage,
|
||||||
|
save_action=SaveAction(SaveAction.SAVE_AND_FORCE_APPLY),
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
@@ -829,25 +861,26 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
|
async def _get_mac(self, grpc_miner_details: dict | None = None) -> str | None:
|
||||||
if grpc_miner_details is None:
|
if grpc_miner_details is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_details = await self.web.get_miner_details()
|
grpc_miner_details = await self.web.get_miner_details()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if grpc_miner_details is not None:
|
if grpc_miner_details is not None:
|
||||||
try:
|
try:
|
||||||
return grpc_miner_details["macAddress"].upper()
|
return grpc_miner_details["macAddress"].upper()
|
||||||
except (LookupError, TypeError):
|
except (LookupError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_version is not None:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
@@ -855,16 +888,16 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
rpc_ver = None
|
rpc_ver = None
|
||||||
self.api_ver = rpc_ver
|
self.api_ver = rpc_ver
|
||||||
self.rpc.rpc_ver = self.api_ver
|
self.rpc.rpc_ver = self.api_ver # type: ignore
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, grpc_miner_details: dict | None = None) -> str | None:
|
||||||
if grpc_miner_details is None:
|
if grpc_miner_details is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_details = await self.web.get_miner_details()
|
grpc_miner_details = await self.web.get_miner_details()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
fw_ver = None
|
fw_ver = None
|
||||||
|
|
||||||
@@ -882,43 +915,47 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
|
async def _get_hostname(self, grpc_miner_details: dict | None = None) -> str | None:
|
||||||
if grpc_miner_details is None:
|
if grpc_miner_details is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_details = await self.web.get_miner_details()
|
grpc_miner_details = await self.web.get_miner_details()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if grpc_miner_details is not None:
|
if grpc_miner_details is not None:
|
||||||
try:
|
try:
|
||||||
return grpc_miner_details["hostname"]
|
return grpc_miner_details["hostname"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_summary is not None:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (KeyError, IndexError, ValueError, TypeError):
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, grpc_miner_details: dict = None
|
self, grpc_miner_details: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if grpc_miner_details is None:
|
if grpc_miner_details is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_details = await self.web.get_miner_details()
|
grpc_miner_details = await self.web.get_miner_details()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if grpc_miner_details is not None:
|
if grpc_miner_details is not None:
|
||||||
try:
|
try:
|
||||||
@@ -926,12 +963,15 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
rate=float(
|
rate=float(
|
||||||
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"]
|
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"]
|
||||||
),
|
),
|
||||||
unit=self.algo.unit.GH,
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(
|
||||||
|
self, grpc_hashboards: dict | None = None
|
||||||
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -944,7 +984,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
try:
|
try:
|
||||||
grpc_hashboards = await self.web.get_hashboards()
|
grpc_hashboards = await self.web.get_hashboards()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return hashboards
|
||||||
|
|
||||||
if grpc_hashboards is not None:
|
if grpc_hashboards is not None:
|
||||||
grpc_boards = sorted(
|
grpc_boards = sorted(
|
||||||
@@ -967,35 +1007,38 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
"gigahashPerSecond"
|
"gigahashPerSecond"
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
unit=self.algo.unit.GH,
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[idx].missing = False
|
hashboards[idx].missing = False
|
||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, grpc_miner_stats: dict | None = None) -> int | None:
|
||||||
if grpc_miner_stats is None:
|
if grpc_miner_stats is None:
|
||||||
try:
|
try:
|
||||||
grpc_miner_stats = await self.web.get_miner_stats()
|
grpc_miner_stats = await self.web.get_miner_stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if grpc_miner_stats is not None:
|
if grpc_miner_stats is not None:
|
||||||
try:
|
try:
|
||||||
return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
|
return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(
|
async def _get_wattage_limit(
|
||||||
self, grpc_active_performance_mode: dict = None
|
self, grpc_active_performance_mode: dict | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
if grpc_active_performance_mode is None:
|
if grpc_active_performance_mode is None:
|
||||||
try:
|
try:
|
||||||
grpc_active_performance_mode = (
|
grpc_active_performance_mode = (
|
||||||
await self.web.get_active_performance_mode()
|
await self.web.get_active_performance_mode()
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if grpc_active_performance_mode is not None:
|
if grpc_active_performance_mode is not None:
|
||||||
try:
|
try:
|
||||||
@@ -1004,8 +1047,9 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
]["watt"]
|
]["watt"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]:
|
async def _get_fans(self, grpc_cooling_state: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -1013,7 +1057,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
try:
|
try:
|
||||||
grpc_cooling_state = await self.web.get_cooling_state()
|
grpc_cooling_state = await self.web.get_cooling_state()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return [Fan() for _ in range(self.expected_fans)]
|
||||||
|
|
||||||
if grpc_cooling_state is not None:
|
if grpc_cooling_state is not None:
|
||||||
fans = []
|
fans = []
|
||||||
@@ -1025,12 +1069,14 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
return fans
|
return fans
|
||||||
return [Fan() for _ in range(self.expected_fans)]
|
return [Fan() for _ in range(self.expected_fans)]
|
||||||
|
|
||||||
async def _get_errors(self, rpc_tunerstatus: dict = None) -> List[MinerErrorData]:
|
async def _get_errors(
|
||||||
|
self, rpc_tunerstatus: dict | None = None
|
||||||
|
) -> list[MinerErrorData]:
|
||||||
if rpc_tunerstatus is None:
|
if rpc_tunerstatus is None:
|
||||||
try:
|
try:
|
||||||
rpc_tunerstatus = await self.rpc.tunerstatus()
|
rpc_tunerstatus = await self.rpc.tunerstatus()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
if rpc_tunerstatus is not None:
|
if rpc_tunerstatus is not None:
|
||||||
errors = []
|
errors = []
|
||||||
@@ -1053,11 +1099,14 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
errors.append(
|
errors.append(
|
||||||
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
||||||
)
|
)
|
||||||
return errors
|
return errors # type: ignore
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
async def _get_fault_light(self, grpc_locate_device_status: dict = None) -> bool:
|
async def _get_fault_light(
|
||||||
|
self, grpc_locate_device_status: dict | None = None
|
||||||
|
) -> bool:
|
||||||
if self.light is not None:
|
if self.light is not None:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
@@ -1065,7 +1114,7 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
try:
|
try:
|
||||||
grpc_locate_device_status = await self.web.get_locate_device_status()
|
grpc_locate_device_status = await self.web.get_locate_device_status()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return False
|
||||||
|
|
||||||
if grpc_locate_device_status is not None:
|
if grpc_locate_device_status is not None:
|
||||||
if grpc_locate_device_status == {}:
|
if grpc_locate_device_status == {}:
|
||||||
@@ -1074,36 +1123,41 @@ class BOSer(BraiinsOSFirmware):
|
|||||||
return grpc_locate_device_status["enabled"]
|
return grpc_locate_device_status["enabled"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def _is_mining(self, rpc_devdetails: dict = None) -> Optional[bool]:
|
async def _is_mining(self, rpc_devdetails: dict | None = None) -> bool | None:
|
||||||
if rpc_devdetails is None:
|
if rpc_devdetails is None:
|
||||||
try:
|
try:
|
||||||
rpc_devdetails = await self.rpc.send_command(
|
rpc_devdetails = await self.rpc.send_command(
|
||||||
"devdetails", ignore_errors=True, allow_warning=False
|
"devdetails", ignore_errors=True, allow_warning=False
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_devdetails is not None:
|
if rpc_devdetails is not None:
|
||||||
try:
|
try:
|
||||||
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
return not rpc_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_summary is not None:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, grpc_pool_groups: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(
|
||||||
|
self, grpc_pool_groups: dict | None = None
|
||||||
|
) -> list[PoolMetrics]:
|
||||||
if grpc_pool_groups is None:
|
if grpc_pool_groups is None:
|
||||||
try:
|
try:
|
||||||
grpc_pool_groups = await self.web.get_pool_groups()
|
grpc_pool_groups = await self.web.get_pool_groups()
|
||||||
|
|||||||
@@ -13,22 +13,50 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
import aiofiles
|
import aiofiles
|
||||||
|
import semver
|
||||||
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
from pyasic.miners.device.firmware import StockFirmware
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
from pyasic.rpc.btminer import BTMinerRPCAPI, BTMinerV3RPCAPI
|
||||||
|
|
||||||
|
|
||||||
|
class BTMiner(StockFirmware):
|
||||||
|
def __new__(cls, ip: str, version: str | None = None):
|
||||||
|
bases = cls.__bases__
|
||||||
|
bases = bases[1:]
|
||||||
|
|
||||||
|
def get_new(v: str | None):
|
||||||
|
if v is None:
|
||||||
|
return BTMinerV2
|
||||||
|
try:
|
||||||
|
semantic = semver.Version(
|
||||||
|
major=int(v[0:4]),
|
||||||
|
minor=int(v[4:6]),
|
||||||
|
patch=int(v[6:8]),
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
return BTMinerV2
|
||||||
|
if semantic > semver.Version(major=2024, minor=11, patch=0):
|
||||||
|
return BTMinerV3
|
||||||
|
return BTMinerV2
|
||||||
|
|
||||||
|
inject = get_new(version)
|
||||||
|
|
||||||
|
bases = (inject,) + bases
|
||||||
|
|
||||||
|
cls = type(cls.__name__, bases, {})(ip=ip, version=version)
|
||||||
|
return cls
|
||||||
|
|
||||||
|
|
||||||
BTMINER_DATA_LOC = DataLocations(
|
BTMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
@@ -119,7 +147,7 @@ BTMINER_DATA_LOC = DataLocations(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BTMiner(StockFirmware):
|
class BTMinerV2(StockFirmware):
|
||||||
"""Base handler for BTMiner based miners."""
|
"""Base handler for BTMiner based miners."""
|
||||||
|
|
||||||
_rpc_cls = BTMinerRPCAPI
|
_rpc_cls = BTMinerRPCAPI
|
||||||
@@ -208,7 +236,9 @@ class BTMiner(StockFirmware):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
|
|
||||||
conf = config.as_wm(user_suffix=user_suffix)
|
conf = config.as_wm(user_suffix=user_suffix)
|
||||||
@@ -279,6 +309,9 @@ class BTMiner(StockFirmware):
|
|||||||
self.config = cfg
|
self.config = cfg
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
cfg.mining_mode = MiningModeConfig.normal()
|
||||||
|
return cfg
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
await self.rpc.adjust_power_limit(wattage)
|
await self.rpc.adjust_power_limit(wattage)
|
||||||
@@ -287,14 +320,15 @@ class BTMiner(StockFirmware):
|
|||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(
|
async def _get_mac(
|
||||||
self, rpc_summary: dict = None, rpc_get_miner_info: dict = None
|
self, rpc_summary: dict | None = None, rpc_get_miner_info: dict | None = None
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
if rpc_get_miner_info is None:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_info = await self.rpc.get_miner_info()
|
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||||
@@ -321,7 +355,9 @@ class BTMiner(StockFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_get_version: dict = None) -> Optional[str]:
|
return None
|
||||||
|
|
||||||
|
async def _get_api_ver(self, rpc_get_version: dict | None = None) -> str | None:
|
||||||
if rpc_get_version is None:
|
if rpc_get_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_version = await self.rpc.get_version()
|
rpc_get_version = await self.rpc.get_version()
|
||||||
@@ -339,14 +375,13 @@ class BTMiner(StockFirmware):
|
|||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.rpc.rpc_ver = self.api_ver
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(
|
async def _get_fw_ver(
|
||||||
self, rpc_get_version: dict = None, rpc_summary: dict = None
|
self, rpc_get_version: dict | None = None, rpc_summary: dict | None = None
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
if rpc_get_version is None:
|
if rpc_get_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_version = await self.rpc.get_version()
|
rpc_get_version = await self.rpc.get_version()
|
||||||
@@ -379,7 +414,7 @@ class BTMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self, rpc_get_miner_info: dict = None) -> Optional[str]:
|
async def _get_hostname(self, rpc_get_miner_info: dict | None = None) -> str | None:
|
||||||
hostname = None
|
hostname = None
|
||||||
if rpc_get_miner_info is None:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
@@ -395,7 +430,9 @@ class BTMiner(StockFirmware):
|
|||||||
|
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -406,12 +443,13 @@ class BTMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||||
unit=self.algo.unit.MH,
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_devs: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_devs: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -440,8 +478,11 @@ class BTMiner(StockFirmware):
|
|||||||
hashboards[asc].chip_temp = round(board["Chip Temp Avg"])
|
hashboards[asc].chip_temp = round(board["Chip Temp Avg"])
|
||||||
hashboards[asc].temp = round(board["Temperature"])
|
hashboards[asc].temp = round(board["Temperature"])
|
||||||
hashboards[asc].hashrate = self.algo.hashrate(
|
hashboards[asc].hashrate = self.algo.hashrate(
|
||||||
rate=float(board["MHS 1m"]), unit=self.algo.unit.MH
|
rate=float(board["MHS 1m"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[asc].chips = board["Effective Chips"]
|
hashboards[asc].chips = board["Effective Chips"]
|
||||||
hashboards[asc].serial_number = board["PCB SN"]
|
hashboards[asc].serial_number = board["PCB SN"]
|
||||||
hashboards[asc].missing = False
|
hashboards[asc].missing = False
|
||||||
@@ -450,7 +491,7 @@ class BTMiner(StockFirmware):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self, rpc_summary: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_summary: dict | None = None) -> float | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -462,8 +503,9 @@ class BTMiner(StockFirmware):
|
|||||||
return rpc_summary["SUMMARY"][0]["Env Temp"]
|
return rpc_summary["SUMMARY"][0]["Env Temp"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -476,8 +518,9 @@ class BTMiner(StockFirmware):
|
|||||||
return wattage if not wattage == -1 else None
|
return wattage if not wattage == -1 else None
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -489,10 +532,11 @@ class BTMiner(StockFirmware):
|
|||||||
return rpc_summary["SUMMARY"][0]["Power Limit"]
|
return rpc_summary["SUMMARY"][0]["Power Limit"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(
|
async def _get_fans(
|
||||||
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
self, rpc_summary: dict | None = None, rpc_get_psu: dict | None = None
|
||||||
) -> List[Fan]:
|
) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -516,8 +560,8 @@ class BTMiner(StockFirmware):
|
|||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_fan_psu(
|
async def _get_fan_psu(
|
||||||
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
self, rpc_summary: dict | None = None, rpc_get_psu: dict | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -541,10 +585,11 @@ class BTMiner(StockFirmware):
|
|||||||
return int(rpc_get_psu["Msg"]["fan_speed"])
|
return int(rpc_get_psu["Msg"]["fan_speed"])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_errors(
|
async def _get_errors(
|
||||||
self, rpc_summary: dict = None, rpc_get_error_code: dict = None
|
self, rpc_summary: dict | None = None, rpc_get_error_code: dict | None = None
|
||||||
) -> List[MinerErrorData]:
|
) -> list[MinerErrorData]:
|
||||||
errors = []
|
errors = []
|
||||||
if rpc_get_error_code is None and rpc_summary is None:
|
if rpc_get_error_code is None and rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
@@ -577,11 +622,11 @@ class BTMiner(StockFirmware):
|
|||||||
errors.append(WhatsminerError(error_code=err))
|
errors.append(WhatsminerError(error_code=err))
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return errors
|
return errors # type: ignore[return-value]
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_summary: dict = None
|
self, rpc_summary: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -593,13 +638,17 @@ class BTMiner(StockFirmware):
|
|||||||
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
|
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
|
||||||
if expected_hashrate:
|
if expected_hashrate:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(expected_hashrate), unit=self.algo.unit.GH
|
rate=float(expected_hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
|
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> Optional[bool]:
|
async def _get_fault_light(
|
||||||
|
self, rpc_get_miner_info: dict | None = None
|
||||||
|
) -> bool | None:
|
||||||
if rpc_get_miner_info is None:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_info = await self.rpc.get_miner_info()
|
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||||
@@ -621,15 +670,17 @@ class BTMiner(StockFirmware):
|
|||||||
dns: str,
|
dns: str,
|
||||||
gateway: str,
|
gateway: str,
|
||||||
subnet_mask: str = "255.255.255.0",
|
subnet_mask: str = "255.255.255.0",
|
||||||
hostname: str = None,
|
hostname: str | None = None,
|
||||||
):
|
):
|
||||||
if not hostname:
|
if not hostname:
|
||||||
hostname = await self.get_hostname()
|
hostname = await self.get_hostname()
|
||||||
|
if hostname is None:
|
||||||
|
hostname = str(self.ip)
|
||||||
await self.rpc.net_config(
|
await self.rpc.net_config(
|
||||||
ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
|
ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
|
||||||
)
|
)
|
||||||
|
|
||||||
async def set_dhcp(self, hostname: str = None):
|
async def set_dhcp(self, hostname: str | None = None):
|
||||||
if hostname:
|
if hostname:
|
||||||
await self.set_hostname(hostname)
|
await self.set_hostname(hostname)
|
||||||
await self.rpc.net_config()
|
await self.rpc.net_config()
|
||||||
@@ -637,7 +688,7 @@ class BTMiner(StockFirmware):
|
|||||||
async def set_hostname(self, hostname: str):
|
async def set_hostname(self, hostname: str):
|
||||||
await self.rpc.set_hostname(hostname)
|
await self.rpc.set_hostname(hostname)
|
||||||
|
|
||||||
async def _is_mining(self, rpc_status: dict = None) -> Optional[bool]:
|
async def _is_mining(self, rpc_status: dict | None = None) -> bool | None:
|
||||||
if rpc_status is None:
|
if rpc_status is None:
|
||||||
try:
|
try:
|
||||||
rpc_status = await self.rpc.status()
|
rpc_status = await self.rpc.status()
|
||||||
@@ -655,8 +706,9 @@ class BTMiner(StockFirmware):
|
|||||||
return True if rpc_status["Msg"]["mineroff"] == "false" else False
|
return True if rpc_status["Msg"]["mineroff"] == "false" else False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_summary: dict | None = None) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -668,8 +720,9 @@ class BTMiner(StockFirmware):
|
|||||||
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
@@ -699,15 +752,25 @@ class BTMiner(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return pools_data
|
return pools_data
|
||||||
|
|
||||||
async def upgrade_firmware(self, file: Path) -> str:
|
async def upgrade_firmware(
|
||||||
|
self,
|
||||||
|
*,
|
||||||
|
file: str | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
version: str | None = None,
|
||||||
|
keep_settings: bool = True,
|
||||||
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the Whatsminer device.
|
Upgrade the firmware of the Whatsminer device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path): The local file path of the firmware to be uploaded.
|
file: The local file path of the firmware to be uploaded.
|
||||||
|
url: URL to download firmware from (not supported).
|
||||||
|
version: Specific version to upgrade to (not supported).
|
||||||
|
keep_settings: Whether to keep settings after upgrade.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: Confirmation message after upgrading the firmware.
|
bool: True if firmware upgrade was successful.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logging.info("Starting firmware upgrade process for Whatsminer.")
|
logging.info("Starting firmware upgrade process for Whatsminer.")
|
||||||
@@ -719,12 +782,12 @@ class BTMiner(StockFirmware):
|
|||||||
async with aiofiles.open(file, "rb") as f:
|
async with aiofiles.open(file, "rb") as f:
|
||||||
upgrade_contents = await f.read()
|
upgrade_contents = await f.read()
|
||||||
|
|
||||||
result = await self.rpc.update_firmware(upgrade_contents)
|
await self.rpc.update_firmware(upgrade_contents)
|
||||||
|
|
||||||
logging.info(
|
logging.info(
|
||||||
"Firmware upgrade process completed successfully for Whatsminer."
|
"Firmware upgrade process completed successfully for Whatsminer."
|
||||||
)
|
)
|
||||||
return result
|
return True
|
||||||
except FileNotFoundError as e:
|
except FileNotFoundError as e:
|
||||||
logging.error(f"File not found during the firmware upgrade process: {e}")
|
logging.error(f"File not found during the firmware upgrade process: {e}")
|
||||||
raise
|
raise
|
||||||
@@ -742,3 +805,462 @@ class BTMiner(StockFirmware):
|
|||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
BTMINERV3_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
|
),
|
||||||
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
|
"_get_api_version",
|
||||||
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_firmware_version",
|
||||||
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
|
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
|
),
|
||||||
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
|
"_get_light_flashing",
|
||||||
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
|
"_get_wattage_limit",
|
||||||
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FAN_PSU): DataFunction(
|
||||||
|
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[
|
||||||
|
RPCAPICommand("rpc_get_device_info", "get.device.info"),
|
||||||
|
RPCAPICommand(
|
||||||
|
"rpc_get_miner_status_edevs",
|
||||||
|
"get.miner.status:edevs",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
|
"_get_wattage",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
|
"_get_env_temp",
|
||||||
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerV3(StockFirmware):
|
||||||
|
_rpc_cls = BTMinerV3RPCAPI
|
||||||
|
rpc: BTMinerV3RPCAPI
|
||||||
|
|
||||||
|
data_locations = BTMINERV3_DATA_LOC
|
||||||
|
|
||||||
|
supports_shutdown = True
|
||||||
|
supports_autotuning = True
|
||||||
|
supports_power_modes = True
|
||||||
|
|
||||||
|
async def get_config(self) -> MinerConfig:
|
||||||
|
pools = None
|
||||||
|
settings = None
|
||||||
|
device_info = None
|
||||||
|
try:
|
||||||
|
pools = await self.rpc.get_miner_status_pools()
|
||||||
|
settings = await self.rpc.get_miner_setting()
|
||||||
|
device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError as e:
|
||||||
|
logging.warning(e)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if pools is not None and settings is not None and device_info is not None:
|
||||||
|
self.config = MinerConfig.from_btminer_v3(
|
||||||
|
rpc_pools=pools, rpc_settings=settings, rpc_device_info=device_info
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.config = MinerConfig()
|
||||||
|
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
conf = config.as_btminer_v3(user_suffix=user_suffix)
|
||||||
|
|
||||||
|
await asyncio.gather(
|
||||||
|
*[self.rpc.send_command(k, parameters=v) for k, v in conf.values()]
|
||||||
|
)
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_system_led()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data:
|
||||||
|
if "code" in data.keys():
|
||||||
|
if data["code"] == 0:
|
||||||
|
self.light = False
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_system_led(
|
||||||
|
leds=[{"color": "red", "period": 60, "duration": 20, "start": 0}]
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data:
|
||||||
|
if "code" in data.keys():
|
||||||
|
if data["code"] == 0:
|
||||||
|
self.light = True
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_system_reboot()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data and data.get("msg"):
|
||||||
|
if data["msg"] == "ok":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_miner_service("restart")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data and data.get("msg"):
|
||||||
|
if data["msg"] == "ok":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_miner_service("stop")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data and data.get("msg"):
|
||||||
|
if data["msg"] == "ok":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
data = await self.rpc.set_miner_service("start")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
if data and data.get("msg"):
|
||||||
|
if data["msg"] == "ok":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
|
try:
|
||||||
|
await self.rpc.set_miner_power_limit(wattage)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"{self} set_power_limit: {e}")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def _get_mac(self, rpc_get_device_info: dict | None = None) -> str | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
return rpc_get_device_info.get("msg", {}).get("network", {}).get("mac")
|
||||||
|
|
||||||
|
async def _get_api_version(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
return rpc_get_device_info.get("msg", {}).get("system", {}).get("api")
|
||||||
|
|
||||||
|
async def _get_firmware_version(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
return rpc_get_device_info.get("msg", {}).get("system", {}).get("fwversion")
|
||||||
|
|
||||||
|
async def _get_hostname(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
return rpc_get_device_info.get("msg", {}).get("network", {}).get("hostname")
|
||||||
|
|
||||||
|
async def _get_light_flashing(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> bool | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
val = rpc_get_device_info.get("msg", {}).get("system", {}).get("ledstatus")
|
||||||
|
if isinstance(val, str):
|
||||||
|
return val != "auto"
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_wattage_limit(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> int | None:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
val = rpc_get_device_info.get("msg", {}).get("miner", {}).get("power-limit-set")
|
||||||
|
try:
|
||||||
|
return int(float(val))
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_fans(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> list[Fan]:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
fans = []
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return []
|
||||||
|
summary = rpc_get_miner_status_summary.get("msg", {}).get("summary", {})
|
||||||
|
for idx, direction in enumerate(["in", "out"]):
|
||||||
|
rpm = summary.get(f"fan-speed-{direction}")
|
||||||
|
if rpm is not None:
|
||||||
|
fans.append(Fan(speed=rpm))
|
||||||
|
return fans
|
||||||
|
|
||||||
|
async def _get_psu_fans(self, rpc_get_device_info: dict | None = None) -> list[Fan]:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return []
|
||||||
|
rpm = rpc_get_device_info.get("msg", {}).get("power", {}).get("fanspeed")
|
||||||
|
return [Fan(speed=rpm)] if rpm is not None else []
|
||||||
|
|
||||||
|
async def _get_hashboards(
|
||||||
|
self,
|
||||||
|
rpc_get_device_info: dict | None = None,
|
||||||
|
rpc_get_miner_status_edevs: dict | None = None,
|
||||||
|
) -> list[HashBoard]:
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
try:
|
||||||
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
if rpc_get_miner_status_edevs is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_edevs = await self.rpc.get_miner_status_edevs()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
|
||||||
|
boards = []
|
||||||
|
if rpc_get_device_info is None or rpc_get_miner_status_edevs is None:
|
||||||
|
return []
|
||||||
|
board_count = (
|
||||||
|
rpc_get_device_info.get("msg", {}).get("hardware", {}).get("boards", 3)
|
||||||
|
)
|
||||||
|
edevs = rpc_get_miner_status_edevs.get("msg", {}).get("edevs", [])
|
||||||
|
for idx in range(board_count):
|
||||||
|
board_data = edevs[idx] if idx < len(edevs) else {}
|
||||||
|
boards.append(
|
||||||
|
HashBoard(
|
||||||
|
slot=idx,
|
||||||
|
hashrate=self.algo.hashrate(
|
||||||
|
rate=board_data.get("hash-average", 0),
|
||||||
|
unit=self.algo.unit.TH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
),
|
||||||
|
temp=board_data.get("chip-temp-min"),
|
||||||
|
inlet_temp=board_data.get("chip-temp-min"),
|
||||||
|
outlet_temp=board_data.get("chip-temp-max"),
|
||||||
|
serial_number=rpc_get_device_info.get("msg", {})
|
||||||
|
.get("miner", {})
|
||||||
|
.get(f"pcbsn{idx}"),
|
||||||
|
chips=board_data.get("effective-chips"),
|
||||||
|
expected_chips=self.expected_chips,
|
||||||
|
active=(board_data.get("hash-average") or 0) > 0,
|
||||||
|
missing=False,
|
||||||
|
tuned=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return boards
|
||||||
|
|
||||||
|
async def _get_pools(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> list[PoolMetrics]:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return []
|
||||||
|
pools = []
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return []
|
||||||
|
msg_pools = rpc_get_miner_status_summary.get("msg", {}).get("pools", [])
|
||||||
|
for idx, pool in enumerate(msg_pools):
|
||||||
|
pools.append(
|
||||||
|
PoolMetrics(
|
||||||
|
index=idx,
|
||||||
|
user=pool.get("account"),
|
||||||
|
alive=pool.get("status") == "alive",
|
||||||
|
active=pool.get("stratum-active"),
|
||||||
|
url=pool.get("url"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return pools
|
||||||
|
|
||||||
|
async def _get_uptime(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> int | None:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
return (
|
||||||
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
|
.get("summary", {})
|
||||||
|
.get("elapsed")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _get_wattage(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> int | None:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
power_val = (
|
||||||
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
|
.get("summary", {})
|
||||||
|
.get("power-realtime")
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
return int(float(power_val)) if power_val is not None else None
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_hashrate(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return (
|
||||||
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
|
.get("summary", {})
|
||||||
|
.get("hash-realtime")
|
||||||
|
)
|
||||||
|
|
||||||
|
async def _get_expected_hashrate(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
res = (
|
||||||
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
|
.get("summary", {})
|
||||||
|
.get("factory-hash")
|
||||||
|
)
|
||||||
|
|
||||||
|
if self.expected_hashboards is not None and res == (
|
||||||
|
-0.001 * self.expected_hashboards
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
return res
|
||||||
|
|
||||||
|
async def _get_env_temp(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> float | None:
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
|
except APIError:
|
||||||
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
return (
|
||||||
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
|
.get("summary", {})
|
||||||
|
.get("environment-temperature")
|
||||||
|
)
|
||||||
|
|||||||
@@ -14,11 +14,10 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
from pyasic.miners.device.firmware import StockFirmware
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
@@ -75,7 +74,7 @@ class CGMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
pools = await self.rpc.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config or MinerConfig()
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
@@ -84,7 +83,7 @@ class CGMiner(StockFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -99,7 +98,7 @@ class CGMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict | None = None) -> str | None:
|
||||||
if rpc_version is None:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
rpc_version = await self.rpc.version()
|
rpc_version = await self.rpc.version()
|
||||||
@@ -114,7 +113,9 @@ class CGMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_summary = await self.rpc.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
@@ -125,12 +126,15 @@ class CGMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||||
unit=self.algo.unit.GH,
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -142,8 +146,9 @@ class CGMiner(StockFirmware):
|
|||||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, rpc_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if rpc_pools is None:
|
if rpc_pools is None:
|
||||||
try:
|
try:
|
||||||
rpc_pools = await self.rpc.pools()
|
rpc_pools = await self.rpc.pools()
|
||||||
|
|||||||
@@ -13,13 +13,11 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic import APIError, MinerConfig
|
from pyasic import APIError, MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard, X19Error
|
from pyasic.data import Fan, HashBoard, X19Error
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
@@ -95,9 +93,13 @@ class ElphapexMiner(StockFirmware):
|
|||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_elphapex(data)
|
self.config = MinerConfig.from_elphapex(data)
|
||||||
|
if self.config is None:
|
||||||
|
self.config = MinerConfig()
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
await self.web.set_miner_conf(config.as_elphapex(user_suffix=user_suffix))
|
await self.web.set_miner_conf(config.as_elphapex(user_suffix=user_suffix))
|
||||||
|
|
||||||
@@ -106,14 +108,14 @@ class ElphapexMiner(StockFirmware):
|
|||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B000":
|
if data.get("code") == "B000":
|
||||||
self.light = True
|
self.light = True
|
||||||
return self.light
|
return self.light if self.light is not None else False
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
data = await self.web.blink(blink=False)
|
data = await self.web.blink(blink=False)
|
||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B100":
|
if data.get("code") == "B100":
|
||||||
self.light = False
|
self.light = False
|
||||||
return self.light
|
return self.light if self.light is not None else False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
@@ -121,7 +123,7 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_api_ver(self, web_summary: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -136,7 +138,7 @@ class ElphapexMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_get_system_info: dict | None = None) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -155,7 +157,9 @@ class ElphapexMiner(StockFirmware):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_hostname(
|
||||||
|
self, web_get_system_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -167,8 +171,9 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return web_get_system_info["hostname"]
|
return web_get_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_get_system_info: dict | None = None) -> str | None:
|
||||||
if web_get_system_info is None:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
@@ -187,8 +192,11 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return data["macaddr"]
|
return data["macaddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
async def _get_errors( # type: ignore[override]
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> list[X19Error]:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -208,7 +216,7 @@ class ElphapexMiner(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def _get_hashboards(self, web_stats: dict | None = None) -> List[HashBoard]:
|
async def _get_hashboards(self, web_stats: dict | None = None) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -227,15 +235,19 @@ class ElphapexMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
for board in web_stats["STATS"][0]["chain"]:
|
for board in web_stats["STATS"][0]["chain"]:
|
||||||
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
hashboards[board["index"]].hashrate = self.algo.hashrate(
|
||||||
rate=board["rate_real"], unit=self.algo.unit.MH
|
rate=board["rate_real"],
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[board["index"]].chips = board["asic_num"]
|
hashboards[board["index"]].chips = board["asic_num"]
|
||||||
board_temp_data = list(
|
board_temp_data = list(
|
||||||
filter(lambda x: not x == 0, board["temp_pcb"])
|
filter(lambda x: not x == 0, board["temp_pcb"])
|
||||||
)
|
)
|
||||||
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
if not len(board_temp_data) == 0:
|
||||||
board_temp_data
|
hashboards[board["index"]].temp = sum(board_temp_data) / len(
|
||||||
)
|
board_temp_data
|
||||||
|
)
|
||||||
chip_temp_data = list(
|
chip_temp_data = list(
|
||||||
filter(lambda x: not x == "", board["temp_chip"])
|
filter(lambda x: not x == "", board["temp_chip"])
|
||||||
)
|
)
|
||||||
@@ -249,8 +261,8 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fault_light(
|
async def _get_fault_light(
|
||||||
self, web_get_blink_status: dict = None
|
self, web_get_blink_status: dict | None = None
|
||||||
) -> Optional[bool]:
|
) -> bool | None:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
@@ -268,8 +280,8 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, web_stats: dict = None
|
self, web_stats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_stats is None:
|
if web_stats is None:
|
||||||
try:
|
try:
|
||||||
web_stats = await self.web.stats()
|
web_stats = await self.web.stats()
|
||||||
@@ -285,11 +297,12 @@ class ElphapexMiner(StockFirmware):
|
|||||||
rate_unit = "MH"
|
rate_unit = "MH"
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
rate=float(expected_rate), unit=self.algo.unit.from_str(rate_unit)
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _is_mining(self, web_get_miner_conf: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_get_miner_conf: dict | None = None) -> bool | None:
|
||||||
if web_get_miner_conf is None:
|
if web_get_miner_conf is None:
|
||||||
try:
|
try:
|
||||||
web_get_miner_conf = await self.web.get_miner_conf()
|
web_get_miner_conf = await self.web.get_miner_conf()
|
||||||
@@ -305,8 +318,9 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return False
|
return False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_summary: dict | None = None) -> int | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -318,8 +332,9 @@ class ElphapexMiner(StockFirmware):
|
|||||||
return int(web_summary["SUMMARY"][1]["elapsed"])
|
return int(web_summary["SUMMARY"][1]["elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, web_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_stats: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -339,13 +354,16 @@ class ElphapexMiner(StockFirmware):
|
|||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
|
async def _get_pools(self, web_pools: dict | None = None) -> list[PoolMetrics]:
|
||||||
if web_pools is None:
|
if web_pools is None:
|
||||||
try:
|
try:
|
||||||
web_pools = await self.web.pools()
|
web_pools = await self.web.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
if web_pools is None:
|
||||||
|
return []
|
||||||
|
|
||||||
active_pool_index = None
|
active_pool_index = None
|
||||||
highest_priority = float("inf")
|
highest_priority = float("inf")
|
||||||
|
|
||||||
@@ -358,23 +376,22 @@ class ElphapexMiner(StockFirmware):
|
|||||||
active_pool_index = pool_info["index"]
|
active_pool_index = pool_info["index"]
|
||||||
|
|
||||||
pools_data = []
|
pools_data = []
|
||||||
if web_pools is not None:
|
try:
|
||||||
try:
|
for pool_info in web_pools["POOLS"]:
|
||||||
for pool_info in web_pools["POOLS"]:
|
url = pool_info.get("url")
|
||||||
url = pool_info.get("url")
|
pool_url = PoolUrl.from_str(url) if url else None
|
||||||
pool_url = PoolUrl.from_str(url) if url else None
|
pool_data = PoolMetrics(
|
||||||
pool_data = PoolMetrics(
|
accepted=pool_info.get("accepted"),
|
||||||
accepted=pool_info.get("accepted"),
|
rejected=pool_info.get("rejected"),
|
||||||
rejected=pool_info.get("rejected"),
|
get_failures=pool_info.get("stale"),
|
||||||
get_failures=pool_info.get("stale"),
|
remote_failures=pool_info.get("discarded"),
|
||||||
remote_failures=pool_info.get("discarded"),
|
active=pool_info.get("index") == active_pool_index,
|
||||||
active=pool_info.get("index") == active_pool_index,
|
alive=pool_info.get("status") == "Alive",
|
||||||
alive=pool_info.get("status") == "Alive",
|
url=pool_url,
|
||||||
url=pool_url,
|
user=pool_info.get("user"),
|
||||||
user=pool_info.get("user"),
|
index=pool_info.get("index"),
|
||||||
index=pool_info.get("index"),
|
)
|
||||||
)
|
pools_data.append(pool_data)
|
||||||
pools_data.append(pool_data)
|
except LookupError:
|
||||||
except LookupError:
|
pass
|
||||||
pass
|
|
||||||
return pools_data
|
return pools_data
|
||||||
|
|||||||
@@ -14,14 +14,12 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate, ScryptAlgo
|
from pyasic.device.algorithm import AlgoHashRateType, ScryptAlgo
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
@@ -116,7 +114,9 @@ class ePIC(ePICFirmware):
|
|||||||
self.config = cfg
|
self.config = cfg
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(
|
||||||
|
self, config: MinerConfig, user_suffix: str | None = None
|
||||||
|
) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
conf = self.config.as_epic(user_suffix=user_suffix)
|
conf = self.config.as_epic(user_suffix=user_suffix)
|
||||||
|
|
||||||
@@ -180,7 +180,7 @@ class ePIC(ePICFirmware):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_mac(self, web_network: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_network: dict | None = None) -> str | None:
|
||||||
if web_network is None:
|
if web_network is None:
|
||||||
try:
|
try:
|
||||||
web_network = await self.web.network()
|
web_network = await self.web.network()
|
||||||
@@ -194,8 +194,9 @@ class ePIC(ePICFirmware):
|
|||||||
return mac
|
return mac
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hostname(self, web_summary: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -208,8 +209,9 @@ class ePIC(ePICFirmware):
|
|||||||
return hostname
|
return hostname
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_summary: dict | None = None) -> int | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -223,8 +225,11 @@ class ePIC(ePICFirmware):
|
|||||||
return wattage
|
return wattage
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -238,14 +243,16 @@ class ePIC(ePICFirmware):
|
|||||||
for hb in web_summary["HBs"]:
|
for hb in web_summary["HBs"]:
|
||||||
hashrate += hb["Hashrate"][0]
|
hashrate += hb["Hashrate"][0]
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.MH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.TH)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.TH) # type: ignore[attr-defined]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, web_summary: dict = None
|
self, web_summary: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -264,12 +271,14 @@ class ePIC(ePICFirmware):
|
|||||||
|
|
||||||
hashrate += hb["Hashrate"][0] / ideal
|
hashrate += hb["Hashrate"][0] / ideal
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.MH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -283,8 +292,9 @@ class ePIC(ePICFirmware):
|
|||||||
return fw_ver
|
return fw_ver
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_summary: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -305,8 +315,8 @@ class ePIC(ePICFirmware):
|
|||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, web_summary: dict = None, web_capabilities: dict = None
|
self, web_summary: dict | None = None, web_capabilities: dict | None = None
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -362,16 +372,18 @@ class ePIC(ePICFirmware):
|
|||||||
# Update the Hashboard object
|
# Update the Hashboard object
|
||||||
hb_list[hb["Index"]].missing = False
|
hb_list[hb["Index"]].missing = False
|
||||||
hb_list[hb["Index"]].hashrate = self.algo.hashrate(
|
hb_list[hb["Index"]].hashrate = self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.MH
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
hb_list[hb["Index"]].chips = num_of_chips
|
hb_list[hb["Index"]].chips = num_of_chips
|
||||||
hb_list[hb["Index"]].temp = int(hb["Temperature"])
|
hb_list[hb["Index"]].temp = int(hb["Temperature"])
|
||||||
hb_list[hb["Index"]].tuned = tuned
|
hb_list[hb["Index"]].tuned = tuned
|
||||||
hb_list[hb["Index"]].active = active
|
hb_list[hb["Index"]].active = active
|
||||||
hb_list[hb["Index"]].voltage = hb["Input Voltage"]
|
hb_list[hb["Index"]].voltage = hb["Input Voltage"]
|
||||||
return hb_list
|
return hb_list
|
||||||
|
return hb_list
|
||||||
|
|
||||||
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
|
async def _is_mining(self, web_summary: dict | None = None) -> bool | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -383,8 +395,9 @@ class ePIC(ePICFirmware):
|
|||||||
return not op_state == "Idling"
|
return not op_state == "Idling"
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_summary: dict | None = None) -> int | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -399,7 +412,7 @@ class ePIC(ePICFirmware):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, web_summary: dict = None) -> Optional[bool]:
|
async def _get_fault_light(self, web_summary: dict | None = None) -> bool | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -414,7 +427,9 @@ class ePIC(ePICFirmware):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
async def _get_errors(
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> list[MinerErrorData]:
|
||||||
if not web_summary:
|
if not web_summary:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -427,12 +442,11 @@ class ePIC(ePICFirmware):
|
|||||||
error = web_summary["Status"]["Last Error"]
|
error = web_summary["Status"]["Last Error"]
|
||||||
if error is not None:
|
if error is not None:
|
||||||
errors.append(X19Error(error_message=str(error)))
|
errors.append(X19Error(error_message=str(error)))
|
||||||
return errors
|
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return errors
|
return errors # type: ignore[return-value]
|
||||||
|
|
||||||
async def _get_pools(self, web_summary: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, web_summary: dict | None = None) -> list[PoolMetrics]:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -466,18 +480,29 @@ class ePIC(ePICFirmware):
|
|||||||
return pool_data
|
return pool_data
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
async def upgrade_firmware(
|
async def upgrade_firmware(
|
||||||
self, file: Path | str, keep_settings: bool = True
|
self,
|
||||||
|
*,
|
||||||
|
file: str | None = None,
|
||||||
|
url: str | None = None,
|
||||||
|
version: str | None = None,
|
||||||
|
keep_settings: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Upgrade the firmware of the ePIC miner device.
|
Upgrade the firmware of the ePIC miner device.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
file (Path | str): The local file path of the firmware to be uploaded.
|
file: The local file path of the firmware to be uploaded.
|
||||||
keep_settings (bool): Whether to keep the current settings after the update.
|
url: The URL to download the firmware from. Must be a valid URL if provided.
|
||||||
|
version: The version of the firmware to upgrade to. If None, the version will be inferred from the file or URL.
|
||||||
|
keep_settings: Whether to keep the current settings after the update.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: Whether the firmware update succeeded.
|
bool: Whether the firmware update succeeded.
|
||||||
"""
|
"""
|
||||||
return await self.web.system_update(file=file, keep_settings=keep_settings)
|
if file is not None:
|
||||||
|
await self.web.system_update(file=file, keep_settings=keep_settings)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user