Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ae6ebfb2 | ||
|
|
e65cb0573d | ||
|
|
f8590b0c5f | ||
|
|
43b4992cee | ||
|
|
98e2cfae84 | ||
|
|
cb01c1a8ee | ||
|
|
36a273ec2b | ||
|
|
6a0dc03b9d | ||
|
|
ce7b006c8f | ||
|
|
88cc05bcea | ||
|
|
ae749f4a90 | ||
|
|
36b30a2cdd | ||
|
|
ae9f103578 | ||
|
|
13b583b739 | ||
|
|
aaf0d7fa75 | ||
|
|
a8cbb6394e | ||
|
|
ca6980b1ad |
24
docs/API/api.md
Normal file
24
docs/API/api.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# pyasic
|
||||||
|
## Miner APIs
|
||||||
|
Each miner has a unique API that is used to communicate with it.
|
||||||
|
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||||
|
Each miner that is a subclass of `BaseMiner` should have an API linked to it as `Miner.api`.
|
||||||
|
|
||||||
|
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
||||||
|
|
||||||
|
BaseMinerAPI should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||||
|
Use these instead -
|
||||||
|
|
||||||
|
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||||
|
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||||
|
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||||
|
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||||
|
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## BaseMinerAPI
|
||||||
|
::: pyasic.API.BaseMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
7
docs/API/bmminer.md
Normal file
7
docs/API/bmminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## BMMinerAPI
|
||||||
|
::: pyasic.API.bmminer.BMMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
7
docs/API/bosminer.md
Normal file
7
docs/API/bosminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## BOSMinerAPI
|
||||||
|
::: pyasic.API.bosminer.BOSMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
7
docs/API/btminer.md
Normal file
7
docs/API/btminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## BTMinerAPI
|
||||||
|
::: pyasic.API.btminer.BTMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
7
docs/API/cgminer.md
Normal file
7
docs/API/cgminer.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## CGMinerAPI
|
||||||
|
::: pyasic.API.cgminer.CGMinerAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
7
docs/API/unknown.md
Normal file
7
docs/API/unknown.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# pyasic
|
||||||
|
## UnknownAPI
|
||||||
|
::: pyasic.API.unknown.UnknownAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
42
docs/api.md
42
docs/api.md
@@ -1,42 +0,0 @@
|
|||||||
# pyasic
|
|
||||||
## Miner APIs
|
|
||||||
Each miner has a unique API that is used to communicate with it.
|
|
||||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
|
||||||
Each miner that is a subclass of `BaseMiner` should have an API linked to it as `Miner.api`.
|
|
||||||
|
|
||||||
All API implementations inherit from `BaseMinerAPI`, which implements the basic communications protocols.
|
|
||||||
|
|
||||||
## BMMinerAPI
|
|
||||||
::: pyasic.API.bmminer.BMMinerAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## BOSMinerAPI
|
|
||||||
::: pyasic.API.bosminer.BOSMinerAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## BTMinerAPI
|
|
||||||
::: pyasic.API.btminer.BTMinerAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## CGMinerAPI
|
|
||||||
::: pyasic.API.cgminer.CGMinerAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
|
|
||||||
## UnknownAPI
|
|
||||||
::: pyasic.API.unknown.UnknownAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
show_root_heading: false
|
|
||||||
heading_level: 4
|
|
||||||
24
docs/config/miner_config.md
Normal file
24
docs/config/miner_config.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# pyasic
|
||||||
|
## Miner Config
|
||||||
|
|
||||||
|
::: pyasic.config.MinerConfig
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## Pool Groups
|
||||||
|
|
||||||
|
::: pyasic.config._PoolGroup
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## Pools
|
||||||
|
|
||||||
|
::: pyasic.config._Pool
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
## Intro
|
## Intro
|
||||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
||||||
|
|
||||||
|
[Supported Miner Types](miners/supported_types.md)
|
||||||
|
|
||||||
Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|||||||
59
docs/miners/antminer/X17.md
Normal file
59
docs/miners/antminer/X17.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# pyasic
|
||||||
|
## X17 Models
|
||||||
|
|
||||||
|
## S17
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S17+
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S17 Pro
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S17e
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## T17
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## T17+
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|
||||||
|
## T17e
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
52
docs/miners/antminer/X19.md
Normal file
52
docs/miners/antminer/X19.md
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
# pyasic
|
||||||
|
## X19 Models
|
||||||
|
|
||||||
|
## S19
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19 Pro
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|
||||||
|
## S19a
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|
||||||
|
## S19j
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S19j Pro
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## T19
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
26
docs/miners/antminer/X9.md
Normal file
26
docs/miners/antminer/X9.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# pyasic
|
||||||
|
## X9 Models
|
||||||
|
|
||||||
|
## S9
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## S9i
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## T9
|
||||||
|
|
||||||
|
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
26
docs/miners/avalonminer/A10X.md
Normal file
26
docs/miners/avalonminer/A10X.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# pyasic
|
||||||
|
## A10X Models
|
||||||
|
|
||||||
|
## A1026
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A1047
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A1066
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
26
docs/miners/avalonminer/A7X.md
Normal file
26
docs/miners/avalonminer/A7X.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# pyasic
|
||||||
|
## A7X Models
|
||||||
|
|
||||||
|
## A721
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A741
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A761
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
26
docs/miners/avalonminer/A8X.md
Normal file
26
docs/miners/avalonminer/A8X.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# pyasic
|
||||||
|
## A8X Models
|
||||||
|
|
||||||
|
## A821
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A841
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## A851
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
10
docs/miners/avalonminer/A9X.md
Normal file
10
docs/miners/avalonminer/A9X.md
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
# pyasic
|
||||||
|
## A9X Models
|
||||||
|
|
||||||
|
## A921
|
||||||
|
|
||||||
|
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/bmminer.md
Normal file
8
docs/miners/backends/bmminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## BMMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners._backends.bmminer.BMMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/bosminer.md
Normal file
8
docs/miners/backends/bosminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## BOSMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners._backends.bosminer.BOSMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/btminer.md
Normal file
8
docs/miners/backends/btminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## BTMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners._backends.btminer.BTMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/cgminer.md
Normal file
8
docs/miners/backends/cgminer.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## CGMiner Backend
|
||||||
|
|
||||||
|
::: pyasic.miners._backends.cgminer.CGMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
8
docs/miners/backends/hiveon.md
Normal file
8
docs/miners/backends/hiveon.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# pyasic
|
||||||
|
## Hiveon Backend
|
||||||
|
|
||||||
|
::: pyasic.miners._backends.hiveon.Hiveon
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
73
docs/miners/supported_types.md
Normal file
73
docs/miners/supported_types.md
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# pyasic
|
||||||
|
## Supported Miners
|
||||||
|
|
||||||
|
Supported miner types are here on this list. If your miner (or miner version) is not on this list, please feel free to [open an issue on GitHub](https://github.com/UpstreamData/pyasic/issues) to get it added.
|
||||||
|
|
||||||
|
## Miner List
|
||||||
|
|
||||||
|
##### pyasic currently supports the following miners and subtypes:
|
||||||
|
* Braiins OS+ Devices:
|
||||||
|
* All devices supported by BraiinsOS+ are supported here.
|
||||||
|
* Stock Firmware Whatsminers:
|
||||||
|
* M3X Series:
|
||||||
|
* [M30S][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S]:
|
||||||
|
* [VE10][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10]
|
||||||
|
* [VG20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20]
|
||||||
|
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20]
|
||||||
|
* [V50][pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50]
|
||||||
|
* [M30S+][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlus]:
|
||||||
|
* [VF20][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20]
|
||||||
|
* [VE40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40]
|
||||||
|
* [VG60][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60]
|
||||||
|
* [M30S++][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus]:
|
||||||
|
* [VG30][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30]
|
||||||
|
* [VG40][pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40]
|
||||||
|
* [M31S][pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S]
|
||||||
|
* [M31S+][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus]:
|
||||||
|
* [VE20][pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20]
|
||||||
|
* [M32S][pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S]
|
||||||
|
* M2X Series:
|
||||||
|
* [M20S][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S]:
|
||||||
|
* [V10][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10]
|
||||||
|
* [V20][pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20]
|
||||||
|
* [M20S+][pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus]
|
||||||
|
* [M21][pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21]
|
||||||
|
* [M21S][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S]:
|
||||||
|
* [V20][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20]
|
||||||
|
* [V60][pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60]
|
||||||
|
* [M21S+][pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus]
|
||||||
|
* Stock Firmware Antminers:
|
||||||
|
* X19 Series:
|
||||||
|
* [S19][pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19]
|
||||||
|
* [S19 Pro][pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro]
|
||||||
|
* [S19a][pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a]
|
||||||
|
* [S19j][pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j]
|
||||||
|
* [S19j Pro][pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro]
|
||||||
|
* [T19][pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19]
|
||||||
|
* X17 Series:
|
||||||
|
* [S17][pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17]
|
||||||
|
* [S17+][pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus]
|
||||||
|
* [S17 Pro][pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro]
|
||||||
|
* [S17e][pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e]
|
||||||
|
* [T17][pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17]
|
||||||
|
* [T17+][pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus]
|
||||||
|
* [T17e][pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e]
|
||||||
|
* X9 Series:
|
||||||
|
* [S9][pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9]
|
||||||
|
* [S9i][pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i]
|
||||||
|
* [T9][pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9]
|
||||||
|
* Stock Firmware Avalonminers:
|
||||||
|
* A7X Series:
|
||||||
|
* [A721][pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721]
|
||||||
|
* [A741][pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741]
|
||||||
|
* [A761][pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761]
|
||||||
|
* A8X Series:
|
||||||
|
* [A821][pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821]
|
||||||
|
* [A841][pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841]
|
||||||
|
* [A851][pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851]
|
||||||
|
* A9X Series:
|
||||||
|
* [A921][pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921]
|
||||||
|
* A10X Series:
|
||||||
|
* [A1026][pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026]
|
||||||
|
* [A1047][pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047]
|
||||||
|
* [A1066][pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066]
|
||||||
75
docs/miners/whatsminer/M2X.md
Normal file
75
docs/miners/whatsminer/M2X.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# pyasic
|
||||||
|
## M2X Models
|
||||||
|
|
||||||
|
## M20S
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20S
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20SV10
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20SV20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M20S+
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|
||||||
|
## M21S
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21S
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21SV20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21SV60
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M21S+
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
131
docs/miners/whatsminer/M3X.md
Normal file
131
docs/miners/whatsminer/M3X.md
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
# pyasic
|
||||||
|
## M3X Models
|
||||||
|
|
||||||
|
## M30S
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30S
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30SVE10
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE10
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30SVG20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVG20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30SVE20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SVE20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30SV50
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S.BTMinerM30SV50
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VF20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVF20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VE40
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVE40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VG60
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus.BTMinerM30SPlusVG60
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S++
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S++VG30
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG30
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M30S+VG40
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M30S_Plus_Plus.BTMinerM30SPlusPlusVG40
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
|
||||||
|
## M31S
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31S.BTMinerM31S
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M31S+
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlus
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M31S+VE20
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M31S_Plus.BTMinerM31SPlusVE20
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## M32S
|
||||||
|
|
||||||
|
::: pyasic.miners.whatsminer.btminer.M3X.M32S.BTMinerM32S
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
12
docs/network/miner_network_range.md
Normal file
12
docs/network/miner_network_range.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# pyasic
|
||||||
|
## Miner Network Range
|
||||||
|
|
||||||
|
`MinerNetworkRange` is a class used by [`MinerNetwork`][pyasic.network.MinerNetwork] to handle any constructor stings.
|
||||||
|
The goal is to emulate what is produced by `ipaddress.ip_network` by allowing [`MinerNetwork`][pyasic.network.MinerNetwork] to get a list of hosts.
|
||||||
|
This allows this class to be the [`MinerNetwork.network`][pyasic.network.MinerNetwork] and hence be used for scanning.
|
||||||
|
|
||||||
|
::: pyasic.network.net_range.MinerNetworkRange
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
40
mkdocs.yml
40
mkdocs.yml
@@ -2,12 +2,42 @@ site_name: pyasic
|
|||||||
repo_url: https://github.com/UpstreamData/pyasic
|
repo_url: https://github.com/UpstreamData/pyasic
|
||||||
nav:
|
nav:
|
||||||
- Introduction: "index.md"
|
- Introduction: "index.md"
|
||||||
- Usage:
|
- Miners:
|
||||||
- Miner Factory: "miner_factory.md"
|
- Supported Miners: "miners/supported_types.md"
|
||||||
- Miner Network: "miner_network.md"
|
- Miner Factory: "miners/miner_factory.md"
|
||||||
- Miner Data: "miner_data.md"
|
- Backends:
|
||||||
|
- BMMiner: "miners/backends/bmminer.md"
|
||||||
|
- BOSMiner: "miners/backends/bosminer.md"
|
||||||
|
- BTMiner: "miners/backends/btminer.md"
|
||||||
|
- CGMiner: "miners/backends/cgminer.md"
|
||||||
|
- Hiveon: "miners/backends/hiveon.md"
|
||||||
|
|
||||||
|
- Classes:
|
||||||
|
- Antminer X9: "miners/antminer/X9.md"
|
||||||
|
- Antminer X17: "miners/antminer/X17.md"
|
||||||
|
- Antminer X19: "miners/antminer/X19.md"
|
||||||
|
- Avalon 7X: "miners/avalonminer/A7X.md"
|
||||||
|
- Avalon 8X: "miners/avalonminer/A8X.md"
|
||||||
|
- Avalon 9X: "miners/avalonminer/A9X.md"
|
||||||
|
- Avalon 10X: "miners/avalonminer/A10X.md"
|
||||||
|
- Whatsminer M2X: "miners/whatsminer/M2X.md"
|
||||||
|
- Whatsminer M3X: "miners/whatsminer/M3X.md"
|
||||||
|
|
||||||
|
- Network:
|
||||||
|
- Miner Network: "network/miner_network.md"
|
||||||
|
- Miner Network Range: "network/miner_network_range.md"
|
||||||
|
- Data:
|
||||||
|
- Miner Data: "data/miner_data.md"
|
||||||
|
- Config:
|
||||||
|
- Miner Config: "config/miner_config.md"
|
||||||
- Advanced:
|
- Advanced:
|
||||||
- API: "api.md"
|
- Miner APIs:
|
||||||
|
- Base: "API/api.md"
|
||||||
|
- BMMiner: "API/bmminer.md"
|
||||||
|
- BOSMiner: "API/bosminer.md"
|
||||||
|
- BTMiner: "API/btminer.md"
|
||||||
|
- CGMiner: "API/cgminer.md"
|
||||||
|
- Unknown: "API/unknown.md"
|
||||||
|
|
||||||
plugins:
|
plugins:
|
||||||
- mkdocstrings
|
- mkdocstrings
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import json
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import warnings
|
import warnings
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
class APIError(Exception):
|
class APIError(Exception):
|
||||||
@@ -41,7 +42,11 @@ class BaseMinerAPI:
|
|||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
|
|
||||||
def get_commands(self) -> list:
|
def get_commands(self) -> list:
|
||||||
"""Get a list of command accessible to a specific type of API on the miner."""
|
"""Get a list of command accessible to a specific type of API on the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of all API commands that the miner supports.
|
||||||
|
"""
|
||||||
return [
|
return [
|
||||||
func
|
func
|
||||||
for func in
|
for func in
|
||||||
@@ -59,51 +64,75 @@ class BaseMinerAPI:
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def _check_commands(self, *commands):
|
||||||
|
allowed_commands = self.get_commands()
|
||||||
|
return_commands = []
|
||||||
|
for command in [*commands]:
|
||||||
|
if command in allowed_commands:
|
||||||
|
return_commands.append(command)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
f"""Removing incorrect command: {command}
|
||||||
|
If you are sure you want to use this command please use API.send_command("{command}", ignore_errors=True) instead.""",
|
||||||
|
APIWarning,
|
||||||
|
)
|
||||||
|
return return_commands
|
||||||
|
|
||||||
async def multicommand(
|
async def multicommand(
|
||||||
self, *commands: str, ignore_x19_error: bool = False
|
self, *commands: str, ignore_x19_error: bool = False
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Creates and sends multiple commands as one command to the miner."""
|
"""Creates and sends multiple commands as one command to the miner.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
*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]}")
|
||||||
# split the commands into a proper list
|
# make sure we can actually run each command, otherwise they will fail
|
||||||
user_commands = [*commands]
|
commands = self._check_commands(*commands)
|
||||||
allowed_commands = self.get_commands()
|
|
||||||
# make sure we can actually run the command, otherwise it will fail
|
|
||||||
commands = [command for command in user_commands if command in allowed_commands]
|
|
||||||
for item in list(set(user_commands) - set(commands)):
|
|
||||||
warnings.warn(
|
|
||||||
f"""Removing incorrect command: {item}
|
|
||||||
If you are sure you want to use this command please use API.send_command("{item}", ignore_errors=True) instead.""",
|
|
||||||
APIWarning,
|
|
||||||
)
|
|
||||||
# standard multicommand format is "command1+command2"
|
# standard multicommand format is "command1+command2"
|
||||||
# doesnt work for S19 which is dealt with in the send command function
|
# doesnt work for S19 which uses the backup _x19_multicommand
|
||||||
command = "+".join(commands)
|
command = "+".join(commands)
|
||||||
data = None
|
|
||||||
try:
|
try:
|
||||||
data = await self.send_command(command, x19_command=ignore_x19_error)
|
data = await self.send_command(command, x19_command=ignore_x19_error)
|
||||||
except APIError:
|
except APIError:
|
||||||
try:
|
logging.debug(f"{self.ip}: Handling X19 multicommand.")
|
||||||
data = {}
|
data = await self._x19_multicommand(command.split("+"))
|
||||||
# S19 handler, try again
|
logging.debug(f"{self.ip}: Received multicommand data.")
|
||||||
for cmd in command.split("+"):
|
return data
|
||||||
data[cmd] = []
|
|
||||||
data[cmd].append(await self.send_command(cmd))
|
async def _x19_multicommand(self, *commands):
|
||||||
except APIError as e:
|
data = None
|
||||||
raise APIError(e)
|
try:
|
||||||
except Exception as e:
|
data = {}
|
||||||
logging.warning(f"{self.ip}: API Multicommand Error: {e}")
|
# send all commands individually
|
||||||
if data:
|
for cmd in commands:
|
||||||
logging.debug(f"{self.ip}: Received multicommand data.")
|
data[cmd] = []
|
||||||
return data
|
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.__name__} - {e}")
|
||||||
|
return data
|
||||||
|
|
||||||
async def send_command(
|
async def send_command(
|
||||||
self,
|
self,
|
||||||
command: str or bytes,
|
command: Union[str, bytes],
|
||||||
parameters: str or int or bool = None,
|
parameters: Union[str, int, bool] = None,
|
||||||
ignore_errors: bool = False,
|
ignore_errors: bool = False,
|
||||||
x19_command: bool = False,
|
x19_command: bool = False,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Send an API command to the miner and return the result."""
|
"""Send an API command to the miner and return the result.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
command: The command to sent to the miner.
|
||||||
|
parameters: Any additional parameters to be sent with the command.
|
||||||
|
ignore_errors: Whether or not to raise APIError when the command returns an error.
|
||||||
|
x19_command: Whether this is a command for an x19 that may be an issue (such as a "+" delimited multicommand)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The return data from the API command parsed from JSON into a dict.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
reader, writer = await asyncio.open_connection(str(self.ip), self.port)
|
||||||
@@ -115,7 +144,7 @@ If you are sure you want to use this command please use API.send_command("{item}
|
|||||||
|
|
||||||
# create the command
|
# create the command
|
||||||
cmd = {"command": command}
|
cmd = {"command": command}
|
||||||
if parameters is not None:
|
if parameters:
|
||||||
cmd["parameter"] = parameters
|
cmd["parameter"] = parameters
|
||||||
|
|
||||||
# send the command
|
# send the command
|
||||||
@@ -133,9 +162,9 @@ If you are sure you want to use this command please use API.send_command("{item}
|
|||||||
break
|
break
|
||||||
data += d
|
data += d
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self.ip}: API Command Error: {e}")
|
logging.warning(f"{self.ip}: API Command Error: {e.__name__} - {e}")
|
||||||
|
|
||||||
data = self.load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# close the connection
|
# close the connection
|
||||||
writer.close()
|
writer.close()
|
||||||
@@ -144,7 +173,7 @@ If you are sure you want to use this command please use API.send_command("{item}
|
|||||||
# 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: {validation[1]}")
|
||||||
@@ -153,8 +182,7 @@ If you are sure you want to use this command please use API.send_command("{item}
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def validate_command_output(data: dict) -> tuple:
|
def _validate_command_output(data: dict) -> tuple:
|
||||||
"""Check if the returned command output is correctly formatted."""
|
|
||||||
# check if the data returned is correct or an error
|
# check if the data returned is correct or an error
|
||||||
# if status isn't a key, it is a multicommand
|
# if status isn't a key, it is a multicommand
|
||||||
if "STATUS" not in data.keys():
|
if "STATUS" not in data.keys():
|
||||||
@@ -181,8 +209,7 @@ If you are sure you want to use this command please use API.send_command("{item}
|
|||||||
return True, None
|
return True, None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def load_api_data(data: bytes) -> dict:
|
def _load_api_data(data: bytes) -> dict:
|
||||||
"""Convert API data from JSON to dict"""
|
|
||||||
str_data = None
|
str_data = None
|
||||||
try:
|
try:
|
||||||
# some json from the API returns with a null byte (\x00) on the end
|
# some json from the API returns with a null byte (\x00) on the end
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from passlib.handlers.md5_crypt import md5_crypt
|
|||||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||||
|
|
||||||
from pyasic.API import BaseMinerAPI, APIError
|
from pyasic.API import BaseMinerAPI, APIError
|
||||||
from pyasic.settings import WHATSMINER_PWD
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
### IMPORTANT ###
|
### IMPORTANT ###
|
||||||
@@ -161,7 +161,12 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
pwd: The admin password of the miner. Default is admin.
|
pwd: The admin password of the miner. Default is admin.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip: str, port: int = 4028, pwd: str = WHATSMINER_PWD):
|
def __init__(
|
||||||
|
self,
|
||||||
|
ip: str,
|
||||||
|
port: int = 4028,
|
||||||
|
pwd: str = PyasicSettings().global_whatsminer_password,
|
||||||
|
):
|
||||||
super().__init__(ip, port)
|
super().__init__(ip, port)
|
||||||
self.admin_pwd = pwd
|
self.admin_pwd = pwd
|
||||||
self.current_token = None
|
self.current_token = None
|
||||||
@@ -204,7 +209,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.info(f"{str(self.ip)}: {e}")
|
logging.info(f"{str(self.ip)}: {e}")
|
||||||
|
|
||||||
data = self.load_api_data(data)
|
data = self._load_api_data(data)
|
||||||
|
|
||||||
# close the connection
|
# close the connection
|
||||||
writer.close()
|
writer.close()
|
||||||
@@ -220,7 +225,7 @@ class BTMinerAPI(BaseMinerAPI):
|
|||||||
|
|
||||||
if not ignore_errors:
|
if not ignore_errors:
|
||||||
# if it fails to validate, it is likely an error
|
# if it fails to validate, it is likely an error
|
||||||
validation = self.validate_command_output(data)
|
validation = self._validate_command_output(data)
|
||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from dataclasses import dataclass, asdict
|
from dataclasses import dataclass, asdict
|
||||||
from typing import List, Literal
|
from typing import Literal, List
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
|
|
||||||
@@ -13,9 +13,10 @@ import time
|
|||||||
class _Pool:
|
class _Pool:
|
||||||
"""A dataclass for pool information.
|
"""A dataclass for pool information.
|
||||||
|
|
||||||
:param url: URL of the pool.
|
Attributes:
|
||||||
:param username: Username on the pool.
|
url: URL of the pool.
|
||||||
:param password: Worker password on the pool.
|
username: Username on the pool.
|
||||||
|
password: Worker password on the pool.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
url: str = ""
|
url: str = ""
|
||||||
@@ -25,7 +26,8 @@ class _Pool:
|
|||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The raw config data to convert.
|
||||||
"""
|
"""
|
||||||
for key in data.keys():
|
for key in data.keys():
|
||||||
if key == "url":
|
if key == "url":
|
||||||
@@ -36,10 +38,11 @@ class _Pool:
|
|||||||
self.password = data[key]
|
self.password = data[key]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None):
|
def as_x19(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a dict usable by an X19 device.
|
"""Convert the data in this class to a dict usable by an X19 device.
|
||||||
|
|
||||||
:param user_suffix: The suffix to append to username.
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
username = self.username
|
username = self.username
|
||||||
if user_suffix:
|
if user_suffix:
|
||||||
@@ -48,7 +51,12 @@ class _Pool:
|
|||||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None):
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
|
"""Convert the data in this class to a string usable by an Avalonminer device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
username = self.username
|
username = self.username
|
||||||
if user_suffix:
|
if user_suffix:
|
||||||
username = f"{username}{user_suffix}"
|
username = f"{username}{user_suffix}"
|
||||||
@@ -56,10 +64,11 @@ class _Pool:
|
|||||||
pool = ",".join([self.url, username, self.password])
|
pool = ",".join([self.url, username, self.password])
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def as_bos(self, user_suffix: str = None):
|
def as_bos(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||||
|
|
||||||
:param user_suffix: The suffix to append to username.
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
username = self.username
|
username = self.username
|
||||||
if user_suffix:
|
if user_suffix:
|
||||||
@@ -73,9 +82,10 @@ class _Pool:
|
|||||||
class _PoolGroup:
|
class _PoolGroup:
|
||||||
"""A dataclass for pool group information.
|
"""A dataclass for pool group information.
|
||||||
|
|
||||||
:param quota: The group quota.
|
Attributes:
|
||||||
:param group_name: The name of the pool group.
|
quota: The group quota.
|
||||||
:param pools: A list of pools in this group.
|
group_name: The name of the pool group.
|
||||||
|
pools: A list of pools in this group.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
quota: int = 1
|
quota: int = 1
|
||||||
@@ -91,7 +101,8 @@ class _PoolGroup:
|
|||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The raw config data to convert.
|
||||||
"""
|
"""
|
||||||
pools = []
|
pools = []
|
||||||
for key in data.keys():
|
for key in data.keys():
|
||||||
@@ -105,24 +116,31 @@ class _PoolGroup:
|
|||||||
self.pools = pools
|
self.pools = pools
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None):
|
def as_x19(self, user_suffix: str = None) -> List[dict]:
|
||||||
"""Convert the data in this class to a dict usable by an X19 device.
|
"""Convert the data in this class to a list usable by an X19 device.
|
||||||
|
|
||||||
:param user_suffix: The suffix to append to username.
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
pools = []
|
pools = []
|
||||||
for pool in self.pools[:3]:
|
for pool in self.pools[:3]:
|
||||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||||
return pools
|
return pools
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None):
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
|
"""Convert the data in this class to a dict usable by an Avalonminer device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||||
return pool
|
return pool
|
||||||
|
|
||||||
def as_bos(self, user_suffix: str = None):
|
def as_bos(self, user_suffix: str = None) -> dict:
|
||||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||||
|
|
||||||
:param user_suffix: The suffix to append to username.
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
group = {
|
group = {
|
||||||
"name": self.group_name,
|
"name": self.group_name,
|
||||||
@@ -136,21 +154,22 @@ class _PoolGroup:
|
|||||||
class MinerConfig:
|
class MinerConfig:
|
||||||
"""A dataclass for miner configuration information.
|
"""A dataclass for miner configuration information.
|
||||||
|
|
||||||
:param pool_groups: A list of pool groups in this config.
|
Attributes:
|
||||||
:param temp_mode: The temperature control mode.
|
pool_groups: A list of pool groups in this config.
|
||||||
:param temp_target: The target temp.
|
temp_mode: The temperature control mode.
|
||||||
:param temp_hot: The hot temp (100% fans).
|
temp_target: The target temp.
|
||||||
:param temp_dangerous: The dangerous temp (shutdown).
|
temp_hot: The hot temp (100% fans).
|
||||||
:param minimum_fans: The minimum numbers of fans to run the miner.
|
temp_dangerous: The dangerous temp (shutdown).
|
||||||
:param fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
minimum_fans: The minimum numbers of fans to run the miner.
|
||||||
:param asicboost: Whether or not to enable asicboost.
|
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
||||||
:param autotuning_enabled: Whether or not to enable autotuning.
|
asicboost: Whether or not to enable asicboost.
|
||||||
:param autotuning_wattage: The wattage to use when autotuning.
|
autotuning_enabled: Whether or not to enable autotuning.
|
||||||
:param dps_enabled: Whether or not to enable dynamic power scaling.
|
autotuning_wattage: The wattage to use when autotuning.
|
||||||
:param dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
dps_enabled: Whether or not to enable dynamic power scaling.
|
||||||
:param dps_min_power: The minimum power to reduce autotuning to.
|
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
||||||
:param dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
dps_min_power: The minimum power to reduce autotuning to.
|
||||||
:param dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
||||||
|
dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pool_groups: List[_PoolGroup] = None
|
pool_groups: List[_PoolGroup] = None
|
||||||
@@ -174,27 +193,28 @@ class MinerConfig:
|
|||||||
dps_shutdown_enabled: bool = None
|
dps_shutdown_enabled: bool = None
|
||||||
dps_shutdown_duration: float = None
|
dps_shutdown_duration: float = None
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self) -> dict:
|
||||||
"""Convert the data in this class to a dict."""
|
"""Convert the data in this class to a dict."""
|
||||||
|
|
||||||
data_dict = asdict(self)
|
data_dict = asdict(self)
|
||||||
for key in asdict(self).keys():
|
for key in asdict(self).keys():
|
||||||
if data_dict[key] is None:
|
if data_dict[key] is None:
|
||||||
del data_dict[key]
|
del data_dict[key]
|
||||||
return data_dict
|
return data_dict
|
||||||
|
|
||||||
def as_toml(self):
|
def as_toml(self) -> str:
|
||||||
"""Convert the data in this class to toml."""
|
"""Convert the data in this class to toml."""
|
||||||
return toml.dumps(self.as_dict())
|
return toml.dumps(self.as_dict())
|
||||||
|
|
||||||
def as_yaml(self):
|
def as_yaml(self) -> str:
|
||||||
"""Convert the data in this class to yaml."""
|
"""Convert the data in this class to yaml."""
|
||||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||||
|
|
||||||
def from_raw(self, data: dict):
|
def from_raw(self, data: dict):
|
||||||
"""Convert raw config data as a dict to usable data and save it to this class.
|
"""Convert raw config data as a dict to usable data and save it to this class.
|
||||||
|
This should be able to handle any raw config file from any miner supported by pyasic.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The raw config data to convert.
|
||||||
"""
|
"""
|
||||||
pool_groups = []
|
pool_groups = []
|
||||||
for key in data.keys():
|
for key in data.keys():
|
||||||
@@ -256,7 +276,8 @@ class MinerConfig:
|
|||||||
def from_dict(self, data: dict):
|
def from_dict(self, data: dict):
|
||||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The dict config data to convert.
|
||||||
"""
|
"""
|
||||||
pool_groups = []
|
pool_groups = []
|
||||||
for group in data["pool_groups"]:
|
for group in data["pool_groups"]:
|
||||||
@@ -270,21 +291,24 @@ class MinerConfig:
|
|||||||
def from_toml(self, data: str):
|
def from_toml(self, data: str):
|
||||||
"""Convert output toml of this class back into usable data and save it to this class.
|
"""Convert output toml of this class back into usable data and save it to this class.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The toml config data to convert.
|
||||||
"""
|
"""
|
||||||
return self.from_dict(toml.loads(data))
|
return self.from_dict(toml.loads(data))
|
||||||
|
|
||||||
def from_yaml(self, data: str):
|
def from_yaml(self, data: str):
|
||||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
"""Convert output yaml of this class back into usable data and save it to this class.
|
||||||
|
|
||||||
:param data: The raw config data to convert.
|
Parameters:
|
||||||
|
data: The yaml config data to convert.
|
||||||
"""
|
"""
|
||||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||||
|
|
||||||
def as_x19(self, user_suffix: str = None) -> str:
|
def as_x19(self, user_suffix: str = None) -> str:
|
||||||
"""Convert the data in this class to a config usable by an X19 device.
|
"""Convert the data in this class to a config usable by an X19 device.
|
||||||
|
|
||||||
:param user_suffix: The suffix to append to username.
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
cfg = {
|
cfg = {
|
||||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||||
@@ -301,14 +325,20 @@ class MinerConfig:
|
|||||||
return json.dumps(cfg)
|
return json.dumps(cfg)
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> str:
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
cfg = self.pool_groups[0].as_avalon()
|
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
|
"""
|
||||||
|
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||||
return cfg
|
return cfg
|
||||||
|
|
||||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
||||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
"""Convert the data in this class to a config usable by an BOSMiner device.
|
||||||
|
|
||||||
:param model: The model of the miner to be used in the format portion of the config.
|
Parameters:
|
||||||
:param user_suffix: The suffix to append to username.
|
model: The model of the miner to be used in the format portion of the config.
|
||||||
|
user_suffix: The suffix to append to username.
|
||||||
"""
|
"""
|
||||||
cfg = {
|
cfg = {
|
||||||
"format": {
|
"format": {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from pyasic.settings import DEBUG, LOGFILE
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
def init_logger():
|
def init_logger():
|
||||||
if LOGFILE:
|
if PyasicSettings().logfile:
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
filename="logfile.txt",
|
filename="logfile.txt",
|
||||||
filemode="a",
|
filemode="a",
|
||||||
@@ -18,7 +18,7 @@ def init_logger():
|
|||||||
|
|
||||||
_logger = logging.getLogger()
|
_logger = logging.getLogger()
|
||||||
|
|
||||||
if DEBUG:
|
if PyasicSettings().debug:
|
||||||
_logger.setLevel(logging.DEBUG)
|
_logger.setLevel(logging.DEBUG)
|
||||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -54,13 +54,11 @@ class BaseMiner:
|
|||||||
)
|
)
|
||||||
return conn
|
return conn
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# logging.warning(f"{self} raised an exception: {e}")
|
|
||||||
raise e
|
raise e
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
logging.warning(f"Connection refused: {self}")
|
logging.warning(f"Connection refused: {self}")
|
||||||
raise e
|
raise e
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# logging.warning(f"{self} raised an exception: {e}")
|
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
from pyasic.API.bmminer import BMMinerAPI
|
||||||
@@ -7,10 +8,12 @@ from pyasic.miners import BaseMiner
|
|||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
|
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
class BMMiner(BaseMiner):
|
class BMMiner(BaseMiner):
|
||||||
|
"""Base handler for BMMiner based miners."""
|
||||||
|
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
@@ -19,10 +22,11 @@ class BMMiner(BaseMiner):
|
|||||||
self.uname = "root"
|
self.uname = "root"
|
||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
|
|
||||||
async def get_model(self) -> str or None:
|
async def get_model(self) -> Union[str, None]:
|
||||||
"""Get miner model.
|
"""Get miner model.
|
||||||
|
|
||||||
:return: Miner model or None.
|
Returns:
|
||||||
|
Miner model or None.
|
||||||
"""
|
"""
|
||||||
# check if model is cached
|
# check if model is cached
|
||||||
if self.model:
|
if self.model:
|
||||||
@@ -46,7 +50,8 @@ class BMMiner(BaseMiner):
|
|||||||
async def get_hostname(self) -> str:
|
async def get_hostname(self) -> str:
|
||||||
"""Get miner hostname.
|
"""Get miner hostname.
|
||||||
|
|
||||||
:return: The hostname of the miner as a string or "?"
|
Returns:
|
||||||
|
The hostname of the miner as a string or "?"
|
||||||
"""
|
"""
|
||||||
if self.hostname:
|
if self.hostname:
|
||||||
return self.hostname
|
return self.hostname
|
||||||
@@ -72,12 +77,14 @@ class BMMiner(BaseMiner):
|
|||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||||
"""Send a command to the miner over ssh.
|
"""Send a command to the miner over ssh.
|
||||||
|
|
||||||
:param cmd: The command to run.
|
Parameters:
|
||||||
|
cmd: The command to run.
|
||||||
|
|
||||||
:return: Result of the command or None.
|
Returns:
|
||||||
|
Result of the command or None.
|
||||||
"""
|
"""
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
@@ -101,10 +108,11 @@ class BMMiner(BaseMiner):
|
|||||||
# return the result, either command output or None
|
# return the result, either command output or None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def get_config(self) -> list or None:
|
async def get_config(self) -> Union[list, None]:
|
||||||
"""Get the pool configuration of the miner.
|
"""Get the pool configuration of the miner.
|
||||||
|
|
||||||
:return: Pool config data or None.
|
Returns:
|
||||||
|
Pool config data or None.
|
||||||
"""
|
"""
|
||||||
# get pool data
|
# get pool data
|
||||||
pools = await self.api.pools()
|
pools = await self.api.pools()
|
||||||
@@ -120,6 +128,11 @@ class BMMiner(BaseMiner):
|
|||||||
return pool_data
|
return pool_data
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
|
"""Reboot the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The result of rebooting the miner.
|
||||||
|
"""
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
logging.debug(f"{self}: Sending reboot command.")
|
||||||
_ret = await self.send_ssh_command("reboot")
|
_ret = await self.send_ssh_command("reboot")
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
logging.debug(f"{self}: Reboot command completed.")
|
||||||
@@ -128,6 +141,11 @@ class BMMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||||
|
"""
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||||
|
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
@@ -147,7 +165,7 @@ class BMMiner(BaseMiner):
|
|||||||
data.mac = mac
|
data.mac = mac
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"summary", "pools", "stats", ignore_x19_error=True
|
"summary", "pools", "stats", ignore_x19_error=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
import json
|
import json
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
import toml
|
import toml
|
||||||
|
|
||||||
@@ -14,7 +15,7 @@ from pyasic.data import MinerData
|
|||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
|
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
class BOSMiner(BaseMiner):
|
class BOSMiner(BaseMiner):
|
||||||
@@ -27,10 +28,11 @@ class BOSMiner(BaseMiner):
|
|||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||||
"""Send a command to the miner over ssh.
|
"""Send a command to the miner over ssh.
|
||||||
|
|
||||||
:return: Result of the command or None.
|
Returns:
|
||||||
|
Result of the command or None.
|
||||||
"""
|
"""
|
||||||
result = None
|
result = None
|
||||||
|
|
||||||
@@ -74,6 +76,7 @@ class BOSMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
|
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners._backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
|
||||||
return await self.restart_bosminer()
|
return await self.restart_bosminer()
|
||||||
|
|
||||||
async def restart_bosminer(self) -> bool:
|
async def restart_bosminer(self) -> bool:
|
||||||
@@ -94,7 +97,12 @@ class BOSMiner(BaseMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_config(self) -> None:
|
async def get_config(self) -> MinerConfig:
|
||||||
|
"""Gets the config for the miner and sets it as `self.config`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The config from `self.config`.
|
||||||
|
"""
|
||||||
logging.debug(f"{self}: Getting config.")
|
logging.debug(f"{self}: Getting config.")
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with (await self._get_ssh_connection()) as conn:
|
||||||
logging.debug(f"{self}: Opening SFTP connection.")
|
logging.debug(f"{self}: Opening SFTP connection.")
|
||||||
@@ -110,7 +118,8 @@ class BOSMiner(BaseMiner):
|
|||||||
async def get_hostname(self) -> str:
|
async def get_hostname(self) -> str:
|
||||||
"""Get miner hostname.
|
"""Get miner hostname.
|
||||||
|
|
||||||
:return: The hostname of the miner as a string or "?"
|
Returns:
|
||||||
|
The hostname of the miner as a string or "?"
|
||||||
"""
|
"""
|
||||||
if self.hostname:
|
if self.hostname:
|
||||||
return self.hostname
|
return self.hostname
|
||||||
@@ -129,10 +138,11 @@ class BOSMiner(BaseMiner):
|
|||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
async def get_model(self) -> str or None:
|
async def get_model(self) -> Union[str, None]:
|
||||||
"""Get miner model.
|
"""Get miner model.
|
||||||
|
|
||||||
:return: Miner model or None.
|
Returns:
|
||||||
|
Miner model or None.
|
||||||
"""
|
"""
|
||||||
# check if model is cached
|
# check if model is cached
|
||||||
if self.model:
|
if self.model:
|
||||||
@@ -166,10 +176,11 @@ class BOSMiner(BaseMiner):
|
|||||||
logging.warning(f"Failed to get model for miner: {self}")
|
logging.warning(f"Failed to get model for miner: {self}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_version(self):
|
async def get_version(self) -> Union[str, None]:
|
||||||
"""Get miner firmware version.
|
"""Get miner firmware version.
|
||||||
|
|
||||||
:return: Miner firmware version or None.
|
Returns:
|
||||||
|
Miner firmware version or None.
|
||||||
"""
|
"""
|
||||||
# check if version is cached
|
# check if version is cached
|
||||||
if self.version:
|
if self.version:
|
||||||
@@ -206,65 +217,21 @@ class BOSMiner(BaseMiner):
|
|||||||
.as_bos(model=self.model.replace(" (BOS)", ""))
|
.as_bos(model=self.model.replace(" (BOS)", ""))
|
||||||
)
|
)
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with (await self._get_ssh_connection()) as conn:
|
||||||
|
await conn.run("/etc/init.d/bosminer stop")
|
||||||
logging.debug(f"{self}: Opening SFTP connection.")
|
logging.debug(f"{self}: Opening SFTP connection.")
|
||||||
async with conn.start_sftp_client() as sftp:
|
async with conn.start_sftp_client() as sftp:
|
||||||
logging.debug(f"{self}: Opening config file.")
|
logging.debug(f"{self}: Opening config file.")
|
||||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||||
await file.write(toml_conf)
|
await file.write(toml_conf)
|
||||||
logging.debug(f"{self}: Restarting BOSMiner")
|
logging.debug(f"{self}: Restarting BOSMiner")
|
||||||
await conn.run("/etc/init.d/bosminer restart")
|
await conn.run("/etc/init.d/bosminer start")
|
||||||
|
|
||||||
async def get_board_info(self) -> dict:
|
|
||||||
"""Gets data on each board and chain in the miner."""
|
|
||||||
logging.debug(f"{self}: Getting board info.")
|
|
||||||
devdetails = await self.api.devdetails()
|
|
||||||
if not devdetails.get("DEVDETAILS"):
|
|
||||||
print("devdetails error", devdetails)
|
|
||||||
return {0: [], 1: [], 2: []}
|
|
||||||
devs = devdetails["DEVDETAILS"]
|
|
||||||
boards = {}
|
|
||||||
offset = devs[0]["ID"]
|
|
||||||
for board in devs:
|
|
||||||
boards[board["ID"] - offset] = []
|
|
||||||
if not board["Chips"] == self.nominal_chips:
|
|
||||||
nominal = False
|
|
||||||
else:
|
|
||||||
nominal = True
|
|
||||||
boards[board["ID"] - offset].append(
|
|
||||||
{
|
|
||||||
"chain": board["ID"] - offset,
|
|
||||||
"chip_count": board["Chips"],
|
|
||||||
"chip_status": "o" * board["Chips"],
|
|
||||||
"nominal": nominal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
logging.debug(f"Found board data for {self}: {boards}")
|
|
||||||
return boards
|
|
||||||
|
|
||||||
async def get_bad_boards(self) -> dict:
|
|
||||||
"""Checks for and provides list of non working boards."""
|
|
||||||
boards = await self.get_board_info()
|
|
||||||
bad_boards = {}
|
|
||||||
for board in boards.keys():
|
|
||||||
for chain in boards[board]:
|
|
||||||
if not chain["chip_count"] == 63:
|
|
||||||
if board not in bad_boards.keys():
|
|
||||||
bad_boards[board] = []
|
|
||||||
bad_boards[board].append(chain)
|
|
||||||
return bad_boards
|
|
||||||
|
|
||||||
async def check_good_boards(self) -> str:
|
|
||||||
"""Checks for and provides list for working boards."""
|
|
||||||
devs = await self.api.devdetails()
|
|
||||||
bad = 0
|
|
||||||
chains = devs["DEVDETAILS"]
|
|
||||||
for chain in chains:
|
|
||||||
if chain["Chips"] == 0:
|
|
||||||
bad += 1
|
|
||||||
if not bad > 0:
|
|
||||||
return str(self.ip)
|
|
||||||
|
|
||||||
async def get_data(self) -> MinerData:
|
async def get_data(self) -> MinerData:
|
||||||
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||||
|
"""
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||||
|
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
@@ -284,7 +251,7 @@ class BOSMiner(BaseMiner):
|
|||||||
data.mac = mac
|
data.mac = mac
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
try:
|
try:
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"summary",
|
"summary",
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
from pyasic.API.btminer import BTMinerAPI
|
||||||
@@ -9,7 +10,7 @@ from pyasic.API import APIError
|
|||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.data.error_codes import WhatsminerError
|
from pyasic.data.error_codes import WhatsminerError
|
||||||
|
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
class BTMiner(BaseMiner):
|
class BTMiner(BaseMiner):
|
||||||
@@ -19,7 +20,12 @@ class BTMiner(BaseMiner):
|
|||||||
self.api = BTMinerAPI(ip)
|
self.api = BTMinerAPI(ip)
|
||||||
self.api_type = "BTMiner"
|
self.api_type = "BTMiner"
|
||||||
|
|
||||||
async def get_model(self):
|
async def get_model(self) -> Union[str, None]:
|
||||||
|
"""Get miner model.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner model or None.
|
||||||
|
"""
|
||||||
if self.model:
|
if self.model:
|
||||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||||
return self.model
|
return self.model
|
||||||
@@ -31,7 +37,12 @@ class BTMiner(BaseMiner):
|
|||||||
logging.warning(f"Failed to get model for miner: {self}")
|
logging.warning(f"Failed to get model for miner: {self}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_hostname(self) -> str or None:
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
|
"""Get miner hostname.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hostname of the miner as a string or None.
|
||||||
|
"""
|
||||||
if self.hostname:
|
if self.hostname:
|
||||||
return self.hostname
|
return self.hostname
|
||||||
try:
|
try:
|
||||||
@@ -48,38 +59,12 @@ class BTMiner(BaseMiner):
|
|||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_board_info(self) -> dict:
|
async def get_mac(self) -> str:
|
||||||
"""Gets data on each board and chain in the miner."""
|
"""Get the mac address of the miner.
|
||||||
logging.debug(f"{self}: Getting board info.")
|
|
||||||
devs = await self.api.devs()
|
|
||||||
if not devs.get("DEVS"):
|
|
||||||
print("devs error", devs)
|
|
||||||
return {0: [], 1: [], 2: []}
|
|
||||||
devs = devs["DEVS"]
|
|
||||||
boards = {}
|
|
||||||
offset = devs[0]["ID"]
|
|
||||||
for board in devs:
|
|
||||||
boards[board["ID"] - offset] = []
|
|
||||||
if "Effective Chips" in board.keys():
|
|
||||||
if not board["Effective Chips"] in self.nominal_chips:
|
|
||||||
nominal = False
|
|
||||||
else:
|
|
||||||
nominal = True
|
|
||||||
boards[board["ID"] - offset].append(
|
|
||||||
{
|
|
||||||
"chain": board["ID"] - offset,
|
|
||||||
"chip_count": board["Effective Chips"],
|
|
||||||
"chip_status": "o" * board["Effective Chips"],
|
|
||||||
"nominal": nominal,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
logging.warning(f"Incorrect board data from {self}: {board}")
|
|
||||||
print(board)
|
|
||||||
logging.debug(f"Found board data for {self}: {boards}")
|
|
||||||
return boards
|
|
||||||
|
|
||||||
async def get_mac(self):
|
Returns:
|
||||||
|
The mac address of the miner as a string.
|
||||||
|
"""
|
||||||
mac = ""
|
mac = ""
|
||||||
data = await self.api.summary()
|
data = await self.api.summary()
|
||||||
if data:
|
if data:
|
||||||
@@ -100,7 +85,12 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return str(mac).upper()
|
return str(mac).upper()
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self) -> MinerData:
|
||||||
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||||
|
"""
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||||
|
|
||||||
mac = None
|
mac = None
|
||||||
@@ -126,7 +116,7 @@ class BTMiner(BaseMiner):
|
|||||||
data.hostname = hostname
|
data.hostname = hostname
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
try:
|
try:
|
||||||
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
||||||
if miner_data:
|
if miner_data:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Union
|
||||||
|
|
||||||
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
from pyasic.API.cgminer import CGMinerAPI
|
||||||
@@ -8,7 +9,7 @@ from pyasic.API import APIError
|
|||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
|
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
|
|
||||||
|
|
||||||
class CGMiner(BaseMiner):
|
class CGMiner(BaseMiner):
|
||||||
@@ -21,7 +22,12 @@ class CGMiner(BaseMiner):
|
|||||||
self.pwd = "admin"
|
self.pwd = "admin"
|
||||||
self.config = None
|
self.config = None
|
||||||
|
|
||||||
async def get_model(self):
|
async def get_model(self) -> Union[str, None]:
|
||||||
|
"""Get miner model.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Miner model or None.
|
||||||
|
"""
|
||||||
if self.model:
|
if self.model:
|
||||||
return self.model
|
return self.model
|
||||||
try:
|
try:
|
||||||
@@ -33,7 +39,12 @@ class CGMiner(BaseMiner):
|
|||||||
return self.model
|
return self.model
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_hostname(self) -> str or None:
|
async def get_hostname(self) -> Union[str, None]:
|
||||||
|
"""Get miner hostname.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The hostname of the miner as a string or "?"
|
||||||
|
"""
|
||||||
if self.hostname:
|
if self.hostname:
|
||||||
return self.hostname
|
return self.hostname
|
||||||
try:
|
try:
|
||||||
@@ -48,7 +59,15 @@ class CGMiner(BaseMiner):
|
|||||||
except Exception:
|
except Exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd):
|
async def send_ssh_command(self, cmd: str) -> Union[str, None]:
|
||||||
|
"""Send a command to the miner over ssh.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
cmd: The command to run.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Result of the command or None.
|
||||||
|
"""
|
||||||
result = None
|
result = None
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with (await self._get_ssh_connection()) as conn:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
@@ -63,9 +82,11 @@ class CGMiner(BaseMiner):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
|
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners._backends.cgminer.CGMiner.restart_cgminer] to standardize."""
|
||||||
return await self.restart_cgminer()
|
return await self.restart_cgminer()
|
||||||
|
|
||||||
async def restart_cgminer(self) -> bool:
|
async def restart_cgminer(self) -> bool:
|
||||||
|
"""Restart cgminer hashing process."""
|
||||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
||||||
commands = ";".join(commands)
|
commands = ";".join(commands)
|
||||||
_ret = await self.send_ssh_command(commands)
|
_ret = await self.send_ssh_command(commands)
|
||||||
@@ -74,6 +95,7 @@ class CGMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
|
"""Reboots power to the physical miner."""
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
logging.debug(f"{self}: Sending reboot command.")
|
||||||
_ret = await self.send_ssh_command("reboot")
|
_ret = await self.send_ssh_command("reboot")
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
logging.debug(f"{self}: Reboot command completed.")
|
||||||
@@ -82,6 +104,7 @@ class CGMiner(BaseMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def start_cgminer(self) -> None:
|
async def start_cgminer(self) -> None:
|
||||||
|
"""Start cgminer hashing process."""
|
||||||
commands = [
|
commands = [
|
||||||
"mkdir -p /etc/tmp/",
|
"mkdir -p /etc/tmp/",
|
||||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
||||||
@@ -92,6 +115,7 @@ class CGMiner(BaseMiner):
|
|||||||
await self.send_ssh_command(commands)
|
await self.send_ssh_command(commands)
|
||||||
|
|
||||||
async def stop_cgminer(self) -> None:
|
async def stop_cgminer(self) -> None:
|
||||||
|
"""Restart cgminer hashing process."""
|
||||||
commands = [
|
commands = [
|
||||||
"mkdir -p /etc/tmp/",
|
"mkdir -p /etc/tmp/",
|
||||||
'echo "" > /etc/tmp/root',
|
'echo "" > /etc/tmp/root',
|
||||||
@@ -101,14 +125,24 @@ class CGMiner(BaseMiner):
|
|||||||
commands = ";".join(commands)
|
commands = ";".join(commands)
|
||||||
await self.send_ssh_command(commands)
|
await self.send_ssh_command(commands)
|
||||||
|
|
||||||
async def get_config(self) -> None:
|
async def get_config(self) -> str:
|
||||||
|
"""Gets the config for the miner and sets it as `self.config`.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The config from `self.config`.
|
||||||
|
"""
|
||||||
async with (await self._get_ssh_connection()) as conn:
|
async with (await self._get_ssh_connection()) as conn:
|
||||||
command = "cat /etc/config/cgminer"
|
command = "cat /etc/config/cgminer"
|
||||||
result = await conn.run(command, check=True)
|
result = await conn.run(command, check=True)
|
||||||
self.config = result.stdout
|
self.config = result.stdout
|
||||||
print(str(self.config))
|
return self.config
|
||||||
|
|
||||||
async def get_data(self):
|
async def get_data(self) -> MinerData:
|
||||||
|
"""Get data from the miner.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A [`MinerData`][pyasic.data.MinerData] instance containing the miners data.
|
||||||
|
"""
|
||||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||||
|
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
@@ -128,7 +162,7 @@ class CGMiner(BaseMiner):
|
|||||||
data.mac = mac
|
data.mac = mac
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"summary", "pools", "stats", ignore_x19_error=True
|
"summary", "pools", "stats", ignore_x19_error=True
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,3 +8,21 @@ class M20S(BaseMiner):
|
|||||||
self.model = "M20S"
|
self.model = "M20S"
|
||||||
self.nominal_chips = 66
|
self.nominal_chips = 66
|
||||||
self.fan_count = 2
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M20SV10(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20S"
|
||||||
|
self.nominal_chips = 105
|
||||||
|
self.fan_count = 2
|
||||||
|
|
||||||
|
|
||||||
|
class M20SV20(BaseMiner):
|
||||||
|
def __init__(self, ip: str):
|
||||||
|
super().__init__()
|
||||||
|
self.ip = ip
|
||||||
|
self.model = "M20S"
|
||||||
|
self.nominal_chips = 111
|
||||||
|
self.fan_count = 2
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .M20S import M20S
|
from .M20S import M20S, M20SV10, M20SV20
|
||||||
from .M20S_Plus import M20SPlus
|
from .M20S_Plus import M20SPlus
|
||||||
|
|
||||||
from .M21 import M21
|
from .M21 import M21
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon1026 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon1026 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon1026(CGMiner, Avalon1026):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon1047 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon1047 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon1047(CGMiner, Avalon1047):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon1066 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon1066 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon1066(CGMiner, Avalon1066):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon721 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon721 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon721(CGMiner, Avalon721):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon741 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon741 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon741(CGMiner, Avalon741):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon761 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon761 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon761(CGMiner, Avalon761):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon821 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon821 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon821(CGMiner, Avalon821):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon841 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon841 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon841(CGMiner, Avalon841):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon851 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon851 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon851(CGMiner, Avalon851):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
|||||||
from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module
|
from pyasic.miners._types import Avalon921 # noqa - Ignore access to _module
|
||||||
|
|
||||||
from pyasic.data import MinerData
|
from pyasic.data import MinerData
|
||||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
from pyasic.settings import PyasicSettings
|
||||||
import re
|
import re
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
import logging
|
import logging
|
||||||
@@ -67,7 +67,7 @@ class CGMinerAvalon921(CGMiner, Avalon921):
|
|||||||
data.model = model
|
data.model = model
|
||||||
|
|
||||||
miner_data = None
|
miner_data = None
|
||||||
for i in range(DATA_RETRIES):
|
for i in range(PyasicSettings().miner_get_data_retries):
|
||||||
miner_data = await self.api.multicommand(
|
miner_data = await self.api.multicommand(
|
||||||
"version", "summary", "pools", "stats"
|
"version", "summary", "pools", "stats"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from typing import TypeVar, Tuple, List, Union
|
from typing import TypeVar, Tuple, List, Union
|
||||||
from collections.abc import AsyncIterable
|
from collections.abc import AsyncIterable
|
||||||
from pyasic.miners import BaseMiner
|
from pyasic.miners import BaseMiner
|
||||||
|
import httpx
|
||||||
|
|
||||||
from pyasic.miners.antminer import *
|
from pyasic.miners.antminer import *
|
||||||
from pyasic.miners.avalonminer import *
|
from pyasic.miners.avalonminer import *
|
||||||
@@ -23,10 +24,7 @@ import ipaddress
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from pyasic.settings import (
|
from pyasic.settings import PyasicSettings
|
||||||
MINER_FACTORY_GET_VERSION_RETRIES as GET_VERSION_RETRIES,
|
|
||||||
NETWORK_PING_TIMEOUT as PING_TIMEOUT,
|
|
||||||
)
|
|
||||||
|
|
||||||
import asyncssh
|
import asyncssh
|
||||||
|
|
||||||
@@ -123,6 +121,8 @@ MINER_CLASSES = {
|
|||||||
"M20S": {
|
"M20S": {
|
||||||
"Default": BTMinerM20S,
|
"Default": BTMinerM20S,
|
||||||
"BTMiner": BTMinerM20S,
|
"BTMiner": BTMinerM20S,
|
||||||
|
"10": BTMinerM20SV10,
|
||||||
|
"20": BTMinerM20SV20,
|
||||||
},
|
},
|
||||||
"M20S+": {
|
"M20S+": {
|
||||||
"Default": BTMinerM20SPlus,
|
"Default": BTMinerM20SPlus,
|
||||||
@@ -282,13 +282,12 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
ver = None
|
ver = None
|
||||||
|
|
||||||
# try to get the API multiple times based on retries
|
# try to get the API multiple times based on retries
|
||||||
for i in range(GET_VERSION_RETRIES):
|
for i in range(PyasicSettings().miner_factory_get_version_retries):
|
||||||
try:
|
try:
|
||||||
# get the API type, should be BOSMiner, CGMiner, BMMiner, BTMiner, or None
|
# get the API type, should be BOSMiner, CGMiner, BMMiner, BTMiner, or None
|
||||||
new_model, new_api, new_ver = await asyncio.wait_for(
|
new_model, new_api, new_ver = await asyncio.wait_for(
|
||||||
self._get_miner_type(ip), timeout=PING_TIMEOUT
|
self._get_miner_type(ip), timeout=10
|
||||||
)
|
)
|
||||||
|
|
||||||
# keep track of the API and model we found first
|
# keep track of the API and model we found first
|
||||||
if new_api and not api:
|
if new_api and not api:
|
||||||
api = new_api
|
api = new_api
|
||||||
@@ -296,13 +295,11 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
model = new_model
|
model = new_model
|
||||||
if new_ver and not ver:
|
if new_ver and not ver:
|
||||||
ver = new_ver
|
ver = new_ver
|
||||||
|
|
||||||
# if we find the API and model, don't need to loop anymore
|
# if we find the API and model, don't need to loop anymore
|
||||||
if api and model:
|
if api and model:
|
||||||
break
|
break
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
pass
|
logging.warning(f"{ip}: Get Miner Timed Out")
|
||||||
|
|
||||||
# make sure we have model information
|
# make sure we have model information
|
||||||
if model:
|
if model:
|
||||||
if not api:
|
if not api:
|
||||||
@@ -310,12 +307,10 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
|
|
||||||
if model not in MINER_CLASSES.keys():
|
if model not in MINER_CLASSES.keys():
|
||||||
if "avalon" in model:
|
if "avalon" in model:
|
||||||
print(model)
|
|
||||||
if model == "avalon10":
|
if model == "avalon10":
|
||||||
miner = CGMinerAvalon1066(str(ip))
|
miner = CGMinerAvalon1066(str(ip))
|
||||||
else:
|
else:
|
||||||
miner = CGMinerAvalon821(str(ip))
|
miner = CGMinerAvalon821(str(ip))
|
||||||
miner = UnknownMiner(str(ip))
|
|
||||||
return miner
|
return miner
|
||||||
if api not in MINER_CLASSES[model].keys():
|
if api not in MINER_CLASSES[model].keys():
|
||||||
api = "Default"
|
api = "Default"
|
||||||
@@ -355,32 +350,26 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
async def _get_miner_type(
|
async def _get_miner_type(
|
||||||
self, ip: ipaddress.ip_address or str
|
self, ip: ipaddress.ip_address or str
|
||||||
) -> Tuple[str or None, str or None, str or None]:
|
) -> Tuple[str or None, str or None, str or None]:
|
||||||
|
data = None
|
||||||
|
|
||||||
model = None
|
model = None
|
||||||
api = None
|
api = None
|
||||||
ver = None
|
ver = None
|
||||||
|
|
||||||
devdetails = None
|
devdetails = None
|
||||||
version = None
|
version = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# get device details and version data
|
# get device details and version data
|
||||||
data = await self._send_api_command(str(ip), "devdetails+version")
|
data = await self._send_api_command(str(ip), "devdetails+version")
|
||||||
|
|
||||||
# validate success
|
# validate success
|
||||||
validation = await self._validate_command(data)
|
validation = await self._validate_command(data)
|
||||||
if not validation[0]:
|
if not validation[0]:
|
||||||
raise APIError(validation[1])
|
raise APIError(validation[1])
|
||||||
|
|
||||||
# copy each part of the main command to devdetails and version
|
# copy each part of the main command to devdetails and version
|
||||||
devdetails = data["devdetails"][0]
|
devdetails = data["devdetails"][0]
|
||||||
version = data["version"][0]
|
version = data["version"][0]
|
||||||
|
|
||||||
except APIError:
|
except APIError:
|
||||||
# if getting data fails we need to check again
|
|
||||||
data = None
|
|
||||||
|
|
||||||
# if data is None then get it a slightly different way
|
|
||||||
if not data:
|
|
||||||
try:
|
try:
|
||||||
# try devdetails and version separately (X19s mainly require this)
|
# try devdetails and version separately (X19s mainly require this)
|
||||||
# get devdetails and validate
|
# get devdetails and validate
|
||||||
@@ -405,6 +394,44 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# catch APIError and let the factory know we cant get data
|
# catch APIError and let the factory know we cant get data
|
||||||
logging.warning(f"{ip}: API Command Error: {e}")
|
logging.warning(f"{ip}: API Command Error: {e}")
|
||||||
return None, None, None
|
return None, None, None
|
||||||
|
except OSError as e:
|
||||||
|
# miner refused connection on API port, we wont be able to get data this way
|
||||||
|
# try ssh
|
||||||
|
try:
|
||||||
|
async with asyncssh.connect(
|
||||||
|
str(ip),
|
||||||
|
known_hosts=None,
|
||||||
|
username="root",
|
||||||
|
password="admin",
|
||||||
|
server_host_key_algs=["ssh-rsa"],
|
||||||
|
) as conn:
|
||||||
|
board_name = None
|
||||||
|
cmd = await conn.run("cat /tmp/sysinfo/board_name")
|
||||||
|
if cmd:
|
||||||
|
board_name = cmd.stdout.strip()
|
||||||
|
|
||||||
|
if board_name:
|
||||||
|
if board_name == "am1-s9":
|
||||||
|
model = "Antminer S9"
|
||||||
|
if board_name == "am2-s17":
|
||||||
|
model = "Antminer S17"
|
||||||
|
api = "BOSMiner+"
|
||||||
|
return model, api, None
|
||||||
|
|
||||||
|
except asyncssh.misc.PermissionDenied:
|
||||||
|
try:
|
||||||
|
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||||
|
auth = httpx.DigestAuth("root", "root")
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
data = await client.get(url, auth=auth)
|
||||||
|
if data.status_code == 200:
|
||||||
|
data = data.json()
|
||||||
|
if "minertype" in data.keys():
|
||||||
|
model = data["minertype"]
|
||||||
|
if "bmminer" in "\t".join(data.keys()):
|
||||||
|
api = "BMMiner"
|
||||||
|
except:
|
||||||
|
return None, None, None
|
||||||
|
|
||||||
# if we have devdetails, we can get model data from there
|
# if we have devdetails, we can get model data from there
|
||||||
if devdetails:
|
if devdetails:
|
||||||
@@ -452,24 +479,24 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
if "BOSminer+" in version["VERSION"][0].keys():
|
if "BOSminer+" in version["VERSION"][0].keys():
|
||||||
api = "BOSMiner+"
|
api = "BOSMiner+"
|
||||||
|
|
||||||
|
# check for avalonminers
|
||||||
|
if version["VERSION"][0].get("PROD"):
|
||||||
|
_data = version["VERSION"][0]["PROD"].split("-")
|
||||||
|
model = _data[0]
|
||||||
|
if len(data) > 1:
|
||||||
|
ver = _data[1]
|
||||||
|
elif version["VERSION"][0].get("MODEL"):
|
||||||
|
_data = version["VERSION"][0]["MODEL"].split("-")
|
||||||
|
model = f"AvalonMiner {_data[0]}"
|
||||||
|
if len(data) > 1:
|
||||||
|
ver = _data[1]
|
||||||
|
|
||||||
# if all that fails, check the Description to see if it is a whatsminer
|
# if all that fails, check the Description to see if it is a whatsminer
|
||||||
if version.get("Description") and (
|
if version.get("Description") and (
|
||||||
"whatsminer" in version.get("Description")
|
"whatsminer" in version.get("Description")
|
||||||
):
|
):
|
||||||
api = "BTMiner"
|
api = "BTMiner"
|
||||||
|
|
||||||
# check for avalonminers
|
|
||||||
if version["VERSION"][0].get("PROD"):
|
|
||||||
_data = version["VERSION"][0]["PROD"].split("-")
|
|
||||||
model = _data[0]
|
|
||||||
if len(data) > 1:
|
|
||||||
ver = _data[1]
|
|
||||||
elif version["VERSION"][0].get("MODEL"):
|
|
||||||
_data = version["VERSION"][0]["MODEL"].split("-")
|
|
||||||
model = f"AvalonMiner {_data[0]}"
|
|
||||||
if len(data) > 1:
|
|
||||||
ver = _data[1]
|
|
||||||
|
|
||||||
# if we have no model from devdetails but have version, try to get it from there
|
# if we have no model from devdetails but have version, try to get it from there
|
||||||
if version and not model:
|
if version and not model:
|
||||||
# make sure version isn't blank
|
# make sure version isn't blank
|
||||||
@@ -486,23 +513,6 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
elif "am2-s17" in version["STATUS"][0]["Description"]:
|
elif "am2-s17" in version["STATUS"][0]["Description"]:
|
||||||
model = "Antminer S17"
|
model = "Antminer S17"
|
||||||
|
|
||||||
# final try on a braiins OS bug with devdetails not returning
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
async with asyncssh.connect(
|
|
||||||
str(ip),
|
|
||||||
known_hosts=None,
|
|
||||||
username="root",
|
|
||||||
password="admin",
|
|
||||||
server_host_key_algs=["ssh-rsa"],
|
|
||||||
) as conn:
|
|
||||||
cfg = await conn.run("bosminer config --data")
|
|
||||||
if cfg:
|
|
||||||
cfg = json.loads(cfg.stdout)
|
|
||||||
model = cfg.get("data").get("format").get("model")
|
|
||||||
except asyncssh.misc.PermissionDenied:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if model:
|
if model:
|
||||||
# whatsminer have a V in their version string (M20SV41), remove everything after it
|
# whatsminer have a V in their version string (M20SV41), remove everything after it
|
||||||
if "V" in model:
|
if "V" in model:
|
||||||
@@ -519,7 +529,7 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
async def _validate_command(data: dict) -> Tuple[bool, str or None]:
|
async def _validate_command(data: dict) -> Tuple[bool, str or None]:
|
||||||
"""Check if the returned command output is correctly formatted."""
|
"""Check if the returned command output is correctly formatted."""
|
||||||
# check if the data returned is correct or an error
|
# check if the data returned is correct or an error
|
||||||
if not data:
|
if not data or data == {}:
|
||||||
return False, "No API data."
|
return False, "No API data."
|
||||||
# if status isn't a key, it is a multicommand
|
# if status isn't a key, it is a multicommand
|
||||||
if "STATUS" not in data.keys():
|
if "STATUS" not in data.keys():
|
||||||
@@ -548,9 +558,10 @@ class MinerFactory(metaclass=Singleton):
|
|||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
reader, writer = await asyncio.open_connection(str(ip), 4028)
|
reader, writer = await asyncio.open_connection(str(ip), 4028)
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
|
if e.errno in [10061, 22] or e.winerror == 1225:
|
||||||
|
raise e
|
||||||
logging.warning(f"{str(ip)} - Command {command}: {e}")
|
logging.warning(f"{str(ip)} - Command {command}: {e}")
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
# create the command
|
# create the command
|
||||||
cmd = {"command": command}
|
cmd = {"command": command}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,24 @@
|
|||||||
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
from pyasic.miners._backends import BTMiner # noqa - Ignore access to _module
|
||||||
from pyasic.miners._types import M20S # noqa - Ignore access to _module
|
from pyasic.miners._types import ( # noqa - Ignore access to _module
|
||||||
|
M20S,
|
||||||
|
M20SV10,
|
||||||
|
M20SV20,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM20S(BTMiner, M20S):
|
class BTMinerM20S(BTMiner, M20S):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20SV10(BTMiner, M20SV10):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM20SV20(BTMiner, M20SV20):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .M20S import BTMinerM20S
|
from .M20S import BTMinerM20S, BTMinerM20SV10, BTMinerM20SV20
|
||||||
from .M20S_Plus import BTMinerM20SPlus
|
from .M20S_Plus import BTMinerM20SPlus
|
||||||
|
|
||||||
from .M21 import BTMinerM21
|
from .M21 import BTMinerM21
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ class BTMinerM30S(BTMiner, M30S):
|
|||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SV50(BTMiner, M30SV50):
|
class BTMinerM30SVE10(BTMiner, M30SVE10):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
@@ -32,7 +32,7 @@ class BTMinerM30SVE20(BTMiner, M30SVE20):
|
|||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SVE10(BTMiner, M30SVE10):
|
class BTMinerM30SV50(BTMiner, M30SV50):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ class BTMinerM30SPlus(BTMiner, M30SPlus):
|
|||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SPlusVE40(BTMiner, M30SPlusVE40):
|
class BTMinerM30SPlusVF20(BTMiner, M30SPlusVF20):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SPlusVF20(BTMiner, M30SPlusVF20):
|
class BTMinerM30SPlusVE40(BTMiner, M30SPlusVE40):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ class BTMinerM30SPlusPlus(BTMiner, M30SPlusPlus):
|
|||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40):
|
|
||||||
def __init__(self, ip: str) -> None:
|
|
||||||
super().__init__(ip)
|
|
||||||
self.ip = ip
|
|
||||||
|
|
||||||
|
|
||||||
class BTMinerM30SPlusPlusVG30(BTMiner, M30SPlusPlusVG30):
|
class BTMinerM30SPlusPlusVG30(BTMiner, M30SPlusPlusVG30):
|
||||||
def __init__(self, ip: str) -> None:
|
def __init__(self, ip: str) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
|
|
||||||
|
|
||||||
|
class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
super().__init__(ip)
|
||||||
|
self.ip = ip
|
||||||
|
|||||||
@@ -1,70 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
from pyasic.network import MinerNetwork
|
|
||||||
from pyasic.miners._backends.bosminer import BOSMiner # noqa - Ignore access to _module
|
|
||||||
|
|
||||||
|
|
||||||
async def get_bos_bad_tuners(ip: str = "192.168.1.0", mask: int = 24):
|
|
||||||
# create a miner network
|
|
||||||
miner_network = MinerNetwork(ip, mask=mask)
|
|
||||||
|
|
||||||
# scan for miners
|
|
||||||
miners = await miner_network.scan_network_for_miners()
|
|
||||||
|
|
||||||
# create an empty list of tasks
|
|
||||||
tuner_tasks = []
|
|
||||||
|
|
||||||
# loop checks if the miner is a BOSMiner
|
|
||||||
for miner in miners:
|
|
||||||
# can only do this if its a subclass of BOSMiner
|
|
||||||
if BOSMiner in type(miner).__bases__:
|
|
||||||
tuner_tasks.append(_get_tuner_status(miner))
|
|
||||||
|
|
||||||
# run all the tuner status commands
|
|
||||||
tuner_status = await asyncio.gather(*tuner_tasks)
|
|
||||||
|
|
||||||
# create a list of all miners with bad board tuner status
|
|
||||||
bad_tuner_miners = []
|
|
||||||
for item in tuner_status:
|
|
||||||
# loop through and get each miners' bad board count
|
|
||||||
bad_boards = []
|
|
||||||
for board in item["tuner_status"]:
|
|
||||||
# if its not stable or still testing, its bad
|
|
||||||
if board["status"] not in [
|
|
||||||
"Stable",
|
|
||||||
"Testing performance profile",
|
|
||||||
"Tuning individual chips",
|
|
||||||
]:
|
|
||||||
# remove the part about the board refusing to start
|
|
||||||
bad_boards.append(
|
|
||||||
{
|
|
||||||
"board": board["board"],
|
|
||||||
"error": board["status"].replace(
|
|
||||||
"Hashchain refused to start: ", ""
|
|
||||||
),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# if this miner has bad boards, add it to the list of bad board miners
|
|
||||||
if len(bad_boards) > 0:
|
|
||||||
bad_tuner_miners.append({"ip": item["ip"], "boards": bad_boards})
|
|
||||||
|
|
||||||
# return the list of bad board miners
|
|
||||||
return bad_tuner_miners
|
|
||||||
|
|
||||||
|
|
||||||
async def _get_tuner_status(miner):
|
|
||||||
# run the tunerstatus command, since the miner will always be BOSMiner
|
|
||||||
tuner_status = await miner.api.tunerstatus()
|
|
||||||
|
|
||||||
# create a list to add the tuner data to
|
|
||||||
tuner_data = []
|
|
||||||
|
|
||||||
# if we have data, loop through to get the hashchain status
|
|
||||||
if tuner_status:
|
|
||||||
for board in tuner_status["TUNERSTATUS"][0]["TunerChainStatus"]:
|
|
||||||
tuner_data.append(
|
|
||||||
{"board": board["HashchainIndex"], "status": board["Status"]}
|
|
||||||
)
|
|
||||||
|
|
||||||
# return the data along with the IP or later tracking
|
|
||||||
return {"ip": str(miner.ip), "tuner_status": tuner_data}
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import Union
|
from typing import Union, List, AsyncIterator
|
||||||
|
|
||||||
from pyasic.network.net_range import MinerNetworkRange
|
from pyasic.network.net_range import MinerNetworkRange
|
||||||
from pyasic.miners.miner_factory import MinerFactory
|
from pyasic.miners.miner_factory import MinerFactory, AnyMiner
|
||||||
from pyasic.settings import (
|
from pyasic.settings import PyasicSettings
|
||||||
NETWORK_PING_RETRIES as PING_RETRIES,
|
|
||||||
NETWORK_PING_TIMEOUT as PING_TIMEOUT,
|
|
||||||
NETWORK_SCAN_THREADS as SCAN_THREADS,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class MinerNetwork:
|
class MinerNetwork:
|
||||||
@@ -32,8 +28,9 @@ class MinerNetwork:
|
|||||||
self.network = None
|
self.network = None
|
||||||
self.ip_addr = ip_addr
|
self.ip_addr = ip_addr
|
||||||
self.connected_miners = {}
|
self.connected_miners = {}
|
||||||
if mask.startswith("/"):
|
if isinstance(mask, str):
|
||||||
mask = mask.replace("/", "")
|
if mask.startswith("/"):
|
||||||
|
mask = mask.replace("/", "")
|
||||||
self.mask = mask
|
self.mask = mask
|
||||||
|
|
||||||
def __len__(self):
|
def __len__(self):
|
||||||
@@ -43,7 +40,11 @@ class MinerNetwork:
|
|||||||
return str(self.network)
|
return str(self.network)
|
||||||
|
|
||||||
def get_network(self) -> ipaddress.ip_network:
|
def get_network(self) -> ipaddress.ip_network:
|
||||||
"""Get the network using the information passed to the MinerNetwork or from cache."""
|
"""Get the network using the information passed to the MinerNetwork or from cache.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The proper network to be able to scan.
|
||||||
|
"""
|
||||||
# if we have a network cached already, use that
|
# if we have a network cached already, use that
|
||||||
if self.network:
|
if self.network:
|
||||||
return self.network
|
return self.network
|
||||||
@@ -71,13 +72,19 @@ class MinerNetwork:
|
|||||||
self.network = ipaddress.ip_network(
|
self.network = ipaddress.ip_network(
|
||||||
f"{default_gateway}/{subnet_mask}", strict=False
|
f"{default_gateway}/{subnet_mask}", strict=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logging.debug(f"Setting MinerNetwork: {self.network}")
|
||||||
return self.network
|
return self.network
|
||||||
|
|
||||||
async def scan_network_for_miners(self) -> None or list:
|
async def scan_network_for_miners(self) -> List[AnyMiner]:
|
||||||
"""Scan the network for miners, and return found miners as a list."""
|
"""Scan the network for miners, and return found miners as a list.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A list of found miners.
|
||||||
|
"""
|
||||||
# get the network
|
# get the network
|
||||||
local_network = self.get_network()
|
local_network = self.get_network()
|
||||||
print(f"Scanning {local_network} for miners...")
|
logging.debug(f"Scanning {local_network} for miners")
|
||||||
|
|
||||||
# clear cached miners
|
# clear cached miners
|
||||||
MinerFactory().clear_cached_miners()
|
MinerFactory().clear_cached_miners()
|
||||||
@@ -90,7 +97,7 @@ class MinerNetwork:
|
|||||||
for host in local_network.hosts():
|
for host in local_network.hosts():
|
||||||
|
|
||||||
# make sure we don't exceed the allowed async tasks
|
# make sure we don't exceed the allowed async tasks
|
||||||
if len(scan_tasks) < SCAN_THREADS:
|
if len(scan_tasks) < round(PyasicSettings().network_scan_threads / 3):
|
||||||
# add the task to the list
|
# add the task to the list
|
||||||
scan_tasks.append(self.ping_and_get_miner(host))
|
scan_tasks.append(self.ping_and_get_miner(host))
|
||||||
else:
|
else:
|
||||||
@@ -106,16 +113,17 @@ class MinerNetwork:
|
|||||||
|
|
||||||
# remove all None from the miner list
|
# remove all None from the miner list
|
||||||
miners = list(filter(None, miners))
|
miners = list(filter(None, miners))
|
||||||
print(f"Found {len(miners)} connected miners...")
|
logging.debug(f"Found {len(miners)} connected miners")
|
||||||
|
|
||||||
# return the miner objects
|
# return the miner objects
|
||||||
return miners
|
return miners
|
||||||
|
|
||||||
async def scan_network_generator(self):
|
async def scan_network_generator(self) -> AsyncIterator[AnyMiner]:
|
||||||
"""
|
"""
|
||||||
Scan the network for miners using an async generator.
|
Scan the network for miners using an async generator.
|
||||||
|
|
||||||
Returns an asynchronous generator containing found miners.
|
Returns:
|
||||||
|
An asynchronous generator containing found miners.
|
||||||
"""
|
"""
|
||||||
# get the current event loop
|
# get the current event loop
|
||||||
loop = asyncio.get_event_loop()
|
loop = asyncio.get_event_loop()
|
||||||
@@ -129,7 +137,7 @@ class MinerNetwork:
|
|||||||
# for each ip on the network, loop through and scan it
|
# for each ip on the network, loop through and scan it
|
||||||
for host in local_network.hosts():
|
for host in local_network.hosts():
|
||||||
# make sure we don't exceed the allowed async tasks
|
# make sure we don't exceed the allowed async tasks
|
||||||
if len(scan_tasks) >= SCAN_THREADS:
|
if len(scan_tasks) >= round(PyasicSettings().network_scan_threads / 3):
|
||||||
# scanned is a loopable list of awaitables
|
# scanned is a loopable list of awaitables
|
||||||
scanned = asyncio.as_completed(scan_tasks)
|
scanned = asyncio.as_completed(scan_tasks)
|
||||||
# when we scan, empty the scan tasks
|
# when we scan, empty the scan tasks
|
||||||
@@ -148,25 +156,33 @@ class MinerNetwork:
|
|||||||
yield await miner
|
yield await miner
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def ping_miner(ip: ipaddress.ip_address) -> None or ipaddress.ip_address:
|
async def ping_miner(ip: ipaddress.ip_address) -> Union[None, ipaddress.ip_address]:
|
||||||
return await ping_miner(ip)
|
tasks = [ping_miner(ip, port=port) for port in [4028, 4029, 8889]]
|
||||||
|
for miner in asyncio.as_completed(tasks):
|
||||||
|
miner = await miner
|
||||||
|
if miner:
|
||||||
|
return miner
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def ping_and_get_miner(
|
async def ping_and_get_miner(
|
||||||
ip: ipaddress.ip_address,
|
ip: ipaddress.ip_address,
|
||||||
) -> None or ipaddress.ip_address:
|
) -> Union[None, AnyMiner]:
|
||||||
return await ping_and_get_miner(ip)
|
tasks = [ping_and_get_miner(ip, port=port) for port in [4028, 4029, 8889]]
|
||||||
|
for miner in asyncio.as_completed(tasks):
|
||||||
|
miner = await miner
|
||||||
|
if miner:
|
||||||
|
return miner
|
||||||
|
|
||||||
|
|
||||||
async def ping_miner(
|
async def ping_miner(
|
||||||
ip: ipaddress.ip_address, port=4028
|
ip: ipaddress.ip_address, port=4028
|
||||||
) -> None or ipaddress.ip_address:
|
) -> Union[None, ipaddress.ip_address]:
|
||||||
for i in range(PING_RETRIES):
|
for i in range(PyasicSettings().network_ping_retries):
|
||||||
connection_fut = asyncio.open_connection(str(ip), port)
|
connection_fut = asyncio.open_connection(str(ip), port)
|
||||||
try:
|
try:
|
||||||
# get the read and write streams from the connection
|
# get the read and write streams from the connection
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
connection_fut, timeout=PING_TIMEOUT
|
connection_fut, timeout=PyasicSettings().network_ping_timeout
|
||||||
)
|
)
|
||||||
# immediately close connection, we know connection happened
|
# immediately close connection, we know connection happened
|
||||||
writer.close()
|
writer.close()
|
||||||
@@ -189,13 +205,13 @@ async def ping_miner(
|
|||||||
|
|
||||||
async def ping_and_get_miner(
|
async def ping_and_get_miner(
|
||||||
ip: ipaddress.ip_address, port=4028
|
ip: ipaddress.ip_address, port=4028
|
||||||
) -> None or ipaddress.ip_address:
|
) -> Union[None, AnyMiner]:
|
||||||
for i in range(PING_RETRIES):
|
for i in range(PyasicSettings().network_ping_retries):
|
||||||
connection_fut = asyncio.open_connection(str(ip), port)
|
connection_fut = asyncio.open_connection(str(ip), port)
|
||||||
try:
|
try:
|
||||||
# get the read and write streams from the connection
|
# get the read and write streams from the connection
|
||||||
reader, writer = await asyncio.wait_for(
|
reader, writer = await asyncio.wait_for(
|
||||||
connection_fut, timeout=PING_TIMEOUT
|
connection_fut, timeout=PyasicSettings().network_ping_timeout
|
||||||
)
|
)
|
||||||
# immediately close connection, we know connection happened
|
# immediately close connection, we know connection happened
|
||||||
writer.close()
|
writer.close()
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ class MinerNetworkRange:
|
|||||||
Parameters:
|
Parameters:
|
||||||
ip_range: ## A range of IP addresses to put in the network, or a list of IPs
|
ip_range: ## A range of IP addresses to put in the network, or a list of IPs
|
||||||
* Takes a string formatted as:
|
* Takes a string formatted as:
|
||||||
* {ip_range_1_start}-{ip_range_1_end}, {ip_address_1}, {ip_range_2_start}-{ip_range_2_end}, {ip_address_2}...
|
```f"{ip_range_1_start}-{ip_range_1_end}, {ip_address_1}, {ip_range_2_start}-{ip_range_2_end}, {ip_address_2}..."```
|
||||||
* Also takes a list of strings formatted as:
|
* Also takes a list of strings or `ipaddress.ipaddress` formatted as:
|
||||||
* [{ip_address_1}, {ip_address_2}, {ip_address_3}, ...]
|
```[{ip_address_1}, {ip_address_2}, {ip_address_3}, ...]```
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, ip_range: Union[str, list]):
|
def __init__(self, ip_range: Union[str, list]):
|
||||||
|
|||||||
@@ -1,53 +1,26 @@
|
|||||||
import toml
|
from dataclasses import dataclass
|
||||||
import os
|
|
||||||
|
|
||||||
NETWORK_PING_RETRIES: int = 3
|
|
||||||
NETWORK_PING_TIMEOUT: int = 5
|
|
||||||
NETWORK_SCAN_THREADS: int = 300
|
|
||||||
|
|
||||||
CFG_UTIL_REBOOT_THREADS: int = 300
|
|
||||||
CFG_UTIL_CONFIG_THREADS: int = 300
|
|
||||||
|
|
||||||
MINER_FACTORY_GET_VERSION_RETRIES: int = 3
|
|
||||||
|
|
||||||
WHATSMINER_PWD = "admin"
|
|
||||||
|
|
||||||
DEBUG = False
|
|
||||||
LOGFILE = False
|
|
||||||
|
|
||||||
settings_keys = {}
|
|
||||||
|
|
||||||
try:
|
|
||||||
with open(
|
|
||||||
os.path.join(os.path.dirname(__file__), "settings.toml"), "r"
|
|
||||||
) as settings_file:
|
|
||||||
settings = toml.loads(settings_file.read())
|
|
||||||
settings_keys = settings.keys()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if "ping_retries" in settings_keys:
|
|
||||||
NETWORK_PING_RETRIES: int = settings["ping_retries"]
|
|
||||||
if "ping_timeout" in settings_keys:
|
|
||||||
NETWORK_PING_TIMEOUT: int = settings["ping_timeout"]
|
|
||||||
if "scan_threads" in settings_keys:
|
|
||||||
NETWORK_SCAN_THREADS: int = settings["scan_threads"]
|
|
||||||
|
|
||||||
if "reboot_threads" in settings_keys:
|
|
||||||
CFG_UTIL_REBOOT_THREADS: int = settings["reboot_threads"]
|
|
||||||
if "config_threads" in settings_keys:
|
|
||||||
CFG_UTIL_CONFIG_THREADS: int = settings["config_threads"]
|
|
||||||
|
|
||||||
|
|
||||||
if "get_version_retries" in settings_keys:
|
class Singleton(type):
|
||||||
MINER_FACTORY_GET_VERSION_RETRIES: int = settings["get_version_retries"]
|
_instances = {}
|
||||||
|
|
||||||
|
def __call__(cls, *args, **kwargs):
|
||||||
|
if cls not in cls._instances:
|
||||||
|
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
|
||||||
|
return cls._instances[cls]
|
||||||
|
|
||||||
|
|
||||||
if "whatsminer_pwd" in settings_keys:
|
@dataclass
|
||||||
WHATSMINER_PWD: str = settings["whatsminer_pwd"]
|
class PyasicSettings(metaclass=Singleton):
|
||||||
|
network_ping_retries: int = 1
|
||||||
|
network_ping_timeout: int = 3
|
||||||
|
network_scan_threads: int = 300
|
||||||
|
|
||||||
if "debug" in settings_keys:
|
miner_factory_get_version_retries: int = 1
|
||||||
DEBUG: int = settings["debug"]
|
|
||||||
|
|
||||||
if "logfile" in settings_keys:
|
miner_get_data_retries: int = 1
|
||||||
LOGFILE: bool = settings["logfile"]
|
|
||||||
|
global_whatsminer_password = "admin"
|
||||||
|
|
||||||
|
debug: bool = False
|
||||||
|
logfile: bool = False
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
get_version_retries = 3
|
|
||||||
ping_retries = 3
|
|
||||||
ping_timeout = 3 # Seconds
|
|
||||||
scan_threads = 300
|
|
||||||
config_threads = 300
|
|
||||||
reboot_threads = 300
|
|
||||||
|
|
||||||
|
|
||||||
### IMPORTANT ###
|
|
||||||
# You need to change the password of the miners using the whatsminer
|
|
||||||
# tool or the privileged API will not work using admin as the password.
|
|
||||||
# If you change the password, you can pass that password here.
|
|
||||||
|
|
||||||
whatsminer_pwd = "admin"
|
|
||||||
|
|
||||||
logfile = true
|
|
||||||
|
|
||||||
### DEBUG MODE ###
|
|
||||||
# change this to debug = true
|
|
||||||
# to enable debug mode.
|
|
||||||
debug = false
|
|
||||||
# debug = true
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "pyasic"
|
name = "pyasic"
|
||||||
version = "0.11.0"
|
version = "0.12.4"
|
||||||
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