Compare commits
35 Commits
v0.75.0
...
pre-commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e6067aef5 | ||
|
|
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 |
@@ -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.2
|
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.14.4
|
||||||
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
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
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,14 +1,14 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Byte Models
|
## byte Models
|
||||||
|
|
||||||
## Byte (Stock)
|
## Byte (Stock)
|
||||||
|
|
||||||
- [x] Shutdowns
|
- [ ] Shutdowns
|
||||||
- [ ] Power Modes
|
- [ ] Power Modes
|
||||||
- [ ] Setpoints
|
- [ ] Setpoints
|
||||||
- [ ] Presets
|
- [ ] Presets
|
||||||
|
|
||||||
::: pyasic.miners.goldshell.bfgminer.Byte.Byte.GoldshellByte
|
::: pyasic.miners.goldshell.bfgminer.byte.byte.GoldshellByte
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
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>
|
||||||
@@ -107,6 +109,7 @@ 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_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_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>
|
||||||
@@ -600,12 +603,6 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>Stock Firmware Goldshells:</summary>
|
<summary>Stock Firmware Goldshells:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
|
||||||
<summary>Byte Series:</summary>
|
|
||||||
<ul>
|
|
||||||
<li><a href="../goldshell/Byte#byte-stock">Byte (Stock)</a></li>
|
|
||||||
</ul>
|
|
||||||
</details>
|
|
||||||
<details>
|
<details>
|
||||||
<summary>X5 Series:</summary>
|
<summary>X5 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
@@ -627,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>
|
||||||
@@ -740,6 +749,7 @@ details {
|
|||||||
<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-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>
|
||||||
</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
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ nav:
|
|||||||
- 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 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"
|
||||||
|
|||||||
1099
poetry.lock
generated
1099
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 {
|
||||||
@@ -248,7 +286,12 @@ class MinerConfig(BaseModel):
|
|||||||
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
return cls(pools=PoolConfig.from_am_modern(web_conf))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_goldshell_byte(cls, web_conf: dict) -> "MinerConfig":
|
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."""
|
"""Constructs a MinerConfig object from web configuration for Goldshell Byte miners."""
|
||||||
return cls(pools=PoolConfig.from_goldshell_byte(web_conf))
|
return cls(pools=PoolConfig.from_goldshell_byte(web_conf))
|
||||||
|
|
||||||
@@ -355,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,13 +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_target_kwargs: dict[str, Any] = {}
|
||||||
if self.scaling.step is not None:
|
if self.scaling.step is not None:
|
||||||
power_target_kwargs["power_step"] = Power(self.scaling.step)
|
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)),
|
||||||
@@ -296,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"]
|
||||||
@@ -331,14 +365,16 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
return {"autotuning": conf}
|
return {"autotuning": conf}
|
||||||
|
|
||||||
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]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -349,17 +385,17 @@ 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 = {}
|
hashrate_target_kwargs: dict[str, Any] = {}
|
||||||
if self.scaling.step is not None:
|
if self.scaling.step is not None:
|
||||||
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
|
hashrate_target_kwargs["hashrate_step"] = TeraHashrate(
|
||||||
self.scaling.step
|
terahash_per_second=float(self.scaling.step)
|
||||||
)
|
)
|
||||||
if self.scaling.minimum is not None:
|
if self.scaling.minimum is not None:
|
||||||
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
|
hashrate_target_kwargs["min_hashrate_target"] = TeraHashrate(
|
||||||
self.scaling.minimum
|
terahash_per_second=float(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(
|
target=DpsTarget(
|
||||||
@@ -375,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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -416,7 +456,7 @@ class MiningModePreset(MinerConfigValue):
|
|||||||
web_overclock_settings: dict,
|
web_overclock_settings: dict,
|
||||||
web_presets: list[dict],
|
web_presets: list[dict],
|
||||||
web_perf_summary: dict,
|
web_perf_summary: dict,
|
||||||
) -> "MiningModePreset":
|
) -> MiningModePreset:
|
||||||
active_preset = web_perf_summary.get("current_preset")
|
active_preset = web_perf_summary.get("current_preset")
|
||||||
|
|
||||||
if active_preset is None:
|
if active_preset is None:
|
||||||
@@ -425,12 +465,12 @@ class MiningModePreset(MinerConfigValue):
|
|||||||
active_preset = 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,
|
||||||
@@ -448,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):
|
||||||
@@ -456,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:
|
||||||
@@ -484,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:
|
||||||
@@ -512,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"]
|
||||||
@@ -526,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:
|
||||||
@@ -566,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()
|
||||||
|
|
||||||
@@ -578,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 == "":
|
||||||
@@ -597,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 == "":
|
||||||
@@ -611,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 == "":
|
||||||
@@ -625,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:
|
||||||
@@ -664,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"]
|
||||||
@@ -711,21 +756,19 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(
|
def from_vnish(
|
||||||
cls, web_settings: dict, web_presets: list[dict], web_perf_summary: dict
|
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(
|
return cls.preset.from_vnish(mode_settings, web_presets, web_perf_summary)
|
||||||
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):
|
||||||
@@ -771,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":
|
||||||
@@ -788,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":
|
||||||
@@ -815,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,17 +673,17 @@ 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_goldshell_byte(cls, web_pools: list) -> "PoolConfig":
|
def from_goldshell_byte(cls, web_pools: list) -> PoolConfig:
|
||||||
return cls(
|
return cls(
|
||||||
groups=[
|
groups=[
|
||||||
PoolGroup.from_goldshell(g["pools"])
|
PoolGroup.from_goldshell(g["pools"])
|
||||||
@@ -642,25 +693,25 @@ class PoolConfig(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
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=[
|
||||||
@@ -672,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
|
||||||
@@ -83,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
|
||||||
@@ -193,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:
|
||||||
@@ -202,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:
|
||||||
@@ -221,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:
|
||||||
@@ -232,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:
|
||||||
@@ -249,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:
|
||||||
@@ -259,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:
|
||||||
@@ -269,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:
|
||||||
@@ -282,7 +298,7 @@ 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)
|
efficiency = self._efficiency(0)
|
||||||
@@ -291,7 +307,7 @@ class MinerData(BaseModel):
|
|||||||
else:
|
else:
|
||||||
return int(efficiency)
|
return int(efficiency)
|
||||||
|
|
||||||
@computed_field # type: ignore[misc]
|
@computed_field # type: ignore[prop-decorator]
|
||||||
@property
|
@property
|
||||||
def efficiency_fract(self) -> float | None:
|
def efficiency_fract(self) -> float | None:
|
||||||
return self._efficiency(2)
|
return self._efficiency(2)
|
||||||
@@ -304,39 +320,43 @@ class MinerData(BaseModel):
|
|||||||
except ZeroDivisionError:
|
except ZeroDivisionError:
|
||||||
return 0.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())
|
||||||
@@ -416,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:
|
||||||
@@ -460,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,
|
||||||
@@ -498,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
|
||||||
@@ -77,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}"
|
||||||
|
|
||||||
@@ -90,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 = {
|
||||||
@@ -117,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,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
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.blockflow import BlockFlowUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class BlockFlowHashRate(AlgoHashRateType):
|
class BlockFlowHashRate(AlgoHashRateType[BlockFlowUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: BlockFlowUnit = HashUnit.BLOCKFLOW.default
|
unit: BlockFlowUnit = HashUnit.BLOCKFLOW.default
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ from pyasic.device.algorithm.hashrate.unit.zksnark import ZkSnarkUnit
|
|||||||
from .unit import HashUnit
|
from .unit import HashUnit
|
||||||
|
|
||||||
|
|
||||||
class ZkSnarkHashRate(AlgoHashRateType):
|
class ZkSnarkHashRate(AlgoHashRateType[ZkSnarkUnit]):
|
||||||
rate: float
|
rate: float
|
||||||
unit: ZkSnarkUnit = HashUnit.ZKSNARK.default
|
unit: ZkSnarkUnit = HashUnit.ZKSNARK.default
|
||||||
|
|
||||||
|
|||||||
@@ -224,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"
|
||||||
@@ -480,6 +481,7 @@ class GoldshellModels(MinerModelType):
|
|||||||
KDBoxII = "KD Box II"
|
KDBoxII = "KD Box II"
|
||||||
KDBoxPro = "KD Box Pro"
|
KDBoxPro = "KD Box Pro"
|
||||||
Byte = "Byte"
|
Byte = "Byte"
|
||||||
|
MiniDoge = "Mini Doge"
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.value
|
return self.value
|
||||||
@@ -561,12 +563,18 @@ 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"
|
DG1 = "DG1"
|
||||||
DG1Plus = "DG1+"
|
DG1Plus = "DG1+"
|
||||||
DG1Home = "DG1Home"
|
DG1Home = "DG1Home"
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
class MinerModel:
|
class MinerModel:
|
||||||
ANTMINER = AntminerModels
|
ANTMINER = AntminerModels
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import LUXMiner
|
from pyasic.miners.backends import LUXMiner
|
||||||
from pyasic.miners.device.models import S21, T21
|
from pyasic.miners.device.models import T21
|
||||||
|
|
||||||
|
|
||||||
class LUXMinerT21(LUXMiner, T21):
|
class LUXMinerT21(LUXMiner, T21):
|
||||||
|
|||||||
@@ -87,7 +87,3 @@ class VNishS19ProHydro(VNish, S19ProHydro):
|
|||||||
|
|
||||||
class VNishS19kPro(VNish, S19kPro):
|
class VNishS19kPro(VNish, S19kPro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class VNishS19ProA(VNish, S19ProA):
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -13,11 +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 typing import Any
|
||||||
|
|
||||||
from pyasic import APIError
|
from pyasic import APIError
|
||||||
from pyasic.data.boards import HashBoard
|
from pyasic.data.boards import HashBoard
|
||||||
from pyasic.device.algorithm.hashrate import AlgoHashRate
|
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,
|
||||||
@@ -150,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:
|
||||||
@@ -164,64 +164,64 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
|
|||||||
return mac.upper()
|
return mac.upper()
|
||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
|
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
|
||||||
|
|
||||||
data_locations = AVALON_NANO3S_DATA_LOC
|
data_locations = AVALON_NANO3S_DATA_LOC
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_estats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_estats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_estats = self.parse_estats(unparsed_estats)
|
|
||||||
return int(parsed_estats["PS"][6])
|
return int(parsed_estats["PS"][6])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_estats: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_estats: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_estats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_estats = self.parse_estats(unparsed_estats)
|
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(parsed_estats["GHSspd"]), unit=self.algo.unit.GH
|
rate=float(parsed_estats["GHSspd"]), unit=self.algo.unit.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, rpc_estats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_estats: dict | None = None) -> list[HashBoard]:
|
||||||
hashboards = await AvalonMiner._get_hashboards(self, rpc_estats)
|
hashboards = await AvalonMiner._get_hashboards(self, rpc_estats)
|
||||||
|
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return hashboards
|
||||||
|
|
||||||
if rpc_estats is not None:
|
if rpc_estats is not None:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
parsed_estats = self.parse_estats(unparsed_estats)
|
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
for board in range(len(hashboards)):
|
for board in range(len(hashboards)):
|
||||||
try:
|
try:
|
||||||
board_hr = parsed_estats["GHSspd"][board]
|
board_hr = parsed_estats["GHSspd"]
|
||||||
hashboards[board].hashrate = self.algo.hashrate(
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
rate=float(board_hr), unit=self.algo.unit.GH
|
rate=float(board_hr), unit=self.algo.unit.GH
|
||||||
).into(self.algo.unit.default)
|
).into(self.algo.unit.default)
|
||||||
|
|||||||
@@ -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,8 +303,11 @@ 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"]
|
||||||
|
|
||||||
if "S21+ Hyd" in self.model:
|
if "S21+ Hyd" in self.model:
|
||||||
@@ -320,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
|
||||||
|
|
||||||
@@ -339,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()
|
||||||
@@ -356,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,
|
||||||
@@ -366,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,
|
||||||
@@ -379,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
|
||||||
)
|
)
|
||||||
@@ -402,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()
|
||||||
@@ -418,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()
|
||||||
@@ -431,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()
|
||||||
@@ -521,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
|
||||||
@@ -545,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)
|
||||||
@@ -556,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()
|
||||||
@@ -565,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
|
||||||
|
|
||||||
@@ -583,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()
|
||||||
@@ -595,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 []
|
||||||
|
|
||||||
@@ -621,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:
|
||||||
@@ -670,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:
|
||||||
@@ -688,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()
|
||||||
@@ -713,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()
|
||||||
@@ -725,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
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
import copy
|
import copy
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
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
|
||||||
@@ -119,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":
|
||||||
@@ -143,7 +142,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
# Shut off 5 seconds from now
|
# Shut off 5 seconds from now
|
||||||
timestamp = int(time.time()) + 5
|
timestamp = int(time.time()) + 5
|
||||||
data = await self.rpc.ascset(0, f"softoff", f"1:{timestamp}")
|
data = await self.rpc.ascset(0, "softoff", f"1:{timestamp}")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if "success" in data["STATUS"][0]["Msg"]:
|
if "success" in data["STATUS"][0]["Msg"]:
|
||||||
@@ -154,7 +153,7 @@ class AvalonMiner(CGMiner):
|
|||||||
try:
|
try:
|
||||||
# Shut off 5 seconds from now
|
# Shut off 5 seconds from now
|
||||||
timestamp = int(time.time()) + 5
|
timestamp = int(time.time()) + 5
|
||||||
data = await self.rpc.ascset(0, f"softon", f"1:{timestamp}")
|
data = await self.rpc.ascset(0, "softon", f"1:{timestamp}")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if "success" in data["STATUS"][0]["Msg"]:
|
if "success" in data["STATUS"][0]["Msg"]:
|
||||||
@@ -232,7 +231,7 @@ class AvalonMiner(CGMiner):
|
|||||||
### 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()
|
||||||
@@ -249,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_estats: 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 []
|
||||||
|
|
||||||
@@ -287,17 +291,22 @@ class AvalonMiner(CGMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
for board in range(self.expected_hashboards):
|
for board in range(self.expected_hashboards):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
board_hr = parsed_estats["STATS"][0]["MM ID0"]["MGHS"]
|
board_hr = parsed_estats["STATS"][0]["MM ID0"]["MGHS"]
|
||||||
if isinstance(board_hr, list):
|
if isinstance(board_hr, list):
|
||||||
hashboards[board].hashrate = self.algo.hashrate(
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
rate=float(board_hr[board]), unit=self.algo.unit.GH
|
rate=float(board_hr[board]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
hashboards[board].hashrate = self.algo.hashrate(
|
hashboards[board].hashrate = self.algo.hashrate(
|
||||||
rate=float(board_hr), unit=self.algo.unit.GH
|
rate=float(board_hr),
|
||||||
).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
|
||||||
@@ -306,10 +315,12 @@ class AvalonMiner(CGMiner):
|
|||||||
hashboards[board].chip_temp = int(
|
hashboards[board].chip_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
)
|
)
|
||||||
except LookupError:
|
except (LookupError, TypeError):
|
||||||
try:
|
try:
|
||||||
hashboards[board].chip_temp = int(
|
hashboards[board].chip_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["Tmax"]
|
parsed_estats["STATS"][0]["MM ID0"].get(
|
||||||
|
"Tmax", parsed_estats["STATS"][0]["MM ID0"]["TMax"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -318,10 +329,12 @@ class AvalonMiner(CGMiner):
|
|||||||
hashboards[board].temp = int(
|
hashboards[board].temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
)
|
)
|
||||||
except LookupError:
|
except (LookupError, TypeError):
|
||||||
try:
|
try:
|
||||||
hashboards[board].temp = int(
|
hashboards[board].temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["Tavg"]
|
parsed_estats["STATS"][0]["MM ID0"].get(
|
||||||
|
"Tavg", parsed_estats["STATS"][0]["MM ID0"]["TAvg"]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -330,7 +343,7 @@ class AvalonMiner(CGMiner):
|
|||||||
hashboards[board].inlet_temp = int(
|
hashboards[board].inlet_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["MTavg"][board]
|
parsed_estats["STATS"][0]["MM ID0"]["MTavg"][board]
|
||||||
)
|
)
|
||||||
except LookupError:
|
except (LookupError, TypeError):
|
||||||
try:
|
try:
|
||||||
hashboards[board].inlet_temp = int(
|
hashboards[board].inlet_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["HBITemp"]
|
parsed_estats["STATS"][0]["MM ID0"]["HBITemp"]
|
||||||
@@ -342,7 +355,7 @@ class AvalonMiner(CGMiner):
|
|||||||
hashboards[board].outlet_temp = int(
|
hashboards[board].outlet_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
|
||||||
)
|
)
|
||||||
except LookupError:
|
except (LookupError, TypeError):
|
||||||
try:
|
try:
|
||||||
hashboards[board].outlet_temp = int(
|
hashboards[board].outlet_temp = int(
|
||||||
parsed_estats["STATS"][0]["MM ID0"]["HBOTemp"]
|
parsed_estats["STATS"][0]["MM ID0"]["HBOTemp"]
|
||||||
@@ -357,7 +370,7 @@ class AvalonMiner(CGMiner):
|
|||||||
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):
|
||||||
try:
|
try:
|
||||||
chip_data = parsed_estats["STATS"][0]["HBinfo"][f"HB{board}"][
|
chip_data = parsed_estats["STATS"][0]["HBinfo"][f"HB{board}"][
|
||||||
f"PVT_T{board}"
|
f"PVT_T{board}"
|
||||||
@@ -373,24 +386,26 @@ class AvalonMiner(CGMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_estats: dict = None
|
self, rpc_estats: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_estats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(parsed_estats["GHSmm"]), 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_estats: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_estats: dict | None = None) -> float | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
@@ -403,13 +418,14 @@ class AvalonMiner(CGMiner):
|
|||||||
return float(parsed_estats["Temp"])
|
return float(parsed_estats["Temp"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self, rpc_estats: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if rpc_estats is not None:
|
if rpc_estats is not None:
|
||||||
try:
|
try:
|
||||||
@@ -417,8 +433,9 @@ class AvalonMiner(CGMiner):
|
|||||||
return int(parsed_estats["MPO"])
|
return int(parsed_estats["MPO"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_estats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_estats: dict | None = None) -> int | None:
|
||||||
if rpc_estats is None:
|
if rpc_estats is None:
|
||||||
try:
|
try:
|
||||||
rpc_estats = await self.rpc.estats()
|
rpc_estats = await self.rpc.estats()
|
||||||
@@ -431,8 +448,9 @@ class AvalonMiner(CGMiner):
|
|||||||
return int(parsed_estats["WALLPOWER"])
|
return int(parsed_estats["WALLPOWER"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, rpc_estats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_estats: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -456,7 +474,7 @@ class AvalonMiner(CGMiner):
|
|||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
async def _get_fault_light(self, rpc_estats: 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_estats is None:
|
if rpc_estats is None:
|
||||||
|
|||||||
@@ -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,51 @@
|
|||||||
# 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
|
|
||||||
|
|
||||||
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, BTMinerV3RPCAPI
|
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(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
@@ -118,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
|
||||||
@@ -207,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)
|
||||||
@@ -278,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)
|
||||||
@@ -286,13 +320,14 @@ 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
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
if rpc_get_miner_info is None:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
@@ -320,7 +355,9 @@ class BTMiner(StockFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_api_ver(self, rpc_get_version: dict = None) -> str | None:
|
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()
|
||||||
@@ -338,13 +375,12 @@ 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
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
if rpc_get_version is None:
|
if rpc_get_version is None:
|
||||||
try:
|
try:
|
||||||
@@ -378,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) -> str | None:
|
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:
|
||||||
@@ -394,7 +430,9 @@ class BTMiner(StockFirmware):
|
|||||||
|
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_summary: dict = None) -> AlgoHashRate | None:
|
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()
|
||||||
@@ -405,13 +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
|
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) -> float | None:
|
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()
|
||||||
@@ -464,7 +505,7 @@ class BTMiner(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_summary: dict = None) -> int | None:
|
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()
|
||||||
@@ -479,7 +520,7 @@ class BTMiner(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self, rpc_summary: dict = None) -> int | None:
|
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()
|
||||||
@@ -494,7 +535,7 @@ class BTMiner(StockFirmware):
|
|||||||
return None
|
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 []
|
||||||
@@ -519,7 +560,7 @@ 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
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
if rpc_summary is None:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
@@ -547,7 +588,7 @@ class BTMiner(StockFirmware):
|
|||||||
return None
|
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:
|
||||||
@@ -581,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
|
||||||
) -> AlgoHashRate | 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()
|
||||||
@@ -597,14 +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
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> bool | None:
|
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()
|
||||||
@@ -626,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()
|
||||||
@@ -642,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) -> bool | None:
|
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()
|
||||||
@@ -662,7 +708,7 @@ class BTMiner(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_uptime(self, rpc_summary: dict = None) -> int | None:
|
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()
|
||||||
@@ -674,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()
|
||||||
@@ -705,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.")
|
||||||
@@ -725,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
|
||||||
@@ -753,67 +810,67 @@ class BTMiner(StockFirmware):
|
|||||||
BTMINERV3_DATA_LOC = DataLocations(
|
BTMINERV3_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_version",
|
"_get_api_version",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_firmware_version",
|
"_get_firmware_version",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_light_flashing",
|
"_get_light_flashing",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit",
|
"_get_wattage_limit",
|
||||||
[RPCAPICommand("rpc_get_device_info", "get_device_info")],
|
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans",
|
"_get_fans",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction(
|
str(DataOptions.FAN_PSU): DataFunction(
|
||||||
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get_device_info")]
|
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[
|
[
|
||||||
RPCAPICommand("rpc_get_device_info", "get_device_info"),
|
RPCAPICommand("rpc_get_device_info", "get.device.info"),
|
||||||
RPCAPICommand(
|
RPCAPICommand(
|
||||||
"rpc_get_miner_status_edevs",
|
"rpc_get_miner_status_edevs",
|
||||||
"get_miner_status_edevs",
|
"get.miner.status:edevs",
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.POOLS): DataFunction(
|
str(DataOptions.POOLS): DataFunction(
|
||||||
"_get_pools",
|
"_get_pools",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime",
|
"_get_uptime",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage",
|
"_get_wattage",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate",
|
"_get_hashrate",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate",
|
"_get_expected_hashrate",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp",
|
"_get_env_temp",
|
||||||
[RPCAPICommand("rpc_get_miner_status_summary", "get_miner_status_summary")],
|
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -829,6 +886,39 @@ class BTMinerV3(StockFirmware):
|
|||||||
supports_autotuning = True
|
supports_autotuning = True
|
||||||
supports_power_modes = 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:
|
async def fault_light_off(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.rpc.set_system_led()
|
data = await self.rpc.set_system_led()
|
||||||
@@ -844,11 +934,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.rpc.set_system_led(
|
data = await self.rpc.set_system_led(
|
||||||
leds=[
|
leds=[{"color": "red", "period": 60, "duration": 20, "start": 0}]
|
||||||
{
|
|
||||||
{"color": "red", "period": 60, "duration": 20, "start": 0},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
@@ -864,7 +950,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
data = await self.rpc.set_system_reboot()
|
data = await self.rpc.set_system_reboot()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("msg"):
|
if data and data.get("msg"):
|
||||||
if data["msg"] == "ok":
|
if data["msg"] == "ok":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -874,7 +960,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
data = await self.rpc.set_miner_service("restart")
|
data = await self.rpc.set_miner_service("restart")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("msg"):
|
if data and data.get("msg"):
|
||||||
if data["msg"] == "ok":
|
if data["msg"] == "ok":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -884,7 +970,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
data = await self.rpc.set_miner_service("stop")
|
data = await self.rpc.set_miner_service("stop")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("msg"):
|
if data and data.get("msg"):
|
||||||
if data["msg"] == "ok":
|
if data["msg"] == "ok":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -894,7 +980,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
data = await self.rpc.set_miner_service("start")
|
data = await self.rpc.set_miner_service("start")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("msg"):
|
if data and data.get("msg"):
|
||||||
if data["msg"] == "ok":
|
if data["msg"] == "ok":
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -908,74 +994,94 @@ class BTMinerV3(StockFirmware):
|
|||||||
else:
|
else:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _get_mac(self, rpc_get_device_info: dict = None) -> str | None:
|
async def _get_mac(self, rpc_get_device_info: dict | None = None) -> str | None:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
return rpc_get_device_info.get("msg", {}).get("network", {}).get("mac")
|
return rpc_get_device_info.get("msg", {}).get("network", {}).get("mac")
|
||||||
|
|
||||||
async def _get_api_version(self, rpc_get_device_info: dict = None) -> str | None:
|
async def _get_api_version(
|
||||||
if rpc_get_device_info is None:
|
self, rpc_get_device_info: dict | None = None
|
||||||
try:
|
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
|
||||||
except APIError:
|
|
||||||
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
|
|
||||||
) -> str | None:
|
) -> str | None:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
return rpc_get_device_info.get("msg", {}).get("system", {}).get("fwversion")
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
|
return rpc_get_device_info.get("msg", {}).get("system", {}).get("api")
|
||||||
|
|
||||||
async def _get_hostname(self, rpc_get_device_info: dict = None) -> str | None:
|
async def _get_firmware_version(
|
||||||
|
self, rpc_get_device_info: dict | None = None
|
||||||
|
) -> str | None:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
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")
|
return rpc_get_device_info.get("msg", {}).get("network", {}).get("hostname")
|
||||||
|
|
||||||
async def _get_light_flashing(
|
async def _get_light_flashing(
|
||||||
self, rpc_get_device_info: dict = None
|
self, rpc_get_device_info: dict | None = None
|
||||||
) -> bool | None:
|
) -> bool | None:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
val = rpc_get_device_info.get("msg", {}).get("system", {}).get("ledstatus")
|
val = rpc_get_device_info.get("msg", {}).get("system", {}).get("ledstatus")
|
||||||
if isinstance(val, str):
|
if isinstance(val, str):
|
||||||
return val != "auto"
|
return val != "auto"
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(
|
async def _get_wattage_limit(
|
||||||
self, rpc_get_device_info: dict = None
|
self, rpc_get_device_info: dict | None = None
|
||||||
) -> float | None:
|
) -> int | None:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return None
|
||||||
val = rpc_get_device_info.get("msg", {}).get("miner", {}).get("power-limit-set")
|
val = rpc_get_device_info.get("msg", {}).get("miner", {}).get("power-limit-set")
|
||||||
try:
|
try:
|
||||||
return float(val)
|
return int(float(val))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, rpc_get_miner_status_summary: dict = None) -> list[Fan]:
|
async def _get_fans(
|
||||||
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
|
) -> list[Fan]:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
fans = []
|
fans = []
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return []
|
||||||
summary = rpc_get_miner_status_summary.get("msg", {}).get("summary", {})
|
summary = rpc_get_miner_status_summary.get("msg", {}).get("summary", {})
|
||||||
for idx, direction in enumerate(["in", "out"]):
|
for idx, direction in enumerate(["in", "out"]):
|
||||||
rpm = summary.get(f"fan-speed-{direction}")
|
rpm = summary.get(f"fan-speed-{direction}")
|
||||||
@@ -983,19 +1089,21 @@ class BTMinerV3(StockFirmware):
|
|||||||
fans.append(Fan(speed=rpm))
|
fans.append(Fan(speed=rpm))
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_psu_fans(self, rpc_get_device_info: dict = None) -> list[Fan]:
|
async def _get_psu_fans(self, rpc_get_device_info: dict | None = None) -> list[Fan]:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_device_info = await self.rpc.get_device_info()
|
rpc_get_device_info = await self.rpc.get_device_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
|
if rpc_get_device_info is None:
|
||||||
|
return []
|
||||||
rpm = rpc_get_device_info.get("msg", {}).get("power", {}).get("fanspeed")
|
rpm = rpc_get_device_info.get("msg", {}).get("power", {}).get("fanspeed")
|
||||||
return [Fan(speed=rpm)] if rpm is not None else []
|
return [Fan(speed=rpm)] if rpm is not None else []
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self,
|
self,
|
||||||
rpc_get_device_info: dict = None,
|
rpc_get_device_info: dict | None = None,
|
||||||
rpc_get_miner_status_edevs: dict = None,
|
rpc_get_miner_status_edevs: dict | None = None,
|
||||||
) -> list[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if rpc_get_device_info is None:
|
if rpc_get_device_info is None:
|
||||||
try:
|
try:
|
||||||
@@ -1009,11 +1117,11 @@ class BTMinerV3(StockFirmware):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
boards = []
|
boards = []
|
||||||
|
if rpc_get_device_info is None or rpc_get_miner_status_edevs is None:
|
||||||
|
return []
|
||||||
board_count = (
|
board_count = (
|
||||||
rpc_get_device_info.get("msg", {}).get("hardware", {}).get("boards", 3)
|
rpc_get_device_info.get("msg", {}).get("hardware", {}).get("boards", 3)
|
||||||
)
|
)
|
||||||
print(rpc_get_miner_status_edevs)
|
|
||||||
print(rpc_get_device_info)
|
|
||||||
edevs = rpc_get_miner_status_edevs.get("msg", {}).get("edevs", [])
|
edevs = rpc_get_miner_status_edevs.get("msg", {}).get("edevs", [])
|
||||||
for idx in range(board_count):
|
for idx in range(board_count):
|
||||||
board_data = edevs[idx] if idx < len(edevs) else {}
|
board_data = edevs[idx] if idx < len(edevs) else {}
|
||||||
@@ -1021,12 +1129,17 @@ class BTMinerV3(StockFirmware):
|
|||||||
HashBoard(
|
HashBoard(
|
||||||
slot=idx,
|
slot=idx,
|
||||||
hashrate=self.algo.hashrate(
|
hashrate=self.algo.hashrate(
|
||||||
rate=board_data.get("hash-average", 0), unit=self.algo.unit.TH
|
rate=board_data.get("hash-average", 0),
|
||||||
).into(self.algo.unit.default),
|
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"),
|
temp=board_data.get("chip-temp-min"),
|
||||||
inlet_temp=board_data.get("chip-temp-min"),
|
inlet_temp=board_data.get("chip-temp-min"),
|
||||||
outlet_temp=board_data.get("chip-temp-max"),
|
outlet_temp=board_data.get("chip-temp-max"),
|
||||||
serial_number=board_data.get(f"pcbsn{idx}"),
|
serial_number=rpc_get_device_info.get("msg", {})
|
||||||
|
.get("miner", {})
|
||||||
|
.get(f"pcbsn{idx}"),
|
||||||
chips=board_data.get("effective-chips"),
|
chips=board_data.get("effective-chips"),
|
||||||
expected_chips=self.expected_chips,
|
expected_chips=self.expected_chips,
|
||||||
active=(board_data.get("hash-average") or 0) > 0,
|
active=(board_data.get("hash-average") or 0) > 0,
|
||||||
@@ -1037,7 +1150,7 @@ class BTMinerV3(StockFirmware):
|
|||||||
return boards
|
return boards
|
||||||
|
|
||||||
async def _get_pools(
|
async def _get_pools(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> list[PoolMetrics]:
|
) -> list[PoolMetrics]:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
@@ -1045,6 +1158,8 @@ class BTMinerV3(StockFirmware):
|
|||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
pools = []
|
pools = []
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return []
|
||||||
msg_pools = rpc_get_miner_status_summary.get("msg", {}).get("pools", [])
|
msg_pools = rpc_get_miner_status_summary.get("msg", {}).get("pools", [])
|
||||||
for idx, pool in enumerate(msg_pools):
|
for idx, pool in enumerate(msg_pools):
|
||||||
pools.append(
|
pools.append(
|
||||||
@@ -1059,13 +1174,15 @@ class BTMinerV3(StockFirmware):
|
|||||||
return pools
|
return pools
|
||||||
|
|
||||||
async def _get_uptime(
|
async def _get_uptime(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> int | None:
|
) -> int | None:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
return (
|
return (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
@@ -1073,27 +1190,37 @@ class BTMinerV3(StockFirmware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _get_wattage(
|
async def _get_wattage(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> float | None:
|
) -> int | None:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
return (
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
power_val = (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
.get("power-realtime")
|
.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(
|
async def _get_hashrate(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> float | None:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
|
||||||
return (
|
return (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
@@ -1101,27 +1228,37 @@ class BTMinerV3(StockFirmware):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> float | None:
|
) -> AlgoHashRateType | None:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
return (
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
|
res = (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
.get("factory-hash")
|
.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(
|
async def _get_env_temp(
|
||||||
self, rpc_get_miner_status_summary: dict = None
|
self, rpc_get_miner_status_summary: dict | None = None
|
||||||
) -> float | None:
|
) -> float | None:
|
||||||
if rpc_get_miner_status_summary is None:
|
if rpc_get_miner_status_summary is None:
|
||||||
try:
|
try:
|
||||||
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
if rpc_get_miner_status_summary is None:
|
||||||
|
return None
|
||||||
return (
|
return (
|
||||||
rpc_get_miner_status_summary.get("msg", {})
|
rpc_get_miner_status_summary.get("msg", {})
|
||||||
.get("summary", {})
|
.get("summary", {})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic import APIError, MinerConfig
|
from pyasic import APIError, 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.device.firmware import MinerFirmware
|
from pyasic.device.firmware import MinerFirmware
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
@@ -72,24 +70,28 @@ class ESPMiner(BaseMiner):
|
|||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
return MinerConfig.from_espminer(web_system_info)
|
return MinerConfig.from_espminer(web_system_info)
|
||||||
|
|
||||||
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:
|
||||||
await self.web.update_settings(**config.as_espminer())
|
await self.web.update_settings(**config.as_espminer())
|
||||||
|
|
||||||
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_system_info: dict | None = None) -> int | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_system_info is not None:
|
if web_system_info is not None:
|
||||||
try:
|
try:
|
||||||
return round(web_system_info["power"])
|
return round(web_system_info["power"])
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(
|
async def _get_hashrate(
|
||||||
self, web_system_info: dict = None
|
self, web_system_info: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -99,14 +101,18 @@ class ESPMiner(BaseMiner):
|
|||||||
if web_system_info is not None:
|
if web_system_info is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(web_system_info["hashRate"]), unit=self.algo.unit.GH
|
rate=float(web_system_info["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 KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, web_system_info: dict = None
|
self, web_system_info: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -126,15 +132,23 @@ class ESPMiner(BaseMiner):
|
|||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
expected_hashrate = small_core_count * asic_count * frequency
|
if (
|
||||||
|
small_core_count is not None
|
||||||
return self.algo.hashrate(
|
and asic_count is not None
|
||||||
rate=float(expected_hashrate), unit=self.algo.unit.MH
|
and frequency is not None
|
||||||
).into(self.algo.unit.default)
|
):
|
||||||
|
expected_hashrate = small_core_count * asic_count * frequency
|
||||||
|
return self.algo.hashrate(
|
||||||
|
rate=float(expected_hashrate),
|
||||||
|
unit=self.algo.unit.MH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_system_info: dict | None = None) -> int | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -146,8 +160,11 @@ class ESPMiner(BaseMiner):
|
|||||||
return web_system_info["uptimeSeconds"]
|
return web_system_info["uptimeSeconds"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(
|
||||||
|
self, web_system_info: dict | None = None
|
||||||
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -163,8 +180,10 @@ class ESPMiner(BaseMiner):
|
|||||||
HashBoard(
|
HashBoard(
|
||||||
hashrate=self.algo.hashrate(
|
hashrate=self.algo.hashrate(
|
||||||
rate=float(web_system_info["hashRate"]),
|
rate=float(web_system_info["hashRate"]),
|
||||||
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]
|
||||||
|
),
|
||||||
chip_temp=web_system_info.get("temp"),
|
chip_temp=web_system_info.get("temp"),
|
||||||
temp=web_system_info.get("vrTemp"),
|
temp=web_system_info.get("vrTemp"),
|
||||||
chips=web_system_info.get("asicCount", 1),
|
chips=web_system_info.get("asicCount", 1),
|
||||||
@@ -178,7 +197,7 @@ class ESPMiner(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_system_info: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -195,7 +214,7 @@ class ESPMiner(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_system_info: dict | None = None) -> str | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -207,8 +226,9 @@ class ESPMiner(BaseMiner):
|
|||||||
return web_system_info["hostname"]
|
return web_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, web_system_info: dict | None = None) -> str | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -220,8 +240,9 @@ class ESPMiner(BaseMiner):
|
|||||||
return web_system_info["version"]
|
return web_system_info["version"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_system_info: dict | None = None) -> str | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -233,8 +254,9 @@ class ESPMiner(BaseMiner):
|
|||||||
return web_system_info["version"]
|
return web_system_info["version"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_mac(self, web_system_info: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_system_info: dict | None = None) -> str | None:
|
||||||
if web_system_info is None:
|
if web_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_system_info = await self.web.system_info()
|
web_system_info = await self.web.system_info()
|
||||||
@@ -246,3 +268,4 @@ class ESPMiner(BaseMiner):
|
|||||||
return web_system_info["macAddr"].upper()
|
return web_system_info["macAddr"].upper()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import HashBoard
|
from pyasic.data import HashBoard
|
||||||
@@ -86,12 +85,15 @@ class GoldshellMiner(BFGMiner):
|
|||||||
try:
|
try:
|
||||||
pools = await self.web.pools()
|
pools = await self.web.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
if self.config is not None:
|
||||||
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_goldshell(pools)
|
self.config = MinerConfig.from_goldshell(pools)
|
||||||
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:
|
||||||
pools_data = await self.web.pools()
|
pools_data = await self.web.pools()
|
||||||
# have to delete all the pools one at a time first
|
# have to delete all the pools one at a time first
|
||||||
for pool in pools_data:
|
for pool in pools_data:
|
||||||
@@ -116,35 +118,37 @@ class GoldshellMiner(BFGMiner):
|
|||||||
settings["select"] = idx
|
settings["select"] = idx
|
||||||
await self.web.set_setting(settings)
|
await self.web.set_setting(settings)
|
||||||
|
|
||||||
async def _get_mac(self, web_setting: dict = None) -> str:
|
async def _get_mac(self, web_setting: dict | None = None) -> str | None:
|
||||||
if web_setting is None:
|
if web_setting is None:
|
||||||
try:
|
try:
|
||||||
web_setting = await self.web.setting()
|
web_setting = await self.web.setting()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_setting is not None:
|
if web_setting is not None:
|
||||||
try:
|
try:
|
||||||
return web_setting["name"]
|
return web_setting["name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_status: dict = None) -> str:
|
async def _get_fw_ver(self, web_status: dict | None = None) -> str | None:
|
||||||
if web_status is None:
|
if web_status is None:
|
||||||
try:
|
try:
|
||||||
web_status = await self.web.setting()
|
web_status = await self.web.setting()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_status is not None:
|
if web_status is not None:
|
||||||
try:
|
try:
|
||||||
return web_status["firmware"]
|
return web_status["firmware"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, rpc_devs: dict = None, rpc_devdetails: dict = None
|
self, rpc_devs: dict | None = None, rpc_devdetails: dict | None = None
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -166,8 +170,11 @@ class GoldshellMiner(BFGMiner):
|
|||||||
try:
|
try:
|
||||||
b_id = board["ID"]
|
b_id = board["ID"]
|
||||||
hashboards[b_id].hashrate = self.algo.hashrate(
|
hashboards[b_id].hashrate = self.algo.hashrate(
|
||||||
rate=float(board["MHS 20s"]), unit=self.algo.unit.MH
|
rate=float(board["MHS 20s"]),
|
||||||
).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 = board["tstemp-2"]
|
hashboards[b_id].temp = board["tstemp-2"]
|
||||||
hashboards[b_id].missing = False
|
hashboards[b_id].missing = False
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@@ -14,13 +14,13 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import cast
|
||||||
|
|
||||||
from pyasic import MinerConfig
|
from pyasic 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
|
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,
|
||||||
@@ -106,9 +106,11 @@ class BlackMiner(StockFirmware):
|
|||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_hammer(data)
|
self.config = MinerConfig.from_hammer(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_hammer(user_suffix=user_suffix))
|
await self.web.set_miner_conf(config.as_hammer(user_suffix=user_suffix))
|
||||||
|
|
||||||
@@ -117,14 +119,14 @@ class BlackMiner(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 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()
|
||||||
@@ -132,7 +134,7 @@ class BlackMiner(StockFirmware):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
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()
|
||||||
@@ -147,7 +149,7 @@ class BlackMiner(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()
|
||||||
@@ -162,7 +164,9 @@ class BlackMiner(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:
|
||||||
@@ -174,12 +178,13 @@ class BlackMiner(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(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 []
|
||||||
|
|
||||||
@@ -232,8 +237,11 @@ class BlackMiner(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.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]
|
||||||
|
)
|
||||||
|
|
||||||
chips = boards[1].get(f"chain_acn{i}")
|
chips = boards[1].get(f"chain_acn{i}")
|
||||||
if chips:
|
if chips:
|
||||||
@@ -247,7 +255,7 @@ class BlackMiner(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 []
|
||||||
|
|
||||||
@@ -272,14 +280,16 @@ class BlackMiner(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
|
||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
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()
|
||||||
@@ -291,8 +301,9 @@ class BlackMiner(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()
|
||||||
@@ -311,8 +322,11 @@ class BlackMiner(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(
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> list[MinerErrorData]:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -330,11 +344,11 @@ class BlackMiner(StockFirmware):
|
|||||||
continue
|
continue
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
return errors
|
return cast(list[MinerErrorData], errors)
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -352,8 +366,8 @@ class BlackMiner(StockFirmware):
|
|||||||
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()
|
||||||
@@ -364,16 +378,22 @@ class BlackMiner(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
expected_rate = rpc_stats["STATS"][1].get("total_rateideal")
|
expected_rate = rpc_stats["STATS"][1].get("total_rateideal")
|
||||||
if expected_rate is None:
|
if expected_rate is None:
|
||||||
return self.sticker_hashrate.into(self.algo.unit.default)
|
if (
|
||||||
|
hasattr(self, "sticker_hashrate")
|
||||||
|
and self.sticker_hashrate is not None
|
||||||
|
):
|
||||||
|
return self.sticker_hashrate.into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
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 set_static_ip(
|
async def set_static_ip(
|
||||||
self,
|
self,
|
||||||
@@ -381,10 +401,10 @@ class BlackMiner(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() or ""
|
||||||
await self.web.set_network_conf(
|
await self.web.set_network_conf(
|
||||||
ip=ip,
|
ip=ip,
|
||||||
dns=dns,
|
dns=dns,
|
||||||
@@ -394,9 +414,9 @@ class BlackMiner(StockFirmware):
|
|||||||
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
|
||||||
)
|
)
|
||||||
@@ -417,7 +437,7 @@ class BlackMiner(StockFirmware):
|
|||||||
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()
|
||||||
@@ -433,8 +453,9 @@ class BlackMiner(StockFirmware):
|
|||||||
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()
|
||||||
@@ -446,8 +467,9 @@ class BlackMiner(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,7 +13,6 @@
|
|||||||
# 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 pyasic import APIError
|
from pyasic import APIError
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
@@ -92,7 +91,7 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
web: HiveonWebAPI
|
web: HiveonWebAPI
|
||||||
_web_cls = HiveonWebAPI
|
_web_cls = HiveonWebAPI
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig | None: # type: ignore[override]
|
||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_hiveon_modern(data)
|
self.config = MinerConfig.from_hiveon_modern(data)
|
||||||
@@ -103,14 +102,16 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
if data:
|
if data:
|
||||||
if data.get("code") == "B000":
|
if data.get("code") == "B000":
|
||||||
self.light = True
|
self.light = True
|
||||||
return self.light
|
return True
|
||||||
|
return 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 True
|
||||||
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
@@ -120,17 +121,21 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
cfg.mining_mode = MiningModeConfig.sleep()
|
if cfg is not None:
|
||||||
await self.send_config(cfg)
|
cfg.mining_mode = MiningModeConfig.sleep()
|
||||||
return True
|
await self.send_config(cfg)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
cfg = await self.get_config()
|
cfg = await self.get_config()
|
||||||
cfg.mining_mode = MiningModeConfig.normal()
|
if cfg is not None:
|
||||||
await self.send_config(cfg)
|
cfg.mining_mode = MiningModeConfig.normal()
|
||||||
return True
|
await self.send_config(cfg)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if not rpc_stats:
|
if not rpc_stats:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -139,15 +144,19 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
|
|
||||||
if rpc_stats:
|
if rpc_stats:
|
||||||
boards = rpc_stats.get("STATS")
|
boards = rpc_stats.get("STATS")
|
||||||
try:
|
if boards:
|
||||||
wattage_raw = boards[1]["chain_power"]
|
try:
|
||||||
except (KeyError, IndexError):
|
wattage_raw = boards[1]["chain_power"]
|
||||||
pass
|
except (KeyError, IndexError):
|
||||||
else:
|
pass
|
||||||
# parse wattage position out of raw data
|
else:
|
||||||
return round(float(wattage_raw.split(" ")[0]))
|
# parse wattage position out of raw data
|
||||||
|
return round(float(wattage_raw.split(" ")[0]))
|
||||||
|
return None
|
||||||
|
|
||||||
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()
|
||||||
@@ -159,8 +168,9 @@ class HiveonModern(HiveonFirmware, 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()
|
||||||
@@ -179,10 +189,11 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
return data["macaddr"]
|
return data["macaddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -199,7 +210,7 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
pass
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
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()
|
||||||
@@ -215,6 +226,7 @@ class HiveonModern(HiveonFirmware, BMMiner):
|
|||||||
return False
|
return False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
HIVEON_OLD_DATA_LOC = DataLocations(
|
HIVEON_OLD_DATA_LOC = DataLocations(
|
||||||
@@ -262,7 +274,7 @@ HIVEON_OLD_DATA_LOC = DataLocations(
|
|||||||
class HiveonOld(HiveonFirmware, BMMiner):
|
class HiveonOld(HiveonFirmware, BMMiner):
|
||||||
data_locations = HIVEON_OLD_DATA_LOC
|
data_locations = HIVEON_OLD_DATA_LOC
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_stats: dict | None = None) -> int | None:
|
||||||
if not rpc_stats:
|
if not rpc_stats:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -271,10 +283,12 @@ class HiveonOld(HiveonFirmware, BMMiner):
|
|||||||
|
|
||||||
if rpc_stats:
|
if rpc_stats:
|
||||||
boards = rpc_stats.get("STATS")
|
boards = rpc_stats.get("STATS")
|
||||||
try:
|
if boards:
|
||||||
wattage_raw = boards[1]["chain_power"]
|
try:
|
||||||
except (KeyError, IndexError):
|
wattage_raw = boards[1]["chain_power"]
|
||||||
pass
|
except (KeyError, IndexError):
|
||||||
else:
|
pass
|
||||||
# parse wattage position out of raw data
|
else:
|
||||||
return round(float(wattage_raw.split(" ")[0]))
|
# parse wattage position out of raw data
|
||||||
|
return round(float(wattage_raw.split(" ")[0]))
|
||||||
|
return None
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic import MinerConfig
|
from pyasic 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, MinerAlgo
|
from pyasic.device.algorithm import AlgoHashRateType, MinerAlgo
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
from pyasic.miners.device.firmware import StockFirmware
|
from pyasic.miners.device.firmware import StockFirmware
|
||||||
@@ -78,7 +76,7 @@ class IceRiver(StockFirmware):
|
|||||||
|
|
||||||
return MinerConfig.from_iceriver(web_userpanel)
|
return MinerConfig.from_iceriver(web_userpanel)
|
||||||
|
|
||||||
async def _get_fans(self, web_userpanel: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_userpanel: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -86,7 +84,7 @@ class IceRiver(StockFirmware):
|
|||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
if web_userpanel is not None:
|
if web_userpanel is not None:
|
||||||
try:
|
try:
|
||||||
@@ -95,13 +93,14 @@ class IceRiver(StockFirmware):
|
|||||||
]
|
]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return []
|
||||||
|
|
||||||
async def _get_mac(self, web_userpanel: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_userpanel: dict | None = None) -> str | None:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_userpanel is not None:
|
if web_userpanel is not None:
|
||||||
try:
|
try:
|
||||||
@@ -110,26 +109,30 @@ class IceRiver(StockFirmware):
|
|||||||
)
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hostname(self, web_userpanel: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_userpanel: dict | None = None) -> str | None:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_userpanel is not None:
|
if web_userpanel is not None:
|
||||||
try:
|
try:
|
||||||
return web_userpanel["userpanel"]["data"]["host"]
|
return web_userpanel["userpanel"]["data"]["host"]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, web_userpanel: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, web_userpanel: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_userpanel is not None:
|
if web_userpanel is not None:
|
||||||
try:
|
try:
|
||||||
@@ -144,8 +147,9 @@ class IceRiver(StockFirmware):
|
|||||||
).into(MinerAlgo.SHA256.unit.default)
|
).into(MinerAlgo.SHA256.unit.default)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, web_userpanel: dict = None) -> bool:
|
async def _get_fault_light(self, web_userpanel: dict | None = None) -> bool:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
@@ -159,20 +163,23 @@ class IceRiver(StockFirmware):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _is_mining(self, web_userpanel: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_userpanel: dict | None = None) -> bool | None:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return False
|
||||||
|
|
||||||
if web_userpanel is not None:
|
if web_userpanel is not None:
|
||||||
try:
|
try:
|
||||||
return web_userpanel["userpanel"]["data"]["powstate"]
|
return web_userpanel["userpanel"]["data"]["powstate"]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def _get_hashboards(self, web_userpanel: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(
|
||||||
|
self, web_userpanel: dict | None = None
|
||||||
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -195,15 +202,17 @@ class IceRiver(StockFirmware):
|
|||||||
hb_list[idx].temp = round(board["intmp"])
|
hb_list[idx].temp = round(board["intmp"])
|
||||||
hb_list[idx].hashrate = self.algo.hashrate(
|
hb_list[idx].hashrate = self.algo.hashrate(
|
||||||
rate=float(board["rtpow"].replace("G", "")),
|
rate=float(board["rtpow"].replace("G", "")),
|
||||||
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]
|
||||||
|
)
|
||||||
hb_list[idx].chips = int(board["chipnum"])
|
hb_list[idx].chips = int(board["chipnum"])
|
||||||
hb_list[idx].missing = False
|
hb_list[idx].missing = False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
return hb_list
|
return hb_list
|
||||||
|
|
||||||
async def _get_uptime(self, web_userpanel: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_userpanel: dict | None = None) -> int | None:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
@@ -222,8 +231,9 @@ class IceRiver(StockFirmware):
|
|||||||
)
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, web_userpanel: dict = None) -> List[PoolMetrics]:
|
async def _get_pools(self, web_userpanel: dict | None = None) -> list[PoolMetrics]:
|
||||||
if web_userpanel is None:
|
if web_userpanel is None:
|
||||||
try:
|
try:
|
||||||
web_userpanel = await self.web.userpanel()
|
web_userpanel = await self.web.userpanel()
|
||||||
|
|||||||
@@ -13,14 +13,12 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from 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
|
|
||||||
from pyasic.data.error_codes.innosilicon import InnosiliconError
|
from pyasic.data.error_codes.innosilicon import InnosiliconError
|
||||||
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 import CGMiner
|
from pyasic.miners.backends import CGMiner
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
@@ -113,17 +111,17 @@ class Innosilicon(CGMiner):
|
|||||||
# get pool data
|
# get pool data
|
||||||
try:
|
try:
|
||||||
pools = await self.web.pools()
|
pools = await self.web.pools()
|
||||||
|
if pools and "pools" in pools:
|
||||||
|
self.config = MinerConfig.from_inno(pools["pools"])
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
pass
|
||||||
|
return self.config or MinerConfig()
|
||||||
self.config = MinerConfig.from_inno(pools["pools"])
|
|
||||||
return self.config
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.web.reboot()
|
data = await self.web.reboot()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return False
|
||||||
else:
|
else:
|
||||||
return data["success"]
|
return data["success"]
|
||||||
|
|
||||||
@@ -131,14 +129,16 @@ class Innosilicon(CGMiner):
|
|||||||
try:
|
try:
|
||||||
data = await self.web.restart_cgminer()
|
data = await self.web.restart_cgminer()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return False
|
||||||
else:
|
else:
|
||||||
return data["success"]
|
return data["success"]
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
return await self.restart_cgminer()
|
return await self.restart_cgminer()
|
||||||
|
|
||||||
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.update_pools(config.as_inno(user_suffix=user_suffix))
|
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
|
||||||
|
|
||||||
@@ -147,8 +147,8 @@ class Innosilicon(CGMiner):
|
|||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(
|
async def _get_mac(
|
||||||
self, web_get_all: dict = None, web_overview: dict = None
|
self, web_get_all: dict | None = None, web_overview: dict | None = None
|
||||||
) -> Optional[str]:
|
) -> str | None:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
@@ -171,10 +171,11 @@ class Innosilicon(CGMiner):
|
|||||||
return mac.upper()
|
return mac.upper()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(
|
async def _get_hashrate(
|
||||||
self, rpc_summary: dict = None, web_get_all: dict = None
|
self, rpc_summary: dict | None = None, web_get_all: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
@@ -189,13 +190,17 @@ class Innosilicon(CGMiner):
|
|||||||
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(web_get_all["total_hash"]["Hash Rate H"]),
|
rate=float(web_get_all["total_hash"]["Hash Rate H"]),
|
||||||
unit=self.algo.unit.H,
|
unit=self.algo.unit.H, # type: ignore[attr-defined]
|
||||||
).into(self.algo.unit.default)
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
elif "Hash Rate" in web_get_all["total_hash"].keys():
|
elif "Hash Rate" in web_get_all["total_hash"].keys():
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(web_get_all["total_hash"]["Hash Rate"]),
|
rate=float(web_get_all["total_hash"]["Hash Rate"]),
|
||||||
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:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -203,14 +208,17 @@ class Innosilicon(CGMiner):
|
|||||||
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):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, rpc_stats: dict = None, web_get_all: dict = None
|
self, rpc_stats: dict | None = None, web_get_all: dict | None = None
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -260,8 +268,11 @@ class Innosilicon(CGMiner):
|
|||||||
hashrate = board.get("Hash Rate H")
|
hashrate = board.get("Hash Rate H")
|
||||||
if hashrate:
|
if hashrate:
|
||||||
hashboards[idx].hashrate = self.algo.hashrate(
|
hashboards[idx].hashrate = self.algo.hashrate(
|
||||||
rate=float(hashrate), unit=self.algo.unit.H
|
rate=float(hashrate),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.H, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
|
|
||||||
chip_temp = board.get("Temp max")
|
chip_temp = board.get("Temp max")
|
||||||
if chip_temp:
|
if chip_temp:
|
||||||
@@ -270,8 +281,8 @@ class Innosilicon(CGMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(
|
async def _get_wattage(
|
||||||
self, web_get_all: dict = None, rpc_stats: dict = None
|
self, web_get_all: dict | None = None, rpc_stats: dict | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
@@ -305,8 +316,9 @@ class Innosilicon(CGMiner):
|
|||||||
else:
|
else:
|
||||||
wattage = int(wattage)
|
wattage = int(wattage)
|
||||||
return wattage
|
return wattage
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, web_get_all: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_get_all: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -328,15 +340,15 @@ class Innosilicon(CGMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
round((int(spd) * 6000) / 100)
|
spd_converted = round((int(spd) * 6000) / 100)
|
||||||
for i in range(self.expected_fans):
|
for i in range(self.expected_fans):
|
||||||
fans[i].speed = spd
|
fans[i].speed = spd_converted
|
||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_errors(
|
async def _get_errors( # type: ignore[override]
|
||||||
self, web_get_error_detail: dict = None
|
self, web_get_error_detail: dict | None = None
|
||||||
) -> List[MinerErrorData]:
|
) -> list[InnosiliconError]:
|
||||||
errors = []
|
errors = []
|
||||||
if web_get_error_detail is None:
|
if web_get_error_detail is None:
|
||||||
try:
|
try:
|
||||||
@@ -357,7 +369,7 @@ class Innosilicon(CGMiner):
|
|||||||
errors.append(InnosiliconError(error_code=err))
|
errors.append(InnosiliconError(error_code=err))
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def _get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, web_get_all: dict | None = None) -> int | None:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
@@ -379,8 +391,9 @@ class Innosilicon(CGMiner):
|
|||||||
level = int(level)
|
level = int(level)
|
||||||
limit = 1250 + (250 * level)
|
limit = 1250 + (250 * level)
|
||||||
return limit
|
return limit
|
||||||
|
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()
|
||||||
|
|||||||
@@ -14,13 +14,12 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.config.mining import MiningModePreset
|
from pyasic.config.mining import MiningModePreset
|
||||||
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 LuxOSFirmware
|
from pyasic.miners.device.firmware import LuxOSFirmware
|
||||||
@@ -131,6 +130,7 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
try:
|
try:
|
||||||
@@ -169,24 +169,40 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def atm_enabled(self) -> Optional[bool]:
|
async def atm_enabled(self) -> bool | None:
|
||||||
try:
|
try:
|
||||||
result = await self.rpc.atm()
|
result = await self.rpc.atm()
|
||||||
return result["ATM"][0]["Enabled"]
|
return result["ATM"][0]["Enabled"]
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
config = await self.get_config()
|
config = await self.get_config()
|
||||||
|
|
||||||
|
# Check if we have preset mode with available presets
|
||||||
|
if not hasattr(config.mining_mode, "available_presets"):
|
||||||
|
logging.warning(f"{self} - Mining mode does not support presets")
|
||||||
|
return False
|
||||||
|
|
||||||
|
available_presets = getattr(config.mining_mode, "available_presets", [])
|
||||||
|
if not available_presets:
|
||||||
|
logging.warning(f"{self} - No available presets found")
|
||||||
|
return False
|
||||||
|
|
||||||
valid_presets = {
|
valid_presets = {
|
||||||
preset.name: preset.power
|
preset.name: preset.power
|
||||||
for preset in config.mining_mode.available_presets
|
for preset in available_presets
|
||||||
if preset.power <= wattage
|
if preset.power is not None and preset.power <= wattage
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if not valid_presets:
|
||||||
|
logging.warning(f"{self} - No valid presets found for wattage {wattage}")
|
||||||
|
return False
|
||||||
|
|
||||||
# Set power to highest preset <= wattage
|
# Set power to highest preset <= wattage
|
||||||
# If ATM enabled, must disable it before setting power limit
|
# If ATM enabled, must disable it before setting power limit
|
||||||
new_preset = max(valid_presets, key=valid_presets.get)
|
new_preset = max(valid_presets, key=lambda x: valid_presets[x])
|
||||||
|
|
||||||
re_enable_atm = False
|
re_enable_atm = False
|
||||||
try:
|
try:
|
||||||
@@ -211,12 +227,13 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, rpc_config: dict = None) -> Optional[str]:
|
async def _get_mac(self, rpc_config: dict | None = None) -> str | None:
|
||||||
if rpc_config is None:
|
if rpc_config is None:
|
||||||
try:
|
try:
|
||||||
rpc_config = await self.rpc.config()
|
rpc_config = await self.rpc.config()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if rpc_config is not None:
|
if rpc_config is not None:
|
||||||
try:
|
try:
|
||||||
@@ -224,12 +241,15 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
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
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if rpc_summary is not None:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
@@ -240,7 +260,7 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
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 []
|
||||||
|
|
||||||
@@ -262,8 +282,10 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
board_n = idx + 1
|
board_n = idx + 1
|
||||||
hashboards[idx].hashrate = self.algo.hashrate(
|
hashboards[idx].hashrate = self.algo.hashrate(
|
||||||
rate=float(board_stats[f"chain_rate{board_n}"]),
|
rate=float(board_stats[f"chain_rate{board_n}"]),
|
||||||
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].chips = int(board_stats[f"chain_acn{board_n}"])
|
hashboards[idx].chips = int(board_stats[f"chain_acn{board_n}"])
|
||||||
chip_temp_data = list(
|
chip_temp_data = list(
|
||||||
filter(
|
filter(
|
||||||
@@ -288,22 +310,26 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
pass
|
pass
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_power: dict | None = None) -> int | None:
|
||||||
if rpc_power is None:
|
if rpc_power is None:
|
||||||
try:
|
try:
|
||||||
rpc_power = await self.rpc.power()
|
rpc_power = await self.rpc.power()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if rpc_power is not None:
|
if rpc_power is not None:
|
||||||
try:
|
try:
|
||||||
return rpc_power["POWER"][0]["Watts"]
|
return rpc_power["POWER"][0]["Watts"]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(
|
async def _get_wattage_limit(
|
||||||
self, rpc_config: dict = None, rpc_profiles: list[dict] = None
|
self, rpc_config: dict | None = None, rpc_profiles: dict | None = None
|
||||||
) -> Optional[int]:
|
) -> int | None:
|
||||||
|
if rpc_config is None or rpc_profiles is None:
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
active_preset = MiningModePreset.get_active_preset_from_luxos(
|
active_preset = MiningModePreset.get_active_preset_from_luxos(
|
||||||
rpc_config, rpc_profiles
|
rpc_config, rpc_profiles
|
||||||
@@ -311,8 +337,9 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
return active_preset.power
|
return active_preset.power
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
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 []
|
||||||
|
|
||||||
@@ -333,8 +360,8 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
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:
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
rpc_stats = await self.rpc.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
@@ -350,11 +377,12 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
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()
|
||||||
@@ -366,8 +394,9 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
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_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()
|
||||||
@@ -379,8 +408,9 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
return rpc_version["VERSION"][0]["Miner"]
|
return rpc_version["VERSION"][0]["Miner"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
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()
|
||||||
@@ -392,8 +422,9 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
return rpc_version["VERSION"][0]["API"]
|
return rpc_version["VERSION"][0]["API"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, rpc_config: dict = None) -> Optional[bool]:
|
async def _get_fault_light(self, rpc_config: dict | None = None) -> bool | None:
|
||||||
if rpc_config is None:
|
if rpc_config is None:
|
||||||
try:
|
try:
|
||||||
rpc_config = await self.rpc.config()
|
rpc_config = await self.rpc.config()
|
||||||
@@ -405,8 +436,9 @@ class LUXMiner(LuxOSFirmware):
|
|||||||
return not rpc_config["CONFIG"][0]["RedLed"] == "off"
|
return not rpc_config["CONFIG"][0]["RedLed"] == "off"
|
||||||
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()
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic import MinerConfig
|
from pyasic import MinerConfig
|
||||||
from pyasic.config import MiningModeConfig
|
from pyasic.config import MiningModeConfig
|
||||||
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, WebAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
from pyasic.miners.device.firmware import MaraFirmware
|
from pyasic.miners.device.firmware import MaraFirmware
|
||||||
@@ -90,9 +88,12 @@ class MaraMiner(MaraFirmware):
|
|||||||
data = await self.web.get_miner_config()
|
data = await self.web.get_miner_config()
|
||||||
if data:
|
if data:
|
||||||
self.config = MinerConfig.from_mara(data)
|
self.config = MinerConfig.from_mara(data)
|
||||||
return self.config
|
return self.config
|
||||||
|
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:
|
||||||
data = await self.web.get_miner_config()
|
data = await self.web.get_miner_config()
|
||||||
cfg_data = config.as_mara(user_suffix=user_suffix)
|
cfg_data = config.as_mara(user_suffix=user_suffix)
|
||||||
merged_cfg = merge_dicts(data, cfg_data)
|
merged_cfg = merge_dicts(data, cfg_data)
|
||||||
@@ -124,12 +125,13 @@ class MaraMiner(MaraFirmware):
|
|||||||
await self.web.reload()
|
await self.web.reload()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def _get_wattage(self, web_brief: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_brief: dict | None = None) -> int | None:
|
||||||
if web_brief is None:
|
if web_brief is None:
|
||||||
try:
|
try:
|
||||||
web_brief = await self.web.brief()
|
web_brief = await self.web.brief()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if web_brief is not None:
|
if web_brief is not None:
|
||||||
try:
|
try:
|
||||||
@@ -137,12 +139,13 @@ class MaraMiner(MaraFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _is_mining(self, web_brief: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_brief: dict | None = None) -> bool | None:
|
||||||
if web_brief is None:
|
if web_brief is None:
|
||||||
try:
|
try:
|
||||||
web_brief = await self.web.brief()
|
web_brief = await self.web.brief()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if web_brief is not None:
|
if web_brief is not None:
|
||||||
try:
|
try:
|
||||||
@@ -150,12 +153,13 @@ class MaraMiner(MaraFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_uptime(self, web_brief: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_brief: dict | None = None) -> int | None:
|
||||||
if web_brief is None:
|
if web_brief is None:
|
||||||
try:
|
try:
|
||||||
web_brief = await self.web.brief()
|
web_brief = await self.web.brief()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
if web_brief is not None:
|
if web_brief is not None:
|
||||||
try:
|
try:
|
||||||
@@ -163,7 +167,9 @@ class MaraMiner(MaraFirmware):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, web_hashboards: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(
|
||||||
|
self, web_hashboards: dict | None = None
|
||||||
|
) -> list[HashBoard]:
|
||||||
if self.expected_hashboards is None:
|
if self.expected_hashboards is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -183,8 +189,11 @@ class MaraMiner(MaraFirmware):
|
|||||||
for hb in web_hashboards["hashboards"]:
|
for hb in web_hashboards["hashboards"]:
|
||||||
idx = hb["index"]
|
idx = hb["index"]
|
||||||
hashboards[idx].hashrate = self.algo.hashrate(
|
hashboards[idx].hashrate = self.algo.hashrate(
|
||||||
rate=float(hb["hashrate_average"]), unit=self.algo.unit.GH
|
rate=float(hb["hashrate_average"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.GH, # type: ignore[attr-defined]
|
||||||
|
).into(
|
||||||
|
self.algo.unit.default # type: ignore[attr-defined]
|
||||||
|
)
|
||||||
hashboards[idx].temp = round(
|
hashboards[idx].temp = round(
|
||||||
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"])
|
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"])
|
||||||
)
|
)
|
||||||
@@ -198,7 +207,7 @@ class MaraMiner(MaraFirmware):
|
|||||||
pass
|
pass
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_mac(self, web_overview: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_overview: dict | None = None) -> str | None:
|
||||||
if web_overview is None:
|
if web_overview is None:
|
||||||
try:
|
try:
|
||||||
web_overview = await self.web.overview()
|
web_overview = await self.web.overview()
|
||||||
@@ -210,8 +219,9 @@ class MaraMiner(MaraFirmware):
|
|||||||
return web_overview["mac"].upper()
|
return web_overview["mac"].upper()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_overview: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_overview: dict | None = None) -> str | None:
|
||||||
if web_overview is None:
|
if web_overview is None:
|
||||||
try:
|
try:
|
||||||
web_overview = await self.web.overview()
|
web_overview = await self.web.overview()
|
||||||
@@ -223,8 +233,9 @@ class MaraMiner(MaraFirmware):
|
|||||||
return web_overview["version_firmware"]
|
return web_overview["version_firmware"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hostname(self, web_network_config: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_network_config: dict | None = None) -> str | None:
|
||||||
if web_network_config is None:
|
if web_network_config is None:
|
||||||
try:
|
try:
|
||||||
web_network_config = await self.web.get_network_config()
|
web_network_config = await self.web.get_network_config()
|
||||||
@@ -236,8 +247,11 @@ class MaraMiner(MaraFirmware):
|
|||||||
return web_network_config["hostname"]
|
return web_network_config["hostname"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self, web_brief: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, web_brief: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
if web_brief is None:
|
if web_brief is None:
|
||||||
try:
|
try:
|
||||||
web_brief = await self.web.brief()
|
web_brief = await self.web.brief()
|
||||||
@@ -247,12 +261,14 @@ class MaraMiner(MaraFirmware):
|
|||||||
if web_brief is not None:
|
if web_brief is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(web_brief["hashrate_realtime"]), unit=self.algo.unit.TH
|
rate=float(web_brief["hashrate_realtime"]),
|
||||||
).into(self.algo.unit.default)
|
unit=self.algo.unit.TH, # type: ignore[attr-defined]
|
||||||
|
).into(self.algo.unit.default) # type: ignore[attr-defined]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_fans(self, web_fans: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_fans: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -272,7 +288,7 @@ class MaraMiner(MaraFirmware):
|
|||||||
return fans
|
return fans
|
||||||
return [Fan() for _ in range(self.expected_fans)]
|
return [Fan() for _ in range(self.expected_fans)]
|
||||||
|
|
||||||
async def _get_fault_light(self, web_locate_miner: dict = None) -> bool:
|
async def _get_fault_light(self, web_locate_miner: dict | None = None) -> bool:
|
||||||
if web_locate_miner is None:
|
if web_locate_miner is None:
|
||||||
try:
|
try:
|
||||||
web_locate_miner = await self.web.get_locate_miner()
|
web_locate_miner = await self.web.get_locate_miner()
|
||||||
@@ -287,8 +303,8 @@ class MaraMiner(MaraFirmware):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, web_brief: dict = None
|
self, web_brief: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> AlgoHashRateType | None:
|
||||||
if web_brief is None:
|
if web_brief is None:
|
||||||
try:
|
try:
|
||||||
web_brief = await self.web.brief()
|
web_brief = await self.web.brief()
|
||||||
@@ -298,14 +314,16 @@ class MaraMiner(MaraFirmware):
|
|||||||
if web_brief is not None:
|
if web_brief is not None:
|
||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(web_brief["hashrate_ideal"]), unit=self.algo.unit.GH
|
rate=float(web_brief["hashrate_ideal"]),
|
||||||
).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_wattage_limit(
|
async def _get_wattage_limit(
|
||||||
self, web_miner_config: dict = None
|
self, web_miner_config: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> int | None:
|
||||||
if web_miner_config is None:
|
if web_miner_config is None:
|
||||||
try:
|
try:
|
||||||
web_miner_config = await self.web.get_miner_config()
|
web_miner_config = await self.web.get_miner_config()
|
||||||
@@ -317,8 +335,9 @@ class MaraMiner(MaraFirmware):
|
|||||||
return web_miner_config["mode"]["concorde"]["power-target"]
|
return web_miner_config["mode"]["concorde"]["power-target"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
|
async def _get_pools(self, web_pools: list | 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()
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pyasic import APIError
|
from pyasic import APIError
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.miners.backends import BMMiner
|
from pyasic.miners.backends import BMMiner
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
@@ -66,7 +64,9 @@ class MSKMiner(BMMiner):
|
|||||||
web: MSKMinerWebAPI
|
web: MSKMinerWebAPI
|
||||||
_web_cls = MSKMinerWebAPI
|
_web_cls = MSKMinerWebAPI
|
||||||
|
|
||||||
async def _get_hashrate(self, rpc_stats: dict = None) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(
|
||||||
|
self, rpc_stats: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
# get hr from API
|
# get hr from API
|
||||||
if rpc_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
@@ -78,12 +78,15 @@ class MSKMiner(BMMiner):
|
|||||||
try:
|
try:
|
||||||
return self.algo.hashrate(
|
return self.algo.hashrate(
|
||||||
rate=float(rpc_stats["STATS"][0]["total_rate"]),
|
rate=float(rpc_stats["STATS"][0]["total_rate"]),
|
||||||
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_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(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()
|
||||||
@@ -95,8 +98,9 @@ class MSKMiner(BMMiner):
|
|||||||
return rpc_stats["STATS"][0]["total_power"]
|
return rpc_stats["STATS"][0]["total_power"]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_mac(self, web_info_v1: dict = None) -> Optional[str]:
|
async def _get_mac(self, web_info_v1: dict | None = None) -> str | None:
|
||||||
if web_info_v1 is None:
|
if web_info_v1 is None:
|
||||||
try:
|
try:
|
||||||
web_info_v1 = await self.web.info_v1()
|
web_info_v1 = await self.web.info_v1()
|
||||||
@@ -108,3 +112,4 @@ class MSKMiner(BMMiner):
|
|||||||
return web_info_v1["network_info"]["result"]["macaddr"].upper()
|
return web_info_v1["network_info"]["result"]["macaddr"].upper()
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|||||||
@@ -12,12 +12,12 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
from typing import List, Optional, Tuple
|
|
||||||
|
|
||||||
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
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.data.pools import PoolMetrics
|
||||||
|
from pyasic.device.algorithm import AlgoHashRateType
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
from pyasic.rpc.unknown import UnknownRPCAPI
|
from pyasic.rpc.unknown import UnknownRPCAPI
|
||||||
|
|
||||||
@@ -47,8 +47,8 @@ class UnknownMiner(BaseMiner):
|
|||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_config(self) -> None:
|
async def get_config(self) -> MinerConfig:
|
||||||
return None
|
return MinerConfig()
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
return False
|
return False
|
||||||
@@ -62,7 +62,9 @@ class UnknownMiner(BaseMiner):
|
|||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
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:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
@@ -72,53 +74,62 @@ class UnknownMiner(BaseMiner):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_mac(self) -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
async def _get_serial_number(self) -> str | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_version(self) -> tuple[str | None, str | None]:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
async def _get_hostname(self) -> Optional[str]:
|
async def _get_hostname(self) -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self) -> Optional[AlgoHashRate]:
|
async def _get_hashrate(self) -> AlgoHashRateType | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self) -> List[HashBoard]:
|
async def _get_hashboards(self) -> list[HashBoard]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_env_temp(self) -> Optional[float]:
|
async def _get_env_temp(self) -> float | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self) -> Optional[int]:
|
async def _get_wattage(self) -> int | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self) -> Optional[int]:
|
async def _get_wattage_limit(self) -> int | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_fans(self) -> List[Fan]:
|
async def _get_fans(self) -> list[Fan]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_fan_psu(self) -> Optional[int]:
|
async def _get_fan_psu(self) -> int | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_api_ver(self) -> Optional[str]:
|
async def _get_api_ver(self) -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self) -> Optional[str]:
|
async def _get_fw_ver(self) -> str | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
async def _get_errors(self) -> list[MinerErrorData]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
async def _get_fault_light(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
async def _get_expected_hashrate(self) -> AlgoHashRateType | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
async def _is_mining(self, *args, **kwargs) -> bool | None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
async def _get_uptime(self, *args, **kwargs) -> int | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_pools(self) -> list[PoolMetrics]:
|
||||||
|
return []
|
||||||
|
|
||||||
|
async def _get_voltage(self) -> float | None:
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -15,11 +15,11 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic import MinerConfig
|
from pyasic import MinerConfig
|
||||||
from pyasic.data.error_codes import MinerErrorData, VnishError
|
from pyasic.config.mining import MiningModePreset
|
||||||
from pyasic.device.algorithm import AlgoHashRate
|
from pyasic.data.error_codes import VnishError
|
||||||
|
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.data import (
|
from pyasic.miners.data import (
|
||||||
@@ -106,7 +106,9 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
|
|
||||||
data_locations = VNISH_DATA_LOC
|
data_locations = VNISH_DATA_LOC
|
||||||
|
|
||||||
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:
|
||||||
await self.web.post_settings(
|
await self.web.post_settings(
|
||||||
miner_settings=config.as_vnish(user_suffix=user_suffix)
|
miner_settings=config.as_vnish(user_suffix=user_suffix)
|
||||||
)
|
)
|
||||||
@@ -147,7 +149,7 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_mac(self, web_summary: dict = None) -> str:
|
async def _get_mac(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is not None:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
mac = web_summary["system"]["network_status"]["mac"]
|
mac = web_summary["system"]["network_status"]["mac"]
|
||||||
@@ -164,6 +166,8 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
result = await self.web.find_miner()
|
result = await self.web.find_miner()
|
||||||
if result is not None:
|
if result is not None:
|
||||||
@@ -171,6 +175,7 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
await self.web.find_miner()
|
await self.web.find_miner()
|
||||||
|
return False
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
result = await self.web.find_miner()
|
result = await self.web.find_miner()
|
||||||
@@ -179,26 +184,27 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
await self.web.find_miner()
|
await self.web.find_miner()
|
||||||
|
return False
|
||||||
|
|
||||||
async def _get_hostname(self, web_summary: dict = None) -> str:
|
async def _get_hostname(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
web_info = await self.web.info()
|
web_info = await self.web.info()
|
||||||
|
|
||||||
if web_info is not None:
|
if web_info is not None:
|
||||||
try:
|
try:
|
||||||
hostname = web_info["system"]["network_status"]["hostname"]
|
hostname = web_info["system"]["network_status"]["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
else:
|
||||||
if web_summary is not None:
|
|
||||||
try:
|
try:
|
||||||
hostname = web_summary["system"]["network_status"]["hostname"]
|
hostname = web_summary["system"]["network_status"]["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
return None
|
||||||
|
|
||||||
|
async def _get_wattage(self, web_summary: dict | None = None) -> int | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
@@ -209,25 +215,30 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
return wattage
|
return wattage
|
||||||
except KeyError:
|
except KeyError:
|
||||||
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:
|
||||||
# get hr from API
|
# get hr from API
|
||||||
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]["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
|
||||||
|
|
||||||
async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
|
return None
|
||||||
|
|
||||||
|
async def _get_wattage_limit(self, web_settings: dict | None = None) -> int | None:
|
||||||
if web_settings is None:
|
if web_settings is None:
|
||||||
web_settings = await self.web.summary()
|
web_settings = await self.web.summary()
|
||||||
|
|
||||||
@@ -240,7 +251,9 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
return None
|
||||||
|
|
||||||
|
async def _get_fw_ver(self, web_summary: dict | None = None) -> str | None:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
@@ -253,16 +266,16 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
return fw_ver
|
return fw_ver
|
||||||
|
|
||||||
async def _is_mining(self, web_summary: dict = None) -> 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()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
if web_summary is not None:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
is_mining = not web_summary["miner"]["miner_status"]["miner_state"] in [
|
is_mining = web_summary["miner"]["miner_status"]["miner_state"] not in [
|
||||||
"stopped",
|
"stopped",
|
||||||
"shutting-down",
|
"shutting-down",
|
||||||
"failure",
|
"failure",
|
||||||
@@ -271,8 +284,13 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
return None
|
||||||
errors = []
|
|
||||||
|
async def _get_errors( # type: ignore[override]
|
||||||
|
self, web_summary: dict | None = None
|
||||||
|
) -> list[VnishError]:
|
||||||
|
errors: list[VnishError] = []
|
||||||
|
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
@@ -292,10 +310,13 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
try:
|
try:
|
||||||
web_settings = await self.web.settings()
|
web_settings = await self.web.settings()
|
||||||
web_presets = await self.web.autotune_presets()
|
web_presets_dict = await self.web.autotune_presets()
|
||||||
|
web_presets = (
|
||||||
|
web_presets_dict.get("presets", []) if web_presets_dict else []
|
||||||
|
)
|
||||||
web_perf_summary = (await self.web.perf_summary()) or {}
|
web_perf_summary = (await self.web.perf_summary()) or {}
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config or MinerConfig()
|
||||||
self.config = MinerConfig.from_vnish(
|
self.config = MinerConfig.from_vnish(
|
||||||
web_settings, web_presets, web_perf_summary
|
web_settings, web_presets, web_perf_summary
|
||||||
)
|
)
|
||||||
@@ -303,11 +324,20 @@ class VNish(VNishFirmware, BMMiner):
|
|||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
config = await self.get_config()
|
config = await self.get_config()
|
||||||
|
|
||||||
|
# Check if mining mode is preset mode and has available presets
|
||||||
|
if not isinstance(config.mining_mode, MiningModePreset):
|
||||||
|
return False
|
||||||
|
|
||||||
valid_presets = [
|
valid_presets = [
|
||||||
preset.power
|
preset.power
|
||||||
for preset in config.mining_mode.available_presets
|
for preset in config.mining_mode.available_presets
|
||||||
if preset.tuned and preset.power <= wattage
|
if (preset.tuned and preset.power is not None and preset.power <= wattage)
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if not valid_presets:
|
||||||
|
return False
|
||||||
|
|
||||||
new_wattage = max(valid_presets)
|
new_wattage = max(valid_presets)
|
||||||
|
|
||||||
# Set power to highest preset <= wattage
|
# Set power to highest preset <= wattage
|
||||||
|
|||||||
@@ -13,24 +13,28 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.miners.backends.btminer import BTMiner, BTMinerV3
|
from pyasic.miners.backends.btminer import BTMiner, BTMinerV2
|
||||||
|
|
||||||
|
|
||||||
class M7X(BTMinerV3):
|
class M7X(BTMiner):
|
||||||
pass
|
supports_autotuning = True
|
||||||
|
supports_presets = True
|
||||||
|
|
||||||
class M6X(BTMinerV3):
|
|
||||||
pass
|
class M6X(BTMiner):
|
||||||
|
supports_autotuning = True
|
||||||
|
supports_presets = True
|
||||||
class M5X(BTMinerV3):
|
|
||||||
pass
|
|
||||||
|
class M5X(BTMiner):
|
||||||
|
supports_autotuning = True
|
||||||
class M3X(BTMinerV3):
|
supports_presets = True
|
||||||
pass
|
|
||||||
|
|
||||||
|
class M3X(BTMiner):
|
||||||
class M2X(BTMiner):
|
supports_autotuning = True
|
||||||
|
supports_presets = True
|
||||||
|
|
||||||
|
|
||||||
|
class M2X(BTMinerV2):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -16,54 +16,53 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import warnings
|
import warnings
|
||||||
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
from typing import Any, Protocol, TypeVar
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard, MinerData
|
from pyasic.data import Fan, HashBoard, MinerData
|
||||||
from pyasic.data.device import DeviceInfo
|
from pyasic.data.device import DeviceInfo
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
from pyasic.data.pools import PoolMetrics
|
from pyasic.data.pools import PoolMetrics
|
||||||
from pyasic.device.algorithm import MinerAlgoType
|
from pyasic.device.algorithm import AlgoHashRateType, MinerAlgoType
|
||||||
from pyasic.device.algorithm.base import GenericAlgo
|
from pyasic.device.algorithm.base import GenericAlgo
|
||||||
from pyasic.device.algorithm.hashrate import AlgoHashRate
|
|
||||||
from pyasic.device.firmware import MinerFirmware
|
from pyasic.device.firmware import MinerFirmware
|
||||||
from pyasic.device.makes import MinerMake
|
from pyasic.device.makes import MinerMake
|
||||||
from pyasic.device.models import MinerModelType
|
from pyasic.device.models import MinerModelType
|
||||||
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 DataLocations, DataOptions, RPCAPICommand, WebAPICommand
|
from pyasic.miners.data import DataOptions, RPCAPICommand, WebAPICommand
|
||||||
|
|
||||||
|
|
||||||
class MinerProtocol(Protocol):
|
class MinerProtocol(Protocol):
|
||||||
_rpc_cls: Type = None
|
_rpc_cls: type[Any] | None = None
|
||||||
_web_cls: Type = None
|
_web_cls: type[Any] | None = None
|
||||||
_ssh_cls: Type = None
|
_ssh_cls: type[Any] | None = None
|
||||||
|
|
||||||
ip: str = None
|
ip: str | None = None
|
||||||
rpc: _rpc_cls = None
|
rpc: Any | None = None
|
||||||
web: _web_cls = None
|
web: Any | None = None
|
||||||
ssh: _ssh_cls = None
|
ssh: Any | None = None
|
||||||
|
|
||||||
make: MinerMake = None
|
make: MinerMake | None = None
|
||||||
raw_model: MinerModelType = None
|
raw_model: MinerModelType | None = None
|
||||||
firmware: MinerFirmware = None
|
firmware: MinerFirmware | None = None
|
||||||
algo: type[MinerAlgoType] = GenericAlgo
|
algo: type[MinerAlgoType] = GenericAlgo
|
||||||
|
|
||||||
expected_hashboards: int = None
|
expected_hashboards: int | None = None
|
||||||
expected_chips: int = None
|
expected_chips: int | None = None
|
||||||
expected_fans: int = None
|
expected_fans: int | None = None
|
||||||
|
|
||||||
data_locations: DataLocations = None
|
data_locations: Any | None = None
|
||||||
|
|
||||||
supports_shutdown: bool = False
|
supports_shutdown: bool = False
|
||||||
supports_power_modes: bool = False
|
supports_power_modes: bool = False
|
||||||
supports_presets: bool = False
|
supports_presets: bool = False
|
||||||
supports_autotuning: bool = False
|
supports_autotuning: bool = False
|
||||||
|
|
||||||
api_ver: str = None
|
api_ver: str | None = None
|
||||||
fw_ver: str = None
|
fw_ver: str | None = None
|
||||||
light: bool = None
|
light: bool | None = None
|
||||||
config: MinerConfig = None
|
config: MinerConfig | None = None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.model}: {str(self.ip)}"
|
return f"{self.model}: {str(self.ip)}"
|
||||||
@@ -80,9 +79,9 @@ class MinerProtocol(Protocol):
|
|||||||
@property
|
@property
|
||||||
def model(self) -> str:
|
def model(self) -> str:
|
||||||
if self.raw_model is not None:
|
if self.raw_model is not None:
|
||||||
model_data = [self.raw_model]
|
model_data = [str(self.raw_model)]
|
||||||
elif self.make is not None:
|
elif self.make is not None:
|
||||||
model_data = [self.make]
|
model_data = [str(self.make)]
|
||||||
else:
|
else:
|
||||||
model_data = ["Unknown"]
|
model_data = ["Unknown"]
|
||||||
if self.firmware is not None:
|
if self.firmware is not None:
|
||||||
@@ -148,7 +147,9 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
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:
|
||||||
"""Set the mining configuration of the miner.
|
"""Set the mining configuration of the miner.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
@@ -187,9 +188,9 @@ class MinerProtocol(Protocol):
|
|||||||
async def upgrade_firmware(
|
async def upgrade_firmware(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
file: str = None,
|
file: str | None = None,
|
||||||
url: str = None,
|
url: str | None = None,
|
||||||
version: str = None,
|
version: str | None = None,
|
||||||
keep_settings: bool = True,
|
keep_settings: bool = True,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Upgrade the firmware of the miner.
|
"""Upgrade the firmware of the miner.
|
||||||
@@ -209,7 +210,15 @@ class MinerProtocol(Protocol):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def get_mac(self) -> Optional[str]:
|
async def get_serial_number(self) -> str | None:
|
||||||
|
"""Get the serial number of the miner and return it as a string.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A string representing the serial number of the miner.
|
||||||
|
"""
|
||||||
|
return await self._get_serial_number()
|
||||||
|
|
||||||
|
async def get_mac(self) -> str | None:
|
||||||
"""Get the MAC address of the miner and return it as a string.
|
"""Get the MAC address of the miner and return it as a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -217,7 +226,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_mac()
|
return await self._get_mac()
|
||||||
|
|
||||||
async def get_model(self) -> Optional[str]:
|
async def get_model(self) -> str | None:
|
||||||
"""Get the model of the miner and return it as a string.
|
"""Get the model of the miner and return it as a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -225,7 +234,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return self.model
|
return self.model
|
||||||
|
|
||||||
async def get_device_info(self) -> Optional[DeviceInfo]:
|
async def get_device_info(self) -> DeviceInfo | None:
|
||||||
"""Get device information, including model, make, and firmware.
|
"""Get device information, including model, make, and firmware.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -233,7 +242,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return self.device_info
|
return self.device_info
|
||||||
|
|
||||||
async def get_api_ver(self) -> Optional[str]:
|
async def get_api_ver(self) -> str | None:
|
||||||
"""Get the API version of the miner and is as a string.
|
"""Get the API version of the miner and is as a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -241,7 +250,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_api_ver()
|
return await self._get_api_ver()
|
||||||
|
|
||||||
async def get_fw_ver(self) -> Optional[str]:
|
async def get_fw_ver(self) -> str | None:
|
||||||
"""Get the firmware version of the miner and is as a string.
|
"""Get the firmware version of the miner and is as a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -249,7 +258,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_fw_ver()
|
return await self._get_fw_ver()
|
||||||
|
|
||||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
async def get_version(self) -> tuple[str | None, str | None]:
|
||||||
"""Get the API version and firmware version of the miner and return them as strings.
|
"""Get the API version and firmware version of the miner and return them as strings.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -259,7 +268,7 @@ class MinerProtocol(Protocol):
|
|||||||
fw_ver = await self.get_fw_ver()
|
fw_ver = await self.get_fw_ver()
|
||||||
return api_ver, fw_ver
|
return api_ver, fw_ver
|
||||||
|
|
||||||
async def get_hostname(self) -> Optional[str]:
|
async def get_hostname(self) -> str | None:
|
||||||
"""Get the hostname of the miner and return it as a string.
|
"""Get the hostname of the miner and return it as a string.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -267,7 +276,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_hostname()
|
return await self._get_hostname()
|
||||||
|
|
||||||
async def get_hashrate(self) -> Optional[AlgoHashRate]:
|
async def get_hashrate(self) -> AlgoHashRateType | None:
|
||||||
"""Get the hashrate of the miner and return it as a float in TH/s.
|
"""Get the hashrate of the miner and return it as a float in TH/s.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -275,7 +284,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_hashrate()
|
return await self._get_hashrate()
|
||||||
|
|
||||||
async def get_hashboards(self) -> List[HashBoard]:
|
async def get_hashboards(self) -> list[HashBoard]:
|
||||||
"""Get hashboard data from the miner in the form of [`HashBoard`][pyasic.data.HashBoard].
|
"""Get hashboard data from the miner in the form of [`HashBoard`][pyasic.data.HashBoard].
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -283,7 +292,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_hashboards()
|
return await self._get_hashboards()
|
||||||
|
|
||||||
async def get_env_temp(self) -> Optional[float]:
|
async def get_env_temp(self) -> float | None:
|
||||||
"""Get environment temp from the miner as a float.
|
"""Get environment temp from the miner as a float.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -291,7 +300,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_env_temp()
|
return await self._get_env_temp()
|
||||||
|
|
||||||
async def get_wattage(self) -> Optional[int]:
|
async def get_wattage(self) -> int | None:
|
||||||
"""Get wattage from the miner as an int.
|
"""Get wattage from the miner as an int.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -299,7 +308,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_wattage()
|
return await self._get_wattage()
|
||||||
|
|
||||||
async def get_voltage(self) -> Optional[float]:
|
async def get_voltage(self) -> float | None:
|
||||||
"""Get output voltage of the PSU as a float.
|
"""Get output voltage of the PSU as a float.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -307,7 +316,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_voltage()
|
return await self._get_voltage()
|
||||||
|
|
||||||
async def get_wattage_limit(self) -> Optional[int]:
|
async def get_wattage_limit(self) -> int | None:
|
||||||
"""Get wattage limit from the miner as an int.
|
"""Get wattage limit from the miner as an int.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -315,7 +324,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_wattage_limit()
|
return await self._get_wattage_limit()
|
||||||
|
|
||||||
async def get_fans(self) -> List[Fan]:
|
async def get_fans(self) -> list[Fan]:
|
||||||
"""Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4].
|
"""Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4].
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -323,7 +332,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_fans()
|
return await self._get_fans()
|
||||||
|
|
||||||
async def get_fan_psu(self) -> Optional[int]:
|
async def get_fan_psu(self) -> int | None:
|
||||||
"""Get PSU fan speed from the miner.
|
"""Get PSU fan speed from the miner.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -331,7 +340,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_fan_psu()
|
return await self._get_fan_psu()
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
async def get_errors(self) -> list[MinerErrorData]:
|
||||||
"""Get a list of the errors the miner is experiencing.
|
"""Get a list of the errors the miner is experiencing.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -345,9 +354,10 @@ class MinerProtocol(Protocol):
|
|||||||
Returns:
|
Returns:
|
||||||
A boolean value where `True` represents on and `False` represents off.
|
A boolean value where `True` represents on and `False` represents off.
|
||||||
"""
|
"""
|
||||||
return await self._get_fault_light()
|
result = await self._get_fault_light()
|
||||||
|
return result if result is not None else False
|
||||||
|
|
||||||
async def get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
async def get_expected_hashrate(self) -> AlgoHashRateType | None:
|
||||||
"""Get the nominal hashrate from factory if available.
|
"""Get the nominal hashrate from factory if available.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -355,7 +365,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_expected_hashrate()
|
return await self._get_expected_hashrate()
|
||||||
|
|
||||||
async def is_mining(self) -> Optional[bool]:
|
async def is_mining(self) -> bool | None:
|
||||||
"""Check whether the miner is mining.
|
"""Check whether the miner is mining.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -363,7 +373,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._is_mining()
|
return await self._is_mining()
|
||||||
|
|
||||||
async def get_uptime(self) -> Optional[int]:
|
async def get_uptime(self) -> int | None:
|
||||||
"""Get the uptime of the miner in seconds.
|
"""Get the uptime of the miner in seconds.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -371,7 +381,7 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_uptime()
|
return await self._get_uptime()
|
||||||
|
|
||||||
async def get_pools(self) -> List[PoolMetrics]:
|
async def get_pools(self) -> list[PoolMetrics]:
|
||||||
"""Get the pools information from Miner.
|
"""Get the pools information from Miner.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -379,65 +389,68 @@ class MinerProtocol(Protocol):
|
|||||||
"""
|
"""
|
||||||
return await self._get_pools()
|
return await self._get_pools()
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_serial_number(self) -> str | None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_api_ver(self) -> Optional[str]:
|
async def _get_mac(self) -> str | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_fw_ver(self) -> Optional[str]:
|
async def _get_api_ver(self) -> str | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_hostname(self) -> Optional[str]:
|
async def _get_fw_ver(self) -> str | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_hashrate(self) -> Optional[AlgoHashRate]:
|
async def _get_hostname(self) -> str | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_hashboards(self) -> List[HashBoard]:
|
async def _get_hashrate(self) -> AlgoHashRateType | None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_hashboards(self) -> list[HashBoard]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_env_temp(self) -> Optional[float]:
|
async def _get_env_temp(self) -> float | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_wattage(self) -> Optional[int]:
|
async def _get_wattage(self) -> int | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_voltage(self) -> Optional[float]:
|
async def _get_voltage(self) -> float | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_wattage_limit(self) -> Optional[int]:
|
async def _get_wattage_limit(self) -> int | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_fans(self) -> List[Fan]:
|
async def _get_fans(self) -> list[Fan]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_fan_psu(self) -> Optional[int]:
|
async def _get_fan_psu(self) -> int | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
async def _get_errors(self) -> list[MinerErrorData]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def _get_fault_light(self) -> Optional[bool]:
|
async def _get_fault_light(self) -> bool | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
async def _get_expected_hashrate(self) -> AlgoHashRateType | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _is_mining(self) -> Optional[bool]:
|
async def _is_mining(self) -> bool | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self) -> Optional[int]:
|
async def _get_uptime(self) -> int | None:
|
||||||
pass
|
return None
|
||||||
|
|
||||||
async def _get_pools(self) -> List[PoolMetrics]:
|
async def _get_pools(self) -> list[PoolMetrics]:
|
||||||
pass
|
return []
|
||||||
|
|
||||||
async def _get_data(
|
async def _get_data(
|
||||||
self,
|
self,
|
||||||
allow_warning: bool,
|
allow_warning: bool,
|
||||||
include: List[Union[str, DataOptions]] = None,
|
include: list[str | DataOptions] | None = None,
|
||||||
exclude: List[Union[str, DataOptions]] = None,
|
exclude: list[str | DataOptions] | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
# handle include
|
# handle include
|
||||||
if include is not None:
|
if include is not None:
|
||||||
@@ -459,7 +472,7 @@ class MinerProtocol(Protocol):
|
|||||||
for data_name in include:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
# get kwargs needed for the _get_xyz function
|
# get kwargs needed for the _get_xyz function
|
||||||
fn_args = getattr(self.data_locations, data_name).kwargs
|
fn_args = getattr(self.data_locations, str(data_name)).kwargs
|
||||||
|
|
||||||
# keep track of which RPC/Web commands need to be sent
|
# keep track of which RPC/Web commands need to be sent
|
||||||
for arg in fn_args:
|
for arg in fn_args:
|
||||||
@@ -472,13 +485,21 @@ class MinerProtocol(Protocol):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
# create tasks for all commands that need to be sent, or no-op with sleep(0) -> None
|
# create tasks for all commands that need to be sent, or no-op with sleep(0) -> None
|
||||||
if len(rpc_multicommand) > 0:
|
if (
|
||||||
|
len(rpc_multicommand) > 0
|
||||||
|
and self.rpc is not None
|
||||||
|
and hasattr(self.rpc, "multicommand")
|
||||||
|
):
|
||||||
rpc_command_task = asyncio.create_task(
|
rpc_command_task = asyncio.create_task(
|
||||||
self.rpc.multicommand(*rpc_multicommand, allow_warning=allow_warning)
|
self.rpc.multicommand(*rpc_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
rpc_command_task = asyncio.create_task(asyncio.sleep(0))
|
rpc_command_task = asyncio.create_task(asyncio.sleep(0))
|
||||||
if len(web_multicommand) > 0:
|
if (
|
||||||
|
len(web_multicommand) > 0
|
||||||
|
and self.web is not None
|
||||||
|
and hasattr(self.web, "multicommand")
|
||||||
|
):
|
||||||
web_command_task = asyncio.create_task(
|
web_command_task = asyncio.create_task(
|
||||||
self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
|
self.web.multicommand(*web_multicommand, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
@@ -500,7 +521,7 @@ class MinerProtocol(Protocol):
|
|||||||
|
|
||||||
for data_name in include:
|
for data_name in include:
|
||||||
try:
|
try:
|
||||||
fn_args = getattr(self.data_locations, data_name).kwargs
|
fn_args = getattr(self.data_locations, str(data_name)).kwargs
|
||||||
args_to_send = {k.name: None for k in fn_args}
|
args_to_send = {k.name: None for k in fn_args}
|
||||||
for arg in fn_args:
|
for arg in fn_args:
|
||||||
try:
|
try:
|
||||||
@@ -521,7 +542,9 @@ class MinerProtocol(Protocol):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
function = getattr(self, getattr(self.data_locations, data_name).cmd)
|
function = getattr(
|
||||||
|
self, getattr(self.data_locations, str(data_name)).cmd
|
||||||
|
)
|
||||||
miner_data[data_name] = await function(**args_to_send)
|
miner_data[data_name] = await function(**args_to_send)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
@@ -532,8 +555,8 @@ class MinerProtocol(Protocol):
|
|||||||
async def get_data(
|
async def get_data(
|
||||||
self,
|
self,
|
||||||
allow_warning: bool = False,
|
allow_warning: bool = False,
|
||||||
include: List[Union[str, DataOptions]] = None,
|
include: list[str | DataOptions] | None = None,
|
||||||
exclude: List[Union[str, DataOptions]] = None,
|
exclude: list[str | DataOptions] | None = None,
|
||||||
) -> MinerData:
|
) -> MinerData:
|
||||||
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
"""Get data from the miner in the form of [`MinerData`][pyasic.data.MinerData].
|
||||||
|
|
||||||
@@ -551,6 +574,7 @@ class MinerProtocol(Protocol):
|
|||||||
expected_chips=(
|
expected_chips=(
|
||||||
self.expected_chips * self.expected_hashboards
|
self.expected_chips * self.expected_hashboards
|
||||||
if self.expected_chips is not None
|
if self.expected_chips is not None
|
||||||
|
and self.expected_hashboards is not None
|
||||||
else 0
|
else 0
|
||||||
),
|
),
|
||||||
expected_hashboards=self.expected_hashboards,
|
expected_hashboards=self.expected_hashboards,
|
||||||
@@ -576,7 +600,7 @@ class MinerProtocol(Protocol):
|
|||||||
|
|
||||||
|
|
||||||
class BaseMiner(MinerProtocol):
|
class BaseMiner(MinerProtocol):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str, version: str | None = None) -> None:
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
if self.expected_chips is None and self.raw_model is not None:
|
if self.expected_chips is None and self.raw_model is not None:
|
||||||
|
|||||||
@@ -16,10 +16,10 @@
|
|||||||
|
|
||||||
from dataclasses import dataclass, field, make_dataclass
|
from dataclasses import dataclass, field, make_dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
|
|
||||||
class DataOptions(Enum):
|
class DataOptions(Enum):
|
||||||
|
SERIAL_NUMBER = "serial_number"
|
||||||
MAC = "mac"
|
MAC = "mac"
|
||||||
API_VERSION = "api_ver"
|
API_VERSION = "api_ver"
|
||||||
FW_VERSION = "fw_ver"
|
FW_VERSION = "fw_ver"
|
||||||
@@ -66,7 +66,7 @@ class WebAPICommand:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class DataFunction:
|
class DataFunction:
|
||||||
cmd: str
|
cmd: str
|
||||||
kwargs: List[Union[RPCAPICommand, WebAPICommand]] = field(default_factory=list)
|
kwargs: list[RPCAPICommand | WebAPICommand] = field(default_factory=list)
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return self
|
return self
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.device.algorithm import MinerAlgo
|
from pyasic.device.algorithm import MinerAlgo
|
||||||
from pyasic.device.models import MinerModel
|
from pyasic.device.models import MinerModel, MinerModelType
|
||||||
from pyasic.miners.device.makes import AntMinerMake
|
from pyasic.miners.device.makes import AntMinerMake
|
||||||
|
|
||||||
|
|
||||||
@@ -118,7 +118,7 @@ class S19jNoPIC(AntMinerMake):
|
|||||||
|
|
||||||
|
|
||||||
class S19jPro(AntMinerMake):
|
class S19jPro(AntMinerMake):
|
||||||
raw_model = MinerModel.ANTMINER.S19jPro
|
raw_model: MinerModelType = MinerModel.ANTMINER.S19jPro
|
||||||
|
|
||||||
expected_chips = 126
|
expected_chips = 126
|
||||||
expected_fans = 4
|
expected_fans = 4
|
||||||
@@ -163,7 +163,7 @@ class S19jProPlusNoPIC(AntMinerMake):
|
|||||||
|
|
||||||
|
|
||||||
class S19kPro(AntMinerMake):
|
class S19kPro(AntMinerMake):
|
||||||
raw_model = MinerModel.ANTMINER.S19kPro
|
raw_model: MinerModelType = MinerModel.ANTMINER.S19kPro
|
||||||
|
|
||||||
expected_chips = 77
|
expected_chips = 77
|
||||||
expected_fans = 4
|
expected_fans = 4
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ from .S19 import (
|
|||||||
S19jProPlus,
|
S19jProPlus,
|
||||||
S19jProPlusNoPIC,
|
S19jProPlusNoPIC,
|
||||||
S19jXP,
|
S19jXP,
|
||||||
S19kPro,
|
|
||||||
S19KPro,
|
S19KPro,
|
||||||
|
S19kPro,
|
||||||
S19kProNoPIC,
|
S19kProNoPIC,
|
||||||
S19NoPIC,
|
S19NoPIC,
|
||||||
S19Plus,
|
S19Plus,
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
# 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 .Byte import *
|
from .byte import *
|
||||||
|
from .mini_doge import *
|
||||||
from .X5 import *
|
from .X5 import *
|
||||||
from .XBox import *
|
from .XBox import *
|
||||||
from .XMax import *
|
from .XMax import *
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Copyright 2022 Upstream Data Inc -
|
# Copyright 2025 Upstream Data Inc -
|
||||||
# -
|
# -
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# you may not use this file except in compliance with the License. -
|
# you may not use this file except in compliance with the License. -
|
||||||
@@ -13,4 +13,4 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .Byte import Byte
|
from .byte import Byte
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Copyright 2022 Upstream Data Inc -
|
# Copyright 2025 Upstream Data Inc -
|
||||||
# -
|
# -
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# you may not use this file except in compliance with the License. -
|
# you may not use this file except in compliance with the License. -
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Copyright 2022 Upstream Data Inc -
|
# Copyright 2025 Upstream Data Inc -
|
||||||
# -
|
# -
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# you may not use this file except in compliance with the License. -
|
# you may not use this file except in compliance with the License. -
|
||||||
@@ -13,4 +13,4 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .Byte import GoldshellByte
|
from .mini_doge import MiniDoge
|
||||||
27
pyasic/miners/device/models/goldshell/mini_doge/mini_doge.py
Normal file
27
pyasic/miners/device/models/goldshell/mini_doge/mini_doge.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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.device.algorithm import MinerAlgo
|
||||||
|
from pyasic.device.models import MinerModel
|
||||||
|
from pyasic.miners.device.makes import GoldshellMake
|
||||||
|
|
||||||
|
|
||||||
|
class MiniDoge(GoldshellMake):
|
||||||
|
raw_model = MinerModel.GOLDSHELL.MiniDoge
|
||||||
|
|
||||||
|
expected_chips = 40
|
||||||
|
expected_fans = 2
|
||||||
|
expected_hashboards = 1
|
||||||
|
algo = MinerAlgo.SCRYPT
|
||||||
@@ -40,5 +40,6 @@ class KS5M(IceRiverMake):
|
|||||||
raw_model = MinerModel.ICERIVER.KS5M
|
raw_model = MinerModel.ICERIVER.KS5M
|
||||||
|
|
||||||
expected_fans = 4
|
expected_fans = 4
|
||||||
|
expected_chips = 18
|
||||||
expected_hashboards = 3
|
expected_hashboards = 3
|
||||||
algo = MinerAlgo.KHEAVYHASH
|
algo = MinerAlgo.KHEAVYHASH
|
||||||
|
|||||||
@@ -1,2 +1,18 @@
|
|||||||
from .ALX import *
|
from .ALX import *
|
||||||
from .KSX import *
|
from .KSX import *
|
||||||
|
|
||||||
|
# Define what gets exported with wildcard to exclude KS3 and KS5
|
||||||
|
# which conflict with antminer models
|
||||||
|
__all__ = [
|
||||||
|
# From ALX
|
||||||
|
"AL3",
|
||||||
|
# From KSX
|
||||||
|
"KS0",
|
||||||
|
"KS1",
|
||||||
|
"KS2",
|
||||||
|
"KS3L",
|
||||||
|
"KS3M",
|
||||||
|
"KS5L",
|
||||||
|
"KS5M",
|
||||||
|
# Note: KS3 and KS5 are excluded to avoid conflicts with antminer
|
||||||
|
]
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ from .M31S_Plus import (
|
|||||||
)
|
)
|
||||||
from .M31SE import M31SEV10, M31SEV20, M31SEV30
|
from .M31SE import M31SEV10, M31SEV20, M31SEV30
|
||||||
from .M32 import M32V10, M32V20
|
from .M32 import M32V10, M32V20
|
||||||
|
from .M32S import M32S
|
||||||
from .M33 import M33V10, M33V20, M33V30
|
from .M33 import M33V10, M33V20, M33V30
|
||||||
from .M33S import M33SVG30
|
from .M33S import M33SVG30
|
||||||
from .M33S_Plus import M33SPlusVG20, M33SPlusVG30, M33SPlusVH20, M33SPlusVH30
|
from .M33S_Plus import M33SPlusVG20, M33SPlusVG30, M33SPlusVH20, M33SPlusVH30
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ import ipaddress
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Any, AsyncGenerator, Callable
|
from collections.abc import AsyncGenerator, Callable
|
||||||
|
from typing import Any, cast
|
||||||
|
|
||||||
import anyio
|
import anyio
|
||||||
import httpx
|
import httpx
|
||||||
@@ -70,7 +71,7 @@ class MinerTypes(enum.Enum):
|
|||||||
MSKMINER = 18
|
MSKMINER = 18
|
||||||
|
|
||||||
|
|
||||||
MINER_CLASSES = {
|
MINER_CLASSES: dict[MinerTypes, dict[str | None, Any]] = {
|
||||||
MinerTypes.ANTMINER: {
|
MinerTypes.ANTMINER: {
|
||||||
None: type("AntminerUnknown", (BMMiner, AntMinerMake), {}),
|
None: type("AntminerUnknown", (BMMiner, AntMinerMake), {}),
|
||||||
"ANTMINER D3": CGMinerD3,
|
"ANTMINER D3": CGMinerD3,
|
||||||
@@ -82,6 +83,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER KS5": BMMinerKS5,
|
"ANTMINER KS5": BMMinerKS5,
|
||||||
"ANTMINER KS5 PRO": BMMinerKS5Pro,
|
"ANTMINER KS5 PRO": BMMinerKS5Pro,
|
||||||
"ANTMINER L7": BMMinerL7,
|
"ANTMINER L7": BMMinerL7,
|
||||||
|
"ANTMINER L7_I": BMMinerL7,
|
||||||
"ANTMINER K7": BMMinerK7,
|
"ANTMINER K7": BMMinerK7,
|
||||||
"ANTMINER D7": BMMinerD7,
|
"ANTMINER D7": BMMinerD7,
|
||||||
"ANTMINER E9 PRO": BMMinerE9Pro,
|
"ANTMINER E9 PRO": BMMinerE9Pro,
|
||||||
@@ -91,6 +93,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S9J": BMMinerS9j,
|
"ANTMINER S9J": BMMinerS9j,
|
||||||
"ANTMINER T9": BMMinerT9,
|
"ANTMINER T9": BMMinerT9,
|
||||||
"ANTMINER L9": BMMinerL9,
|
"ANTMINER L9": BMMinerL9,
|
||||||
|
"ANTMINER L9_I": BMMinerL9,
|
||||||
"ANTMINER Z15": CGMinerZ15,
|
"ANTMINER Z15": CGMinerZ15,
|
||||||
"ANTMINER Z15 PRO": BMMinerZ15Pro,
|
"ANTMINER Z15 PRO": BMMinerZ15Pro,
|
||||||
"ANTMINER S17": BMMinerS17,
|
"ANTMINER S17": BMMinerS17,
|
||||||
@@ -125,6 +128,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER BHB68606": BMMinerS21, # ???
|
"ANTMINER BHB68606": BMMinerS21, # ???
|
||||||
"ANTMINER S21+": BMMinerS21Plus,
|
"ANTMINER S21+": BMMinerS21Plus,
|
||||||
"ANTMINER S21+ HYD.": BMMinerS21PlusHydro,
|
"ANTMINER S21+ HYD.": BMMinerS21PlusHydro,
|
||||||
|
"ANTMINER S21+ HYD": BMMinerS21PlusHydro,
|
||||||
"ANTMINER S21 PRO": BMMinerS21Pro,
|
"ANTMINER S21 PRO": BMMinerS21Pro,
|
||||||
"ANTMINER T21": BMMinerT21,
|
"ANTMINER T21": BMMinerT21,
|
||||||
"ANTMINER S21 HYD.": BMMinerS21Hydro,
|
"ANTMINER S21 HYD.": BMMinerS21Hydro,
|
||||||
@@ -530,6 +534,7 @@ MINER_CLASSES = {
|
|||||||
"GOLDSHELL KDBOXII": GoldshellKDBoxII,
|
"GOLDSHELL KDBOXII": GoldshellKDBoxII,
|
||||||
"GOLDSHELL KDBOXPRO": GoldshellKDBoxPro,
|
"GOLDSHELL KDBOXPRO": GoldshellKDBoxPro,
|
||||||
"GOLDSHELL BYTE": GoldshellByte,
|
"GOLDSHELL BYTE": GoldshellByte,
|
||||||
|
"GOLDSHELL MINIDOGE": GoldshellMiniDoge,
|
||||||
},
|
},
|
||||||
MinerTypes.BRAIINS_OS: {
|
MinerTypes.BRAIINS_OS: {
|
||||||
None: BOSMiner,
|
None: BOSMiner,
|
||||||
@@ -591,6 +596,7 @@ MINER_CLASSES = {
|
|||||||
"ANTMINER S19A PRO": VNishS19aPro,
|
"ANTMINER S19A PRO": VNishS19aPro,
|
||||||
"ANTMINER S19 PRO A": VNishS19ProA,
|
"ANTMINER S19 PRO A": VNishS19ProA,
|
||||||
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
|
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
|
||||||
|
"ANTMINER S19 PRO HYDRO": VNishS19ProHydro,
|
||||||
"ANTMINER S19K PRO": VNishS19kPro,
|
"ANTMINER S19K PRO": VNishS19kPro,
|
||||||
"ANTMINER T19": VNishT19,
|
"ANTMINER T19": VNishT19,
|
||||||
"ANTMINER T21": VNishT21,
|
"ANTMINER T21": VNishT21,
|
||||||
@@ -726,8 +732,9 @@ class MinerFactory:
|
|||||||
async def get_multiple_miners(
|
async def get_multiple_miners(
|
||||||
self, ips: list[str], limit: int = 200
|
self, ips: list[str], limit: int = 200
|
||||||
) -> list[AnyMiner]:
|
) -> list[AnyMiner]:
|
||||||
results = []
|
results: list[AnyMiner] = []
|
||||||
|
|
||||||
|
miner: AnyMiner
|
||||||
async for miner in self.get_miner_generator(ips, limit):
|
async for miner in self.get_miner_generator(ips, limit):
|
||||||
results.append(miner)
|
results.append(miner)
|
||||||
|
|
||||||
@@ -735,7 +742,7 @@ class MinerFactory:
|
|||||||
|
|
||||||
async def get_miner_generator(
|
async def get_miner_generator(
|
||||||
self, ips: list, limit: int = 200
|
self, ips: list, limit: int = 200
|
||||||
) -> AsyncGenerator[AnyMiner]:
|
) -> AsyncGenerator[AnyMiner, None]:
|
||||||
tasks = []
|
tasks = []
|
||||||
semaphore = asyncio.Semaphore(limit)
|
semaphore = asyncio.Semaphore(limit)
|
||||||
|
|
||||||
@@ -744,11 +751,13 @@ class MinerFactory:
|
|||||||
|
|
||||||
for task in tasks:
|
for task in tasks:
|
||||||
async with semaphore:
|
async with semaphore:
|
||||||
result = await task
|
result = await task # type: ignore[func-returns-value]
|
||||||
if result is not None:
|
if result is not None:
|
||||||
yield result
|
yield result
|
||||||
|
|
||||||
async def get_miner(self, ip: str | ipaddress.ip_address) -> AnyMiner | None:
|
async def get_miner(
|
||||||
|
self, ip: str | ipaddress.IPv4Address | ipaddress.IPv6Address
|
||||||
|
) -> AnyMiner | None:
|
||||||
ip = str(ip)
|
ip = str(ip)
|
||||||
|
|
||||||
miner_type = None
|
miner_type = None
|
||||||
@@ -766,7 +775,7 @@ class MinerFactory:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if miner_type is not None:
|
if miner_type is not None:
|
||||||
miner_model = None
|
miner_model: str | None = None
|
||||||
miner_model_fns = {
|
miner_model_fns = {
|
||||||
MinerTypes.ANTMINER: self.get_miner_model_antminer,
|
MinerTypes.ANTMINER: self.get_miner_model_antminer,
|
||||||
MinerTypes.WHATSMINER: self.get_miner_model_whatsminer,
|
MinerTypes.WHATSMINER: self.get_miner_model_whatsminer,
|
||||||
@@ -787,23 +796,35 @@ class MinerFactory:
|
|||||||
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
|
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
|
||||||
MinerTypes.ELPHAPEX: self.get_miner_model_elphapex,
|
MinerTypes.ELPHAPEX: self.get_miner_model_elphapex,
|
||||||
}
|
}
|
||||||
fn = miner_model_fns.get(miner_type)
|
version: str | None = None
|
||||||
|
miner_version_fns = {
|
||||||
|
MinerTypes.WHATSMINER: self.get_miner_version_whatsminer,
|
||||||
|
}
|
||||||
|
model_fn = miner_model_fns.get(miner_type)
|
||||||
|
version_fn = miner_version_fns.get(miner_type)
|
||||||
|
|
||||||
if fn is not None:
|
if model_fn is not None:
|
||||||
# noinspection PyArgumentList
|
# noinspection PyArgumentList
|
||||||
task = asyncio.create_task(fn(ip))
|
model_task = asyncio.create_task(model_fn(ip))
|
||||||
try:
|
try:
|
||||||
miner_model = await asyncio.wait_for(
|
miner_model = await asyncio.wait_for(
|
||||||
task, timeout=settings.get("factory_get_timeout", 3)
|
model_task, timeout=settings.get("factory_get_timeout", 3)
|
||||||
|
)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
pass
|
||||||
|
if version_fn is not None:
|
||||||
|
version_task = asyncio.create_task(version_fn(ip))
|
||||||
|
try:
|
||||||
|
version = await asyncio.wait_for(
|
||||||
|
version_task, timeout=settings.get("factory_get_timeout", 3)
|
||||||
)
|
)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
pass
|
||||||
miner = self._select_miner_from_classes(
|
miner = self._select_miner_from_classes(
|
||||||
ip,
|
ip, miner_type=miner_type, miner_model=miner_model, version=version
|
||||||
miner_type=miner_type,
|
|
||||||
miner_model=miner_model,
|
|
||||||
)
|
)
|
||||||
return miner
|
return miner
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_miner_type(self, ip: str) -> MinerTypes | None:
|
async def _get_miner_type(self, ip: str) -> MinerTypes | None:
|
||||||
tasks = [
|
tasks = [
|
||||||
@@ -834,14 +855,15 @@ class MinerFactory:
|
|||||||
if res is not None:
|
if res is not None:
|
||||||
mtype = MinerTypes.MARATHON
|
mtype = MinerTypes.MARATHON
|
||||||
if mtype == MinerTypes.HAMMER:
|
if mtype == MinerTypes.HAMMER:
|
||||||
res = await self.get_miner_model_hammer(ip)
|
hammer_model = await self.get_miner_model_hammer(ip)
|
||||||
if res is None:
|
if hammer_model is None:
|
||||||
return MinerTypes.HAMMER
|
return MinerTypes.HAMMER
|
||||||
if "HAMMER" in res.upper():
|
if "HAMMER" in hammer_model.upper():
|
||||||
mtype = MinerTypes.HAMMER
|
mtype = MinerTypes.HAMMER
|
||||||
else:
|
else:
|
||||||
mtype = MinerTypes.VOLCMINER
|
mtype = MinerTypes.VOLCMINER
|
||||||
return mtype
|
return mtype
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _web_ping(
|
async def _web_ping(
|
||||||
@@ -903,6 +925,7 @@ class MinerFactory:
|
|||||||
return MinerTypes.INNOSILICON
|
return MinerTypes.INNOSILICON
|
||||||
if "Miner UI" in web_text:
|
if "Miner UI" in web_text:
|
||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
async def _get_miner_socket(self, ip: str) -> MinerTypes | None:
|
||||||
commands = ["version", "devdetails"]
|
commands = ["version", "devdetails"]
|
||||||
@@ -915,6 +938,7 @@ class MinerFactory:
|
|||||||
if data is not None:
|
if data is not None:
|
||||||
d = self._parse_socket_type(data)
|
d = self._parse_socket_type(data)
|
||||||
return d
|
return d
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _socket_ping(ip: str, cmd: str) -> str | None:
|
async def _socket_ping(ip: str, cmd: str) -> str | None:
|
||||||
@@ -925,13 +949,13 @@ class MinerFactory:
|
|||||||
timeout=settings.get("factory_get_timeout", 3),
|
timeout=settings.get("factory_get_timeout", 3),
|
||||||
)
|
)
|
||||||
except (ConnectionError, OSError, asyncio.TimeoutError):
|
except (ConnectionError, OSError, asyncio.TimeoutError):
|
||||||
return
|
return None
|
||||||
|
|
||||||
cmd = {"command": cmd}
|
command_dict = {"command": cmd}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# send the command
|
# send the command
|
||||||
writer.write(json.dumps(cmd).encode("utf-8"))
|
writer.write(json.dumps(command_dict).encode("utf-8"))
|
||||||
await writer.drain()
|
await writer.drain()
|
||||||
|
|
||||||
# loop to receive all the data
|
# loop to receive all the data
|
||||||
@@ -948,11 +972,11 @@ class MinerFactory:
|
|||||||
logger.warning(f"{ip}: Socket ping timeout.")
|
logger.warning(f"{ip}: Socket ping timeout.")
|
||||||
break
|
break
|
||||||
except ConnectionResetError:
|
except ConnectionResetError:
|
||||||
return
|
return None
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
finally:
|
finally:
|
||||||
# Handle cancellation explicitly
|
# Handle cancellation explicitly
|
||||||
if writer.transport.is_closing():
|
if writer.transport.is_closing():
|
||||||
@@ -962,9 +986,10 @@ class MinerFactory:
|
|||||||
try:
|
try:
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
if data:
|
if data:
|
||||||
return data.decode("utf-8")
|
return data.decode("utf-8")
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _parse_socket_type(data: str) -> MinerTypes | None:
|
def _parse_socket_type(data: str) -> MinerTypes | None:
|
||||||
@@ -997,12 +1022,13 @@ class MinerFactory:
|
|||||||
return MinerTypes.AURADINE
|
return MinerTypes.AURADINE
|
||||||
if "VNISH" in upper_data:
|
if "VNISH" in upper_data:
|
||||||
return MinerTypes.VNISH
|
return MinerTypes.VNISH
|
||||||
|
return None
|
||||||
|
|
||||||
async def send_web_command(
|
async def send_web_command(
|
||||||
self,
|
self,
|
||||||
ip: str,
|
ip: str,
|
||||||
location: str,
|
location: str,
|
||||||
auth: httpx.DigestAuth = None,
|
auth: httpx.DigestAuth | None = None,
|
||||||
) -> dict | None:
|
) -> dict | None:
|
||||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
try:
|
try:
|
||||||
@@ -1013,16 +1039,16 @@ class MinerFactory:
|
|||||||
)
|
)
|
||||||
except (httpx.HTTPError, asyncio.TimeoutError):
|
except (httpx.HTTPError, asyncio.TimeoutError):
|
||||||
logger.info(f"{ip}: Web command timeout.")
|
logger.info(f"{ip}: Web command timeout.")
|
||||||
return
|
return None
|
||||||
if data is None:
|
if data is None:
|
||||||
return
|
return None
|
||||||
try:
|
try:
|
||||||
json_data = data.json()
|
json_data = data.json()
|
||||||
except (json.JSONDecodeError, asyncio.TimeoutError):
|
except (json.JSONDecodeError, asyncio.TimeoutError):
|
||||||
try:
|
try:
|
||||||
return json.loads(data.text)
|
return json.loads(data.text)
|
||||||
except (json.JSONDecodeError, httpx.HTTPError):
|
except (json.JSONDecodeError, httpx.HTTPError):
|
||||||
return
|
return None
|
||||||
else:
|
else:
|
||||||
return json_data
|
return json_data
|
||||||
|
|
||||||
@@ -1031,7 +1057,7 @@ class MinerFactory:
|
|||||||
try:
|
try:
|
||||||
reader, writer = await asyncio.open_connection(ip, 4028)
|
reader, writer = await asyncio.open_connection(ip, 4028)
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
cmd = {"command": command}
|
cmd = {"command": command}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1051,26 +1077,26 @@ class MinerFactory:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
return
|
return None
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
if data == b"Socket connect failed: Connection refused\n":
|
if data == b"Socket connect failed: Connection refused\n":
|
||||||
return
|
return None
|
||||||
|
|
||||||
data = await self._fix_api_data(data)
|
data_str = await self._fix_api_data(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(data)
|
data_dict = json.loads(data_str)
|
||||||
except json.JSONDecodeError:
|
except json.JSONDecodeError:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
return data
|
return data_dict
|
||||||
|
|
||||||
async def send_btminer_v3_api_command(self, ip, command):
|
async def send_btminer_v3_api_command(self, ip, command):
|
||||||
try:
|
try:
|
||||||
reader, writer = await asyncio.open_connection(ip, 4433)
|
reader, writer = await asyncio.open_connection(ip, 4433)
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
cmd = {"cmd": command}
|
cmd = {"cmd": command}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1092,11 +1118,11 @@ class MinerFactory:
|
|||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
writer.close()
|
writer.close()
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
return
|
return None
|
||||||
except (ConnectionError, OSError):
|
except (ConnectionError, OSError):
|
||||||
return
|
return None
|
||||||
if data == b"Socket connect failed: Connection refused\n":
|
if data == b"Socket connect failed: Connection refused\n":
|
||||||
return
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(data)
|
data = json.loads(data)
|
||||||
@@ -1140,16 +1166,21 @@ class MinerFactory:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _select_miner_from_classes(
|
def _select_miner_from_classes(
|
||||||
ip: ipaddress.ip_address,
|
ip: str | ipaddress.IPv4Address | ipaddress.IPv6Address,
|
||||||
miner_model: str | None,
|
miner_model: str | None,
|
||||||
miner_type: MinerTypes | None,
|
miner_type: MinerTypes | None,
|
||||||
|
version: str | None = None,
|
||||||
) -> AnyMiner | None:
|
) -> AnyMiner | None:
|
||||||
# special case since hiveon miners return web results copying the antminer stock FW
|
# special case since hiveon miners return web results copying the antminer stock FW
|
||||||
if "HIVEON" in str(miner_model).upper():
|
if "HIVEON" in str(miner_model).upper():
|
||||||
miner_model = str(miner_model).upper().replace(" HIVEON", "")
|
miner_model = str(miner_model).upper().replace(" HIVEON", "")
|
||||||
miner_type = MinerTypes.HIVEON
|
miner_type = MinerTypes.HIVEON
|
||||||
|
|
||||||
|
if miner_type is None:
|
||||||
|
return cast(AnyMiner, UnknownMiner(str(ip), version))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
|
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, version)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
if miner_type in MINER_CLASSES:
|
if miner_type in MINER_CLASSES:
|
||||||
if miner_model is not None:
|
if miner_model is not None:
|
||||||
@@ -1157,8 +1188,8 @@ class MinerFactory:
|
|||||||
f"Partially supported miner found: {miner_model}, type: {miner_type}, please open an issue with miner data "
|
f"Partially supported miner found: {miner_model}, type: {miner_type}, please open an issue with miner data "
|
||||||
f"and this model on GitHub (https://github.com/UpstreamData/pyasic/issues)."
|
f"and this model on GitHub (https://github.com/UpstreamData/pyasic/issues)."
|
||||||
)
|
)
|
||||||
return MINER_CLASSES[miner_type][None](ip)
|
return MINER_CLASSES[miner_type][None](ip, version)
|
||||||
return UnknownMiner(str(ip))
|
return cast(AnyMiner, UnknownMiner(str(ip), version))
|
||||||
|
|
||||||
async def get_miner_model_antminer(self, ip: str) -> str | None:
|
async def get_miner_model_antminer(self, ip: str) -> str | None:
|
||||||
tasks = [
|
tasks = [
|
||||||
@@ -1177,25 +1208,28 @@ class MinerFactory:
|
|||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["minertype"]
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def _get_model_antminer_sock(self, ip: str) -> str | None:
|
async def _get_model_antminer_sock(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
try:
|
||||||
|
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||||
|
|
||||||
if " (" in miner_model:
|
if " (" in miner_model:
|
||||||
split_miner_model = miner_model.split(" (")
|
split_miner_model = miner_model.split(" (")
|
||||||
miner_model = split_miner_model[0]
|
miner_model = split_miner_model[0]
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
sock_json_data = await self.send_api_command(ip, "stats")
|
sock_json_data = await self.send_api_command(ip, "stats")
|
||||||
try:
|
try:
|
||||||
@@ -1208,24 +1242,29 @@ class MinerFactory:
|
|||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_goldshell(self, ip: str) -> str | None:
|
async def get_miner_model_goldshell(self, ip: str) -> str | None:
|
||||||
json_data = await self.send_web_command(ip, "/mcb/status")
|
json_data = await self.send_web_command(ip, "/mcb/status")
|
||||||
|
|
||||||
try:
|
if json_data is not None:
|
||||||
miner_model = json_data["model"].replace("-", " ")
|
try:
|
||||||
|
miner_model = json_data["model"].replace("-", " ")
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_whatsminer(self, ip: str) -> str | None:
|
async def get_miner_model_whatsminer(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
try:
|
||||||
miner_model = miner_model[:-1] + "0"
|
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
|
||||||
return miner_model
|
miner_model = miner_model[:-1] + "0"
|
||||||
except (TypeError, LookupError):
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
sock_json_data_v3 = await self.send_btminer_v3_api_command(
|
sock_json_data_v3 = await self.send_btminer_v3_api_command(
|
||||||
ip, "get.device.info"
|
ip, "get.device.info"
|
||||||
)
|
)
|
||||||
@@ -1236,19 +1275,32 @@ class MinerFactory:
|
|||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def get_miner_version_whatsminer(self, ip: str) -> str | None:
|
||||||
|
sock_json_data = await self.send_api_command(ip, "get_version")
|
||||||
|
if sock_json_data is not None:
|
||||||
|
try:
|
||||||
|
version = sock_json_data["Msg"]["fw_ver"]
|
||||||
|
return version
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
async def get_miner_model_avalonminer(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
try:
|
||||||
if "-" in miner_model:
|
miner_model = sock_json_data["VERSION"][0]["PROD"].upper()
|
||||||
miner_model = miner_model.split("-")[0]
|
if "-" in miner_model:
|
||||||
if miner_model in ["AVALONNANO", "AVALON0O", "AVALONMINER 15"]:
|
miner_model = miner_model.split("-")[0]
|
||||||
subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
if miner_model in ["AVALONNANO", "AVALON0O", "AVALONMINER 15"]:
|
||||||
miner_model = f"AVALONMINER {subtype}"
|
subtype = sock_json_data["VERSION"][0]["MODEL"].upper()
|
||||||
return miner_model
|
miner_model = f"AVALONMINER {subtype}"
|
||||||
except (TypeError, LookupError):
|
return miner_model
|
||||||
pass
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_innosilicon(self, ip: str) -> str | None:
|
async def get_miner_model_innosilicon(self, ip: str) -> str | None:
|
||||||
try:
|
try:
|
||||||
@@ -1264,7 +1316,7 @@ class MinerFactory:
|
|||||||
)
|
)
|
||||||
auth = auth_req.json()["jwt"]
|
auth = auth_req.json()["jwt"]
|
||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
return
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
@@ -1278,6 +1330,7 @@ class MinerFactory:
|
|||||||
return web_data["type"]
|
return web_data["type"]
|
||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
web_data = (
|
web_data = (
|
||||||
@@ -1290,18 +1343,21 @@ class MinerFactory:
|
|||||||
return web_data["type"]
|
return web_data["type"]
|
||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_braiins_os(self, ip: str) -> str | None:
|
async def get_miner_model_braiins_os(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = (
|
try:
|
||||||
sock_json_data["DEVDETAILS"][0]["Model"]
|
miner_model = (
|
||||||
.replace("Bitmain ", "")
|
sock_json_data["DEVDETAILS"][0]["Model"]
|
||||||
.replace("S19XP", "S19 XP")
|
.replace("Bitmain ", "")
|
||||||
)
|
.replace("S19XP", "S19 XP")
|
||||||
return miner_model
|
)
|
||||||
except (TypeError, LookupError):
|
return miner_model
|
||||||
pass
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
async with httpx.AsyncClient(transport=settings.transport()) as session:
|
||||||
@@ -1317,64 +1373,75 @@ class MinerFactory:
|
|||||||
return miner_model
|
return miner_model
|
||||||
except (httpx.HTTPError, LookupError):
|
except (httpx.HTTPError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_vnish(self, ip: str) -> str | None:
|
async def get_miner_model_vnish(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "stats")
|
sock_json_data = await self.send_api_command(ip, "stats")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["STATS"][0]["Type"]
|
try:
|
||||||
if " (" in miner_model:
|
miner_model = sock_json_data["STATS"][0]["Type"]
|
||||||
split_miner_model = miner_model.split(" (")
|
if " (" in miner_model:
|
||||||
miner_model = split_miner_model[0]
|
split_miner_model = miner_model.split(" (")
|
||||||
|
miner_model = split_miner_model[0]
|
||||||
|
|
||||||
if "(88)" in miner_model:
|
if "(88)" in miner_model:
|
||||||
miner_model = miner_model.replace("(88)", "NOPIC")
|
miner_model = miner_model.replace("(88)", "NOPIC")
|
||||||
|
|
||||||
if " AML" in miner_model:
|
if " AML" in miner_model:
|
||||||
miner_model = miner_model.replace(" AML", "")
|
miner_model = miner_model.replace(" AML", "")
|
||||||
|
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_epic(self, ip: str) -> str | None:
|
async def get_miner_model_epic(self, ip: str) -> str | None:
|
||||||
for retry_cnt in range(settings.get("get_data_retries", 1)):
|
for retry_cnt in range(settings.get("get_data_retries", 1)):
|
||||||
sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
|
sock_json_data = await self.send_web_command(ip, ":4028/capabilities")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["Model"]
|
try:
|
||||||
return miner_model
|
miner_model = sock_json_data["Model"]
|
||||||
except (TypeError, LookupError):
|
return miner_model
|
||||||
|
except (TypeError, LookupError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
if retry_cnt < settings.get("get_data_retries", 1) - 1:
|
if retry_cnt < settings.get("get_data_retries", 1) - 1:
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_hiveon(self, ip: str) -> str | None:
|
async def get_miner_model_hiveon(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_type = sock_json_data["VERSION"][0]["Type"]
|
try:
|
||||||
|
miner_type = sock_json_data["VERSION"][0]["Type"]
|
||||||
return miner_type.replace(" HIVEON", "")
|
return miner_type.replace(" HIVEON", "")
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_luxos(self, ip: str) -> str | None:
|
async def get_miner_model_luxos(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
miner_model = sock_json_data["VERSION"][0]["Type"]
|
try:
|
||||||
|
miner_model = sock_json_data["VERSION"][0]["Type"]
|
||||||
if " (" in miner_model:
|
if " (" in miner_model:
|
||||||
split_miner_model = miner_model.split(" (")
|
split_miner_model = miner_model.split(" (")
|
||||||
miner_model = split_miner_model[0]
|
miner_model = split_miner_model[0]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_auradine(self, ip: str) -> str | None:
|
async def get_miner_model_auradine(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "devdetails")
|
sock_json_data = await self.send_api_command(ip, "devdetails")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
return sock_json_data["DEVDETAILS"][0]["Model"]
|
try:
|
||||||
except LookupError:
|
return sock_json_data["DEVDETAILS"][0]["Model"]
|
||||||
pass
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_marathon(self, ip: str) -> str | None:
|
async def get_miner_model_marathon(self, ip: str) -> str | None:
|
||||||
auth = httpx.DigestAuth("root", "root")
|
auth = httpx.DigestAuth("root", "root")
|
||||||
@@ -1382,38 +1449,41 @@ class MinerFactory:
|
|||||||
ip, "/kaonsu/v1/overview", auth=auth
|
ip, "/kaonsu/v1/overview", auth=auth
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["model"]
|
try:
|
||||||
if miner_model == "":
|
miner_model = web_json_data["model"]
|
||||||
return None
|
if miner_model == "":
|
||||||
|
return None
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_bitaxe(self, ip: str) -> str | None:
|
async def get_miner_model_bitaxe(self, ip: str) -> str | None:
|
||||||
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["ASICModel"]
|
try:
|
||||||
if miner_model == "":
|
miner_model = web_json_data["ASICModel"]
|
||||||
return None
|
if miner_model == "":
|
||||||
|
return None
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_luckyminer(self, ip: str) -> str | None:
|
async def get_miner_model_luckyminer(self, ip: str) -> str | None:
|
||||||
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["minerModel"]
|
try:
|
||||||
if miner_model == "":
|
miner_model = web_json_data["minerModel"]
|
||||||
return None
|
if miner_model == "":
|
||||||
|
return None
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_iceriver(self, ip: str) -> str | None:
|
async def get_miner_model_iceriver(self, ip: str) -> str | None:
|
||||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||||
@@ -1436,7 +1506,7 @@ class MinerFactory:
|
|||||||
f"http://{ip}:/user/userpanel", params={"post": "4"}
|
f"http://{ip}:/user/userpanel", params={"post": "4"}
|
||||||
)
|
)
|
||||||
if not resp.status_code == 200:
|
if not resp.status_code == 200:
|
||||||
return
|
return None
|
||||||
result = resp.json()
|
result = resp.json()
|
||||||
software_ver = result["data"]["softver1"]
|
software_ver = result["data"]["softver1"]
|
||||||
split_ver = software_ver.split("_")
|
split_ver = software_ver.split("_")
|
||||||
@@ -1447,6 +1517,7 @@ class MinerFactory:
|
|||||||
return miner_ver.upper()
|
return miner_ver.upper()
|
||||||
except httpx.HTTPError:
|
except httpx.HTTPError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_hammer(self, ip: str) -> str | None:
|
async def get_miner_model_hammer(self, ip: str) -> str | None:
|
||||||
auth = httpx.DigestAuth(
|
auth = httpx.DigestAuth(
|
||||||
@@ -1456,12 +1527,13 @@ class MinerFactory:
|
|||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["minertype"]
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_volcminer(self, ip: str) -> str | None:
|
async def get_miner_model_volcminer(self, ip: str) -> str | None:
|
||||||
auth = httpx.DigestAuth(
|
auth = httpx.DigestAuth(
|
||||||
@@ -1471,12 +1543,13 @@ class MinerFactory:
|
|||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["minertype"]
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_elphapex(self, ip: str) -> str | None:
|
async def get_miner_model_elphapex(self, ip: str) -> str | None:
|
||||||
auth = httpx.DigestAuth(
|
auth = httpx.DigestAuth(
|
||||||
@@ -1486,24 +1559,29 @@ class MinerFactory:
|
|||||||
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
ip, "/cgi-bin/get_system_info.cgi", auth=auth
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
if web_json_data is not None:
|
||||||
miner_model = web_json_data["minertype"]
|
try:
|
||||||
|
miner_model = web_json_data["minertype"]
|
||||||
return miner_model
|
return miner_model
|
||||||
except (TypeError, LookupError):
|
except (TypeError, LookupError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def get_miner_model_mskminer(self, ip: str) -> str | None:
|
async def get_miner_model_mskminer(self, ip: str) -> str | None:
|
||||||
sock_json_data = await self.send_api_command(ip, "version")
|
sock_json_data = await self.send_api_command(ip, "version")
|
||||||
try:
|
if sock_json_data is not None:
|
||||||
return sock_json_data["VERSION"][0]["Type"].split(" ")[0]
|
try:
|
||||||
except LookupError:
|
return sock_json_data["VERSION"][0]["Type"].split(" ")[0]
|
||||||
pass
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
miner_factory = MinerFactory()
|
miner_factory = MinerFactory()
|
||||||
|
|
||||||
|
|
||||||
# abstracted version of get miner that is easier to access
|
# abstracted version of get miner that is easier to access
|
||||||
async def get_miner(ip: ipaddress.ip_address | str) -> AnyMiner:
|
async def get_miner(
|
||||||
return await miner_factory.get_miner(ip)
|
ip: str | ipaddress.IPv4Address | ipaddress.IPv6Address,
|
||||||
|
) -> AnyMiner | None:
|
||||||
|
return await miner_factory.get_miner(ip) # type: ignore[func-returns-value]
|
||||||
|
|||||||
@@ -13,7 +13,8 @@
|
|||||||
# 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 .Byte import *
|
from .byte import *
|
||||||
|
from .mini_doge import *
|
||||||
from .X5 import *
|
from .X5 import *
|
||||||
from .XBox import *
|
from .XBox import *
|
||||||
from .XMax import *
|
from .XMax import *
|
||||||
|
|||||||
16
pyasic/miners/goldshell/bfgminer/byte/__init__.py
Normal file
16
pyasic/miners/goldshell/bfgminer/byte/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 .byte import GoldshellByte
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
# Copyright 2022 Upstream Data Inc -
|
# Copyright 2025 Upstream Data Inc -
|
||||||
# -
|
# -
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||||
# you may not use this file except in compliance with the License. -
|
# you may not use this file except in compliance with the License. -
|
||||||
@@ -13,13 +13,12 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, MinerData
|
from pyasic.data import Fan, MinerData
|
||||||
from pyasic.data.boards import HashBoard
|
from pyasic.data.boards import HashBoard
|
||||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||||
from pyasic.device.algorithm import AlgoHashRate, MinerAlgo
|
from pyasic.device.algorithm import AlgoHashRateType, MinerAlgo
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends import GoldshellMiner
|
from pyasic.miners.backends import GoldshellMiner
|
||||||
from pyasic.miners.data import (
|
from pyasic.miners.data import (
|
||||||
@@ -73,45 +72,46 @@ GOLDSHELL_BYTE_DATA_LOC = DataLocations(
|
|||||||
"_get_pools",
|
"_get_pools",
|
||||||
[RPCAPICommand("rpc_pools", "pools")],
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
),
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[WebAPICommand("web_devs", "devs")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
|
"_get_wattage",
|
||||||
|
[WebAPICommand("web_devs", "devs")],
|
||||||
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class GoldshellByte(GoldshellMiner, Byte):
|
class GoldshellByte(GoldshellMiner, Byte):
|
||||||
|
|
||||||
data_locations = GOLDSHELL_BYTE_DATA_LOC
|
data_locations = GOLDSHELL_BYTE_DATA_LOC
|
||||||
|
supports_shutdown = False
|
||||||
cgdev: dict | None = None
|
supports_power_modes = False
|
||||||
|
web_devs: dict | None = None
|
||||||
|
|
||||||
async def get_data(
|
async def get_data(
|
||||||
self,
|
self,
|
||||||
allow_warning: bool = False,
|
allow_warning: bool = False,
|
||||||
include: List[Union[str, DataOptions]] = None,
|
include: list[str | DataOptions] | None = None,
|
||||||
exclude: List[Union[str, DataOptions]] = None,
|
exclude: list[str | DataOptions] | None = None,
|
||||||
) -> MinerData:
|
) -> MinerData:
|
||||||
if self.cgdev is None:
|
if self.web_devs is None:
|
||||||
try:
|
try:
|
||||||
self.cgdev = await self.web.send_command("cgminer?cgminercmd=devs")
|
self.web_devs = await self.web.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
scrypt_board_count = 0
|
scrypt_board_count = 0
|
||||||
zksnark_board_count = 0
|
zksnark_board_count = 0
|
||||||
total_wattage = 0
|
|
||||||
total_uptime_mins = 0
|
|
||||||
|
|
||||||
for minfo in self.cgdev.get("minfos", []):
|
|
||||||
|
|
||||||
|
for minfo in (self.web_devs or {}).get("minfos", []):
|
||||||
algo_name = minfo.get("name")
|
algo_name = minfo.get("name")
|
||||||
|
|
||||||
for info in minfo.get("infos", []):
|
for _ in minfo.get("infos", []):
|
||||||
|
|
||||||
self.expected_hashboards += 1
|
self.expected_hashboards += 1
|
||||||
self.expected_fans += 1
|
self.expected_fans += 1
|
||||||
|
|
||||||
total_wattage = int(float(info.get("power", 0)))
|
|
||||||
total_uptime_mins = int(info.get("time", 0))
|
|
||||||
|
|
||||||
if algo_name == ALGORITHM_SCRYPT_NAME:
|
if algo_name == ALGORITHM_SCRYPT_NAME:
|
||||||
scrypt_board_count += 1
|
scrypt_board_count += 1
|
||||||
elif algo_name == ALGORITHM_ZKSNARK_NAME:
|
elif algo_name == ALGORITHM_ZKSNARK_NAME:
|
||||||
@@ -122,18 +122,12 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if scrypt_board_count > 0 and zksnark_board_count == 0:
|
if scrypt_board_count > 0 and zksnark_board_count == 0:
|
||||||
self.algo = MinerAlgo.SCRYPT
|
self.algo = MinerAlgo.SCRYPT # type: ignore[assignment]
|
||||||
elif zksnark_board_count > 0 and scrypt_board_count == 0:
|
elif zksnark_board_count > 0 and scrypt_board_count == 0:
|
||||||
self.algo = MinerAlgo.ZKSNARK
|
self.algo = MinerAlgo.ZKSNARK # type: ignore[assignment]
|
||||||
|
|
||||||
data = await super().get_data(allow_warning, include, exclude)
|
data = await super().get_data(allow_warning, include, exclude)
|
||||||
data.expected_chips = self.expected_chips
|
data.expected_chips = self.expected_chips
|
||||||
data.wattage = total_wattage
|
|
||||||
data.uptime = total_uptime_mins
|
|
||||||
data.voltage = 0
|
|
||||||
|
|
||||||
for board in data.hashboards:
|
|
||||||
data.voltage += board.voltage
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -141,12 +135,12 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
try:
|
try:
|
||||||
pools = await self.web.pools()
|
pools = await self.web.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config or MinerConfig()
|
||||||
|
|
||||||
self.config = MinerConfig.from_goldshell_byte(pools)
|
self.config = MinerConfig.from_goldshell_byte(pools.get("groups", []))
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def _get_api_ver(self, web_setting: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, web_setting: dict | None = None) -> str | None:
|
||||||
if web_setting is None:
|
if web_setting is None:
|
||||||
try:
|
try:
|
||||||
web_setting = await self.web.setting()
|
web_setting = await self.web.setting()
|
||||||
@@ -165,19 +159,18 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_expected_hashrate(
|
async def _get_expected_hashrate(
|
||||||
self, rpc_devs: dict = None
|
self, rpc_devs: dict | None = None
|
||||||
) -> Optional[AlgoHashRate]:
|
) -> 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
|
pass
|
||||||
|
|
||||||
total_hash_rate_mh = 0
|
total_hash_rate_mh = 0.0
|
||||||
|
|
||||||
if rpc_devs is not None:
|
if rpc_devs is not None:
|
||||||
for board in rpc_devs.get("DEVS", []):
|
for board in rpc_devs.get("DEVS", []):
|
||||||
|
|
||||||
algo_name = board.get("pool")
|
algo_name = board.get("pool")
|
||||||
|
|
||||||
if algo_name == ALGORITHM_SCRYPT_NAME:
|
if algo_name == ALGORITHM_SCRYPT_NAME:
|
||||||
@@ -198,14 +191,16 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
|
|
||||||
return hash_rate
|
return hash_rate
|
||||||
|
|
||||||
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
|
pass
|
||||||
|
|
||||||
total_hash_rate_mh = 0
|
total_hash_rate_mh = 0.0
|
||||||
|
|
||||||
if rpc_devs is not None:
|
if rpc_devs is not None:
|
||||||
for board in rpc_devs.get("DEVS", []):
|
for board in rpc_devs.get("DEVS", []):
|
||||||
@@ -217,7 +212,7 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
|
|
||||||
return hash_rate
|
return hash_rate
|
||||||
|
|
||||||
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()
|
||||||
@@ -246,8 +241,8 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
return pools_data
|
return pools_data
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, rpc_devs: dict = None, rpc_devdetails: dict = None
|
self, rpc_devs: dict | None = None, rpc_devdetails: dict | None = None
|
||||||
) -> List[HashBoard]:
|
) -> list[HashBoard]:
|
||||||
if rpc_devs is None:
|
if rpc_devs is None:
|
||||||
try:
|
try:
|
||||||
rpc_devs = await self.rpc.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
@@ -291,7 +286,7 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fans(self, rpc_devs: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_devs: dict | None = None) -> list[Fan]:
|
||||||
if self.expected_fans is None:
|
if self.expected_fans is None:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -317,3 +312,39 @@ class GoldshellByte(GoldshellMiner, Byte):
|
|||||||
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
|
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
|
||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
|
async def _get_uptime(self, web_devs: dict | None = None) -> int | None:
|
||||||
|
if web_devs is None:
|
||||||
|
try:
|
||||||
|
web_devs = await self.web.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_devs is not None:
|
||||||
|
try:
|
||||||
|
for minfo in (self.web_devs or {}).get("minfos", []):
|
||||||
|
for info in minfo.get("infos", []):
|
||||||
|
uptime = int(float(info["time"]))
|
||||||
|
return uptime
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_wattage(self, web_devs: dict | None = None) -> int | None:
|
||||||
|
if web_devs is None:
|
||||||
|
try:
|
||||||
|
web_devs = await self.web.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_devs is not None:
|
||||||
|
try:
|
||||||
|
for minfo in (self.web_devs or {}).get("minfos", []):
|
||||||
|
for info in minfo.get("infos", []):
|
||||||
|
wattage = int(float(info["power"]))
|
||||||
|
return wattage
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
16
pyasic/miners/goldshell/bfgminer/mini_doge/__init__.py
Normal file
16
pyasic/miners/goldshell/bfgminer/mini_doge/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 .mini_doge import GoldshellMiniDoge
|
||||||
179
pyasic/miners/goldshell/bfgminer/mini_doge/mini_doge.py
Normal file
179
pyasic/miners/goldshell/bfgminer/mini_doge/mini_doge.py
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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.config import MinerConfig
|
||||||
|
from pyasic.data.boards import HashBoard
|
||||||
|
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.logger import logger
|
||||||
|
from pyasic.miners.backends import GoldshellMiner
|
||||||
|
from pyasic.miners.data import (
|
||||||
|
DataFunction,
|
||||||
|
DataLocations,
|
||||||
|
DataOptions,
|
||||||
|
RPCAPICommand,
|
||||||
|
WebAPICommand,
|
||||||
|
)
|
||||||
|
from pyasic.miners.device.models import MiniDoge
|
||||||
|
|
||||||
|
GOLDSHELL_MINI_DOGE_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_setting", "setting")],
|
||||||
|
),
|
||||||
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_status", "status")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_devs", "devs")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[
|
||||||
|
RPCAPICommand("rpc_devs", "devs"),
|
||||||
|
RPCAPICommand("rpc_devdetails", "devdetails"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.POOLS): DataFunction(
|
||||||
|
"_get_pools",
|
||||||
|
[RPCAPICommand("rpc_pools", "pools")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[WebAPICommand("web_devs", "devs")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class GoldshellMiniDoge(GoldshellMiner, MiniDoge):
|
||||||
|
data_locations = GOLDSHELL_MINI_DOGE_DATA_LOC
|
||||||
|
supports_shutdown = False
|
||||||
|
supports_power_modes = False
|
||||||
|
|
||||||
|
async def get_config(self) -> MinerConfig | None: # type: ignore[override]
|
||||||
|
try:
|
||||||
|
pools = await self.web.pools()
|
||||||
|
except APIError:
|
||||||
|
if self.config is not None:
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
self.config = MinerConfig.from_goldshell(pools)
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
async def _get_expected_hashrate(
|
||||||
|
self, rpc_devs: dict | None = None
|
||||||
|
) -> AlgoHashRateType | None:
|
||||||
|
if rpc_devs is None:
|
||||||
|
try:
|
||||||
|
rpc_devs = await self.rpc.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_devs is not None:
|
||||||
|
try:
|
||||||
|
hash_rate = rpc_devs["DEVS"][0]["estimate_hash_rate"]
|
||||||
|
return self.algo.hashrate(
|
||||||
|
rate=float(hash_rate), unit=self.algo.unit.H
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
async def _get_hashboards(
|
||||||
|
self, rpc_devs: dict | None = None, rpc_devdetails: dict | None = None
|
||||||
|
) -> list[HashBoard]:
|
||||||
|
if rpc_devs is None:
|
||||||
|
try:
|
||||||
|
rpc_devs = await self.rpc.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
hashboards = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
|
for i in range(self.expected_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if rpc_devs is not None:
|
||||||
|
if rpc_devs.get("DEVS"):
|
||||||
|
for board in rpc_devs["DEVS"]:
|
||||||
|
if board.get("ID") is not None:
|
||||||
|
try:
|
||||||
|
b_id = board["ID"]
|
||||||
|
hashboards[b_id].hashrate = self.algo.hashrate(
|
||||||
|
rate=float(board["MHS 20s"]), unit=self.algo.unit.MH
|
||||||
|
).into(self.algo.unit.default)
|
||||||
|
hashboards[b_id].chip_temp = board["tstemp-0"]
|
||||||
|
hashboards[b_id].temp = board["tstemp-1"]
|
||||||
|
hashboards[b_id].voltage = board["voltage"]
|
||||||
|
hashboards[b_id].active = board["Status"] == "Alive"
|
||||||
|
hashboards[b_id].missing = False
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
logger.error(self, rpc_devs)
|
||||||
|
|
||||||
|
if rpc_devdetails is None:
|
||||||
|
try:
|
||||||
|
rpc_devdetails = await self.rpc.devdetails()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_devdetails is not None:
|
||||||
|
if rpc_devdetails.get("DEVS"):
|
||||||
|
for board in rpc_devdetails["DEVS"]:
|
||||||
|
if board.get("ID") is not None:
|
||||||
|
try:
|
||||||
|
b_id = board["ID"]
|
||||||
|
hashboards[b_id].chips = board["chips-nr"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
logger.error(self, rpc_devdetails)
|
||||||
|
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
async def _get_uptime(self, web_devs: dict | None = None) -> int | None:
|
||||||
|
if web_devs is None:
|
||||||
|
try:
|
||||||
|
web_devs = await self.web.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_devs is not None:
|
||||||
|
try:
|
||||||
|
uptime = int(web_devs["data"][0]["time"])
|
||||||
|
return uptime
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return None
|
||||||
@@ -55,7 +55,7 @@ class MinerListenerProtocol(asyncio.Protocol):
|
|||||||
|
|
||||||
class MinerListener:
|
class MinerListener:
|
||||||
def __init__(self, bind_addr: str = "0.0.0.0"):
|
def __init__(self, bind_addr: str = "0.0.0.0"):
|
||||||
self.found_miners = []
|
self.found_miners: list[dict[str, str]] = []
|
||||||
self.stop = asyncio.Event()
|
self.stop = asyncio.Event()
|
||||||
self.bind_addr = bind_addr
|
self.bind_addr = bind_addr
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
from typing import AsyncIterator, List, Union
|
from collections.abc import AsyncIterator
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.miners.factory import AnyMiner, miner_factory
|
from pyasic.miners.factory import AnyMiner, miner_factory
|
||||||
@@ -30,24 +31,24 @@ class MinerNetwork:
|
|||||||
hosts: A list of `ipaddress.IPv4Address` to be used when scanning.
|
hosts: A list of `ipaddress.IPv4Address` to be used when scanning.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, hosts: List[ipaddress.IPv4Address]):
|
def __init__(self, hosts: list[ipaddress.IPv4Address]):
|
||||||
self.hosts = hosts
|
self.hosts = hosts
|
||||||
semaphore_limit = settings.get("network_scan_semaphore", 255)
|
semaphore_limit = settings.get("network_scan_semaphore", 255)
|
||||||
if semaphore_limit is None:
|
if semaphore_limit is None:
|
||||||
semaphore_limit = 255
|
semaphore_limit = 255
|
||||||
self.semaphore = asyncio.Semaphore(semaphore_limit)
|
self.semaphore = asyncio.Semaphore(semaphore_limit)
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self) -> int:
|
||||||
return len(self.hosts)
|
return len(self.hosts)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_list(cls, addresses: list) -> "MinerNetwork":
|
def from_list(cls, addresses: list[str]) -> "MinerNetwork":
|
||||||
"""Parse a list of address constructors into a MinerNetwork.
|
"""Parse a list of address constructors into a MinerNetwork.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
addresses: A list of address constructors, such as `["10.1-2.1.1-50", "10.4.1-2.1-50"]`.
|
addresses: A list of address constructors, such as `["10.1-2.1.1-50", "10.4.1-2.1-50"]`.
|
||||||
"""
|
"""
|
||||||
hosts = []
|
hosts: list[ipaddress.IPv4Address] = []
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
hosts = [*hosts, *cls.from_address(address).hosts]
|
hosts = [*hosts, *cls.from_address(address).hosts]
|
||||||
return cls(sorted(list(set(hosts))))
|
return cls(sorted(list(set(hosts))))
|
||||||
@@ -79,7 +80,7 @@ class MinerNetwork:
|
|||||||
oct_4: An octet constructor, such as `"1-50"`.
|
oct_4: An octet constructor, such as `"1-50"`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
hosts = []
|
hosts: list[ipaddress.IPv4Address] = []
|
||||||
|
|
||||||
oct_1_start, oct_1_end = compute_oct_range(oct_1)
|
oct_1_start, oct_1_end = compute_oct_range(oct_1)
|
||||||
for oct_1_idx in range((abs(oct_1_end - oct_1_start)) + 1):
|
for oct_1_idx in range((abs(oct_1_end - oct_1_start)) + 1):
|
||||||
@@ -97,11 +98,11 @@ class MinerNetwork:
|
|||||||
for oct_4_idx in range((abs(oct_4_end - oct_4_start)) + 1):
|
for oct_4_idx in range((abs(oct_4_end - oct_4_start)) + 1):
|
||||||
oct_4_val = str(oct_4_idx + oct_4_start)
|
oct_4_val = str(oct_4_idx + oct_4_start)
|
||||||
|
|
||||||
hosts.append(
|
ip_addr = ipaddress.ip_address(
|
||||||
ipaddress.ip_address(
|
".".join([oct_1_val, oct_2_val, oct_3_val, oct_4_val])
|
||||||
".".join([oct_1_val, oct_2_val, oct_3_val, oct_4_val])
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
if isinstance(ip_addr, ipaddress.IPv4Address):
|
||||||
|
hosts.append(ip_addr)
|
||||||
return cls(sorted(hosts))
|
return cls(sorted(hosts))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -111,9 +112,13 @@ class MinerNetwork:
|
|||||||
Parameters:
|
Parameters:
|
||||||
subnet: A subnet string, such as `"10.0.0.1/24"`.
|
subnet: A subnet string, such as `"10.0.0.1/24"`.
|
||||||
"""
|
"""
|
||||||
return cls(list(ipaddress.ip_network(subnet, strict=False).hosts()))
|
network = ipaddress.ip_network(subnet, strict=False)
|
||||||
|
hosts = [
|
||||||
|
host for host in network.hosts() if isinstance(host, ipaddress.IPv4Address)
|
||||||
|
]
|
||||||
|
return cls(hosts)
|
||||||
|
|
||||||
async def scan(self) -> List[AnyMiner]:
|
async def scan(self) -> list[AnyMiner]:
|
||||||
"""Scan the network for miners.
|
"""Scan the network for miners.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -121,15 +126,17 @@ class MinerNetwork:
|
|||||||
"""
|
"""
|
||||||
return await self.scan_network_for_miners()
|
return await self.scan_network_for_miners()
|
||||||
|
|
||||||
async def scan_network_for_miners(self) -> List[AnyMiner]:
|
async def scan_network_for_miners(self) -> list[AnyMiner]:
|
||||||
logging.debug(f"{self} - (Scan Network For Miners) - Scanning")
|
logging.debug(f"{self} - (Scan Network For Miners) - Scanning")
|
||||||
|
|
||||||
miners = await asyncio.gather(
|
raw_miners: list[AnyMiner | None] = await asyncio.gather(
|
||||||
*[self.ping_and_get_miner(host) for host in self.hosts]
|
*[self.ping_and_get_miner(host) for host in self.hosts]
|
||||||
)
|
)
|
||||||
|
|
||||||
# remove all None from the miner list
|
# remove all None from the miner list
|
||||||
miners = list(filter(None, miners))
|
miners: list[AnyMiner] = cast(
|
||||||
|
list[AnyMiner], [miner for miner in raw_miners if miner is not None]
|
||||||
|
)
|
||||||
logging.debug(
|
logging.debug(
|
||||||
f"{self} - (Scan Network For Miners) - Found {len(miners)} miners"
|
f"{self} - (Scan Network For Miners) - Found {len(miners)} miners"
|
||||||
)
|
)
|
||||||
@@ -137,7 +144,7 @@ class MinerNetwork:
|
|||||||
# return the miner objects
|
# return the miner objects
|
||||||
return miners
|
return miners
|
||||||
|
|
||||||
async def scan_network_generator(self) -> AsyncIterator[AnyMiner]:
|
async def scan_network_generator(self) -> AsyncIterator[AnyMiner | None]:
|
||||||
"""
|
"""
|
||||||
Scan the network for miners using an async generator.
|
Scan the network for miners using an async generator.
|
||||||
|
|
||||||
@@ -145,39 +152,47 @@ class MinerNetwork:
|
|||||||
An asynchronous generator containing found miners.
|
An asynchronous generator containing found miners.
|
||||||
"""
|
"""
|
||||||
# create a list of scan tasks
|
# create a list of scan tasks
|
||||||
miners = asyncio.as_completed(
|
tasks: list[asyncio.Task[AnyMiner | None]] = [
|
||||||
[asyncio.create_task(self.ping_and_get_miner(host)) for host in self.hosts]
|
asyncio.create_task(self.ping_and_get_miner(host)) for host in self.hosts
|
||||||
)
|
]
|
||||||
for miner in miners:
|
for miner in asyncio.as_completed(tasks):
|
||||||
try:
|
try:
|
||||||
yield await miner
|
result = await miner
|
||||||
|
yield result
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
yield None
|
yield None
|
||||||
|
return
|
||||||
|
|
||||||
async def ping_and_get_miner(
|
async def ping_and_get_miner(
|
||||||
self, ip: ipaddress.ip_address
|
self, ip: ipaddress.IPv4Address | ipaddress.IPv6Address
|
||||||
) -> Union[None, AnyMiner]:
|
) -> AnyMiner | None:
|
||||||
if settings.get("network_scan_semaphore") is None:
|
if settings.get("network_scan_semaphore") is None:
|
||||||
return await self._ping_and_get_miner(ip)
|
return await self._ping_and_get_miner(ip) # type: ignore[func-returns-value]
|
||||||
async with self.semaphore:
|
async with self.semaphore:
|
||||||
return await self._ping_and_get_miner(ip)
|
return await self._ping_and_get_miner(ip) # type: ignore[func-returns-value]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def _ping_and_get_miner(ip: ipaddress.ip_address) -> Union[None, AnyMiner]:
|
async def _ping_and_get_miner(
|
||||||
|
ip: ipaddress.IPv4Address | ipaddress.IPv6Address,
|
||||||
|
) -> AnyMiner | None:
|
||||||
try:
|
try:
|
||||||
return await ping_and_get_miner(ip)
|
return await ping_and_get_miner(ip) # type: ignore[func-returns-value]
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]]
|
tasks: list[asyncio.Task[AnyMiner | None]] = [
|
||||||
|
asyncio.create_task(ping_and_get_miner(ip, port=port))
|
||||||
|
for port in [4028, 4029, 8889]
|
||||||
|
]
|
||||||
for miner in asyncio.as_completed(tasks):
|
for miner in asyncio.as_completed(tasks):
|
||||||
try:
|
try:
|
||||||
return await miner
|
return await miner
|
||||||
except ConnectionRefusedError:
|
except ConnectionRefusedError:
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
async def ping_and_get_miner(
|
async def ping_and_get_miner(
|
||||||
ip: ipaddress.ip_address, port=80
|
ip: ipaddress.IPv4Address | ipaddress.IPv6Address, port: int = 80
|
||||||
) -> Union[None, AnyMiner]:
|
) -> AnyMiner | None:
|
||||||
for _ in range(settings.get("network_ping_retries", 1)):
|
for _ in range(settings.get("network_ping_retries", 1)):
|
||||||
try:
|
try:
|
||||||
connection_fut = asyncio.open_connection(str(ip), port)
|
connection_fut = asyncio.open_connection(str(ip), port)
|
||||||
@@ -190,7 +205,7 @@ async def ping_and_get_miner(
|
|||||||
# make sure the writer is closed
|
# make sure the writer is closed
|
||||||
await writer.wait_closed()
|
await writer.wait_closed()
|
||||||
# ping was successful
|
# ping was successful
|
||||||
return await miner_factory.get_miner(ip)
|
return await miner_factory.get_miner(ip) # type: ignore[func-returns-value]
|
||||||
except asyncio.exceptions.TimeoutError:
|
except asyncio.exceptions.TimeoutError:
|
||||||
# ping failed if we time out
|
# ping failed if we time out
|
||||||
continue
|
continue
|
||||||
@@ -198,11 +213,11 @@ async def ping_and_get_miner(
|
|||||||
raise ConnectionRefusedError from e
|
raise ConnectionRefusedError from e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{str(ip)}: Unhandled ping exception: {e}")
|
logging.warning(f"{str(ip)}: Unhandled ping exception: {e}")
|
||||||
return
|
return None
|
||||||
return
|
return None
|
||||||
|
|
||||||
|
|
||||||
def compute_oct_range(octet: str) -> tuple:
|
def compute_oct_range(octet: str) -> tuple[int, int]:
|
||||||
octet_split = octet.split("-")
|
octet_split = octet.split("-")
|
||||||
octet_start = int(octet_split[0])
|
octet_start = int(octet_split[0])
|
||||||
octet_end = None
|
octet_end = None
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
from pyasic.misc import validate_command_output
|
from pyasic.misc import validate_command_output
|
||||||
@@ -35,7 +34,7 @@ class BaseMinerRPCAPI:
|
|||||||
# api version if known
|
# api version if known
|
||||||
self.api_ver = api_ver
|
self.api_ver = api_ver
|
||||||
|
|
||||||
self.pwd = None
|
self.pwd: str | None = None
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
if cls is BaseMinerRPCAPI:
|
if cls is BaseMinerRPCAPI:
|
||||||
@@ -47,8 +46,8 @@ class BaseMinerRPCAPI:
|
|||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
command: Union[str, bytes],
|
command: str,
|
||||||
parameters: Union[str, int, bool] = None,
|
parameters: str | int | bool | None = None,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
allow_warning: bool = True,
|
allow_warning: bool = True,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -86,10 +85,10 @@ class BaseMinerRPCAPI:
|
|||||||
raise APIError(data.decode("utf-8"))
|
raise APIError(data.decode("utf-8"))
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
data = self._load_api_data(data)
|
api_data = self._load_api_data(data)
|
||||||
|
|
||||||
# check for if the user wants to allow errors to return
|
# check for if the user wants to allow errors to return
|
||||||
validation = validate_command_output(data)
|
validation = validate_command_output(api_data)
|
||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# validate the command succeeded
|
# validate the command succeeded
|
||||||
@@ -100,7 +99,7 @@ class BaseMinerRPCAPI:
|
|||||||
)
|
)
|
||||||
|
|
||||||
logging.debug(f"{self} - (Send Command) - Received data.")
|
logging.debug(f"{self} - (Send Command) - Received data.")
|
||||||
return data
|
return api_data
|
||||||
|
|
||||||
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
# Privileged command handler, only used by whatsminers, defined here for consistency.
|
||||||
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
||||||
@@ -115,10 +114,10 @@ class BaseMinerRPCAPI:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
commands = self._check_commands(*commands)
|
valid_commands = self._check_commands(*commands)
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
# doesn't work for S19 which uses the backup _send_split_multicommand
|
# doesn't work for S19 which uses the backup _send_split_multicommand
|
||||||
command = "+".join(commands)
|
command = "+".join(valid_commands)
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(command, allow_warning=allow_warning)
|
data = await self.send_command(command, allow_warning=allow_warning)
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -136,17 +135,16 @@ class BaseMinerRPCAPI:
|
|||||||
self.send_command(cmd, allow_warning=allow_warning)
|
self.send_command(cmd, allow_warning=allow_warning)
|
||||||
)
|
)
|
||||||
|
|
||||||
await asyncio.gather(*[tasks[cmd] for cmd in tasks], return_exceptions=True)
|
results = await asyncio.gather(
|
||||||
|
*[tasks[cmd] for cmd in tasks], return_exceptions=True
|
||||||
|
)
|
||||||
|
|
||||||
data = {}
|
data = {}
|
||||||
for cmd in tasks:
|
for cmd, result in zip(tasks.keys(), results):
|
||||||
try:
|
if not isinstance(result, (APIError, Exception)):
|
||||||
result = tasks[cmd].result()
|
|
||||||
if result is None or result == {}:
|
if result is None or result == {}:
|
||||||
result = {}
|
result = {}
|
||||||
data[cmd] = [result]
|
data[cmd] = [result]
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@@ -165,10 +163,13 @@ class BaseMinerRPCAPI:
|
|||||||
for func in
|
for func in
|
||||||
# each function in self
|
# each function in self
|
||||||
dir(self)
|
dir(self)
|
||||||
if not func in ["commands", "open_api"]
|
if func not in ["commands", "open_api"]
|
||||||
if callable(getattr(self, func)) and
|
if callable(getattr(self, func))
|
||||||
|
and
|
||||||
# no __ or _ methods
|
# no __ or _ methods
|
||||||
not func.startswith("__") and not func.startswith("_") and
|
not func.startswith("__")
|
||||||
|
and not func.startswith("_")
|
||||||
|
and
|
||||||
# remove all functions that are in this base class
|
# remove all functions that are in this base class
|
||||||
func
|
func
|
||||||
not in [
|
not in [
|
||||||
@@ -197,7 +198,7 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
self,
|
self,
|
||||||
data: bytes,
|
data: bytes,
|
||||||
*,
|
*,
|
||||||
port: int = None,
|
port: int | None = None,
|
||||||
timeout: int = 100,
|
timeout: int = 100,
|
||||||
) -> bytes:
|
) -> bytes:
|
||||||
if port is None:
|
if port is None:
|
||||||
@@ -253,10 +254,10 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# some json from the API returns with a null byte (\x00) on the end
|
# some json from the API returns with a null byte (\x00) on the end
|
||||||
if data.endswith(b"\x00"):
|
if data.endswith(b"\x00"):
|
||||||
# handle the null byte
|
# handle the null byte
|
||||||
str_data = data.decode("utf-8")[:-1]
|
str_data = data.decode("utf-8", errors="replace")[:-1]
|
||||||
else:
|
else:
|
||||||
# no null byte
|
# no null byte
|
||||||
str_data = data.decode("utf-8")
|
str_data = data.decode("utf-8", errors="replace")
|
||||||
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
||||||
str_data = str_data.replace(",}", "}")
|
str_data = str_data.replace(",}", "}")
|
||||||
# fix an error with a btminer return having a newline that breaks json.loads()
|
# fix an error with a btminer return having a newline that breaks json.loads()
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ class BFGMinerRPCAPI(CGMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_command("procidentify", parameters=n)
|
return await self.send_command("procidentify", parameters=n)
|
||||||
|
|
||||||
async def procset(self, n: int, opt: str, val: int = None) -> dict:
|
async def procset(self, n: int, opt: str, val: int | None = None) -> dict:
|
||||||
"""Set processor option opt to val on processor n.
|
"""Set processor option opt to val on processor n.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|||||||
@@ -23,15 +23,18 @@ import json
|
|||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
import struct
|
import struct
|
||||||
from asyncio import Future, StreamReader, StreamWriter
|
import typing
|
||||||
from typing import Any, AsyncGenerator, Callable, Literal, Union
|
import warnings
|
||||||
|
from collections.abc import AsyncGenerator
|
||||||
|
from typing import Any, Literal
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
from passlib.handlers.md5_crypt import md5_crypt
|
from passlib.handlers.md5_crypt import md5_crypt
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError, APIWarning
|
||||||
from pyasic.misc import api_min_version, validate_command_output
|
from pyasic.misc import api_min_version, validate_command_output
|
||||||
from pyasic.rpc.base import BaseMinerRPCAPI
|
from pyasic.rpc.base import BaseMinerRPCAPI
|
||||||
|
|
||||||
@@ -43,11 +46,54 @@ from pyasic.rpc.base import BaseMinerRPCAPI
|
|||||||
# you change the password, you can pass that to this class as pwd,
|
# you change the password, you can pass that to this class as pwd,
|
||||||
# or add it as the Whatsminer_pwd in the settings.toml file.
|
# or add it as the Whatsminer_pwd in the settings.toml file.
|
||||||
|
|
||||||
PrePowerOnMessage = Union[
|
|
||||||
Literal["wait for adjust temp"],
|
class TokenResponse(BaseModel):
|
||||||
Literal["adjust complete"],
|
salt: str
|
||||||
Literal["adjust continue"],
|
time: str
|
||||||
]
|
newsalt: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "allow"
|
||||||
|
|
||||||
|
|
||||||
|
class TokenData(BaseModel):
|
||||||
|
host_sign: str
|
||||||
|
host_passwd_md5: str
|
||||||
|
timestamp: datetime.datetime = Field(default_factory=datetime.datetime.now)
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerPrivilegedCommand(BaseModel):
|
||||||
|
cmd: str
|
||||||
|
token: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "allow"
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerV3Command(BaseModel):
|
||||||
|
cmd: str
|
||||||
|
param: Any | None = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "forbid"
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerV3PrivilegedCommand(BaseModel):
|
||||||
|
cmd: str
|
||||||
|
param: Any | None = None
|
||||||
|
ts: int
|
||||||
|
account: str
|
||||||
|
token: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
extra = "forbid"
|
||||||
|
|
||||||
|
|
||||||
|
PrePowerOnMessage = (
|
||||||
|
Literal["wait for adjust temp"]
|
||||||
|
| Literal["adjust complete"]
|
||||||
|
| Literal["adjust continue"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _crypt(word: str, salt: str) -> str:
|
def _crypt(word: str, salt: str) -> str:
|
||||||
@@ -92,7 +138,7 @@ def _add_to_16(string: str) -> bytes:
|
|||||||
return str.encode(string) # return bytes
|
return str.encode(string) # return bytes
|
||||||
|
|
||||||
|
|
||||||
def parse_btminer_priviledge_data(token_data: dict, data: dict) -> dict:
|
def parse_btminer_priviledge_data(token_data: TokenData, data: dict) -> dict:
|
||||||
"""Parses data returned from the BTMiner privileged API.
|
"""Parses data returned from the BTMiner privileged API.
|
||||||
|
|
||||||
Parses data from the BTMiner privileged API using the token
|
Parses data from the BTMiner privileged API using the token
|
||||||
@@ -108,9 +154,9 @@ def parse_btminer_priviledge_data(token_data: dict, data: dict) -> dict:
|
|||||||
# get the encoded data from the dict
|
# get the encoded data from the dict
|
||||||
enc_data = data["enc"]
|
enc_data = data["enc"]
|
||||||
# get the aes key from the token data
|
# get the aes key from the token data
|
||||||
aeskey = hashlib.sha256(token_data["host_passwd_md5"].encode()).hexdigest()
|
aeskey_hex = hashlib.sha256(token_data.host_passwd_md5.encode()).hexdigest()
|
||||||
# unhexlify the aes key
|
# unhexlify the aes key
|
||||||
aeskey = binascii.unhexlify(aeskey.encode())
|
aeskey = binascii.unhexlify(aeskey_hex.encode())
|
||||||
# create the required decryptor
|
# create the required decryptor
|
||||||
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
||||||
decryptor = aes.decryptor()
|
decryptor = aes.decryptor()
|
||||||
@@ -123,7 +169,7 @@ def parse_btminer_priviledge_data(token_data: dict, data: dict) -> dict:
|
|||||||
return ret_msg
|
return ret_msg
|
||||||
|
|
||||||
|
|
||||||
def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
def create_privileged_cmd(token_data: TokenData, command: dict) -> bytes:
|
||||||
"""Create a privileged command to send to the BTMiner API.
|
"""Create a privileged command to send to the BTMiner API.
|
||||||
|
|
||||||
Creates a privileged command using the token from the API and the
|
Creates a privileged command using the token from the API and the
|
||||||
@@ -137,13 +183,13 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
|||||||
Returns:
|
Returns:
|
||||||
The encrypted privileged command to be sent to the miner.
|
The encrypted privileged command to be sent to the miner.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"(Create Prilileged Command) - Creating Privileged Command")
|
logging.debug("(Create Prilileged Command) - Creating Privileged Command")
|
||||||
# add token to command
|
# add token to command
|
||||||
command["token"] = token_data["host_sign"]
|
command["token"] = token_data.host_sign
|
||||||
# encode host_passwd data and get hexdigest
|
# encode host_passwd data and get hexdigest
|
||||||
aeskey = hashlib.sha256(token_data["host_passwd_md5"].encode()).hexdigest()
|
aeskey_hex = hashlib.sha256(token_data.host_passwd_md5.encode()).hexdigest()
|
||||||
# unhexlify the encoded host_passwd
|
# unhexlify the encoded host_passwd
|
||||||
aeskey = binascii.unhexlify(aeskey.encode())
|
aeskey = binascii.unhexlify(aeskey_hex.encode())
|
||||||
# create a new AES key
|
# create a new AES key
|
||||||
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
|
||||||
encryptor = aes.encryptor()
|
encryptor = aes.encryptor()
|
||||||
@@ -187,8 +233,8 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
def __init__(self, ip: str, port: int = 4028, api_ver: str = "0.0.0") -> None:
|
def __init__(self, ip: str, port: int = 4028, api_ver: str = "0.0.0") -> None:
|
||||||
super().__init__(ip, port, api_ver)
|
super().__init__(ip, port, api_ver)
|
||||||
self.pwd = settings.get("default_whatsminer_rpc_password", "admin")
|
self.pwd: str = settings.get("default_whatsminer_rpc_password", "admin")
|
||||||
self.token = None
|
self.token: TokenData | None = None
|
||||||
|
|
||||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
"""Creates and sends multiple commands as one command to the miner.
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
@@ -198,19 +244,19 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
allow_warning: A boolean to supress APIWarnings.
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
"""
|
"""
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
commands = self._check_commands(*commands)
|
commands_list = self._check_commands(*commands)
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
# commands starting with "get_" and the "status" command aren't supported, but we can fake that
|
# commands starting with "get_" and the "status" command aren't supported, but we can fake that
|
||||||
|
|
||||||
split_commands = []
|
split_commands = []
|
||||||
|
|
||||||
for command in list(commands):
|
for command in commands_list:
|
||||||
if command.startswith("get_") or command == "status":
|
if command.startswith("get_") or command == "status":
|
||||||
commands.remove(command)
|
commands_list.remove(command)
|
||||||
# send seperately and append later
|
# send seperately and append later
|
||||||
split_commands.append(command)
|
split_commands.append(command)
|
||||||
|
|
||||||
command = "+".join(commands)
|
command = "+".join(commands_list)
|
||||||
|
|
||||||
tasks = []
|
tasks = []
|
||||||
if len(split_commands) > 0:
|
if len(split_commands) > 0:
|
||||||
@@ -239,7 +285,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
async def send_privileged_command(
|
async def send_privileged_command(
|
||||||
self,
|
self,
|
||||||
command: Union[str, bytes],
|
command: str,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
timeout: int = 10,
|
timeout: int = 10,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -251,17 +297,19 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
except APIError as e:
|
except APIError as e:
|
||||||
if not e.message == "can't access write cmd":
|
if not e.message == "can't access write cmd":
|
||||||
raise
|
raise
|
||||||
try:
|
# If we get here, we caught the specific error but didn't handle it
|
||||||
await self.open_api()
|
raise
|
||||||
except Exception as e:
|
# try:
|
||||||
raise APIError("Failed to open whatsminer API.") from e
|
# await self.open_api()
|
||||||
return await self._send_privileged_command(
|
# except Exception as e:
|
||||||
command=command, ignore_errors=ignore_errors, timeout=timeout, **kwargs
|
# raise APIError("Failed to open whatsminer API.") from e
|
||||||
)
|
# return await self._send_privileged_command(
|
||||||
|
# command=command, ignore_errors=ignore_errors, timeout=timeout, **kwargs
|
||||||
|
# )
|
||||||
|
|
||||||
async def _send_privileged_command(
|
async def _send_privileged_command(
|
||||||
self,
|
self,
|
||||||
command: Union[str, bytes],
|
command: str,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
timeout: int = 10,
|
timeout: int = 10,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
@@ -271,10 +319,10 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
if len(kwargs) > 0
|
if len(kwargs) > 0
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
command = {"cmd": command, **kwargs}
|
cmd = {"cmd": command, **kwargs}
|
||||||
|
|
||||||
token_data = await self.get_token()
|
token_data = await self.get_token()
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
enc_command = create_privileged_cmd(token_data, cmd)
|
||||||
|
|
||||||
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
||||||
try:
|
try:
|
||||||
@@ -288,23 +336,23 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
if ignore_errors:
|
if ignore_errors:
|
||||||
return {}
|
return {}
|
||||||
raise APIError("No data was returned from the API.")
|
raise APIError("No data was returned from the API.")
|
||||||
data = self._load_api_data(data)
|
data_dict: dict[Any, Any] = self._load_api_data(data)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = parse_btminer_priviledge_data(self.token, data)
|
data_dict = parse_btminer_priviledge_data(token_data, data_dict)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
logging.info(f"{str(self.ip)}: {e}")
|
||||||
|
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# if it fails to validate, it is likely an error
|
# if it fails to validate, it is likely an error
|
||||||
validation = validate_command_output(data)
|
validation = validate_command_output(data_dict)
|
||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
|
|
||||||
# return the parsed json as a dict
|
# return the parsed json as a dict
|
||||||
return data
|
return data_dict
|
||||||
|
|
||||||
async def get_token(self) -> dict:
|
async def get_token(self) -> TokenData:
|
||||||
"""Gets token information from the API.
|
"""Gets token information from the API.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -315,7 +363,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
logging.debug(f"{self} - (Get Token) - Getting token")
|
logging.debug(f"{self} - (Get Token) - Getting token")
|
||||||
if self.token:
|
if self.token:
|
||||||
if self.token["timestamp"] > datetime.datetime.now() - datetime.timedelta(
|
if self.token.timestamp > datetime.datetime.now() - datetime.timedelta(
|
||||||
minutes=30
|
minutes=30
|
||||||
):
|
):
|
||||||
return self.token
|
return self.token
|
||||||
@@ -323,26 +371,30 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
# get the token
|
# get the token
|
||||||
data = await self.send_command("get_token")
|
data = await self.send_command("get_token")
|
||||||
|
|
||||||
|
token_response = TokenResponse.model_validate(data["Msg"])
|
||||||
|
|
||||||
# encrypt the admin password with the salt
|
# encrypt the admin password with the salt
|
||||||
pwd = _crypt(self.pwd, "$1$" + data["Msg"]["salt"] + "$")
|
pwd_str = _crypt(self.pwd, "$1$" + token_response.salt + "$")
|
||||||
pwd = pwd.split("$")
|
pwd_parts = pwd_str.split("$")
|
||||||
|
|
||||||
# take the 4th item from the pwd split
|
# take the 4th item from the pwd split
|
||||||
host_passwd_md5 = pwd[3]
|
host_passwd_md5 = pwd_parts[3]
|
||||||
|
|
||||||
# encrypt the pwd with the time and new salt
|
# encrypt the pwd with the time and new salt
|
||||||
tmp = _crypt(pwd[3] + data["Msg"]["time"], "$1$" + data["Msg"]["newsalt"] + "$")
|
tmp_str = _crypt(
|
||||||
tmp = tmp.split("$")
|
pwd_parts[3] + token_response.time, "$1$" + token_response.newsalt + "$"
|
||||||
|
)
|
||||||
|
tmp_parts = tmp_str.split("$")
|
||||||
|
|
||||||
# take the 4th item from the encrypted pwd split
|
# take the 4th item from the encrypted pwd split
|
||||||
host_sign = tmp[3]
|
host_sign = tmp_parts[3]
|
||||||
|
|
||||||
# set the current token
|
# set the current token
|
||||||
self.token = {
|
self.token = TokenData(
|
||||||
"host_sign": host_sign,
|
host_sign=host_sign,
|
||||||
"host_passwd_md5": host_passwd_md5,
|
host_passwd_md5=host_passwd_md5,
|
||||||
"timestamp": datetime.datetime.now(),
|
timestamp=datetime.datetime.now(),
|
||||||
}
|
)
|
||||||
logging.debug(f"{self} - (Get Token) - Gathered token data: {self.token}")
|
logging.debug(f"{self} - (Get Token) - Gathered token data: {self.token}")
|
||||||
return self.token
|
return self.token
|
||||||
|
|
||||||
@@ -386,12 +438,12 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
pool_1: str,
|
pool_1: str,
|
||||||
worker_1: str,
|
worker_1: str,
|
||||||
passwd_1: str,
|
passwd_1: str,
|
||||||
pool_2: str = None,
|
pool_2: str | None = None,
|
||||||
worker_2: str = None,
|
worker_2: str | None = None,
|
||||||
passwd_2: str = None,
|
passwd_2: str | None = None,
|
||||||
pool_3: str = None,
|
pool_3: str | None = None,
|
||||||
worker_3: str = None,
|
worker_3: str | None = None,
|
||||||
passwd_3: str = None,
|
passwd_3: str | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Update the pools of the miner using the API.
|
"""Update the pools of the miner using the API.
|
||||||
<details>
|
<details>
|
||||||
@@ -648,11 +700,11 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
async def net_config(
|
async def net_config(
|
||||||
self,
|
self,
|
||||||
ip: str = None,
|
ip: str | None = None,
|
||||||
mask: str = None,
|
mask: str | None = None,
|
||||||
gate: str = None,
|
gate: str | None = None,
|
||||||
dns: str = None,
|
dns: str | None = None,
|
||||||
host: str = None,
|
host: str | None = None,
|
||||||
dhcp: bool = True,
|
dhcp: bool = True,
|
||||||
):
|
):
|
||||||
if dhcp:
|
if dhcp:
|
||||||
@@ -681,9 +733,9 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
if not -100 < percent < 100:
|
if not -100 < percent < 100:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Frequency % is outside of the allowed "
|
"Frequency % is outside of the allowed "
|
||||||
f"range. Please set a % between -100 and "
|
"range. Please set a % between -100 and "
|
||||||
f"100"
|
"100"
|
||||||
)
|
)
|
||||||
return await self.send_privileged_command(
|
return await self.send_privileged_command(
|
||||||
"set_target_freq", percent=str(percent)
|
"set_target_freq", percent=str(percent)
|
||||||
@@ -784,9 +836,9 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
if not 0 < percent < 100:
|
if not 0 < percent < 100:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Power PCT % is outside of the allowed "
|
"Power PCT % is outside of the allowed "
|
||||||
f"range. Please set a % between 0 and "
|
"range. Please set a % between 0 and "
|
||||||
f"100"
|
"100"
|
||||||
)
|
)
|
||||||
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||||
|
|
||||||
@@ -844,9 +896,9 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
if not 0 < percent < 100:
|
if not 0 < percent < 100:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Power PCT % is outside of the allowed "
|
"Power PCT % is outside of the allowed "
|
||||||
f"range. Please set a % between 0 and "
|
"range. Please set a % between 0 and "
|
||||||
f"100"
|
"100"
|
||||||
)
|
)
|
||||||
return await self.send_privileged_command(
|
return await self.send_privileged_command(
|
||||||
"set_power_pct_v2", percent=str(percent)
|
"set_power_pct_v2", percent=str(percent)
|
||||||
@@ -871,9 +923,9 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
if not -30 < temp_offset < 0:
|
if not -30 < temp_offset < 0:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Temp offset is outside of the allowed "
|
"Temp offset is outside of the allowed "
|
||||||
f"range. Please set a number between -30 and "
|
"range. Please set a number between -30 and "
|
||||||
f"0."
|
"0."
|
||||||
)
|
)
|
||||||
|
|
||||||
return await self.send_privileged_command(
|
return await self.send_privileged_command(
|
||||||
@@ -922,9 +974,9 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
if not 0 < upfreq_speed < 9:
|
if not 0 < upfreq_speed < 9:
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Upfreq speed is outside of the allowed "
|
"Upfreq speed is outside of the allowed "
|
||||||
f"range. Please set a number between 0 (Normal) and "
|
"range. Please set a number between 0 (Normal) and "
|
||||||
f"9 (Fastest)."
|
"9 (Fastest)."
|
||||||
)
|
)
|
||||||
return await self.send_privileged_command(
|
return await self.send_privileged_command(
|
||||||
"adjust_upfreq_speed", upfreq_speed=upfreq_speed
|
"adjust_upfreq_speed", upfreq_speed=upfreq_speed
|
||||||
@@ -1107,85 +1159,128 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
|||||||
def __init__(self, ip: str, port: int = 4433, api_ver: str = "0.0.0"):
|
def __init__(self, ip: str, port: int = 4433, api_ver: str = "0.0.0"):
|
||||||
super().__init__(ip, port, api_ver=api_ver)
|
super().__init__(ip, port, api_ver=api_ver)
|
||||||
|
|
||||||
self.reader: StreamReader | None = None
|
self.salt: str | None = None
|
||||||
self.writer: StreamWriter | None = None
|
self.pwd: str = "super"
|
||||||
self.reader_loop = None
|
|
||||||
|
|
||||||
self.salt = None
|
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||||
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
self.cmd_results = {}
|
Parameters:
|
||||||
self.cmd_callbacks = {"get.miner.report": set()}
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
|
allow_warning: A boolean to supress APIWarnings.
|
||||||
|
|
||||||
async def connect(self):
|
"""
|
||||||
self.reader, self.writer = await asyncio.open_connection(
|
checked_commands = self._check_commands(*commands)
|
||||||
str(self.ip), self.port
|
data = await self._send_split_multicommand(*checked_commands)
|
||||||
)
|
data["multicommand"] = True
|
||||||
self.reader_loop = asyncio.create_task(self._read_loop())
|
return data
|
||||||
|
|
||||||
async def disconnect(self):
|
|
||||||
self.writer.close()
|
|
||||||
await self.writer.wait_closed()
|
|
||||||
self.reader_loop.cancel()
|
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self, command: str, parameters: Any = None, **kwargs
|
self,
|
||||||
|
command: str,
|
||||||
|
parameters: Any = None,
|
||||||
|
ignore_errors: bool = False,
|
||||||
|
allow_warning: bool = True,
|
||||||
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
if self.writer is None:
|
if ":" in command:
|
||||||
await self.connect()
|
parameters = command.split(":")[1]
|
||||||
|
command = command.split(":")[0]
|
||||||
|
|
||||||
while command in self.cmd_results:
|
cmd: BTMinerV3Command | BTMinerV3PrivilegedCommand
|
||||||
wait_fut = self.cmd_results[command]
|
|
||||||
await wait_fut
|
|
||||||
|
|
||||||
result_fut = Future()
|
|
||||||
self.cmd_results[command] = result_fut
|
|
||||||
|
|
||||||
cmd = {"cmd": command}
|
|
||||||
if parameters is not None:
|
|
||||||
cmd["param"] = parameters
|
|
||||||
|
|
||||||
if command.startswith("set."):
|
if command.startswith("set."):
|
||||||
salt = await self.get_salt()
|
salt = await self.get_salt()
|
||||||
ts = int(datetime.datetime.now().timestamp())
|
ts = int(datetime.datetime.now().timestamp())
|
||||||
cmd["ts"] = ts
|
token_str = command + self.pwd + salt + str(ts)
|
||||||
token_str = cmd["cmd"] + self.pwd + salt + str(ts)
|
|
||||||
token_hashed = bytearray(
|
token_hashed = bytearray(
|
||||||
base64.b64encode(hashlib.sha256(token_str.encode("utf-8")).digest())
|
base64.b64encode(hashlib.sha256(token_str.encode("utf-8")).digest())
|
||||||
)
|
)
|
||||||
token_hashed[8] = 0
|
b_arr = bytearray(token_hashed)
|
||||||
cmd["account"] = "super"
|
b_arr[8] = 0
|
||||||
cmd["token"] = token_hashed.decode("ascii")
|
str_token = b_arr.split(b"\x00")[0].decode("utf-8")
|
||||||
|
|
||||||
|
cmd = BTMinerV3PrivilegedCommand(
|
||||||
|
cmd=command, param=parameters, ts=ts, account="super", token=str_token
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
cmd = BTMinerV3Command(cmd=command, param=parameters)
|
||||||
|
|
||||||
|
cmd_dict = cmd.model_dump()
|
||||||
|
ser = json.dumps(cmd_dict).encode("utf-8")
|
||||||
|
header = struct.pack("<I", len(ser))
|
||||||
|
return json.loads(await self._send_bytes(header + ser))
|
||||||
|
|
||||||
|
async def _send_bytes(
|
||||||
|
self,
|
||||||
|
data: bytes,
|
||||||
|
*,
|
||||||
|
port: int | None = None,
|
||||||
|
timeout: int = 100,
|
||||||
|
) -> bytes:
|
||||||
|
if port is None:
|
||||||
|
port = self.port
|
||||||
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Sending")
|
||||||
|
try:
|
||||||
|
# get reader and writer streams
|
||||||
|
reader, writer = await asyncio.open_connection(str(self.ip), port)
|
||||||
|
# handle OSError 121
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno == 121:
|
||||||
|
logging.warning(
|
||||||
|
f"{self} - ([Hidden] Send Bytes) - Semaphore timeout expired."
|
||||||
|
)
|
||||||
|
return b"{}"
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
ser = json.dumps(cmd).encode("utf-8")
|
try:
|
||||||
header = struct.pack("<I", len(ser))
|
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
|
||||||
await self._send_bytes(header + json.dumps(cmd).encode("utf-8"))
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
|
||||||
|
writer.write(data)
|
||||||
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
await result_fut
|
await data_task
|
||||||
return result_fut.result()
|
ret_data = data_task.result()
|
||||||
|
except TimeoutError:
|
||||||
|
logging.warning(f"{self} - ([Hidden] Send Bytes) - Read timeout expired.")
|
||||||
|
return b"{}"
|
||||||
|
|
||||||
async def _read_loop(self):
|
# close the connection
|
||||||
while True:
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
|
||||||
result = await self._read_bytes()
|
writer.close()
|
||||||
data = self._load_api_data(result)
|
await writer.wait_closed()
|
||||||
command = data["desc"]
|
|
||||||
if command in self.cmd_callbacks:
|
return ret_data
|
||||||
callbacks: list[Callable] = self.cmd_callbacks[command]
|
|
||||||
await asyncio.gather(*[callback(data) for callback in callbacks])
|
def _check_commands(self, *commands) -> list:
|
||||||
elif command in self.cmd_results:
|
return_commands = []
|
||||||
future: Future = self.cmd_results.pop(command)
|
|
||||||
future.set_result(data)
|
for command in commands:
|
||||||
|
if command.startswith("get.") or command.startswith("set."):
|
||||||
|
return_commands.append(command)
|
||||||
else:
|
else:
|
||||||
logging.error(f"Received unexpected data for {self}: {data}")
|
warnings.warn(
|
||||||
|
f"""Removing incorrect command: {command}
|
||||||
|
If you are sure you want to use this command please use API.send_command("{command}", ignore_errors=True) instead.""",
|
||||||
|
APIWarning,
|
||||||
|
)
|
||||||
|
return return_commands
|
||||||
|
|
||||||
async def _read_bytes(self, **kwargs) -> bytes:
|
async def _read_bytes(self, reader: asyncio.StreamReader, timeout: int) -> bytes:
|
||||||
header = await self.reader.readexactly(4)
|
ret_data = b""
|
||||||
length = struct.unpack("<I", header)[0]
|
|
||||||
return await self.reader.readexactly(length)
|
|
||||||
|
|
||||||
async def _send_bytes(self, data: bytes, **kwargs):
|
# loop to receive all the data
|
||||||
self.writer.write(data)
|
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
|
||||||
await self.writer.drain()
|
try:
|
||||||
|
header = await reader.readexactly(4)
|
||||||
|
length = struct.unpack("<I", header)[0]
|
||||||
|
ret_data = await reader.readexactly(length)
|
||||||
|
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||||
|
raise e
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"{self} - ([Hidden] Send Bytes) - API Command Error {e}")
|
||||||
|
return ret_data
|
||||||
|
|
||||||
async def get_salt(self) -> str:
|
async def get_salt(self) -> str:
|
||||||
if self.salt is not None:
|
if self.salt is not None:
|
||||||
@@ -1194,7 +1289,8 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
|||||||
self.salt = data["msg"]["salt"]
|
self.salt = data["msg"]["salt"]
|
||||||
return self.salt
|
return self.salt
|
||||||
|
|
||||||
async def get_miner_report(self) -> AsyncGenerator[dict]:
|
@typing.no_type_check
|
||||||
|
async def get_miner_report(self) -> AsyncGenerator[dict, None]:
|
||||||
if self.writer is None:
|
if self.writer is None:
|
||||||
await self.connect()
|
await self.connect()
|
||||||
|
|
||||||
@@ -1223,6 +1319,9 @@ class BTMinerV3RPCAPI(BaseMinerRPCAPI):
|
|||||||
async def get_miner_status_edevs(self) -> dict | None:
|
async def get_miner_status_edevs(self) -> dict | None:
|
||||||
return await self.send_command("get.miner.status", parameters="edevs")
|
return await self.send_command("get.miner.status", parameters="edevs")
|
||||||
|
|
||||||
|
async def get_miner_status_pools(self) -> dict | None:
|
||||||
|
return await self.send_command("get.miner.status", parameters="pools")
|
||||||
|
|
||||||
async def get_miner_history(self) -> dict | None:
|
async def get_miner_history(self) -> dict | None:
|
||||||
data = await self.send_command(
|
data = await self.send_command(
|
||||||
"get.miner.history",
|
"get.miner.history",
|
||||||
|
|||||||
@@ -243,7 +243,7 @@ class CGMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_command("removepool", parameters=n)
|
return await self.send_command("removepool", parameters=n)
|
||||||
|
|
||||||
async def save(self, filename: str = None) -> dict:
|
async def save(self, filename: str | None = None) -> dict:
|
||||||
"""Save the config.
|
"""Save the config.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -484,7 +484,7 @@ class CGMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_command("usbstats")
|
return await self.send_command("usbstats")
|
||||||
|
|
||||||
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
|
async def pgaset(self, n: int, opt: str, val: int | None = None) -> dict:
|
||||||
"""Set PGA option opt to val on PGA n.
|
"""Set PGA option opt to val on PGA n.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -611,7 +611,7 @@ class CGMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_command("asccount")
|
return await self.send_command("asccount")
|
||||||
|
|
||||||
async def ascset(self, n: int, opt: str, val: int = None) -> dict:
|
async def ascset(self, n: int, opt: str, val: int | str | None = None) -> dict:
|
||||||
"""Set ASC n option opt to value val.
|
"""Set ASC n option opt to value val.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
|
|||||||
@@ -13,7 +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 typing import Literal, Optional, Union
|
from typing import Literal
|
||||||
|
|
||||||
from pyasic import APIError
|
from pyasic import APIError
|
||||||
from pyasic.rpc.base import BaseMinerRPCAPI
|
from pyasic.rpc.base import BaseMinerRPCAPI
|
||||||
@@ -37,9 +37,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.session_token = None
|
self.session_token = None
|
||||||
|
|
||||||
async def send_privileged_command(
|
async def send_privileged_command(self, command: str, *args, **kwargs) -> dict:
|
||||||
self, command: Union[str, bytes], *args, **kwargs
|
|
||||||
) -> dict:
|
|
||||||
if self.session_token is None:
|
if self.session_token is None:
|
||||||
await self.auth()
|
await self.auth()
|
||||||
return await self.send_command(
|
return await self.send_command(
|
||||||
@@ -51,7 +49,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
command: Union[str, bytes],
|
command: str,
|
||||||
*args,
|
*args,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
@@ -59,7 +57,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
return await super().send_command(command, **kwargs)
|
return await super().send_command(command, **kwargs)
|
||||||
return await super().send_command(command, parameters=",".join(args), **kwargs)
|
return await super().send_command(command, parameters=",".join(args), **kwargs)
|
||||||
|
|
||||||
async def auth(self) -> Optional[str]:
|
async def auth(self) -> str | None:
|
||||||
try:
|
try:
|
||||||
data = await self.session()
|
data = await self.session()
|
||||||
if not data["SESSION"][0]["SessionID"] == "":
|
if not data["SESSION"][0]["SessionID"] == "":
|
||||||
@@ -74,6 +72,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
return self.session_token
|
return self.session_token
|
||||||
except (LookupError, APIError):
|
except (LookupError, APIError):
|
||||||
pass
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
async def addgroup(self, name: str, quota: int) -> dict:
|
async def addgroup(self, name: str, quota: int) -> dict:
|
||||||
"""Add a pool group.
|
"""Add a pool group.
|
||||||
@@ -91,7 +90,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
return await self.send_command("addgroup", name, quota)
|
return await self.send_command("addgroup", name, quota)
|
||||||
|
|
||||||
async def addpool(
|
async def addpool(
|
||||||
self, url: str, user: str, pwd: str = "", group_id: str = None
|
self, url: str, user: str, pwd: str = "", group_id: str | None = None
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Add a pool.
|
"""Add a pool.
|
||||||
<details>
|
<details>
|
||||||
@@ -163,13 +162,13 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
|
|
||||||
async def atmset(
|
async def atmset(
|
||||||
self,
|
self,
|
||||||
enabled: bool = None,
|
enabled: bool | None = None,
|
||||||
startup_minutes: int = None,
|
startup_minutes: int | None = None,
|
||||||
post_ramp_minutes: int = None,
|
post_ramp_minutes: int | None = None,
|
||||||
temp_window: int = None,
|
temp_window: int | None = None,
|
||||||
min_profile: str = None,
|
min_profile: str | None = None,
|
||||||
max_profile: str = None,
|
max_profile: str | None = None,
|
||||||
prevent_oc: bool = None,
|
prevent_oc: bool | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Sets the ATM configuration.
|
"""Sets the ATM configuration.
|
||||||
<details>
|
<details>
|
||||||
@@ -357,7 +356,10 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
return await self.send_command("fans")
|
return await self.send_command("fans")
|
||||||
|
|
||||||
async def fanset(
|
async def fanset(
|
||||||
self, speed: int = None, min_fans: int = None, power_off_speed: int = None
|
self,
|
||||||
|
speed: int | None = None,
|
||||||
|
min_fans: int | None = None,
|
||||||
|
power_off_speed: int | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Set fan control.
|
"""Set fan control.
|
||||||
<details>
|
<details>
|
||||||
@@ -380,7 +382,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
fanset_data.append(f"power_off_speed={power_off_speed}")
|
fanset_data.append(f"power_off_speed={power_off_speed}")
|
||||||
return await self.send_privileged_command("fanset", *fanset_data)
|
return await self.send_privileged_command("fanset", *fanset_data)
|
||||||
|
|
||||||
async def frequencyget(self, board_n: int, chip_n: int = None) -> dict:
|
async def frequencyget(self, board_n: int, chip_n: int | None = None) -> dict:
|
||||||
"""Get frequency data for a board and chips.
|
"""Get frequency data for a board and chips.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -453,7 +455,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_command("groups")
|
return await self.send_command("groups")
|
||||||
|
|
||||||
async def healthchipget(self, board_n: int, chip_n: int = None) -> dict:
|
async def healthchipget(self, board_n: int, chip_n: int | None = None) -> dict:
|
||||||
"""Get chip health.
|
"""Get chip health.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -471,7 +473,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
healthchipget_data.append(str(chip_n))
|
healthchipget_data.append(str(chip_n))
|
||||||
return await self.send_command("healthchipget", *healthchipget_data)
|
return await self.send_command("healthchipget", *healthchipget_data)
|
||||||
|
|
||||||
async def healthchipset(self, board_n: int, chip_n: int = None) -> dict:
|
async def healthchipset(self, board_n: int, chip_n: int | None = None) -> dict:
|
||||||
"""Select the next chip to have its health checked.
|
"""Select the next chip to have its health checked.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -641,7 +643,7 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
"""
|
"""
|
||||||
return await self.send_privileged_command("profileset", profile)
|
return await self.send_privileged_command("profileset", profile)
|
||||||
|
|
||||||
async def reboot(self, board_n: int, delay_s: int = None) -> dict:
|
async def reboot(self, board_n: int, delay_s: int | None = None) -> dict:
|
||||||
"""Reboot a board.
|
"""Reboot a board.
|
||||||
<details>
|
<details>
|
||||||
<summary>Expand</summary>
|
<summary>Expand</summary>
|
||||||
@@ -721,7 +723,10 @@ class LUXMinerRPCAPI(BaseMinerRPCAPI):
|
|||||||
return await self.send_command("session")
|
return await self.send_command("session")
|
||||||
|
|
||||||
async def tempctrlset(
|
async def tempctrlset(
|
||||||
self, target: int = None, hot: int = None, dangerous: int = None
|
self,
|
||||||
|
target: int | None = None,
|
||||||
|
hot: int | None = None,
|
||||||
|
dangerous: int | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Set temp control values.
|
"""Set temp control values.
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -14,37 +14,45 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from ssl import SSLContext
|
from ssl import SSLContext
|
||||||
from typing import Any, Union
|
from typing import Any
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from httpx import AsyncHTTPTransport
|
from httpx import AsyncHTTPTransport
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
_settings = { # defaults
|
|
||||||
"network_ping_retries": 1,
|
class Settings(BaseModel):
|
||||||
"network_ping_timeout": 3,
|
network_ping_retries: int = Field(default=1)
|
||||||
"network_scan_semaphore": None,
|
network_ping_timeout: int = Field(default=3)
|
||||||
"factory_get_retries": 1,
|
network_scan_semaphore: int | None = Field(default=None)
|
||||||
"factory_get_timeout": 3,
|
factory_get_retries: int = Field(default=1)
|
||||||
"get_data_retries": 1,
|
factory_get_timeout: int = Field(default=3)
|
||||||
"api_function_timeout": 5,
|
get_data_retries: int = Field(default=1)
|
||||||
"antminer_mining_mode_as_str": False,
|
api_function_timeout: int = Field(default=5)
|
||||||
"default_whatsminer_rpc_password": "admin",
|
antminer_mining_mode_as_str: bool = Field(default=False)
|
||||||
"default_innosilicon_web_password": "admin",
|
default_whatsminer_rpc_password: str = Field(default="admin")
|
||||||
"default_antminer_web_password": "root",
|
default_innosilicon_web_password: str = Field(default="admin")
|
||||||
"default_hammer_web_password": "root",
|
default_antminer_web_password: str = Field(default="root")
|
||||||
"default_volcminer_web_password": "ltc@dog",
|
default_hammer_web_password: str = Field(default="root")
|
||||||
"default_bosminer_web_password": "root",
|
default_volcminer_web_password: str = Field(default="ltc@dog")
|
||||||
"default_vnish_web_password": "admin",
|
default_bosminer_web_password: str = Field(default="root")
|
||||||
"default_goldshell_web_password": "123456789",
|
default_vnish_web_password: str = Field(default="admin")
|
||||||
"default_auradine_web_password": "admin",
|
default_goldshell_web_password: str = Field(default="123456789")
|
||||||
"default_epic_web_password": "letmein",
|
default_auradine_web_password: str = Field(default="admin")
|
||||||
"default_hive_web_password": "root",
|
default_epic_web_password: str = Field(default="letmein")
|
||||||
"default_iceriver_web_password": "12345678",
|
default_hive_web_password: str = Field(default="root")
|
||||||
"default_elphapex_web_password": "root",
|
default_iceriver_web_password: str = Field(default="12345678")
|
||||||
"default_mskminer_web_password": "root",
|
default_elphapex_web_password: str = Field(default="root")
|
||||||
"default_antminer_ssh_password": "miner",
|
default_mskminer_web_password: str = Field(default="root")
|
||||||
"default_bosminer_ssh_password": "root",
|
default_antminer_ssh_password: str = Field(default="miner")
|
||||||
}
|
default_bosminer_ssh_password: str = Field(default="root")
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
validate_assignment = True
|
||||||
|
extra = "allow"
|
||||||
|
|
||||||
|
|
||||||
|
_settings = Settings()
|
||||||
|
|
||||||
|
|
||||||
ssl_cxt = httpx.create_ssl_context()
|
ssl_cxt = httpx.create_ssl_context()
|
||||||
@@ -52,13 +60,21 @@ ssl_cxt = httpx.create_ssl_context()
|
|||||||
|
|
||||||
# this function returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
|
# this function returns an AsyncHTTPTransport instance to perform asynchronous HTTP requests
|
||||||
# using those options.
|
# using those options.
|
||||||
def transport(verify: Union[str, bool, SSLContext] = ssl_cxt):
|
def transport(verify: str | bool | SSLContext = ssl_cxt):
|
||||||
return AsyncHTTPTransport(verify=verify)
|
return AsyncHTTPTransport(verify=verify)
|
||||||
|
|
||||||
|
|
||||||
def get(key: str, other: Any = None) -> Any:
|
def get(key: str, other: Any = None) -> Any:
|
||||||
return _settings.get(key, other)
|
try:
|
||||||
|
return getattr(_settings, key)
|
||||||
|
except AttributeError:
|
||||||
|
if hasattr(_settings, "__dict__") and key in _settings.__dict__:
|
||||||
|
return _settings.__dict__[key]
|
||||||
|
return other
|
||||||
|
|
||||||
|
|
||||||
def update(key: str, val: Any) -> Any:
|
def update(key: str, val: Any) -> None:
|
||||||
_settings[key] = val
|
if hasattr(_settings, key):
|
||||||
|
setattr(_settings, key, val)
|
||||||
|
else:
|
||||||
|
_settings.__dict__[key] = val
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
import asyncssh
|
import asyncssh
|
||||||
|
|
||||||
@@ -8,9 +7,9 @@ import asyncssh
|
|||||||
class BaseSSH:
|
class BaseSSH:
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.pwd = None
|
self.pwd: str | None = None
|
||||||
self.username = "root"
|
self.username: str = "root"
|
||||||
self.port = 22
|
self.port: int = 22
|
||||||
|
|
||||||
async def _get_connection(self) -> asyncssh.connect:
|
async def _get_connection(self) -> asyncssh.connect:
|
||||||
"""Create a new asyncssh connection"""
|
"""Create a new asyncssh connection"""
|
||||||
@@ -29,7 +28,7 @@ class BaseSSH:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise ConnectionError from e
|
raise ConnectionError from e
|
||||||
|
|
||||||
async def send_command(self, cmd: str) -> Optional[str]:
|
async def send_command(self, cmd: str) -> str | None:
|
||||||
"""Send an ssh command to the miner"""
|
"""Send an ssh command to the miner"""
|
||||||
try:
|
try:
|
||||||
conn = await asyncio.wait_for(self._get_connection(), timeout=10)
|
conn = await asyncio.wait_for(self._get_connection(), timeout=10)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user