Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82f6d2f274 | ||
|
|
833de3ab43 | ||
|
|
08d273c7c1 | ||
|
|
aac7598187 | ||
|
|
5c0ac4e665 | ||
|
|
7bd5e49412 | ||
|
|
53ff3c5f79 | ||
|
|
36ae6e5272 | ||
|
|
fb3dffb216 | ||
|
|
ff6a6d2ec6 | ||
|
|
f4bbc2c3e5 | ||
|
|
4dbfdbe29c | ||
|
|
5e2a18f91e | ||
|
|
3363bdc592 |
@@ -17,31 +17,31 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>X19 Series:</summary>
|
<summary>X19 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X19#s19-bos">S19</a></li>
|
<li><a href="../antminer/X19#s19-bos">S19</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19-pro-bos">S19 Pro</a></li>
|
<li><a href="../antminer/X19#s19-pro-bos">S19 Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19j-bos">S19j</a></li>
|
<li><a href="../antminer/X19#s19j-bos">S19j</a></li>
|
||||||
<li><a href="/miners/antminer/X19#s19j-pro-bos">S19j Pro</a></li>
|
<li><a href="../antminer/X19#s19j-pro-bos">S19j Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X19#t19-bos">T19</a></li>
|
<li><a href="../antminer/X19#t19-bos">T19</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X17 Series:</summary>
|
<summary>X17 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X17#s17-bos">S17</a></li>
|
<li><a href="../antminer/X17#s17-bos">S17</a></li>
|
||||||
<li><a href="/miners/antminer/X17#s17-plus-bos">S17+</a></li>
|
<li><a href="../antminer/X17#s17-plus-bos">S17+</a></li>
|
||||||
<li><a href="/miners/antminer/X17#s17-pro-bos">S17 Pro</a></li>
|
<li><a href="../antminer/X17#s17-pro-bos">S17 Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X17#s17e-bos">S17e</a></li>
|
<li><a href="../antminer/X17#s17e-bos">S17e</a></li>
|
||||||
<li><a href="/miners/antminer/X17#t17-bos">T17</a></li>
|
<li><a href="../antminer/X17#t17-bos">T17</a></li>
|
||||||
<li><a href="/miners/antminer/X17#t17-plus-bos">T17+</a></li>
|
<li><a href="../antminer/X17#t17-plus-bos">T17+</a></li>
|
||||||
<li><a href="/miners/antminer/X17#t17e-bos">T17e</a></li>
|
<li><a href="../antminer/X17#t17e-bos">T17e</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X9 Series:</summary>
|
<summary>X9 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X9#s9-bos">S9</a></li>
|
<li><a href="../antminer/X9#s9-bos">S9</a></li>
|
||||||
<li><a href="/miners/antminer/X9#s9-bos">S9i</a></li>
|
<li><a href="../antminer/X9#s9-bos">S9i</a></li>
|
||||||
<li><a href="/miners/antminer/X9#s9-bos">S9j</a></li>
|
<li><a href="../antminer/X9#s9-bos">S9j</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -50,55 +50,64 @@ details {
|
|||||||
<summary>Stock Firmware Whatsminers:</summary>
|
<summary>Stock Firmware Whatsminers:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<details>
|
||||||
|
<summary>M5X Series:</summary>
|
||||||
|
<ul>
|
||||||
|
<details>
|
||||||
|
<summary><a href="../whatsminer/M5X/#m50">M50</a></summary>
|
||||||
|
<ul>
|
||||||
|
<li><a href="../whatsminer/M5X/#m50vh50">VH50</a></li>
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</ul>
|
||||||
<summary>M3X Series:</summary>
|
<summary>M3X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s">M30S</a></summary>
|
<summary><a href="../whatsminer/M3X/#m30s">M30S</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sve10">VE10</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve10">VE10</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg20">VG20</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg20">VG20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sve20">VE20</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve20">VE20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sv50">V50</a></li>
|
<li><a href="../whatsminer/M3X/#m30sv50">V50</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
<summary><a href="../whatsminer/M3X/#m30s_1">M30S+</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svf20">VF20</a></li>
|
<li><a href="../whatsminer/M3X/#m30svf20">VF20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30sve40">VE40</a></li>
|
<li><a href="../whatsminer/M3X/#m30sve40">VE40</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg60">VG60</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg60">VG60</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m30s_2">M30S++</a></summary>
|
<summary><a href="../whatsminer/M3X/#m30s_2">M30S++</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg30">VG30</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg30">VG30</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svg40">VG40</a></li>
|
<li><a href="../whatsminer/M3X/#m30svg40">VG40</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m30svh60">VH60</a></li>
|
<li><a href="../whatsminer/M3X/#m30svh60">VH60</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m31s">M31S</a></summary>
|
<summary><a href="../whatsminer/M3X/#m31s">M31S</a></summary>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m31s_1">M31S+</a></summary>
|
<summary><a href="../whatsminer/M3X/#m31s_1">M31S+</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sve20">VE20</a></li>
|
<li><a href="../whatsminer/M3X/#m31sve20">VE20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv30">V30</a></li>
|
<li><a href="../whatsminer/M3X/#m31sv30">V30</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv40">V40</a></li>
|
<li><a href="../whatsminer/M3X/#m31sv40">V40</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv60">V60</a></li>
|
<li><a href="../whatsminer/M3X/#m31sv60">V60</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv80">V80</a></li>
|
<li><a href="../whatsminer/M3X/#m31sv80">V80</a></li>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m31sv90">V90</a></li>
|
<li><a href="../whatsminer/M3X/#m31sv90">V90</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m32">M32</a></summary>
|
<summary><a href="../whatsminer/M3X/#m32">M32</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M3X/#m32v20">V20</a></li>
|
<li><a href="../whatsminer/M3X/#m32v20">V20</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M3X/#m32s">M32S</a></summary>
|
<summary><a href="../whatsminer/M3X/#m32s">M32S</a></summary>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -106,33 +115,33 @@ details {
|
|||||||
<summary>M2X Series:</summary>
|
<summary>M2X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20">M20</a></summary>
|
<summary><a href="../whatsminer/M2X/#m20">M20</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m20v10">V10</a></li>
|
<li><a href="../whatsminer/M2X/#m20v10">V10</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20s">M20S</a></summary>
|
<summary><a href="../whatsminer/M2X/#m20s">M20S</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m20sv10">V10</a></li>
|
<li><a href="../whatsminer/M2X/#m20sv10">V10</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m20sv20">V20</a></li>
|
<li><a href="../whatsminer/M2X/#m20sv20">V20</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m20s_1">M20S+</a></summary>
|
<summary><a href="../whatsminer/M2X/#m20s_1">M20S+</a></summary>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21">M21</a></summary>
|
<summary><a href="../whatsminer/M2X/#m21">M21</a></summary>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21s">M21S</a></summary>
|
<summary><a href="../whatsminer/M2X/#m21s">M21S</a></summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m21sv20">V20</a></li>
|
<li><a href="../whatsminer/M2X/#m21sv20">V20</a></li>
|
||||||
<li><a href="/miners/whatsminer/M2X/#m21sv60">V60</a></li>
|
<li><a href="../whatsminer/M2X/#m21sv60">V60</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary><a href="/miners/whatsminer/M2X/#m21s_1">M21S+</a></summary>
|
<summary><a href="../whatsminer/M2X/#m21s_1">M21S+</a></summary>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
@@ -144,33 +153,33 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>X19 Series:</summary>
|
<summary>X19 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X19/#s19">S19</a></li>
|
<li><a href="../antminer/X19/#s19">S19</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19-pro">S19 Pro</a></li>
|
<li><a href="../antminer/X19/#s19-pro">S19 Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19a">S19a</a></li>
|
<li><a href="../antminer/X19/#s19a">S19a</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19j">S19j</a></li>
|
<li><a href="../antminer/X19/#s19j">S19j</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19j-pro">S19j Pro</a></li>
|
<li><a href="../antminer/X19/#s19j-pro">S19j Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#s19-xp">S19 XP</a></li>
|
<li><a href="../antminer/X19/#s19-xp">S19 XP</a></li>
|
||||||
<li><a href="/miners/antminer/X19/#t19">T19</a></li>
|
<li><a href="../antminer/X19/#t19">T19</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X17 Series:</summary>
|
<summary>X17 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X17/#s17">S17</a></li>
|
<li><a href="../antminer/X17/#s17">S17</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17_1">S17+</a></li>
|
<li><a href="../antminer/X17/#s17_1">S17+</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17-pro">S17 Pro</a></li>
|
<li><a href="../antminer/X17/#s17-pro">S17 Pro</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#s17e">S17e</a></li>
|
<li><a href="../antminer/X17/#s17e">S17e</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17">T17</a></li>
|
<li><a href="../antminer/X17/#t17">T17</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17_1">T17+</a></li>
|
<li><a href="../antminer/X17/#t17_1">T17+</a></li>
|
||||||
<li><a href="/miners/antminer/X17/#t17e">T17e</a></li>
|
<li><a href="../antminer/X17/#t17e">T17e</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>X9 Series:</summary>
|
<summary>X9 Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/antminer/X9/#s9">S9</a></li>
|
<li><a href="../antminer/X9/#s9">S9</a></li>
|
||||||
<li><a href="/miners/antminer/X9/#s9i">S9i</a></li>
|
<li><a href="../antminer/X9/#s9i">S9i</a></li>
|
||||||
<li><a href="/miners/antminer/X9/#t9">T9</a></li>
|
<li><a href="../antminer/X9/#t9">T9</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -181,31 +190,31 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>A7X Series:</summary>
|
<summary>A7X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/avalonminer/A7X/#a721">A721</a></li>
|
<li><a href="../avalonminer/A7X/#a721">A721</a></li>
|
||||||
<li><a href="/miners/avalonminer/A7X/#a741">A741</a></li>
|
<li><a href="../avalonminer/A7X/#a741">A741</a></li>
|
||||||
<li><a href="/miners/avalonminer/A7X/#a761">A761</a></li>
|
<li><a href="../avalonminer/A7X/#a761">A761</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>A8X Series:</summary>
|
<summary>A8X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/avalonminer/A8X/#a821">A821</a></li>
|
<li><a href="../avalonminer/A8X/#a821">A821</a></li>
|
||||||
<li><a href="/miners/avalonminer/A8X/#a841">A841</a></li>
|
<li><a href="../avalonminer/A8X/#a841">A841</a></li>
|
||||||
<li><a href="/miners/avalonminer/A8X/#a851">A851</a></li>
|
<li><a href="../avalonminer/A8X/#a851">A851</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>A9X Series:</summary>
|
<summary>A9X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/avalonminer/A9X/#a921">A921</a></li>
|
<li><a href="../avalonminer/A9X/#a921">A921</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
<details>
|
<details>
|
||||||
<summary>A10X Series:</summary>
|
<summary>A10X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/avalonminer/A10X/#a1026">A1026</a></li>
|
<li><a href="../avalonminer/A10X/#a1026">A1026</a></li>
|
||||||
<li><a href="/miners/avalonminer/A10X/#a1047">A1047</a></li>
|
<li><a href="../avalonminer/A10X/#a1047">A1047</a></li>
|
||||||
<li><a href="/miners/avalonminer/A10X/#a1066">A1066</a></li>
|
<li><a href="../avalonminer/A10X/#a1066">A1066</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -216,7 +225,7 @@ details {
|
|||||||
<details>
|
<details>
|
||||||
<summary>T3X Series:</summary>
|
<summary>T3X Series:</summary>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="/miners/innosilicon/T3X/#t3h">T3H+</a></li>
|
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
18
docs/miners/whatsminer/M5X.md
Normal file
18
docs/miners/whatsminer/M5X.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# pyasic
|
||||||
|
## M5X Models
|
||||||
|
|
||||||
|
## M50
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M50VH50
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -21,6 +21,7 @@ nav:
|
|||||||
- Avalon 10X: "miners/avalonminer/A10X.md"
|
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||||
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||||
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||||
|
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||||
- Network:
|
- Network:
|
||||||
- Miner Network: "network/miner_network.md"
|
- Miner Network: "network/miner_network.md"
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ipaddress
|
|||||||
import warnings
|
import warnings
|
||||||
import logging
|
import logging
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
import re
|
||||||
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
|
|
||||||
@@ -46,8 +47,8 @@ class BaseMinerAPI:
|
|||||||
# each function in self
|
# each function in self
|
||||||
dir(self)
|
dir(self)
|
||||||
if callable(getattr(self, func)) and
|
if callable(getattr(self, func)) and
|
||||||
# no __ methods
|
# no __ or _ methods
|
||||||
not func.startswith("__") and
|
not func.startswith("__") and not func.startswith("_") and
|
||||||
# remove all functions that are in this base class
|
# remove all functions that are in this base class
|
||||||
func
|
func
|
||||||
not in [
|
not in [
|
||||||
@@ -71,14 +72,11 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
)
|
)
|
||||||
return return_commands
|
return return_commands
|
||||||
|
|
||||||
async def multicommand(
|
async def multicommand(self, *commands: str) -> dict:
|
||||||
self, *commands: str, ignore_x19_error: bool = False
|
|
||||||
) -> dict:
|
|
||||||
"""Creates and sends multiple commands as one command to the miner.
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
*commands: The commands to send as a multicommand to the miner.
|
*commands: The commands to send as a multicommand to the miner.
|
||||||
ignore_x19_error: Whether or not to ignore errors raised by x19 miners when using the "+" delimited style.
|
|
||||||
"""
|
"""
|
||||||
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||||
# make sure we can actually run each command, otherwise they will fail
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
@@ -87,26 +85,44 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
# doesnt work for S19 which uses the backup _x19_multicommand
|
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||||
command = "+".join(commands)
|
command = "+".join(commands)
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
data = await self.send_command(command)
|
||||||
except APIError:
|
except APIError:
|
||||||
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
return {}
|
||||||
data = await self._x19_multicommand(*command.split("+"))
|
|
||||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands):
|
async def _send_bytes(self, data: bytes) -> bytes:
|
||||||
data = None
|
|
||||||
try:
|
try:
|
||||||
data = {}
|
# get reader and writer streams
|
||||||
# send all commands individually
|
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||||
for cmd in commands:
|
# handle OSError 121
|
||||||
data[cmd] = []
|
except OSError as e:
|
||||||
data[cmd].append(await self.send_command(cmd, x19_command=True))
|
if e.winerror == "121":
|
||||||
except APIError as e:
|
logging.warning("Semaphore Timeout has Expired.")
|
||||||
raise APIError(e)
|
return b"{}"
|
||||||
|
|
||||||
|
# send the command
|
||||||
|
writer.write(data)
|
||||||
|
await writer.drain()
|
||||||
|
|
||||||
|
# instantiate data
|
||||||
|
ret_data = b""
|
||||||
|
|
||||||
|
# loop to receive all the data
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
d = await reader.read(4096)
|
||||||
|
if not d:
|
||||||
|
break
|
||||||
|
ret_data += d
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
logging.warning(f"{self.ip}: API Command Error: - {e}")
|
||||||
return data
|
|
||||||
|
# close the connection
|
||||||
|
writer.close()
|
||||||
|
await writer.wait_closed()
|
||||||
|
|
||||||
|
return ret_data
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
@@ -126,54 +142,32 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
Returns:
|
Returns:
|
||||||
The return data from the API command parsed from JSON into a dict.
|
The return data from the API command parsed from JSON into a dict.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
# get reader and writer streams
|
|
||||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
|
||||||
# handle OSError 121
|
|
||||||
except OSError as e:
|
|
||||||
if e.winerror == "121":
|
|
||||||
logging.warning("Semaphore Timeout has Expired.")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# create the command
|
# create the command
|
||||||
cmd = {"command": command}
|
cmd = {"command": command}
|
||||||
if parameters:
|
if parameters:
|
||||||
cmd["parameter"] = parameters
|
cmd["parameter"] = parameters
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
writer.write(json.dumps(cmd).encode("utf-8"))
|
data = await self._send_bytes(json.dumps(cmd).encode("utf-8"))
|
||||||
await writer.drain()
|
|
||||||
|
|
||||||
# instantiate data
|
|
||||||
data = b""
|
|
||||||
|
|
||||||
# loop to receive all the data
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
d = await reader.read(4096)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
data += d
|
|
||||||
except Exception as e:
|
|
||||||
logging.warning(f"{self.ip}: API Command Error: - {e}")
|
|
||||||
|
|
||||||
data = self._load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# close the connection
|
|
||||||
writer.close()
|
|
||||||
await writer.wait_closed()
|
|
||||||
|
|
||||||
# check for if the user wants to allow errors to return
|
# check for if the user wants to allow errors to return
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# validate the command succeeded
|
# validate the command succeeded
|
||||||
validation = self._validate_command_output(data)
|
validation = self._validate_command_output(data)
|
||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
if not x19_command:
|
if not x19_command:
|
||||||
logging.warning(f"{self.ip}: API Command Error: {validation[1]}")
|
logging.warning(
|
||||||
|
f"{self.ip}: API Command Error: {command}: {validation[1]}"
|
||||||
|
)
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def send_privileged_command(self, *args, **kwargs) -> dict:
|
||||||
|
return await self.send_command(*args, **kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate_command_output(data: dict) -> tuple:
|
def _validate_command_output(data: dict) -> tuple:
|
||||||
# check if the data returned is correct or an error
|
# check if the data returned is correct or an error
|
||||||
@@ -204,31 +198,39 @@ If you are sure you want to use this command please use API.send_command("{comma
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _load_api_data(data: bytes) -> dict:
|
def _load_api_data(data: bytes) -> dict:
|
||||||
str_data = None
|
str_data = None
|
||||||
|
# some json from the API returns with a null byte (\x00) on the end
|
||||||
|
if data.endswith(b"\x00"):
|
||||||
|
# handle the null byte
|
||||||
|
str_data = data.decode("utf-8")[:-1]
|
||||||
|
else:
|
||||||
|
# no null byte
|
||||||
|
str_data = data.decode("utf-8")
|
||||||
|
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace(",}", "}")
|
||||||
|
# fix an error with a btminer return having a newline that breaks json.loads()
|
||||||
|
str_data = str_data.replace("\n", "")
|
||||||
|
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace("}{", "},{")
|
||||||
|
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||||
|
str_data = str_data.replace("[,{", "[{")
|
||||||
|
# fix an error with Avalonminers returning inf and nan
|
||||||
|
str_data = str_data.replace("inf", "0")
|
||||||
|
str_data = str_data.replace("nan", "0")
|
||||||
|
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||||
|
if str_data.startswith(","):
|
||||||
|
str_data = f"{{{str_data[1:]}"
|
||||||
|
# try to fix an error with overflowing the receive buffer
|
||||||
|
# this can happen in cases such as bugged btminers returning arbitrary length error info with 100s of errors.
|
||||||
|
if not str_data.endswith("}"):
|
||||||
|
str_data = ",".join(str_data.split(",")[:-1]) + "}"
|
||||||
|
|
||||||
|
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
|
||||||
|
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
|
||||||
|
str_data = str_data.replace("[", "{").replace("]", "}")
|
||||||
|
|
||||||
|
# parse the json
|
||||||
try:
|
try:
|
||||||
# some json from the API returns with a null byte (\x00) on the end
|
|
||||||
if data.endswith(b"\x00"):
|
|
||||||
# handle the null byte
|
|
||||||
str_data = data.decode("utf-8")[:-1]
|
|
||||||
else:
|
|
||||||
# no null byte
|
|
||||||
str_data = data.decode("utf-8")
|
|
||||||
# fix an error with a btminer return having an extra comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace(",}", "}")
|
|
||||||
# fix an error with a btminer return having a newline that breaks json.loads()
|
|
||||||
str_data = str_data.replace("\n", "")
|
|
||||||
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace("}{", "},{")
|
|
||||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
|
||||||
str_data = str_data.replace("[,{", "[{")
|
|
||||||
# fix an error with Avalonminers returning inf and nan
|
|
||||||
str_data = str_data.replace("inf", "0")
|
|
||||||
str_data = str_data.replace("nan", "0")
|
|
||||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
|
||||||
if str_data.startswith(","):
|
|
||||||
str_data = f"{{{str_data[1:]}"
|
|
||||||
# parse the json
|
|
||||||
parsed_data = json.loads(str_data)
|
parsed_data = json.loads(str_data)
|
||||||
# handle bad json
|
|
||||||
except json.decoder.JSONDecodeError as e:
|
except json.decoder.JSONDecodeError as e:
|
||||||
raise APIError(f"Decode Error {e}: {str_data}")
|
raise APIError(f"Decode Error {e}: {str_data}")
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|||||||
@@ -11,8 +11,10 @@
|
|||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import logging
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI
|
from pyasic.API import BaseMinerAPI
|
||||||
|
from pyasic.API import APIError
|
||||||
|
|
||||||
|
|
||||||
class BMMinerAPI(BaseMinerAPI):
|
class BMMinerAPI(BaseMinerAPI):
|
||||||
@@ -36,6 +38,37 @@ class BMMinerAPI(BaseMinerAPI):
|
|||||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
def __init__(self, ip: str, port: int = 4028) -> None:
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
|
|
||||||
|
async def multicommand(
|
||||||
|
self, *commands: str, ignore_x19_error: bool = False
|
||||||
|
) -> dict:
|
||||||
|
logging.debug(f"{self.ip}: Sending multicommand: {[*commands]}")
|
||||||
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
|
commands = self._check_commands(*commands)
|
||||||
|
# standard multicommand format is "command1+command2"
|
||||||
|
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||||
|
command = "+".join(commands)
|
||||||
|
try:
|
||||||
|
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||||
|
except APIError:
|
||||||
|
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||||
|
data = await self._x19_multicommand(*command.split("+"))
|
||||||
|
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def _x19_multicommand(self, *commands):
|
||||||
|
data = None
|
||||||
|
try:
|
||||||
|
data = {}
|
||||||
|
# send all commands individually
|
||||||
|
for cmd in commands:
|
||||||
|
data[cmd] = []
|
||||||
|
data[cmd].append(await self.send_command(cmd, x19_command=True))
|
||||||
|
except APIError as e:
|
||||||
|
raise APIError(e)
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
||||||
|
return data
|
||||||
|
|
||||||
async def version(self) -> dict:
|
async def version(self) -> dict:
|
||||||
"""Get miner version info.
|
"""Get miner version info.
|
||||||
<details>
|
<details>
|
||||||
|
|||||||
@@ -187,57 +187,24 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
self.pwd = pwd
|
self.pwd = pwd
|
||||||
self.current_token = None
|
self.current_token = None
|
||||||
|
|
||||||
async def send_command(
|
async def send_privileged_command(
|
||||||
self,
|
self, command: Union[str, bytes], ignore_errors: bool = False, **kwargs
|
||||||
command: Union[str, bytes],
|
|
||||||
parameters: Union[str, int, bool] = None,
|
|
||||||
ignore_errors: bool = False,
|
|
||||||
**kwargs,
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
# check if command is a string
|
command = {"cmd": command}
|
||||||
# if its bytes its encoded and needs to be sent raw
|
for kwarg in kwargs:
|
||||||
if isinstance(command, str):
|
if kwargs[kwarg]:
|
||||||
# if it is a string, put it into the standard command format
|
command[kwarg] = kwargs[kwarg]
|
||||||
command = json.dumps({"command": command}).encode("utf-8")
|
|
||||||
try:
|
|
||||||
# get reader and writer streams
|
|
||||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
|
||||||
# handle OSError 121
|
|
||||||
except OSError as e:
|
|
||||||
if e.winerror == "121":
|
|
||||||
print("Semaphore Timeout has Expired.")
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# send the command
|
token_data = await self.get_token()
|
||||||
writer.write(command)
|
enc_command = create_privileged_cmd(token_data, command)
|
||||||
await writer.drain()
|
|
||||||
|
|
||||||
# instantiate data
|
|
||||||
data = b""
|
|
||||||
|
|
||||||
# loop to receive all the data
|
|
||||||
try:
|
|
||||||
while True:
|
|
||||||
d = await reader.read(4096)
|
|
||||||
if not d:
|
|
||||||
break
|
|
||||||
data += d
|
|
||||||
except Exception as e:
|
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
|
||||||
|
|
||||||
|
data = await self._send_bytes(enc_command)
|
||||||
data = self._load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# close the connection
|
try:
|
||||||
writer.close()
|
data = parse_btminer_priviledge_data(self.current_token, data)
|
||||||
await writer.wait_closed()
|
except Exception as e:
|
||||||
|
logging.info(f"{str(self.ip)}: {e}")
|
||||||
# check if the returned data is encoded
|
|
||||||
if "enc" in data.keys():
|
|
||||||
# try to parse the encoded data
|
|
||||||
try:
|
|
||||||
data = parse_btminer_priviledge_data(self.current_token, data)
|
|
||||||
except Exception as e:
|
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
|
||||||
|
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# if it fails to validate, it is likely an error
|
# if it fails to validate, it is likely an error
|
||||||
@@ -320,46 +287,18 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A dict from the API to confirm the pools were updated.
|
A dict from the API to confirm the pools were updated.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
# get the token and password from the miner
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"update_pools",
|
||||||
|
pool1=pool_1,
|
||||||
# parse pool data
|
worker1=worker_1,
|
||||||
if not pool_1:
|
passwd1=passwd_1,
|
||||||
raise APIError("No pools set.")
|
pool2=pool_2,
|
||||||
elif pool_2 and pool_3:
|
worker2=worker_2,
|
||||||
command = {
|
passwd2=passwd_2,
|
||||||
"cmd": "update_pools",
|
pool3=pool_3,
|
||||||
"pool1": pool_1,
|
worker3=worker_3,
|
||||||
"worker1": worker_1,
|
passwd3=passwd_3,
|
||||||
"passwd1": passwd_1,
|
)
|
||||||
"pool2": pool_2,
|
|
||||||
"worker2": worker_2,
|
|
||||||
"passwd2": passwd_2,
|
|
||||||
"pool3": pool_3,
|
|
||||||
"worker3": worker_3,
|
|
||||||
"passwd3": passwd_3,
|
|
||||||
}
|
|
||||||
elif pool_2:
|
|
||||||
command = {
|
|
||||||
"cmd": "update_pools",
|
|
||||||
"pool1": pool_1,
|
|
||||||
"worker1": worker_1,
|
|
||||||
"passwd1": passwd_1,
|
|
||||||
"pool2": pool_2,
|
|
||||||
"worker2": worker_2,
|
|
||||||
"passwd2": passwd_2,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
command = {
|
|
||||||
"cmd": "update_pools",
|
|
||||||
"pool1": pool_1,
|
|
||||||
"worker1": worker_1,
|
|
||||||
"passwd1": passwd_1,
|
|
||||||
}
|
|
||||||
# encode the command with the token data
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
# send the command
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def restart(self) -> dict:
|
async def restart(self) -> dict:
|
||||||
"""Restart BTMiner using the API.
|
"""Restart BTMiner using the API.
|
||||||
@@ -373,10 +312,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the restart.
|
A reply informing of the restart.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "restart_btminer"}
|
return await self.send_privileged_command("restart_btminer")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def power_off(self, respbefore: bool = True) -> dict:
|
async def power_off(self, respbefore: bool = True) -> dict:
|
||||||
"""Power off the miner using the API.
|
"""Power off the miner using the API.
|
||||||
@@ -393,12 +329,8 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
if respbefore:
|
if respbefore:
|
||||||
command = {"cmd": "power_off", "respbefore": "true"}
|
return await self.send_privileged_command("power_off", respbefore="true")
|
||||||
else:
|
return await self.send_privileged_command("power_off", respbefore="false")
|
||||||
command = {"cmd": "power_off", "respbefore": "false"}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def power_on(self) -> dict:
|
async def power_on(self) -> dict:
|
||||||
"""Power on the miner using the API.
|
"""Power on the miner using the API.
|
||||||
@@ -413,10 +345,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of powering on.
|
A reply informing of the status of powering on.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "power_on"}
|
return await self.send_privileged_command("power_on")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def reset_led(self) -> dict:
|
async def reset_led(self) -> dict:
|
||||||
"""Reset the LED on the miner using the API.
|
"""Reset the LED on the miner using the API.
|
||||||
@@ -431,10 +360,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of resetting the LED.
|
A reply informing of the status of resetting the LED.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_led", "param": "auto"}
|
return await self.set_led(auto=True)
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_led(
|
async def set_led(
|
||||||
self,
|
self,
|
||||||
@@ -462,19 +388,11 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of setting the LED.
|
A reply informing of the status of setting the LED.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
if not auto:
|
if auto:
|
||||||
command = {
|
return await self.send_privileged_command("set_led", param=auto)
|
||||||
"cmd": "set_led",
|
return await self.send_privileged_command(
|
||||||
"color": color,
|
"set_led", color=color, period=period, duration=duration, start=start
|
||||||
"period": period,
|
)
|
||||||
"duration": duration,
|
|
||||||
"start": start,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
command = {"cmd": "set_led", "param": "auto"}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command, ignore_errors=True)
|
|
||||||
|
|
||||||
async def set_low_power(self) -> dict:
|
async def set_low_power(self) -> dict:
|
||||||
"""Set low power mode on the miner using the API.
|
"""Set low power mode on the miner using the API.
|
||||||
@@ -489,10 +407,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of setting low power mode.
|
A reply informing of the status of setting low power mode.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_low_power"}
|
return await self.send_privileged_command("set_low_power")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def update_firmware(self): # noqa - static
|
async def update_firmware(self): # noqa - static
|
||||||
"""Not implemented."""
|
"""Not implemented."""
|
||||||
@@ -510,10 +425,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of the reboot.
|
A reply informing of the status of the reboot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "reboot"}
|
return await self.send_privileged_command("reboot")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def factory_reset(self) -> dict:
|
async def factory_reset(self) -> dict:
|
||||||
"""Reset the miner to factory defaults.
|
"""Reset the miner to factory defaults.
|
||||||
@@ -525,10 +437,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of the reset.
|
A reply informing of the status of the reset.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "factory_reset"}
|
return await self.send_privileged_command("factory_reset")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
||||||
"""Update the admin user's password.
|
"""Update the admin user's password.
|
||||||
@@ -555,11 +464,10 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"New password too long, the max length is 8. "
|
f"New password too long, the max length is 8. "
|
||||||
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
||||||
)
|
)
|
||||||
command = {"cmd": "update_pwd", "old": old_pwd, "new": new_pwd}
|
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(enc_command)
|
data = await self.send_privileged_command(
|
||||||
|
"update_pwd", old=old_pwd, new=new_pwd
|
||||||
|
)
|
||||||
except APIError as e:
|
except APIError as e:
|
||||||
raise e
|
raise e
|
||||||
self.pwd = new_pwd
|
self.pwd = new_pwd
|
||||||
@@ -588,10 +496,9 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"range. Please set a % between -10 and "
|
f"range. Please set a % between -10 and "
|
||||||
f"100"
|
f"100"
|
||||||
)
|
)
|
||||||
command = {"cmd": "set_target_freq", "percent": str(percent)}
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"set_target_freq", percent=str(percent)
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
)
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def enable_fast_boot(self) -> dict:
|
async def enable_fast_boot(self) -> dict:
|
||||||
"""Turn on fast boot.
|
"""Turn on fast boot.
|
||||||
@@ -607,10 +514,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of enabling fast boot.
|
A reply informing of the status of enabling fast boot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "enable_btminer_fast_boot"}
|
return await self.send_privileged_command("enable_btminer_fast_boot")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def disable_fast_boot(self) -> dict:
|
async def disable_fast_boot(self) -> dict:
|
||||||
"""Turn off fast boot.
|
"""Turn off fast boot.
|
||||||
@@ -626,10 +530,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of disabling fast boot.
|
A reply informing of the status of disabling fast boot.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "disable_btminer_fast_boot"}
|
return await self.send_privileged_command("disable_btminer_fast_boot")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def enable_web_pools(self) -> dict:
|
async def enable_web_pools(self) -> dict:
|
||||||
"""Turn on web pool updates.
|
"""Turn on web pool updates.
|
||||||
@@ -645,10 +546,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of enabling web pools.
|
A reply informing of the status of enabling web pools.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "enable_web_pools"}
|
return await self.send_privileged_command("enable_web_pools")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def disable_web_pools(self) -> dict:
|
async def disable_web_pools(self) -> dict:
|
||||||
"""Turn off web pool updates.
|
"""Turn off web pool updates.
|
||||||
@@ -664,10 +562,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of disabling web pools.
|
A reply informing of the status of disabling web pools.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "disable_web_pools"}
|
return await self.send_privileged_command("disable_web_pools")
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_hostname(self, hostname: str) -> dict:
|
async def set_hostname(self, hostname: str) -> dict:
|
||||||
"""Set the hostname of the miner.
|
"""Set the hostname of the miner.
|
||||||
@@ -685,10 +580,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
A reply informing of the status of setting the hostname.
|
A reply informing of the status of setting the hostname.
|
||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
command = {"cmd": "set_hostname", "hostname": hostname}
|
return await self.send_privileged_command("set_hostname", hostname=hostname)
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def set_power_pct(self, percent: int) -> dict:
|
async def set_power_pct(self, percent: int) -> dict:
|
||||||
"""Set the power percentage of the miner.
|
"""Set the power percentage of the miner.
|
||||||
@@ -713,10 +605,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
f"range. Please set a % between 0 and "
|
f"range. Please set a % between 0 and "
|
||||||
f"100"
|
f"100"
|
||||||
)
|
)
|
||||||
command = {"cmd": "set_power_pct", "percent": str(percent)}
|
return await self.send_privileged_command("set_power_pct", percent=str(percent))
|
||||||
token_data = await self.get_token()
|
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||||
"""Configure or check status of pre power on.
|
"""Configure or check status of pre power on.
|
||||||
@@ -747,13 +636,12 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
'"adjust continue"]'
|
'"adjust continue"]'
|
||||||
)
|
)
|
||||||
if complete:
|
if complete:
|
||||||
complete = "true"
|
return await self.send_privileged_command(
|
||||||
else:
|
"pre_power_on", complete="true", msg=msg
|
||||||
complete = "false"
|
)
|
||||||
command = {"cmd": "pre_power_on", "complete": complete, "msg": msg}
|
return await self.send_privileged_command(
|
||||||
token_data = await self.get_token()
|
"pre_power_on", complete="false", msg=msg
|
||||||
enc_command = create_privileged_cmd(token_data, command)
|
)
|
||||||
return await self.send_command(enc_command)
|
|
||||||
|
|
||||||
#### END privileged COMMANDS ####
|
#### END privileged COMMANDS ####
|
||||||
|
|
||||||
@@ -880,3 +768,17 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
</details>
|
</details>
|
||||||
"""
|
"""
|
||||||
return await self.send_command("get_miner_info")
|
return await self.send_command("get_miner_info")
|
||||||
|
|
||||||
|
async def get_error_code(self) -> dict:
|
||||||
|
"""Get a list of error codes from the miner.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Expand</summary>
|
||||||
|
Get a list of error codes from the miner. Replaced `summary` as the location of error codes with API version 2.0.4.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
|
||||||
|
A list of error codes on the miner.
|
||||||
|
</details>
|
||||||
|
"""
|
||||||
|
return await self.send_command("get_error_code")
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class MinerData:
|
|||||||
fan_2: The speed of the second fan as an int.
|
fan_2: The speed of the second fan as an int.
|
||||||
fan_3: The speed of the third fan as an int.
|
fan_3: The speed of the third fan as an int.
|
||||||
fan_4: The speed of the fourth fan as an int.
|
fan_4: The speed of the fourth fan as an int.
|
||||||
|
fan_psu: The speed of the PSU on the fan if the miner collects it.
|
||||||
left_chips: The number of chips online in the left board as an int.
|
left_chips: The number of chips online in the left board as an int.
|
||||||
center_chips: The number of chips online in the left board as an int.
|
center_chips: The number of chips online in the left board as an int.
|
||||||
right_chips: The number of chips online in the left board as an int.
|
right_chips: The number of chips online in the left board as an int.
|
||||||
@@ -76,19 +77,20 @@ class MinerData:
|
|||||||
center_board_hashrate: float = 0.0
|
center_board_hashrate: float = 0.0
|
||||||
right_board_hashrate: float = 0.0
|
right_board_hashrate: float = 0.0
|
||||||
temperature_avg: int = field(init=False)
|
temperature_avg: int = field(init=False)
|
||||||
env_temp: float = 0.0
|
env_temp: float = -1.0
|
||||||
left_board_temp: int = 0
|
left_board_temp: int = -1
|
||||||
left_board_chip_temp: int = 0
|
left_board_chip_temp: int = -1
|
||||||
center_board_temp: int = 0
|
center_board_temp: int = -1
|
||||||
center_board_chip_temp: int = 0
|
center_board_chip_temp: int = -1
|
||||||
right_board_temp: int = 0
|
right_board_temp: int = -1
|
||||||
right_board_chip_temp: int = 0
|
right_board_chip_temp: int = -1
|
||||||
wattage: int = 0
|
wattage: int = -1
|
||||||
wattage_limit: int = 0
|
wattage_limit: int = -1
|
||||||
fan_1: int = -1
|
fan_1: int = -1
|
||||||
fan_2: int = -1
|
fan_2: int = -1
|
||||||
fan_3: int = -1
|
fan_3: int = -1
|
||||||
fan_4: int = -1
|
fan_4: int = -1
|
||||||
|
fan_psu: int = -1
|
||||||
left_chips: int = 0
|
left_chips: int = 0
|
||||||
center_chips: int = 0
|
center_chips: int = 0
|
||||||
right_chips: int = 0
|
right_chips: int = 0
|
||||||
@@ -192,7 +194,7 @@ class MinerData:
|
|||||||
self.center_board_chip_temp,
|
self.center_board_chip_temp,
|
||||||
self.right_board_chip_temp,
|
self.right_board_chip_temp,
|
||||||
]:
|
]:
|
||||||
if temp and not temp == 0:
|
if temp and not temp == -1:
|
||||||
total_temp += temp
|
total_temp += temp
|
||||||
temp_count += 1
|
temp_count += 1
|
||||||
if not temp_count > 0:
|
if not temp_count > 0:
|
||||||
@@ -232,6 +234,11 @@ class MinerData:
|
|||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
|
|
||||||
def as_csv(self) -> str:
|
def as_csv(self) -> str:
|
||||||
|
"""Get this dataclass as CSV.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A CSV version of this class with no headers.
|
||||||
|
"""
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||||
errs = []
|
errs = []
|
||||||
|
|||||||
@@ -158,14 +158,29 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
async def get_errors(self) -> List[MinerErrorData]:
|
||||||
data = []
|
data = []
|
||||||
|
try:
|
||||||
|
err_data = await self.api.get_error_code()
|
||||||
|
if err_data:
|
||||||
|
if err_data.get("Msg"):
|
||||||
|
if err_data["Msg"].get("error_code"):
|
||||||
|
for err in err_data["Msg"]["error_code"]:
|
||||||
|
data.append(
|
||||||
|
WhatsminerError(
|
||||||
|
error_code=int(err)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except APIError:
|
||||||
|
summary_data = await self.api.summary()
|
||||||
|
if summary_data[0].get("Error Code Count"):
|
||||||
|
for i in range(summary_data[0]["Error Code Count"]):
|
||||||
|
if summary_data[0].get(f"Error Code {i}"):
|
||||||
|
if not summary_data[0][f"Error Code {i}"] == "":
|
||||||
|
data.append(
|
||||||
|
WhatsminerError(
|
||||||
|
error_code=summary_data[0][f"Error Code {i}"]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
summary_data = await self.api.summary()
|
|
||||||
if summary_data[0].get("Error Code Count"):
|
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
|
||||||
data.append(
|
|
||||||
WhatsminerError(error_code=summary_data[0][f"Error Code {i}"])
|
|
||||||
)
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
@@ -265,13 +280,20 @@ class BTMiner(BaseMiner):
|
|||||||
break
|
break
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not miner_data:
|
if not miner_data:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
summary = miner_data.get("summary")[0]
|
summary = miner_data.get("summary")[0]
|
||||||
devs = miner_data.get("devs")[0]
|
devs = miner_data.get("devs")[0]
|
||||||
pools = miner_data.get("pools")[0]
|
pools = miner_data.get("pools")[0]
|
||||||
|
try:
|
||||||
|
psu_data = await self.api.get_psu()
|
||||||
|
except APIError:
|
||||||
|
psu_data = None
|
||||||
|
try:
|
||||||
|
err_data = await self.api.get_error_code()
|
||||||
|
except APIError:
|
||||||
|
err_data = None
|
||||||
|
|
||||||
if summary:
|
if summary:
|
||||||
summary_data = summary.get("SUMMARY")
|
summary_data = summary.get("SUMMARY")
|
||||||
@@ -287,6 +309,9 @@ class BTMiner(BaseMiner):
|
|||||||
if summary_data[0].get("Power Limit"):
|
if summary_data[0].get("Power Limit"):
|
||||||
wattage_limit = summary_data[0]["Power Limit"]
|
wattage_limit = summary_data[0]["Power Limit"]
|
||||||
|
|
||||||
|
if summary_data[0].get("Power Fanspeed"):
|
||||||
|
data.fan_psu = summary_data[0]["Power Fanspeed"]
|
||||||
|
|
||||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
data.fan_1 = summary_data[0]["Fan Speed In"]
|
||||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
||||||
|
|
||||||
@@ -306,11 +331,30 @@ class BTMiner(BaseMiner):
|
|||||||
if summary_data[0].get("Error Code Count"):
|
if summary_data[0].get("Error Code Count"):
|
||||||
for i in range(summary_data[0]["Error Code Count"]):
|
for i in range(summary_data[0]["Error Code Count"]):
|
||||||
if summary_data[0].get(f"Error Code {i}"):
|
if summary_data[0].get(f"Error Code {i}"):
|
||||||
data.errors.append(
|
if not summary_data[0][f"Error Code {i}"] == "":
|
||||||
WhatsminerError(
|
data.errors.append(
|
||||||
error_code=summary_data[0][f"Error Code {i}"]
|
WhatsminerError(
|
||||||
|
error_code=summary_data[0][
|
||||||
|
f"Error Code {i}"
|
||||||
|
]
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
if psu_data:
|
||||||
|
psu = psu_data.get("Msg")
|
||||||
|
if psu:
|
||||||
|
if psu.get("fan_speed"):
|
||||||
|
data.fan_psu = psu["fan_speed"]
|
||||||
|
|
||||||
|
if err_data:
|
||||||
|
if err_data.get("Msg"):
|
||||||
|
if err_data["Msg"].get("error_code"):
|
||||||
|
for err in err_data["Msg"]["error_code"]:
|
||||||
|
data.errors.append(
|
||||||
|
WhatsminerError(
|
||||||
|
error_code=int(err)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
if devs:
|
if devs:
|
||||||
temp_data = devs.get("DEVS")
|
temp_data = devs.get("DEVS")
|
||||||
|
|||||||
33
pyasic/miners/_types/whatsminer/M5X/M50.py
Normal file
33
pyasic/miners/_types/whatsminer/M5X/M50.py
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from pyasic.miners.base import BaseMiner
|
||||||
|
|
||||||
|
|
||||||
|
class M50(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M50"
|
||||||
|
self.nominal_chips = 105
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M50VH50(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M50 VH50"
|
||||||
|
self.nominal_chips = 105
|
||||||
|
self.fan_count = 2
|
||||||
15
pyasic/miners/_types/whatsminer/M5X/__init__.py
Normal file
15
pyasic/miners/_types/whatsminer/M5X/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from .M50 import M50, M50VH50
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
|
|
||||||
from .M2X import *
|
from .M2X import *
|
||||||
from .M3X import *
|
from .M3X import *
|
||||||
|
from .M5X import *
|
||||||
@@ -216,6 +216,11 @@ MINER_CLASSES = {
|
|||||||
"BTMiner": BTMinerM32,
|
"BTMiner": BTMinerM32,
|
||||||
"20": BTMinerM32V20,
|
"20": BTMinerM32V20,
|
||||||
},
|
},
|
||||||
|
"M50": {
|
||||||
|
"Default": BTMinerM50,
|
||||||
|
"BTMiner": BTMinerM50,
|
||||||
|
"H50": BTMinerM50VH50,
|
||||||
|
},
|
||||||
"AVALONMINER 721": {
|
"AVALONMINER 721": {
|
||||||
"Default": CGMinerAvalon721,
|
"Default": CGMinerAvalon721,
|
||||||
"CGMiner": CGMinerAvalon721,
|
"CGMiner": CGMinerAvalon721,
|
||||||
|
|||||||
28
pyasic/miners/whatsminer/btminer/M5X/M50.py
Normal file
28
pyasic/miners/whatsminer/btminer/M5X/M50.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
||||||
|
from pyasic.miners._types import M50, M50VH50 # noqa - Ignore access to _module
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM50(BTMiner, M50):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM50VH50(BTMiner, M50VH50):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
18
pyasic/miners/whatsminer/btminer/M5X/__init__.py
Normal file
18
pyasic/miners/whatsminer/btminer/M5X/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Copyright 2022 Upstream Data Inc
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from .M50 import (
|
||||||
|
BTMinerM50,
|
||||||
|
BTMinerM50VH50,
|
||||||
|
)
|
||||||
@@ -14,3 +14,4 @@
|
|||||||
|
|
||||||
from .M2X import *
|
from .M2X import *
|
||||||
from .M3X import *
|
from .M3X import *
|
||||||
|
from .M5X import *
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.18.4"
|
version = "0.19.0"
|
||||||
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
description = "A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH."
|
||||||
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
authors = ["UpstreamData <brett@upstreamdata.ca>"]
|
||||||
repository = "https://github.com/UpstreamData/pyasic"
|
repository = "https://github.com/UpstreamData/pyasic"
|
||||||
|
|||||||
Reference in New Issue
Block a user