diff --git a/docs/generate_miners.py b/docs/generate_miners.py
index cf3ea670..87f23886 100644
--- a/docs/generate_miners.py
+++ b/docs/generate_miners.py
@@ -49,6 +49,8 @@ def backend_str(backend: MinerTypes) -> str:
return "LuxOS Firmware Miners"
case MinerTypes.MARATHON:
return "Mara Firmware Miners"
+ case MinerTypes.BITAXE:
+ return "Stock Firmware BitAxe Miners"
def create_url_str(mtype: str):
diff --git a/docs/miners/antminer/X15.md b/docs/miners/antminer/X15.md
index 8fbb1c26..9c6b9073 100644
--- a/docs/miners/antminer/X15.md
+++ b/docs/miners/antminer/X15.md
@@ -1,7 +1,7 @@
# pyasic
## X15 Models
-## Z15
+## Z15 (Stock)
::: pyasic.miners.antminer.cgminer.X15.Z15.CGMinerZ15
handler: python
options:
diff --git a/docs/miners/antminer/X17.md b/docs/miners/antminer/X17.md
index b60db7b8..4dfe6072 100644
--- a/docs/miners/antminer/X17.md
+++ b/docs/miners/antminer/X17.md
@@ -1,49 +1,49 @@
# pyasic
## X17 Models
-## S17
+## S17 (Stock)
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
handler: python
options:
show_root_heading: false
heading_level: 4
-## S17+
+## S17+ (Stock)
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S17 Pro
+## S17 Pro (Stock)
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17Pro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S17e
+## S17e (Stock)
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17e
handler: python
options:
show_root_heading: false
heading_level: 4
-## T17
+## T17 (Stock)
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
handler: python
options:
show_root_heading: false
heading_level: 4
-## T17+
+## T17+ (Stock)
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17Plus
handler: python
options:
show_root_heading: false
heading_level: 4
-## T17e
+## T17e (Stock)
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17e
handler: python
options:
diff --git a/docs/miners/antminer/X19.md b/docs/miners/antminer/X19.md
index 11c7fbdf..f01ba1ba 100644
--- a/docs/miners/antminer/X19.md
+++ b/docs/miners/antminer/X19.md
@@ -1,224 +1,231 @@
# pyasic
## X19 Models
-## S19
+## S19 (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19L
+## S19L (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19L
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Pro
+## S19 Pro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Pro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j
+## S19j (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19i
+## S19i (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19i
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19+
+## S19+ (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Plus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j No PIC
+## S19j No PIC (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Pro+
+## S19 Pro+ (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro
+## S19j Pro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19jPro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 XP
+## S19 XP (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19XP
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19a
+## S19a (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19a
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19a Pro
+## S19a Pro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19aPro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Hydro
+## S19 Hydro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19Hydro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Pro Hydro
+## S19 Pro Hydro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProHydro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Pro+ Hydro
+## S19 Pro+ Hydro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19ProPlusHydro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19K Pro
+## S19K Pro (Stock)
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19KPro
handler: python
options:
show_root_heading: false
heading_level: 4
-## T19
+## T19 (Stock)
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19
+## S19 (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19+
+## S19+ (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Plus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 Pro
+## S19 Pro (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19Pro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19a
+## S19a (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19a
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19a Pro
+## S19a Pro (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19aPro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j
+## S19j (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19j
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j No PIC
+## S19j No PIC (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro
+## S19j Pro (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jPro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro No PIC
+## S19j Pro No PIC (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro+
+## S19j Pro+ (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro+
+## S19j Pro+ (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlus
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19j Pro+ No PIC
+## S19j Pro+ No PIC (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19jProPlusNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19k Pro No PIC
+## S19k Pro No PIC (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19kProNoPIC
handler: python
options:
show_root_heading: false
heading_level: 4
-## S19 XP
+## S19k Pro No PIC (BOS+)
+::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19kProNoPIC
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
+## S19 XP (BOS+)
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19XP
handler: python
options:
show_root_heading: false
heading_level: 4
-## T19
+## T19 (BOS+)
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
handler: python
options:
diff --git a/docs/miners/antminer/X21.md b/docs/miners/antminer/X21.md
index 39fcff04..fc7539d8 100644
--- a/docs/miners/antminer/X21.md
+++ b/docs/miners/antminer/X21.md
@@ -1,27 +1,34 @@
# pyasic
## X21 Models
-## S21
+## S21 (Stock)
::: pyasic.miners.antminer.bmminer.X21.S21.BMMinerS21
handler: python
options:
show_root_heading: false
heading_level: 4
-## T21
+## T21 (Stock)
::: pyasic.miners.antminer.bmminer.X21.T21.BMMinerT21
handler: python
options:
show_root_heading: false
heading_level: 4
-## S21
+## S21 (BOS+)
::: pyasic.miners.antminer.bosminer.X21.S21.BOSMinerS21
handler: python
options:
show_root_heading: false
heading_level: 4
+## S21 (VNish)
+::: pyasic.miners.antminer.vnish.X21.S21.VNishS21
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
## S21 (ePIC)
::: pyasic.miners.antminer.epic.X21.S21.ePICS21
handler: python
diff --git a/docs/miners/antminer/X3.md b/docs/miners/antminer/X3.md
index b3695dd9..5ca2d80d 100644
--- a/docs/miners/antminer/X3.md
+++ b/docs/miners/antminer/X3.md
@@ -1,21 +1,21 @@
# pyasic
## X3 Models
-## D3
+## D3 (Stock)
::: pyasic.miners.antminer.cgminer.X3.D3.CGMinerD3
handler: python
options:
show_root_heading: false
heading_level: 4
-## HS3
+## HS3 (Stock)
::: pyasic.miners.antminer.bmminer.X3.HS3.BMMinerHS3
handler: python
options:
show_root_heading: false
heading_level: 4
-## L3+
+## L3+ (Stock)
::: pyasic.miners.antminer.bmminer.X3.L3.BMMinerL3Plus
handler: python
options:
diff --git a/docs/miners/antminer/X5.md b/docs/miners/antminer/X5.md
index deb33a89..944f4b05 100644
--- a/docs/miners/antminer/X5.md
+++ b/docs/miners/antminer/X5.md
@@ -1,7 +1,7 @@
# pyasic
## X5 Models
-## DR5
+## DR5 (Stock)
::: pyasic.miners.antminer.cgminer.X5.DR5.CGMinerDR5
handler: python
options:
diff --git a/docs/miners/antminer/X7.md b/docs/miners/antminer/X7.md
index ea532304..7bccd093 100644
--- a/docs/miners/antminer/X7.md
+++ b/docs/miners/antminer/X7.md
@@ -1,7 +1,7 @@
# pyasic
## X7 Models
-## L7
+## L7 (Stock)
::: pyasic.miners.antminer.bmminer.X7.L7.BMMinerL7
handler: python
options:
diff --git a/docs/miners/antminer/X9.md b/docs/miners/antminer/X9.md
index 6a69d0df..0fda0313 100644
--- a/docs/miners/antminer/X9.md
+++ b/docs/miners/antminer/X9.md
@@ -1,35 +1,35 @@
# pyasic
## X9 Models
-## E9Pro
+## E9Pro (Stock)
::: pyasic.miners.antminer.bmminer.X9.E9.BMMinerE9Pro
handler: python
options:
show_root_heading: false
heading_level: 4
-## S9
+## S9 (Stock)
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
handler: python
options:
show_root_heading: false
heading_level: 4
-## S9i
+## S9i (Stock)
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9i
handler: python
options:
show_root_heading: false
heading_level: 4
-## S9j
+## S9j (Stock)
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9j
handler: python
options:
show_root_heading: false
heading_level: 4
-## T9
+## T9 (Stock)
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
handler: python
options:
@@ -43,7 +43,7 @@
show_root_heading: false
heading_level: 4
-## T9 (Hive)
+## T9 (Stock)
::: pyasic.miners.antminer.hiveon.X9.T9.HiveonT9
handler: python
options:
diff --git a/docs/miners/auradine/AD.md b/docs/miners/auradine/AD.md
index 08f9db64..e3ec74a6 100644
--- a/docs/miners/auradine/AD.md
+++ b/docs/miners/auradine/AD.md
@@ -1,21 +1,21 @@
# pyasic
## AD Models
-## AT1500
+## AT1500 (Stock)
::: pyasic.miners.auradine.flux.AD.AT1.AuradineFluxAT1500
handler: python
options:
show_root_heading: false
heading_level: 4
-## AT2860
+## AT2860 (Stock)
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2860
handler: python
options:
show_root_heading: false
heading_level: 4
-## AT2880
+## AT2880 (Stock)
::: pyasic.miners.auradine.flux.AD.AT2.AuradineFluxAT2880
handler: python
options:
diff --git a/docs/miners/auradine/AI.md b/docs/miners/auradine/AI.md
index 6a05a788..d237658d 100644
--- a/docs/miners/auradine/AI.md
+++ b/docs/miners/auradine/AI.md
@@ -1,14 +1,14 @@
# pyasic
## AI Models
-## AI2500
+## AI2500 (Stock)
::: pyasic.miners.auradine.flux.AI.AI2.AuradineFluxAI2500
handler: python
options:
show_root_heading: false
heading_level: 4
-## AI3680
+## AI3680 (Stock)
::: pyasic.miners.auradine.flux.AI.AI3.AuradineFluxAI3680
handler: python
options:
diff --git a/docs/miners/auradine/AT.md b/docs/miners/auradine/AT.md
index 1081b21f..d49c54ae 100644
--- a/docs/miners/auradine/AT.md
+++ b/docs/miners/auradine/AT.md
@@ -1,14 +1,14 @@
# pyasic
## AT Models
-## AD2500
+## AD2500 (Stock)
::: pyasic.miners.auradine.flux.AT.AD2.AuradineFluxAD2500
handler: python
options:
show_root_heading: false
heading_level: 4
-## AD3500
+## AD3500 (Stock)
::: pyasic.miners.auradine.flux.AT.AD3.AuradineFluxAD3500
handler: python
options:
diff --git a/docs/miners/avalonminer/A10X.md b/docs/miners/avalonminer/A10X.md
index 60f9bb81..bdc91f72 100644
--- a/docs/miners/avalonminer/A10X.md
+++ b/docs/miners/avalonminer/A10X.md
@@ -1,21 +1,21 @@
# pyasic
## A10X Models
-## Avalon 1026
+## Avalon 1026 (Stock)
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 1047
+## Avalon 1047 (Stock)
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 1066
+## Avalon 1066 (Stock)
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
handler: python
options:
diff --git a/docs/miners/avalonminer/A11X.md b/docs/miners/avalonminer/A11X.md
index 4624b2f4..c51ebc87 100644
--- a/docs/miners/avalonminer/A11X.md
+++ b/docs/miners/avalonminer/A11X.md
@@ -1,7 +1,7 @@
# pyasic
## A11X Models
-## Avalon 1166 Pro
+## Avalon 1166 Pro (Stock)
::: pyasic.miners.avalonminer.cgminer.A11X.A1166.CGMinerAvalon1166Pro
handler: python
options:
diff --git a/docs/miners/avalonminer/A12X.md b/docs/miners/avalonminer/A12X.md
index 0a8429a0..644d65e4 100644
--- a/docs/miners/avalonminer/A12X.md
+++ b/docs/miners/avalonminer/A12X.md
@@ -1,7 +1,7 @@
# pyasic
## A12X Models
-## Avalon 1246
+## Avalon 1246 (Stock)
::: pyasic.miners.avalonminer.cgminer.A12X.A1246.CGMinerAvalon1246
handler: python
options:
diff --git a/docs/miners/avalonminer/A7X.md b/docs/miners/avalonminer/A7X.md
index b2c8371c..f760417f 100644
--- a/docs/miners/avalonminer/A7X.md
+++ b/docs/miners/avalonminer/A7X.md
@@ -1,21 +1,21 @@
# pyasic
## A7X Models
-## Avalon 721
+## Avalon 721 (Stock)
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 741
+## Avalon 741 (Stock)
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 761
+## Avalon 761 (Stock)
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
handler: python
options:
diff --git a/docs/miners/avalonminer/A8X.md b/docs/miners/avalonminer/A8X.md
index 7e99dcba..b0a34c3d 100644
--- a/docs/miners/avalonminer/A8X.md
+++ b/docs/miners/avalonminer/A8X.md
@@ -1,21 +1,21 @@
# pyasic
## A8X Models
-## Avalon 821
+## Avalon 821 (Stock)
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 841
+## Avalon 841 (Stock)
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
handler: python
options:
show_root_heading: false
heading_level: 4
-## Avalon 851
+## Avalon 851 (Stock)
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
handler: python
options:
diff --git a/docs/miners/avalonminer/A9X.md b/docs/miners/avalonminer/A9X.md
index 1af53274..c2b0acca 100644
--- a/docs/miners/avalonminer/A9X.md
+++ b/docs/miners/avalonminer/A9X.md
@@ -1,7 +1,7 @@
# pyasic
## A9X Models
-## Avalon 921
+## Avalon 921 (Stock)
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
handler: python
options:
diff --git a/docs/miners/bitaxe/BM.md b/docs/miners/bitaxe/BM.md
new file mode 100644
index 00000000..fe0bfbf3
--- /dev/null
+++ b/docs/miners/bitaxe/BM.md
@@ -0,0 +1,24 @@
+# pyasic
+## BM Models
+
+## Supra (Stock)
+::: pyasic.miners.bitaxe.espminer.BM.BM1368.BitAxeSupra
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
+## Ultra (Stock)
+::: pyasic.miners.bitaxe.espminer.BM.BM1366.BitAxeUltra
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
+## Max (Stock)
+::: pyasic.miners.bitaxe.espminer.BM.BM1397.BitAxeMax
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
diff --git a/docs/miners/blockminer/blockminer.md b/docs/miners/blockminer/blockminer.md
new file mode 100644
index 00000000..715be9e2
--- /dev/null
+++ b/docs/miners/blockminer/blockminer.md
@@ -0,0 +1,17 @@
+# pyasic
+## blockminer Models
+
+## BlockMiner 520i (ePIC)
+::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner520i
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
+## BlockMiner 720i (ePIC)
+::: pyasic.miners.blockminer.epic.blockminer.blockminer.ePICBlockMiner720i
+ handler: python
+ options:
+ show_root_heading: false
+ heading_level: 4
+
diff --git a/docs/miners/goldshell/X5.md b/docs/miners/goldshell/X5.md
index 8ca83002..f4d77c6b 100644
--- a/docs/miners/goldshell/X5.md
+++ b/docs/miners/goldshell/X5.md
@@ -1,21 +1,21 @@
# pyasic
## X5 Models
-## CK5
+## CK5 (Stock)
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
handler: python
options:
show_root_heading: false
heading_level: 4
-## HS5
+## HS5 (Stock)
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
handler: python
options:
show_root_heading: false
heading_level: 4
-## KD5
+## KD5 (Stock)
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
handler: python
options:
diff --git a/docs/miners/goldshell/XBox.md b/docs/miners/goldshell/XBox.md
index 263ed930..d483ba8b 100644
--- a/docs/miners/goldshell/XBox.md
+++ b/docs/miners/goldshell/XBox.md
@@ -1,14 +1,14 @@
# pyasic
## XBox Models
-## KD Box II
+## KD Box II (Stock)
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxII
handler: python
options:
show_root_heading: false
heading_level: 4
-## KD Box Pro
+## KD Box Pro (Stock)
::: pyasic.miners.goldshell.bfgminer.XBox.KDBox.GoldshellKDBoxPro
handler: python
options:
diff --git a/docs/miners/goldshell/XMax.md b/docs/miners/goldshell/XMax.md
index 0533a633..8b0953f7 100644
--- a/docs/miners/goldshell/XMax.md
+++ b/docs/miners/goldshell/XMax.md
@@ -1,7 +1,7 @@
# pyasic
## XMax Models
-## KD Max
+## KD Max (Stock)
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.GoldshellKDMax
handler: python
options:
diff --git a/docs/miners/innosilicon/A10X.md b/docs/miners/innosilicon/A10X.md
index 8319625f..c81ccfeb 100644
--- a/docs/miners/innosilicon/A10X.md
+++ b/docs/miners/innosilicon/A10X.md
@@ -1,7 +1,7 @@
# pyasic
## A10X Models
-## A10X
+## A10X (Stock)
::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X
handler: python
options:
diff --git a/docs/miners/innosilicon/T3X.md b/docs/miners/innosilicon/T3X.md
index 15e4f8fb..b81af696 100644
--- a/docs/miners/innosilicon/T3X.md
+++ b/docs/miners/innosilicon/T3X.md
@@ -1,7 +1,7 @@
# pyasic
## T3X Models
-## T3H+
+## T3H+ (Stock)
::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus
handler: python
options:
diff --git a/docs/miners/supported_types.md b/docs/miners/supported_types.md
index dcd0280e..27cd9120 100644
--- a/docs/miners/supported_types.md
+++ b/docs/miners/supported_types.md
@@ -18,78 +18,78 @@ details {
X3 Series:
X5 Series:
X7 Series:
X9 Series:
X15 Series:
X17 Series:
X19 Series:
X21 Series:
@@ -100,234 +100,234 @@ details {
M2X Series:
M3X Series:
M5X Series:
M6X Series:
@@ -338,43 +338,43 @@ details {
A7X Series:
A8X Series:
A9X Series:
A10X Series:
A11X Series:
A12X Series:
@@ -385,13 +385,13 @@ details {
T3X Series:
A10X Series:
@@ -402,22 +402,22 @@ details {
X5 Series:
XMax Series:
XBox Series:
@@ -446,27 +446,28 @@ details {
X19 Series:
X21 Series:
@@ -507,6 +508,12 @@ details {
T19 (VNish)
+
+ X21 Series:
+
+
@@ -546,7 +553,7 @@ details {
X9 Series:
@@ -586,23 +593,23 @@ details {
AD Series:
AI Series:
AT Series:
@@ -630,4 +637,17 @@ details {
+
+
+Stock Firmware BitAxe Miners:
+
\ No newline at end of file
diff --git a/docs/miners/whatsminer/M2X.md b/docs/miners/whatsminer/M2X.md
index 2cae2ddc..0724c495 100644
--- a/docs/miners/whatsminer/M2X.md
+++ b/docs/miners/whatsminer/M2X.md
@@ -1,91 +1,91 @@
# pyasic
## M2X Models
-## M20 V10
+## M20 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20S V10
+## M20S V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20S V20
+## M20S V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20S V30
+## M20S V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20P V10
+## M20P V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20P V30
+## M20P V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20P.BTMinerM20PV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M20S+ V30
+## M20S+ V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M21 V10
+## M21 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M21S V20
+## M21S V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M21S V60
+## M21S V60 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M21S V70
+## M21S V70 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M21S+ V20
+## M21S+ V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M29 V10
+## M29 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
handler: python
options:
diff --git a/docs/miners/whatsminer/M3X.md b/docs/miners/whatsminer/M3X.md
index a060b1f5..01f1e7d7 100644
--- a/docs/miners/whatsminer/M3X.md
+++ b/docs/miners/whatsminer/M3X.md
@@ -1,1008 +1,1008 @@
# pyasic
## M3X Models
-## M30 V10
+## M30 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30.BTMinerM30V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30 V20
+## M30 V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30.BTMinerM30V20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30K V10
+## M30K V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30K.BTMinerM30KV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30L V10
+## M30L V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30L.BTMinerM30LV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V10
+## M30S V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V20
+## M30S V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V30
+## M30S V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V40
+## M30S V40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V50
+## M30S V50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V60
+## M30S V60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V70
+## M30S V70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S V80
+## M30S V80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE10
+## M30S VE10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE20
+## M30S VE20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE30
+## M30S VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE40
+## M30S VE40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE50
+## M30S VE50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE60
+## M30S VE60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VE70
+## M30S VE70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VF10
+## M30S VF10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VF20
+## M30S VF20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VF30
+## M30S VF30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVF30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VG10
+## M30S VG10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VG20
+## M30S VG20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VG30
+## M30S VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VG40
+## M30S VG40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH10
+## M30S VH10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH20
+## M30S VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH30
+## M30S VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH40
+## M30S VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH50
+## M30S VH50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VH60
+## M30S VH60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVH60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S VI20
+## M30S VI20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVI20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V10
+## M30S+ V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V20
+## M30S+ V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V30
+## M30S+ V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V40
+## M30S+ V40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V50
+## M30S+ V50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V60
+## M30S+ V60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V70
+## M30S+ V70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V80
+## M30S+ V80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V90
+## M30S+ V90 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV90
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ V100
+## M30S+ V100 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusV100
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE30
+## M30S+ VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE40
+## M30S+ VE40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE50
+## M30S+ VE50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE60
+## M30S+ VE60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE70
+## M30S+ VE70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE80
+## M30S+ VE80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE90
+## M30S+ VE90 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE90
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VE100
+## M30S+ VE100 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE100
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VF20
+## M30S+ VF20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VF30
+## M30S+ VF30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VG20
+## M30S+ VG20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VG30
+## M30S+ VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VG40
+## M30S+ VG40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VG50
+## M30S+ VG50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VG60
+## M30S+ VG60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH10
+## M30S+ VH10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH20
+## M30S+ VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH30
+## M30S+ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH40
+## M30S+ VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH50
+## M30S+ VH50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S+ VH60
+## M30S+ VH60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVH60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ V10
+## M30S++ V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ V20
+## M30S++ V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VE30
+## M30S++ VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VE40
+## M30S++ VE40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VE50
+## M30S++ VE50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVE50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VF40
+## M30S++ VF40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVF40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VG30
+## M30S++ VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VG40
+## M30S++ VG40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VG50
+## M30S++ VG50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH10
+## M30S++ VH10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH20
+## M30S++ VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH30
+## M30S++ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH40
+## M30S++ VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH50
+## M30S++ VH50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH60
+## M30S++ VH60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH70
+## M30S++ VH70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH80
+## M30S++ VH80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH90
+## M30S++ VH90 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH90
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VH100
+## M30S++ VH100 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVH100
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VJ20
+## M30S++ VJ20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVJ20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30S++ VJ30
+## M30S++ VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31 V10
+## M31 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31.BTMinerM31V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31 V20
+## M31 V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31.BTMinerM31V20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31H V10
+## M31H V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31H V40
+## M31H V40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31H.BTMinerM31HV40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M30L V10
+## M30L V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31L.BTMinerM31LV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V10
+## M31S V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V20
+## M31S V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V30
+## M31S V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V40
+## M31S V40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V50
+## M31S V50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V60
+## M31S V60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V70
+## M31S V70 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V80
+## M31S V80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S V90
+## M31S V90 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SV90
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S VE10
+## M31S VE10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S VE20
+## M31S VE20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S VE30
+## M31S VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31SVE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31SE V10
+## M31SE V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31SE V20
+## M31SE V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31SE V30
+## M31SE V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31SE.BTMinerM31SEV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V10
+## M31S+ V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V20
+## M31S+ V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V30
+## M31S+ V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V40
+## M31S+ V40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V50
+## M31S+ V50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V60
+## M31S+ V60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V80
+## M31S+ V80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V90
+## M31S+ V90 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV90
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ V100
+## M31S+ V100 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusV100
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE10
+## M31S+ VE10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE20
+## M31S+ VE20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE30
+## M31S+ VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE40
+## M31S+ VE40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE50
+## M31S+ VE50 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE60
+## M31S+ VE60 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VE80
+## M31S+ VE80 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VF20
+## M31S+ VF20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVF20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VF30
+## M31S+ VF30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVF30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VG20
+## M31S+ VG20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVG20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M31S+ VG30
+## M31S+ VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M32 V10
+## M32 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M32 V20
+## M32 V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M32.BTMinerM32V20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33 V10
+## M33 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33 V20
+## M33 V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33 V30
+## M33 V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33.BTMinerM33V30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S VG30
+## M33S VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S.BTMinerM33SVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S+ VG20
+## M33S+ VG20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVG20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S+ VH20
+## M33S+ VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S+ VH30
+## M33S+ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus.BTMinerM33SPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S++ VH20
+## M33S++ VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S++ VH30
+## M33S++ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M33S++ VG40
+## M33S++ VG40 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M33S_Plus_Plus.BTMinerM33SPlusPlusVG40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M34S+ VE10
+## M34S+ VE10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M34S_Plus.BTMinerM34SPlusVE10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M36S VE10
+## M36S VE10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M36S.BTMinerM36SVE10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M36S+ VG30
+## M36S+ VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M36S_Plus.BTMinerM36SPlusVG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M36S++ VH30
+## M36S++ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M36S_Plus_Plus.BTMinerM36SPlusPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M39 V10
+## M39 V10 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M39 V20
+## M39 V20 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M39 V30
+## M39 V30 (Stock)
::: pyasic.miners.whatsminer.btminer.M3X.M39.BTMinerM39V30
handler: python
options:
diff --git a/docs/miners/whatsminer/M5X.md b/docs/miners/whatsminer/M5X.md
index 2ef24281..34249629 100644
--- a/docs/miners/whatsminer/M5X.md
+++ b/docs/miners/whatsminer/M5X.md
@@ -1,259 +1,259 @@
# pyasic
## M5X Models
-## M50 VE30
+## M50 VE30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VE30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VG30
+## M50 VG30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH10
+## M50 VH10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH20
+## M50 VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH30
+## M50 VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH40
+## M50 VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH50
+## M50 VH50 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH60
+## M50 VH60 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH70
+## M50 VH70 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VH80
+## M50 VH80 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VJ10
+## M50 VJ10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VJ20
+## M50 VJ20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50 VJ30
+## M50 VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VJ10
+## M50S VJ10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VJ20
+## M50S VJ20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VJ30
+## M50S VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VH10
+## M50S VH10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VH20
+## M50S VH20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VH30
+## M50S VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VH40
+## M50S VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S VH50
+## M50S VH50 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S+ VH30
+## M50S+ VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S+ VH40
+## M50S+ VH40 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S+ VJ30
+## M50S+ VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S+ VK20
+## M50S+ VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S++ VK10
+## M50S++ VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S++ VK20
+## M50S++ VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M50S++ VK30
+## M50S++ VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus_Plus.BTMinerM50SPlusPlusVK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M53 VH30
+## M53 VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M53S VH30
+## M53S VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M53S VJ40
+## M53S VJ40 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVJ40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M53S+ VJ30
+## M53S+ VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M53S++ VK10
+## M53S++ VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus_Plus.BTMinerM53SPlusPlusVK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M56 VH30
+## M56 VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M56S VH30
+## M56S VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M56S+ VJ30
+## M56S+ VJ30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M59 VH30
+## M59 VH30 (Stock)
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
handler: python
options:
diff --git a/docs/miners/whatsminer/M6X.md b/docs/miners/whatsminer/M6X.md
index bf67327d..be87c25b 100644
--- a/docs/miners/whatsminer/M6X.md
+++ b/docs/miners/whatsminer/M6X.md
@@ -1,133 +1,133 @@
# pyasic
## M6X Models
-## M60 VK10
+## M60 VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60 VK20
+## M60 VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60 VK30
+## M60 VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60 VK40
+## M60 VK40 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60.BTMinerM60VK40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60S VK10
+## M60S VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60S VK20
+## M60S VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60S VK30
+## M60S VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M60S VK40
+## M60S VK40 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M60S.BTMinerM60SVK40
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63 VK10
+## M63 VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63 VK20
+## M63 VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63 VK30
+## M63 VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63.BTMinerM63VK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63S VK10
+## M63S VK10 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK10
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63S VK20
+## M63S VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M63S VK30
+## M63S VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M63S.BTMinerM63SVK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M66 VK20
+## M66 VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M66 VK30
+## M66 VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M66.BTMinerM66VK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M66S VK20
+## M66S VK20 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK20
handler: python
options:
show_root_heading: false
heading_level: 4
-## M66S VK30
+## M66S VK30 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK30
handler: python
options:
show_root_heading: false
heading_level: 4
-## M66S VK40
+## M66S VK40 (Stock)
::: pyasic.miners.whatsminer.btminer.M6X.M66S.BTMinerM66SVK40
handler: python
options:
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 55112534..29721bb6 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,3 +1,4 @@
jinja2<3.1.4
mkdocs
mkdocstrings[python]
+zipp>=3.19.1 # not directly required, pinned by Snyk to avoid a vulnerability
diff --git a/mkdocs.yml b/mkdocs.yml
index 0e99b1b6..7ed5fb21 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -68,6 +68,8 @@ nav:
- Auradine AD: "miners/auradine/AD.md"
- Auradine AI: "miners/auradine/AI.md"
- Auradine AT: "miners/auradine/AT.md"
+ - Blockminer: "miners/blockminer/blockminer.md"
+ - BitAxe BM: "miners/bitaxe/BM.md"
- Base Miner: "miners/base_miner.md"
- Settings:
- Settings: "settings/settings.md"
diff --git a/pyasic/config/__init__.py b/pyasic/config/__init__.py
index 4b1af0e9..bacf5c38 100644
--- a/pyasic/config/__init__.py
+++ b/pyasic/config/__init__.py
@@ -140,6 +140,14 @@ class MinerConfig:
**self.pools.as_mara(user_suffix=user_suffix),
}
+ def as_bitaxe(self, user_suffix: str = None) -> dict:
+ return {
+ **self.fan_mode.as_bitaxe(),
+ **self.temperature.as_bitaxe(),
+ **self.mining_mode.as_bitaxe(),
+ **self.pools.as_bitaxe(user_suffix=user_suffix),
+ }
+
@classmethod
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
"""Constructs a MinerConfig object from a dictionary."""
@@ -235,3 +243,10 @@ class MinerConfig:
fan_mode=FanModeConfig.from_mara(web_miner_config),
mining_mode=MiningModeConfig.from_mara(web_miner_config),
)
+
+ @classmethod
+ def from_bitaxe(cls, web_system_info: dict) -> "MinerConfig":
+ return cls(
+ pools=PoolConfig.from_bitaxe(web_system_info),
+ fan_mode=FanModeConfig.from_bitaxe(web_system_info),
+ )
diff --git a/pyasic/config/base.py b/pyasic/config/base.py
index 08834068..c8e55f00 100644
--- a/pyasic/config/base.py
+++ b/pyasic/config/base.py
@@ -60,6 +60,9 @@ class MinerConfigOption(Enum):
def as_mara(self) -> dict:
return self.value.as_mara()
+ def as_bitaxe(self) -> dict:
+ return self.value.as_bitaxe()
+
def __call__(self, *args, **kwargs):
return self.value(*args, **kwargs)
@@ -119,6 +122,9 @@ class MinerConfigValue:
def as_mara(self) -> dict:
return {}
+ def as_bitaxe(self) -> dict:
+ return {}
+
def __getitem__(self, item):
try:
return getattr(self, item)
diff --git a/pyasic/config/fans.py b/pyasic/config/fans.py
index ed0ef002..6359b2fd 100644
--- a/pyasic/config/fans.py
+++ b/pyasic/config/fans.py
@@ -80,6 +80,9 @@ class FanModeNormal(MinerConfigValue):
},
}
+ def as_bitaxe(self) -> dict:
+ return {"autoFanspeed": 1}
+
@dataclass
class FanModeManual(MinerConfigValue):
@@ -138,6 +141,9 @@ class FanModeManual(MinerConfigValue):
},
}
+ def as_bitaxe(self) -> dict:
+ return {"autoFanspeed": 0, "fanspeed": self.speed}
+
@dataclass
class FanModeImmersion(MinerConfigValue):
@@ -291,3 +297,10 @@ class FanModeConfig(MinerConfigOption):
except LookupError:
pass
return cls.default()
+
+ @classmethod
+ def from_bitaxe(cls, web_system_info: dict):
+ if web_system_info["autofanspeed"] == 1:
+ return cls.normal()
+ else:
+ return cls.manual(speed=web_system_info["fanspeed"])
diff --git a/pyasic/config/mining/algo.py b/pyasic/config/mining/algo.py
index c8c00e79..96021b8f 100644
--- a/pyasic/config/mining/algo.py
+++ b/pyasic/config/mining/algo.py
@@ -1,3 +1,5 @@
+from __future__ import annotations
+
from dataclasses import dataclass, field
from pyasic.config.base import MinerConfigOption, MinerConfigValue
diff --git a/pyasic/config/pools.py b/pyasic/config/pools.py
index 19c88262..e2572cd9 100644
--- a/pyasic/config/pools.py
+++ b/pyasic/config/pools.py
@@ -127,6 +127,13 @@ class Pool(MinerConfigValue):
}
return {"url": self.url, "user": self.user, "pass": self.password}
+ def as_bitaxe(self, user_suffix: str = None) -> dict:
+ return {
+ "stratumURL": self.url,
+ "stratumUser": f"{self.user}{user_suffix}",
+ "stratumPassword": self.password,
+ }
+
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "Pool":
return cls(
@@ -194,6 +201,15 @@ class Pool(MinerConfigValue):
password=web_pool["pass"],
)
+ @classmethod
+ def from_bitaxe(cls, web_system_info: dict) -> "Pool":
+ url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
+ return cls(
+ url=url,
+ user=web_system_info["stratumUser"],
+ password=web_system_info.get("stratumPassword", ""),
+ )
+
@dataclass
class PoolGroup(MinerConfigValue):
@@ -287,6 +303,9 @@ class PoolGroup(MinerConfigValue):
def as_mara(self, user_suffix: str = None) -> list:
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
+ def as_bitaxe(self, user_suffix: str = None) -> dict:
+ return self.pools[0].as_bitaxe(user_suffix=user_suffix)
+
@classmethod
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
cls_conf = {}
@@ -360,6 +379,10 @@ class PoolGroup(MinerConfigValue):
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
+ @classmethod
+ def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
+ return cls(pools=[Pool.from_bitaxe(web_system_info)])
+
@dataclass
class PoolConfig(MinerConfigValue):
@@ -456,6 +479,9 @@ class PoolConfig(MinerConfigValue):
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
return {"pools": []}
+ def as_bitaxe(self, user_suffix: str = None) -> dict:
+ return self.groups[0].as_bitaxe(user_suffix=user_suffix)
+
@classmethod
def from_api(cls, api_pools: dict) -> "PoolConfig":
try:
@@ -514,3 +540,7 @@ class PoolConfig(MinerConfigValue):
@classmethod
def from_mara(cls, web_config: dict) -> "PoolConfig":
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
+
+ @classmethod
+ def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
+ return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
diff --git a/pyasic/data/pools.py b/pyasic/data/pools.py
index 4ff4e158..f4970fbf 100644
--- a/pyasic/data/pools.py
+++ b/pyasic/data/pools.py
@@ -1,4 +1,35 @@
from dataclasses import dataclass, field
+from enum import Enum
+from typing import Optional
+from urllib.parse import urlparse
+
+
+class Scheme(Enum):
+ STRATUM_V1 = "stratum+tcp"
+ STRATUM_V2 = "stratum2+tcp"
+
+
+@dataclass
+class PoolUrl:
+ scheme: Scheme
+ host: str
+ port: int
+ pubkey: Optional[str] = None
+
+ def __str__(self) -> str:
+ if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
+ return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
+ else:
+ return f"{self.scheme.value}://{self.host}:{self.port}"
+
+ @classmethod
+ def from_str(cls, url: str) -> "PoolUrl":
+ parsed_url = urlparse(url)
+ scheme = Scheme(parsed_url.scheme)
+ host = parsed_url.hostname
+ port = parsed_url.port
+ pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
+ return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
@dataclass
@@ -19,13 +50,13 @@ class PoolMetrics:
pool_stale_percent: Percentage of stale shares by the pool.
"""
+ url: PoolUrl
accepted: int = None
rejected: int = None
get_failures: int = None
remote_failures: int = None
active: bool = None
alive: bool = None
- url: str = None
index: int = None
user: str = None
pool_rejected_percent: float = field(init=False)
diff --git a/pyasic/device/makes.py b/pyasic/device/makes.py
index f31bfdcd..cfb1ecbe 100644
--- a/pyasic/device/makes.py
+++ b/pyasic/device/makes.py
@@ -25,6 +25,7 @@ class MinerMake(str, Enum):
GOLDSHELL = "Goldshell"
AURADINE = "Auradine"
EPIC = "ePIC"
+ BITAXE = "BitAxe"
def __str__(self):
return self.value
diff --git a/pyasic/device/models.py b/pyasic/device/models.py
index 883da95c..8eab6655 100644
--- a/pyasic/device/models.py
+++ b/pyasic/device/models.py
@@ -329,6 +329,15 @@ class AuradineModels(str, Enum):
return self.value
+class BitAxeModels(str, Enum):
+ BM1366 = "Ultra"
+ BM1368 = "Supra"
+ BM1397 = "Max"
+
+ def __str__(self):
+ return self.value
+
+
class MinerModel:
ANTMINER = AntminerModels
WHATSMINER = WhatsminerModels
@@ -337,3 +346,4 @@ class MinerModel:
GOLDSHELL = GoldshellModels
AURADINE = AuradineModels
EPIC = ePICModels
+ BITAXE = BitAxeModels
diff --git a/pyasic/miners/antminer/vnish/X19/S19.py b/pyasic/miners/antminer/vnish/X19/S19.py
index 032d5db9..7dd9dd11 100644
--- a/pyasic/miners/antminer/vnish/X19/S19.py
+++ b/pyasic/miners/antminer/vnish/X19/S19.py
@@ -24,6 +24,7 @@ from pyasic.miners.device.models import (
S19jPro,
S19NoPIC,
S19Pro,
+ S19ProHydro,
)
@@ -57,3 +58,7 @@ class VNishS19j(VNish, S19j):
class VNishS19jPro(VNish, S19jPro):
pass
+
+
+class VNishS19ProHydro(VNish, S19ProHydro):
+ pass
diff --git a/pyasic/miners/antminer/vnish/X19/__init__.py b/pyasic/miners/antminer/vnish/X19/__init__.py
index f69c45e2..b7f679c0 100644
--- a/pyasic/miners/antminer/vnish/X19/__init__.py
+++ b/pyasic/miners/antminer/vnish/X19/__init__.py
@@ -22,6 +22,7 @@ from .S19 import (
VNishS19jPro,
VNishS19NoPIC,
VNishS19Pro,
+ VNishS19ProHydro,
VNishS19XP,
)
from .T19 import VNishT19
diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py
index 26b803c4..5d162730 100644
--- a/pyasic/miners/backends/antminer.py
+++ b/pyasic/miners/backends/antminer.py
@@ -19,6 +19,8 @@ from typing import List, Optional, Union
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import MinerErrorData, X19Error
+from pyasic.data.pools import PoolMetrics, PoolUrl
+from pyasic.errors import APIError
from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.data import (
@@ -31,8 +33,6 @@ from pyasic.miners.data import (
from pyasic.rpc.antminer import AntminerRPCAPI
from pyasic.ssh.antminer import AntminerModernSSH
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
-from pyasic.data.pools import PoolMetrics
-from pyasic.errors import APIError
ANTMINER_MODERN_DATA_LOC = DataLocations(
**{
@@ -95,7 +95,7 @@ class AntminerModern(BMMiner):
web: AntminerModernWebAPI
_rpc_cls = AntminerRPCAPI
- web: AntminerRPCAPI
+ rpc: AntminerRPCAPI
_ssh_cls = AntminerModernSSH
ssh: AntminerModernSSH
@@ -156,7 +156,7 @@ class AntminerModern(BMMiner):
await self.send_config(cfg)
return True
- async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
+ async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
@@ -169,7 +169,7 @@ class AntminerModern(BMMiner):
except KeyError:
pass
- async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
+ async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
if web_get_system_info is None:
try:
web_get_system_info = await self.web.get_system_info()
@@ -264,7 +264,9 @@ class AntminerModern(BMMiner):
pass
return self.light
- async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_stats: dict = None
+ ) -> Optional[AlgoHashRate]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
@@ -368,6 +370,8 @@ class AntminerModern(BMMiner):
try:
pools = rpc_pools.get("POOLS", [])
for pool_info in 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"),
@@ -375,10 +379,9 @@ class AntminerModern(BMMiner):
remote_failures=pool_info.get("Remote Failures"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
- url=pool_info.get("URL"),
+ url=pool_url,
user=pool_info.get("User"),
- index=pool_info.get("POOL")
-
+ index=pool_info.get("POOL"),
)
pools_data.append(pool_data)
except LookupError:
@@ -446,7 +449,7 @@ class AntminerOld(CGMiner):
self.config = config
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
- async def _get_mac(self) -> Union[str, None]:
+ async def _get_mac(self) -> Optional[str]:
try:
data = await self.web.get_system_info()
if data:
diff --git a/pyasic/miners/backends/auradine.py b/pyasic/miners/backends/auradine.py
index 690848d1..893f0770 100644
--- a/pyasic/miners/backends/auradine.py
+++ b/pyasic/miners/backends/auradine.py
@@ -281,7 +281,7 @@ class Auradine(StockFirmware):
except LookupError:
pass
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
diff --git a/pyasic/miners/backends/avalonminer.py b/pyasic/miners/backends/avalonminer.py
index da552e61..a831ce41 100644
--- a/pyasic/miners/backends/avalonminer.py
+++ b/pyasic/miners/backends/avalonminer.py
@@ -173,7 +173,7 @@ class AvalonMiner(CGMiner):
except (KeyError, ValueError):
pass
- async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[AlgoHashRate]:
if rpc_devs is None:
try:
rpc_devs = await self.rpc.devs()
@@ -238,7 +238,9 @@ class AvalonMiner(CGMiner):
return hashboards
- async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_stats: dict = None
+ ) -> Optional[AlgoHashRate]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py
index 5b1a3b8c..c3855d57 100644
--- a/pyasic/miners/backends/bfgminer.py
+++ b/pyasic/miners/backends/bfgminer.py
@@ -105,7 +105,7 @@ class BFGMiner(StockFirmware):
return self.fw_ver
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_summary is None:
try:
@@ -207,7 +207,9 @@ class BFGMiner(StockFirmware):
return fans
- async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_stats: dict = None
+ ) -> Optional[AlgoHashRate]:
# X19 method, not sure compatibility
if rpc_stats is None:
try:
diff --git a/pyasic/miners/backends/bitaxe.py b/pyasic/miners/backends/bitaxe.py
new file mode 100644
index 00000000..ca0d7f06
--- /dev/null
+++ b/pyasic/miners/backends/bitaxe.py
@@ -0,0 +1,189 @@
+from typing import List, Optional
+
+from pyasic import APIError, MinerConfig
+from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
+from pyasic.device import MinerFirmware
+from pyasic.miners.base import BaseMiner
+from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
+from pyasic.web.bitaxe import BitAxeWebAPI
+
+BITAXE_DATA_LOC = DataLocations(
+ **{
+ str(DataOptions.HASHRATE): DataFunction(
+ "_get_hashrate",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.WATTAGE): DataFunction(
+ "_get_wattage",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.UPTIME): DataFunction(
+ "_get_uptime",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.HASHBOARDS): DataFunction(
+ "_get_hashboards",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.HOSTNAME): DataFunction(
+ "_get_hostname",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.FANS): DataFunction(
+ "_get_fans",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.FW_VERSION): DataFunction(
+ "_get_fw_ver",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ str(DataOptions.API_VERSION): DataFunction(
+ "_get_api_ver",
+ [WebAPICommand("web_system_info", "system/info")],
+ ),
+ }
+)
+
+
+class BitAxe(BaseMiner):
+ """Handler for BitAxe"""
+
+ web: BitAxeWebAPI
+ _web_cls = BitAxeWebAPI
+
+ firmware = MinerFirmware.STOCK
+
+ data_locations = BITAXE_DATA_LOC
+
+ async def reboot(self) -> bool:
+ await self.web.restart()
+ return True
+
+ async def get_config(self) -> MinerConfig:
+ web_system_info = await self.web.system_info()
+ return MinerConfig.from_bitaxe(web_system_info)
+
+ async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
+ await self.web.update_settings(**config.as_bitaxe())
+
+ async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+ if web_system_info is not None:
+ try:
+ return round(web_system_info["power"])
+ except KeyError:
+ pass
+
+ async def _get_hashrate(
+ self, web_system_info: dict = None
+ ) -> Optional[AlgoHashRate]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return AlgoHashRate.SHA256(
+ web_system_info["hashRate"], HashUnit.SHA256.GH
+ ).into(self.algo.unit.default)
+ except KeyError:
+ pass
+
+ async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return web_system_info["uptimeSeconds"]
+ except KeyError:
+ pass
+
+ async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return [
+ HashBoard(
+ hashrate=AlgoHashRate.SHA256(
+ web_system_info["hashRate"], HashUnit.SHA256.GH
+ ).into(self.algo.unit.default),
+ chip_temp=web_system_info.get("temp"),
+ temp=web_system_info.get("vrTemp"),
+ chips=web_system_info.get("asicCount", 1),
+ expected_chips=self.expected_chips,
+ missing=False,
+ active=True,
+ voltage=web_system_info.get("voltage"),
+ )
+ ]
+ except KeyError:
+ pass
+ return []
+
+ async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return [Fan(speed=web_system_info["fanrpm"])]
+ except KeyError:
+ pass
+ return []
+
+ async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return web_system_info["hostname"]
+ except KeyError:
+ pass
+
+ async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return web_system_info["version"]
+ except KeyError:
+ pass
+
+ async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
+ if web_system_info is None:
+ try:
+ web_system_info = await self.web.system_info()
+ except APIError:
+ pass
+
+ if web_system_info is not None:
+ try:
+ return web_system_info["version"]
+ except KeyError:
+ pass
diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py
index 963ef61e..a04462dc 100644
--- a/pyasic/miners/backends/bmminer.py
+++ b/pyasic/miners/backends/bmminer.py
@@ -109,7 +109,7 @@ class BMMiner(StockFirmware):
return self.fw_ver
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_summary is None:
try:
@@ -223,7 +223,9 @@ class BMMiner(StockFirmware):
return fans
- async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_stats: dict = None
+ ) -> Optional[AlgoHashRate]:
# X19 method, not sure compatibility
if rpc_stats is None:
try:
diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py
index ab0c75ea..fbbdba37 100644
--- a/pyasic/miners/backends/braiins_os.py
+++ b/pyasic/miners/backends/braiins_os.py
@@ -26,6 +26,7 @@ from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
+from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError
from pyasic.miners.data import (
DataFunction,
@@ -39,7 +40,6 @@ from pyasic.rpc.bosminer import BOSMinerRPCAPI
from pyasic.ssh.braiins_os import BOSMinerSSH
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
from pyasic.web.braiins_os.proto.braiins.bos.v1 import SaveAction
-from pyasic.data.pools import PoolMetrics
BOSMINER_DATA_LOC = DataLocations(
**{
@@ -349,7 +349,7 @@ class BOSMiner(BraiinsOSFirmware):
return None
return hostname
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -525,7 +525,9 @@ class BOSMiner(BraiinsOSFirmware):
except (TypeError, AttributeError):
return self.light
- async def _get_expected_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_devs: dict = None
+ ) -> Optional[AlgoHashRate]:
if rpc_devs is None:
try:
rpc_devs = await self.rpc.devs()
@@ -590,6 +592,8 @@ class BOSMiner(BraiinsOSFirmware):
try:
pools = rpc_pools.get("POOLS", [])
for pool_info in 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"),
@@ -597,17 +601,15 @@ class BOSMiner(BraiinsOSFirmware):
remote_failures=pool_info.get("Remote Failures"),
active=pool_info.get("Stratum Active"),
alive=pool_info.get("Status") == "Alive",
- url=pool_info.get("URL"),
+ url=pool_url,
user=pool_info.get("User"),
index=pool_info.get("POOL"),
-
)
pools_data.append(pool_data)
except LookupError:
pass
return pools_data
-
async def upgrade_firmware(self, file: Path):
"""
Upgrade the firmware of the BOSMiner device.
@@ -866,7 +868,7 @@ class BOSer(BraiinsOSFirmware):
except LookupError:
pass
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -883,7 +885,7 @@ class BOSer(BraiinsOSFirmware):
async def _get_expected_hashrate(
self, grpc_miner_details: dict = None
- ) -> Optional[float]:
+ ) -> Optional[AlgoHashRate]:
if grpc_miner_details is None:
try:
grpc_miner_details = await self.web.get_miner_details()
diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py
index 7e6b6852..8427bee9 100644
--- a/pyasic/miners/backends/btminer.py
+++ b/pyasic/miners/backends/btminer.py
@@ -15,9 +15,10 @@
# ------------------------------------------------------------------------------
import logging
-from typing import List, Optional
-import aiofiles
from pathlib import Path
+from typing import List, Optional
+
+import aiofiles
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
@@ -388,7 +389,7 @@ class BTMiner(StockFirmware):
return hostname
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -564,7 +565,9 @@ class BTMiner(StockFirmware):
pass
return errors
- async def _get_expected_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_summary: dict = None
+ ) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -675,17 +678,24 @@ class BTMiner(StockFirmware):
result = await self.rpc.update_firmware(upgrade_contents)
- logging.info("Firmware upgrade process completed successfully for Whatsminer.")
+ logging.info(
+ "Firmware upgrade process completed successfully for Whatsminer."
+ )
return result
except FileNotFoundError as e:
logging.error(f"File not found during the firmware upgrade process: {e}")
raise
except ValueError as e:
- logging.error(f"Validation error occurred during the firmware upgrade process: {e}")
+ logging.error(
+ f"Validation error occurred during the firmware upgrade process: {e}"
+ )
raise
except OSError as e:
logging.error(f"OS error occurred during the firmware upgrade process: {e}")
raise
except Exception as e:
- logging.error(f"An unexpected error occurred during the firmware upgrade process: {e}", exc_info=True)
+ logging.error(
+ f"An unexpected error occurred during the firmware upgrade process: {e}",
+ exc_info=True,
+ )
raise
diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py
index 53322aed..bb1d1ca4 100644
--- a/pyasic/miners/backends/cgminer.py
+++ b/pyasic/miners/backends/cgminer.py
@@ -109,7 +109,7 @@ class CGMiner(StockFirmware):
return self.fw_ver
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py
index 5ddca5a6..a89a01c7 100644
--- a/pyasic/miners/backends/epic.py
+++ b/pyasic/miners/backends/epic.py
@@ -14,14 +14,15 @@
# limitations under the License. -
# ------------------------------------------------------------------------------
+from pathlib import Path
from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
from pyasic.data.error_codes import MinerErrorData, X19Error
+from pyasic.data.pools import PoolMetrics
from pyasic.errors import APIError
from pyasic.logger import logger
-from pyasic.data.pools import PoolMetrics
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
from pyasic.miners.device.firmware import ePICFirmware
from pyasic.web.epic import ePICWebAPI
@@ -220,7 +221,7 @@ class ePIC(ePICFirmware):
except KeyError:
pass
- async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, web_summary: dict = None) -> Optional[AlgoHashRate]:
if web_summary is None:
try:
web_summary = await self.web.summary()
@@ -239,7 +240,9 @@ class ePIC(ePICFirmware):
except (LookupError, ValueError, TypeError):
pass
- async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, web_summary: dict = None
+ ) -> Optional[AlgoHashRate]:
if web_summary is None:
try:
web_summary = await self.web.summary()
@@ -449,4 +452,18 @@ class ePIC(ePICFirmware):
)
return pool_data
except LookupError:
- pass
\ No newline at end of file
+ pass
+
+ async def upgrade_firmware(self, file: Path | str, keep_settings: bool = True) -> bool:
+
+ """
+ Upgrade the firmware of the ePIC miner device.
+
+ Args:
+ file (Path | str): The local file path of the firmware to be uploaded.
+ keep_settings (bool): Whether to keep the current settings after the update.
+
+ Returns:
+ bool: Whether the firmware update succeeded.
+ """
+ return await self.web.system_update(file=file, keep_settings=keep_settings)
\ No newline at end of file
diff --git a/pyasic/miners/backends/innosilicon.py b/pyasic/miners/backends/innosilicon.py
index 8efe28f2..b6cc88b1 100644
--- a/pyasic/miners/backends/innosilicon.py
+++ b/pyasic/miners/backends/innosilicon.py
@@ -169,7 +169,7 @@ class Innosilicon(CGMiner):
async def _get_hashrate(
self, rpc_summary: dict = None, web_get_all: dict = None
- ) -> Optional[float]:
+ ) -> Optional[AlgoHashRate]:
if web_get_all:
web_get_all = web_get_all["all"]
diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py
index 0a6a4e38..abaa3664 100644
--- a/pyasic/miners/backends/luxminer.py
+++ b/pyasic/miners/backends/luxminer.py
@@ -17,6 +17,7 @@ from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
+from pyasic.data.pools import PoolMetrics, PoolUrl
from pyasic.errors import APIError
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.firmware import LuxOSFirmware
@@ -51,6 +52,9 @@ LUXMINER_DATA_LOC = DataLocations(
str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("rpc_stats", "stats")]
),
+ str(DataOptions.POOLS): DataFunction(
+ "_get_pools", [RPCAPICommand("rpc_pools", "pools")]
+ ),
}
)
@@ -162,7 +166,7 @@ class LUXMiner(LuxOSFirmware):
return mac
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
if rpc_summary is None:
try:
rpc_summary = await self.rpc.summary()
@@ -263,7 +267,9 @@ class LUXMiner(LuxOSFirmware):
fans.append(Fan())
return fans
- async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, rpc_stats: dict = None
+ ) -> Optional[AlgoHashRate]:
if rpc_stats is None:
try:
rpc_stats = await self.rpc.stats()
@@ -295,3 +301,33 @@ class LUXMiner(LuxOSFirmware):
return int(rpc_stats["STATS"][1]["Elapsed"])
except LookupError:
pass
+
+ 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 pool_info in 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"),
+ get_failures=pool_info.get("Get Failures"),
+ remote_failures=pool_info.get("Remote Failures"),
+ active=pool_info.get("Stratum Active"),
+ alive=pool_info.get("Status") == "Alive",
+ url=pool_url,
+ user=pool_info.get("User"),
+ index=pool_info.get("POOL"),
+ )
+ pools_data.append(pool_data)
+ except LookupError:
+ pass
+ return pools_data
diff --git a/pyasic/miners/backends/marathon.py b/pyasic/miners/backends/marathon.py
index f9edee32..0c8b1c5f 100644
--- a/pyasic/miners/backends/marathon.py
+++ b/pyasic/miners/backends/marathon.py
@@ -225,7 +225,7 @@ class MaraMiner(MaraFirmware):
except LookupError:
pass
- async def _get_hashrate(self, web_brief: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, web_brief: dict = None) -> Optional[AlgoHashRate]:
if web_brief is None:
try:
web_brief = await self.web.brief()
@@ -271,7 +271,9 @@ class MaraMiner(MaraFirmware):
pass
return False
- async def _get_expected_hashrate(self, web_brief: dict = None) -> Optional[float]:
+ async def _get_expected_hashrate(
+ self, web_brief: dict = None
+ ) -> Optional[AlgoHashRate]:
if web_brief is None:
try:
web_brief = await self.web.brief()
@@ -288,7 +290,7 @@ class MaraMiner(MaraFirmware):
async def _get_wattage_limit(
self, web_miner_config: dict = None
- ) -> Optional[float]:
+ ) -> Optional[AlgoHashRate]:
if web_miner_config is None:
try:
web_miner_config = await self.web.get_miner_config()
diff --git a/pyasic/miners/backends/unknown.py b/pyasic/miners/backends/unknown.py
index 5eccae04..a5ecdd3f 100644
--- a/pyasic/miners/backends/unknown.py
+++ b/pyasic/miners/backends/unknown.py
@@ -15,7 +15,7 @@
from typing import List, Optional, Tuple
from pyasic.config import MinerConfig
-from pyasic.data import Fan, HashBoard
+from pyasic.data import AlgoHashRate, Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.miners.base import BaseMiner
from pyasic.rpc.unknown import UnknownRPCAPI
@@ -80,7 +80,7 @@ class UnknownMiner(BaseMiner):
async def _get_hostname(self) -> Optional[str]:
return None
- async def _get_hashrate(self) -> Optional[float]:
+ async def _get_hashrate(self) -> Optional[AlgoHashRate]:
return None
async def _get_hashboards(self) -> List[HashBoard]:
@@ -113,7 +113,7 @@ class UnknownMiner(BaseMiner):
async def _get_fault_light(self) -> bool:
return False
- async def _get_expected_hashrate(self) -> Optional[float]:
+ async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
return None
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py
index 4a560288..b8adc30f 100644
--- a/pyasic/miners/backends/vnish.py
+++ b/pyasic/miners/backends/vnish.py
@@ -193,7 +193,7 @@ class VNish(VNishFirmware, BMMiner):
except KeyError:
pass
- async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
+ async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
# get hr from API
if rpc_summary is None:
try:
diff --git a/pyasic/miners/backends/whatsminer.py b/pyasic/miners/backends/whatsminer.py
index 78d2415b..0654f7fe 100644
--- a/pyasic/miners/backends/whatsminer.py
+++ b/pyasic/miners/backends/whatsminer.py
@@ -29,4 +29,4 @@ class M3X(BTMiner):
class M2X(BTMiner):
- pass
\ No newline at end of file
+ pass
diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py
index 576c0ea4..3ec91703 100644
--- a/pyasic/miners/base.py
+++ b/pyasic/miners/base.py
@@ -19,7 +19,7 @@ import warnings
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
from pyasic.config import MinerConfig
-from pyasic.data import Fan, HashBoard, MinerData
+from pyasic.data import AlgoHashRate, Fan, HashBoard, MinerData
from pyasic.data.device import DeviceInfo
from pyasic.data.error_codes import MinerErrorData
from pyasic.data.pools import PoolMetrics
@@ -238,7 +238,7 @@ class MinerProtocol(Protocol):
"""
return await self._get_hostname()
- async def get_hashrate(self) -> Optional[float]:
+ async def get_hashrate(self) -> Optional[AlgoHashRate]:
"""Get the hashrate of the miner and return it as a float in TH/s.
Returns:
@@ -318,7 +318,7 @@ class MinerProtocol(Protocol):
"""
return await self._get_fault_light()
- async def get_expected_hashrate(self) -> Optional[float]:
+ async def get_expected_hashrate(self) -> Optional[AlgoHashRate]:
"""Get the nominal hashrate from factory if available.
Returns:
@@ -362,7 +362,7 @@ class MinerProtocol(Protocol):
async def _get_hostname(self) -> Optional[str]:
pass
- async def _get_hashrate(self) -> Optional[float]:
+ async def _get_hashrate(self) -> Optional[AlgoHashRate]:
pass
async def _get_hashboards(self) -> List[HashBoard]:
@@ -392,7 +392,7 @@ class MinerProtocol(Protocol):
async def _get_fault_light(self) -> Optional[bool]:
pass
- async def _get_expected_hashrate(self) -> Optional[float]:
+ async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
pass
async def _is_mining(self) -> Optional[bool]:
@@ -574,4 +574,4 @@ class BaseMiner(MinerProtocol):
"""
return False
-AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
\ No newline at end of file
+AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
diff --git a/pyasic/miners/bitaxe/__init__.py b/pyasic/miners/bitaxe/__init__.py
new file mode 100644
index 00000000..6068242d
--- /dev/null
+++ b/pyasic/miners/bitaxe/__init__.py
@@ -0,0 +1 @@
+from .espminer import *
diff --git a/pyasic/miners/bitaxe/espminer/BM/BM1366.py b/pyasic/miners/bitaxe/espminer/BM/BM1366.py
new file mode 100644
index 00000000..d2507541
--- /dev/null
+++ b/pyasic/miners/bitaxe/espminer/BM/BM1366.py
@@ -0,0 +1,6 @@
+from pyasic.miners.backends.bitaxe import BitAxe
+from pyasic.miners.device.models.bitaxe import Ultra
+
+
+class BitAxeUltra(BitAxe, Ultra):
+ pass
diff --git a/pyasic/miners/bitaxe/espminer/BM/BM1368.py b/pyasic/miners/bitaxe/espminer/BM/BM1368.py
new file mode 100644
index 00000000..ea2e3b7b
--- /dev/null
+++ b/pyasic/miners/bitaxe/espminer/BM/BM1368.py
@@ -0,0 +1,6 @@
+from pyasic.miners.backends.bitaxe import BitAxe
+from pyasic.miners.device.models.bitaxe import Supra
+
+
+class BitAxeSupra(BitAxe, Supra):
+ pass
diff --git a/pyasic/miners/bitaxe/espminer/BM/BM1397.py b/pyasic/miners/bitaxe/espminer/BM/BM1397.py
new file mode 100644
index 00000000..722b1d35
--- /dev/null
+++ b/pyasic/miners/bitaxe/espminer/BM/BM1397.py
@@ -0,0 +1,6 @@
+from pyasic.miners.backends.bitaxe import BitAxe
+from pyasic.miners.device.models.bitaxe import Max
+
+
+class BitAxeMax(BitAxe, Max):
+ pass
diff --git a/pyasic/miners/bitaxe/espminer/BM/__init__.py b/pyasic/miners/bitaxe/espminer/BM/__init__.py
new file mode 100644
index 00000000..9926a677
--- /dev/null
+++ b/pyasic/miners/bitaxe/espminer/BM/__init__.py
@@ -0,0 +1,3 @@
+from .BM1366 import BitAxeUltra
+from .BM1368 import BitAxeSupra
+from .BM1397 import BitAxeMax
diff --git a/pyasic/miners/bitaxe/espminer/__init__.py b/pyasic/miners/bitaxe/espminer/__init__.py
new file mode 100644
index 00000000..dc06b2e0
--- /dev/null
+++ b/pyasic/miners/bitaxe/espminer/__init__.py
@@ -0,0 +1 @@
+from .BM import *
diff --git a/pyasic/miners/device/makes.py b/pyasic/miners/device/makes.py
index 2d8433a4..6bfbe40b 100644
--- a/pyasic/miners/device/makes.py
+++ b/pyasic/miners/device/makes.py
@@ -44,3 +44,7 @@ class AuradineMake(BaseMiner):
class ePICMake(BaseMiner):
make = MinerMake.EPIC
+
+
+class BitAxeMake(BaseMiner):
+ make = MinerMake.BITAXE
diff --git a/pyasic/miners/device/models/antminer/X3/L3.py b/pyasic/miners/device/models/antminer/X3/L3.py
index 82bb7172..bd5ba911 100644
--- a/pyasic/miners/device/models/antminer/X3/L3.py
+++ b/pyasic/miners/device/models/antminer/X3/L3.py
@@ -18,6 +18,6 @@ from pyasic.miners.device.makes import AntMinerMake
class L3Plus(AntMinerMake):
- raw_model = MinerModel.ANTMINER
+ raw_model = MinerModel.ANTMINER.L3Plus
expected_chips = 72
diff --git a/pyasic/miners/device/models/bitaxe/BM/BM1366.py b/pyasic/miners/device/models/bitaxe/BM/BM1366.py
new file mode 100644
index 00000000..6db1ad16
--- /dev/null
+++ b/pyasic/miners/device/models/bitaxe/BM/BM1366.py
@@ -0,0 +1,10 @@
+from pyasic.device.models import MinerModel
+from pyasic.miners.device.makes import BitAxeMake
+
+
+class Ultra(BitAxeMake):
+ raw_model = MinerModel.BITAXE.BM1366
+
+ expected_hashboards = 1
+ expected_chips = 1
+ expected_fans = 1
diff --git a/pyasic/miners/device/models/bitaxe/BM/BM1368.py b/pyasic/miners/device/models/bitaxe/BM/BM1368.py
new file mode 100644
index 00000000..76cf63c9
--- /dev/null
+++ b/pyasic/miners/device/models/bitaxe/BM/BM1368.py
@@ -0,0 +1,10 @@
+from pyasic.device.models import MinerModel
+from pyasic.miners.device.makes import BitAxeMake
+
+
+class Supra(BitAxeMake):
+ raw_model = MinerModel.BITAXE.BM1368
+
+ expected_hashboards = 1
+ expected_chips = 1
+ expected_fans = 1
diff --git a/pyasic/miners/device/models/bitaxe/BM/BM1397.py b/pyasic/miners/device/models/bitaxe/BM/BM1397.py
new file mode 100644
index 00000000..97230f17
--- /dev/null
+++ b/pyasic/miners/device/models/bitaxe/BM/BM1397.py
@@ -0,0 +1,10 @@
+from pyasic.device.models import MinerModel
+from pyasic.miners.device.makes import BitAxeMake
+
+
+class Max(BitAxeMake):
+ raw_model = MinerModel.BITAXE.BM1397
+
+ expected_hashboards = 1
+ expected_chips = 1
+ expected_fans = 1
diff --git a/pyasic/miners/device/models/bitaxe/BM/__init__.py b/pyasic/miners/device/models/bitaxe/BM/__init__.py
new file mode 100644
index 00000000..b2c1b2c0
--- /dev/null
+++ b/pyasic/miners/device/models/bitaxe/BM/__init__.py
@@ -0,0 +1,3 @@
+from .BM1366 import Ultra
+from .BM1368 import Supra
+from .BM1397 import Max
diff --git a/pyasic/miners/device/models/bitaxe/__init__.py b/pyasic/miners/device/models/bitaxe/__init__.py
new file mode 100644
index 00000000..dc06b2e0
--- /dev/null
+++ b/pyasic/miners/device/models/bitaxe/__init__.py
@@ -0,0 +1 @@
+from .BM import *
diff --git a/pyasic/miners/factory.py b/pyasic/miners/factory.py
index 7a338029..bff39771 100644
--- a/pyasic/miners/factory.py
+++ b/pyasic/miners/factory.py
@@ -31,8 +31,10 @@ from pyasic.miners.antminer import *
from pyasic.miners.auradine import *
from pyasic.miners.avalonminer import *
from pyasic.miners.backends import *
+from pyasic.miners.backends.bitaxe import BitAxe
from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner
+from pyasic.miners.bitaxe import *
from pyasic.miners.blockminer import *
from pyasic.miners.device.makes import *
from pyasic.miners.goldshell import *
@@ -53,6 +55,7 @@ class MinerTypes(enum.Enum):
EPIC = 9
AURADINE = 10
MARATHON = 11
+ BITAXE = 12
MINER_CLASSES = {
@@ -383,6 +386,7 @@ MINER_CLASSES = {
"ANTMINER S19J PRO": VNishS19jPro,
"ANTMINER S19A": VNishS19a,
"ANTMINER S19A PRO": VNishS19aPro,
+ "ANTMINER S19 PRO HYD.": VNishS19ProHydro,
"ANTMINER T19": VNishT19,
"ANTMINER S21": VNishS21,
},
@@ -438,6 +442,12 @@ MINER_CLASSES = {
"ANTMINER S21": MaraS21,
"ANTMINER T21": MaraT21,
},
+ MinerTypes.BITAXE: {
+ None: BitAxe,
+ "BM1368": BitAxeSupra,
+ "BM1366": BitAxeUltra,
+ "BM1397": BitAxeMax,
+ },
}
@@ -514,6 +524,7 @@ class MinerFactory:
MinerTypes.LUX_OS: self.get_miner_model_luxos,
MinerTypes.AURADINE: self.get_miner_model_auradine,
MinerTypes.MARATHON: self.get_miner_model_marathon,
+ MinerTypes.BITAXE: self.get_miner_model_bitaxe,
}
fn = miner_model_fns.get(miner_type)
@@ -595,6 +606,8 @@ class MinerFactory:
return MinerTypes.WHATSMINER
if "Braiins OS" in web_text:
return MinerTypes.BRAIINS_OS
+ if "AxeOS" in web_text:
+ return MinerTypes.BITAXE
if "cloud-box" in web_text:
return MinerTypes.GOLDSHELL
if "AnthillOS" in web_text:
@@ -1008,6 +1021,18 @@ class MinerFactory:
except (TypeError, LookupError):
pass
+ async def get_miner_model_bitaxe(self, ip: str) -> str | None:
+ web_json_data = await self.send_web_command(ip, "/api/system/info")
+
+ try:
+ miner_model = web_json_data["ASICModel"]
+ if miner_model == "":
+ return None
+
+ return miner_model
+ except (TypeError, LookupError):
+ pass
+
miner_factory = MinerFactory()
diff --git a/pyasic/ssh/braiins_os.py b/pyasic/ssh/braiins_os.py
index 28ad000d..6dee3502 100644
--- a/pyasic/ssh/braiins_os.py
+++ b/pyasic/ssh/braiins_os.py
@@ -92,4 +92,4 @@ class BOSMinerSSH(BaseSSH):
Returns:
str: Status of the LED.
"""
- return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
\ No newline at end of file
+ return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
diff --git a/pyasic/web/bitaxe.py b/pyasic/web/bitaxe.py
new file mode 100644
index 00000000..6f5c5de7
--- /dev/null
+++ b/pyasic/web/bitaxe.py
@@ -0,0 +1,96 @@
+from __future__ import annotations
+
+import asyncio
+import json
+from typing import Any
+
+import httpx
+
+from pyasic import APIError, settings
+from pyasic.web.base import BaseWebAPI
+
+
+class BitAxeWebAPI(BaseWebAPI):
+ async def send_command(
+ self,
+ command: str | bytes,
+ ignore_errors: bool = False,
+ allow_warning: bool = True,
+ privileged: bool = False,
+ **parameters: Any,
+ ) -> dict:
+ url = f"http://{self.ip}:{self.port}/api/{command}"
+ try:
+ async with httpx.AsyncClient(
+ transport=settings.transport(),
+ ) as client:
+ if parameters.get("post", False):
+ parameters.pop("post")
+ data = await client.post(
+ url,
+ timeout=settings.get("api_function_timeout", 3),
+ json=parameters,
+ )
+ elif parameters.get("patch", False):
+ parameters.pop("patch")
+ data = await client.patch(
+ url,
+ timeout=settings.get("api_function_timeout", 3),
+ json=parameters,
+ )
+ else:
+ data = await client.get(url)
+ except httpx.HTTPError:
+ pass
+ else:
+ if data.status_code == 200:
+ try:
+ return data.json()
+ except json.decoder.JSONDecodeError:
+ pass
+
+ async def multicommand(
+ self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
+ ) -> dict:
+ """Execute multiple commands simultaneously on the BitAxe miner.
+
+ Args:
+ *commands (str): Commands to execute.
+ ignore_errors (bool): Whether to ignore errors during command execution.
+ allow_warning (bool): Whether to proceed despite warnings.
+
+ Returns:
+ dict: A dictionary containing responses for all commands executed.
+ """
+ tasks = {}
+ # send all commands individually
+ for cmd in commands:
+ tasks[cmd] = asyncio.create_task(
+ self.send_command(cmd, allow_warning=allow_warning)
+ )
+
+ await asyncio.gather(*[tasks[cmd] for cmd in tasks], return_exceptions=True)
+
+ data = {"multicommand": True}
+ for cmd in tasks:
+ try:
+ result = tasks[cmd].result()
+ if result is None or result == {}:
+ result = {}
+ data[cmd] = result
+ except APIError:
+ pass
+
+ return data
+
+ async def system_info(self):
+ return await self.send_command("system/info")
+
+ async def swarm_info(self):
+ return await self.send_command("swarm/info")
+
+ async def restart(self):
+ return await self.send_command("system/restart", post=True)
+
+ async def update_settings(self, **config):
+ return await self.send_command("system", patch=True, **config)
diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py
index 8f0e1b41..4a72ec93 100644
--- a/pyasic/web/epic.py
+++ b/pyasic/web/epic.py
@@ -15,9 +15,12 @@
# ------------------------------------------------------------------------------
from __future__ import annotations
+import hashlib
import json
+from pathlib import Path
from typing import Any
+import aiofiles
import httpx
from pyasic import settings
@@ -46,6 +49,14 @@ class ePICWebAPI(BaseWebAPI):
async with httpx.AsyncClient(transport=settings.transport()) as client:
for retry_cnt in range(settings.get("get_data_retries", 1)):
try:
+ if parameters.get("form") is not None:
+ form_data = parameters["form"]
+ form_data.add_field("password", self.pwd)
+ response = await client.post(
+ f"http://{self.ip}:{self.port}/{command}",
+ timeout=5,
+ data=form_data,
+ )
if post:
response = await client.post(
f"http://{self.ip}:{self.port}/{command}",
@@ -135,3 +146,22 @@ class ePICWebAPI(BaseWebAPI):
async def capabilities(self) -> dict:
return await self.send_command("capabilities")
+
+ async def system_update(self, file: Path | str, keep_settings: bool = True):
+ """Perform a system update by uploading a firmware file and sending a
+ command to initiate the update."""
+
+ # calculate the SHA256 checksum of the firmware file
+ sha256_hash = hashlib.sha256()
+ async with aiofiles.open(str(file), "rb") as f:
+ while chunk := await f.read(8192):
+ sha256_hash.update(chunk)
+ checksum = sha256_hash.hexdigest()
+
+ # prepare the multipart/form-data request
+ form_data = aiohttp.FormData()
+ form_data.add_field("checksum", checksum)
+ form_data.add_field("keepsettings", str(keep_settings).lower())
+ form_data.add_field("update.zip", open(file, "rb"), filename="update.zip")
+
+ await self.send_command("systemupdate", form=form_data)
diff --git a/pyproject.toml b/pyproject.toml
index 47937b3d..2de66080 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "pyasic"
-version = "0.57.6"
+version = "0.58.1"
description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData "]
repository = "https://github.com/UpstreamData/pyasic"
diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py
index 6beb5c49..523271a2 100644
--- a/tests/miners_tests/__init__.py
+++ b/tests/miners_tests/__init__.py
@@ -55,7 +55,6 @@ class MinersTest(unittest.TestCase):
"expected_hashrate",
"uptime",
"wattage",
- "voltage",
"wattage_limit",
"pools",
]