Compare commits
72 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f84a054ecc | ||
|
|
6b54607588 | ||
|
|
85ee8a479b | ||
|
|
9decbf2a4b | ||
|
|
15ce3a3140 | ||
|
|
d4d48f5582 | ||
|
|
a577f64d59 | ||
|
|
aaf48cc686 | ||
|
|
aa6dc74471 | ||
|
|
63c8fe6868 | ||
|
|
ee1502c6a0 | ||
|
|
2960295385 | ||
|
|
a9e09f7b1a | ||
|
|
fd17a20a1b | ||
|
|
1e03ec5fa3 | ||
|
|
a67e4ada8e | ||
|
|
2d08b10076 | ||
|
|
92e972aa57 | ||
|
|
05cfe8cc5d | ||
|
|
b4d9e60bff | ||
|
|
6bcf372be6 | ||
|
|
092a586329 | ||
|
|
e598d4b63c | ||
|
|
848acedd52 | ||
|
|
e3c4464556 | ||
|
|
9e28f7968a | ||
|
|
6159a72d46 | ||
|
|
932c034e0e | ||
|
|
645828f35b | ||
|
|
841a546505 | ||
|
|
e65b718699 | ||
|
|
15e4338046 | ||
|
|
720d4aec3d | ||
|
|
09f9028ab5 | ||
|
|
25d971b699 | ||
|
|
cd16ef3a25 | ||
|
|
b70010272f | ||
|
|
140a457445 | ||
|
|
f4775e6311 | ||
|
|
a4ecda93a2 | ||
|
|
ba90f2f082 | ||
|
|
44ac958bbb | ||
|
|
e9bcf2ec9f | ||
|
|
c73dfad01a | ||
|
|
d222912e30 | ||
|
|
6f1c1e0290 | ||
|
|
ba0bb73aa3 | ||
|
|
13fcf1d4aa | ||
|
|
6be1e94216 | ||
|
|
709b3efa81 | ||
|
|
5ac5770331 | ||
|
|
f131ebbdf5 | ||
|
|
5441e50f73 | ||
|
|
dc6a952de4 | ||
|
|
b781d215fb | ||
|
|
086b31ba23 | ||
|
|
46b7352769 | ||
|
|
e218c5039d | ||
|
|
3bb392980e | ||
|
|
92264619d2 | ||
|
|
3e21829fae | ||
|
|
d3619b0e48 | ||
|
|
0fbb05d62d | ||
|
|
01fc2591ad | ||
|
|
c08d7fa5cd | ||
|
|
91c0e1c125 | ||
|
|
3d137f95d9 | ||
|
|
d5e8062f09 | ||
|
|
2dc4b2cb8a | ||
|
|
5ff0074d33 | ||
|
|
bbc88e175c | ||
|
|
ff54bea0ed |
@@ -10,6 +10,7 @@ All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI],
|
||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||
Use these instead -
|
||||
|
||||
#### [BFGMiner API][pyasic.API.bfgminer.BFGMinerAPI]
|
||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
|
||||
7
docs/API/bfgminer.md
Normal file
7
docs/API/bfgminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BFGMinerAPI
|
||||
::: pyasic.API.bfgminer.BFGMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -42,21 +42,27 @@ if __name__ == "__main__":
|
||||
<br>
|
||||
|
||||
## Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners.
|
||||
The function [`MinerFactory().get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner].
|
||||
The function [`get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.miners.miner_factory import MinerFactory # miner factory handles miners creation
|
||||
from pyasic import get_miner # handles miner creation
|
||||
|
||||
|
||||
async def get_miners(): # define async scan function to allow awaiting
|
||||
# get the miner with miner factory
|
||||
# miner factory is a singleton, and will always use the same object and cache
|
||||
# this means you can always call it as MinerFactory().get_miner()
|
||||
miner_1 = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_2 = await MinerFactory().get_miner("192.168.1.76")
|
||||
# get the miner with the miner factory
|
||||
# the miner factory is a singleton, and will always use the same object and cache
|
||||
# this means you can always call it as MinerFactory().get_miner(), or just get_miner()
|
||||
miner_1 = await get_miner("192.168.1.75")
|
||||
miner_2 = await get_miner("192.168.1.76")
|
||||
print(miner_1, miner_2)
|
||||
|
||||
# can also gather these, since they are async
|
||||
tasks = [get_miner("192.168.1.75"), get_miner("192.168.1.76")]
|
||||
miners = await asyncio.gather(*tasks)
|
||||
print(miners)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
```
|
||||
@@ -66,7 +72,7 @@ if __name__ == "__main__":
|
||||
## Getting data from miners
|
||||
|
||||
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called `get_data()`.
|
||||
This function will return a instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||
This function will return an instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
11
docs/miners/antminer/X15.md
Normal file
11
docs/miners/antminer/X15.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# pyasic
|
||||
## X15 Models
|
||||
|
||||
|
||||
## Z15
|
||||
|
||||
::: pyasic.miners.zec.antminer.cgminer.X15.Z15.CGMinerZ15
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## S17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17.BMMinerS17
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.S17.BMMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## S17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.S17_Plus.BMMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## S17 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.S17_Pro.BMMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
## S17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.S17e.BMMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
## T17
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17.BMMinerT17
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.T17.BMMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
## T17+
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.T17_Plus.BMMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
## T17e
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||
::: pyasic.miners.btc.antminer.bmminer.X17.T17e.BMMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -61,7 +61,7 @@
|
||||
|
||||
## S17 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.S17.BOSMinerS17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -69,7 +69,7 @@
|
||||
|
||||
## S17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.S17_Plus.BOSMinerS17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -77,7 +77,7 @@
|
||||
|
||||
## S17 Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.S17_Pro.BOSMinerS17Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
## S17e (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.S17e.BOSMinerS17e
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.S17e.BOSMinerS17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
## T17 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.T17.BOSMinerT17
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -101,7 +101,7 @@
|
||||
|
||||
## T17+ (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.T17_Plus.BOSMinerT17Plus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -110,7 +110,7 @@
|
||||
|
||||
## T17e (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X17.T17e.BOSMinerT17e
|
||||
::: pyasic.miners.btc.antminer.bosminer.X17.T17e.BOSMinerT17e
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -3,7 +3,15 @@
|
||||
|
||||
## S19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19.BMMinerS19
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19.BMMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## S19L
|
||||
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19L.BMMinerS19L
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +19,7 @@
|
||||
|
||||
## S19 Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19_Pro.BMMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -20,7 +28,7 @@
|
||||
|
||||
## S19a
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19a.BMMinerS19a
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -29,7 +37,7 @@
|
||||
|
||||
## S19j
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j.BMMinerS19j
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19j.BMMinerS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -37,7 +45,7 @@
|
||||
|
||||
## S19j Pro
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19j_Pro.BMMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -45,7 +53,7 @@
|
||||
|
||||
## S19 XP
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.S19_XP.BMMinerS19XP
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.S19_XP.BMMinerS19XP
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -53,7 +61,7 @@
|
||||
|
||||
## T19
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X19.T19.BMMinerT19
|
||||
::: pyasic.miners.btc.antminer.bmminer.X19.T19.BMMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -62,7 +70,7 @@
|
||||
|
||||
## S19 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19.BOSMinerS19
|
||||
::: pyasic.miners.btc.antminer.bosminer.X19.S19.BOSMinerS19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -70,7 +78,7 @@
|
||||
|
||||
## S19 Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro
|
||||
::: pyasic.miners.btc.antminer.bosminer.X19.S19_Pro.BOSMinerS19Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -79,7 +87,7 @@
|
||||
|
||||
## S19j (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19j.BOSMinerS19j
|
||||
::: pyasic.miners.btc.antminer.bosminer.X19.S19j.BOSMinerS19j
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -87,7 +95,7 @@
|
||||
|
||||
## S19j Pro (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro
|
||||
::: pyasic.miners.btc.antminer.bosminer.X19.S19j_Pro.BOSMinerS19jPro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -95,7 +103,7 @@
|
||||
|
||||
## T19 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
::: pyasic.miners.btc.antminer.bosminer.X19.T19.BOSMinerT19
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
11
docs/miners/antminer/X3.md
Normal file
11
docs/miners/antminer/X3.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# pyasic
|
||||
## X3 Models
|
||||
|
||||
|
||||
## HS3
|
||||
|
||||
::: pyasic.miners.hns.antminer.cgminer.X3.HS3.CGMinerHS3
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
11
docs/miners/antminer/X5.md
Normal file
11
docs/miners/antminer/X5.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# pyasic
|
||||
## X5 Models
|
||||
|
||||
|
||||
## DR5
|
||||
|
||||
::: pyasic.miners.dcr.antminer.cgminer.X5.DR5.CGMinerDR5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
11
docs/miners/antminer/X7.md
Normal file
11
docs/miners/antminer/X7.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# pyasic
|
||||
## X7 Models
|
||||
|
||||
|
||||
## L7
|
||||
|
||||
::: pyasic.miners.ltc.antminer.bmminer.X7.L7.BMMinerL7
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## X9 (BOS)
|
||||
|
||||
::: pyasic.miners.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
::: pyasic.miners.btc.antminer.bosminer.X9.S9.BOSMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
## S9
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9.BMMinerS9
|
||||
::: pyasic.miners.btc.antminer.bmminer.X9.S9.BMMinerS9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
## S9i
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||
::: pyasic.miners.btc.antminer.bmminer.X9.S9i.BMMinerS9i
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -29,7 +29,15 @@
|
||||
|
||||
## T9
|
||||
|
||||
::: pyasic.miners.antminer.bmminer.X9.T9.BMMinerT9
|
||||
::: pyasic.miners.btc.antminer.bmminer.X9.T9.BMMinerT9
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## E9 Pro
|
||||
|
||||
::: pyasic.miners.etc.antminer.cgminer.X9.E9_Pro.CGMinerE9Pro
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## A1026
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1026.CGMinerAvalon1026
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## A1047
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1047.CGMinerAvalon1047
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## A1066
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A10X.A1066.CGMinerAvalon1066
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## A721
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A721.CGMinerAvalon721
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## A741
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A741.CGMinerAvalon741
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## A761
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A7X.A761.CGMinerAvalon761
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## A821
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A821.CGMinerAvalon821
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## A841
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A841.CGMinerAvalon841
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## A851
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A8X.A851.CGMinerAvalon851
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## A921
|
||||
|
||||
::: pyasic.miners.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
::: pyasic.miners.btc.avalonminer.cgminer.A9X.A921.CGMinerAvalon921
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
8
docs/miners/backends/bfgminer.md
Normal file
8
docs/miners/backends/bfgminer.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## BFGMiner Backend
|
||||
|
||||
::: pyasic.miners.backends.bfgminer.BFGMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BMMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bmminer.BMMiner
|
||||
::: pyasic.miners.backends.bmminer.BMMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.bosminer.BOSMiner
|
||||
::: pyasic.miners.backends.bosminer.BOSMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## BTMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.btminer.BTMiner
|
||||
::: pyasic.miners.backends.btminer.BTMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## CGMiner Backend
|
||||
|
||||
::: pyasic.miners._backends.cgminer.CGMiner
|
||||
::: pyasic.miners.backends.cgminer.CGMiner
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# pyasic
|
||||
## Hiveon Backend
|
||||
|
||||
::: pyasic.miners._backends.hiveon.Hiveon
|
||||
::: pyasic.miners.backends.hiveon.Hiveon
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
10
docs/miners/goldshell/CKX.md
Normal file
10
docs/miners/goldshell/CKX.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## CKX Models
|
||||
|
||||
## CK5
|
||||
|
||||
::: pyasic.miners.ckb.goldshell.bfgminer.CKX.CK5.BFGMinerCK5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
10
docs/miners/goldshell/HSX.md
Normal file
10
docs/miners/goldshell/HSX.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## HSX Models
|
||||
|
||||
## HS5
|
||||
|
||||
::: pyasic.miners.hns.goldshell.bfgminer.HSX.HS5.BFGMinerHS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
19
docs/miners/goldshell/KDX.md
Normal file
19
docs/miners/goldshell/KDX.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# pyasic
|
||||
## KDX Models
|
||||
|
||||
## KD5
|
||||
|
||||
::: pyasic.miners.kda.goldshell.bfgminer.KDX.KD5.BFGMinerKD5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
## KD Max
|
||||
|
||||
::: pyasic.miners.kda.goldshell.bfgminer.KDX.KDMax.BFGMinerKDMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
10
docs/miners/innosilicon/A10X.md
Normal file
10
docs/miners/innosilicon/A10X.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# pyasic
|
||||
## A10X Models
|
||||
|
||||
## A10X
|
||||
|
||||
::: pyasic.miners.etc.innosilicon.cgminer.A10X.A10X.CGMinerA10X
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## T3H+
|
||||
|
||||
::: pyasic.miners.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
|
||||
::: pyasic.miners.btc.innosilicon.cgminer.T3X.T3H_Plus.CGMinerInnosiliconT3HPlus
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -432,6 +432,7 @@ details {
|
||||
<summary>X19 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X19/#s19">S19</a></li>
|
||||
<li><a href="../antminer/X19/#s19l">S19L</a></li>
|
||||
<li><a href="../antminer/X19/#s19-pro">S19 Pro</a></li>
|
||||
<li><a href="../antminer/X19/#s19a">S19a</a></li>
|
||||
<li><a href="../antminer/X19/#s19j">S19j</a></li>
|
||||
@@ -453,11 +454,36 @@ details {
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X15 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X15/#z15">Z15</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X9 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X9/#s9">S9</a></li>
|
||||
<li><a href="../antminer/X9/#s9i">S9i</a></li>
|
||||
<li><a href="../antminer/X9/#t9">T9</a></li>
|
||||
<li><a href="../antminer/X9/#e9-pro">E9 Pro</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X7 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X7/#l7">L7</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X5 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X5/#dr5">DR5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>X3 Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../antminer/X3/#hs3">HS3</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
@@ -506,5 +532,35 @@ details {
|
||||
<li><a href="../innosilicon/T3X/#t3h">T3H+</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>A10X Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../innosilicon/A10X/#a10x">A10X</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>Stock Firmware BFGMinerGoldshell Miners:</summary>
|
||||
<ul>
|
||||
<details>
|
||||
<summary>CKX Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../goldshell/CKX/#ck5">CK5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>HSX Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../goldshell/HSX/#hs5">HS5</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
<details>
|
||||
<summary>KDX Series:</summary>
|
||||
<ul>
|
||||
<li><a href="../goldshell/KDX/#kd5">KD5</a></li>
|
||||
<li><a href="../goldshell/KDX/#kd-max">KD Max</a></li>
|
||||
</ul>
|
||||
</details>
|
||||
</ul>
|
||||
</details>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
## M20V10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20.BTMinerM20V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## M20SV10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## M20SV20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
## M20SV30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S.BTMinerM20SV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
## M20S+V30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M20S_Plus.BTMinerM20SPlusV30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
## M21V10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21.BTMinerM21V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
## M21SV20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
## M21SV60
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
## M21SV70
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S.BTMinerM21SV70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
## M21S+V20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M21S_Plus.BTMinerM21SPlusV20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
## M29V10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M2X.M29.BTMinerM29V10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M2X.M29.BTMinerM29V10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@
|
||||
|
||||
## M50VG30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VG30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
## M50VH10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
## M50VH20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
## M50VH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -35,7 +35,7 @@
|
||||
|
||||
## M50VH40
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
## M50VH50
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
## M50VH60
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH60
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -59,7 +59,7 @@
|
||||
|
||||
## M50VH70
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH70
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -67,7 +67,7 @@
|
||||
|
||||
## M50VH80
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VH80
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -75,7 +75,7 @@
|
||||
|
||||
## M50VJ10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -83,7 +83,7 @@
|
||||
|
||||
## M50VJ20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -91,7 +91,7 @@
|
||||
|
||||
## M50VJ30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50.BTMinerM50VJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -99,7 +99,7 @@
|
||||
|
||||
## M50SVJ10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -107,7 +107,7 @@
|
||||
|
||||
## M50SVJ20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -115,7 +115,7 @@
|
||||
|
||||
## M50SVJ30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -123,7 +123,7 @@
|
||||
|
||||
## M50SVH10
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH10
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
## M50SVH20
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH20
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -139,7 +139,7 @@
|
||||
|
||||
## M50SVH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -147,7 +147,7 @@
|
||||
|
||||
## M50SVH40
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -155,7 +155,7 @@
|
||||
|
||||
## M50SVH50
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S.BTMinerM50SVH50
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -163,7 +163,7 @@
|
||||
|
||||
## M50S+VH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -171,7 +171,7 @@
|
||||
|
||||
## M50S+VH40
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVH40
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -179,7 +179,7 @@
|
||||
|
||||
## M50S+VJ30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M50S_Plus.BTMinerM50SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -187,7 +187,7 @@
|
||||
|
||||
## M53VH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53.BTMinerM53VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -195,7 +195,7 @@
|
||||
|
||||
## M53SVH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53S.BTMinerM53SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -203,7 +203,7 @@
|
||||
|
||||
## M53S+VJ30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M53S_Plus.BTMinerM53SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -211,7 +211,7 @@
|
||||
|
||||
## M56VH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56.BTMinerM56VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -219,7 +219,7 @@
|
||||
|
||||
## M56SVH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56S.BTMinerM56SVH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
## M56S+VJ30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M56S_Plus.BTMinerM56SPlusVJ30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -235,7 +235,7 @@
|
||||
|
||||
## M59VH30
|
||||
|
||||
::: pyasic.miners.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
||||
::: pyasic.miners.btc.whatsminer.btminer.M5X.M59.BTMinerM59VH30
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
87
mkdocs.yml
87
mkdocs.yml
@@ -1,45 +1,54 @@
|
||||
site_name: pyasic
|
||||
repo_url: https://github.com/UpstreamData/pyasic
|
||||
nav:
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Miner Factory: "miners/miner_factory.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"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Supported Miners: "miners/supported_types.md"
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Miner Network Range: "network/miner_network_range.md"
|
||||
- Dataclasses:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BFGMiner: "API/bfgminer.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.md"
|
||||
- BOSMiner: "miners/backends/bosminer.md"
|
||||
- BFGMiner: "miners/backends/bfgminer.md"
|
||||
- BTMiner: "miners/backends/btminer.md"
|
||||
- CGMiner: "miners/backends/cgminer.md"
|
||||
- Hiveon: "miners/backends/hiveon.md"
|
||||
- Classes:
|
||||
- Antminer X3: "miners/antminer/X3.md"
|
||||
- Antminer X5: "miners/antminer/X5.md"
|
||||
- Antminer X7: "miners/antminer/X7.md"
|
||||
- Antminer X9: "miners/antminer/X9.md"
|
||||
- Antminer X15: "miners/antminer/X15.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"
|
||||
- Whatsminer M5X: "miners/whatsminer/M5X.md"
|
||||
- Innosilicon T3X: "miners/innosilicon/T3X.md"
|
||||
- Innosilicon A10X: "miners/innosilicon/A10X.md"
|
||||
- Goldshell CKX: "miners/goldshell/CKX.md"
|
||||
- Goldshell HSX: "miners/goldshell/HSX.md"
|
||||
- Goldshell KDX: "miners/goldshell/KDX.md"
|
||||
- Base Miner: "miners/base_miner.md"
|
||||
|
||||
|
||||
plugins:
|
||||
|
||||
442
poetry.lock
generated
442
poetry.lock
generated
@@ -1,6 +1,6 @@
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
version = "3.6.2"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -13,11 +13,11 @@ sniffio = ">=1.1"
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
|
||||
test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
trio = ["trio (>=0.16,<0.22)"]
|
||||
|
||||
[[package]]
|
||||
name = "asyncssh"
|
||||
version = "2.12.0"
|
||||
version = "2.13.1"
|
||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -38,7 +38,7 @@ pywin32 = ["pywin32 (>=227)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.9.24"
|
||||
version = "2022.12.7"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -76,15 +76,15 @@ colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||
|
||||
[[package]]
|
||||
name = "colorama"
|
||||
version = "0.4.5"
|
||||
version = "0.4.6"
|
||||
description = "Cross-platform colored terminal text."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "38.0.1"
|
||||
version = "39.0.1"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -94,12 +94,14 @@ python-versions = ">=3.6"
|
||||
cffi = ">=1.12"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
pep8test = ["black", "check-manifest", "mypy", "ruff", "types-pytz", "types-requests"]
|
||||
sdist = ["setuptools-rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"]
|
||||
test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-shard (>=0.1.2)", "pytest-subtests", "pytest-xdist", "pytz"]
|
||||
test-randomorder = ["pytest-randomly"]
|
||||
tox = ["tox"]
|
||||
|
||||
[[package]]
|
||||
name = "distlib"
|
||||
@@ -111,15 +113,15 @@ python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "filelock"
|
||||
version = "3.8.0"
|
||||
version = "3.9.0"
|
||||
description = "A platform independent file lock."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"]
|
||||
testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"]
|
||||
docs = ["furo (>=2022.12.7)", "sphinx (>=5.3)", "sphinx-autodoc-typehints (>=1.19.5)"]
|
||||
testing = ["covdefaults (>=2.2.2)", "coverage (>=7.0.1)", "pytest (>=7.2)", "pytest-cov (>=4)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "ghp-import"
|
||||
@@ -137,35 +139,38 @@ dev = ["flake8", "markdown", "twine", "wheel"]
|
||||
|
||||
[[package]]
|
||||
name = "griffe"
|
||||
version = "0.22.2"
|
||||
version = "0.25.5"
|
||||
description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
colorama = ">=0.4"
|
||||
|
||||
[package.extras]
|
||||
async = ["aiofiles (>=0.7,<1.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
version = "0.14.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.15.0"
|
||||
version = "0.16.3"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0,<4.0.0"
|
||||
anyio = ">=3.0,<5.0"
|
||||
certifi = "*"
|
||||
h11 = ">=0.11,<0.13"
|
||||
h11 = ">=0.13,<0.15"
|
||||
sniffio = ">=1.0.0,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
@@ -174,7 +179,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.23.0"
|
||||
version = "0.23.3"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -182,7 +187,7 @@ python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
httpcore = ">=0.15.0,<0.16.0"
|
||||
httpcore = ">=0.15.0,<0.17.0"
|
||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||
sniffio = "*"
|
||||
|
||||
@@ -194,7 +199,7 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "identify"
|
||||
version = "2.5.5"
|
||||
version = "2.5.18"
|
||||
description = "File identification library for Python"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -213,7 +218,7 @@ python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "importlib-metadata"
|
||||
version = "4.12.0"
|
||||
version = "6.0.0"
|
||||
description = "Read metadata from Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -223,9 +228,23 @@ python-versions = ">=3.7"
|
||||
zipp = ">=0.5"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=9)", "rst.linker (>=1.9)", "sphinx"]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
perf = ["ipython"]
|
||||
testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
|
||||
testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "isort"
|
||||
version = "5.12.0"
|
||||
description = "A Python utility / library to sort Python imports."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.8.0"
|
||||
|
||||
[package.extras]
|
||||
colors = ["colorama (>=0.4.3)"]
|
||||
pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
|
||||
plugins = ["setuptools"]
|
||||
requirements-deprecated-finder = ["pip-api", "pipreqs"]
|
||||
|
||||
[[package]]
|
||||
name = "Jinja2"
|
||||
@@ -257,7 +276,7 @@ testing = ["coverage", "pyyaml"]
|
||||
|
||||
[[package]]
|
||||
name = "MarkupSafe"
|
||||
version = "2.1.1"
|
||||
version = "2.1.2"
|
||||
description = "Safely add untrusted strings to HTML/XML markup."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -273,7 +292,7 @@ python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs"
|
||||
version = "1.4.0"
|
||||
version = "1.4.2"
|
||||
description = "Project documentation with Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -281,18 +300,20 @@ python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
click = ">=7.0"
|
||||
colorama = {version = ">=0.4", markers = "platform_system == \"Windows\""}
|
||||
ghp-import = ">=1.0"
|
||||
importlib-metadata = {version = ">=4.3", markers = "python_version < \"3.10\""}
|
||||
Jinja2 = ">=2.11.1"
|
||||
Markdown = ">=3.2.1,<3.4"
|
||||
jinja2 = ">=2.11.1"
|
||||
markdown = ">=3.2.1,<3.4"
|
||||
mergedeep = ">=1.3.4"
|
||||
packaging = ">=20.5"
|
||||
PyYAML = ">=5.1"
|
||||
pyyaml = ">=5.1"
|
||||
pyyaml-env-tag = ">=0.1"
|
||||
watchdog = ">=2.0"
|
||||
|
||||
[package.extras]
|
||||
i18n = ["babel (>=2.9.0)"]
|
||||
min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4)", "ghp-import (==1.0)", "importlib-metadata (==4.3)", "jinja2 (==2.11.1)", "markdown (==3.2.1)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "packaging (==20.5)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "typing-extensions (==3.10)", "watchdog (==2.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocs-autorefs"
|
||||
@@ -308,7 +329,7 @@ mkdocs = ">=1.1"
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings"
|
||||
version = "0.19.0"
|
||||
version = "0.20.0"
|
||||
description = "Automatic documentation from sources, for MkDocs."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -330,14 +351,14 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "mkdocstrings-python"
|
||||
version = "0.7.1"
|
||||
version = "0.8.3"
|
||||
description = "A Python handler for mkdocstrings."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
griffe = ">=0.11.1"
|
||||
griffe = ">=0.24"
|
||||
mkdocstrings = ">=0.19"
|
||||
|
||||
[[package]]
|
||||
@@ -353,14 +374,11 @@ setuptools = "*"
|
||||
|
||||
[[package]]
|
||||
name = "packaging"
|
||||
version = "21.3"
|
||||
version = "23.0"
|
||||
description = "Core utilities for Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
pyparsing = ">=2.0.2,<3.0.5 || >3.0.5"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
@@ -378,31 +396,30 @@ totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
version = "2.5.2"
|
||||
description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
version = "3.0.0"
|
||||
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"]
|
||||
test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"]
|
||||
test = ["appdirs (==1.4.4)", "covdefaults (>=2.2.2)", "pytest (>=7.2.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "pre-commit"
|
||||
version = "2.20.0"
|
||||
version = "3.1.0"
|
||||
description = "A framework for managing and maintaining multi-language pre-commit hooks."
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
python-versions = ">=3.8"
|
||||
|
||||
[package.dependencies]
|
||||
cfgv = ">=2.0.0"
|
||||
identify = ">=1.0.0"
|
||||
nodeenv = ">=0.11.1"
|
||||
pyyaml = ">=5.1"
|
||||
toml = "*"
|
||||
virtualenv = ">=20.0.8"
|
||||
virtualenv = ">=20.10.0"
|
||||
|
||||
[[package]]
|
||||
name = "pyaml"
|
||||
@@ -425,7 +442,7 @@ python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pymdown-extensions"
|
||||
version = "9.6"
|
||||
version = "9.9.2"
|
||||
description = "Extension pack for Python Markdown."
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -434,17 +451,6 @@ python-versions = ">=3.7"
|
||||
[package.dependencies]
|
||||
markdown = ">=3.2"
|
||||
|
||||
[[package]]
|
||||
name = "pyparsing"
|
||||
version = "3.0.9"
|
||||
description = "pyparsing module - Classes and methods to define and execute parsing grammars"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6.8"
|
||||
|
||||
[package.extras]
|
||||
diagrams = ["jinja2", "railroad-diagrams"]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.8.2"
|
||||
@@ -491,15 +497,15 @@ idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "setuptools"
|
||||
version = "65.4.1"
|
||||
version = "67.4.0"
|
||||
description = "Easily download, build, install, upgrade, and uninstall Python packages"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
|
||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||
testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||
|
||||
[[package]]
|
||||
@@ -528,7 +534,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
version = "4.5.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
@@ -536,24 +542,24 @@ python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "virtualenv"
|
||||
version = "20.16.5"
|
||||
version = "20.19.0"
|
||||
description = "Virtual Python Environment builder"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
distlib = ">=0.3.5,<1"
|
||||
distlib = ">=0.3.6,<1"
|
||||
filelock = ">=3.4.1,<4"
|
||||
platformdirs = ">=2.4,<3"
|
||||
platformdirs = ">=2.4,<4"
|
||||
|
||||
[package.extras]
|
||||
docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"]
|
||||
testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"]
|
||||
docs = ["furo (>=2022.12.7)", "proselint (>=0.13)", "sphinx (>=6.1.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"]
|
||||
test = ["covdefaults (>=2.2.2)", "coverage (>=7.1)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23)", "pytest (>=7.2.1)", "pytest-env (>=0.8.1)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.10)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)"]
|
||||
|
||||
[[package]]
|
||||
name = "watchdog"
|
||||
version = "2.1.9"
|
||||
version = "2.2.1"
|
||||
description = "Filesystem events monitoring"
|
||||
category = "dev"
|
||||
optional = false
|
||||
@@ -564,33 +570,33 @@ watchmedo = ["PyYAML (>=3.10)"]
|
||||
|
||||
[[package]]
|
||||
name = "zipp"
|
||||
version = "3.8.1"
|
||||
version = "3.14.0"
|
||||
description = "Backport of pathlib-compatible object wrapper for zip files"
|
||||
category = "dev"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.extras]
|
||||
docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"]
|
||||
testing = ["func-timeout", "jaraco.itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||
docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||
testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"]
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "98c3026a6f27c29c0357bbfa07166d6d8b604a869f3a802adc3bb3610f86964c"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "44b511a675e63d02eaf82f3ec78baa2364e5116e990f9f0a6fc2cb8f21cf616f"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
{file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"},
|
||||
{file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"},
|
||||
]
|
||||
asyncssh = [
|
||||
{file = "asyncssh-2.12.0-py3-none-any.whl", hash = "sha256:6841c4242c606fd51188c974ec2f4887efeec67ecdfa5b84140711dacd985ab3"},
|
||||
{file = "asyncssh-2.12.0.tar.gz", hash = "sha256:274101322c4b941823aeed8e1ab6e7be5191686c6db2d2bd35afeba30505e780"},
|
||||
{file = "asyncssh-2.13.1-py3-none-any.whl", hash = "sha256:c90eb5e2b4f9a7cc6e6af01fd844563d722c0d667f8c5f51fe5b3c2a79fa0575"},
|
||||
{file = "asyncssh-2.13.1.tar.gz", hash = "sha256:ebbb83c05c0b45cf230de1ef2f06059e360f9afa5c3ddf60fc92faf7b94ff887"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"},
|
||||
{file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"},
|
||||
{file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
|
||||
{file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
@@ -667,76 +673,77 @@ click = [
|
||||
{file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"},
|
||||
]
|
||||
colorama = [
|
||||
{file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"},
|
||||
{file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"},
|
||||
{file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
|
||||
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-win32.whl", hash = "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0"},
|
||||
{file = "cryptography-38.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a"},
|
||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d"},
|
||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9"},
|
||||
{file = "cryptography-38.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d"},
|
||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818"},
|
||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6"},
|
||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750"},
|
||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013"},
|
||||
{file = "cryptography-38.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5"},
|
||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61"},
|
||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac"},
|
||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb"},
|
||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a"},
|
||||
{file = "cryptography-38.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b"},
|
||||
{file = "cryptography-38.0.1.tar.gz", hash = "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_universal2.whl", hash = "sha256:6687ef6d0a6497e2b58e7c5b852b53f62142cfa7cd1555795758934da363a965"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-macosx_10_12_x86_64.whl", hash = "sha256:706843b48f9a3f9b9911979761c91541e3d90db1ca905fd63fee540a217698bc"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:5d2d8b87a490bfcd407ed9d49093793d0f75198a35e6eb1a923ce1ee86c62b41"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83e17b26de248c33f3acffb922748151d71827d6021d98c70e6c1a25ddd78505"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e124352fd3db36a9d4a21c1aa27fd5d051e621845cb87fb851c08f4f75ce8be6"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:5aa67414fcdfa22cf052e640cb5ddc461924a045cacf325cd164e65312d99502"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:35f7c7d015d474f4011e859e93e789c87d21f6f4880ebdc29896a60403328f1f"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f24077a3b5298a5a06a8e0536e3ea9ec60e4c7ac486755e5fb6e6ea9b3500106"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:f0c64d1bd842ca2633e74a1a28033d139368ad959872533b1bab8c80e8240a0c"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:0f8da300b5c8af9f98111ffd512910bc792b4c77392a9523624680f7956a99d4"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-win32.whl", hash = "sha256:fe913f20024eb2cb2f323e42a64bdf2911bb9738a15dba7d3cce48151034e3a8"},
|
||||
{file = "cryptography-39.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:ced4e447ae29ca194449a3f1ce132ded8fcab06971ef5f618605aacaa612beac"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:807ce09d4434881ca3a7594733669bd834f5b2c6d5c7e36f8c00f691887042ad"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c5caeb8188c24888c90b5108a441c106f7faa4c4c075a2bcae438c6e8ca73cef"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4789d1e3e257965e960232345002262ede4d094d1a19f4d3b52e48d4d8f3b885"},
|
||||
{file = "cryptography-39.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:96f1157a7c08b5b189b16b47bc9db2332269d6680a196341bf30046330d15388"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:e422abdec8b5fa8462aa016786680720d78bdce7a30c652b7fadf83a4ba35336"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:b0afd054cd42f3d213bf82c629efb1ee5f22eba35bf0eec88ea9ea7304f511a2"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:6f8ba7f0328b79f08bdacc3e4e66fb4d7aab0c3584e0bd41328dce5262e26b2e"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ef8b72fa70b348724ff1218267e7f7375b8de4e8194d1636ee60510aae104cd0"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:aec5a6c9864be7df2240c382740fcf3b96928c46604eaa7f3091f58b878c0bb6"},
|
||||
{file = "cryptography-39.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fdd188c8a6ef8769f148f88f859884507b954cc64db6b52f66ef199bb9ad660a"},
|
||||
{file = "cryptography-39.0.1.tar.gz", hash = "sha256:d1f6198ee6d9148405e49887803907fe8962a23e6c6f83ea7d98f1c0de375695"},
|
||||
]
|
||||
distlib = [
|
||||
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
|
||||
{file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"},
|
||||
]
|
||||
filelock = [
|
||||
{file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"},
|
||||
{file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"},
|
||||
{file = "filelock-3.9.0-py3-none-any.whl", hash = "sha256:f58d535af89bb9ad5cd4df046f741f8553a418c01a7856bf0d173bbc9f6bd16d"},
|
||||
{file = "filelock-3.9.0.tar.gz", hash = "sha256:7b319f24340b51f55a2bf7a12ac0755a9b03e718311dac567a0f4f7fabd2f5de"},
|
||||
]
|
||||
ghp-import = [
|
||||
{file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"},
|
||||
{file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"},
|
||||
]
|
||||
griffe = [
|
||||
{file = "griffe-0.22.2-py3-none-any.whl", hash = "sha256:cea5415ac6a92f4a22638e3f1f2e661402bac09fb8e8266936d67185a7e0d0fb"},
|
||||
{file = "griffe-0.22.2.tar.gz", hash = "sha256:1408e336a4155392bbd81eed9f2f44bf144e71b9c664e905630affe83bbc088e"},
|
||||
{file = "griffe-0.25.5-py3-none-any.whl", hash = "sha256:1fb9edff48e66d4873014a2ebf21aca5f271d0006a4c937826e3cf592ffb3706"},
|
||||
{file = "griffe-0.25.5.tar.gz", hash = "sha256:11ea3403ef0560a1cbcf7f302eb5d21cf4c1d8ed3f8a16a75aa9f6f458caf3f1"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
{file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
|
||||
{file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
||||
{file = "httpcore-0.16.3-py3-none-any.whl", hash = "sha256:da1fb708784a938aa084bde4feb8317056c55037247c787bd7e19eb2c2949dc0"},
|
||||
{file = "httpcore-0.16.3.tar.gz", hash = "sha256:c5d6f04e2fc530f39e0c077e6a30caa53f1451096120f1f38b954afd0b17c0cb"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
||||
{file = "httpx-0.23.3-py3-none-any.whl", hash = "sha256:a211fcce9b1254ea24f0cd6af9869b3d29aba40154e947d2a07bb499b3e310d6"},
|
||||
{file = "httpx-0.23.3.tar.gz", hash = "sha256:9818458eb565bb54898ccb9b8b251a28785dd4a55afbc23d0eb410754fe7d0f9"},
|
||||
]
|
||||
identify = [
|
||||
{file = "identify-2.5.5-py2.py3-none-any.whl", hash = "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97"},
|
||||
{file = "identify-2.5.5.tar.gz", hash = "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6"},
|
||||
{file = "identify-2.5.18-py2.py3-none-any.whl", hash = "sha256:93aac7ecf2f6abf879b8f29a8002d3c6de7086b8c28d88e1ad15045a15ab63f9"},
|
||||
{file = "identify-2.5.18.tar.gz", hash = "sha256:89e144fa560cc4cffb6ef2ab5e9fb18ed9f9b3cb054384bab4b95c12f6c309fe"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
|
||||
{file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
|
||||
]
|
||||
importlib-metadata = [
|
||||
{file = "importlib_metadata-4.12.0-py3-none-any.whl", hash = "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23"},
|
||||
{file = "importlib_metadata-4.12.0.tar.gz", hash = "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670"},
|
||||
{file = "importlib_metadata-6.0.0-py3-none-any.whl", hash = "sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad"},
|
||||
{file = "importlib_metadata-6.0.0.tar.gz", hash = "sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d"},
|
||||
]
|
||||
isort = [
|
||||
{file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
|
||||
{file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
|
||||
]
|
||||
Jinja2 = [
|
||||
{file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
|
||||
@@ -747,86 +754,96 @@ Markdown = [
|
||||
{file = "Markdown-3.3.7.tar.gz", hash = "sha256:cbb516f16218e643d8e0a95b309f77eb118cb138d39a4f27851e6a63581db874"},
|
||||
]
|
||||
MarkupSafe = [
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"},
|
||||
{file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"},
|
||||
{file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"},
|
||||
{file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"},
|
||||
{file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"},
|
||||
{file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:665a36ae6f8f20a4676b53224e33d456a6f5a72657d9c83c2aa00765072f31f7"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:340bea174e9761308703ae988e982005aedf427de816d1afe98147668cc03036"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22152d00bf4a9c7c83960521fc558f55a1adbc0631fbb00a9471e097b19d72e1"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28057e985dace2f478e042eaa15606c7efccb700797660629da387eb289b9323"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca244fa73f50a800cf8c3ebf7fd93149ec37f5cb9596aa8873ae2c1d23498601"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d9d971ec1e79906046aa3ca266de79eac42f1dbf3612a05dc9368125952bd1a1"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7e007132af78ea9df29495dbf7b5824cb71648d7133cf7848a2a5dd00d36f9ff"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7313ce6a199651c4ed9d7e4cfb4aa56fe923b1adf9af3b420ee14e6d9a73df65"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c4a549890a45f57f1ebf99c067a4ad0cb423a05544accaf2b065246827ed9603"},
|
||||
{file = "MarkupSafe-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:835fb5e38fd89328e9c81067fd642b3593c33e1e17e2fdbf77f5676abb14a156"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2ec4f2d48ae59bbb9d1f9d7efb9236ab81429a764dedca114f5fdabbc3788013"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608e7073dfa9e38a85d38474c082d4281f4ce276ac0010224eaba11e929dd53a"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:65608c35bfb8a76763f37036547f7adfd09270fbdbf96608be2bead319728fcd"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2bfb563d0211ce16b63c7cb9395d2c682a23187f54c3d79bfec33e6705473c6"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da25303d91526aac3672ee6d49a2f3db2d9502a4a60b55519feb1a4c7714e07d"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:9cad97ab29dfc3f0249b483412c85c8ef4766d96cdf9dcf5a1e3caa3f3661cf1"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:085fd3201e7b12809f9e6e9bc1e5c96a368c8523fad5afb02afe3c051ae4afcc"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1bea30e9bf331f3fef67e0a3877b2288593c98a21ccb2cf29b74c581a4eb3af0"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-win32.whl", hash = "sha256:7df70907e00c970c60b9ef2938d894a9381f38e6b9db73c5be35e59d92e06625"},
|
||||
{file = "MarkupSafe-2.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e55e40ff0cc8cc5c07996915ad367fa47da6b3fc091fdadca7f5403239c5fec3"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a6e40afa7f45939ca356f348c8e23048e02cb109ced1eb8420961b2f40fb373a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf877ab4ed6e302ec1d04952ca358b381a882fbd9d1b07cccbfd61783561f98a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:63ba06c9941e46fa389d389644e2d8225e0e3e5ebcc4ff1ea8506dce646f8c8a"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f1cd098434e83e656abf198f103a8207a8187c0fc110306691a2e94a78d0abb2"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a6f2fcca746e8d5910e18782f976489939d54a91f9411c32051b4aab2bd7c513"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0b462104ba25f1ac006fdab8b6a01ebbfbce9ed37fd37fd4acd70c67c973e460"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:7668b52e102d0ed87cb082380a7e2e1e78737ddecdde129acadb0eccc5423859"},
|
||||
{file = "MarkupSafe-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6d6607f98fcf17e534162f0709aaad3ab7a96032723d8ac8750ffe17ae5a0666"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a806db027852538d2ad7555b203300173dd1b77ba116de92da9afbc3a3be3eed"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a4abaec6ca3ad8660690236d11bfe28dfd707778e2442b45addd2f086d6ef094"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f03a532d7dee1bed20bc4884194a16160a2de9ffc6354b3878ec9682bb623c54"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4cf06cdc1dda95223e9d2d3c58d3b178aa5dacb35ee7e3bbac10e4e1faacb419"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:22731d79ed2eb25059ae3df1dfc9cb1546691cc41f4e3130fe6bfbc3ecbbecfa"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:f8ffb705ffcf5ddd0e80b65ddf7bed7ee4f5a441ea7d3419e861a12eaf41af58"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8db032bf0ce9022a8e41a22598eefc802314e81b879ae093f36ce9ddf39ab1ba"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2298c859cfc5463f1b64bd55cb3e602528db6fa0f3cfd568d3605c50678f8f03"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-win32.whl", hash = "sha256:50c42830a633fa0cf9e7d27664637532791bfc31c731a87b202d2d8ac40c3ea2"},
|
||||
{file = "MarkupSafe-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:bb06feb762bade6bf3c8b844462274db0c76acc95c52abe8dbed28ae3d44a147"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:99625a92da8229df6d44335e6fcc558a5037dd0a760e11d84be2260e6f37002f"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bca7e26c1dd751236cfb0c6c72d4ad61d986e9a41bbf76cb445f69488b2a2bd"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40dfd3fefbef579ee058f139733ac336312663c6706d1163b82b3003fb1925c4"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2e7821bffe00aa6bd07a23913b7f4e01328c3d5cc0b40b36c0bd81d362faeb65"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c0a33bc9f02c2b17c3ea382f91b4db0e6cde90b63b296422a939886a7a80de1c"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b8526c6d437855442cdd3d87eede9c425c4445ea011ca38d937db299382e6fa3"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-win32.whl", hash = "sha256:137678c63c977754abe9086a3ec011e8fd985ab90631145dfb9294ad09c102a7"},
|
||||
{file = "MarkupSafe-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:0576fe974b40a400449768941d5d0858cc624e3249dfd1e0c33674e5c7ca7aed"},
|
||||
{file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"},
|
||||
]
|
||||
mergedeep = [
|
||||
{file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"},
|
||||
{file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"},
|
||||
]
|
||||
mkdocs = [
|
||||
{file = "mkdocs-1.4.0-py3-none-any.whl", hash = "sha256:ce057e9992f017b8e1496b591b6c242cbd34c2d406e2f9af6a19b97dd6248faa"},
|
||||
{file = "mkdocs-1.4.0.tar.gz", hash = "sha256:e5549a22d59e7cb230d6a791edd2c3d06690908454c0af82edc31b35d57e3069"},
|
||||
{file = "mkdocs-1.4.2-py3-none-any.whl", hash = "sha256:c8856a832c1e56702577023cd64cc5f84948280c1c0fcc6af4cd39006ea6aa8c"},
|
||||
{file = "mkdocs-1.4.2.tar.gz", hash = "sha256:8947af423a6d0facf41ea1195b8e1e8c85ad94ac95ae307fe11232e0424b11c5"},
|
||||
]
|
||||
mkdocs-autorefs = [
|
||||
{file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"},
|
||||
{file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"},
|
||||
]
|
||||
mkdocstrings = [
|
||||
{file = "mkdocstrings-0.19.0-py3-none-any.whl", hash = "sha256:3217d510d385c961f69385a670b2677e68e07b5fea4a504d86bf54c006c87c7d"},
|
||||
{file = "mkdocstrings-0.19.0.tar.gz", hash = "sha256:efa34a67bad11229d532d89f6836a8a215937548623b64f3698a1df62e01cc3e"},
|
||||
{file = "mkdocstrings-0.20.0-py3-none-any.whl", hash = "sha256:f17fc2c4f760ec302b069075ef9e31045aa6372ca91d2f35ded3adba8e25a472"},
|
||||
{file = "mkdocstrings-0.20.0.tar.gz", hash = "sha256:c757f4f646d4f939491d6bc9256bfe33e36c5f8026392f49eaa351d241c838e5"},
|
||||
]
|
||||
mkdocstrings-python = [
|
||||
{file = "mkdocstrings-python-0.7.1.tar.gz", hash = "sha256:c334b382dca202dfa37071c182418a6df5818356a95d54362a2b24822ca3af71"},
|
||||
{file = "mkdocstrings_python-0.7.1-py3-none-any.whl", hash = "sha256:a22060bfa374697678e9af4e62b020d990dad2711c98f7a9fac5c0345bef93c7"},
|
||||
{file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"},
|
||||
{file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"},
|
||||
]
|
||||
nodeenv = [
|
||||
{file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"},
|
||||
{file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"},
|
||||
]
|
||||
packaging = [
|
||||
{file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"},
|
||||
{file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"},
|
||||
{file = "packaging-23.0-py3-none-any.whl", hash = "sha256:714ac14496c3e68c99c29b00845f7a2b85f3bb6f1078fd9f72fd20f0570002b2"},
|
||||
{file = "packaging-23.0.tar.gz", hash = "sha256:b6ad297f8907de0fa2fe1ccbd26fdaf387f5f47c7275fedf8cce89f99446cf97"},
|
||||
]
|
||||
passlib = [
|
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"},
|
||||
{file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"},
|
||||
{file = "platformdirs-3.0.0-py3-none-any.whl", hash = "sha256:b1d5eb14f221506f50d6604a561f4c5786d9e80355219694a1b244bcd96f4567"},
|
||||
{file = "platformdirs-3.0.0.tar.gz", hash = "sha256:8a1228abb1ef82d788f74139988b137e78692984ec7b08eaa6c65f1723af28f9"},
|
||||
]
|
||||
pre-commit = [
|
||||
{file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"},
|
||||
{file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"},
|
||||
{file = "pre_commit-3.1.0-py2.py3-none-any.whl", hash = "sha256:7001dfcd174540658822b1fd3630ceadf4f41375a5d1844b5c3b3830f227348c"},
|
||||
{file = "pre_commit-3.1.0.tar.gz", hash = "sha256:61bd9f1b96d3d1e763f2a9a0f8522aed341646800642ff6803c73fac5781f5b7"},
|
||||
]
|
||||
pyaml = [
|
||||
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
|
||||
@@ -837,12 +854,8 @@ pycparser = [
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pymdown-extensions = [
|
||||
{file = "pymdown_extensions-9.6-py3-none-any.whl", hash = "sha256:1e36490adc7bfcef1fdb21bb0306e93af99cff8ec2db199bd17e3bf009768c11"},
|
||||
{file = "pymdown_extensions-9.6.tar.gz", hash = "sha256:b956b806439bbff10f726103a941266beb03fbe99f897c7d5e774d7170339ad9"},
|
||||
]
|
||||
pyparsing = [
|
||||
{file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"},
|
||||
{file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"},
|
||||
{file = "pymdown_extensions-9.9.2-py3-none-any.whl", hash = "sha256:c3d804eb4a42b85bafb5f36436342a5ad38df03878bb24db8855a4aa8b08b765"},
|
||||
{file = "pymdown_extensions-9.9.2.tar.gz", hash = "sha256:ebb33069bafcb64d5f5988043331d4ea4929325dc678a6bcf247ddfcf96499f8"},
|
||||
]
|
||||
python-dateutil = [
|
||||
{file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"},
|
||||
@@ -899,8 +912,8 @@ rfc3986 = [
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
setuptools = [
|
||||
{file = "setuptools-65.4.1-py3-none-any.whl", hash = "sha256:1b6bdc6161661409c5f21508763dc63ab20a9ac2f8ba20029aaaa7fdb9118012"},
|
||||
{file = "setuptools-65.4.1.tar.gz", hash = "sha256:3050e338e5871e70c72983072fe34f6032ae1cdeeeb67338199c2f74e083a80e"},
|
||||
{file = "setuptools-67.4.0-py3-none-any.whl", hash = "sha256:f106dee1b506dee5102cc3f3e9e68137bbad6d47b616be7991714b0c62204251"},
|
||||
{file = "setuptools-67.4.0.tar.gz", hash = "sha256:e5fd0a713141a4a105412233c63dc4e17ba0090c8e8334594ac790ec97792330"},
|
||||
]
|
||||
six = [
|
||||
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||
@@ -915,41 +928,44 @@ toml = [
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
{file = "typing_extensions-4.5.0-py3-none-any.whl", hash = "sha256:fb33085c39dd998ac16d1431ebc293a8b3eedd00fd4a32de0ff79002c19511b4"},
|
||||
{file = "typing_extensions-4.5.0.tar.gz", hash = "sha256:5cb5f4a79139d699607b3ef622a1dedafa84e115ab0024e0d9c044a9479ca7cb"},
|
||||
]
|
||||
virtualenv = [
|
||||
{file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"},
|
||||
{file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"},
|
||||
{file = "virtualenv-20.19.0-py3-none-any.whl", hash = "sha256:54eb59e7352b573aa04d53f80fc9736ed0ad5143af445a1e539aada6eb947dd1"},
|
||||
{file = "virtualenv-20.19.0.tar.gz", hash = "sha256:37a640ba82ed40b226599c522d411e4be5edb339a0c0de030c0dc7b646d61590"},
|
||||
]
|
||||
watchdog = [
|
||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a735a990a1095f75ca4f36ea2ef2752c99e6ee997c46b0de507ba40a09bf7330"},
|
||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b17d302850c8d412784d9246cfe8d7e3af6bcd45f958abb2d08a6f8bedf695d"},
|
||||
{file = "watchdog-2.1.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ee3e38a6cc050a8830089f79cbec8a3878ec2fe5160cdb2dc8ccb6def8552658"},
|
||||
{file = "watchdog-2.1.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64a27aed691408a6abd83394b38503e8176f69031ca25d64131d8d640a307591"},
|
||||
{file = "watchdog-2.1.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:195fc70c6e41237362ba720e9aaf394f8178bfc7fa68207f112d108edef1af33"},
|
||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bfc4d351e6348d6ec51df007432e6fe80adb53fd41183716017026af03427846"},
|
||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8250546a98388cbc00c3ee3cc5cf96799b5a595270dfcfa855491a64b86ef8c3"},
|
||||
{file = "watchdog-2.1.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:117ffc6ec261639a0209a3252546b12800670d4bf5f84fbd355957a0595fe654"},
|
||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:97f9752208f5154e9e7b76acc8c4f5a58801b338de2af14e7e181ee3b28a5d39"},
|
||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:247dcf1df956daa24828bfea5a138d0e7a7c98b1a47cf1fa5b0c3c16241fcbb7"},
|
||||
{file = "watchdog-2.1.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:226b3c6c468ce72051a4c15a4cc2ef317c32590d82ba0b330403cafd98a62cfd"},
|
||||
{file = "watchdog-2.1.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d9820fe47c20c13e3c9dd544d3706a2a26c02b2b43c993b62fcd8011bcc0adb3"},
|
||||
{file = "watchdog-2.1.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:70af927aa1613ded6a68089a9262a009fbdf819f46d09c1a908d4b36e1ba2b2d"},
|
||||
{file = "watchdog-2.1.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed80a1628cee19f5cfc6bb74e173f1b4189eb532e705e2a13e3250312a62e0c9"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_aarch64.whl", hash = "sha256:9f05a5f7c12452f6a27203f76779ae3f46fa30f1dd833037ea8cbc2887c60213"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_armv7l.whl", hash = "sha256:255bb5758f7e89b1a13c05a5bceccec2219f8995a3a4c4d6968fe1de6a3b2892"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_i686.whl", hash = "sha256:d3dda00aca282b26194bdd0adec21e4c21e916956d972369359ba63ade616153"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64.whl", hash = "sha256:186f6c55abc5e03872ae14c2f294a153ec7292f807af99f57611acc8caa75306"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:083171652584e1b8829581f965b9b7723ca5f9a2cd7e20271edf264cfd7c1412"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_s390x.whl", hash = "sha256:b530ae007a5f5d50b7fbba96634c7ee21abec70dc3e7f0233339c81943848dc1"},
|
||||
{file = "watchdog-2.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:4f4e1c4aa54fb86316a62a87b3378c025e228178d55481d30d857c6c438897d6"},
|
||||
{file = "watchdog-2.1.9-py3-none-win32.whl", hash = "sha256:5952135968519e2447a01875a6f5fc8c03190b24d14ee52b0f4b1682259520b1"},
|
||||
{file = "watchdog-2.1.9-py3-none-win_amd64.whl", hash = "sha256:7a833211f49143c3d336729b0020ffd1274078e94b0ae42e22f596999f50279c"},
|
||||
{file = "watchdog-2.1.9-py3-none-win_ia64.whl", hash = "sha256:ad576a565260d8f99d97f2e64b0f97a48228317095908568a9d5c786c829d428"},
|
||||
{file = "watchdog-2.1.9.tar.gz", hash = "sha256:43ce20ebb36a51f21fa376f76d1d4692452b2527ccd601950d69ed36b9e21609"},
|
||||
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a09483249d25cbdb4c268e020cb861c51baab2d1affd9a6affc68ffe6a231260"},
|
||||
{file = "watchdog-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5100eae58133355d3ca6c1083a33b81355c4f452afa474c2633bd2fbbba398b3"},
|
||||
{file = "watchdog-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e618a4863726bc7a3c64f95c218437f3349fb9d909eb9ea3a1ed3b567417c661"},
|
||||
{file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:102a60093090fc3ff76c983367b19849b7cc24ec414a43c0333680106e62aae1"},
|
||||
{file = "watchdog-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:748ca797ff59962e83cc8e4b233f87113f3cf247c23e6be58b8a2885c7337aa3"},
|
||||
{file = "watchdog-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6ccd8d84b9490a82b51b230740468116b8205822ea5fdc700a553d92661253a3"},
|
||||
{file = "watchdog-2.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6e01d699cd260d59b84da6bda019dce0a3353e3fcc774408ae767fe88ee096b7"},
|
||||
{file = "watchdog-2.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8586d98c494690482c963ffb24c49bf9c8c2fe0589cec4dc2f753b78d1ec301d"},
|
||||
{file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:adaf2ece15f3afa33a6b45f76b333a7da9256e1360003032524d61bdb4c422ae"},
|
||||
{file = "watchdog-2.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83a7cead445008e880dbde833cb9e5cc7b9a0958edb697a96b936621975f15b9"},
|
||||
{file = "watchdog-2.2.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f8ac23ff2c2df4471a61af6490f847633024e5aa120567e08d07af5718c9d092"},
|
||||
{file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d0f29fd9f3f149a5277929de33b4f121a04cf84bb494634707cfa8ea8ae106a8"},
|
||||
{file = "watchdog-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:967636031fa4c4955f0f3f22da3c5c418aa65d50908d31b73b3b3ffd66d60640"},
|
||||
{file = "watchdog-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:96cbeb494e6cbe3ae6aacc430e678ce4b4dd3ae5125035f72b6eb4e5e9eb4f4e"},
|
||||
{file = "watchdog-2.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61fdb8e9c57baf625e27e1420e7ca17f7d2023929cd0065eb79c83da1dfbeacd"},
|
||||
{file = "watchdog-2.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4cb5ecc332112017fbdb19ede78d92e29a8165c46b68a0b8ccbd0a154f196d5e"},
|
||||
{file = "watchdog-2.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a480d122740debf0afac4ddd583c6c0bb519c24f817b42ed6f850e2f6f9d64a8"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_aarch64.whl", hash = "sha256:978a1aed55de0b807913b7482d09943b23a2d634040b112bdf31811a422f6344"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_armv7l.whl", hash = "sha256:8c28c23972ec9c524967895ccb1954bc6f6d4a557d36e681a36e84368660c4ce"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_i686.whl", hash = "sha256:c27d8c1535fd4474e40a4b5e01f4ba6720bac58e6751c667895cbc5c8a7af33c"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64.whl", hash = "sha256:d6b87477752bd86ac5392ecb9eeed92b416898c30bd40c7e2dd03c3146105646"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_ppc64le.whl", hash = "sha256:cece1aa596027ff56369f0b50a9de209920e1df9ac6d02c7f9e5d8162eb4f02b"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_s390x.whl", hash = "sha256:8b5cde14e5c72b2df5d074774bdff69e9b55da77e102a91f36ef26ca35f9819c"},
|
||||
{file = "watchdog-2.2.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:e038be858425c4f621900b8ff1a3a1330d9edcfeaa1c0468aeb7e330fb87693e"},
|
||||
{file = "watchdog-2.2.1-py3-none-win32.whl", hash = "sha256:bc43c1b24d2f86b6e1cc15f68635a959388219426109233e606517ff7d0a5a73"},
|
||||
{file = "watchdog-2.2.1-py3-none-win_amd64.whl", hash = "sha256:17f1708f7410af92ddf591e94ae71a27a13974559e72f7e9fde3ec174b26ba2e"},
|
||||
{file = "watchdog-2.2.1-py3-none-win_ia64.whl", hash = "sha256:195ab1d9d611a4c1e5311cbf42273bc541e18ea8c32712f2fb703cfc6ff006f9"},
|
||||
{file = "watchdog-2.2.1.tar.gz", hash = "sha256:cdcc23c9528601a8a293eb4369cbd14f6b4f34f07ae8769421252e9c22718b6f"},
|
||||
]
|
||||
zipp = [
|
||||
{file = "zipp-3.8.1-py3-none-any.whl", hash = "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009"},
|
||||
{file = "zipp-3.8.1.tar.gz", hash = "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2"},
|
||||
{file = "zipp-3.14.0-py3-none-any.whl", hash = "sha256:188834565033387710d046e3fe96acfc9b5e86cbca7f39ff69cf21a4128198b7"},
|
||||
{file = "zipp-3.14.0.tar.gz", hash = "sha256:9e5421e176ef5ab4c0ad896624e87a7b2f07aca746c9b2aa305952800cb8eecb"},
|
||||
]
|
||||
|
||||
@@ -101,17 +101,25 @@ class BaseMinerAPI:
|
||||
allow_warning: A boolean to supress APIWarnings.
|
||||
|
||||
"""
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# standard format doesn't work for X19
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
return {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
return data
|
||||
while True:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# standard format doesn't work for X19
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError as e:
|
||||
# try to identify the error
|
||||
if ":" in e.message:
|
||||
err_command = e.message.split(":")[0]
|
||||
if err_command in commands:
|
||||
commands.remove(err_command)
|
||||
continue
|
||||
return {command: [{}] for command in commands}
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
@property
|
||||
def commands(self) -> list:
|
||||
@@ -142,9 +150,10 @@ class BaseMinerAPI:
|
||||
]
|
||||
|
||||
def _check_commands(self, *commands):
|
||||
allowed_commands = self.get_commands()
|
||||
allowed_commands = self.commands
|
||||
return_commands = []
|
||||
for command in [*commands]:
|
||||
|
||||
for command in commands:
|
||||
if command in allowed_commands:
|
||||
return_commands.append(command)
|
||||
else:
|
||||
|
||||
674
pyasic/API/bfgminer.py
Normal file
674
pyasic/API/bfgminer.py
Normal file
@@ -0,0 +1,674 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
|
||||
from pyasic.API import APIError, BaseMinerAPI
|
||||
|
||||
|
||||
class BFGMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the BFGMiner API.
|
||||
|
||||
Each method corresponds to an API command in BFGMiner.
|
||||
|
||||
[BFGMiner API documentation](https://github.com/luke-jr/bfgminer/blob/bfgminer/README.RPC)
|
||||
|
||||
This class abstracts use of the BFGMiner API, as well as the
|
||||
methods for sending commands to it. The self.send_command()
|
||||
function handles sending a command to the miner asynchronously, and
|
||||
as such is the base for many of the functions in this class, which
|
||||
rely on it to send the command for them.
|
||||
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0", port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def multicommand(self, *commands: str, allow_warning: bool = True) -> dict:
|
||||
# make sure we can actually run each command, otherwise they will fail
|
||||
commands = self._check_commands(*commands)
|
||||
# standard multicommand format is "command1+command2"
|
||||
# doesn't work for S19 which uses the backup _x19_multicommand
|
||||
command = "+".join(commands)
|
||||
try:
|
||||
data = await self.send_command(command, allow_warning=allow_warning)
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
f"{self} - ([Hidden] X19 Multicommand) - API Command Error {e}"
|
||||
)
|
||||
return data
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner version information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Some miner configuration information:
|
||||
* ASC Count <- the number of ASCs
|
||||
* PGA Count <- the number of PGAs
|
||||
* Pool Count <- the number of Pools
|
||||
* Strategy <- the current pool strategy
|
||||
* Log Interval <- the interval of logging
|
||||
* Device Code <- list of compiled device drivers
|
||||
* OS <- the current operating system
|
||||
* Failover-Only <- failover-only setting
|
||||
* Scan Time <- scan-time setting
|
||||
* Queue <- queue setting
|
||||
* Expiry <- expiry setting
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The status summary of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner pool information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def procs(self) -> dict:
|
||||
"""Get data on each processor with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on each processor with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procs")
|
||||
|
||||
async def devscan(self, info: str = "") -> dict:
|
||||
"""Get data on each processor with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
info: Info to scan for device by.
|
||||
|
||||
Returns:
|
||||
Data on each processor with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devscan", parameters=info)
|
||||
|
||||
async def pga(self, n: int) -> dict:
|
||||
"""Get data from PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA number to get data from.
|
||||
|
||||
Returns:
|
||||
Data on the PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pga", parameters=n)
|
||||
|
||||
async def proc(self, n: int = 0) -> dict:
|
||||
"""Get data processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to get data on.
|
||||
|
||||
Returns:
|
||||
Data on processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("proc", parameters=n)
|
||||
|
||||
async def pgacount(self) -> dict:
|
||||
"""Get data fon all PGAs.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the PGAs connected.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgacount")
|
||||
|
||||
async def proccount(self) -> dict:
|
||||
"""Get data fon all processors.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the processors connected.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("proccount")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
"""Switch pools to pool n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The pool to switch to.
|
||||
|
||||
Returns:
|
||||
A confirmation of switching to pool n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
"""Enable pool n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The pool to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling pool n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("enablepool", parameters=n)
|
||||
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
"""Add a pool to the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
url: The URL of the new pool to add.
|
||||
username: The users username on the new pool.
|
||||
password: The worker password on the new pool.
|
||||
|
||||
Returns:
|
||||
A confirmation of adding the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url},{username},{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
"""Set pool priority.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
*n: Pools in order of priority.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting pool priority.
|
||||
</details>
|
||||
"""
|
||||
pools = f"{','.join([str(item) for item in n])}"
|
||||
return await self.send_command("poolpriority", parameters=pools)
|
||||
|
||||
async def poolquota(self, n: int, q: int) -> dict:
|
||||
"""Set pool quota.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool number to set quota on.
|
||||
q: Quota to set the pool to.
|
||||
|
||||
Returns:
|
||||
A confirmation of setting pool quota.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n},{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of diabling the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
"""Remove a pool.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: Pool to remove.
|
||||
|
||||
Returns:
|
||||
A confirmation of removing the pool.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("removepool", parameters=n)
|
||||
|
||||
async def save(self, filename: str = None) -> dict:
|
||||
"""Save the config.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
filename: Filename to save the config as.
|
||||
|
||||
Returns:
|
||||
A confirmation of saving the config.
|
||||
</details>
|
||||
"""
|
||||
if filename:
|
||||
return await self.send_command("save", parameters=filename)
|
||||
else:
|
||||
return await self.send_command("save")
|
||||
|
||||
async def quit(self) -> dict:
|
||||
"""Quit CGMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A single "BYE" before CGMiner quits.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("quit")
|
||||
|
||||
async def notify(self) -> dict:
|
||||
"""Notify the user of past errors.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The last status and count of each devices problem(s).
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("notify")
|
||||
|
||||
async def privileged(self) -> dict:
|
||||
"""Check if you have privileged access.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
The STATUS section with an error if you have no privileged access, or success if you have privileged access.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("privileged")
|
||||
|
||||
async def pgaenable(self, n: int) -> dict:
|
||||
"""Enable PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgaenable", parameters=n)
|
||||
|
||||
async def pgadisable(self, n: int) -> dict:
|
||||
"""Disable PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of disabling PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgarestart(self, n: int) -> dict:
|
||||
"""Restart PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to restart.
|
||||
|
||||
Returns:
|
||||
A confirmation of restarting PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgaidentify(self, n: int) -> dict:
|
||||
"""Identify PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The PGA to identify.
|
||||
|
||||
Returns:
|
||||
A confirmation of identifying PGA n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pgaidentify", parameters=n)
|
||||
|
||||
async def procenable(self, n: int) -> dict:
|
||||
"""Enable processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to enable.
|
||||
|
||||
Returns:
|
||||
A confirmation of enabling processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procenable", parameters=n)
|
||||
|
||||
async def procdisable(self, n: int) -> dict:
|
||||
"""Disable processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to disable.
|
||||
|
||||
Returns:
|
||||
A confirmation of disabling processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procdisable", parameters=n)
|
||||
|
||||
async def procrestart(self, n: int) -> dict:
|
||||
"""Restart processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to restart.
|
||||
|
||||
Returns:
|
||||
A confirmation of restarting processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procdisable", parameters=n)
|
||||
|
||||
async def procidentify(self, n: int) -> dict:
|
||||
"""Identify processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
n: The processor to identify.
|
||||
|
||||
Returns:
|
||||
A confirmation of identifying processor n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("procidentify", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart CGMiner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
A reply informing of the restart.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("restart")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Stats of each device/pool with more than 1 getwork.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def check(self, command: str) -> dict:
|
||||
"""Check if the command command exists in CGMiner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
command: The command to check.
|
||||
|
||||
Returns:
|
||||
## Information about a command:
|
||||
* Exists (Y/N) <- the command exists in this version
|
||||
* Access (Y/N) <- you have access to use the command
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def failover_only(self, failover: bool) -> dict:
|
||||
"""Set failover-only.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
failover: What to set failover-only to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting failover-only.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("failover-only", parameters=failover)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
## Information about the current coin being mined:
|
||||
* Hash Method <- the hashing algorithm
|
||||
* Current Block Time <- blocktime as a float, 0 means none
|
||||
* Current Block Hash <- the hash of the current block, blank means none
|
||||
* LP <- whether LP is in use on at least 1 pool
|
||||
* Network Difficulty: the current network difficulty
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def debug(self, setting: str) -> dict:
|
||||
"""Set a debug setting.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
setting: Which setting to switch to.
|
||||
## Options are:
|
||||
* Silent
|
||||
* Quiet
|
||||
* Verbose
|
||||
* Debug
|
||||
* RPCProto
|
||||
* PerDevice
|
||||
* WorkTime
|
||||
* Normal
|
||||
|
||||
Returns:
|
||||
Data on which debug setting was enabled or disabled.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("debug", parameters=setting)
|
||||
|
||||
async def setconfig(self, name: str, n: int) -> dict:
|
||||
"""Set config of name to value n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
name: The name of the config setting to set.
|
||||
## Options are:
|
||||
* queue
|
||||
* scantime
|
||||
* expiry
|
||||
n: The value to set the 'name' setting to.
|
||||
|
||||
Returns:
|
||||
The results of setting config of name to n.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name},{n}")
|
||||
|
||||
async def pgaset(self, n: int, opt: str, val: int = None) -> dict:
|
||||
"""Set PGA option opt to val on PGA n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Options:
|
||||
```
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
XBS -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
```
|
||||
|
||||
Parameters:
|
||||
n: The PGA to set the options on.
|
||||
opt: The option to set. Setting this to 'help' returns a help message.
|
||||
val: The value to set the option to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting PGA n with opt[,val].
|
||||
</details>
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def pprocset(self, n: int, opt: str, val: int = None) -> dict:
|
||||
"""Set processor option opt to val on processor n.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Options:
|
||||
```
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
XBS -
|
||||
opt: clock
|
||||
val: 2 - 250 (multiple of 2)
|
||||
```
|
||||
|
||||
Parameters:
|
||||
n: The PGA to set the options on.
|
||||
opt: The option to set. Setting this to 'help' returns a help message.
|
||||
val: The value to set the option to.
|
||||
|
||||
Returns:
|
||||
Confirmation of setting PGA n with opt[,val].
|
||||
</details>
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt},{val}")
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n},{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Parameters:
|
||||
which: Which device to zero. Setting this to 'all' zeros all devices. Setting this to 'bestshare' zeros only the bestshare values for each pool and global.
|
||||
summary: Whether or not to show a full summary.
|
||||
|
||||
|
||||
Returns:
|
||||
the STATUS section with info on the zero and optional summary.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which},{summary}")
|
||||
@@ -53,9 +53,10 @@ class BMMinerAPI(BaseMinerAPI):
|
||||
data = await self._x19_multicommand(
|
||||
*command.split("+"), allow_warning=allow_warning
|
||||
)
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True):
|
||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
|
||||
@@ -224,6 +224,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
logging.debug(f"{self} - (Multicommand) - Received data")
|
||||
|
||||
data = dict(**main_data, **get_commands_data)
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def send_privileged_command(
|
||||
@@ -246,7 +247,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
logging.debug(f"{self} - (Send Privileged Command) - Sending")
|
||||
try:
|
||||
data = await self._send_bytes(enc_command, timeout)
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError) as e:
|
||||
except (asyncio.CancelledError, asyncio.TimeoutError):
|
||||
if ignore_errors:
|
||||
return {}
|
||||
raise APIError("No data was returned from the API.")
|
||||
@@ -767,7 +768,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
|
||||
"""
|
||||
return await self.send_privileged_command(
|
||||
"adjust_power_limit", power_limit=power_limit
|
||||
"adjust_power_limit", power_limit=str(power_limit)
|
||||
)
|
||||
|
||||
@api_min_version("2.0.5")
|
||||
|
||||
@@ -52,9 +52,10 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
except APIError:
|
||||
logging.debug(f"{self} - (Multicommand) - Handling X19 multicommand.")
|
||||
data = await self._x19_multicommand(*command.split("+"))
|
||||
data["multicommand"] = True
|
||||
return data
|
||||
|
||||
async def _x19_multicommand(self, *commands):
|
||||
async def _x19_multicommand(self, *commands) -> dict:
|
||||
data = None
|
||||
try:
|
||||
data = {}
|
||||
@@ -62,7 +63,7 @@ class CGMinerAPI(BaseMinerAPI):
|
||||
for cmd in commands:
|
||||
data[cmd] = []
|
||||
data[cmd].append(await self.send_command(cmd, allow_warning=True))
|
||||
except APIError as e:
|
||||
except APIError:
|
||||
pass
|
||||
except Exception as e:
|
||||
logging.warning(
|
||||
|
||||
@@ -14,18 +14,24 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import json
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from typing import Dict, List, Literal
|
||||
from enum import IntEnum
|
||||
from typing import List, Literal
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
|
||||
|
||||
class X19PowerMode(IntEnum):
|
||||
Normal = 0
|
||||
Sleep = 1
|
||||
LPM = 3
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Pool:
|
||||
"""A dataclass for pool information.
|
||||
@@ -85,6 +91,32 @@ class _Pool:
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an X5 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an Innosilicon device.
|
||||
|
||||
@@ -182,30 +214,80 @@ class _PoolGroup:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by an X5 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {
|
||||
"_ant_pool1url": "",
|
||||
"_ant_pool1user": "",
|
||||
"_ant_pool1pw": "",
|
||||
"_ant_pool2url": "",
|
||||
"_ant_pool2user": "",
|
||||
"_ant_pool2pw": "",
|
||||
"_ant_pool3url": "",
|
||||
"_ant_pool3user": "",
|
||||
"_ant_pool3pw": "",
|
||||
}
|
||||
for idx, pool in enumerate(self.pools[:3]):
|
||||
pools[f"_ant_pool{idx+1}url"] = pool.as_x17(user_suffix=user_suffix)["url"]
|
||||
pools[f"_ant_pool{idx+1}user"] = pool.as_x17(user_suffix=user_suffix)[
|
||||
"user"
|
||||
]
|
||||
pools[f"_ant_pool{idx+1}pw"] = pool.as_x17(user_suffix=user_suffix)["pass"]
|
||||
|
||||
return pools
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
"""Convert the data in this class to a list usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
return [pool.as_goldshell(user_suffix=user_suffix) for pool in self.pools[:3]]
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by an Innosilicon device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = {}
|
||||
pools = {
|
||||
"Pool1": None,
|
||||
"UserName1": None,
|
||||
"Password1": None,
|
||||
"Pool2": None,
|
||||
"UserName2": None,
|
||||
"Password2": None,
|
||||
"Pool3": None,
|
||||
"UserName3": None,
|
||||
"Password3": None,
|
||||
}
|
||||
for idx, pool in enumerate(self.pools[:3]):
|
||||
pool_data = pool.as_inno(user_suffix=user_suffix)
|
||||
for key in pool_data:
|
||||
pools[f"{key}{idx+1}"] = pool_data[key]
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> List[dict]:
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a list usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_wm(user_suffix=user_suffix))
|
||||
while len(pools) < 3:
|
||||
pools.append({"url": None, "user": None, "pass": None})
|
||||
pools = {}
|
||||
for i in range(1, 4):
|
||||
if i <= len(self.pools):
|
||||
pool_wm = self.pools[i - 1].as_wm(user_suffix)
|
||||
pools[f"pool_{i}"] = pool_wm["url"]
|
||||
pools[f"worker_{i}"] = pool_wm["user"]
|
||||
pools[f"passwd_{i}"] = pool_wm["pass"]
|
||||
else:
|
||||
pools[f"pool_{i}"] = ""
|
||||
pools[f"worker_{i}"] = ""
|
||||
pools[f"passwd_{i}"] = ""
|
||||
return pools
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
@@ -267,6 +349,7 @@ class MinerConfig:
|
||||
|
||||
asicboost: bool = None
|
||||
|
||||
miner_mode: IntEnum = X19PowerMode.Normal
|
||||
autotuning_enabled: bool = True
|
||||
autotuning_mode: Literal["power", "hashrate"] = None
|
||||
autotuning_wattage: int = None
|
||||
@@ -287,6 +370,8 @@ class MinerConfig:
|
||||
logging.debug(f"MinerConfig - (To Dict) - Dumping Dict config")
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if isinstance(data_dict[key], IntEnum):
|
||||
data_dict[key] = data_dict[key].value
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
@@ -310,6 +395,9 @@ class MinerConfig:
|
||||
"""
|
||||
logging.debug(f"MinerConfig - (From Raw) - Loading raw config")
|
||||
pool_groups = []
|
||||
if isinstance(data, list):
|
||||
# goldshell config list
|
||||
data = {"pools": data}
|
||||
for key in data.keys():
|
||||
if key == "pools":
|
||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||
@@ -324,16 +412,15 @@ class MinerConfig:
|
||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||
elif key == "bitmain-work-mode":
|
||||
if data[key]:
|
||||
if data[key] == 1:
|
||||
self.autotuning_wattage = 0
|
||||
self.miner_mode = X19PowerMode(int(data[key]))
|
||||
elif key == "fan_control":
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
if _key == "min_fans":
|
||||
self.minimum_fans = data[key][_key]
|
||||
elif _key == "speed":
|
||||
self.fan_speed = data[key][_key]
|
||||
elif key == "temp_control":
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
if _key == "mode":
|
||||
self.temp_mode = data[key][_key]
|
||||
elif _key == "target_temp":
|
||||
@@ -348,7 +435,7 @@ class MinerConfig:
|
||||
self.asicboost = data[key]["asic_boost"]
|
||||
|
||||
if key == "autotuning":
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
if _key == "enabled":
|
||||
self.autotuning_enabled = data[key][_key]
|
||||
elif _key == "psu_power_limit":
|
||||
@@ -361,7 +448,7 @@ class MinerConfig:
|
||||
self.autotuning_mode = data[key][_key].replace("_target", "")
|
||||
|
||||
if key in ["power_scaling", "performance_scaling"]:
|
||||
for _key in data[key].keys():
|
||||
for _key in data[key]:
|
||||
if _key == "enabled":
|
||||
self.dps_enabled = data[key][_key]
|
||||
elif _key == "power_step":
|
||||
@@ -402,8 +489,14 @@ class MinerConfig:
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data:
|
||||
if hasattr(self, key) and not key == "pool_groups":
|
||||
if (
|
||||
hasattr(self, key)
|
||||
and not key == "pool_groups"
|
||||
and not key == "miner_mode"
|
||||
):
|
||||
setattr(self, key, data[key])
|
||||
if key == "miner_mode":
|
||||
self.miner_mode = X19PowerMode(data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
@@ -425,7 +518,7 @@ class MinerConfig:
|
||||
logging.debug(f"MinerConfig - (From YAML) - Loading YAML config")
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_wm(self, user_suffix: str = None) -> Dict[str, int]:
|
||||
def as_wm(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by a Whatsminer device.
|
||||
|
||||
Parameters:
|
||||
@@ -446,7 +539,7 @@ class MinerConfig:
|
||||
logging.debug(f"MinerConfig - (As Inno) - Generating Innosilicon config")
|
||||
return self.pool_groups[0].as_inno(user_suffix=user_suffix)
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
def as_x19(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
@@ -457,10 +550,8 @@ class MinerConfig:
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
"miner-mode": 0, # Normal Mode
|
||||
"miner-mode": self.miner_mode.value,
|
||||
}
|
||||
if self.autotuning_wattage == 0:
|
||||
cfg["miner-mode"] = 1 # Sleep Mode
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
@@ -468,7 +559,27 @@ class MinerConfig:
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
return cfg
|
||||
|
||||
def as_x17(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a config usable by an X5 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_x17(user_suffix=user_suffix)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||
"""Convert the data in this class to a config usable by a goldshell device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_goldshell(user_suffix=user_suffix)
|
||||
|
||||
return cfg
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||
|
||||
@@ -259,8 +259,9 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def left_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[0].chips
|
||||
|
||||
return 0
|
||||
|
||||
@left_chips.setter
|
||||
@@ -271,7 +272,7 @@ class MinerData:
|
||||
def center_chips(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chips
|
||||
if len(self.hashboards) == 3:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[1].chips
|
||||
return 0
|
||||
|
||||
@@ -285,6 +286,8 @@ class MinerData:
|
||||
return self.hashboards[1].chips
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chips
|
||||
if len(self.hashboards) > 3:
|
||||
return self.hashboards[-1:][0].chips
|
||||
return 0
|
||||
|
||||
@right_chips.setter
|
||||
@@ -293,7 +296,7 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def left_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[0].hashrate
|
||||
return 0
|
||||
|
||||
@@ -305,7 +308,7 @@ class MinerData:
|
||||
def center_board_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[1].hashrate
|
||||
return 0
|
||||
|
||||
@@ -319,6 +322,8 @@ class MinerData:
|
||||
return self.hashboards[1].hashrate
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].hashrate
|
||||
if len(self.hashboards) > 3:
|
||||
return self.hashboards[-1:][0].hashrate
|
||||
return 0
|
||||
|
||||
@right_board_hashrate.setter
|
||||
@@ -327,7 +332,7 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def left_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[0].temp
|
||||
return 0
|
||||
|
||||
@@ -339,7 +344,7 @@ class MinerData:
|
||||
def center_board_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].temp
|
||||
if len(self.hashboards) == 3:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[1].temp
|
||||
return 0
|
||||
|
||||
@@ -353,6 +358,8 @@ class MinerData:
|
||||
return self.hashboards[1].temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].temp
|
||||
if len(self.hashboards) > 3:
|
||||
return self.hashboards[-1:][0].temp
|
||||
return 0
|
||||
|
||||
@right_board_temp.setter
|
||||
@@ -361,7 +368,7 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def left_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) in [2, 3]:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[0].chip_temp
|
||||
return 0
|
||||
|
||||
@@ -373,7 +380,7 @@ class MinerData:
|
||||
def center_board_chip_temp(self): # noqa - Skip PyCharm inspection
|
||||
if len(self.hashboards) == 1:
|
||||
return self.hashboards[0].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
if len(self.hashboards) in [2, 3, 4]:
|
||||
return self.hashboards[1].chip_temp
|
||||
return 0
|
||||
|
||||
@@ -387,6 +394,8 @@ class MinerData:
|
||||
return self.hashboards[1].chip_temp
|
||||
if len(self.hashboards) == 3:
|
||||
return self.hashboards[2].chip_temp
|
||||
if len(self.hashboards) > 3:
|
||||
return self.hashboards[-1:][0].chip_temp
|
||||
return 0
|
||||
|
||||
@right_board_chip_temp.setter
|
||||
@@ -429,7 +438,7 @@ class MinerData:
|
||||
|
||||
@property
|
||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||
if self.hashrate == 0:
|
||||
if self.hashrate == 0 or self.wattage == -1:
|
||||
return 0
|
||||
return round(self.wattage / self.hashrate)
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
C_N_CODES = ["52", "53", "54", "55"]
|
||||
C_N_CODES = ["52", "53", "54", "55", "56"]
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -264,6 +264,7 @@ ERROR_CODES = {
|
||||
53: {"n": {"c": "Slot {n} chip {c} too few nonce."}},
|
||||
54: {"n": {"c": "Slot {n} chip {c} temp protected."}},
|
||||
55: {"n": {"c": "Slot {n} chip {c} has been reset."}},
|
||||
56: {"n": {"c": "Slot {n} chip {c} does not return to the nonce."}},
|
||||
80: {
|
||||
0: {0: "The tool version is too low, please update."},
|
||||
1: {0: "Low freq."},
|
||||
|
||||
@@ -24,11 +24,27 @@ class APIError(Exception):
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
if self.message == "can't access write cmd":
|
||||
return f"{self.message}, please make sure your miner has been unlocked."
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Incorrect API parameters."
|
||||
|
||||
|
||||
class PhaseBalancingError(Exception):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
self.message = args[0]
|
||||
else:
|
||||
self.message = None
|
||||
|
||||
def __str__(self):
|
||||
if self.message:
|
||||
return f"{self.message}"
|
||||
else:
|
||||
return "Failed to balance phase."
|
||||
|
||||
|
||||
class APIWarning(Warning):
|
||||
def __init__(self, *args):
|
||||
if args:
|
||||
|
||||
350
pyasic/load/__init__.py
Normal file
350
pyasic/load/__init__.py
Normal file
@@ -0,0 +1,350 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
from typing import List, Union
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners import AnyMiner
|
||||
from pyasic.miners.backends import ( # noqa - Ignore access to _module
|
||||
AntminerModern,
|
||||
BOSMiner,
|
||||
BTMiner,
|
||||
)
|
||||
from pyasic.miners.btc._types import ( # noqa - Ignore access to _module
|
||||
S9,
|
||||
S17,
|
||||
T17,
|
||||
S17e,
|
||||
S17Plus,
|
||||
S17Pro,
|
||||
T17e,
|
||||
T17Plus,
|
||||
)
|
||||
|
||||
FAN_USAGE = 50 # 50 W per fan
|
||||
|
||||
|
||||
class MinerLoadBalancer:
|
||||
"""A load balancer for miners. Can be passed a list of `AnyMiner`, or a list of phases (lists of `AnyMiner`)."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
phases: Union[List[List[AnyMiner]], None] = None,
|
||||
):
|
||||
self.phases = [_MinerPhaseBalancer(phase) for phase in phases]
|
||||
|
||||
async def balance(self, wattage: int) -> int:
|
||||
phase_wattage = wattage // len(self.phases)
|
||||
setpoints = await asyncio.gather(
|
||||
*[phase.get_balance_setpoints(phase_wattage) for phase in self.phases]
|
||||
)
|
||||
tasks = []
|
||||
total_wattage = 0
|
||||
for setpoint in setpoints:
|
||||
wattage_set = 0
|
||||
for miner in setpoint:
|
||||
if setpoint[miner]["set"] == "on":
|
||||
wattage_set += setpoint[miner]["max"]
|
||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||
elif setpoint[miner]["set"] == "off":
|
||||
wattage_set += setpoint[miner]["min"]
|
||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||
else:
|
||||
wattage_set += setpoint[miner]["set"]
|
||||
tasks.append(
|
||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||
)
|
||||
total_wattage += wattage_set
|
||||
await asyncio.gather(*tasks)
|
||||
return total_wattage
|
||||
|
||||
|
||||
class _MinerPhaseBalancer:
|
||||
def __init__(self, miners: List[AnyMiner]):
|
||||
self.miners = {
|
||||
str(miner.ip): {
|
||||
"miner": miner,
|
||||
"set": 0,
|
||||
"min": miner.fan_count * FAN_USAGE,
|
||||
}
|
||||
for miner in miners
|
||||
}
|
||||
for miner in miners:
|
||||
if (
|
||||
isinstance(miner, BTMiner)
|
||||
and not (miner.model.startswith("M2") if miner.model else True)
|
||||
) or isinstance(miner, BOSMiner):
|
||||
if isinstance(miner, S9):
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 1400
|
||||
elif True in [
|
||||
isinstance(miner, x)
|
||||
for x in [S17, S17Plus, S17Pro, S17e, T17, T17Plus, T17e]
|
||||
]:
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 2400
|
||||
else:
|
||||
self.miners[str(miner.ip)]["tune"] = True
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
elif isinstance(miner, AntminerModern):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
elif isinstance(miner, BTMiner):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
if miner.model:
|
||||
if miner.model.startswith("M2"):
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = True
|
||||
self.miners[str(miner.ip)]["max"] = 2400
|
||||
else:
|
||||
self.miners[str(miner.ip)]["tune"] = False
|
||||
self.miners[str(miner.ip)]["shutdown"] = False
|
||||
self.miners[str(miner.ip)]["max"] = 3600
|
||||
self.miners[str(miner.ip)]["min"] = 3600
|
||||
|
||||
async def balance(self, wattage: int) -> int:
|
||||
setpoint = await self.get_balance_setpoints(wattage)
|
||||
wattage_set = 0
|
||||
tasks = []
|
||||
for miner in setpoint:
|
||||
if setpoint[miner]["set"] == "on":
|
||||
wattage_set += setpoint[miner]["max"]
|
||||
tasks.append(setpoint[miner]["miner"].resume_mining())
|
||||
elif setpoint[miner]["set"] == "off":
|
||||
wattage_set += setpoint[miner]["min"]
|
||||
tasks.append(setpoint[miner]["miner"].stop_mining())
|
||||
else:
|
||||
wattage_set += setpoint[miner]["set"]
|
||||
tasks.append(
|
||||
setpoint[miner]["miner"].set_power_limit(setpoint[miner]["set"])
|
||||
)
|
||||
await asyncio.gather(*tasks)
|
||||
return wattage_set
|
||||
|
||||
async def get_balance_setpoints(self, wattage: int) -> dict:
|
||||
# gather data needed to optimize shutdown only miners
|
||||
dp = ["hashrate", "wattage", "wattage_limit", "hashboards"]
|
||||
data = await asyncio.gather(
|
||||
*[
|
||||
self.miners[miner]["miner"].get_data(data_to_get=dp)
|
||||
for miner in self.miners
|
||||
]
|
||||
)
|
||||
pct_ideal_list = [d.percent_ideal for d in data]
|
||||
pct_ideal = 0
|
||||
if len(pct_ideal_list) > 0:
|
||||
pct_ideal = sum(pct_ideal_list) / len(pct_ideal_list)
|
||||
|
||||
wattage = round(wattage * 1 / (pct_ideal / 100))
|
||||
|
||||
for data_point in data:
|
||||
if (not self.miners[data_point.ip]["tune"]) and (
|
||||
not self.miners[data_point.ip]["shutdown"]
|
||||
):
|
||||
# cant do anything with it so need to find a semi-accurate power limit
|
||||
if not data_point.wattage_limit == -1:
|
||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||
elif not data_point.wattage == -1:
|
||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||
|
||||
max_tune_wattage = sum(
|
||||
[miner["max"] for miner in self.miners.values() if miner["tune"]]
|
||||
)
|
||||
max_shutdown_wattage = sum(
|
||||
[
|
||||
miner["max"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (miner["shutdown"])
|
||||
]
|
||||
)
|
||||
max_other_wattage = sum(
|
||||
[
|
||||
miner["max"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (not miner["shutdown"])
|
||||
]
|
||||
)
|
||||
min_tune_wattage = sum(
|
||||
[miner["min"] for miner in self.miners.values() if miner["tune"]]
|
||||
)
|
||||
min_shutdown_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if (not miner["tune"]) and (miner["shutdown"])
|
||||
]
|
||||
)
|
||||
# min_other_wattage = sum([miner["min"] for miner in self.miners.values() if (not miner["tune"]) and (not miner["shutdown"])])
|
||||
|
||||
# make sure wattage isnt set too high
|
||||
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
||||
raise APIError(
|
||||
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
||||
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W")
|
||||
|
||||
# should now know wattage limits and which can be tuned/shutdown
|
||||
# check if 1/2 max of the miners which can be tuned is low enough
|
||||
if (max_tune_wattage / 2) + max_shutdown_wattage + max_other_wattage < wattage:
|
||||
useable_wattage = wattage - (max_other_wattage + max_shutdown_wattage)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
|
||||
# check if shutting down miners will help
|
||||
elif (
|
||||
max_tune_wattage / 2
|
||||
) + min_shutdown_wattage + max_other_wattage < wattage:
|
||||
# tuneable inclusive since could be S9 BOS+ and S19 Stock, would rather shut down the S9, tuneable should always support shutdown
|
||||
useable_wattage = wattage - (
|
||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
for miner in sorted(
|
||||
[miner for miner in self.miners.values() if miner["shutdown"]],
|
||||
key=lambda x: x["max"],
|
||||
reverse=True,
|
||||
):
|
||||
if miner["tune"]:
|
||||
miner_min_watt_use = miner["max"] / 2
|
||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += miner_min_watt_use - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
else:
|
||||
miner_min_watt_use = miner["max"]
|
||||
useable_wattage -= miner_min_watt_use - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += miner_min_watt_use - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
|
||||
new_shutdown_wattage = sum(
|
||||
[
|
||||
miner["max"] if miner["set"] == 0 else miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["shutdown"] and not miner["tune"]
|
||||
]
|
||||
)
|
||||
new_tune_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["set"] == "off"
|
||||
]
|
||||
)
|
||||
|
||||
useable_wattage = wattage - (
|
||||
new_tune_wattage + max_other_wattage + new_shutdown_wattage
|
||||
)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
|
||||
# check if shutting down tuneable miners will do it
|
||||
elif min_tune_wattage + min_shutdown_wattage + max_other_wattage < wattage:
|
||||
# all miners that can be shutdown need to be
|
||||
for miner in self.miners:
|
||||
if (not self.miners[miner]["tune"]) and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "off"
|
||||
# calculate wattage usable by tuneable miners
|
||||
useable_wattage = wattage - (
|
||||
min_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
|
||||
# loop through miners to see how much is actually useable
|
||||
# sort the largest first
|
||||
for miner in sorted(
|
||||
[
|
||||
miner
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["shutdown"]
|
||||
],
|
||||
key=lambda x: x["max"],
|
||||
reverse=True,
|
||||
):
|
||||
# add min to useable wattage since it was removed earlier, and remove 1/2 tuner max
|
||||
useable_wattage -= (miner["max"] / 2) - miner["min"]
|
||||
if useable_wattage < 0:
|
||||
useable_wattage += (miner["max"] / 2) - miner["min"]
|
||||
self.miners[str(miner["miner"].ip)]["set"] = "off"
|
||||
|
||||
new_tune_wattage = sum(
|
||||
[
|
||||
miner["min"]
|
||||
for miner in self.miners.values()
|
||||
if miner["tune"] and miner["set"] == "off"
|
||||
]
|
||||
)
|
||||
|
||||
useable_wattage = wattage - (
|
||||
new_tune_wattage + max_other_wattage + min_shutdown_wattage
|
||||
)
|
||||
useable_miners = len(
|
||||
[m for m in self.miners.values() if (m["set"] == 0) and (m["tune"])]
|
||||
)
|
||||
|
||||
if not useable_miners == 0:
|
||||
watts_per_miner = useable_wattage // useable_miners
|
||||
# loop through and set useable miners to wattage
|
||||
for miner in self.miners:
|
||||
if (self.miners[miner]["set"] == 0) and (
|
||||
self.miners[miner]["tune"]
|
||||
):
|
||||
self.miners[miner]["set"] = watts_per_miner
|
||||
elif self.miners[miner]["set"] == 0 and (
|
||||
self.miners[miner]["shutdown"]
|
||||
):
|
||||
self.miners[miner]["set"] = "on"
|
||||
else:
|
||||
raise APIError(
|
||||
f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W"
|
||||
) # PhaseBalancingError(f"Wattage setpoint is too low, setpoint: {wattage}W, min: {min_tune_wattage + min_shutdown_wattage + max_other_wattage}W")
|
||||
|
||||
return self.miners
|
||||
@@ -1,233 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from pyasic.API import APIError
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
|
||||
class X19(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver=api_ver)
|
||||
self.ip = ip
|
||||
self.uname = "root"
|
||||
self.pwd = PyasicSettings().global_x19_password
|
||||
|
||||
async def send_web_command(
|
||||
self, command: str, params: dict = None
|
||||
) -> Optional[dict]:
|
||||
url = f"http://{self.ip}/cgi-bin/{command}.cgi"
|
||||
auth = httpx.DigestAuth(self.uname, self.pwd)
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
if params:
|
||||
data = await client.post(url, data=params, auth=auth)
|
||||
else:
|
||||
data = await client.get(url, auth=auth)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.send_web_command("get_miner_conf")
|
||||
if data:
|
||||
self.config = MinerConfig().from_raw(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
conf = config.as_x19(user_suffix=user_suffix)
|
||||
await self.send_web_command(
|
||||
"set_miner_conf", params=conf # noqa: ignore conf being a str
|
||||
)
|
||||
|
||||
for i in range(7):
|
||||
data = await self.get_config()
|
||||
if data.as_x19() == conf:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
data = await self.send_web_command(
|
||||
"blink",
|
||||
params=json.dumps({"blink": "true"}), # noqa - ignore params being a str
|
||||
)
|
||||
if data:
|
||||
if data.get("code") == "B000":
|
||||
self.light = True
|
||||
return self.light
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
data = await self.send_web_command(
|
||||
"blink",
|
||||
params=json.dumps({"blink": "false"}), # noqa - ignore params being a str
|
||||
)
|
||||
if data:
|
||||
if data.get("code") == "B100":
|
||||
self.light = True
|
||||
return self.light
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
data = await self.send_web_command("reboot")
|
||||
if data:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.autotuning_wattage = 0
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.autotuning_wattage = 1
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.send_web_command("get_system_info")
|
||||
if data:
|
||||
return data["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_mac(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.send_web_command("get_system_info")
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.send_web_command("get_network_info")
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
data = await self.send_web_command("summary")
|
||||
if data:
|
||||
try:
|
||||
for item in data["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(item["msg"]))
|
||||
except KeyError:
|
||||
continue
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def get_fault_light(self) -> bool:
|
||||
if self.light:
|
||||
return self.light
|
||||
try:
|
||||
data = await self.send_web_command("get_blink_status")
|
||||
if data:
|
||||
self.light = data["blink"]
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
return round(ideal_rate / 1000, 2)
|
||||
if rate_unit == "MH":
|
||||
return round(ideal_rate / 1000000, 2)
|
||||
else:
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def set_static_ip(
|
||||
self,
|
||||
ip: str,
|
||||
dns: str,
|
||||
gateway: str,
|
||||
subnet_mask: str = "255.255.255.0",
|
||||
hostname: str = None,
|
||||
):
|
||||
if not hostname:
|
||||
hostname = await self.get_hostname()
|
||||
payload = {
|
||||
"ipAddress": ip,
|
||||
"ipDns": dns,
|
||||
"ipGateway": gateway,
|
||||
"ipHost": hostname,
|
||||
"ipPro": 2, # static
|
||||
"ipSub": subnet_mask,
|
||||
}
|
||||
await self.send_web_command("set_network_conf", params=payload)
|
||||
|
||||
async def set_dhcp(self, hostname: str = None):
|
||||
if not hostname:
|
||||
hostname = await self.get_hostname()
|
||||
payload = {
|
||||
"ipAddress": "",
|
||||
"ipDns": "",
|
||||
"ipGateway": "",
|
||||
"ipHost": hostname,
|
||||
"ipPro": 1, # DHCP
|
||||
"ipSub": "",
|
||||
}
|
||||
await self.send_web_command("set_network_conf", params=payload)
|
||||
|
||||
async def set_hostname(self, hostname: str):
|
||||
cfg = await self.send_web_command("get_network_info")
|
||||
dns = cfg["conf_dnsservers"]
|
||||
gateway = cfg["conf_gateway"]
|
||||
ip = cfg["conf_ipaddress"]
|
||||
subnet_mask = cfg["conf_netmask"]
|
||||
protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2
|
||||
payload = {
|
||||
"ipAddress": ip,
|
||||
"ipDns": dns,
|
||||
"ipGateway": gateway,
|
||||
"ipHost": hostname,
|
||||
"ipPro": protocol,
|
||||
"ipSub": subnet_mask,
|
||||
}
|
||||
await self.send_web_command("set_network_conf", params=payload)
|
||||
@@ -1,24 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._backends import X19
|
||||
from pyasic.miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
# noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19(X19, S19):
|
||||
pass
|
||||
@@ -1,24 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._backends import X19
|
||||
from pyasic.miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
# noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT19(X19, T19):
|
||||
pass
|
||||
@@ -13,7 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .antminer import AntminerModern, AntminerOld
|
||||
from .bfgminer import BFGMiner
|
||||
from .bfgminer_goldshell import BFGMinerGoldshell
|
||||
from .bmminer import BMMiner
|
||||
from .bosminer import BOSMiner
|
||||
from .btminer import BTMiner
|
||||
@@ -21,4 +23,4 @@ from .cgminer import CGMiner
|
||||
from .cgminer_avalon import CGMinerAvalon
|
||||
from .hiveon import Hiveon
|
||||
from .vnish import VNish
|
||||
from .X19 import X19
|
||||
from .whatsminer import M2X, M3X, M5X
|
||||
423
pyasic/miners/backends/antminer.py
Normal file
423
pyasic/miners/backends/antminer.py
Normal file
@@ -0,0 +1,423 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import asyncio
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from pyasic.API import APIError
|
||||
from pyasic.config import MinerConfig, X19PowerMode
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
ANTMINER_MODERN_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class AntminerModern(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = AntminerModernWebAPI(ip)
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = ANTMINER_MODERN_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_shutdown = True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
if data:
|
||||
self.config = MinerConfig().from_raw(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
conf = config.as_x19(user_suffix=user_suffix)
|
||||
await self.web.set_miner_conf(conf)
|
||||
|
||||
for i in range(7):
|
||||
data = await self.get_config()
|
||||
if data.as_x19() == conf:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
data = await self.web.blink(blink=True)
|
||||
if data:
|
||||
if data.get("code") == "B000":
|
||||
self.light = True
|
||||
return self.light
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
data = await self.web.blink(blink=False)
|
||||
if data:
|
||||
if data.get("code") == "B100":
|
||||
self.light = True
|
||||
return self.light
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.miner_mode = X19PowerMode.Sleep
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.miner_mode = X19PowerMode.Normal
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
return data["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_mac(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
data = await self.web.get_network_info()
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
data = await self.web.summary()
|
||||
if data:
|
||||
try:
|
||||
for item in data["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(item["msg"]))
|
||||
except KeyError:
|
||||
continue
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def get_fault_light(self) -> bool:
|
||||
if self.light:
|
||||
return self.light
|
||||
try:
|
||||
data = await self.web.get_blink_status()
|
||||
if data:
|
||||
self.light = data["blink"]
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
return round(ideal_rate / 1000, 2)
|
||||
if rate_unit == "MH":
|
||||
return round(ideal_rate / 1000000, 2)
|
||||
else:
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def set_static_ip(
|
||||
self,
|
||||
ip: str,
|
||||
dns: str,
|
||||
gateway: str,
|
||||
subnet_mask: str = "255.255.255.0",
|
||||
hostname: str = None,
|
||||
):
|
||||
if not hostname:
|
||||
hostname = await self.get_hostname()
|
||||
await self.web.set_network_conf(
|
||||
ip=ip,
|
||||
dns=dns,
|
||||
gateway=gateway,
|
||||
subnet_mask=subnet_mask,
|
||||
hostname=hostname,
|
||||
protocol=2,
|
||||
)
|
||||
|
||||
async def set_dhcp(self, hostname: str = None):
|
||||
if not hostname:
|
||||
hostname = await self.get_hostname()
|
||||
await self.web.set_network_conf(
|
||||
ip="", dns="", gateway="", subnet_mask="", hostname=hostname, protocol=1
|
||||
)
|
||||
|
||||
async def set_hostname(self, hostname: str):
|
||||
cfg = await self.web.get_network_info()
|
||||
dns = cfg["conf_dnsservers"]
|
||||
gateway = cfg["conf_gateway"]
|
||||
ip = cfg["conf_ipaddress"]
|
||||
subnet_mask = cfg["conf_netmask"]
|
||||
protocol = 1 if cfg["conf_nettype"] == "DHCP" else 2
|
||||
await self.web.set_network_conf(
|
||||
ip=ip,
|
||||
dns=dns,
|
||||
gateway=gateway,
|
||||
subnet_mask=subnet_mask,
|
||||
hostname=hostname,
|
||||
protocol=protocol,
|
||||
)
|
||||
|
||||
|
||||
ANTMINER_OLD_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"model": {
|
||||
"cmd": "get_model",
|
||||
"kwargs": {},
|
||||
},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {
|
||||
"cmd": "get_hostname",
|
||||
"kwargs": {"web_get_system_info": {"web": "get_system_info"}},
|
||||
},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {
|
||||
"cmd": "get_fault_light",
|
||||
"kwargs": {"web_get_blink_status": {"web": "get_blink_status"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class AntminerOld(CGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = AntminerOldWebAPI(ip)
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = ANTMINER_OLD_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
if data:
|
||||
self.config = MinerConfig().from_raw(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
await self.web.set_miner_conf(config.as_x17(user_suffix=user_suffix))
|
||||
|
||||
async def get_mac(self) -> Union[str, None]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
return data["macaddr"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
# this should time out, after it does do a check
|
||||
await self.web.blink(blink=True)
|
||||
try:
|
||||
data = await self.web.get_blink_status()
|
||||
if data:
|
||||
if data["isBlinking"]:
|
||||
self.light = True
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
await self.web.blink(blink=False)
|
||||
try:
|
||||
data = await self.web.get_blink_status()
|
||||
if data:
|
||||
if not data["isBlinking"]:
|
||||
self.light = False
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_fault_light(self, web_get_blink_status: dict = None) -> bool:
|
||||
if self.light:
|
||||
return self.light
|
||||
|
||||
if not web_get_blink_status:
|
||||
try:
|
||||
web_get_blink_status = await self.web.get_blink_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_blink_status:
|
||||
try:
|
||||
self.light = web_get_blink_status["isBlinking"]
|
||||
except KeyError:
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if not web_get_system_info:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info:
|
||||
try:
|
||||
return web_get_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [Fan(), Fan(), Fan(), Fan()]
|
||||
if api_stats:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num + 2
|
||||
if fan_offset == -1:
|
||||
fan_offset = 3
|
||||
|
||||
for fan in range(self.fan_count):
|
||||
fans_data[fan] = Fan(
|
||||
api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
return fans_data
|
||||
|
||||
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||
hashboard = HashBoard(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
|
||||
chip_temp = boards[1].get(f"temp{i}")
|
||||
if chip_temp:
|
||||
hashboard.chip_temp = round(chip_temp)
|
||||
|
||||
temp = boards[1].get(f"temp2_{i}")
|
||||
if temp:
|
||||
hashboard.temp = round(temp)
|
||||
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
if chips:
|
||||
hashboard.chips = chips
|
||||
hashboard.missing = False
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
hashboards.append(hashboard)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
319
pyasic/miners/backends/bfgminer.py
Normal file
319
pyasic/miners/backends/bfgminer.py
Normal file
@@ -0,0 +1,319 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pyasic.API.bfgminer import BFGMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
|
||||
BFGMINER_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class BFGMiner(BaseMiner):
|
||||
"""Base handler for BFGMiner based miners."""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BFGMinerAPI(ip, api_ver)
|
||||
|
||||
# static data
|
||||
self.api_type = "BFGMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BFGMINER_DATA_LOC
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
pools = await self.api.pools()
|
||||
except APIError:
|
||||
return self.config
|
||||
|
||||
self.config = MinerConfig().from_api(pools["POOLS"])
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
return None
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
return False
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
return False
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
return False
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
return False
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def get_mac(self) -> str:
|
||||
return "00:00:00:00:00:00"
|
||||
|
||||
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
# Check to see if the version info is already cached
|
||||
if self.api_ver:
|
||||
return self.api_ver
|
||||
|
||||
if not api_version:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
# Check to see if the version info is already cached
|
||||
if self.fw_ver:
|
||||
return self.fw_ver
|
||||
|
||||
if not api_version:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def get_version(
|
||||
self, api_version: dict = None
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
# check if version is cached
|
||||
miner_version = namedtuple("MinerVersion", "api_ver fw_ver")
|
||||
return miner_version(
|
||||
api_ver=await self.get_api_ver(api_version),
|
||||
fw_ver=await self.get_fw_ver(api_version=api_version),
|
||||
)
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
return False
|
||||
|
||||
async def get_fan_psu(self):
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not api_summary:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
for i in range(board_offset, board_offset + self.ideal_hashboards):
|
||||
hashboard = HashBoard(
|
||||
slot=i - board_offset, expected_chips=self.nominal_chips
|
||||
)
|
||||
|
||||
chip_temp = boards[1].get(f"temp{i}")
|
||||
if chip_temp:
|
||||
hashboard.chip_temp = round(chip_temp)
|
||||
|
||||
temp = boards[1].get(f"temp2_{i}")
|
||||
if temp:
|
||||
hashboard.temp = round(temp)
|
||||
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = round(float(hashrate) / 1000, 2)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
if chips:
|
||||
hashboard.chips = chips
|
||||
hashboard.missing = False
|
||||
if (not chips) or (not chips > 0):
|
||||
hashboard.missing = True
|
||||
hashboards.append(hashboard)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
return hashboards
|
||||
|
||||
async def get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_wattage(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_wattage_limit(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [None, None, None, None]
|
||||
if api_stats:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
for fan_num in range(0, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
|
||||
for fan in range(self.fan_count):
|
||||
fans_data[fan] = api_stats["STATS"][1].get(f"fan{fan_offset+fan}")
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
fans = [Fan(speed=d) if d else Fan() for d in fans_data]
|
||||
|
||||
return fans
|
||||
|
||||
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||
groups = []
|
||||
|
||||
if not api_pools:
|
||||
try:
|
||||
api_pools = await self.api.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_pools:
|
||||
try:
|
||||
pools = {}
|
||||
for i, pool in enumerate(api_pools["POOLS"]):
|
||||
pools[f"pool_{i + 1}_url"] = (
|
||||
pool["URL"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||
|
||||
groups.append(pools)
|
||||
except KeyError:
|
||||
pass
|
||||
return groups
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
return []
|
||||
|
||||
async def get_fault_light(self) -> bool:
|
||||
return False
|
||||
|
||||
async def get_nominal_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
# X19 method, not sure compatibility
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
ideal_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
if rate_unit == "GH":
|
||||
return round(ideal_rate / 1000, 2)
|
||||
if rate_unit == "MH":
|
||||
return round(ideal_rate / 1000000, 2)
|
||||
else:
|
||||
return round(ideal_rate, 2)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
159
pyasic/miners/backends/bfgminer_goldshell.py
Normal file
159
pyasic/miners/backends/bfgminer_goldshell.py
Normal file
@@ -0,0 +1,159 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import BFGMiner
|
||||
from pyasic.web.goldshell import GoldshellWebAPI
|
||||
|
||||
GOLDSHELL_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {"web_setting": {"web": "setting"}}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_status": {"web": "status"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {
|
||||
"cmd": "get_hashboards",
|
||||
"kwargs": {
|
||||
"api_devs": {"api": "devs"},
|
||||
"api_devdetails": {"api": "devdetails"},
|
||||
},
|
||||
},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class BFGMinerGoldshell(BFGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = GoldshellWebAPI(ip)
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = GOLDSHELL_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
return MinerConfig().from_raw(await self.web.pools())
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
pools_data = await self.web.pools()
|
||||
# have to delete all the pools one at a time first
|
||||
for pool in pools_data:
|
||||
await self.web.delpool(
|
||||
url=pool["url"],
|
||||
user=pool["user"],
|
||||
password=pool["pass"],
|
||||
dragid=pool["dragid"],
|
||||
)
|
||||
|
||||
self.config = config
|
||||
|
||||
# send them back 1 at a time
|
||||
for pool in config.as_goldshell(user_suffix=user_suffix):
|
||||
await self.web.newpool(
|
||||
url=pool["url"], user=pool["user"], password=pool["pass"]
|
||||
)
|
||||
|
||||
async def get_mac(self, web_setting: dict = None) -> str:
|
||||
if not web_setting:
|
||||
try:
|
||||
web_setting = await self.web.setting()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_setting:
|
||||
try:
|
||||
return web_setting["name"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_fw_ver(self, web_status: dict = None) -> str:
|
||||
if not web_status:
|
||||
try:
|
||||
web_status = await self.web.setting()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_status:
|
||||
try:
|
||||
return web_status["firmware"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def get_hashboards(
|
||||
self, api_devs: dict = None, api_devdetails: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if not api_devs:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.nominal_chips)
|
||||
for i in range(self.ideal_hashboards)
|
||||
]
|
||||
|
||||
if api_devs:
|
||||
if api_devs.get("DEVS"):
|
||||
for board in api_devs["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
try:
|
||||
b_id = board["ID"]
|
||||
hashboards[b_id].hashrate = round(
|
||||
board["MHS 20s"] / 1000000, 2
|
||||
)
|
||||
hashboards[b_id].temp = board["tstemp-2"]
|
||||
hashboards[b_id].missing = False
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
print(self, api_devs)
|
||||
|
||||
if not api_devdetails:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
if api_devdetails.get("DEVS"):
|
||||
for board in api_devdetails["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
try:
|
||||
b_id = board["ID"]
|
||||
hashboards[b_id].chips = board["chips-nr"]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
print(self, api_devdetails)
|
||||
|
||||
return hashboards
|
||||
@@ -14,20 +14,40 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import asyncssh
|
||||
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
BMMINER_DATA_LOC = {
|
||||
"mac": {"cmd": "mac", "kwargs": {}},
|
||||
"model": {"cmd": "model", "kwargs": {"api_devdetails": {"api": "devdetails"}}},
|
||||
"api_ver": {"cmd": "api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {"cmd": "hostname", "kwargs": {}},
|
||||
"hashrate": {"cmd": "hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class BMMiner(BaseMiner):
|
||||
@@ -35,12 +55,16 @@ class BMMiner(BaseMiner):
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
# interfaces
|
||||
self.api = BMMinerAPI(ip, api_ver)
|
||||
|
||||
# static data
|
||||
self.api_type = "BMMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BMMINER_DATA_LOC
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
@@ -116,30 +140,6 @@ class BMMiner(BaseMiner):
|
||||
async def get_mac(self) -> str:
|
||||
return "00:00:00:00:00:00"
|
||||
|
||||
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
if not api_devdetails:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
try:
|
||||
self.model = api_devdetails["DEVDETAILS"][0]["Model"].replace(
|
||||
"Antminer ", ""
|
||||
)
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
except (TypeError, IndexError, KeyError):
|
||||
pass
|
||||
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
# Check to see if the version info is already cached
|
||||
if self.api_ver:
|
||||
@@ -194,8 +194,7 @@ class BMMiner(BaseMiner):
|
||||
async def get_hostname(self) -> Optional[str]:
|
||||
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||
if hn:
|
||||
self.hostname = hn
|
||||
return self.hostname
|
||||
return hn
|
||||
|
||||
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
@@ -14,35 +14,180 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import json
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
|
||||
import asyncssh
|
||||
import httpx
|
||||
import toml
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
from pyasic.web.bosminer import BOSMinerWebAPI
|
||||
|
||||
BOSMINER_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {
|
||||
"cmd": "get_api_ver",
|
||||
"kwargs": {"api_version": {"api": "version"}},
|
||||
},
|
||||
"fw_ver": {
|
||||
"cmd": "get_fw_ver",
|
||||
"kwargs": {
|
||||
"graphql_version": {"web": {"bos": {"info": {"version": {"full": None}}}}}
|
||||
},
|
||||
},
|
||||
"hostname": {
|
||||
"cmd": "get_hostname",
|
||||
"kwargs": {"graphql_hostname": {"web": {"bos": {"hostname": None}}}},
|
||||
},
|
||||
"hashrate": {
|
||||
"cmd": "get_hashrate",
|
||||
"kwargs": {
|
||||
"api_summary": {"api": "summary"},
|
||||
"graphql_hashrate": {
|
||||
"web": {
|
||||
"bosminer": {
|
||||
"info": {"workSolver": {"realHashrate": {"mhs1M": None}}}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_devs": {"api": "devs"}},
|
||||
},
|
||||
"hashboards": {
|
||||
"cmd": "get_hashboards",
|
||||
"kwargs": {
|
||||
"api_temps": {"api": "temps"},
|
||||
"api_devdetails": {"api": "devdetails"},
|
||||
"api_devs": {"api": "devs"},
|
||||
"graphql_boards": {
|
||||
"web": {
|
||||
"bosminer": {
|
||||
"info": {
|
||||
"workSolver": {
|
||||
"childSolvers": {
|
||||
"name": None,
|
||||
"realHashrate": {"mhs1M": None},
|
||||
"hwDetails": {"chips": None},
|
||||
"temperatures": {"degreesC": None},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"wattage": {
|
||||
"cmd": "get_wattage",
|
||||
"kwargs": {
|
||||
"api_tunerstatus": {"api": "tunerstatus"},
|
||||
"graphql_wattage": {
|
||||
"web": {
|
||||
"bosminer": {
|
||||
"info": {"workSolver": {"power": {"approxConsumptionW": None}}}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"wattage_limit": {
|
||||
"cmd": "get_wattage_limit",
|
||||
"kwargs": {
|
||||
"api_tunerstatus": {"api": "tunerstatus"},
|
||||
"graphql_wattage_limit": {
|
||||
"web": {
|
||||
"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"fans": {
|
||||
"cmd": "get_fans",
|
||||
"kwargs": {
|
||||
"api_fans": {"api": "fans"},
|
||||
"graphql_fans": {
|
||||
"web": {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}}
|
||||
},
|
||||
},
|
||||
},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"errors": {
|
||||
"cmd": "get_errors",
|
||||
"kwargs": {
|
||||
"api_tunerstatus": {"api": "tunerstatus"},
|
||||
"graphql_errors": {
|
||||
"web": {
|
||||
"bosminer": {
|
||||
"info": {
|
||||
"workSolver": {
|
||||
"childSolvers": {
|
||||
"name": None,
|
||||
"tuner": {"statusMessages": None},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
"fault_light": {
|
||||
"cmd": "get_fault_light",
|
||||
"kwargs": {"graphql_fault_light": {"web": {"bos": {"faultLight": None}}}},
|
||||
},
|
||||
"pools": {
|
||||
"cmd": "get_pools",
|
||||
"kwargs": {
|
||||
"api_pools": {"api": "pools"},
|
||||
"graphql_pools": {
|
||||
"web": {
|
||||
"bosminer": {
|
||||
"config": {
|
||||
"... on BosminerConfig": {
|
||||
"groups": {
|
||||
"pools": {"urluser": None},
|
||||
"strategy": {
|
||||
"... on QuotaStrategy": {"quota": None}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerAPI(ip, api_ver)
|
||||
self.web = BOSMinerWebAPI(ip)
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BOSMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = True
|
||||
self.supports_shutdown = True
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
@@ -59,8 +204,12 @@ class BOSMiner(BaseMiner):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
stderr = result.stderr
|
||||
result = result.stdout
|
||||
|
||||
if len(stderr) > len(result):
|
||||
result = stderr
|
||||
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
@@ -72,49 +221,6 @@ class BOSMiner(BaseMiner):
|
||||
# return the result, either command output or None
|
||||
return result
|
||||
|
||||
async def send_graphql_query(self, query) -> Union[dict, None]:
|
||||
# FW version must be equal to or greater than 21.09 to use this
|
||||
if self.fw_ver:
|
||||
try:
|
||||
if not (
|
||||
(
|
||||
int(self.fw_ver.split(".")[0]) == 21
|
||||
and int(self.fw_ver.split(".")[1]) >= 9
|
||||
)
|
||||
or int(self.fw_ver.split(".")[0]) > 21
|
||||
):
|
||||
logging.info(f"FW version {self.fw_ver} is too low to use graphql.")
|
||||
return None
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
url = f"http://{self.ip}/graphql"
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
_auth = await client.post(
|
||||
url,
|
||||
json={
|
||||
"query": 'mutation{auth{login(username:"'
|
||||
+ self.uname
|
||||
+ '", password:"'
|
||||
+ self.pwd
|
||||
+ '"){__typename}}}'
|
||||
},
|
||||
)
|
||||
d = await client.post(url, json={"query": query})
|
||||
if d.status_code == 200:
|
||||
try:
|
||||
data = d.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
raise APIError(f"GraphQL returned malformed data: {d.text}")
|
||||
if data.get("errors"):
|
||||
if len(data["errors"]) > 0:
|
||||
raise APIError(f"GraphQL error: {data['errors'][0]['message']}")
|
||||
|
||||
except httpx.HTTPError:
|
||||
return None
|
||||
return None
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
"""Sends command to turn on fault light on the miner."""
|
||||
logging.debug(f"{self}: Sending fault_light on command.")
|
||||
@@ -137,7 +243,7 @@ class BOSMiner(BaseMiner):
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners._backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
|
||||
"""Restart bosminer hashing process. Wraps [`restart_bosminer`][pyasic.miners.backends.bosminer.BOSMiner.restart_bosminer] to standardize."""
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
@@ -199,7 +305,9 @@ class BOSMiner(BaseMiner):
|
||||
if conn:
|
||||
async with conn:
|
||||
# good ol' BBB compatibility :/
|
||||
toml_data = toml.loads((await conn.run("cat /etc/bosminer.toml")).stdout)
|
||||
toml_data = toml.loads(
|
||||
(await conn.run("cat /etc/bosminer.toml")).stdout
|
||||
)
|
||||
logging.debug(f"{self}: Converting config file.")
|
||||
cfg = MinerConfig().from_raw(toml_data)
|
||||
self.config = cfg
|
||||
@@ -208,6 +316,7 @@ class BOSMiner(BaseMiner):
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
"""Configures miner with yaml config."""
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
self.config = config
|
||||
toml_conf = config.as_bos(
|
||||
model=self.model.replace(" (BOS)", ""), user_suffix=user_suffix
|
||||
)
|
||||
@@ -217,7 +326,9 @@ class BOSMiner(BaseMiner):
|
||||
return None
|
||||
async with conn:
|
||||
# BBB check because bitmain suxx
|
||||
bbb_check = await conn.run("if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;")
|
||||
bbb_check = await conn.run(
|
||||
"if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;"
|
||||
)
|
||||
|
||||
bbb = bbb_check.stdout.strip() == "1"
|
||||
|
||||
@@ -263,37 +374,7 @@ class BOSMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def get_model(self) -> Optional[str]:
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# get devdetails data
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError as e:
|
||||
version_data = None
|
||||
if e.message == "Not ready":
|
||||
cfg = json.loads(await self.send_ssh_command("bosminer config --data"))
|
||||
model = cfg.get("data").get("format").get("model")
|
||||
if model:
|
||||
model = model.replace("Antminer ", "")
|
||||
self.model = model
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
if not version_data["DEVDETAILS"] == []:
|
||||
# handle Antminer BOSMiner as a base
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace(
|
||||
"Antminer ", ""
|
||||
)
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we don't get devdetails, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
return self.model + " (BOS)"
|
||||
|
||||
async def get_version(
|
||||
self, api_version: dict = None, graphql_version: dict = None
|
||||
@@ -325,8 +406,8 @@ class BOSMiner(BaseMiner):
|
||||
async def get_fw_ver(self, graphql_version: dict = None) -> Optional[str]:
|
||||
if not graphql_version:
|
||||
try:
|
||||
graphql_version = await self.send_graphql_query(
|
||||
"{bos{info{version{full}}}}"
|
||||
graphql_version = await self.web.send_command(
|
||||
{"bos": {"info": {"version": {"full"}}}}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -353,18 +434,20 @@ class BOSMiner(BaseMiner):
|
||||
return self.fw_ver
|
||||
|
||||
async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
hostname = None
|
||||
|
||||
if not graphql_hostname:
|
||||
try:
|
||||
graphql_hostname = await self.send_graphql_query("{bos {hostname}}")
|
||||
graphql_hostname = await self.web.send_command({"bos": {"hostname"}})
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if graphql_hostname:
|
||||
self.hostname = graphql_hostname["bos"]["hostname"]
|
||||
return self.hostname
|
||||
try:
|
||||
hostname = graphql_hostname["bos"]["hostname"]
|
||||
return hostname
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
try:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
@@ -372,12 +455,12 @@ class BOSMiner(BaseMiner):
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
hostname = host
|
||||
else:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
except Exception as e:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}, {e}")
|
||||
return self.hostname
|
||||
return hostname
|
||||
|
||||
async def get_hashrate(
|
||||
self, api_summary: dict = None, graphql_hashrate: dict = None
|
||||
@@ -386,8 +469,8 @@ class BOSMiner(BaseMiner):
|
||||
# get hr from graphql
|
||||
if not graphql_hashrate:
|
||||
try:
|
||||
graphql_hashrate = await self.send_graphql_query(
|
||||
"{bosminer{info{workSolver{realHashrate{mhs1M}}}}}"
|
||||
graphql_hashrate = await self.web.send_command(
|
||||
{"bosminer": {"info": {"workSolver": {"realHashrate": {"mhs1M"}}}}}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -433,8 +516,21 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
if not graphql_boards and not (api_devs or api_temps or api_devdetails):
|
||||
try:
|
||||
graphql_boards = await self.send_graphql_query(
|
||||
"{bosminer{info{workSolver{childSolvers{name, realHashrate {mhs1M}, hwDetails {chips}, temperatures {degreesC}}}}}}"
|
||||
graphql_boards = await self.web.send_command(
|
||||
{
|
||||
"bosminer": {
|
||||
"info": {
|
||||
"workSolver": {
|
||||
"childSolvers": {
|
||||
"name": None,
|
||||
"realHashrate": {"mhs1M"},
|
||||
"hwDetails": {"chips"},
|
||||
"temperatures": {"degreesC"},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -532,7 +628,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def get_env_temp(self, *args, **kwargs) -> Optional[float]:
|
||||
async def get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_wattage(
|
||||
@@ -540,8 +636,12 @@ class BOSMiner(BaseMiner):
|
||||
) -> Optional[int]:
|
||||
if not graphql_wattage and not api_tunerstatus:
|
||||
try:
|
||||
graphql_wattage = await self.send_graphql_query(
|
||||
"{bosminer{info{workSolver{power{approxConsumptionW}}}}}"
|
||||
graphql_wattage = await self.web.send_command(
|
||||
{
|
||||
"bosminer": {
|
||||
"info": {"workSolver": {"power": {"approxConsumptionW"}}}
|
||||
}
|
||||
}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -573,8 +673,8 @@ class BOSMiner(BaseMiner):
|
||||
) -> Optional[int]:
|
||||
if not graphql_wattage_limit and not api_tunerstatus:
|
||||
try:
|
||||
graphql_wattage_limit = await self.send_graphql_query(
|
||||
"{bosminer{info{workSolver{power{limitW}}}}}"
|
||||
graphql_wattage_limit = await self.web.send_command(
|
||||
{"bosminer": {"info": {"workSolver": {"power": {"limitW"}}}}}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -604,8 +704,8 @@ class BOSMiner(BaseMiner):
|
||||
) -> List[Fan]:
|
||||
if not graphql_fans and not api_fans:
|
||||
try:
|
||||
graphql_fans = await self.send_graphql_query(
|
||||
"{bosminer{info{fans{name, rpm}}}"
|
||||
graphql_fans = await self.web.send_command(
|
||||
{"bosminer": {"info": {"fans": {"name", "rpm"}}}}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -632,7 +732,7 @@ class BOSMiner(BaseMiner):
|
||||
for n in range(self.fan_count):
|
||||
try:
|
||||
fans[f"fan_{n + 1}"].speed = api_fans["FANS"][n]["RPM"]
|
||||
except KeyError:
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return [fans["fan_1"], fans["fan_2"], fans["fan_3"], fans["fan_4"]]
|
||||
return [Fan(), Fan(), Fan(), Fan()]
|
||||
@@ -645,8 +745,19 @@ class BOSMiner(BaseMiner):
|
||||
) -> List[dict]:
|
||||
if not graphql_pools and not api_pools:
|
||||
try:
|
||||
graphql_pools = await self.send_graphql_query(
|
||||
"bosminer{config{... on BosminerConfig{groups{pools{urluser}strategy{... on QuotaStrategy{quota}}}}}"
|
||||
graphql_pools = await self.web.send_command(
|
||||
{
|
||||
"bosminer": {
|
||||
"config": {
|
||||
"... on BosminerConfig": {
|
||||
"groups": {
|
||||
"pools": {"urluser"},
|
||||
"strategy": {"... on QuotaStrategy": {"quota"}},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -678,13 +789,35 @@ class BOSMiner(BaseMiner):
|
||||
if api_pools:
|
||||
seen = []
|
||||
groups = [{"quota": "0"}]
|
||||
for i, pool in enumerate(api_pools["POOLS"]):
|
||||
if len(seen) == 0:
|
||||
seen.append(pool["User"])
|
||||
if not pool["User"] in seen:
|
||||
# need to use get_config, as this will never read perfectly as there are some bad edge cases
|
||||
groups = []
|
||||
cfg = await self.get_config()
|
||||
if api_pools.get("POOLS"):
|
||||
for i, pool in enumerate(api_pools["POOLS"]):
|
||||
if len(seen) == 0:
|
||||
seen.append(pool["User"])
|
||||
if not pool["User"] in seen:
|
||||
# need to use get_config, as this will never read perfectly as there are some bad edge cases
|
||||
groups = []
|
||||
cfg = await self.get_config()
|
||||
if cfg:
|
||||
for group in cfg.pool_groups:
|
||||
pools = {"quota": group.quota}
|
||||
for _i, _pool in enumerate(group.pools):
|
||||
pools[f"pool_{_i + 1}_url"] = _pool.url.replace(
|
||||
"stratum+tcp://", ""
|
||||
).replace("stratum2+tcp://", "")
|
||||
pools[f"pool_{_i + 1}_user"] = _pool.username
|
||||
groups.append(pools)
|
||||
return groups
|
||||
else:
|
||||
groups[0][f"pool_{i + 1}_url"] = (
|
||||
pool["URL"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
groups[0][f"pool_{i + 1}_user"] = pool["User"]
|
||||
else:
|
||||
groups = []
|
||||
cfg = await self.get_config()
|
||||
if cfg:
|
||||
for group in cfg.pool_groups:
|
||||
pools = {"quota": group.quota}
|
||||
for _i, _pool in enumerate(group.pools):
|
||||
@@ -693,14 +826,7 @@ class BOSMiner(BaseMiner):
|
||||
).replace("stratum2+tcp://", "")
|
||||
pools[f"pool_{_i + 1}_user"] = _pool.username
|
||||
groups.append(pools)
|
||||
return groups
|
||||
else:
|
||||
groups[0][f"pool_{i + 1}_url"] = (
|
||||
pool["URL"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
groups[0][f"pool_{i + 1}_user"] = pool["User"]
|
||||
return groups
|
||||
return groups
|
||||
|
||||
async def get_errors(
|
||||
@@ -708,8 +834,19 @@ class BOSMiner(BaseMiner):
|
||||
) -> List[MinerErrorData]:
|
||||
if not graphql_errors and not api_tunerstatus:
|
||||
try:
|
||||
graphql_errors = await self.send_graphql_query(
|
||||
"{bosminer{info{workSolver{childSolvers{name, tuner{statusMessages}}}}}}"
|
||||
graphql_errors = await self.web.send_command(
|
||||
{
|
||||
"bosminer": {
|
||||
"info": {
|
||||
"workSolver": {
|
||||
"childSolvers": {
|
||||
"name": None,
|
||||
"tuner": {"statusMessages"},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -783,8 +920,8 @@ class BOSMiner(BaseMiner):
|
||||
and int(self.fw_ver.split(".")[1]) > 9
|
||||
) or int(self.fw_ver.split(".")[0]) > 21:
|
||||
try:
|
||||
graphql_fault_light = await self.send_graphql_query(
|
||||
"{bos {faultLight}}"
|
||||
graphql_fault_light = await self.web.send_command(
|
||||
{"bos": {"faultLight"}}
|
||||
)
|
||||
except APIError:
|
||||
pass
|
||||
@@ -795,8 +932,8 @@ class BOSMiner(BaseMiner):
|
||||
else:
|
||||
# worth trying
|
||||
try:
|
||||
graphql_fault_light = await self.send_graphql_query(
|
||||
"{bos {faultLight}}"
|
||||
graphql_fault_light = await self.web.send_command(
|
||||
{"bos": {"faultLight"}}
|
||||
)
|
||||
except APIError:
|
||||
logging.debug(
|
||||
@@ -834,7 +971,6 @@ class BOSMiner(BaseMiner):
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
nom_hr = 0
|
||||
|
||||
if api_devs:
|
||||
try:
|
||||
@@ -14,29 +14,20 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import asyncssh
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.backends import BOSMiner
|
||||
|
||||
|
||||
class BOSMinerOld(BaseMiner):
|
||||
class BOSMinerOld(BOSMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip, api_ver)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
super().__init__(ip, api_ver)
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
@@ -104,81 +95,60 @@ class BOSMinerOld(BaseMiner):
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def get_mac(self) -> Optional[str]:
|
||||
async def get_mac(self, *args, **kwargs) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_model(self) -> str:
|
||||
async def get_model(self, *args, **kwargs) -> str:
|
||||
return "S9"
|
||||
|
||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
||||
async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]:
|
||||
return None, None
|
||||
|
||||
async def get_hostname(self) -> Optional[str]:
|
||||
async def get_hostname(self, *args, **kwargs) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_hashrate(self) -> Optional[float]:
|
||||
async def get_hashrate(self, *args, **kwargs) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_hashboards(self) -> List[HashBoard]:
|
||||
async def get_hashboards(self, *args, **kwargs) -> List[HashBoard]:
|
||||
return []
|
||||
|
||||
async def get_env_temp(self) -> Optional[float]:
|
||||
async def get_env_temp(self, *args, **kwargs) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_wattage(self) -> Optional[int]:
|
||||
async def get_wattage(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_wattage_limit(self) -> Optional[int]:
|
||||
async def get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_fans(
|
||||
self,
|
||||
*args,
|
||||
**kwargs,
|
||||
) -> List[Fan]:
|
||||
return [Fan(), Fan(), Fan(), Fan()]
|
||||
|
||||
async def get_fan_psu(self) -> Optional[int]:
|
||||
async def get_fan_psu(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def get_api_ver(self) -> Optional[str]:
|
||||
async def get_api_ver(self, *args, **kwargs) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_fw_ver(self) -> Optional[str]:
|
||||
async def get_fw_ver(self, *args, **kwargs) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_pools(self, api_pools: dict = None) -> List[dict]:
|
||||
groups = []
|
||||
|
||||
if not api_pools:
|
||||
try:
|
||||
api_pools = await self.api.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_pools:
|
||||
try:
|
||||
pools = {}
|
||||
for i, pool in enumerate(api_pools["POOLS"]):
|
||||
pools[f"pool_{i + 1}_url"] = (
|
||||
pool["URL"]
|
||||
.replace("stratum+tcp://", "")
|
||||
.replace("stratum2+tcp://", "")
|
||||
)
|
||||
pools[f"pool_{i + 1}_user"] = pool["User"]
|
||||
pools["quota"] = pool["Quota"] if pool.get("Quota") else "0"
|
||||
|
||||
groups.append(pools)
|
||||
except KeyError:
|
||||
pass
|
||||
return groups
|
||||
|
||||
async def get_errors(self) -> List[MinerErrorData]:
|
||||
async def get_pools(self, *args, **kwargs) -> List[dict]:
|
||||
return []
|
||||
|
||||
async def get_fault_light(self) -> bool:
|
||||
async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
|
||||
return []
|
||||
|
||||
async def get_fault_light(self, *args, **kwargs) -> bool:
|
||||
return False
|
||||
|
||||
async def get_nominal_hashrate(self) -> Optional[float]:
|
||||
async def get_nominal_hashrate(self, *args, **kwargs) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def get_data(self, allow_warning: bool = False) -> MinerData:
|
||||
async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
@@ -14,26 +14,97 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
import warnings
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
BTMINER_DATA_LOC = {
|
||||
"mac": {
|
||||
"cmd": "get_mac",
|
||||
"kwargs": {
|
||||
"api_summary": {"api": "summary"},
|
||||
"api_get_miner_info": {"api": "get_miner_info"},
|
||||
},
|
||||
},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {
|
||||
"cmd": "get_api_ver",
|
||||
"kwargs": {"api_get_version": {"api": "get_version"}},
|
||||
},
|
||||
"fw_ver": {
|
||||
"cmd": "get_fw_ver",
|
||||
"kwargs": {
|
||||
"api_get_version": {"api": "get_version"},
|
||||
"api_summary": {"api": "summary"},
|
||||
},
|
||||
},
|
||||
"hostname": {
|
||||
"cmd": "get_hostname",
|
||||
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
|
||||
},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_summary": {"api": "summary"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_devs": {"api": "devs"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"wattage_limit": {
|
||||
"cmd": "get_wattage_limit",
|
||||
"kwargs": {"api_summary": {"api": "summary"}},
|
||||
},
|
||||
"fans": {
|
||||
"cmd": "get_fans",
|
||||
"kwargs": {
|
||||
"api_summary": {"api": "summary"},
|
||||
"api_get_psu": {"api": "get_psu"},
|
||||
},
|
||||
},
|
||||
"fan_psu": {
|
||||
"cmd": "get_fan_psu",
|
||||
"kwargs": {
|
||||
"api_summary": {"api": "summary"},
|
||||
"api_get_psu": {"api": "get_psu"},
|
||||
},
|
||||
},
|
||||
"errors": {
|
||||
"cmd": "get_errors",
|
||||
"kwargs": {
|
||||
"api_summary": {"api": "summary"},
|
||||
"api_get_error_code": {"api": "get_error_code"},
|
||||
},
|
||||
},
|
||||
"fault_light": {
|
||||
"cmd": "get_fault_light",
|
||||
"kwargs": {"api_get_miner_info": {"api": "get_miner_info"}},
|
||||
},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
# interfaces
|
||||
self.api = BTMinerAPI(ip, api_ver)
|
||||
|
||||
# static data
|
||||
self.api_type = "BTMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BTMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_shutdown = True
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
||||
@@ -115,21 +186,13 @@ class BTMiner(BaseMiner):
|
||||
return False
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
|
||||
conf = config.as_wm(user_suffix=user_suffix)
|
||||
pools_conf = conf["pools"]
|
||||
|
||||
try:
|
||||
await self.api.update_pools(
|
||||
pools_conf[0]["url"],
|
||||
pools_conf[0]["user"],
|
||||
pools_conf[0]["pass"],
|
||||
pools_conf[1]["url"],
|
||||
pools_conf[1]["user"],
|
||||
pools_conf[1]["pass"],
|
||||
pools_conf[2]["url"],
|
||||
pools_conf[2]["user"],
|
||||
pools_conf[2]["pass"],
|
||||
)
|
||||
await self.api.update_pools(**pools_conf)
|
||||
except APIError:
|
||||
pass
|
||||
try:
|
||||
@@ -149,16 +212,25 @@ class BTMiner(BaseMiner):
|
||||
summary = data["summary"][0]
|
||||
except APIError as e:
|
||||
logging.warning(e)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
if pools:
|
||||
if "POOLS" in pools:
|
||||
cfg = cfg.from_api(pools["POOLS"])
|
||||
else:
|
||||
# somethings wrong with the miner
|
||||
warnings.warn(
|
||||
f"Failed to gather pool config for miner: {self}, miner did not return pool information."
|
||||
)
|
||||
if summary:
|
||||
if "SUMMARY" in summary:
|
||||
if wattage := summary["SUMMARY"][0].get("Power Limit"):
|
||||
cfg.autotuning_wattage = wattage
|
||||
|
||||
return cfg
|
||||
self.config = cfg
|
||||
|
||||
return self.config
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
@@ -202,28 +274,6 @@ class BTMiner(BaseMiner):
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
|
||||
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
if not api_devdetails:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
try:
|
||||
self.model = api_devdetails["DEVDETAILS"][0]["Model"].split("V")[0]
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
except (TypeError, IndexError, KeyError):
|
||||
pass
|
||||
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_version(
|
||||
self, api_get_version: dict = None, api_summary: dict = None
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
@@ -301,9 +351,7 @@ class BTMiner(BaseMiner):
|
||||
return self.fw_ver
|
||||
|
||||
async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
|
||||
hostname = None
|
||||
if not api_get_miner_info:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
@@ -312,11 +360,11 @@ class BTMiner(BaseMiner):
|
||||
|
||||
if api_get_miner_info:
|
||||
try:
|
||||
self.hostname = api_get_miner_info["Msg"]["hostname"]
|
||||
hostname = api_get_miner_info["Msg"]["hostname"]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
return self.hostname
|
||||
return hostname
|
||||
|
||||
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
@@ -534,8 +582,6 @@ class BTMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def get_fault_light(self, api_get_miner_info: dict = None) -> bool:
|
||||
data = None
|
||||
|
||||
if not api_get_miner_info:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
@@ -14,32 +14,55 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import asyncssh
|
||||
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
|
||||
CGMINER_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class CGMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
# interfaces
|
||||
self.api = CGMinerAPI(ip, api_ver)
|
||||
self.api_ver = api_ver
|
||||
|
||||
# static data
|
||||
self.api_type = "CGMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
# data gathering locations
|
||||
self.data_locations = CGMINER_DATA_LOC
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
@@ -70,7 +93,7 @@ class CGMiner(BaseMiner):
|
||||
return result
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners._backends.cgminer.CGMiner.restart_cgminer] to standardize."""
|
||||
"""Restart cgminer hashing process. Wraps [`restart_cgminer`][pyasic.miners.backends.cgminer.CGMiner.restart_cgminer] to standardize."""
|
||||
return await self.restart_cgminer()
|
||||
|
||||
async def restart_cgminer(self) -> bool:
|
||||
@@ -129,12 +152,8 @@ class CGMiner(BaseMiner):
|
||||
else:
|
||||
return True
|
||||
|
||||
async def get_config(self, api_pools: dict = None) -> MinerConfig:
|
||||
# get pool data
|
||||
try:
|
||||
api_pools = await self.api.pools()
|
||||
except APIError:
|
||||
pass
|
||||
async def get_config(self) -> MinerConfig:
|
||||
api_pools = await self.api.pools()
|
||||
|
||||
if api_pools:
|
||||
self.config = MinerConfig().from_api(api_pools["POOLS"])
|
||||
@@ -159,30 +178,6 @@ class CGMiner(BaseMiner):
|
||||
async def get_mac(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def get_model(self, api_devdetails: dict = None) -> Optional[str]:
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
if not api_devdetails:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
try:
|
||||
self.model = api_devdetails["DEVDETAILS"][0]["Model"].replace(
|
||||
"Antminer ", ""
|
||||
)
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
except (TypeError, IndexError, KeyError):
|
||||
pass
|
||||
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_version(
|
||||
self, api_version: dict = None
|
||||
) -> Tuple[Optional[str], Optional[str]]:
|
||||
@@ -234,8 +229,7 @@ class CGMiner(BaseMiner):
|
||||
except (asyncssh.Error, OSError):
|
||||
return None
|
||||
if hn:
|
||||
self.hostname = hn
|
||||
return self.hostname
|
||||
return hn
|
||||
|
||||
async def get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
@@ -14,26 +14,45 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
import logging
|
||||
import re
|
||||
from collections import namedtuple
|
||||
from typing import List, Optional, Tuple, Union
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners._backends import CGMiner
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
from pyasic.miners.backends import CGMiner
|
||||
|
||||
AVALON_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {"mac": {"web": "mac"}}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {}},
|
||||
"wattage_limit": {"cmd": "get_wattage_limit", "kwargs": {}},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class CGMinerAvalon(CGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
|
||||
# data gathering locations
|
||||
self.data_locations = AVALON_DATA_LOC
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
try:
|
||||
@@ -74,11 +93,12 @@ class CGMinerAvalon(CGMiner):
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
"""Configures miner with yaml config."""
|
||||
self.config = config
|
||||
return None
|
||||
logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
|
||||
conf = config.as_avalon(user_suffix=user_suffix)
|
||||
try:
|
||||
data = await self.api.ascset(
|
||||
data = await self.api.ascset( # noqa
|
||||
0, "setpool", f"root,root,{conf}"
|
||||
) # this should work but doesn't
|
||||
except APIError:
|
||||
@@ -14,15 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import ipaddress
|
||||
|
||||
from pyasic.miners._backends import BMMiner
|
||||
from pyasic.miners.backends import BMMiner
|
||||
|
||||
|
||||
class Hiveon(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
# static data
|
||||
self.api_type = "Hiveon"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
@@ -14,124 +14,74 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import json
|
||||
import logging
|
||||
import warnings
|
||||
from typing import Optional, Union
|
||||
|
||||
import httpx
|
||||
from typing import Optional
|
||||
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners._backends.bmminer import BMMiner
|
||||
from pyasic.settings import PyasicSettings
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.web.vnish import VNishWebAPI
|
||||
|
||||
VNISH_DATA_LOC = {
|
||||
"mac": {"cmd": "get_mac", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"model": {"cmd": "get_model", "kwargs": {}},
|
||||
"api_ver": {"cmd": "get_api_ver", "kwargs": {"api_version": {"api": "version"}}},
|
||||
"fw_ver": {"cmd": "get_fw_ver", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"hostname": {"cmd": "get_hostname", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"hashrate": {"cmd": "get_hashrate", "kwargs": {"api_summary": {"api": "summary"}}},
|
||||
"nominal_hashrate": {
|
||||
"cmd": "get_nominal_hashrate",
|
||||
"kwargs": {"api_stats": {"api": "stats"}},
|
||||
},
|
||||
"hashboards": {"cmd": "get_hashboards", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"env_temp": {"cmd": "get_env_temp", "kwargs": {}},
|
||||
"wattage": {"cmd": "get_wattage", "kwargs": {"web_summary": {"web": "summary"}}},
|
||||
"wattage_limit": {
|
||||
"cmd": "get_wattage_limit",
|
||||
"kwargs": {"web_settings": {"web": "settings"}},
|
||||
},
|
||||
"fans": {"cmd": "get_fans", "kwargs": {"api_stats": {"api": "stats"}}},
|
||||
"fan_psu": {"cmd": "get_fan_psu", "kwargs": {}},
|
||||
"errors": {"cmd": "get_errors", "kwargs": {}},
|
||||
"fault_light": {"cmd": "get_fault_light", "kwargs": {}},
|
||||
"pools": {"cmd": "get_pools", "kwargs": {"api_pools": {"api": "pools"}}},
|
||||
}
|
||||
|
||||
|
||||
class VNish(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = VNishWebAPI(ip)
|
||||
|
||||
# static data
|
||||
self.api_type = "VNish"
|
||||
self.uname = "root"
|
||||
self.pwd = PyasicSettings().global_vnish_password
|
||||
self.jwt = None
|
||||
|
||||
async def auth(self):
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
auth = await client.post(
|
||||
f"http://{self.ip}/api/v1/unlock",
|
||||
json={"pw": self.pwd},
|
||||
)
|
||||
except httpx.HTTPError:
|
||||
warnings.warn(f"Could not authenticate web token with miner: {self}")
|
||||
else:
|
||||
if not auth.status_code == 200:
|
||||
warnings.warn(
|
||||
f"Could not authenticate web token with miner: {self}"
|
||||
)
|
||||
return None
|
||||
json_auth = auth.json()
|
||||
self.jwt = json_auth["token"]
|
||||
return self.jwt
|
||||
|
||||
async def send_web_command(
|
||||
self, command: str, data: Union[dict, None] = None, method: str = "GET"
|
||||
):
|
||||
if not self.jwt:
|
||||
await self.auth()
|
||||
if not data:
|
||||
data = {}
|
||||
async with httpx.AsyncClient() as client:
|
||||
for i in range(PyasicSettings().miner_get_data_retries):
|
||||
try:
|
||||
auth = self.jwt
|
||||
if command.startswith("system"):
|
||||
auth = "Bearer " + self.jwt
|
||||
if method == "GET":
|
||||
response = await client.get(
|
||||
f"http://{self.ip}/api/v1/{command}",
|
||||
headers={"Authorization": auth},
|
||||
timeout=5,
|
||||
)
|
||||
elif method == "POST":
|
||||
if data:
|
||||
response = await client.post(
|
||||
f"http://{self.ip}/api/v1/{command}",
|
||||
headers={"Authorization": auth},
|
||||
timeout=5,
|
||||
json=data,
|
||||
)
|
||||
else:
|
||||
response = await client.post(
|
||||
f"http://{self.ip}/api/v1/{command}",
|
||||
headers={"Authorization": auth},
|
||||
timeout=5,
|
||||
)
|
||||
else:
|
||||
raise APIError("Bad method type.")
|
||||
if not response.status_code == 200:
|
||||
# refresh the token, retry
|
||||
await self.auth()
|
||||
continue
|
||||
json_data = response.json()
|
||||
if json_data:
|
||||
return json_data
|
||||
return True
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
# data gathering locations
|
||||
self.data_locations = VNISH_DATA_LOC
|
||||
|
||||
async def get_model(self, api_stats: dict = None) -> Optional[str]:
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (VNish)")
|
||||
return self.model + " (VNish)"
|
||||
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
m_type = api_stats["STATS"][0]["Type"]
|
||||
self.model = m_type.split(" ")[1]
|
||||
return self.model
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
return self.model + "(VNISH)"
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
data = await self.send_web_command("mining/restart", method="POST")
|
||||
return data
|
||||
data = await self.web.restart_vnish()
|
||||
if data:
|
||||
try:
|
||||
return data["success"]
|
||||
except KeyError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
data = await self.send_web_command("system/reboot", method="POST")
|
||||
return data
|
||||
data = await self.web.reboot()
|
||||
if data:
|
||||
try:
|
||||
return data["success"]
|
||||
except KeyError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def get_mac(self, web_summary: dict = None) -> str:
|
||||
if not web_summary:
|
||||
web_info = await self.send_web_command("info")
|
||||
web_info = await self.web.info()
|
||||
|
||||
if web_info:
|
||||
try:
|
||||
@@ -149,7 +99,7 @@ class VNish(BMMiner):
|
||||
|
||||
async def get_hostname(self, web_summary: dict = None) -> str:
|
||||
if not web_summary:
|
||||
web_info = await self.send_web_command("info")
|
||||
web_info = await self.web.info()
|
||||
|
||||
if web_info:
|
||||
try:
|
||||
@@ -167,7 +117,7 @@ class VNish(BMMiner):
|
||||
|
||||
async def get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||
if not web_summary:
|
||||
web_summary = await self.send_web_command("summary")
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
@@ -196,18 +146,20 @@ class VNish(BMMiner):
|
||||
|
||||
async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
|
||||
if not web_settings:
|
||||
web_settings = await self.send_web_command("summary")
|
||||
web_settings = await self.web.summary()
|
||||
|
||||
if web_settings:
|
||||
try:
|
||||
wattage_limit = web_settings["miner"]["overclock"]["preset"]
|
||||
if wattage_limit == "disabled":
|
||||
return None
|
||||
return int(wattage_limit)
|
||||
except (KeyError, TypeError):
|
||||
pass
|
||||
|
||||
async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||
if not web_summary:
|
||||
web_summary = await self.send_web_command("summary")
|
||||
web_summary = await self.web.summary()
|
||||
|
||||
if web_summary:
|
||||
try:
|
||||
32
pyasic/miners/backends/whatsminer.py
Normal file
32
pyasic/miners/backends/whatsminer.py
Normal file
@@ -0,0 +1,32 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends.btminer import BTMiner
|
||||
|
||||
|
||||
class M5X(BTMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_autotuning = True
|
||||
|
||||
|
||||
class M3X(BTMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_autotuning = True
|
||||
|
||||
|
||||
class M2X(BTMiner):
|
||||
pass
|
||||
@@ -29,22 +29,32 @@ from pyasic.errors import APIError
|
||||
|
||||
|
||||
class BaseMiner(ABC):
|
||||
def __init__(self, *args, **kwargs) -> None:
|
||||
self.ip = None
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
def __init__(self, ip: str, *args, **kwargs) -> None:
|
||||
# interfaces
|
||||
self.api = None
|
||||
self.web = None
|
||||
|
||||
# static data
|
||||
self.ip = ip
|
||||
self.api_type = None
|
||||
self.api_ver = None
|
||||
self.fw_ver = None
|
||||
self.model = None
|
||||
# type
|
||||
self.make = None
|
||||
self.light = None
|
||||
self.hostname = None
|
||||
self.model = None
|
||||
# physical attributes
|
||||
self.ideal_hashboards = 3
|
||||
self.nominal_chips = 1
|
||||
self.fan_count = 2
|
||||
# data gathering locations
|
||||
self.data_locations = None
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = False
|
||||
self.supports_shutdown = False
|
||||
|
||||
# data storage
|
||||
self.api_ver = None
|
||||
self.fw_ver = None
|
||||
self.light = None
|
||||
self.config = None
|
||||
self.ideal_hashboards = 3
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMiner:
|
||||
@@ -69,8 +79,8 @@ class BaseMiner(ABC):
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username=self.uname,
|
||||
password=self.pwd,
|
||||
username="root",
|
||||
password="root",
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
@@ -194,14 +204,13 @@ class BaseMiner(ABC):
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def get_model(self) -> Optional[str]:
|
||||
"""Get the model of the miner and return it as a string.
|
||||
|
||||
Returns:
|
||||
A string representing the model of the miner.
|
||||
"""
|
||||
pass
|
||||
return self.model
|
||||
|
||||
@abstractmethod
|
||||
async def get_api_ver(self, *args, **kwargs) -> Optional[str]:
|
||||
@@ -359,71 +368,75 @@ class BaseMiner(ABC):
|
||||
"fault_light",
|
||||
"pools",
|
||||
]
|
||||
api_params = []
|
||||
web_params = []
|
||||
gql = False
|
||||
api_multicommand = []
|
||||
web_multicommand = []
|
||||
for data_name in data_to_get:
|
||||
function = getattr(self, "get_" + data_name)
|
||||
sig = inspect.signature(function)
|
||||
for item in sig.parameters:
|
||||
if item.startswith("api_"):
|
||||
command = item.replace("api_", "")
|
||||
api_params.append(command)
|
||||
elif item.startswith("graphql_"):
|
||||
gql = True
|
||||
elif item.startswith("web_"):
|
||||
web_params.append(item.replace("web_", ""))
|
||||
api_params = list(set(api_params))
|
||||
web_params = list(set(web_params))
|
||||
miner_data = {}
|
||||
command_data = await self.api.multicommand(
|
||||
*api_params, allow_warning=allow_warning
|
||||
)
|
||||
if gql:
|
||||
try:
|
||||
gql_data = await self.send_graphql_query( # noqa: bosminer only anyway
|
||||
"{bos {hostname}, bosminer{config{... on BosminerConfig{groups{pools{url, user}, strategy{... on QuotaStrategy {quota}}}}}, info{fans{name, rpm}, workSolver{realHashrate{mhs1M}, temperatures{degreesC}, power{limitW, approxConsumptionW}, childSolvers{name, realHashrate{mhs1M}, hwDetails{chips}, tuner{statusMessages}, temperatures{degreesC}}}}}}"
|
||||
)
|
||||
except APIError:
|
||||
# hostname hates me
|
||||
try:
|
||||
gql_data = await self.send_graphql_query( # noqa: bosminer only anyway
|
||||
"{bosminer{config{... on BosminerConfig{groups{pools{url, user}, strategy{... on QuotaStrategy {quota}}}}}, info{fans{name, rpm}, workSolver{realHashrate{mhs1M}, temperatures{degreesC}, power{limitW, approxConsumptionW}, childSolvers{name, realHashrate{mhs1M}, hwDetails{chips}, tuner{statusMessages}, temperatures{degreesC}}}}}}"
|
||||
)
|
||||
except APIError:
|
||||
# AGAIN???
|
||||
gql_data = None
|
||||
fn_args = self.data_locations[data_name]["kwargs"]
|
||||
for arg_name in fn_args:
|
||||
if fn_args[arg_name].get("api"):
|
||||
api_multicommand.append(fn_args[arg_name]["api"])
|
||||
if fn_args[arg_name].get("web"):
|
||||
web_multicommand.append(fn_args[arg_name]["web"])
|
||||
except KeyError as e:
|
||||
print(e, data_name)
|
||||
continue
|
||||
|
||||
api_multicommand = list(set(api_multicommand))
|
||||
_web_multicommand = web_multicommand
|
||||
for item in web_multicommand:
|
||||
if item not in _web_multicommand:
|
||||
_web_multicommand.append(item)
|
||||
web_multicommand = _web_multicommand
|
||||
if len(api_multicommand) > 0:
|
||||
api_command_data = await self.api.multicommand(
|
||||
*api_multicommand, allow_warning=allow_warning
|
||||
)
|
||||
else:
|
||||
api_command_data = {}
|
||||
if len(web_multicommand) > 0:
|
||||
web_command_data = await self.web.multicommand(
|
||||
*web_multicommand, allow_warning=allow_warning
|
||||
)
|
||||
else:
|
||||
web_command_data = {}
|
||||
|
||||
miner_data = {}
|
||||
|
||||
web_data = {}
|
||||
for command in web_params:
|
||||
data = await self.send_web_command(command) # noqa: web only anyway
|
||||
web_data[command] = data
|
||||
for data_name in data_to_get:
|
||||
function = getattr(self, "get_" + data_name)
|
||||
sig = inspect.signature(function)
|
||||
kwargs = {}
|
||||
for arg_name in sig.parameters:
|
||||
if arg_name.startswith("api_"):
|
||||
try:
|
||||
kwargs[arg_name] = command_data[arg_name.replace("api_", "")][0]
|
||||
except (KeyError, IndexError):
|
||||
kwargs[arg_name] = None
|
||||
elif arg_name.startswith("graphql_"):
|
||||
kwargs[
|
||||
arg_name
|
||||
] = gql_data # noqa: variable is not referenced before assignment
|
||||
elif arg_name.startswith("web_"):
|
||||
try:
|
||||
kwargs[arg_name] = web_data[arg_name.replace("web_", "")]
|
||||
except (KeyError, IndexError):
|
||||
kwargs[arg_name] = None
|
||||
try:
|
||||
fn_args = self.data_locations[data_name]["kwargs"]
|
||||
args_to_send = {k: None for k in fn_args}
|
||||
for arg_name in fn_args:
|
||||
if fn_args[arg_name].get("api"):
|
||||
if api_command_data.get("multicommand"):
|
||||
args_to_send[arg_name] = api_command_data[
|
||||
fn_args[arg_name]["api"]
|
||||
][0]
|
||||
else:
|
||||
args_to_send[arg_name] = api_command_data
|
||||
if fn_args[arg_name].get("web"):
|
||||
if web_command_data.get("multicommand"):
|
||||
args_to_send[arg_name] = web_command_data[
|
||||
fn_args[arg_name]["web"]
|
||||
]
|
||||
else:
|
||||
if not web_command_data == {"multicommand": False}:
|
||||
args_to_send[arg_name] = web_command_data
|
||||
except (KeyError, IndexError) as e:
|
||||
continue
|
||||
|
||||
function = getattr(self, self.data_locations[data_name]["cmd"])
|
||||
if not data_name == "pools":
|
||||
miner_data[data_name] = await function(**kwargs)
|
||||
miner_data[data_name] = await function(**args_to_send)
|
||||
else:
|
||||
pools_data = await function(**kwargs)
|
||||
pools_data = await function(**args_to_send)
|
||||
if pools_data:
|
||||
miner_data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||
miner_data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||
try:
|
||||
miner_data["pool_1_url"] = pools_data[0]["pool_1_url"]
|
||||
miner_data["pool_1_user"] = pools_data[0]["pool_1_user"]
|
||||
except KeyError:
|
||||
pass
|
||||
if len(pools_data) > 1:
|
||||
miner_data["pool_2_url"] = pools_data[1]["pool_2_url"]
|
||||
miner_data["pool_2_user"] = pools_data[1]["pool_2_user"]
|
||||
|
||||
19
pyasic/miners/btc/__init__.py
Normal file
19
pyasic/miners/btc/__init__.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .antminer import *
|
||||
from .avalonminer import *
|
||||
from .innosilicon import *
|
||||
from .whatsminer import *
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S17(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S17"
|
||||
self.nominal_chips = 48
|
||||
@@ -14,13 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.model = "S17+"
|
||||
self.nominal_chips = 65
|
||||
self.fan_count = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S17 Pro"
|
||||
self.nominal_chips = 48
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S17e(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S17e"
|
||||
self.nominal_chips = 135
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class T17(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T17"
|
||||
self.nominal_chips = 30
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T17+"
|
||||
self.nominal_chips = 44
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class T17e(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T17e"
|
||||
self.nominal_chips = 78
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19"
|
||||
self.nominal_chips = 76
|
||||
26
pyasic/miners/btc/_types/antminer/X19/S19L.py
Normal file
26
pyasic/miners/btc/_types/antminer/X19/S19L.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# Copyright 2022 Upstream Data Inc -
|
||||
# -
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
||||
# you may not use this file except in compliance with the License. -
|
||||
# You may obtain a copy of the License at -
|
||||
# -
|
||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
||||
# -
|
||||
# Unless required by applicable law or agreed to in writing, software -
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19L(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19L"
|
||||
self.nominal_chips = 76
|
||||
self.fan_count = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19 Pro"
|
||||
self.nominal_chips = 114
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19XP(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19 XP"
|
||||
self.nominal_chips = 110
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19a(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19a"
|
||||
self.nominal_chips = 72
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19a Pro"
|
||||
self.nominal_chips = 100
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19j(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19j"
|
||||
self.nominal_chips = 114
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S19j Pro"
|
||||
self.nominal_chips = 126
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class T19(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T19"
|
||||
self.nominal_chips = 76
|
||||
@@ -21,4 +21,5 @@ from .S19a import S19a
|
||||
from .S19a_Pro import S19aPro
|
||||
from .S19j import S19j
|
||||
from .S19j_Pro import S19jPro
|
||||
from .S19L import S19L
|
||||
from .T19 import T19
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S9(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S9"
|
||||
self.nominal_chips = 63
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class S9i(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "S9i"
|
||||
self.nominal_chips = 63
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMiner
|
||||
|
||||
|
||||
class T9(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T9"
|
||||
self.nominal_chips = 54
|
||||
@@ -13,7 +13,6 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1026"
|
||||
self.nominal_chips = 80
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1047"
|
||||
self.nominal_chips = 80
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1066"
|
||||
self.nominal_chips = 114
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 721"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 741"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 761"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 821"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 841"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 851"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
|
||||
|
||||
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "Avalon 921"
|
||||
self.ideal_hashboards = 4
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners._types.makes import InnosiliconMiner
|
||||
from pyasic.miners.makes import InnosiliconMiner
|
||||
|
||||
|
||||
class InnosiliconT3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__()
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.model = "T3H+"
|
||||
self.nominal_chips = 114
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user