Compare commits

...

34 Commits

Author SHA1 Message Date
Brett Rowan
2bd031b33d version: bump version number 2025-09-19 15:49:29 -06:00
James Hilliard
e2f07818cc Fix race conditions in RPC and web API multicommand methods
Multiple multicommand methods were double-awaiting tasks - first via
asyncio.gather() with return_exceptions=True, then calling .result() on
the same tasks. This caused ConnectionResetError and other exceptions
when connections were lost.

Changed to properly use the results from gather() instead of calling
.result() on completed tasks, preventing exceptions from being raised
after they were already caught.

Fixed in:
- pyasic/rpc/base.py:144 - RPC _send_split_multicommand
- pyasic/web/espminer.py:79 - ESPMiner multicommand
- pyasic/web/auradine.py:149 - Auradine multicommand
2025-09-19 14:31:33 -06:00
Brett Rowan
75056cfff5 version: bump version number 2025-09-19 14:18:36 -06:00
James Hilliard
7fbcb0dbd2 Fix race condition in BOSer multicommand causing CancelledError
The multicommand method was double-awaiting tasks - first via
asyncio.gather() with return_exceptions=True, then trying to await
the same tasks again. This caused CancelledError when gRPC connections
were lost.

Changed to properly use the results from gather() instead of
re-awaiting completed tasks, preventing the race condition and
properly handling exceptions.

Fixes StreamTerminatedError occurring in pyasic/web/braiins_os/boser.py:91
2025-09-19 14:17:59 -06:00
Brett Rowan
7329aeace2 version: bump version number 2025-09-17 19:18:51 -06:00
Brett Rowan
e8c3953106 bug: fix btminer V3 password 2025-09-17 19:18:27 -06:00
James Hilliard
a1a7562bdb Handle invalid unicode in json response 2025-09-17 19:11:31 -06:00
Brett Rowan
b2726c77a0 version: bump version number 2025-09-09 08:13:09 -06:00
Ryan Heideman
68299fa54d Avalon Nano3s Fixes (#374)
* Avalon Nano3s Fixes

* Fix to handle both cases

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-08-30 22:04:58 -06:00
UpstreamData
1466039e08 version: bump version number 2025-08-28 21:43:00 -06:00
Ryan Heideman
0aa72c6c85 Fix Byte Folder Names (#376) 2025-08-26 21:18:57 -06:00
Ryan Heideman
24134a5991 Goldshell Mini Doge Support (#375)
* Docs for Mini Doge

* Mini Doge Recognition Support

* Loading fixes

* Fix for number of fans

* Fixed expected_hashrate

* Fixes for hashboards

* Implemented uptime

* Fixed uptime

* Doc fixes

* Fixes for byte

* Copyright update

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

* Typo fix

* File renames

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2025-08-26 21:06:10 -06:00
Brett Rowan
038208efa6 version: bump version number 2025-08-16 10:25:55 -06:00
Brett Rowan
7e319b79df feature: add misidentified L7_i 2025-08-16 10:25:40 -06:00
Brett Rowan
a0fdec3ce5 version: bump version number 2025-08-16 10:06:58 -06:00
Brett Rowan
1a8928de18 feature: add retries to elphapex 2025-08-16 10:06:46 -06:00
Brett Rowan
bce0058930 version: bump version number 2025-08-16 09:38:24 -06:00
Brett Rowan
850656fce4 bug: fix elphapex hashboard parsing 2025-08-16 09:38:06 -06:00
Brett Rowan
8bb35d6d7c version: bump version number 2025-08-16 09:24:17 -06:00
Brett Rowan
e85f06dbc2 feature: add misidentified L9_i 2025-08-16 09:23:12 -06:00
Brett Rowan
a566801316 feature: add chip count for IceRiver KS5M 2025-08-16 09:16:07 -06:00
Brett Rowan
e1a9cc5d19 bug: fix some issues with whatsminer BTMinerV3 implementation 2025-08-16 09:14:11 -06:00
Brett Rowan
27bb06de2b version: bump version number 2025-08-15 14:36:04 -06:00
Brett Rowan
debd4d2d4d feature: add BTMiner V3 configuration 2025-08-15 14:35:32 -06:00
Brett Rowan
56ad6cbc6f feature: add dynamic version selection for whatsminers 2025-08-15 14:35:32 -06:00
Brett Rowan
3fa54213bf bug: handle vnish S19 pro hydro alternate naming 2025-08-15 14:35:15 -06:00
Brett Rowan
076958ec0e version: bump version number 2025-08-15 12:39:04 -06:00
Brett Rowan
5319089ebe feature: fix S21+ hydro for cases where there is no . at the end 2025-08-15 12:38:17 -06:00
Brett Rowan
76a77b51e8 version: bump version number 2025-08-14 13:25:26 -06:00
Brett Rowan
b099ff45d2 bug: remove print statements 2025-08-14 13:25:05 -06:00
Brett Rowan
9bc3cc221a version: bump version number 2025-08-14 11:41:50 -06:00
Brett Rowan
6418c2e102 feature: add BTMinerV3 control support 2025-08-14 11:41:08 -06:00
Brett Rowan
aa9f3b2c45 feature: add BTMinerV3 data support 2025-08-14 11:41:08 -06:00
Ryan Heideman
bb1c98f061 Goldshell Byte Support (#366) 2025-08-12 21:53:28 -06:00
59 changed files with 2241 additions and 475 deletions

1
.gitignore vendored
View File

@@ -7,3 +7,4 @@ pyvenv.cfg
bin/
lib/
.idea/
.vs/

View 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

View File

@@ -0,0 +1,16 @@
# pyasic
## byte Models
## Byte (Stock)
- [ ] Shutdowns
- [ ] Power Modes
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.goldshell.bfgminer.byte.byte.GoldshellByte
handler: python
options:
show_root_heading: false
heading_level: 0

View 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

View 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

View 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

View File

@@ -38,6 +38,7 @@ details {
<details>
<summary>X7 Series:</summary>
<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#k7-stock">K7 (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#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>
</ul>
</details>
<details>
@@ -107,6 +109,7 @@ details {
<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-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#t21-stock">T21 (Stock)</a></li>
<li><a href="../antminer/X21#s21-hydro-stock">S21 Hydro (Stock)</a></li>
@@ -621,6 +624,18 @@ details {
<li><a href="../goldshell/XBox#kd-box-pro-stock">KD Box Pro (Stock)</a></li>
</ul>
</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>
</details>
<details>
@@ -734,6 +749,7 @@ details {
<li><a href="../antminer/X19#s19a-pro-vnish">S19a Pro (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-a-vnish">S19 Pro A (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19-pro-hydro-vnish">S19 Pro Hydro (VNish)</a></li>
<li><a href="../antminer/X19#s19k-pro-vnish">S19k Pro (VNish)</a></li>
<li><a href="../antminer/X19#t19-vnish">T19 (VNish)</a></li>
</ul>

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
@@ -18,7 +18,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
@@ -31,7 +31,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
@@ -44,7 +44,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK40
@@ -57,7 +57,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK50
@@ -70,7 +70,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK60
@@ -83,7 +83,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL20
@@ -96,7 +96,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL30
@@ -109,7 +109,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL40
@@ -122,7 +122,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL50
@@ -135,7 +135,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVL60
@@ -148,7 +148,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
@@ -161,7 +161,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
@@ -174,7 +174,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
@@ -187,7 +187,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ40
@@ -200,7 +200,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ60
@@ -213,7 +213,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK10
@@ -226,7 +226,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
@@ -239,7 +239,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK30
@@ -252,7 +252,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL10
@@ -265,7 +265,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL20
@@ -278,7 +278,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVL30
@@ -291,7 +291,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
@@ -304,7 +304,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
@@ -317,7 +317,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
@@ -330,7 +330,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
@@ -343,7 +343,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
@@ -356,7 +356,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
@@ -369,7 +369,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
@@ -382,7 +382,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
@@ -395,7 +395,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ40
@@ -408,7 +408,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ50
@@ -421,7 +421,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK10
@@ -434,7 +434,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK20
@@ -447,7 +447,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK30
@@ -460,7 +460,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK50
@@ -473,7 +473,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK60
@@ -486,7 +486,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK70
@@ -499,7 +499,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVK80
@@ -512,7 +512,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL20
@@ -525,7 +525,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVL30
@@ -538,7 +538,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
@@ -551,7 +551,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
@@ -564,7 +564,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
@@ -577,7 +577,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
@@ -590,7 +590,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
@@ -603,7 +603,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
@@ -616,7 +616,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
@@ -629,7 +629,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
@@ -642,7 +642,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
@@ -655,7 +655,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
@@ -668,7 +668,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH90
@@ -681,7 +681,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
@@ -694,7 +694,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
@@ -707,7 +707,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
@@ -720,7 +720,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ40
@@ -733,7 +733,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ60
@@ -746,7 +746,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK40
@@ -759,7 +759,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VK50
@@ -772,7 +772,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M52S_Plus_Plus.BTMinerM52SPlusPlusVL10
@@ -785,7 +785,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M52S.BTMinerM52SVK30
@@ -798,7 +798,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53H.BTMinerM53HVH10
@@ -811,7 +811,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK10
@@ -824,7 +824,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK20
@@ -837,7 +837,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK30
@@ -850,7 +850,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK50
@@ -863,7 +863,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL10
@@ -876,7 +876,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVL30
@@ -889,7 +889,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
@@ -902,7 +902,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ40
@@ -915,7 +915,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ50
@@ -928,7 +928,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVK30
@@ -941,7 +941,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH20
@@ -954,7 +954,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
@@ -967,7 +967,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ30
@@ -980,7 +980,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ40
@@ -993,7 +993,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVK30
@@ -1006,7 +1006,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
@@ -1019,7 +1019,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH40
@@ -1032,7 +1032,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH50
@@ -1045,7 +1045,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK30
@@ -1058,7 +1058,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VK60
@@ -1071,7 +1071,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVK30
@@ -1084,7 +1084,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL30
@@ -1097,7 +1097,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M54S_Plus_Plus.BTMinerM54SPlusPlusVL40
@@ -1110,7 +1110,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK10
@@ -1123,7 +1123,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK30
@@ -1136,7 +1136,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK40
@@ -1149,7 +1149,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus_Plus.BTMinerM56SPlusPlusVK50
@@ -1162,7 +1162,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
@@ -1175,7 +1175,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK30
@@ -1188,7 +1188,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK40
@@ -1201,7 +1201,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVK50
@@ -1214,7 +1214,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
@@ -1227,7 +1227,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ30
@@ -1240,7 +1240,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVJ40
@@ -1253,7 +1253,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
@@ -1266,7 +1266,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30

View File

@@ -5,7 +5,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL30
@@ -18,7 +18,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus_Plus.BTMinerM60SPlusPlusVL40
@@ -31,7 +31,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK30
@@ -44,7 +44,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK40
@@ -57,7 +57,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK50
@@ -70,7 +70,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK60
@@ -83,7 +83,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVK70
@@ -96,7 +96,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL10
@@ -109,7 +109,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL30
@@ -122,7 +122,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL40
@@ -135,7 +135,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL50
@@ -148,7 +148,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S_Plus.BTMinerM60SPlusVL60
@@ -161,7 +161,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK10
@@ -174,7 +174,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK20
@@ -187,7 +187,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK30
@@ -200,7 +200,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK40
@@ -213,7 +213,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL10
@@ -226,7 +226,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL20
@@ -239,7 +239,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL30
@@ -252,7 +252,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL40
@@ -265,7 +265,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL50
@@ -278,7 +278,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL60
@@ -291,7 +291,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVL70
@@ -304,7 +304,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK10
@@ -317,7 +317,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK20
@@ -330,7 +330,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK30
@@ -343,7 +343,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK40
@@ -356,7 +356,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK6A
@@ -369,7 +369,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL10
@@ -382,7 +382,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL20
@@ -395,7 +395,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL30
@@ -408,7 +408,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL40
@@ -421,7 +421,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VL50
@@ -434,7 +434,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61S_Plus.BTMinerM61SPlusVL30
@@ -447,7 +447,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL10
@@ -460,7 +460,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL20
@@ -473,7 +473,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61S.BTMinerM61SVL30
@@ -486,7 +486,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK10
@@ -499,7 +499,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK20
@@ -512,7 +512,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK30
@@ -525,7 +525,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VK40
@@ -538,7 +538,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL10
@@ -551,7 +551,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL30
@@ -564,7 +564,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL40
@@ -577,7 +577,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL50
@@ -590,7 +590,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M61.BTMinerM61VL60
@@ -603,7 +603,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M62S_Plus.BTMinerM62SPlusVK30
@@ -616,7 +616,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus_Plus.BTMinerM63SPlusPlusVL20
@@ -629,7 +629,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVK30
@@ -642,7 +642,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL10
@@ -655,7 +655,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL20
@@ -668,7 +668,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL30
@@ -681,7 +681,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S_Plus.BTMinerM63SPlusVL50
@@ -694,7 +694,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK10
@@ -707,7 +707,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK20
@@ -720,7 +720,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK30
@@ -733,7 +733,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK60
@@ -746,7 +746,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL10
@@ -759,7 +759,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL50
@@ -772,7 +772,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVL60
@@ -785,7 +785,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK10
@@ -798,7 +798,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK20
@@ -811,7 +811,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK30
@@ -824,7 +824,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL10
@@ -837,7 +837,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VL30
@@ -850,7 +850,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M64S.BTMinerM64SVL30
@@ -863,7 +863,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL30
@@ -876,7 +876,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M64.BTMinerM64VL40
@@ -889,7 +889,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M65S_Plus.BTMinerM65SPlusVK30
@@ -902,7 +902,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVK20
@@ -915,7 +915,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M65S.BTMinerM65SVL60
@@ -928,7 +928,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus_Plus.BTMinerM66SPlusPlusVL20
@@ -941,7 +941,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVK30
@@ -954,7 +954,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL10
@@ -967,7 +967,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL20
@@ -980,7 +980,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL30
@@ -993,7 +993,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL40
@@ -1006,7 +1006,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S_Plus.BTMinerM66SPlusVL60
@@ -1019,7 +1019,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK20
@@ -1032,7 +1032,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK30
@@ -1045,7 +1045,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK40
@@ -1058,7 +1058,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK50
@@ -1071,7 +1071,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK60
@@ -1084,7 +1084,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL10
@@ -1097,7 +1097,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL20
@@ -1110,7 +1110,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL30
@@ -1123,7 +1123,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL40
@@ -1136,7 +1136,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVL50
@@ -1149,7 +1149,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK20
@@ -1162,7 +1162,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK30
@@ -1175,7 +1175,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL20
@@ -1188,7 +1188,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VL30
@@ -1201,7 +1201,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M6X.M67S.BTMinerM67SVK30

View File

@@ -5,7 +5,7 @@
- [x] Shutdowns
- [x] Power Modes
- [x] Setpoints
- [ ] Setpoints
- [ ] Presets
::: pyasic.miners.whatsminer.btminer.M7X.M70.BTMinerM70VM30

View File

@@ -97,6 +97,8 @@ nav:
- Innosilicon T3X: "miners/innosilicon/T3X.md"
- Innosilicon A10X: "miners/innosilicon/A10X.md"
- Innosilicon A11X: "miners/innosilicon/A11X.md"
- Goldshell Byte: "miners/goldshell/Byte.md"
- Goldshell Mini Doge: "miners/goldshell/MiniDoge.md"
- Goldshell X5: "miners/goldshell/X5.md"
- Goldshell XMax: "miners/goldshell/XMax.md"
- Goldshell XBox: "miners/goldshell/XBox.md"

14
poetry.lock generated
View File

@@ -1489,6 +1489,18 @@ urllib3 = ">=1.21.1,<3"
socks = ["PySocks (>=1.5.6,!=1.5.7)"]
use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
[[package]]
name = "semver"
version = "3.0.4"
description = "Python helper for Semantic Versioning (https://semver.org)"
optional = false
python-versions = ">=3.7"
groups = ["main"]
files = [
{file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"},
{file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"},
]
[[package]]
name = "six"
version = "1.17.0"
@@ -1702,4 +1714,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.1"
python-versions = ">3.9, <4.0"
content-hash = "4152b0f7a2143d20368ddfad0aa44dfebdced4ab03586158a54eb27341a547e1"
content-hash = "81ec4faceddb41badda1649e77ddcfba03b0275021ba37ba69290b7e6a326829"

View File

@@ -85,6 +85,13 @@ class MinerConfig(BaseModel):
**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:
"""Generates the configuration in the format suitable for old versions of Antminers."""
return {
@@ -247,6 +254,16 @@ class MinerConfig(BaseModel):
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
return cls(pools=PoolConfig.from_am_modern(web_conf))
@classmethod
def from_goldshell_list(cls, web_conf: list) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Goldshell miners."""
return cls(pools=PoolConfig.from_goldshell(web_conf))
@classmethod
def from_goldshell_byte(cls, web_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Goldshell Byte miners."""
return cls(pools=PoolConfig.from_goldshell_byte(web_conf))
@classmethod
def from_inno(cls, web_pools: list) -> "MinerConfig":
"""Constructs a MinerConfig object from web configuration for Innosilicon miners."""
@@ -350,3 +367,14 @@ class MinerConfig(BaseModel):
@classmethod
def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
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
),
)

View File

@@ -107,6 +107,9 @@ class MinerConfigValue(BaseModel):
def as_wm(self) -> dict:
return {}
def as_btminer_v3(self) -> dict:
return {}
def as_inno(self) -> dict:
return {}

View File

@@ -63,6 +63,9 @@ class MiningModeNormal(MinerConfigValue):
def as_wm(self) -> dict:
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:
return {"mode": {"mode": self.mode}}
@@ -109,6 +112,9 @@ class MiningModeSleep(MinerConfigValue):
def as_wm(self) -> dict:
return {"mode": self.mode}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "stop"}
def as_auradine(self) -> dict:
return {"mode": {"sleep": "on"}}
@@ -149,6 +155,9 @@ class MiningModeLPM(MinerConfigValue):
def as_wm(self) -> dict:
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:
return {"mode": {"mode": "eco"}}
@@ -179,6 +188,9 @@ class MiningModeHPM(MinerConfigValue):
def as_wm(self) -> dict:
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:
return {"mode": {"mode": "turbo"}}
@@ -222,6 +234,9 @@ class MiningModePowerTune(MinerConfigValue):
return {"mode": self.mode, self.mode: {"wattage": self.power}}
return {}
def as_btminer_v3(self) -> dict:
return {"set.miner.service": "start", "set.miner.power_limit": self.power}
def as_bosminer(self) -> dict:
tuning_cfg = {"enabled": True, "mode": "power_target"}
if self.power is not None:
@@ -789,6 +804,26 @@ class MiningModeConfig(MinerConfigOption):
except LookupError:
return cls.default()
@classmethod
def from_btminer_v3(cls, rpc_device_info: dict, rpc_settings: dict):
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()
@classmethod
def from_mara(cls, web_config: dict):
try:

View File

@@ -64,6 +64,13 @@ class Pool(MinerConfigValue):
f"passwd_{idx}": self.password,
}
def as_btminer_v3(self, user_suffix: str | None = None) -> dict:
return {
f"pool": self.url,
f"worker": f"{self.user}{user_suffix or ''}",
f"passwd": self.password,
}
def as_am_old(self, idx: int = 1, user_suffix: str | None = None) -> dict:
return {
f"_ant_pool{idx}url": self.url,
@@ -148,6 +155,10 @@ class Pool(MinerConfigValue):
def from_api(cls, api_pool: dict) -> "Pool":
return cls(url=api_pool["URL"], user=api_pool["User"], password="x")
@classmethod
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(
@@ -296,6 +307,9 @@ class PoolGroup(MinerConfigValue):
idx += 1
return pools
def as_btminer_v3(self, user_suffix: str | None = None) -> list:
return [pool.as_btminer_v3(user_suffix) for pool in self.pools[:3]]
def as_am_old(self, user_suffix: str | None = None) -> dict:
pools = {}
idx = 0
@@ -385,6 +399,13 @@ class PoolGroup(MinerConfigValue):
pools.append(Pool.from_api(pool))
return cls(pools=pools)
@classmethod
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 = []
@@ -513,6 +534,11 @@ class PoolConfig(MinerConfigValue):
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
return {"pools": PoolGroup().as_wm()}
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, user_suffix: str | None = None) -> dict:
if len(self.groups) > 0:
return self.groups[0].as_am_old(user_suffix=user_suffix)
@@ -598,6 +624,16 @@ class PoolConfig(MinerConfigValue):
return cls(groups=[PoolGroup.from_api(pool_data)])
@classmethod
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"]
@@ -631,6 +667,16 @@ class PoolConfig(MinerConfigValue):
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
@classmethod
def from_goldshell_byte(cls, web_pools: list) -> "PoolConfig":
return cls(
groups=[
PoolGroup.from_goldshell(g["pools"])
for g in web_pools
if len(g["pools"]) > 0
]
)
@classmethod
def from_inno(cls, web_pools: list) -> "PoolConfig":
return cls(groups=[PoolGroup.from_inno(web_pools)])

View File

@@ -77,7 +77,6 @@ class HashBoard(BaseModel):
raise KeyError(f"{item}")
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
def serialize_int(key: str, value: int) -> str:
return f"{key}={value}"

View File

@@ -94,7 +94,6 @@ class PoolMetrics(BaseModel):
return (value / total) * 100
def as_influxdb(self, key_root: str, level_delimiter: str = ".") -> str:
def serialize_int(key: str, value: int) -> str:
return f"{key}={value}"

View File

@@ -12,6 +12,7 @@ from .kheavyhash import KHeavyHashAlgo
from .scrypt import ScryptAlgo
from .sha256 import SHA256Algo
from .x11 import X11Algo
from .zksnark import ZkSnarkAlgo
class MinerAlgo:
@@ -26,3 +27,4 @@ class MinerAlgo:
ETHASH = EtHashAlgo
EQUIHASH = EquihashAlgo
BLOCKFLOW = BlockFlowAlgo
ZKSNARK = ZkSnarkAlgo

View File

@@ -10,6 +10,7 @@ from .kheavyhash import KHeavyHashHashRate
from .scrypt import ScryptHashRate
from .sha256 import SHA256HashRate
from .x11 import X11HashRate
from .zksnark import ZkSnarkHashRate
class AlgoHashRate:
@@ -24,3 +25,4 @@ class AlgoHashRate:
ETHASH = EtHashHashRate
EQUIHASH = EquihashHashRate
BLOCKFLOW = BlockFlowHashRate
ZKSNARK = ZkSnarkHashRate

View File

@@ -9,6 +9,7 @@ from .kheavyhash import KHeavyHashUnit
from .scrypt import ScryptUnit
from .sha256 import SHA256Unit
from .x11 import X11Unit
from .zksnark import ZkSnarkUnit
class HashUnit:
@@ -23,3 +24,4 @@ class HashUnit:
ETHASH = EtHashUnit
EQUIHASH = EquihashUnit
BLOCKFLOW = BlockFlowUnit
ZKSNARK = ZkSnarkUnit

View File

@@ -0,0 +1,16 @@
from __future__ import annotations
from .base import AlgoHashRateUnitType
class ZkSnarkUnit(AlgoHashRateUnitType):
H = 1
KH = int(H) * 1000
MH = int(KH) * 1000
GH = int(MH) * 1000
TH = int(GH) * 1000
PH = int(TH) * 1000
EH = int(PH) * 1000
ZH = int(EH) * 1000
default = GH

View File

@@ -0,0 +1,18 @@
from __future__ import annotations
from typing_extensions import Self
from pyasic.device.algorithm.hashrate.base import AlgoHashRateType
from pyasic.device.algorithm.hashrate.unit.zksnark import ZkSnarkUnit
from .unit import HashUnit
class ZkSnarkHashRate(AlgoHashRateType):
rate: float
unit: ZkSnarkUnit = HashUnit.ZKSNARK.default
def into(self, other: ZkSnarkUnit) -> Self:
return self.__class__(
rate=self.rate / (other.value / self.unit.value), unit=other
)

View File

@@ -0,0 +1,12 @@
from __future__ import annotations
from .base import MinerAlgoType
from .hashrate import ZkSnarkHashRate
from .hashrate.unit import ZkSnarkUnit
class ZkSnarkAlgo(MinerAlgoType):
hashrate: type[ZkSnarkHashRate] = ZkSnarkHashRate
unit: type[ZkSnarkUnit] = ZkSnarkUnit
name = "zkSNARK"

View File

@@ -479,6 +479,8 @@ class GoldshellModels(MinerModelType):
KDMax = "KD Max"
KDBoxII = "KD Box II"
KDBoxPro = "KD Box Pro"
Byte = "Byte"
MiniDoge = "Mini Doge"
def __str__(self):
return self.value

View File

@@ -167,7 +167,6 @@ class CGMinerAvalonNano3(AvalonMiner, AvalonNano3):
class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
data_locations = AVALON_NANO3S_DATA_LOC
async def _get_wattage(self, rpc_estats: dict = None) -> Optional[int]:
@@ -179,8 +178,7 @@ class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
if rpc_estats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
return int(parsed_estats["PS"][6])
except (IndexError, KeyError, ValueError, TypeError):
pass
@@ -194,8 +192,7 @@ class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
if rpc_estats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
return self.algo.hashrate(
rate=float(parsed_estats["GHSspd"]), unit=self.algo.unit.GH
).into(self.algo.unit.default)
@@ -212,16 +209,14 @@ class CGMinerAvalonNano3s(AvalonMiner, AvalonNano3s):
pass
if rpc_estats is not None:
try:
unparsed_estats = rpc_estats["STATS"][0]["MM ID0"]
parsed_estats = self.parse_estats(unparsed_estats)
parsed_estats = self.parse_estats(rpc_estats)["STATS"][0]["MM ID0"]
except (IndexError, KeyError, ValueError, TypeError):
return hashboards
for board in range(len(hashboards)):
try:
board_hr = parsed_estats["GHSspd"][board]
board_hr = parsed_estats["GHSspd"]
hashboards[board].hashrate = self.algo.hashrate(
rate=float(board_hr), unit=self.algo.unit.GH
).into(self.algo.unit.default)

View File

@@ -20,7 +20,7 @@ from .bfgminer import BFGMiner
from .bitaxe import BitAxe
from .bmminer import BMMiner
from .braiins_os import BOSer, BOSMiner
from .btminer import BTMiner
from .btminer import BTMiner, BTMinerV2, BTMinerV3
from .cgminer import CGMiner
from .elphapex import ElphapexMiner
from .epic import ePIC

View File

@@ -287,7 +287,6 @@ class AvalonMiner(CGMiner):
return hashboards
for board in range(self.expected_hashboards):
try:
board_hr = parsed_estats["STATS"][0]["MM ID0"]["MGHS"]
if isinstance(board_hr, list):
@@ -306,10 +305,12 @@ class AvalonMiner(CGMiner):
hashboards[board].chip_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
except LookupError:
except (LookupError, TypeError):
try:
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:
pass
@@ -318,10 +319,12 @@ class AvalonMiner(CGMiner):
hashboards[board].temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
except LookupError:
except (LookupError, TypeError):
try:
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:
pass
@@ -330,7 +333,7 @@ class AvalonMiner(CGMiner):
hashboards[board].inlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTavg"][board]
)
except LookupError:
except (LookupError, TypeError):
try:
hashboards[board].inlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["HBITemp"]
@@ -342,7 +345,7 @@ class AvalonMiner(CGMiner):
hashboards[board].outlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["MTmax"][board]
)
except LookupError:
except (LookupError, TypeError):
try:
hashboards[board].outlet_temp = int(
parsed_estats["STATS"][0]["MM ID0"]["HBOTemp"]
@@ -357,7 +360,7 @@ class AvalonMiner(CGMiner):
hashboards[board].chips = len(
[item for item in chip_data if not item == "0"]
)
except LookupError:
except (LookupError, TypeError):
try:
chip_data = parsed_estats["STATS"][0]["HBinfo"][f"HB{board}"][
f"PVT_T{board}"

View File

@@ -216,7 +216,7 @@ class BFGMiner(StockFirmware):
)
except LookupError:
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

View File

@@ -13,14 +13,14 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import asyncio
import logging
from pathlib import Path
from typing import List, Optional
import aiofiles
import semver
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.config import MinerConfig, MiningModeConfig, PoolConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
from pyasic.data.pools import PoolMetrics, PoolUrl
@@ -28,7 +28,36 @@ from pyasic.device.algorithm import AlgoHashRate
from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import StockFirmware
from pyasic.rpc.btminer import BTMinerRPCAPI
from pyasic.rpc.btminer import BTMinerRPCAPI, BTMinerV3RPCAPI
class BTMiner(StockFirmware):
def __new__(cls, ip: str, version: str | None = None):
bases = cls.__bases__
bases = bases[1:]
def get_new(v: str | None):
if v is None:
return BTMinerV2
try:
semantic = semver.Version(
major=int(v[0:4]),
minor=int(v[4:6]),
patch=int(v[6:8]),
)
except ValueError:
return BTMinerV2
if semantic > semver.Version(major=2024, minor=11, patch=0):
return BTMinerV3
return BTMinerV2
inject = get_new(version)
bases = (inject,) + bases
cls = type(cls.__name__, bases, {})(ip=ip, version=version)
return cls
BTMINER_DATA_LOC = DataLocations(
**{
@@ -119,7 +148,7 @@ BTMINER_DATA_LOC = DataLocations(
)
class BTMiner(StockFirmware):
class BTMinerV2(StockFirmware):
"""Base handler for BTMiner based miners."""
_rpc_cls = BTMinerRPCAPI
@@ -294,7 +323,7 @@ class BTMiner(StockFirmware):
async def _get_mac(
self, rpc_summary: dict = None, rpc_get_miner_info: dict = None
) -> Optional[str]:
) -> str | None:
if rpc_get_miner_info is None:
try:
rpc_get_miner_info = await self.rpc.get_miner_info()
@@ -321,7 +350,7 @@ class BTMiner(StockFirmware):
except LookupError:
pass
async def _get_api_ver(self, rpc_get_version: dict = None) -> Optional[str]:
async def _get_api_ver(self, rpc_get_version: dict = None) -> str | None:
if rpc_get_version is None:
try:
rpc_get_version = await self.rpc.get_version()
@@ -346,7 +375,7 @@ class BTMiner(StockFirmware):
async def _get_fw_ver(
self, rpc_get_version: dict = None, rpc_summary: dict = None
) -> Optional[str]:
) -> str | None:
if rpc_get_version is None:
try:
rpc_get_version = await self.rpc.get_version()
@@ -379,7 +408,7 @@ class BTMiner(StockFirmware):
return self.fw_ver
async def _get_hostname(self, rpc_get_miner_info: dict = None) -> Optional[str]:
async def _get_hostname(self, rpc_get_miner_info: dict = None) -> str | None:
hostname = None
if rpc_get_miner_info is None:
try:
@@ -395,7 +424,7 @@ class BTMiner(StockFirmware):
return hostname
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
async def _get_hashrate(self, rpc_summary: dict = None) -> AlgoHashRate | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -410,8 +439,9 @@ class BTMiner(StockFirmware):
).into(self.algo.unit.default)
except LookupError:
pass
return None
async def _get_hashboards(self, rpc_devs: dict = None) -> List[HashBoard]:
async def _get_hashboards(self, rpc_devs: dict = None) -> list[HashBoard]:
if self.expected_hashboards is None:
return []
@@ -450,7 +480,7 @@ class BTMiner(StockFirmware):
return hashboards
async def _get_env_temp(self, rpc_summary: dict = None) -> Optional[float]:
async def _get_env_temp(self, rpc_summary: dict = None) -> float | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -462,8 +492,9 @@ class BTMiner(StockFirmware):
return rpc_summary["SUMMARY"][0]["Env Temp"]
except LookupError:
pass
return None
async def _get_wattage(self, rpc_summary: dict = None) -> Optional[int]:
async def _get_wattage(self, rpc_summary: dict = None) -> int | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -476,8 +507,9 @@ class BTMiner(StockFirmware):
return wattage if not wattage == -1 else None
except LookupError:
pass
return None
async def _get_wattage_limit(self, rpc_summary: dict = None) -> Optional[int]:
async def _get_wattage_limit(self, rpc_summary: dict = None) -> int | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -489,10 +521,11 @@ class BTMiner(StockFirmware):
return rpc_summary["SUMMARY"][0]["Power Limit"]
except LookupError:
pass
return None
async def _get_fans(
self, rpc_summary: dict = None, rpc_get_psu: dict = None
) -> List[Fan]:
) -> list[Fan]:
if self.expected_fans is None:
return []
@@ -517,7 +550,7 @@ class BTMiner(StockFirmware):
async def _get_fan_psu(
self, rpc_summary: dict = None, rpc_get_psu: dict = None
) -> Optional[int]:
) -> int | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -541,10 +574,11 @@ class BTMiner(StockFirmware):
return int(rpc_get_psu["Msg"]["fan_speed"])
except (KeyError, TypeError):
pass
return None
async def _get_errors(
self, rpc_summary: dict = None, rpc_get_error_code: dict = None
) -> List[MinerErrorData]:
) -> list[MinerErrorData]:
errors = []
if rpc_get_error_code is None and rpc_summary is None:
try:
@@ -581,7 +615,7 @@ class BTMiner(StockFirmware):
async def _get_expected_hashrate(
self, rpc_summary: dict = None
) -> Optional[AlgoHashRate]:
) -> AlgoHashRate | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -598,8 +632,9 @@ class BTMiner(StockFirmware):
except LookupError:
pass
return None
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> Optional[bool]:
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> bool | None:
if rpc_get_miner_info is None:
try:
rpc_get_miner_info = await self.rpc.get_miner_info()
@@ -637,7 +672,7 @@ class BTMiner(StockFirmware):
async def set_hostname(self, hostname: str):
await self.rpc.set_hostname(hostname)
async def _is_mining(self, rpc_status: dict = None) -> Optional[bool]:
async def _is_mining(self, rpc_status: dict = None) -> bool | None:
if rpc_status is None:
try:
rpc_status = await self.rpc.status()
@@ -655,8 +690,9 @@ class BTMiner(StockFirmware):
return True if rpc_status["Msg"]["mineroff"] == "false" else False
except LookupError:
pass
return False
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
async def _get_uptime(self, rpc_summary: dict = None) -> int | None:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -669,7 +705,7 @@ class BTMiner(StockFirmware):
except LookupError:
pass
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
async def _get_pools(self, rpc_pools: dict = None) -> list[PoolMetrics]:
if rpc_pools is None:
try:
rpc_pools = await self.rpc.pools()
@@ -742,3 +778,414 @@ class BTMiner(StockFirmware):
exc_info=True,
)
raise
BTMINERV3_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_version",
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_firmware_version",
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_light_flashing",
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
),
str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit",
[RPCAPICommand("rpc_get_device_info", "get.device.info")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.FAN_PSU): DataFunction(
"_get_psu_fans", [RPCAPICommand("rpc_get_device_info", "get.device.info")]
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[
RPCAPICommand("rpc_get_device_info", "get.device.info"),
RPCAPICommand(
"rpc_get_miner_status_edevs",
"get.miner.status:edevs",
),
],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp",
[RPCAPICommand("rpc_get_miner_status_summary", "get.miner.status:summary")],
),
}
)
class BTMinerV3(StockFirmware):
_rpc_cls = BTMinerV3RPCAPI
rpc: BTMinerV3RPCAPI
data_locations = BTMINERV3_DATA_LOC
supports_shutdown = True
supports_autotuning = True
supports_power_modes = True
async def get_config(self) -> MinerConfig:
pools = None
settings = None
device_info = None
try:
pools = await self.rpc.get_miner_status_pools()
settings = await self.rpc.get_miner_setting()
device_info = await self.rpc.get_device_info()
except APIError as e:
logging.warning(e)
except LookupError:
pass
self.config = MinerConfig.from_btminer_v3(
rpc_pools=pools, rpc_settings=settings, rpc_device_info=device_info
)
return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config
conf = config.as_btminer_v3(user_suffix=user_suffix)
await asyncio.gather(
*[self.rpc.send_command(k, parameters=v) for k, v in conf.values()]
)
async def fault_light_off(self) -> bool:
try:
data = await self.rpc.set_system_led()
except APIError:
return False
if data:
if "code" in data.keys():
if data["code"] == 0:
self.light = False
return True
return False
async def fault_light_on(self) -> bool:
try:
data = await self.rpc.set_system_led(
leds=[
{
{"color": "red", "period": 60, "duration": 20, "start": 0},
}
],
)
except APIError:
return False
if data:
if "code" in data.keys():
if data["code"] == 0:
self.light = True
return True
return False
async def reboot(self) -> bool:
try:
data = await self.rpc.set_system_reboot()
except APIError:
return False
if data.get("msg"):
if data["msg"] == "ok":
return True
return False
async def restart_backend(self) -> bool:
try:
data = await self.rpc.set_miner_service("restart")
except APIError:
return False
if data.get("msg"):
if data["msg"] == "ok":
return True
return False
async def stop_mining(self) -> bool:
try:
data = await self.rpc.set_miner_service("stop")
except APIError:
return False
if data.get("msg"):
if data["msg"] == "ok":
return True
return False
async def resume_mining(self) -> bool:
try:
data = await self.rpc.set_miner_service("start")
except APIError:
return False
if data.get("msg"):
if data["msg"] == "ok":
return True
return False
async def set_power_limit(self, wattage: int) -> bool:
try:
await self.rpc.set_miner_power_limit(wattage)
except Exception as e:
logging.warning(f"{self} set_power_limit: {e}")
return False
else:
return True
async def _get_mac(self, rpc_get_device_info: dict = None) -> str | None:
if rpc_get_device_info is None:
try:
rpc_get_device_info = await self.rpc.get_device_info()
except APIError:
return None
return rpc_get_device_info.get("msg", {}).get("network", {}).get("mac")
async def _get_api_version(self, rpc_get_device_info: dict = None) -> str | None:
if rpc_get_device_info is 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:
if rpc_get_device_info is 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("fwversion")
async def _get_hostname(self, rpc_get_device_info: dict = 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
return rpc_get_device_info.get("msg", {}).get("network", {}).get("hostname")
async def _get_light_flashing(
self, rpc_get_device_info: dict = None
) -> bool | None:
if rpc_get_device_info is None:
try:
rpc_get_device_info = await self.rpc.get_device_info()
except APIError:
return None
val = rpc_get_device_info.get("msg", {}).get("system", {}).get("ledstatus")
if isinstance(val, str):
return val != "auto"
return None
async def _get_wattage_limit(
self, rpc_get_device_info: dict = None
) -> float | None:
if rpc_get_device_info is None:
try:
rpc_get_device_info = await self.rpc.get_device_info()
except APIError:
return None
val = rpc_get_device_info.get("msg", {}).get("miner", {}).get("power-limit-set")
try:
return float(val)
except (ValueError, TypeError):
return None
async def _get_fans(self, rpc_get_miner_status_summary: dict = None) -> list[Fan]:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return []
fans = []
summary = rpc_get_miner_status_summary.get("msg", {}).get("summary", {})
for idx, direction in enumerate(["in", "out"]):
rpm = summary.get(f"fan-speed-{direction}")
if rpm is not None:
fans.append(Fan(speed=rpm))
return fans
async def _get_psu_fans(self, rpc_get_device_info: dict = None) -> list[Fan]:
if rpc_get_device_info is None:
try:
rpc_get_device_info = await self.rpc.get_device_info()
except APIError:
return []
rpm = rpc_get_device_info.get("msg", {}).get("power", {}).get("fanspeed")
return [Fan(speed=rpm)] if rpm is not None else []
async def _get_hashboards(
self,
rpc_get_device_info: dict = None,
rpc_get_miner_status_edevs: dict = None,
) -> list[HashBoard]:
if rpc_get_device_info is None:
try:
rpc_get_device_info = await self.rpc.get_device_info()
except APIError:
return []
if rpc_get_miner_status_edevs is None:
try:
rpc_get_miner_status_edevs = await self.rpc.get_miner_status_edevs()
except APIError:
return []
boards = []
board_count = (
rpc_get_device_info.get("msg", {}).get("hardware", {}).get("boards", 3)
)
edevs = rpc_get_miner_status_edevs.get("msg", {}).get("edevs", [])
for idx in range(board_count):
board_data = edevs[idx] if idx < len(edevs) else {}
boards.append(
HashBoard(
slot=idx,
hashrate=self.algo.hashrate(
rate=board_data.get("hash-average", 0), unit=self.algo.unit.TH
).into(self.algo.unit.default),
temp=board_data.get("chip-temp-min"),
inlet_temp=board_data.get("chip-temp-min"),
outlet_temp=board_data.get("chip-temp-max"),
serial_number=rpc_get_device_info.get("msg", {})
.get("miner", {})
.get(f"pcbsn{idx}"),
chips=board_data.get("effective-chips"),
expected_chips=self.expected_chips,
active=(board_data.get("hash-average") or 0) > 0,
missing=False,
tuned=True,
)
)
return boards
async def _get_pools(
self, rpc_get_miner_status_summary: dict = None
) -> list[PoolMetrics]:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return []
pools = []
msg_pools = rpc_get_miner_status_summary.get("msg", {}).get("pools", [])
for idx, pool in enumerate(msg_pools):
pools.append(
PoolMetrics(
index=idx,
user=pool.get("account"),
alive=pool.get("status") == "alive",
active=pool.get("stratum-active"),
url=pool.get("url"),
)
)
return pools
async def _get_uptime(
self, rpc_get_miner_status_summary: dict = None
) -> int | None:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return None
return (
rpc_get_miner_status_summary.get("msg", {})
.get("summary", {})
.get("elapsed")
)
async def _get_wattage(
self, rpc_get_miner_status_summary: dict = None
) -> float | None:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return None
return (
rpc_get_miner_status_summary.get("msg", {})
.get("summary", {})
.get("power-realtime")
)
async def _get_hashrate(
self, rpc_get_miner_status_summary: dict = None
) -> float | None:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return None
return (
rpc_get_miner_status_summary.get("msg", {})
.get("summary", {})
.get("hash-realtime")
)
async def _get_expected_hashrate(
self, rpc_get_miner_status_summary: dict = None
) -> float | None:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return None
res = (
rpc_get_miner_status_summary.get("msg", {})
.get("summary", {})
.get("factory-hash")
)
if res == (-0.001 * self.expected_hashboards):
return None
return res
async def _get_env_temp(
self, rpc_get_miner_status_summary: dict = None
) -> float | None:
if rpc_get_miner_status_summary is None:
try:
rpc_get_miner_status_summary = await self.rpc.get_miner_status_summary()
except APIError:
return None
return (
rpc_get_miner_status_summary.get("msg", {})
.get("summary", {})
.get("environment-temperature")
)

View File

@@ -233,9 +233,10 @@ class ElphapexMiner(StockFirmware):
board_temp_data = list(
filter(lambda x: not x == 0, board["temp_pcb"])
)
hashboards[board["index"]].temp = sum(board_temp_data) / len(
board_temp_data
)
if not len(board_temp_data) == 0:
hashboards[board["index"]].temp = sum(board_temp_data) / len(
board_temp_data
)
chip_temp_data = list(
filter(lambda x: not x == "", board["temp_chip"])
)

View File

@@ -13,24 +13,28 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from pyasic.miners.backends.btminer import BTMiner
from pyasic.miners.backends.btminer import BTMiner, BTMinerV2
class M7X(BTMiner):
supports_autotuning = True
supports_presets = True
class M6X(BTMiner):
supports_autotuning = True
supports_presets = True
class M5X(BTMiner):
supports_autotuning = True
supports_presets = True
class M3X(BTMiner):
supports_autotuning = True
supports_presets = True
class M2X(BTMiner):
class M2X(BTMinerV2):
pass

View File

@@ -576,7 +576,7 @@ class MinerProtocol(Protocol):
class BaseMiner(MinerProtocol):
def __init__(self, ip: str) -> None:
def __init__(self, ip: str, version: str | None = None) -> None:
self.ip = ip
if self.expected_chips is None and self.raw_model is not None:

View File

@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .byte import *
from .mini_doge import *
from .X5 import *
from .XBox import *
from .XMax import *

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

View 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.base import GenericAlgo
from pyasic.device.models import MinerModel
from pyasic.miners.device.makes import GoldshellMake
class Byte(GoldshellMake):
raw_model = MinerModel.GOLDSHELL.Byte
expected_chips = 0
expected_fans = 0
expected_hashboards = 0
algo = GenericAlgo

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

View 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

View File

@@ -40,5 +40,6 @@ class KS5M(IceRiverMake):
raw_model = MinerModel.ICERIVER.KS5M
expected_fans = 4
expected_chips = 18
expected_hashboards = 3
algo = MinerAlgo.KHEAVYHASH

View File

@@ -82,6 +82,7 @@ MINER_CLASSES = {
"ANTMINER KS5": BMMinerKS5,
"ANTMINER KS5 PRO": BMMinerKS5Pro,
"ANTMINER L7": BMMinerL7,
"ANTMINER L7_I": BMMinerL7,
"ANTMINER K7": BMMinerK7,
"ANTMINER D7": BMMinerD7,
"ANTMINER E9 PRO": BMMinerE9Pro,
@@ -91,6 +92,7 @@ MINER_CLASSES = {
"ANTMINER S9J": BMMinerS9j,
"ANTMINER T9": BMMinerT9,
"ANTMINER L9": BMMinerL9,
"ANTMINER L9_I": BMMinerL9,
"ANTMINER Z15": CGMinerZ15,
"ANTMINER Z15 PRO": BMMinerZ15Pro,
"ANTMINER S17": BMMinerS17,
@@ -125,6 +127,7 @@ MINER_CLASSES = {
"ANTMINER BHB68606": BMMinerS21, # ???
"ANTMINER S21+": BMMinerS21Plus,
"ANTMINER S21+ HYD.": BMMinerS21PlusHydro,
"ANTMINER S21+ HYD": BMMinerS21PlusHydro,
"ANTMINER S21 PRO": BMMinerS21Pro,
"ANTMINER T21": BMMinerT21,
"ANTMINER S21 HYD.": BMMinerS21Hydro,
@@ -529,6 +532,8 @@ MINER_CLASSES = {
"GOLDSHELL KDMAX": GoldshellKDMax,
"GOLDSHELL KDBOXII": GoldshellKDBoxII,
"GOLDSHELL KDBOXPRO": GoldshellKDBoxPro,
"GOLDSHELL BYTE": GoldshellByte,
"GOLDSHELL MINIDOGE": GoldshellMiniDoge,
},
MinerTypes.BRAIINS_OS: {
None: BOSMiner,
@@ -590,6 +595,7 @@ MINER_CLASSES = {
"ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER S19 PRO A": VNishS19ProA,
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
"ANTMINER S19 PRO HYDRO": VNishS19ProHydro,
"ANTMINER S19K PRO": VNishS19kPro,
"ANTMINER T19": VNishT19,
"ANTMINER T21": VNishT21,
@@ -786,21 +792,32 @@ class MinerFactory:
MinerTypes.VOLCMINER: self.get_miner_model_volcminer,
MinerTypes.ELPHAPEX: self.get_miner_model_elphapex,
}
fn = miner_model_fns.get(miner_type)
version = 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
task = asyncio.create_task(fn(ip))
task = asyncio.create_task(model_fn(ip))
try:
miner_model = await asyncio.wait_for(
task, timeout=settings.get("factory_get_timeout", 3)
)
except asyncio.TimeoutError:
pass
if version_fn is not None:
task = asyncio.create_task(version_fn(ip))
try:
version = await asyncio.wait_for(
task, timeout=settings.get("factory_get_timeout", 3)
)
except asyncio.TimeoutError:
pass
miner = self._select_miner_from_classes(
ip,
miner_type=miner_type,
miner_model=miner_model,
ip, miner_type=miner_type, miner_model=miner_model, version=version
)
return miner
@@ -1065,6 +1082,45 @@ class MinerFactory:
return data
async def send_btminer_v3_api_command(self, ip, command):
try:
reader, writer = await asyncio.open_connection(ip, 4433)
except (ConnectionError, OSError):
return
cmd = {"cmd": command}
try:
# send the command
json_cmd = json.dumps(cmd).encode("utf-8")
length = len(json_cmd)
writer.write(length.to_bytes(4, byteorder="little"))
writer.write(json_cmd)
await writer.drain()
# receive all the data
resp_len = await reader.readexactly(4)
data = await reader.readexactly(
int.from_bytes(resp_len, byteorder="little")
)
writer.close()
await writer.wait_closed()
except asyncio.CancelledError:
writer.close()
await writer.wait_closed()
return
except (ConnectionError, OSError):
return
if data == b"Socket connect failed: Connection refused\n":
return
try:
data = json.loads(data)
except json.JSONDecodeError:
return {}
return data
@staticmethod
async def _fix_api_data(data: bytes) -> str:
if data.endswith(b"\x00"):
@@ -1103,13 +1159,14 @@ class MinerFactory:
ip: ipaddress.ip_address,
miner_model: str | None,
miner_type: MinerTypes | None,
version: str | None = None,
) -> AnyMiner | None:
# special case since hiveon miners return web results copying the antminer stock FW
if "HIVEON" in str(miner_model).upper():
miner_model = str(miner_model).upper().replace(" HIVEON", "")
miner_type = MinerTypes.HIVEON
try:
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, version)
except LookupError:
if miner_type in MINER_CLASSES:
if miner_model is not None:
@@ -1117,8 +1174,8 @@ class MinerFactory:
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)."
)
return MINER_CLASSES[miner_type][None](ip)
return UnknownMiner(str(ip))
return MINER_CLASSES[miner_type][None](ip, version)
return UnknownMiner(str(ip), version)
async def get_miner_model_antminer(self, ip: str) -> str | None:
tasks = [
@@ -1184,9 +1241,25 @@ class MinerFactory:
try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"].replace("_", "")
miner_model = miner_model[:-1] + "0"
return miner_model
except (TypeError, LookupError):
sock_json_data_v3 = await self.send_btminer_v3_api_command(
ip, "get.device.info"
)
try:
miner_model = sock_json_data_v3["msg"]["miner"]["type"].replace("_", "")
miner_model = miner_model[:-1] + "0"
return miner_model
except (TypeError, LookupError):
pass
async def get_miner_version_whatsminer(self, ip: str) -> str | None:
sock_json_data = await self.send_api_command(ip, "get_version")
try:
version = sock_json_data["Msg"]["fw_ver"]
return version
except LookupError:
pass
async def get_miner_model_avalonminer(self, ip: str) -> str | None:

View File

@@ -13,6 +13,8 @@
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
from .byte import *
from .mini_doge import *
from .X5 import *
from .XBox import *
from .XMax import *

View 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

View File

@@ -0,0 +1,349 @@
# ------------------------------------------------------------------------------
# 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 typing import List, Optional, Union
from pyasic.config import MinerConfig
from pyasic.data import Fan, MinerData
from pyasic.data.boards import HashBoard
from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.device.algorithm import AlgoHashRate, MinerAlgo
from pyasic.errors import APIError
from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.models import Byte
ALGORITHM_SCRYPT_NAME = "scrypt(LTC)"
ALGORITHM_ZKSNARK_NAME = "zkSNARK(ALEO)"
EXPECTED_CHIPS_PER_SCRYPT_BOARD = 5
EXPECTED_CHIPS_PER_ZKSNARK_BOARD = 3
GOLDSHELL_BYTE_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_setting", "setting")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[WebAPICommand("web_setting", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[WebAPICommand("web_status", "status")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_devs", "devs")],
),
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_devs", "devs")],
),
str(DataOptions.POOLS): DataFunction(
"_get_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):
data_locations = GOLDSHELL_BYTE_DATA_LOC
supports_shutdown = False
supports_power_modes = False
web_devs: dict | None = None
async def get_data(
self,
allow_warning: bool = False,
include: List[Union[str, DataOptions]] = None,
exclude: List[Union[str, DataOptions]] = None,
) -> MinerData:
if self.web_devs is None:
try:
self.web_devs = await self.web.devs()
except APIError:
pass
scrypt_board_count = 0
zksnark_board_count = 0
for minfo in self.web_devs.get("minfos", []):
algo_name = minfo.get("name")
for _ in minfo.get("infos", []):
self.expected_hashboards += 1
self.expected_fans += 1
if algo_name == ALGORITHM_SCRYPT_NAME:
scrypt_board_count += 1
elif algo_name == ALGORITHM_ZKSNARK_NAME:
zksnark_board_count += 1
self.expected_chips = (EXPECTED_CHIPS_PER_SCRYPT_BOARD * scrypt_board_count) + (
EXPECTED_CHIPS_PER_ZKSNARK_BOARD * zksnark_board_count
)
if scrypt_board_count > 0 and zksnark_board_count == 0:
self.algo = MinerAlgo.SCRYPT
elif zksnark_board_count > 0 and scrypt_board_count == 0:
self.algo = MinerAlgo.ZKSNARK
data = await super().get_data(allow_warning, include, exclude)
data.expected_chips = self.expected_chips
return data
async def get_config(self) -> MinerConfig:
try:
pools = await self.web.pools()
except APIError:
return self.config
self.config = MinerConfig.from_goldshell_byte(pools)
return self.config
async def _get_api_ver(self, web_setting: dict = None) -> Optional[str]:
if web_setting is None:
try:
web_setting = await self.web.setting()
except APIError:
pass
if web_setting is not None:
try:
version = web_setting.get("version")
if version is not None:
self.api_ver = version.strip("v")
return self.api_ver
except KeyError:
pass
return self.api_ver
async def _get_expected_hashrate(
self, rpc_devs: dict = None
) -> Optional[AlgoHashRate]:
if rpc_devs is None:
try:
rpc_devs = await self.rpc.devs()
except APIError:
pass
total_hash_rate_mh = 0
if rpc_devs is not None:
for board in rpc_devs.get("DEVS", []):
algo_name = board.get("pool")
if algo_name == ALGORITHM_SCRYPT_NAME:
total_hash_rate_mh += (
self.algo.hashrate(
rate=float(board.get("estimate_hash_rate", 0)),
unit=self.algo.unit.H,
)
.into(self.algo.unit.MH)
.rate
)
elif algo_name == ALGORITHM_ZKSNARK_NAME:
total_hash_rate_mh += float(board.get("theory_hash", 0))
hash_rate = self.algo.hashrate(
rate=total_hash_rate_mh, unit=self.algo.unit.MH
).into(self.algo.unit.default)
return hash_rate
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[AlgoHashRate]:
if rpc_devs is None:
try:
rpc_devs = await self.rpc.devs()
except APIError:
pass
total_hash_rate_mh = 0
if rpc_devs is not None:
for board in rpc_devs.get("DEVS", []):
total_hash_rate_mh += float(board.get("MHS 20s", 0))
hash_rate = self.algo.hashrate(
rate=total_hash_rate_mh, unit=self.algo.unit.MH
).into(self.algo.unit.default)
return hash_rate
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
if rpc_pools is None:
try:
rpc_pools = await self.rpc.pools()
except APIError:
pass
pools_data = []
if rpc_pools is not None:
try:
pools = rpc_pools.get("POOLS", [])
for index, pool_info in enumerate(pools):
url = pool_info.get("URL")
pool_url = PoolUrl.from_str(url) if url else None
pool_data = PoolMetrics(
accepted=pool_info.get("Accepted"),
rejected=pool_info.get("Rejected"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
url=pool_url,
user=pool_info.get("User"),
index=index,
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data
async def _get_hashboards(
self, rpc_devs: dict = None, rpc_devdetails: dict = 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:
for board in rpc_devs.get("DEVS", []):
b_id = board["PGA"]
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-1"]
hashboards[b_id].temp = board["tstemp-2"]
hashboards[b_id].voltage = board["voltage"]
hashboards[b_id].active = board["Status"] == "Alive"
hashboards[b_id].missing = False
algo_name = board.get("pool")
if algo_name == ALGORITHM_SCRYPT_NAME:
hashboards[b_id].expected_chips = EXPECTED_CHIPS_PER_SCRYPT_BOARD
elif algo_name == ALGORITHM_ZKSNARK_NAME:
hashboards[b_id].expected_chips = EXPECTED_CHIPS_PER_ZKSNARK_BOARD
if rpc_devdetails is None:
try:
rpc_devdetails = await self.rpc.devdetails()
except APIError:
pass
if rpc_devdetails is not None:
for board in rpc_devdetails.get("DEVS", []):
b_id = board["DEVDETAILS"]
hashboards[b_id].chips = board["chips-nr"]
return hashboards
async def _get_fans(self, rpc_devs: dict = None) -> List[Fan]:
if self.expected_fans is None:
return []
if rpc_devs is None:
try:
rpc_devs = await self.rpc.devs()
except APIError:
pass
fans_data = []
if rpc_devs is not None:
for board in rpc_devs.get("DEVS", []):
if board.get("PGA") is not None:
try:
b_id = board["PGA"]
fan_speed = board[f"fan{b_id}"]
fans_data.append(fan_speed)
except KeyError:
pass
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
return fans
async def _get_uptime(self, web_devs: dict = None) -> Optional[int]:
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.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) -> Optional[int]:
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.get("minfos", []):
for info in minfo.get("infos", []):
wattage = int(float(info["power"]))
return wattage
except KeyError:
pass
return None

View 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

View 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 typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data.boards import HashBoard
from pyasic.device.algorithm import AlgoHashRate
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:
try:
pools = await self.web.pools()
except APIError:
return self.config
self.config = MinerConfig.from_goldshell_list(pools)
return self.config
async def _get_expected_hashrate(
self, rpc_devs: dict = None
) -> Optional[AlgoHashRate]:
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, rpc_devdetails: dict = 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) -> Optional[int]:
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

View File

@@ -136,17 +136,16 @@ class BaseMinerRPCAPI:
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 = {}
for cmd in tasks:
try:
result = tasks[cmd].result()
for cmd, result in zip(tasks.keys(), results):
if not isinstance(result, (APIError, Exception)):
if result is None or result == {}:
result = {}
data[cmd] = [result]
except APIError:
pass
return data
@@ -253,10 +252,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
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode("utf-8")[:-1]
str_data = data.decode("utf-8", errors="replace")[:-1]
else:
# 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()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()

View File

@@ -23,14 +23,16 @@ import json
import logging
import re
import struct
from typing import Literal, Union
import warnings
from asyncio import Future, StreamReader, StreamWriter
from typing import Any, AsyncGenerator, Callable, Literal, Union
import httpx
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from passlib.handlers.md5_crypt import md5_crypt
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.rpc.base import BaseMinerRPCAPI
@@ -250,13 +252,13 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
except APIError as e:
if not e.message == "can't access write cmd":
raise
try:
await self.open_api()
except Exception as e:
raise APIError("Failed to open whatsminer API.") from e
return await self._send_privileged_command(
command=command, ignore_errors=ignore_errors, timeout=timeout, **kwargs
)
# try:
# await self.open_api()
# except Exception as e:
# 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(
self,
@@ -291,6 +293,7 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
try:
data = parse_btminer_priviledge_data(self.token, data)
print(data)
except Exception as e:
logging.info(f"{str(self.ip)}: {e}")
@@ -1100,3 +1103,318 @@ class BTMinerRPCAPI(BaseMinerRPCAPI):
</details>
"""
return await self.send_command("get_error_code", allow_warning=False)
class BTMinerV3RPCAPI(BaseMinerRPCAPI):
def __init__(self, ip: str, port: int = 4433, api_ver: str = "0.0.0"):
super().__init__(ip, port, api_ver=api_ver)
self.salt = None
self.pwd = "super"
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
"""Creates and sends multiple commands as one command to the miner.
Parameters:
*commands: The commands to send as a multicommand to the miner.
allow_warning: A boolean to supress APIWarnings.
"""
commands = self._check_commands(*commands)
data = await self._send_split_multicommand(*commands)
data["multicommand"] = True
return data
async def send_command(
self, command: str, parameters: Any = None, **kwargs
) -> dict:
if ":" in command:
parameters = command.split(":")[1]
command = command.split(":")[0]
cmd = {"cmd": command}
if parameters is not None:
cmd["param"] = parameters
if command.startswith("set."):
salt = await self.get_salt()
ts = int(datetime.datetime.now().timestamp())
cmd["ts"] = ts
token_str = cmd["cmd"] + self.pwd + salt + str(ts)
token_hashed = bytearray(
base64.b64encode(hashlib.sha256(token_str.encode("utf-8")).digest())
)
b_arr = bytearray(token_hashed)
b_arr[8] = 0
str_token = b_arr.split(b"\x00")[0].decode("utf-8")
cmd["account"] = "super"
cmd["token"] = str_token
# send the command
ser = json.dumps(cmd).encode("utf-8")
header = struct.pack("<I", len(ser))
return json.loads(
await self._send_bytes(header + json.dumps(cmd).encode("utf-8"))
)
async def _send_bytes(
self,
data: bytes,
*,
port: int = 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
try:
data_task = asyncio.create_task(self._read_bytes(reader, timeout=timeout))
logging.debug(f"{self} - ([Hidden] Send Bytes) - Writing")
writer.write(data)
logging.debug(f"{self} - ([Hidden] Send Bytes) - Draining")
await writer.drain()
await data_task
ret_data = data_task.result()
except TimeoutError:
logging.warning(f"{self} - ([Hidden] Send Bytes) - Read timeout expired.")
return b"{}"
# close the connection
logging.debug(f"{self} - ([Hidden] Send Bytes) - Closing")
writer.close()
await writer.wait_closed()
return ret_data
def _check_commands(self, *commands) -> list:
return_commands = []
for command in commands:
if command.startswith("get.") or command.startswith("set."):
return_commands.append(command)
else:
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, reader: asyncio.StreamReader, timeout: int) -> bytes:
ret_data = b""
# loop to receive all the data
logging.debug(f"{self} - ([Hidden] Send Bytes) - Receiving")
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:
if self.salt is not None:
return self.salt
data = await self.send_command("get.device.info", "salt")
self.salt = data["msg"]["salt"]
return self.salt
async def get_miner_report(self) -> AsyncGenerator[dict, None]:
if self.writer is None:
await self.connect()
result = asyncio.Queue()
async def callback(data: dict):
await result.put(data)
cb_fn = callback
try:
self.cmd_callbacks["get.miner.report"].add(cb_fn)
while True:
yield await result.get()
if self.writer.is_closing():
break
finally:
self.cmd_callbacks["get.miner.report"].remove(cb_fn)
async def get_system_setting(self) -> dict | None:
return await self.send_command("get.system.setting")
async def get_miner_status_summary(self) -> dict | None:
return await self.send_command("get.miner.status", parameters="summary")
async def get_miner_status_edevs(self) -> dict | None:
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:
data = await self.send_command(
"get.miner.history",
parameters={
"start": "1",
"stop": str(datetime.datetime.now().timestamp()),
},
)
ret = {}
result = data.get("msg")
if result is not None:
unparsed = result["Data"].strip()
for item in unparsed.split(" "):
list_item = item.split(",")
timestamp = int(list_item.pop(0))
ret[timestamp] = list_item
return ret
async def get_psu_command(self):
return await self.send_command("get.psu.command")
async def get_miner_setting(self) -> dict | None:
return await self.send_command("get.miner.setting")
async def get_device_info(self) -> dict | None:
return await self.send_command("get.device.info")
async def get_log_download(self) -> dict | None:
return await self.send_command("get.log.download")
async def get_fan_setting(self) -> dict | None:
return await self.send_command("get.fan.setting")
async def set_system_reboot(self) -> dict | None:
return await self.send_command("set.system.reboot")
async def set_system_factory_reset(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.factory_reset")
async def set_system_update_firmware(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.update_firmware")
async def set_system_net_config(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.net_config")
async def set_system_led(self, leds: list | None = None) -> dict | None:
if leds is None:
return await self.send_command("set.system.led", parameters="auto")
else:
return await self.send_command("set.system.led", parameters=leds)
async def set_system_time_randomized(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.time_randomized")
async def set_system_timezone(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.timezone")
async def set_system_hostname(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.hostname")
async def set_system_webpools(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.webpools")
async def set_miner_target_freq(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.target_freq")
async def set_miner_heat_mode(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.heat_mode")
async def set_system_ntp_server(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.system.ntp_server")
async def set_miner_service(self, value: str) -> dict | None:
return await self.send_command("set.miner.service", parameters=value)
async def set_miner_power_mode(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.power_mode")
async def set_miner_cointype(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.cointype")
async def set_miner_pools(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.pools")
async def set_miner_fastboot(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.fastboot")
async def set_miner_power_percent(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.power_percent")
async def set_miner_pre_power_on(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.pre_power_on")
async def set_miner_restore_setting(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.restore_setting")
async def set_miner_report(self, frequency: int = 1) -> dict | None:
return await self.send_command(
"set.miner.report", parameters={"gap": frequency}
)
async def set_miner_power_limit(self, power: int) -> dict | None:
return await self.send_command("set.miner.power_limit", parameters=power)
async def set_miner_upfreq_speed(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.miner.upfreq_speed")
async def set_log_upload(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.log.upload")
async def set_user_change_passwd(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.user.change_passwd")
async def set_user_permission(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.user.permission")
async def set_fan_temp_offset(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.fan.temp_offset")
async def set_fan_poweroff_cool(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.fan.poweroff_cool")
async def set_fan_zero_speed(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.fan.zero_speed")
async def set_shell_debug(self, *args, **kwargs) -> dict | None:
raise NotImplementedError
return await self.send_command("set.shell.debug")

View File

@@ -62,7 +62,6 @@ class AntminerModernWebAPI(BaseWebAPI):
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
if parameters:
data = await client.post(
url,

View File

@@ -141,17 +141,16 @@ class AuradineWebAPI(BaseWebAPI):
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 = {"multicommand": True}
for cmd in tasks:
try:
result = tasks[cmd].result()
for cmd, result in zip(tasks.keys(), results):
if not isinstance(result, (APIError, Exception)):
if result is None or result == {}:
result = {}
data[cmd] = result
except APIError:
pass
return data

View File

@@ -84,13 +84,13 @@ class BOSerWebAPI(BaseWebAPI):
except AttributeError:
pass
await asyncio.gather(*[t for t in tasks.values()], return_exceptions=True)
results = await asyncio.gather(
*[t for t in tasks.values()], return_exceptions=True
)
for cmd in tasks:
try:
result[cmd] = await tasks[cmd]
except (GRPCError, APIError, ConnectionError):
pass
for cmd, task_result in zip(tasks.keys(), results):
if not isinstance(task_result, (GRPCError, APIError, ConnectionError)):
result[cmd] = task_result
return result

View File

@@ -52,7 +52,6 @@ class ApiVersionServiceStub(betterproto.ServiceStub):
class ApiVersionServiceBase(ServiceBase):
async def get_api_version(
self, api_version_request: "ApiVersionRequest"
) -> "ApiVersion":

View File

@@ -2485,7 +2485,6 @@ class NetworkServiceStub(betterproto.ServiceStub):
class ActionsServiceBase(ServiceBase):
async def start(self, start_request: "StartRequest") -> "StartResponse":
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
@@ -2630,7 +2629,6 @@ class ActionsServiceBase(ServiceBase):
class AuthenticationServiceBase(ServiceBase):
async def login(self, login_request: "LoginRequest") -> "LoginResponse":
raise grpclib.GRPCError(grpclib.const.Status.UNIMPLEMENTED)
@@ -2671,7 +2669,6 @@ class AuthenticationServiceBase(ServiceBase):
class CoolingServiceBase(ServiceBase):
async def get_cooling_state(
self, get_cooling_state_request: "GetCoolingStateRequest"
) -> "GetCoolingStateResponse":
@@ -2716,7 +2713,6 @@ class CoolingServiceBase(ServiceBase):
class PerformanceServiceBase(ServiceBase):
async def get_tuner_state(
self, get_tuner_state_request: "GetTunerStateRequest"
) -> "GetTunerStateResponse":
@@ -2986,7 +2982,6 @@ class PerformanceServiceBase(ServiceBase):
class PoolServiceBase(ServiceBase):
async def get_pool_groups(
self, get_pool_groups_request: "GetPoolGroupsRequest"
) -> "GetPoolGroupsResponse":
@@ -3088,7 +3083,6 @@ class PoolServiceBase(ServiceBase):
class ConfigurationServiceBase(ServiceBase):
async def get_miner_configuration(
self, get_miner_configuration_request: "GetMinerConfigurationRequest"
) -> "GetMinerConfigurationResponse":
@@ -3133,7 +3127,6 @@ class ConfigurationServiceBase(ServiceBase):
class LicenseServiceBase(ServiceBase):
async def get_license_state(
self, get_license_state_request: "GetLicenseStateRequest"
) -> "GetLicenseStateResponse":
@@ -3159,7 +3152,6 @@ class LicenseServiceBase(ServiceBase):
class MinerServiceBase(ServiceBase):
async def get_miner_status(
self, get_miner_status_request: "GetMinerStatusRequest"
) -> AsyncIterator[GetMinerStatusResponse]:
@@ -3325,7 +3317,6 @@ class MinerServiceBase(ServiceBase):
class NetworkServiceBase(ServiceBase):
async def get_network_configuration(
self, get_network_configuration_request: "GetNetworkConfigurationRequest"
) -> "GetNetworkConfigurationResponse":

View File

@@ -60,7 +60,6 @@ class ElphapexWebAPI(BaseWebAPI):
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
if parameters:
data = await client.post(
url,
@@ -121,18 +120,28 @@ class ElphapexWebAPI(BaseWebAPI):
"""
auth = httpx.DigestAuth(self.username, self.pwd)
try:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
ret = await client.get(url, auth=auth)
except httpx.HTTPError:
pass
else:
if ret.status_code == 200:
try:
json_data = ret.json()
return {command: json_data}
except json.decoder.JSONDecodeError:
pass
async def _send():
try:
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
ret = await client.get(url, auth=auth)
except httpx.HTTPError:
pass
else:
if ret.status_code == 200:
try:
json_data = ret.json()
if json_data.get("STATUS", {}).get("STATUS") not in ["S", "I"]:
return None
return {command: json_data}
except json.decoder.JSONDecodeError:
pass
return None
# retry 3 times
for i in range(3):
res = await _send()
if res is not None:
return res
return {command: {}}
async def get_miner_conf(self) -> dict:

View File

@@ -71,17 +71,16 @@ class ESPMinerWebAPI(BaseWebAPI):
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 = {"multicommand": True}
for cmd in tasks:
try:
result = tasks[cmd].result()
for cmd, result in zip(tasks.keys(), results):
if not isinstance(result, (APIError, Exception)):
if result is None or result == {}:
result = {}
data[cmd] = result
except APIError:
pass
return data

View File

@@ -103,8 +103,12 @@ class GoldshellWebAPI(BaseWebAPI):
async with httpx.AsyncClient(transport=settings.transport()) as client:
for command in commands:
try:
uri_commnand = command
if command == "devs":
uri_commnand = "cgminer?cgminercmd=devs"
response = await client.get(
f"http://{self.ip}:{self.port}/mcb/{command}",
f"http://{self.ip}:{self.port}/mcb/{uri_commnand}",
headers={"Authorization": "Bearer " + self.token},
timeout=settings.get("api_function_timeout", 5),
)
@@ -143,3 +147,6 @@ class GoldshellWebAPI(BaseWebAPI):
async def status(self) -> dict:
return await self.send_command("status")
async def devs(self) -> dict:
return await self.send_command("cgminer?cgminercmd=devs")

View File

@@ -60,7 +60,6 @@ class HammerWebAPI(BaseWebAPI):
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(transport=settings.transport()) as client:
if parameters:
data = await client.post(
url,

View File

@@ -1,6 +1,6 @@
[project]
name = "pyasic"
version = "0.74.1"
version = "0.76.9"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]
@@ -52,6 +52,7 @@ dependencies = [
"aiofiles>=23.2.1",
"betterproto==2.0.0b7",
"pydantic>=2.11.0",
"semver (>=3.0.4,<4.0.0)",
]
[tool.poetry.group.dev]

View File

@@ -33,10 +33,7 @@ class MinersTest(unittest.TestCase):
miner_type=miner_type,
miner_model=miner_model,
):
miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1")
self.assertTrue(
isinstance(miner, MINER_CLASSES[miner_type][miner_model])
)
MINER_CLASSES[miner_type][miner_model]("127.0.0.1")
def test_miner_has_hashboards(self):
warnings.filterwarnings("ignore")