Compare commits
105 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5984338c64 | ||
|
|
07d1c48e33 | ||
|
|
d2abae947c | ||
|
|
e4a0f2451a | ||
|
|
880c598b1a | ||
|
|
3632c2c4d8 | ||
|
|
09bc9686ae | ||
|
|
34584ab098 | ||
|
|
554d99ca08 | ||
|
|
5c5d688ffa | ||
|
|
c50d55e87c | ||
|
|
5e5516bfb3 | ||
|
|
4b068c57c5 | ||
|
|
203f199aec | ||
|
|
895f17aaf9 | ||
|
|
8a64ff3559 | ||
|
|
4c45d356c4 | ||
|
|
4dec329f11 | ||
|
|
b563ed118e | ||
|
|
75b2ec40b1 | ||
|
|
d9adaf6667 | ||
|
|
9343308f41 | ||
|
|
88769e40ae | ||
|
|
be45eb7400 | ||
|
|
2f719a03a4 | ||
|
|
64196f9754 | ||
|
|
49a77f1b79 | ||
|
|
3838c4f2f9 | ||
|
|
80d89c95b5 | ||
|
|
30cd8b5cfe | ||
|
|
c443170f78 | ||
|
|
a2c2aa2377 | ||
|
|
4f0eb49a02 | ||
|
|
a821357b4f | ||
|
|
3c7679a22d | ||
|
|
a52737e236 | ||
|
|
7c96bbe153 | ||
|
|
e8bbf22aa7 | ||
|
|
5ac8b27cb6 | ||
|
|
6c14902484 | ||
|
|
96aa346f00 | ||
|
|
c2b6cc7468 | ||
|
|
ac7f41be44 | ||
|
|
718b87fd12 | ||
|
|
5ad23c6cd0 | ||
|
|
66be443dc3 | ||
|
|
a9135e21d4 | ||
|
|
dd4c087749 | ||
|
|
aa1d7c1b6f | ||
|
|
b328a27f04 | ||
|
|
c5eed797ec | ||
|
|
4fd2199435 | ||
|
|
3226d47846 | ||
|
|
6c1931fe7e | ||
|
|
1dd87ac102 | ||
|
|
95d1e40b4f | ||
|
|
31682b7fae | ||
|
|
e6523fc7d5 | ||
|
|
91de12467b | ||
|
|
d81e3e9f88 | ||
|
|
49fc0f3c54 | ||
|
|
4b36044e56 | ||
|
|
90fb67f586 | ||
|
|
edf31ae7df | ||
|
|
af354fd8e2 | ||
|
|
6a2a3e836d | ||
|
|
41709e4706 | ||
|
|
b60c7a55d4 | ||
|
|
eed1973345 | ||
|
|
64774d2017 | ||
|
|
e9751d6cd1 | ||
|
|
e2b0a76e67 | ||
|
|
1c5c39fa97 | ||
|
|
27c48764a8 | ||
|
|
5e01f7517b | ||
|
|
569f659fac | ||
|
|
dd9c6f1f63 | ||
|
|
0958f47cfe | ||
|
|
3820b40f44 | ||
|
|
cce1917c00 | ||
|
|
2ee19f47e7 | ||
|
|
ff526a3273 | ||
|
|
7811245ec9 | ||
|
|
cbab76847a | ||
|
|
ce981d1787 | ||
|
|
4b5314a8f6 | ||
|
|
3be3086a38 | ||
|
|
a0c76fe24f | ||
|
|
acdcfd04cd | ||
|
|
91a5998b4e | ||
|
|
7292af450c | ||
|
|
307926afbb | ||
|
|
10293ae24a | ||
|
|
f820372532 | ||
|
|
22965ffefa | ||
|
|
34ca5ba68f | ||
|
|
468134e754 | ||
|
|
5327b3fe3d | ||
|
|
68b85aa7da | ||
|
|
b78652b279 | ||
|
|
832a276f4b | ||
|
|
2b82b29690 | ||
|
|
56dd1c80b5 | ||
|
|
d686cdacc8 | ||
|
|
aab8825997 |
@@ -1,27 +0,0 @@
|
|||||||
# pyasic
|
|
||||||
## Miner APIs
|
|
||||||
Each miner has a unique API that is used to communicate with it.
|
|
||||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
|
||||||
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
|
||||||
|
|
||||||
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
|
||||||
|
|
||||||
[`BaseMinerAPI`][pyasic.API.BaseMinerAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
|
||||||
[`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]
|
|
||||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
|
||||||
#### [LUXMiner API][pyasic.API.luxminer.LUXMinerAPI]
|
|
||||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
|
||||||
|
|
||||||
<br>
|
|
||||||
|
|
||||||
## BaseMinerAPI
|
|
||||||
::: pyasic.API.BaseMinerAPI
|
|
||||||
handler: python
|
|
||||||
options:
|
|
||||||
heading_level: 4
|
|
||||||
@@ -3,7 +3,7 @@ import importlib
|
|||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from pyasic.miners.miner_factory import MINER_CLASSES, MinerTypes
|
from pyasic.miners.factory import MINER_CLASSES, MinerTypes
|
||||||
|
|
||||||
warnings.filterwarnings("ignore")
|
warnings.filterwarnings("ignore")
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
---
|
---
|
||||||
##### Creating miners based on IP
|
##### 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, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.factory.MinerFactory] to communicate and identify the miners, or an abstraction of its functionality, [`get_miner()`][pyasic.miners.get_miner].
|
||||||
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
The function [`get_miner()`][pyasic.miners.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||||
```python
|
```python
|
||||||
import asyncio # asyncio for handling the async part
|
import asyncio # asyncio for handling the async part
|
||||||
@@ -122,24 +122,24 @@ if __name__ == "__main__":
|
|||||||
## Miner control
|
## Miner control
|
||||||
---
|
---
|
||||||
`pyasic` exposes a standard interface for each miner using control functions.
|
`pyasic` exposes a standard interface for each miner using control functions.
|
||||||
Every miner class in `pyasic` must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner].
|
Every miner class in `pyasic` must implement all the control functions defined in [`MinerProtocol`][pyasic.miners.base.MinerProtocol].
|
||||||
|
|
||||||
These functions are
|
These functions are
|
||||||
[`check_light`][pyasic.miners.BaseMiner.check_light],
|
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
|
||||||
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off],
|
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
|
||||||
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on],
|
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
|
||||||
[`get_config`][pyasic.miners.BaseMiner.get_config],
|
[`get_config`][pyasic.miners.base.MinerProtocol.get_config],
|
||||||
[`get_data`][pyasic.miners.BaseMiner.get_data],
|
[`get_data`][pyasic.miners.base.MinerProtocol.get_data],
|
||||||
[`get_errors`][pyasic.miners.BaseMiner.get_errors],
|
[`get_errors`][pyasic.miners.base.MinerProtocol.get_errors],
|
||||||
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname],
|
[`get_hostname`][pyasic.miners.base.MinerProtocol.get_hostname],
|
||||||
[`get_model`][pyasic.miners.BaseMiner.get_model],
|
[`get_model`][pyasic.miners.base.MinerProtocol.get_model],
|
||||||
[`reboot`][pyasic.miners.BaseMiner.reboot],
|
[`reboot`][pyasic.miners.base.MinerProtocol.reboot],
|
||||||
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend],
|
[`restart_backend`][pyasic.miners.base.MinerProtocol.restart_backend],
|
||||||
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining],
|
[`stop_mining`][pyasic.miners.base.MinerProtocol.stop_mining],
|
||||||
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining],
|
[`resume_mining`][pyasic.miners.base.MinerProtocol.resume_mining],
|
||||||
[`is_mining`][pyasic.miners.BaseMiner.is_mining],
|
[`is_mining`][pyasic.miners.base.MinerProtocol.is_mining],
|
||||||
[`send_config`][pyasic.miners.BaseMiner.send_config], and
|
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
|
||||||
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit].
|
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
|
||||||
|
|
||||||
##### Usage
|
##### Usage
|
||||||
```python
|
```python
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BOSMiner Backend
|
## BOSMiner Backend
|
||||||
|
|
||||||
::: pyasic.miners.backends.bosminer.BOSMiner
|
::: pyasic.miners.backends.braiins_os.BOSMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
show_root_heading: false
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
## BOSer Backend
|
||||||
|
|
||||||
|
::: pyasic.miners.backends.braiins_os.BOSer
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
|
|||||||
@@ -1,10 +1,17 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Base Miner
|
## Base Miner
|
||||||
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
[`BaseMiner`][pyasic.miners.base.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
|
||||||
|
|
||||||
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`.
|
This class inherits from the [`MinerProtocol`][pyasic.miners.base.MinerProtocol], which outlines functionality for miners.
|
||||||
|
|
||||||
::: pyasic.miners.BaseMiner
|
You may not instantiate this class on its own, only subclass from it.
|
||||||
|
|
||||||
|
::: pyasic.miners.base.BaseMiner
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
|
|
||||||
|
::: pyasic.miners.base.MinerProtocol
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|||||||
@@ -1,91 +1,91 @@
|
|||||||
## Control functionality
|
## Control functionality
|
||||||
|
|
||||||
### Check Light
|
### Check Light
|
||||||
::: pyasic.miners.BaseMiner.check_light
|
::: pyasic.miners.base.MinerProtocol.check_light
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Fault Light Off
|
### Fault Light Off
|
||||||
::: pyasic.miners.BaseMiner.fault_light_off
|
::: pyasic.miners.base.MinerProtocol.fault_light_off
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Fault Light On
|
### Fault Light On
|
||||||
::: pyasic.miners.BaseMiner.fault_light_on
|
::: pyasic.miners.base.MinerProtocol.fault_light_on
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Get Config
|
### Get Config
|
||||||
::: pyasic.miners.BaseMiner.get_config
|
::: pyasic.miners.base.MinerProtocol.get_config
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Get Data
|
### Get Data
|
||||||
::: pyasic.miners.BaseMiner.get_data
|
::: pyasic.miners.base.MinerProtocol.get_data
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Get Errors
|
### Get Errors
|
||||||
::: pyasic.miners.BaseMiner.get_errors
|
::: pyasic.miners.base.MinerProtocol.get_errors
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Get Hostname
|
### Get Hostname
|
||||||
::: pyasic.miners.BaseMiner.get_hostname
|
::: pyasic.miners.base.MinerProtocol.get_hostname
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Get Model
|
### Get Model
|
||||||
::: pyasic.miners.BaseMiner.get_model
|
::: pyasic.miners.base.MinerProtocol.get_model
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Reboot
|
### Reboot
|
||||||
::: pyasic.miners.BaseMiner.reboot
|
::: pyasic.miners.base.MinerProtocol.reboot
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Restart Backend
|
### Restart Backend
|
||||||
::: pyasic.miners.BaseMiner.restart_backend
|
::: pyasic.miners.base.MinerProtocol.restart_backend
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Stop Mining
|
### Stop Mining
|
||||||
::: pyasic.miners.BaseMiner.stop_mining
|
::: pyasic.miners.base.MinerProtocol.stop_mining
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Resume Mining
|
### Resume Mining
|
||||||
::: pyasic.miners.BaseMiner.resume_mining
|
::: pyasic.miners.base.MinerProtocol.resume_mining
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Is Mining
|
### Is Mining
|
||||||
::: pyasic.miners.BaseMiner.is_mining
|
::: pyasic.miners.base.MinerProtocol.is_mining
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Send Config
|
### Send Config
|
||||||
::: pyasic.miners.BaseMiner.send_config
|
::: pyasic.miners.base.MinerProtocol.send_config
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
### Set Power Limit
|
### Set Power Limit
|
||||||
::: pyasic.miners.BaseMiner.set_power_limit
|
::: pyasic.miners.base.MinerProtocol.set_power_limit
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|||||||
@@ -2,23 +2,22 @@
|
|||||||
## X5 Models
|
## X5 Models
|
||||||
|
|
||||||
## CK5
|
## CK5
|
||||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5
|
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## HS5
|
## HS5
|
||||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5
|
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
## KD5
|
## KD5
|
||||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5
|
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,8 @@
|
|||||||
## XMax Models
|
## XMax Models
|
||||||
|
|
||||||
## KD Max
|
## KD Max
|
||||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
|
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.KDMax
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## Miner Factory
|
## Miner Factory
|
||||||
|
|
||||||
[`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
[`MinerFactory`][pyasic.MinerFactory] is the way to create miner types in `pyasic`. The most important method is [`get_miner()`][pyasic.get_miner], which is mapped to [`pyasic.get_miner()`][pyasic.get_miner], and should be used from there.
|
||||||
|
|
||||||
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_factory`.
|
||||||
|
|
||||||
@@ -9,7 +9,7 @@ The instance used for [`pyasic.get_miner()`][pyasic.get_miner] is `pyasic.miner_
|
|||||||
|
|
||||||
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
Finally, there is functionality to get multiple miners without using `asyncio.gather()` explicitly. Use `pyasic.miner_factory.get_multiple_miners()` with a list of IPs as strings to get a list of miner instances. You can also get multiple miners with an `AsyncGenerator` by using `pyasic.miner_factory.get_miner_generator()`.
|
||||||
|
|
||||||
::: pyasic.miners.miner_factory.MinerFactory
|
::: pyasic.miners.factory.MinerFactory
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -25,12 +25,12 @@ Finally, there is functionality to get multiple miners without using `asyncio.ga
|
|||||||
<br>
|
<br>
|
||||||
|
|
||||||
## AnyMiner
|
## AnyMiner
|
||||||
::: pyasic.miners.miner_factory.AnyMiner
|
::: pyasic.miners.base.AnyMiner
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
heading_level: 4
|
heading_level: 4
|
||||||
|
|
||||||
[`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
[`AnyMiner`][pyasic.miners.base.AnyMiner] is a placeholder type variable used for typing returns of functions.
|
||||||
A function returning [`AnyMiner`][pyasic.miners.miner_factory.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
A function returning [`AnyMiner`][pyasic.miners.base.AnyMiner] will always return a subclass of [`BaseMiner`][pyasic.miners.BaseMiner],
|
||||||
and is used to specify a function returning some arbitrary type of miner class instance.
|
and is used to specify a function returning some arbitrary type of miner class instance.
|
||||||
|
|||||||
27
docs/rpc/api.md
Normal file
27
docs/rpc/api.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# pyasic
|
||||||
|
## Miner APIs
|
||||||
|
Each miner has a unique API that is used to communicate with it.
|
||||||
|
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||||
|
Each miner that is a subclass of [`BaseMiner`][pyasic.miners.BaseMiner] should have an API linked to it as `Miner.api`.
|
||||||
|
|
||||||
|
All API implementations inherit from [`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||||
|
|
||||||
|
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||||
|
[`BaseMinerRPCAPI`][pyasic.rpc.base.BaseMinerRPCAPI] cannot be instantiated directly, it will raise a `TypeError`.
|
||||||
|
Use these instead -
|
||||||
|
|
||||||
|
#### [BFGMiner API][pyasic.rpc.bfgminer.BFGMinerRPCAPI]
|
||||||
|
#### [BMMiner API][pyasic.rpc.bmminer.BMMinerRPCAPI]
|
||||||
|
#### [BOSMiner API][pyasic.rpc.bosminer.BOSMinerRPCAPI]
|
||||||
|
#### [BTMiner API][pyasic.rpc.btminer.BTMinerRPCAPI]
|
||||||
|
#### [CGMiner API][pyasic.rpc.cgminer.CGMinerRPCAPI]
|
||||||
|
#### [LUXMiner API][pyasic.rpc.luxminer.LUXMinerRPCAPI]
|
||||||
|
#### [Unknown API][pyasic.rpc.unknown.UnknownRPCAPI]
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
## BaseMinerRPCAPI
|
||||||
|
::: pyasic.rpc.base.BaseMinerRPCAPI
|
||||||
|
handler: python
|
||||||
|
options:
|
||||||
|
heading_level: 4
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BFGMinerAPI
|
## BFGMinerRPCAPI
|
||||||
::: pyasic.API.bfgminer.BFGMinerAPI
|
::: pyasic.rpc.bfgminer.BFGMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BMMinerAPI
|
## BMMinerRPCAPI
|
||||||
::: pyasic.API.bmminer.BMMinerAPI
|
::: pyasic.rpc.bmminer.BMMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BOSMinerAPI
|
## BOSMinerRPCAPI
|
||||||
::: pyasic.API.bosminer.BOSMinerAPI
|
::: pyasic.rpc.bosminer.BOSMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## BTMinerAPI
|
## BTMinerRPCAPI
|
||||||
::: pyasic.API.btminer.BTMinerAPI
|
::: pyasic.rpc.btminer.BTMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## CGMinerAPI
|
## CGMinerRPCAPI
|
||||||
::: pyasic.API.cgminer.CGMinerAPI
|
::: pyasic.rpc.cgminer.CGMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## LUXMinerAPI
|
## LUXMinerRPCAPI
|
||||||
::: pyasic.API.luxminer.LUXMinerAPI
|
::: pyasic.rpc.luxminer.LUXMinerRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# pyasic
|
# pyasic
|
||||||
## UnknownAPI
|
## UnknownRPCAPI
|
||||||
::: pyasic.API.unknown.UnknownAPI
|
::: pyasic.rpc.unknown.UnknownRPCAPI
|
||||||
handler: python
|
handler: python
|
||||||
options:
|
options:
|
||||||
show_root_heading: false
|
show_root_heading: false
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
|
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
|
||||||
|
|
||||||
Settings options:
|
Settings options:
|
||||||
|
|
||||||
- `network_ping_retries`
|
- `network_ping_retries`
|
||||||
- `network_ping_timeout`
|
- `network_ping_timeout`
|
||||||
- `network_scan_threads`
|
- `network_scan_threads`
|
||||||
|
|||||||
18
mkdocs.yml
18
mkdocs.yml
@@ -13,15 +13,15 @@ nav:
|
|||||||
- Error Codes: "data/error_codes.md"
|
- Error Codes: "data/error_codes.md"
|
||||||
- Miner Config: "config/miner_config.md"
|
- Miner Config: "config/miner_config.md"
|
||||||
- Advanced:
|
- Advanced:
|
||||||
- Miner APIs:
|
- RPC APIs:
|
||||||
- Intro: "API/api.md"
|
- Intro: "rpc/api.md"
|
||||||
- BFGMiner: "API/bfgminer.md"
|
- BFGMiner: "rpc/bfgminer.md"
|
||||||
- BMMiner: "API/bmminer.md"
|
- BMMiner: "rpc/bmminer.md"
|
||||||
- BOSMiner: "API/bosminer.md"
|
- BOSMiner: "rpc/bosminer.md"
|
||||||
- BTMiner: "API/btminer.md"
|
- BTMiner: "rpc/btminer.md"
|
||||||
- CGMiner: "API/cgminer.md"
|
- CGMiner: "rpc/cgminer.md"
|
||||||
- LUXMiner: "API/luxminer.md"
|
- LUXMiner: "rpc/luxminer.md"
|
||||||
- Unknown: "API/unknown.md"
|
- Unknown: "rpc/unknown.md"
|
||||||
- Backends:
|
- Backends:
|
||||||
- BMMiner: "miners/backends/bmminer.md"
|
- BMMiner: "miners/backends/bmminer.md"
|
||||||
- BOSMiner: "miners/backends/bosminer.md"
|
- BOSMiner: "miners/backends/bosminer.md"
|
||||||
|
|||||||
@@ -1,674 +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 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:
|
|
||||||
tasks = []
|
|
||||||
# send all commands individually
|
|
||||||
for cmd in commands:
|
|
||||||
tasks.append(
|
|
||||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
|
||||||
)
|
|
||||||
|
|
||||||
all_data = await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
for item in all_data:
|
|
||||||
data.update(item)
|
|
||||||
|
|
||||||
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}")
|
|
||||||
@@ -1,731 +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 logging
|
|
||||||
|
|
||||||
from pyasic.API import APIError, BaseMinerAPI
|
|
||||||
|
|
||||||
|
|
||||||
class BMMinerAPI(BaseMinerAPI):
|
|
||||||
"""An abstraction of the BMMiner API.
|
|
||||||
|
|
||||||
Each method corresponds to an API command in BMMiner.
|
|
||||||
|
|
||||||
[BMMiner API documentation](https://github.com/jameshilliard/bmminer/blob/master/API-README)
|
|
||||||
|
|
||||||
This class abstracts use of the BMMiner 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) -> None:
|
|
||||||
super().__init__(ip, port=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("+"), allow_warning=allow_warning
|
|
||||||
)
|
|
||||||
data["multicommand"] = True
|
|
||||||
return data
|
|
||||||
|
|
||||||
async def _x19_multicommand(self, *commands, allow_warning: bool = True) -> dict:
|
|
||||||
tasks = []
|
|
||||||
# send all commands individually
|
|
||||||
for cmd in commands:
|
|
||||||
tasks.append(
|
|
||||||
asyncio.create_task(self._handle_multicommand(cmd, allow_warning=True))
|
|
||||||
)
|
|
||||||
|
|
||||||
all_data = await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
for item in all_data:
|
|
||||||
data.update(item)
|
|
||||||
|
|
||||||
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 edevs(self, old: bool = False) -> dict:
|
|
||||||
"""Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
old: Include zombie devices that became zombies less than 'old' seconds ago
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Data on each PGA/ASC with their details.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
if old:
|
|
||||||
return await self.send_command("edevs", parameters=old)
|
|
||||||
else:
|
|
||||||
return await self.send_command("edevs")
|
|
||||||
|
|
||||||
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 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 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 BMMiner.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
A single "BYE" before BMMiner 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 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 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 BMMiner 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 estats(self, old: bool = False) -> dict:
|
|
||||||
"""Get stats of each device/pool with more than 1 getwork, ignoring zombie devices.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
old: Include zombie devices that became zombies less than 'old' seconds ago.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Stats of each device/pool with more than 1 getwork, ignoring zombie devices.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
if old:
|
|
||||||
return await self.send_command("estats", parameters=old)
|
|
||||||
else:
|
|
||||||
return await self.send_command("estats")
|
|
||||||
|
|
||||||
async def check(self, command: str) -> dict:
|
|
||||||
"""Check if the command command exists in BMMiner.
|
|
||||||
<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 usbstats(self) -> dict:
|
|
||||||
"""Get stats of all USB devices except ztex.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The stats of all USB devices except ztex.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("usbstats")
|
|
||||||
|
|
||||||
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: 160 - 230 (multiple of 2)
|
|
||||||
CMR -
|
|
||||||
opt: clock
|
|
||||||
val: 100 - 220
|
|
||||||
```
|
|
||||||
|
|
||||||
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}")
|
|
||||||
|
|
||||||
async def hotplug(self, n: int) -> dict:
|
|
||||||
"""Enable hotplug.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The device number to set hotplug on.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Information on hotplug status.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("hotplug", parameters=n)
|
|
||||||
|
|
||||||
async def asc(self, n: int) -> dict:
|
|
||||||
"""Get data for ASC device n.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The device to get data for.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The data for ASC device n.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("asc", parameters=n)
|
|
||||||
|
|
||||||
async def ascenable(self, n: int) -> dict:
|
|
||||||
"""Enable ASC device n.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The device to enable.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Confirmation of enabling ASC device n.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("ascenable", parameters=n)
|
|
||||||
|
|
||||||
async def ascdisable(self, n: int) -> dict:
|
|
||||||
"""Disable ASC device n.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The device to disable.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Confirmation of disabling ASC device n.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("ascdisable", parameters=n)
|
|
||||||
|
|
||||||
async def ascidentify(self, n: int) -> dict:
|
|
||||||
"""Identify ASC device n.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The device to identify.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Confirmation of identifying ASC device n.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("ascidentify", parameters=n)
|
|
||||||
|
|
||||||
async def asccount(self) -> dict:
|
|
||||||
"""Get data on the number of ASC devices and their info.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
Data on all ASC devices.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("asccount")
|
|
||||||
|
|
||||||
async def ascset(self, n: int, opt: str, val: int = None) -> dict:
|
|
||||||
"""Set ASC n option opt to value val.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Sets an option on the ASC n to a value. Allowed options are:
|
|
||||||
```
|
|
||||||
AVA+BTB -
|
|
||||||
opt: freq
|
|
||||||
val: 256 - 1024 (chip frequency)
|
|
||||||
BTB -
|
|
||||||
opt: millivolts
|
|
||||||
val: 1000 - 1400 (core voltage)
|
|
||||||
MBA -
|
|
||||||
opt: reset
|
|
||||||
val: 0 - # of chips (reset a chip)
|
|
||||||
|
|
||||||
opt: freq
|
|
||||||
val: 0 - # of chips, 100 - 1400 (chip frequency)
|
|
||||||
|
|
||||||
opt: ledcount
|
|
||||||
val: 0 - 100 (chip count for LED)
|
|
||||||
|
|
||||||
opt: ledlimit
|
|
||||||
val: 0 - 200 (LED off below GH/s)
|
|
||||||
|
|
||||||
opt: spidelay
|
|
||||||
val: 0 - 9999 (SPI per I/O delay)
|
|
||||||
|
|
||||||
opt: spireset
|
|
||||||
val: i or s, 0 - 9999 (SPI regular reset)
|
|
||||||
|
|
||||||
opt: spisleep
|
|
||||||
val: 0 - 9999 (SPI reset sleep in ms)
|
|
||||||
BMA -
|
|
||||||
opt: volt
|
|
||||||
val: 0 - 9
|
|
||||||
|
|
||||||
opt: clock
|
|
||||||
val: 0 - 15
|
|
||||||
```
|
|
||||||
|
|
||||||
Parameters:
|
|
||||||
n: The ASC 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 option opt to value val.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
if val:
|
|
||||||
return await self.send_command("ascset", parameters=f"{n},{opt},{val}")
|
|
||||||
else:
|
|
||||||
return await self.send_command("ascset", parameters=f"{n},{opt}")
|
|
||||||
|
|
||||||
async def lcd(self) -> dict:
|
|
||||||
"""Get a general all-in-one status summary of the miner.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An all-in-one status summary of the miner.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("lcd")
|
|
||||||
|
|
||||||
async def lockstats(self) -> dict:
|
|
||||||
"""Write lockstats to STDERR.
|
|
||||||
<details>
|
|
||||||
<summary>Expand</summary>
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
The result of writing the lock stats to STDERR.
|
|
||||||
</details>
|
|
||||||
"""
|
|
||||||
return await self.send_command("lockstats")
|
|
||||||
@@ -14,46 +14,11 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic import settings
|
from pyasic import settings
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
|
||||||
from pyasic.API.bosminer import BOSMinerAPI
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
|
||||||
from pyasic.API.unknown import UnknownAPI
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import (
|
from pyasic.data import MinerData
|
||||||
BraiinsOSError,
|
|
||||||
InnosiliconError,
|
|
||||||
MinerData,
|
|
||||||
WhatsminerError,
|
|
||||||
X19Error,
|
|
||||||
)
|
|
||||||
from pyasic.errors import APIError, APIWarning
|
from pyasic.errors import APIError, APIWarning
|
||||||
from pyasic.miners import get_miner
|
from pyasic.miners import *
|
||||||
from pyasic.miners.base import AnyMiner, DataOptions
|
|
||||||
from pyasic.miners.miner_factory import MinerFactory, miner_factory
|
|
||||||
from pyasic.miners.miner_listener import MinerListener
|
|
||||||
from pyasic.network import MinerNetwork
|
from pyasic.network import MinerNetwork
|
||||||
|
from pyasic.rpc import *
|
||||||
__all__ = [
|
from pyasic.ssh import *
|
||||||
"BMMinerAPI",
|
from pyasic.web import *
|
||||||
"BOSMinerAPI",
|
|
||||||
"BTMinerAPI",
|
|
||||||
"CGMinerAPI",
|
|
||||||
"UnknownAPI",
|
|
||||||
"MinerConfig",
|
|
||||||
"MinerData",
|
|
||||||
"BraiinsOSError",
|
|
||||||
"InnosiliconError",
|
|
||||||
"WhatsminerError",
|
|
||||||
"X19Error",
|
|
||||||
"APIError",
|
|
||||||
"APIWarning",
|
|
||||||
"get_miner",
|
|
||||||
"AnyMiner",
|
|
||||||
"DataOptions",
|
|
||||||
"MinerFactory",
|
|
||||||
"miner_factory",
|
|
||||||
"MinerListener",
|
|
||||||
"MinerNetwork",
|
|
||||||
"settings",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from copy import deepcopy
|
|
||||||
from dataclasses import asdict, dataclass, field
|
from dataclasses import asdict, dataclass, field
|
||||||
|
|
||||||
from pyasic.config.fans import FanModeConfig
|
from pyasic.config.fans import FanModeConfig
|
||||||
from pyasic.config.mining import MiningModeConfig
|
from pyasic.config.mining import MiningModeConfig
|
||||||
from pyasic.config.pools import PoolConfig
|
from pyasic.config.pools import PoolConfig
|
||||||
from pyasic.config.power_scaling import PowerScalingConfig, PowerScalingShutdown
|
from pyasic.config.power_scaling import PowerScalingConfig
|
||||||
from pyasic.config.temperature import TemperatureConfig
|
from pyasic.config.temperature import TemperatureConfig
|
||||||
|
from pyasic.misc import merge_dicts
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -93,7 +93,7 @@ class MinerConfig:
|
|||||||
|
|
||||||
def as_bosminer(self, user_suffix: str = None) -> dict:
|
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||||
return {
|
return {
|
||||||
**merge(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
**merge_dicts(self.fan_mode.as_bosminer(), self.temperature.as_bosminer()),
|
||||||
**self.mining_mode.as_bosminer(),
|
**self.mining_mode.as_bosminer(),
|
||||||
**self.pools.as_bosminer(user_suffix=user_suffix),
|
**self.pools.as_bosminer(user_suffix=user_suffix),
|
||||||
**self.power_scaling.as_bosminer(),
|
**self.power_scaling.as_bosminer(),
|
||||||
@@ -110,13 +110,21 @@ class MinerConfig:
|
|||||||
|
|
||||||
def as_epic(self, user_suffix: str = None) -> dict:
|
def as_epic(self, user_suffix: str = None) -> dict:
|
||||||
return {
|
return {
|
||||||
**self.fan_mode.as_epic(),
|
**merge_dicts(self.fan_mode.as_epic(), self.temperature.as_epic()),
|
||||||
**self.temperature.as_epic(),
|
|
||||||
**self.mining_mode.as_epic(),
|
**self.mining_mode.as_epic(),
|
||||||
**self.pools.as_epic(user_suffix=user_suffix),
|
**self.pools.as_epic(user_suffix=user_suffix),
|
||||||
**self.power_scaling.as_epic(),
|
**self.power_scaling.as_epic(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||||
|
return {
|
||||||
|
**self.fan_mode.as_auradine(),
|
||||||
|
**self.temperature.as_auradine(),
|
||||||
|
**self.mining_mode.as_auradine(),
|
||||||
|
**self.pools.as_auradine(user_suffix=user_suffix),
|
||||||
|
**self.power_scaling.as_auradine(),
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||||
return cls(
|
return cls(
|
||||||
@@ -189,13 +197,10 @@ class MinerConfig:
|
|||||||
mining_mode=MiningModeConfig.from_vnish(web_settings),
|
mining_mode=MiningModeConfig.from_vnish(web_settings),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
def merge(a: dict, b: dict) -> dict:
|
def from_auradine(cls, web_conf: dict) -> "MinerConfig":
|
||||||
result = deepcopy(a)
|
return cls(
|
||||||
for b_key, b_val in b.items():
|
pools=PoolConfig.from_api(web_conf["pools"]),
|
||||||
a_val = result.get(b_key)
|
fan_mode=FanModeConfig.from_auradine(web_conf["fan"]),
|
||||||
if isinstance(a_val, dict) and isinstance(b_val, dict):
|
mining_mode=MiningModeConfig.from_auradine(web_conf["mode"]),
|
||||||
result[b_key] = merge(a_val, b_val)
|
)
|
||||||
else:
|
|
||||||
result[b_key] = deepcopy(b_val)
|
|
||||||
return result
|
|
||||||
|
|||||||
@@ -13,14 +13,15 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import asdict, dataclass
|
from dataclasses import asdict, dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
|
|
||||||
class MinerConfigOption(Enum):
|
class MinerConfigOption(Enum):
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -53,6 +54,9 @@ class MinerConfigOption(Enum):
|
|||||||
def as_vnish(self) -> dict:
|
def as_vnish(self) -> dict:
|
||||||
return self.value.as_vnish()
|
return self.value.as_vnish()
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return self.value.as_auradine()
|
||||||
|
|
||||||
def __call__(self, *args, **kwargs):
|
def __call__(self, *args, **kwargs):
|
||||||
return self.value(*args, **kwargs)
|
return self.value(*args, **kwargs)
|
||||||
|
|
||||||
@@ -64,10 +68,10 @@ class MinerConfigOption(Enum):
|
|||||||
@dataclass
|
@dataclass
|
||||||
class MinerConfigValue:
|
class MinerConfigValue:
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_dict(self):
|
def as_dict(self) -> dict:
|
||||||
return asdict(self)
|
return asdict(self)
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -99,3 +103,6 @@ class MinerConfigValue:
|
|||||||
|
|
||||||
def as_vnish(self) -> dict:
|
def as_vnish(self) -> dict:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {}
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ class FanModeNormal(MinerConfigValue):
|
|||||||
minimum_speed: int = 0
|
minimum_speed: int = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeNormal":
|
def from_dict(cls, dict_conf: dict | None) -> "FanModeNormal":
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("minimum_fans") is not None:
|
if dict_conf.get("minimum_fans") is not None:
|
||||||
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
cls_conf["minimum_fans"] = dict_conf["minimum_fans"]
|
||||||
@@ -35,7 +36,7 @@ class FanModeNormal(MinerConfigValue):
|
|||||||
return cls(**cls_conf)
|
return cls(**cls_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_cooling_settings: dict):
|
def from_vnish(cls, web_cooling_settings: dict) -> "FanModeNormal":
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if web_cooling_settings.get("fan_min_count") is not None:
|
if web_cooling_settings.get("fan_min_count") is not None:
|
||||||
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
cls_conf["minimum_fans"] = web_cooling_settings["fan_min_count"]
|
||||||
@@ -49,6 +50,17 @@ class FanModeNormal(MinerConfigValue):
|
|||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
return {"temp_control": {"mode": "auto"}}
|
return {"temp_control": {"mode": "auto"}}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {
|
||||||
|
"fans": {
|
||||||
|
"Auto": {
|
||||||
|
"Idle Speed": self.minimum_speed
|
||||||
|
if not self.minimum_speed == 0
|
||||||
|
else 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FanModeManual(MinerConfigValue):
|
class FanModeManual(MinerConfigValue):
|
||||||
@@ -57,7 +69,7 @@ class FanModeManual(MinerConfigValue):
|
|||||||
minimum_fans: int = 1
|
minimum_fans: int = 1
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeManual":
|
def from_dict(cls, dict_conf: dict | None) -> "FanModeManual":
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
if dict_conf.get("speed") is not None:
|
if dict_conf.get("speed") is not None:
|
||||||
cls_conf["speed"] = dict_conf["speed"]
|
cls_conf["speed"] = dict_conf["speed"]
|
||||||
@@ -92,13 +104,19 @@ class FanModeManual(MinerConfigValue):
|
|||||||
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
"fan_control": {"min_fans": self.minimum_fans, "speed": self.speed},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"fan": {"percentage": self.speed}}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {"fans": {"Manual": {"speed": self.speed}}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FanModeImmersion(MinerConfigValue):
|
class FanModeImmersion(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="immersion")
|
mode: str = field(init=False, default="immersion")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "FanModeImmersion":
|
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -107,6 +125,9 @@ class FanModeImmersion(MinerConfigValue):
|
|||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
return {"temp_control": {"mode": "disabled"}}
|
return {"temp_control": {"mode": "disabled"}}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"fan": {"percentage": 0}}
|
||||||
|
|
||||||
|
|
||||||
class FanModeConfig(MinerConfigOption):
|
class FanModeConfig(MinerConfigOption):
|
||||||
normal = FanModeNormal
|
normal = FanModeNormal
|
||||||
@@ -118,7 +139,7 @@ class FanModeConfig(MinerConfigOption):
|
|||||||
return cls.normal()
|
return cls.normal()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@@ -126,9 +147,9 @@ class FanModeConfig(MinerConfigOption):
|
|||||||
if mode is None:
|
if mode is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
clsattr = getattr(cls, mode)
|
cls_attr = getattr(cls, mode)
|
||||||
if clsattr is not None:
|
if cls_attr is not None:
|
||||||
return clsattr().from_dict(dict_conf)
|
return cls_attr().from_dict(dict_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_conf: dict):
|
def from_am_modern(cls, web_conf: dict):
|
||||||
@@ -202,3 +223,13 @@ class FanModeConfig(MinerConfigOption):
|
|||||||
if "minimumRequiredFans" in keys:
|
if "minimumRequiredFans" in keys:
|
||||||
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
|
conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"])
|
||||||
return cls.manual(**conf)
|
return cls.manual(**conf)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_auradine(cls, web_fan: dict):
|
||||||
|
try:
|
||||||
|
fan_data = web_fan["Fan"][0]
|
||||||
|
fan_1_max = fan_data["Max"]
|
||||||
|
fan_1_target = fan_data["Target"]
|
||||||
|
return cls.manual(speed=round((fan_1_target / fan_1_max) * 100))
|
||||||
|
except LookupError:
|
||||||
|
return cls.default()
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Dict, Union
|
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||||
@@ -34,7 +35,7 @@ class MiningModeNormal(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="normal")
|
mode: str = field(init=False, default="normal")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeNormal":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeNormal":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -43,13 +44,22 @@ class MiningModeNormal(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"mode": self.mode}}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {"ptune": {"enabled": False}}
|
||||||
|
|
||||||
|
def as_goldshell(self) -> dict:
|
||||||
|
return {"settings": {"level": 0}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiningModeSleep(MinerConfigValue):
|
class MiningModeSleep(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="sleep")
|
mode: str = field(init=False, default="sleep")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeSleep":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeSleep":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -58,13 +68,22 @@ class MiningModeSleep(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"sleep": "on"}}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {"ptune": {"algo": "Sleep", "target": 0}}
|
||||||
|
|
||||||
|
def as_goldshell(self) -> dict:
|
||||||
|
return {"settings": {"level": 3}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiningModeLPM(MinerConfigValue):
|
class MiningModeLPM(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="low")
|
mode: str = field(init=False, default="low")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeLPM":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeLPM":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -73,30 +92,77 @@ class MiningModeLPM(MinerConfigValue):
|
|||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"mode": "eco"}}
|
||||||
|
|
||||||
|
def as_goldshell(self) -> dict:
|
||||||
|
return {"settings": {"level": 1}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiningModeHPM(MinerConfigValue):
|
class MiningModeHPM(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="high")
|
mode: str = field(init=False, default="high")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHPM":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHPM":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_am_modern(self):
|
def as_am_modern(self) -> dict:
|
||||||
return {"miner-mode": "0"}
|
return {"miner-mode": "0"}
|
||||||
|
|
||||||
def as_wm(self) -> dict:
|
def as_wm(self) -> dict:
|
||||||
return {"mode": self.mode}
|
return {"mode": self.mode}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"mode": "turbo"}}
|
||||||
|
|
||||||
|
|
||||||
|
class StandardTuneAlgo(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="standard")
|
||||||
|
|
||||||
|
def as_epic(self) -> str:
|
||||||
|
return VOptAlgo().as_epic()
|
||||||
|
|
||||||
|
|
||||||
|
class VOptAlgo(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="standard")
|
||||||
|
|
||||||
|
def as_epic(self) -> str:
|
||||||
|
return "VoltageOptimizer"
|
||||||
|
|
||||||
|
|
||||||
|
class ChipTuneAlgo(MinerConfigValue):
|
||||||
|
mode: str = field(init=False, default="standard")
|
||||||
|
|
||||||
|
def as_epic(self) -> str:
|
||||||
|
return "ChipTune"
|
||||||
|
|
||||||
|
|
||||||
|
class TunerAlgo(MinerConfigOption):
|
||||||
|
standard = StandardTuneAlgo
|
||||||
|
voltage_optimizer = VOptAlgo
|
||||||
|
chip_tune = ChipTuneAlgo
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def default(cls):
|
||||||
|
return cls.standard()
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiningModePowerTune(MinerConfigValue):
|
class MiningModePowerTune(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="power_tuning")
|
mode: str = field(init=False, default="power_tuning")
|
||||||
power: int = None
|
power: int = None
|
||||||
|
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModePowerTune":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
|
||||||
return cls(dict_conf.get("power"))
|
cls_conf = {}
|
||||||
|
if dict_conf.get("power"):
|
||||||
|
cls_conf["power"] = dict_conf["power"]
|
||||||
|
if dict_conf.get("algo"):
|
||||||
|
cls_conf["algo"] = dict_conf["algo"]
|
||||||
|
|
||||||
|
return cls(**cls_conf)
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
return {"miner-mode": "0"}
|
return {"miner-mode": "0"}
|
||||||
@@ -123,14 +189,18 @@ class MiningModePowerTune(MinerConfigValue):
|
|||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"mode": "custom", "tune": "power", "power": self.power}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class MiningModeHashrateTune(MinerConfigValue):
|
class MiningModeHashrateTune(MinerConfigValue):
|
||||||
mode: str = field(init=False, default="hashrate_tuning")
|
mode: str = field(init=False, default="hashrate_tuning")
|
||||||
hashrate: int = None
|
hashrate: int = None
|
||||||
|
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeHashrateTune":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
|
||||||
return cls(dict_conf.get("hashrate"))
|
return cls(dict_conf.get("hashrate"))
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -152,6 +222,12 @@ class MiningModeHashrateTune(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def as_auradine(self) -> dict:
|
||||||
|
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ManualBoardSettings(MinerConfigValue):
|
class ManualBoardSettings(MinerConfigValue):
|
||||||
@@ -159,7 +235,7 @@ class ManualBoardSettings(MinerConfigValue):
|
|||||||
volt: float
|
volt: float
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "ManualBoardSettings":
|
def from_dict(cls, dict_conf: dict | None) -> "ManualBoardSettings":
|
||||||
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
return cls(freq=dict_conf["freq"], volt=dict_conf["volt"])
|
||||||
|
|
||||||
def as_am_modern(self) -> dict:
|
def as_am_modern(self) -> dict:
|
||||||
@@ -172,10 +248,10 @@ class MiningModeManual(MinerConfigValue):
|
|||||||
|
|
||||||
global_freq: float
|
global_freq: float
|
||||||
global_volt: float
|
global_volt: float
|
||||||
boards: Dict[int, ManualBoardSettings] = field(default_factory=dict)
|
boards: dict[int, ManualBoardSettings] = field(default_factory=dict)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "MiningModeManual":
|
def from_dict(cls, dict_conf: dict | None) -> "MiningModeManual":
|
||||||
return cls(
|
return cls(
|
||||||
global_freq=dict_conf["global_freq"],
|
global_freq=dict_conf["global_freq"],
|
||||||
global_volt=dict_conf["global_volt"],
|
global_volt=dict_conf["global_volt"],
|
||||||
@@ -214,7 +290,7 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.normal()
|
return cls.normal()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@@ -222,9 +298,9 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
if mode is None:
|
if mode is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
clsattr = getattr(cls, mode)
|
cls_attr = getattr(cls, mode)
|
||||||
if clsattr is not None:
|
if cls_attr is not None:
|
||||||
return clsattr().from_dict(dict_conf)
|
return cls_attr().from_dict(dict_conf)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_am_modern(cls, web_conf: dict):
|
def from_am_modern(cls, web_conf: dict):
|
||||||
@@ -243,20 +319,18 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, web_conf: dict):
|
def from_epic(cls, web_conf: dict):
|
||||||
try:
|
try:
|
||||||
work_mode = web_conf["PerpetualTune"]["Running"]
|
tuner_running = web_conf["PerpetualTune"]["Running"]
|
||||||
if work_mode:
|
if tuner_running:
|
||||||
if (
|
algo_info = web_conf["PerpetualTune"]["Algorithm"]
|
||||||
web_conf["PerpetualTune"]["Algorithm"].get("VoltageOptimizer")
|
if algo_info.get("VoltageOptimizer") is not None:
|
||||||
is not None
|
|
||||||
):
|
|
||||||
return cls.hashrate_tuning(
|
return cls.hashrate_tuning(
|
||||||
web_conf["PerpetualTune"]["Algorithm"]["VoltageOptimizer"][
|
hashrate=algo_info["VoltageOptimizer"]["Target"],
|
||||||
"Target"
|
algo=TunerAlgo.voltage_optimizer,
|
||||||
]
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return cls.hashrate_tuning(
|
return cls.hashrate_tuning(
|
||||||
web_conf["PerpetualTune"]["Algorithm"]["ChipTune"]["Target"]
|
hashrate=algo_info["ChipTune"]["Target"],
|
||||||
|
algo=TunerAlgo.chip_tune,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
return cls.normal()
|
return cls.normal()
|
||||||
@@ -330,3 +404,22 @@ class MiningModeConfig(MinerConfigOption):
|
|||||||
return cls.hashrate_tuning(
|
return cls.hashrate_tuning(
|
||||||
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
|
int(tuner_conf["hashrateTarget"]["terahashPerSecond"])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_auradine(cls, web_mode: dict):
|
||||||
|
try:
|
||||||
|
mode_data = web_mode["Mode"][0]
|
||||||
|
if mode_data.get("Sleep") == "on":
|
||||||
|
return cls.sleep()
|
||||||
|
if mode_data.get("Mode") == "normal":
|
||||||
|
return cls.normal()
|
||||||
|
if mode_data.get("Mode") == "eco":
|
||||||
|
return cls.low()
|
||||||
|
if mode_data.get("Mode") == "turbo":
|
||||||
|
return cls.high()
|
||||||
|
if mode_data.get("Ths") is not None:
|
||||||
|
return cls.hashrate_tuning(mode_data["Ths"])
|
||||||
|
if mode_data.get("Power") is not None:
|
||||||
|
return cls.power_tuning(mode_data["Power"])
|
||||||
|
except LookupError:
|
||||||
|
return cls.default()
|
||||||
|
|||||||
@@ -13,10 +13,12 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Dict, List, Union
|
from typing import List
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigValue
|
from pyasic.config.base import MinerConfigValue
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ class Pool(MinerConfigValue):
|
|||||||
user: str
|
user: str
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
def as_am_modern(self, user_suffix: str = None):
|
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
"url": self.url,
|
"url": self.url,
|
||||||
@@ -36,7 +38,7 @@ class Pool(MinerConfigValue):
|
|||||||
}
|
}
|
||||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||||
|
|
||||||
def as_wm(self, idx: int, user_suffix: str = None):
|
def as_wm(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
f"pool_{idx}": self.url,
|
f"pool_{idx}": self.url,
|
||||||
@@ -49,7 +51,7 @@ class Pool(MinerConfigValue):
|
|||||||
f"passwd_{idx}": self.password,
|
f"passwd_{idx}": self.password,
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_am_old(self, idx: int, user_suffix: str = None):
|
def as_am_old(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
f"_ant_pool{idx}url": self.url,
|
f"_ant_pool{idx}url": self.url,
|
||||||
@@ -62,7 +64,7 @@ class Pool(MinerConfigValue):
|
|||||||
f"_ant_pool{idx}pw": self.password,
|
f"_ant_pool{idx}pw": self.password,
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_goldshell(self, user_suffix: str = None):
|
def as_goldshell(self, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
"url": self.url,
|
"url": self.url,
|
||||||
@@ -71,12 +73,12 @@ class Pool(MinerConfigValue):
|
|||||||
}
|
}
|
||||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None):
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
|
return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
|
||||||
return ",".join([self.url, self.user, self.password])
|
return ",".join([self.url, self.user, self.password])
|
||||||
|
|
||||||
def as_inno(self, idx: int, user_suffix: str = None):
|
def as_inno(self, idx: int = 1, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
f"Pool{idx}": self.url,
|
f"Pool{idx}": self.url,
|
||||||
@@ -89,7 +91,7 @@ class Pool(MinerConfigValue):
|
|||||||
f"Password{idx}": self.password,
|
f"Password{idx}": self.password,
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_bosminer(self, user_suffix: str = None):
|
def as_bosminer(self, user_suffix: str = None) -> dict:
|
||||||
if user_suffix is not None:
|
if user_suffix is not None:
|
||||||
return {
|
return {
|
||||||
"url": self.url,
|
"url": self.url,
|
||||||
@@ -98,8 +100,26 @@ class Pool(MinerConfigValue):
|
|||||||
}
|
}
|
||||||
return {"url": self.url, "user": self.user, "password": self.password}
|
return {"url": self.url, "user": self.user, "password": self.password}
|
||||||
|
|
||||||
|
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||||
|
if user_suffix is not None:
|
||||||
|
return {
|
||||||
|
"url": self.url,
|
||||||
|
"user": f"{self.user}{user_suffix}",
|
||||||
|
"pass": self.password,
|
||||||
|
}
|
||||||
|
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||||
|
|
||||||
|
def as_epic(self, user_suffix: str = None):
|
||||||
|
if user_suffix is not None:
|
||||||
|
return {
|
||||||
|
"pool": self.url,
|
||||||
|
"login": f"{self.user}{user_suffix}",
|
||||||
|
"password": self.password,
|
||||||
|
}
|
||||||
|
return {"pool": self.url, "login": self.user, "password": self.password}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool":
|
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
||||||
return cls(
|
return cls(
|
||||||
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
url=dict_conf["url"], user=dict_conf["user"], password=dict_conf["password"]
|
||||||
)
|
)
|
||||||
@@ -160,7 +180,7 @@ class Pool(MinerConfigValue):
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PoolGroup(MinerConfigValue):
|
class PoolGroup(MinerConfigValue):
|
||||||
pools: List[Pool] = field(default_factory=list)
|
pools: list[Pool] = field(default_factory=list)
|
||||||
quota: int = 1
|
quota: int = 1
|
||||||
name: str = None
|
name: str = None
|
||||||
|
|
||||||
@@ -210,7 +230,7 @@ class PoolGroup(MinerConfigValue):
|
|||||||
def as_goldshell(self, user_suffix: str = None) -> list:
|
def as_goldshell(self, user_suffix: str = None) -> list:
|
||||||
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
return [pool.as_goldshell(user_suffix) for pool in self.pools]
|
||||||
|
|
||||||
def as_avalon(self, user_suffix: str = None) -> dict:
|
def as_avalon(self, user_suffix: str = None) -> str:
|
||||||
if len(self.pools) > 0:
|
if len(self.pools) > 0:
|
||||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||||
return Pool("", "", "").as_avalon()
|
return Pool("", "", "").as_avalon()
|
||||||
@@ -241,8 +261,14 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return conf
|
return conf
|
||||||
return {"name": "Group", "pool": []}
|
return {"name": "Group", "pool": []}
|
||||||
|
|
||||||
|
def as_auradine(self, user_suffix: str = None) -> list:
|
||||||
|
return [p.as_auradine(user_suffix=user_suffix) for p in self.pools]
|
||||||
|
|
||||||
|
def as_epic(self, user_suffix: str = None) -> dict:
|
||||||
|
return [p.as_epic(user_suffix=user_suffix) for p in self.pools]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup":
|
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
||||||
cls_conf = {}
|
cls_conf = {}
|
||||||
|
|
||||||
if dict_conf.get("quota") is not None:
|
if dict_conf.get("quota") is not None:
|
||||||
@@ -296,7 +322,7 @@ class PoolGroup(MinerConfigValue):
|
|||||||
return cls([Pool.from_vnish(p) for p in web_settings_pools])
|
return cls([Pool.from_vnish(p) for p in web_settings_pools])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_pool_group: dict):
|
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]],
|
||||||
@@ -318,14 +344,14 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolConfig":
|
def from_dict(cls, dict_conf: dict | None) -> "PoolConfig":
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
return cls(groups=[PoolGroup.from_dict(g) for g in dict_conf["groups"]])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def simple(cls, pools: List[Union[Pool, Dict[str, str]]]) -> "PoolConfig":
|
def simple(cls, pools: list[Pool | dict[str, str]]) -> "PoolConfig":
|
||||||
group_pools = []
|
group_pools = []
|
||||||
for pool in pools:
|
for pool in pools:
|
||||||
if isinstance(pool, dict):
|
if isinstance(pool, dict):
|
||||||
@@ -373,6 +399,32 @@ class PoolConfig(MinerConfigValue):
|
|||||||
def as_boser(self, user_suffix: str = None) -> dict:
|
def as_boser(self, user_suffix: str = None) -> dict:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
def as_auradine(self, user_suffix: str = None) -> dict:
|
||||||
|
if len(self.groups) > 0:
|
||||||
|
return {
|
||||||
|
"updatepools": {
|
||||||
|
"pools": self.groups[0].as_auradine(user_suffix=user_suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {"updatepools": {"pools": PoolGroup().as_auradine()}}
|
||||||
|
|
||||||
|
def as_epic(self, user_suffix: str = None) -> dict:
|
||||||
|
if len(self.groups) > 0:
|
||||||
|
return {
|
||||||
|
"pools": {
|
||||||
|
"coin": "Btc",
|
||||||
|
"stratum_configs": self.groups[0].as_epic(user_suffix=user_suffix),
|
||||||
|
"unique_id": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"pools": {
|
||||||
|
"coin": "Btc",
|
||||||
|
"stratum_configs": [PoolGroup().as_epic()],
|
||||||
|
"unique_id": False,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||||
try:
|
try:
|
||||||
@@ -417,7 +469,7 @@ class PoolConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_miner_conf: dict):
|
def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig":
|
||||||
try:
|
try:
|
||||||
return cls(
|
return cls(
|
||||||
groups=[
|
groups=[
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||||
@@ -31,7 +32,7 @@ class PowerScalingShutdownEnabled(MinerConfigValue):
|
|||||||
duration: int = None
|
duration: int = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownEnabled":
|
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownEnabled":
|
||||||
return cls(duration=dict_conf.get("duration"))
|
return cls(duration=dict_conf.get("duration"))
|
||||||
|
|
||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
@@ -51,7 +52,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="disabled")
|
mode: str = field(init=False, default="disabled")
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingShutdownDisabled":
|
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingShutdownDisabled":
|
||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
def as_bosminer(self) -> dict:
|
def as_bosminer(self) -> dict:
|
||||||
@@ -66,7 +67,7 @@ class PowerScalingShutdown(MinerConfigOption):
|
|||||||
disabled = PowerScalingShutdownDisabled
|
disabled = PowerScalingShutdownDisabled
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
@@ -107,9 +108,7 @@ class PowerScalingEnabled(MinerConfigValue):
|
|||||||
mode: str = field(init=False, default="enabled")
|
mode: str = field(init=False, default="enabled")
|
||||||
power_step: int = None
|
power_step: int = None
|
||||||
minimum_power: int = None
|
minimum_power: int = None
|
||||||
shutdown_enabled: Union[
|
shutdown_enabled: PowerScalingShutdownEnabled | PowerScalingShutdownDisabled = None
|
||||||
PowerScalingShutdownEnabled, PowerScalingShutdownDisabled
|
|
||||||
] = None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
|
def from_bosminer(cls, power_scaling_conf: dict) -> "PowerScalingEnabled":
|
||||||
@@ -122,7 +121,7 @@ class PowerScalingEnabled(MinerConfigValue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "PowerScalingEnabled":
|
def from_dict(cls, dict_conf: dict | None) -> "PowerScalingEnabled":
|
||||||
cls_conf = {
|
cls_conf = {
|
||||||
"power_step": dict_conf.get("power_step"),
|
"power_step": dict_conf.get("power_step"),
|
||||||
"minimum_power": dict_conf.get("minimum_power"),
|
"minimum_power": dict_conf.get("minimum_power"),
|
||||||
@@ -175,7 +174,7 @@ class PowerScalingConfig(MinerConfigOption):
|
|||||||
return cls.disabled()
|
return cls.disabled()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]):
|
def from_dict(cls, dict_conf: dict | None):
|
||||||
if dict_conf is None:
|
if dict_conf is None:
|
||||||
return cls.default()
|
return cls.default()
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,9 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from pyasic.config.base import MinerConfigValue
|
from pyasic.config.base import MinerConfigValue
|
||||||
|
|
||||||
@@ -39,8 +40,18 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
temp_cfg["dangerous_temp"] = self.danger
|
temp_cfg["dangerous_temp"] = self.danger
|
||||||
return {"temp_control": temp_cfg}
|
return {"temp_control": temp_cfg}
|
||||||
|
|
||||||
|
def as_epic(self) -> dict:
|
||||||
|
temps_config = {"temps": {}, "fans": {"Auto": {}}}
|
||||||
|
if self.target is not None:
|
||||||
|
temps_config["fans"]["Auto"]["Target Temperature"] = self.target
|
||||||
|
else:
|
||||||
|
temps_config["fans"]["Auto"]["Target Temperature"] = 60
|
||||||
|
if self.danger is not None:
|
||||||
|
temps_config["temps"]["shutdown"] = self.danger
|
||||||
|
return temps_config
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, dict_conf: Union[dict, None]) -> "TemperatureConfig":
|
def from_dict(cls, dict_conf: dict | None) -> "TemperatureConfig":
|
||||||
return cls(
|
return cls(
|
||||||
target=dict_conf.get("target"),
|
target=dict_conf.get("target"),
|
||||||
hot=dict_conf.get("hot"),
|
hot=dict_conf.get("hot"),
|
||||||
@@ -59,21 +70,20 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
||||||
dangerous_temp = None
|
|
||||||
try:
|
try:
|
||||||
hot_temp = web_conf["Misc"]["Shutdown Temp"]
|
dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
hot_temp = None
|
dangerous_temp = None
|
||||||
# Need to do this in two blocks to avoid KeyError if one is missing
|
# Need to do this in two blocks to avoid KeyError if one is missing
|
||||||
try:
|
try:
|
||||||
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
|
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
target_temp = None
|
target_temp = None
|
||||||
|
|
||||||
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
return cls(target=target_temp, danger=dangerous_temp)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_vnish(cls, web_settings: dict):
|
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":
|
||||||
try:
|
try:
|
||||||
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
|
if web_settings["miner"]["cooling"]["mode"]["name"] == "auto":
|
||||||
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
|
return cls(target=web_settings["miner"]["cooling"]["mode"]["param"])
|
||||||
@@ -82,7 +92,7 @@ class TemperatureConfig(MinerConfigValue):
|
|||||||
return cls()
|
return cls()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_boser(cls, grpc_miner_conf: dict):
|
def from_boser(cls, grpc_miner_conf: dict) -> "TemperatureConfig":
|
||||||
try:
|
try:
|
||||||
temperature_conf = grpc_miner_conf["temperature"]
|
temperature_conf = grpc_miner_conf["temperature"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import time
|
import time
|
||||||
from dataclasses import asdict, dataclass, field, fields
|
from dataclasses import asdict, dataclass, field, fields
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
@@ -25,73 +24,9 @@ from typing import Any, List, Union
|
|||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.config.mining import MiningModePowerTune
|
from pyasic.config.mining import MiningModePowerTune
|
||||||
|
|
||||||
|
from .boards import HashBoard
|
||||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||||
|
from .fans import Fan
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class HashBoard:
|
|
||||||
"""A Dataclass to standardize hashboard data.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
slot: The slot of the board as an int.
|
|
||||||
hashrate: The hashrate of the board in TH/s as a float.
|
|
||||||
temp: The temperature of the PCB as an int.
|
|
||||||
chip_temp: The temperature of the chips as an int.
|
|
||||||
chips: The chip count of the board as an int.
|
|
||||||
expected_chips: The expected chip count of the board as an int.
|
|
||||||
serial_number: The serial number of the board.
|
|
||||||
missing: Whether the board is returned from the miners data as a bool.
|
|
||||||
"""
|
|
||||||
|
|
||||||
slot: int = 0
|
|
||||||
hashrate: float = None
|
|
||||||
temp: int = None
|
|
||||||
chip_temp: int = None
|
|
||||||
chips: int = None
|
|
||||||
expected_chips: int = None
|
|
||||||
serial_number: str = None
|
|
||||||
missing: bool = True
|
|
||||||
|
|
||||||
def get(self, __key: str, default: Any = None):
|
|
||||||
try:
|
|
||||||
val = self.__getitem__(__key)
|
|
||||||
if val is None:
|
|
||||||
return default
|
|
||||||
return val
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def __getitem__(self, item: str):
|
|
||||||
try:
|
|
||||||
return getattr(self, item)
|
|
||||||
except AttributeError:
|
|
||||||
raise KeyError(f"{item}")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Fan:
|
|
||||||
"""A Dataclass to standardize fan data.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
speed: The speed of the fan.
|
|
||||||
"""
|
|
||||||
|
|
||||||
speed: int = None
|
|
||||||
|
|
||||||
def get(self, __key: str, default: Any = None):
|
|
||||||
try:
|
|
||||||
val = self.__getitem__(__key)
|
|
||||||
if val is None:
|
|
||||||
return default
|
|
||||||
return val
|
|
||||||
except KeyError:
|
|
||||||
return default
|
|
||||||
|
|
||||||
def __getitem__(self, item: str):
|
|
||||||
try:
|
|
||||||
return getattr(self, item)
|
|
||||||
except AttributeError:
|
|
||||||
raise KeyError(f"{item}")
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -351,7 +286,6 @@ class MinerData:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def asdict(self) -> dict:
|
def asdict(self) -> dict:
|
||||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
|
||||||
return asdict(self, dict_factory=self.dict_factory)
|
return asdict(self, dict_factory=self.dict_factory)
|
||||||
|
|
||||||
def as_dict(self) -> dict:
|
def as_dict(self) -> dict:
|
||||||
@@ -368,7 +302,6 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A JSON version of this class.
|
A JSON version of this class.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
|
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||||
return json.dumps(data)
|
return json.dumps(data)
|
||||||
@@ -379,7 +312,6 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A CSV version of this class with no headers.
|
A CSV version of this class with no headers.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
|
|
||||||
data = self.asdict()
|
data = self.asdict()
|
||||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||||
errs = []
|
errs = []
|
||||||
@@ -398,7 +330,6 @@ class MinerData:
|
|||||||
Returns:
|
Returns:
|
||||||
A influxdb line protocol version of this class.
|
A influxdb line protocol version of this class.
|
||||||
"""
|
"""
|
||||||
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
|
|
||||||
tag_data = [measurement_name]
|
tag_data = [measurement_name]
|
||||||
field_data = []
|
field_data = []
|
||||||
|
|
||||||
|
|||||||
@@ -14,39 +14,45 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import warnings
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
from pyasic.miners.makes import WhatsMiner
|
|
||||||
|
|
||||||
|
|
||||||
class M33SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation
|
@dataclass
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
class HashBoard:
|
||||||
super().__init__(ip, api_ver)
|
"""A Dataclass to standardize hashboard data.
|
||||||
self.ip = ip
|
|
||||||
self.raw_model = "M33S+ VG20"
|
|
||||||
self.expected_hashboards = 4
|
|
||||||
self.expected_chips = 112
|
|
||||||
self.fan_count = 0
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
slot: The slot of the board as an int.
|
||||||
|
hashrate: The hashrate of the board in TH/s as a float.
|
||||||
|
temp: The temperature of the PCB as an int.
|
||||||
|
chip_temp: The temperature of the chips as an int.
|
||||||
|
chips: The chip count of the board as an int.
|
||||||
|
expected_chips: The expected chip count of the board as an int.
|
||||||
|
serial_number: The serial number of the board.
|
||||||
|
missing: Whether the board is returned from the miners data as a bool.
|
||||||
|
"""
|
||||||
|
|
||||||
class M33SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation
|
slot: int = 0
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
hashrate: float = None
|
||||||
super().__init__(ip, api_ver)
|
temp: int = None
|
||||||
self.ip = ip
|
chip_temp: int = None
|
||||||
self.raw_model = "M33S+ VH20"
|
chips: int = None
|
||||||
self.expected_hashboards = 4
|
expected_chips: int = None
|
||||||
self.expected_chips = 100
|
serial_number: str = None
|
||||||
self.fan_count = 0
|
missing: bool = True
|
||||||
|
|
||||||
|
def get(self, __key: str, default: Any = None):
|
||||||
|
try:
|
||||||
|
val = self.__getitem__(__key)
|
||||||
|
if val is None:
|
||||||
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
class M33SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation
|
def __getitem__(self, item: str):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
try:
|
||||||
super().__init__(ip, api_ver)
|
return getattr(self, item)
|
||||||
self.ip = ip
|
except AttributeError:
|
||||||
self.raw_model = "M33S+ VH30"
|
raise KeyError(f"{item}")
|
||||||
self.expected_hashboards = 4
|
|
||||||
self.expected_chips = 0 # slot1 116, slot2 106, slot3 116, slot4 106
|
|
||||||
warnings.warn(
|
|
||||||
"Unknown chip count for miner type M30S+ VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
|
||||||
)
|
|
||||||
self.fan_count = 0
|
|
||||||
@@ -14,22 +14,31 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.makes import WhatsMiner
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
|
||||||
class M31V10(WhatsMiner): # noqa - ignore ABC method implementation
|
@dataclass
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
class Fan:
|
||||||
super().__init__(ip, api_ver)
|
"""A Dataclass to standardize fan data.
|
||||||
self.ip = ip
|
|
||||||
self.raw_model = "M31 V10"
|
|
||||||
self.expected_chips = 70
|
|
||||||
self.fan_count = 2
|
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
speed: The speed of the fan.
|
||||||
|
"""
|
||||||
|
|
||||||
class M31V20(WhatsMiner): # noqa - ignore ABC method implementation
|
speed: int = None
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
|
||||||
super().__init__(ip, api_ver)
|
def get(self, __key: str, default: Any = None):
|
||||||
self.ip = ip
|
try:
|
||||||
self.raw_model = "M31 V20"
|
val = self.__getitem__(__key)
|
||||||
self.expected_chips = 74
|
if val is None:
|
||||||
self.fan_count = 2
|
return default
|
||||||
|
return val
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def __getitem__(self, item: str):
|
||||||
|
try:
|
||||||
|
return getattr(self, item)
|
||||||
|
except AttributeError:
|
||||||
|
raise KeyError(f"{item}")
|
||||||
@@ -20,7 +20,7 @@ from typing import List, Union
|
|||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners import AnyMiner
|
from pyasic.miners import AnyMiner
|
||||||
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
from pyasic.miners.backends import AntminerModern, BOSMiner, BTMiner
|
||||||
from pyasic.miners.types import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
from pyasic.miners.models import S9, S17, T17, S17e, S17Plus, S17Pro, T17e, T17Plus
|
||||||
|
|
||||||
FAN_USAGE = 50 # 50 W per fan
|
FAN_USAGE = 50 # 50 W per fan
|
||||||
|
|
||||||
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
|
|||||||
not self.miners[data_point.ip]["shutdown"]
|
not self.miners[data_point.ip]["shutdown"]
|
||||||
):
|
):
|
||||||
# cant do anything with it so need to find a semi-accurate power limit
|
# cant do anything with it so need to find a semi-accurate power limit
|
||||||
if not data_point.wattage_limit == None:
|
if data_point.wattage_limit is not None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
|
||||||
elif not data_point.wattage == None:
|
elif data_point.wattage is not None:
|
||||||
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
self.miners[data_point.ip]["max"] = int(data_point.wattage)
|
||||||
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
self.miners[data_point.ip]["min"] = int(data_point.wattage)
|
||||||
|
|
||||||
@@ -183,13 +183,19 @@ class _MinerPhaseBalancer:
|
|||||||
if (not miner["tune"]) and (miner["shutdown"])
|
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"])])
|
# 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
|
# make sure wattage isnt set too high
|
||||||
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
||||||
raise APIError(
|
raise APIError(
|
||||||
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
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
|
# 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
|
# check if 1/2 max of the miners which can be tuned is low enough
|
||||||
|
|||||||
@@ -14,13 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import ipaddress
|
from .base import AnyMiner
|
||||||
from typing import Union
|
from .data import DataOptions
|
||||||
|
from .factory import get_miner, miner_factory
|
||||||
from pyasic.miners.base import AnyMiner, BaseMiner
|
from .listener import MinerListener
|
||||||
from pyasic.miners.miner_factory import miner_factory
|
|
||||||
|
|
||||||
|
|
||||||
# abstracted version of get miner that is easier to access
|
|
||||||
async def get_miner(ip: Union[ipaddress.ip_address, str]) -> AnyMiner:
|
|
||||||
return await miner_factory.get_miner(ip)
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro
|
from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS17(AntminerOld, S17):
|
class BMMinerS17(AntminerOld, S17):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import T17, T17e, T17Plus
|
from pyasic.miners.models import T17, T17e, T17Plus
|
||||||
|
|
||||||
|
|
||||||
class BMMinerT17(AntminerOld, T17):
|
class BMMinerT17(AntminerOld, T17):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.types import (
|
from pyasic.miners.models import (
|
||||||
S19,
|
S19,
|
||||||
S19L,
|
S19L,
|
||||||
S19XP,
|
S19XP,
|
||||||
|
|||||||
@@ -15,9 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.types import T19
|
from pyasic.miners.models import T19
|
||||||
|
|
||||||
# noqa - Ignore access to _module
|
|
||||||
|
|
||||||
|
|
||||||
class BMMinerT19(AntminerModern, T19):
|
class BMMinerT19(AntminerModern, T19):
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.types import HS3
|
from pyasic.miners.models import HS3
|
||||||
|
|
||||||
|
|
||||||
class BMMinerHS3(AntminerModern, HS3):
|
class BMMinerHS3(AntminerModern, HS3):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import L3Plus
|
from pyasic.miners.models import L3Plus
|
||||||
|
|
||||||
|
|
||||||
class BMMinerL3Plus(AntminerOld, L3Plus):
|
class BMMinerL3Plus(AntminerOld, L3Plus):
|
||||||
|
|||||||
@@ -14,10 +14,8 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.types import L7
|
from pyasic.miners.models import L7
|
||||||
|
|
||||||
|
|
||||||
class BMMinerL7(AntminerModern, L7):
|
class BMMinerL7(AntminerModern, L7):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerModern
|
from pyasic.miners.backends import AntminerModern
|
||||||
from pyasic.miners.types import E9Pro
|
from pyasic.miners.models import E9Pro
|
||||||
|
|
||||||
|
|
||||||
class BMMinerE9Pro(AntminerModern, E9Pro):
|
class BMMinerE9Pro(AntminerModern, E9Pro):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BMMiner
|
from pyasic.miners.backends import BMMiner
|
||||||
from pyasic.miners.types import S9, S9i, S9j
|
from pyasic.miners.models import S9, S9i, S9j
|
||||||
|
|
||||||
|
|
||||||
class BMMinerS9(BMMiner, S9):
|
class BMMinerS9(BMMiner, S9):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BMMiner
|
from pyasic.miners.backends import BMMiner
|
||||||
from pyasic.miners.types import T9
|
from pyasic.miners.models import T9
|
||||||
|
|
||||||
|
|
||||||
class BMMinerT9(BMMiner, T9):
|
class BMMinerT9(BMMiner, T9):
|
||||||
|
|||||||
@@ -14,21 +14,21 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSer
|
from pyasic.miners.backends import BOSMiner
|
||||||
from pyasic.miners.types import S17, S17e, S17Plus, S17Pro
|
from pyasic.miners.models import S17, S17e, S17Plus, S17Pro
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS17(BOSer, S17):
|
class BOSMinerS17(BOSMiner, S17):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS17Plus(BOSer, S17Plus):
|
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS17Pro(BOSer, S17Pro):
|
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS17e(BOSer, S17e):
|
class BOSMinerS17e(BOSMiner, S17e):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,17 +14,17 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSer
|
from pyasic.miners.backends import BOSMiner
|
||||||
from pyasic.miners.types import T17, T17e, T17Plus
|
from pyasic.miners.models import T17, T17e, T17Plus
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerT17(BOSer, T17):
|
class BOSMinerT17(BOSMiner, T17):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerT17Plus(BOSer, T17Plus):
|
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerT17e(BOSer, T17e):
|
class BOSMinerT17e(BOSMiner, T17e):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSer
|
from pyasic.miners.backends import BOSer
|
||||||
from pyasic.miners.types import (
|
from pyasic.miners.models import (
|
||||||
S19,
|
S19,
|
||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSer
|
from pyasic.miners.backends import BOSer
|
||||||
from pyasic.miners.types import T19
|
from pyasic.miners.models import T19
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerT19(BOSer, T19):
|
class BOSMinerT19(BOSer, T19):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import BOSMiner
|
from pyasic.miners.backends import BOSMiner
|
||||||
from pyasic.miners.types import S9
|
from pyasic.miners.models import S9
|
||||||
|
|
||||||
|
|
||||||
class BOSMinerS9(BOSMiner, S9):
|
class BOSMinerS9(BOSMiner, S9):
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import Z15
|
from pyasic.miners.models import Z15
|
||||||
|
|
||||||
|
|
||||||
class CGMinerZ15(AntminerOld, Z15):
|
class CGMinerZ15(AntminerOld, Z15):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -14,10 +14,8 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import D3
|
from pyasic.miners.models import D3
|
||||||
|
|
||||||
|
|
||||||
class CGMinerD3(AntminerOld, D3):
|
class CGMinerD3(AntminerOld, D3):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -15,10 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import AntminerOld
|
from pyasic.miners.backends import AntminerOld
|
||||||
from pyasic.miners.types import DR5
|
from pyasic.miners.models import DR5
|
||||||
|
|
||||||
|
|
||||||
class CGMinerDR5(AntminerOld, DR5):
|
class CGMinerDR5(AntminerOld, DR5):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_shutdown = False
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_shutdown = False
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import ePIC
|
from pyasic.miners.backends import ePIC
|
||||||
from pyasic.miners.types import S19, S19XP, S19j, S19jPro, S19jProPlus, S19kPro, S19Pro
|
from pyasic.miners.models import S19, S19XP, S19j, S19jPro, S19jProPlus, S19kPro, S19Pro
|
||||||
|
|
||||||
|
|
||||||
class ePICS19(ePIC, S19):
|
class ePICS19(ePIC, S19):
|
||||||
|
|||||||
@@ -21,10 +21,53 @@ import asyncssh
|
|||||||
from pyasic.data import HashBoard
|
from pyasic.data import HashBoard
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends import Hiveon
|
from pyasic.miners.backends import Hiveon
|
||||||
from pyasic.miners.types import T9
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
|
from pyasic.miners.models import T9
|
||||||
|
|
||||||
|
HIVEON_T9_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
|
"_get_env_temp",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
|
"_get_wattage",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class HiveonT9(Hiveon, T9):
|
class HiveonT9(Hiveon, T9):
|
||||||
|
data_locations = HIVEON_T9_DATA_LOC
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
@@ -41,15 +84,15 @@ class HiveonT9(Hiveon, T9):
|
|||||||
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
|
except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=board, expected_chips=self.expected_chips)
|
HashBoard(slot=board, expected_chips=self.expected_chips)
|
||||||
for board in range(self.expected_hashboards)
|
for board in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if api_stats is None:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = self.api.stats()
|
rpc_stats = self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
@@ -65,8 +108,8 @@ class HiveonT9(Hiveon, T9):
|
|||||||
for chipset in board_map[board]:
|
for chipset in board_map[board]:
|
||||||
if hashboards[board].chip_temp is None:
|
if hashboards[board].chip_temp is None:
|
||||||
try:
|
try:
|
||||||
hashboards[board].temp = api_stats["STATS"][1][f"temp{chipset}"]
|
hashboards[board].temp = rpc_stats["STATS"][1][f"temp{chipset}"]
|
||||||
hashboards[board].chip_temp = api_stats["STATS"][1][
|
hashboards[board].chip_temp = rpc_stats["STATS"][1][
|
||||||
f"temp2_{chipset}"
|
f"temp2_{chipset}"
|
||||||
]
|
]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
@@ -74,8 +117,8 @@ class HiveonT9(Hiveon, T9):
|
|||||||
else:
|
else:
|
||||||
hashboards[board].missing = False
|
hashboards[board].missing = False
|
||||||
try:
|
try:
|
||||||
hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"]
|
hashrate += rpc_stats["STATS"][1][f"chain_rate{chipset}"]
|
||||||
chips += api_stats["STATS"][1][f"chain_acn{chipset}"]
|
chips += rpc_stats["STATS"][1][f"chain_acn{chipset}"]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
hashboards[board].hashrate = round(hashrate / 1000, 2)
|
hashboards[board].hashrate = round(hashrate / 1000, 2)
|
||||||
@@ -83,15 +126,15 @@ class HiveonT9(Hiveon, T9):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
if not api_stats:
|
if not rpc_stats:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats:
|
||||||
boards = api_stats.get("STATS")
|
boards = rpc_stats.get("STATS")
|
||||||
try:
|
try:
|
||||||
wattage_raw = boards[1]["chain_power"]
|
wattage_raw = boards[1]["chain_power"]
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
@@ -100,23 +143,23 @@ class HiveonT9(Hiveon, T9):
|
|||||||
# parse wattage position out of raw data
|
# parse wattage position out of raw data
|
||||||
return round(float(wattage_raw.split(" ")[0]))
|
return round(float(wattage_raw.split(" ")[0]))
|
||||||
|
|
||||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
env_temp_list = []
|
env_temp_list = []
|
||||||
board_map = {
|
board_map = {
|
||||||
0: [2, 9, 10],
|
0: [2, 9, 10],
|
||||||
1: [3, 11, 12],
|
1: [3, 11, 12],
|
||||||
2: [4, 13, 14],
|
2: [4, 13, 14],
|
||||||
}
|
}
|
||||||
if not api_stats:
|
if not rpc_stats:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
if api_stats:
|
if rpc_stats:
|
||||||
for board in board_map.values():
|
for board in board_map.values():
|
||||||
for chipset in board:
|
for chipset in board:
|
||||||
try:
|
try:
|
||||||
env_temp = api_stats["STATS"][1][f"temp3_{chipset}"]
|
env_temp = rpc_stats["STATS"][1][f"temp3_{chipset}"]
|
||||||
if not env_temp == 0:
|
if not env_temp == 0:
|
||||||
env_temp_list.append(int(env_temp))
|
env_temp_list.append(int(env_temp))
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import LUXMiner
|
from pyasic.miners.backends import LUXMiner
|
||||||
from pyasic.miners.types import S9
|
from pyasic.miners.models import S9
|
||||||
|
|
||||||
|
|
||||||
class LUXMinerS9(LUXMiner, S9):
|
class LUXMinerS9(LUXMiner, S9):
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from pyasic.miners.backends import VNish
|
from pyasic.miners.backends import VNish
|
||||||
from pyasic.miners.types import S17Plus, S17Pro
|
from pyasic.miners.models import S17Plus, S17Pro
|
||||||
|
|
||||||
|
|
||||||
class VNishS17Plus(VNish, S17Plus):
|
class VNishS17Plus(VNish, S17Plus):
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import VNish
|
from pyasic.miners.backends import VNish
|
||||||
from pyasic.miners.types import (
|
from pyasic.miners.models import (
|
||||||
S19,
|
S19,
|
||||||
S19XP,
|
S19XP,
|
||||||
S19a,
|
S19a,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import VNish
|
from pyasic.miners.backends import VNish
|
||||||
from pyasic.miners.types import T19
|
from pyasic.miners.models import T19
|
||||||
|
|
||||||
|
|
||||||
class VNishT19(VNish, T19):
|
class VNishT19(VNish, T19):
|
||||||
|
|||||||
@@ -15,9 +15,8 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import VNish
|
from pyasic.miners.backends import VNish
|
||||||
from pyasic.miners.types import L3Plus
|
from pyasic.miners.models import L3Plus
|
||||||
|
|
||||||
|
|
||||||
class VnishL3Plus(VNish, L3Plus):
|
class VnishL3Plus(VNish, L3Plus):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
pass
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
|
|||||||
1
pyasic/miners/auradine/__init__.py
Normal file
1
pyasic/miners/auradine/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from .flux import *
|
||||||
6
pyasic/miners/auradine/flux/AD/AT1.py
Normal file
6
pyasic/miners/auradine/flux/AD/AT1.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAT1500
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAT1500(AuradineAT1500, Auradine):
|
||||||
|
pass
|
||||||
10
pyasic/miners/auradine/flux/AD/AT2.py
Normal file
10
pyasic/miners/auradine/flux/AD/AT2.py
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAT2860, AuradineAT2880
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAT2860(AuradineAT2860, Auradine):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAT2880(AuradineAT2880, Auradine):
|
||||||
|
pass
|
||||||
2
pyasic/miners/auradine/flux/AD/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AD/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .AT1 import AuradineFluxAT1500
|
||||||
|
from .AT2 import AuradineFluxAT2860, AuradineFluxAT2880
|
||||||
6
pyasic/miners/auradine/flux/AI/AI2.py
Normal file
6
pyasic/miners/auradine/flux/AI/AI2.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAI2500
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAI2500(AuradineAI2500, Auradine):
|
||||||
|
pass
|
||||||
6
pyasic/miners/auradine/flux/AI/AI3.py
Normal file
6
pyasic/miners/auradine/flux/AI/AI3.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAI3680
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAI3680(AuradineAI3680, Auradine):
|
||||||
|
pass
|
||||||
2
pyasic/miners/auradine/flux/AI/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AI/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .AI2 import AuradineFluxAI2500
|
||||||
|
from .AI3 import AuradineFluxAI3680
|
||||||
6
pyasic/miners/auradine/flux/AT/AD2.py
Normal file
6
pyasic/miners/auradine/flux/AT/AD2.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAD2500
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAD2500(AuradineAD2500, Auradine):
|
||||||
|
pass
|
||||||
6
pyasic/miners/auradine/flux/AT/AD3.py
Normal file
6
pyasic/miners/auradine/flux/AT/AD3.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from pyasic.miners.backends import Auradine
|
||||||
|
from pyasic.miners.models import AuradineAD3500
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineFluxAD3500(AuradineAD3500, Auradine):
|
||||||
|
pass
|
||||||
2
pyasic/miners/auradine/flux/AT/__init__.py
Normal file
2
pyasic/miners/auradine/flux/AT/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
from .AD2 import AuradineFluxAD2500
|
||||||
|
from .AD3 import AuradineFluxAD3500
|
||||||
3
pyasic/miners/auradine/flux/__init__.py
Normal file
3
pyasic/miners/auradine/flux/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from .AD import *
|
||||||
|
from .AI import *
|
||||||
|
from .AT import *
|
||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon1026
|
from pyasic.miners.models import Avalon1026
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon1026(CGMinerAvalon, Avalon1026):
|
class CGMinerAvalon1026(AvalonMiner, Avalon1026):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon1047
|
from pyasic.miners.models import Avalon1047
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon1047(CGMinerAvalon, Avalon1047):
|
class CGMinerAvalon1047(AvalonMiner, Avalon1047):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon1066
|
from pyasic.miners.models import Avalon1066
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon1066(CGMinerAvalon, Avalon1066):
|
class CGMinerAvalon1066(AvalonMiner, Avalon1066):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon1166Pro
|
from pyasic.miners.models import Avalon1166Pro
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon1166Pro(CGMinerAvalon, Avalon1166Pro):
|
class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon1246
|
from pyasic.miners.models import Avalon1246
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon1246(CGMinerAvalon, Avalon1246):
|
class CGMinerAvalon1246(AvalonMiner, Avalon1246):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon721
|
from pyasic.miners.models import Avalon721
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon721(CGMinerAvalon, Avalon721):
|
class CGMinerAvalon721(AvalonMiner, Avalon721):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon741
|
from pyasic.miners.models import Avalon741
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon741(CGMinerAvalon, Avalon741):
|
class CGMinerAvalon741(AvalonMiner, Avalon741):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon761
|
from pyasic.miners.models import Avalon761
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon761(CGMinerAvalon, Avalon761):
|
class CGMinerAvalon761(AvalonMiner, Avalon761):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon821
|
from pyasic.miners.models import Avalon821
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon821(CGMinerAvalon, Avalon821):
|
class CGMinerAvalon821(AvalonMiner, Avalon821):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon841
|
from pyasic.miners.models import Avalon841
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon841(CGMinerAvalon, Avalon841):
|
class CGMinerAvalon841(AvalonMiner, Avalon841):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon851
|
from pyasic.miners.models import Avalon851
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon851(CGMinerAvalon, Avalon851):
|
class CGMinerAvalon851(AvalonMiner, Avalon851):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from pyasic.miners.backends import CGMinerAvalon
|
from pyasic.miners.backends import AvalonMiner
|
||||||
from pyasic.miners.types import Avalon921
|
from pyasic.miners.models import Avalon921
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon921(CGMinerAvalon, Avalon921):
|
class CGMinerAvalon921(AvalonMiner, Avalon921):
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,15 +14,17 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from .antminer import AntminerModern, AntminerOld
|
from .antminer import AntminerModern, AntminerOld
|
||||||
|
from .auradine import Auradine
|
||||||
|
from .avalonminer import AvalonMiner
|
||||||
from .bfgminer import BFGMiner
|
from .bfgminer import BFGMiner
|
||||||
from .bfgminer_goldshell import BFGMinerGoldshell
|
|
||||||
from .bmminer import BMMiner
|
from .bmminer import BMMiner
|
||||||
from .braiins_os import BOSer, BOSMiner
|
from .braiins_os import BOSer, BOSMiner
|
||||||
from .btminer import BTMiner
|
from .btminer import BTMiner
|
||||||
from .cgminer import CGMiner
|
from .cgminer import CGMiner
|
||||||
from .cgminer_avalon import CGMinerAvalon
|
|
||||||
from .epic import ePIC
|
from .epic import ePIC
|
||||||
|
from .goldshell import GoldshellMiner
|
||||||
from .hiveon import Hiveon
|
from .hiveon import Hiveon
|
||||||
|
from .innosilicon import Innosilicon
|
||||||
from .luxminer import LUXMiner
|
from .luxminer import LUXMiner
|
||||||
from .vnish import VNish
|
from .vnish import VNish
|
||||||
from .whatsminer import M2X, M3X, M5X, M6X
|
from .whatsminer import M2X, M3X, M5X, M6X
|
||||||
|
|||||||
@@ -16,78 +16,84 @@
|
|||||||
|
|
||||||
from typing import List, Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from pyasic.API import APIError
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||||
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends.bmminer import BMMiner
|
from pyasic.miners.backends.bmminer import BMMiner
|
||||||
from pyasic.miners.backends.cgminer import CGMiner
|
from pyasic.miners.backends.cgminer import CGMiner
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
DataOptions,
|
DataOptions,
|
||||||
RPCAPICommand,
|
RPCAPICommand,
|
||||||
WebAPICommand,
|
WebAPICommand,
|
||||||
)
|
)
|
||||||
|
from pyasic.ssh.antminer import AntminerModernSSH
|
||||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||||
|
|
||||||
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [WebAPICommand("web_get_system_info", "get_system_info")]
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction("_get_hashboards"),
|
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction(
|
str(DataOptions.ERRORS): DataFunction(
|
||||||
"_get_errors", [WebAPICommand("web_summary", "summary")]
|
"_get_errors",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||||
),
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction(
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
"_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
|
"_is_mining",
|
||||||
|
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
||||||
),
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AntminerModern(BMMiner):
|
class AntminerModern(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for AntMiners with the modern web interface, such as S19"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = AntminerModernWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = AntminerModernWebAPI
|
||||||
# data gathering locations
|
web: AntminerModernWebAPI
|
||||||
self.data_locations = ANTMINER_MODERN_DATA_LOC
|
|
||||||
# autotuning/shutdown support
|
_ssh_cls = AntminerModernSSH
|
||||||
self.supports_shutdown = True
|
ssh: AntminerModernSSH
|
||||||
|
|
||||||
|
data_locations = ANTMINER_MODERN_DATA_LOC
|
||||||
|
|
||||||
|
supports_shutdown = True
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
@@ -141,26 +147,26 @@ class AntminerModern(BMMiner):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
|
async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
if not web_get_system_info:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_system_info:
|
if web_get_system_info is not None:
|
||||||
try:
|
try:
|
||||||
return web_get_system_info["hostname"]
|
return web_get_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
|
async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||||
if not web_get_system_info:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_system_info:
|
if web_get_system_info is not None:
|
||||||
try:
|
try:
|
||||||
return web_get_system_info["macaddr"]
|
return web_get_system_info["macaddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -174,14 +180,14 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
for item in web_summary["SUMMARY"][0]["status"]:
|
for item in web_summary["SUMMARY"][0]["status"]:
|
||||||
try:
|
try:
|
||||||
@@ -200,13 +206,13 @@ class AntminerModern(BMMiner):
|
|||||||
]
|
]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.send_command("stats", new_api=True)
|
rpc_stats = await self.rpc.send_command("stats", new_api=True)
|
||||||
except APIError:
|
except APIError:
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
for board in api_stats["STATS"][0]["chain"]:
|
for board in rpc_stats["STATS"][0]["chain"]:
|
||||||
hashboards[board["index"]].hashrate = round(
|
hashboards[board["index"]].hashrate = round(
|
||||||
board["rate_real"] / 1000, 2
|
board["rate_real"] / 1000, 2
|
||||||
)
|
)
|
||||||
@@ -229,35 +235,37 @@ class AntminerModern(BMMiner):
|
|||||||
pass
|
pass
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool:
|
async def _get_fault_light(
|
||||||
|
self, web_get_blink_status: dict = None
|
||||||
|
) -> Optional[bool]:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
if not web_get_blink_status:
|
if web_get_blink_status is None:
|
||||||
try:
|
try:
|
||||||
web_get_blink_status = await self.web.get_blink_status()
|
web_get_blink_status = await self.web.get_blink_status()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_blink_status:
|
if web_get_blink_status is not None:
|
||||||
try:
|
try:
|
||||||
self.light = web_get_blink_status["blink"]
|
self.light = web_get_blink_status["blink"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
@@ -312,13 +320,13 @@ class AntminerModern(BMMiner):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||||
if not web_get_conf:
|
if web_get_conf is None:
|
||||||
try:
|
try:
|
||||||
web_get_conf = await self.web.get_miner_conf()
|
web_get_conf = await self.web.get_miner_conf()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_conf:
|
if web_get_conf is not None:
|
||||||
try:
|
try:
|
||||||
if web_get_conf["bitmain-work-mode"].isdigit():
|
if web_get_conf["bitmain-work-mode"].isdigit():
|
||||||
return (
|
return (
|
||||||
@@ -328,73 +336,69 @@ class AntminerModern(BMMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
ANTMINER_OLD_DATA_LOC = DataLocations(
|
ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")]
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_get_system_info", "get_system_info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
),
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||||
),
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction(
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
"_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")]
|
"_is_mining",
|
||||||
|
[WebAPICommand("web_get_conf", "get_miner_conf")],
|
||||||
),
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class AntminerOld(CGMiner):
|
class AntminerOld(CGMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for AntMiners with the old web interface, such as S17"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = AntminerOldWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = AntminerOldWebAPI
|
||||||
# data gathering locations
|
web: AntminerOldWebAPI
|
||||||
self.data_locations = ANTMINER_OLD_DATA_LOC
|
|
||||||
|
data_locations = ANTMINER_OLD_DATA_LOC
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
data = await self.web.get_miner_conf()
|
data = await self.web.get_miner_conf()
|
||||||
@@ -443,17 +447,19 @@ class AntminerOld(CGMiner):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool:
|
async def _get_fault_light(
|
||||||
|
self, web_get_blink_status: dict = None
|
||||||
|
) -> Optional[bool]:
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
if not web_get_blink_status:
|
if web_get_blink_status is None:
|
||||||
try:
|
try:
|
||||||
web_get_blink_status = await self.web.get_blink_status()
|
web_get_blink_status = await self.web.get_blink_status()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_blink_status:
|
if web_get_blink_status is not None:
|
||||||
try:
|
try:
|
||||||
self.light = web_get_blink_status["isBlinking"]
|
self.light = web_get_blink_status["isBlinking"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -461,59 +467,59 @@ class AntminerOld(CGMiner):
|
|||||||
return self.light
|
return self.light
|
||||||
|
|
||||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||||
if not web_get_system_info:
|
if web_get_system_info is None:
|
||||||
try:
|
try:
|
||||||
web_get_system_info = await self.web.get_system_info()
|
web_get_system_info = await self.web.get_system_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_system_info:
|
if web_get_system_info is not None:
|
||||||
try:
|
try:
|
||||||
return web_get_system_info["hostname"]
|
return web_get_system_info["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
|
|
||||||
for fan_num in range(1, 8, 4):
|
for fan_num in range(1, 8, 4):
|
||||||
for _f_num in range(4):
|
for _f_num in range(4):
|
||||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}")
|
||||||
if f and not f == 0 and fan_offset == -1:
|
if f and not f == 0 and fan_offset == -1:
|
||||||
fan_offset = fan_num + 2
|
fan_offset = fan_num + 2
|
||||||
if fan_offset == -1:
|
if fan_offset == -1:
|
||||||
fan_offset = 3
|
fan_offset = 3
|
||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans_data[fan].speed = api_stats["STATS"][1].get(
|
fans_data[fan].speed = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset+fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = []
|
hashboards = []
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
boards = api_stats["STATS"]
|
boards = rpc_stats["STATS"]
|
||||||
if len(boards) > 1:
|
if len(boards) > 1:
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
@@ -556,39 +562,39 @@ class AntminerOld(CGMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||||
if not web_get_conf:
|
if web_get_conf is None:
|
||||||
try:
|
try:
|
||||||
web_get_conf = await self.web.get_miner_conf()
|
web_get_conf = await self.web.get_miner_conf()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_conf:
|
if web_get_conf is not None:
|
||||||
try:
|
try:
|
||||||
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
api_summary = None
|
rpc_summary = None
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary is not None:
|
if rpc_summary is not None:
|
||||||
if not api_summary == {}:
|
if not rpc_summary == {}:
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
387
pyasic/miners/backends/auradine.py
Normal file
387
pyasic/miners/backends/auradine.py
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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 enum import Enum
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
from pyasic.config import MinerConfig
|
||||||
|
from pyasic.data import Fan, HashBoard
|
||||||
|
from pyasic.errors import APIError
|
||||||
|
from pyasic.miners.base import BaseMiner
|
||||||
|
from pyasic.miners.data import (
|
||||||
|
DataFunction,
|
||||||
|
DataLocations,
|
||||||
|
DataOptions,
|
||||||
|
RPCAPICommand,
|
||||||
|
WebAPICommand,
|
||||||
|
)
|
||||||
|
from pyasic.rpc.gcminer import GCMinerRPCAPI
|
||||||
|
from pyasic.web.auradine import AuradineWebAPI
|
||||||
|
|
||||||
|
AURADINE_DATA_LOC = DataLocations(
|
||||||
|
**{
|
||||||
|
str(DataOptions.MAC): DataFunction(
|
||||||
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_ipreport", "ipreport")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_ipreport", "ipreport")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_ipreport", "ipreport")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
|
),
|
||||||
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
|
"_get_hashboards",
|
||||||
|
[
|
||||||
|
RPCAPICommand("rpc_devs", "devs"),
|
||||||
|
WebAPICommand("web_ipreport", "ipreport"),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
|
"_get_wattage",
|
||||||
|
[WebAPICommand("web_psu", "psu")],
|
||||||
|
),
|
||||||
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
|
"_get_wattage_limit",
|
||||||
|
[WebAPICommand("web_mode", "mode"), WebAPICommand("web_psu", "psu")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FANS): DataFunction(
|
||||||
|
"_get_fans",
|
||||||
|
[WebAPICommand("web_fan", "fan")],
|
||||||
|
),
|
||||||
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
|
"_get_fault_light",
|
||||||
|
[WebAPICommand("web_led", "led")],
|
||||||
|
),
|
||||||
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
|
"_is_mining",
|
||||||
|
[WebAPICommand("web_mode", "mode")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineLEDColors(Enum):
|
||||||
|
OFF = 0
|
||||||
|
GREEN = 1
|
||||||
|
RED = 2
|
||||||
|
YELLOW = 3
|
||||||
|
GREEN_FLASHING = 4
|
||||||
|
RED_FLASHING = 5
|
||||||
|
YELLOW_FLASHING = 6
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class AuradineLEDCodes(Enum):
|
||||||
|
NO_POWER = 1
|
||||||
|
NORMAL = 2
|
||||||
|
LOCATE_MINER = 3
|
||||||
|
TEMPERATURE = 4
|
||||||
|
POOL_CONFIG = 5
|
||||||
|
NETWORK = 6
|
||||||
|
CONTROL_BOARD = 7
|
||||||
|
HASH_RATE_LOW = 8
|
||||||
|
CUSTOM1 = 101
|
||||||
|
CUSTOM2 = 102
|
||||||
|
|
||||||
|
def __int__(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
|
||||||
|
class Auradine(BaseMiner):
|
||||||
|
"""Base handler for Auradine miners"""
|
||||||
|
|
||||||
|
_rpc_cls = GCMinerRPCAPI
|
||||||
|
rpc: GCMinerRPCAPI
|
||||||
|
_web_cls = AuradineWebAPI
|
||||||
|
web: AuradineWebAPI
|
||||||
|
|
||||||
|
data_locations = AURADINE_DATA_LOC
|
||||||
|
|
||||||
|
supports_shutdown = True
|
||||||
|
supports_autotuning = True
|
||||||
|
|
||||||
|
async def fault_light_on(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.set_led(code=int(AuradineLEDCodes.LOCATE_MINER))
|
||||||
|
return True
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def fault_light_off(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.set_led(code=int(AuradineLEDCodes.NORMAL))
|
||||||
|
return True
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def reboot(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.reboot()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def restart_backend(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.restart_gcminer()
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def stop_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.set_mode(sleep="on")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def resume_mining(self) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.set_mode(sleep="off")
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
|
try:
|
||||||
|
await self.web.set_mode(mode="custom", tune="power", power=wattage)
|
||||||
|
except APIError:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
async def get_config(self) -> MinerConfig:
|
||||||
|
try:
|
||||||
|
web_conf = await self.web.multicommand("pools", "mode", "fan")
|
||||||
|
return MinerConfig.from_auradine(web_conf=web_conf)
|
||||||
|
except APIError as e:
|
||||||
|
logging.warning(e)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return MinerConfig()
|
||||||
|
|
||||||
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
conf = config.as_auradine(user_suffix=user_suffix)
|
||||||
|
for key in conf.keys():
|
||||||
|
await self.web.send_command(command=key, **conf[key])
|
||||||
|
|
||||||
|
##################################################
|
||||||
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
|
##################################################
|
||||||
|
|
||||||
|
async def _get_mac(self, web_ipreport: dict = None) -> Optional[str]:
|
||||||
|
if web_ipreport is None:
|
||||||
|
try:
|
||||||
|
web_ipreport = await self.web.ipreport()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_ipreport is not None:
|
||||||
|
try:
|
||||||
|
return web_ipreport["IPReport"][0]["mac"].upper()
|
||||||
|
except (LookupError, AttributeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_fw_ver(self, web_ipreport: dict = None) -> Optional[str]:
|
||||||
|
if web_ipreport is None:
|
||||||
|
try:
|
||||||
|
web_ipreport = await self.web.ipreport()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_ipreport is not None:
|
||||||
|
try:
|
||||||
|
return web_ipreport["IPReport"][0]["version"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hostname(self, web_ipreport: dict = None) -> Optional[str]:
|
||||||
|
if web_ipreport is None:
|
||||||
|
try:
|
||||||
|
web_ipreport = await self.web.ipreport()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_ipreport is not None:
|
||||||
|
try:
|
||||||
|
return web_ipreport["IPReport"][0]["hostname"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
|
if rpc_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_summary = await self.rpc.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_summary is not None:
|
||||||
|
try:
|
||||||
|
return round(
|
||||||
|
float(float(rpc_summary["SUMMARY"][0]["MHS 5s"]) / 1000000), 2
|
||||||
|
)
|
||||||
|
except (LookupError, ValueError, TypeError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_hashboards(
|
||||||
|
self, rpc_devs: dict = None, web_ipreport: dict = None
|
||||||
|
) -> List[HashBoard]:
|
||||||
|
hashboards = [
|
||||||
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
|
for i in range(self.expected_hashboards)
|
||||||
|
]
|
||||||
|
|
||||||
|
if rpc_devs is None:
|
||||||
|
try:
|
||||||
|
rpc_devs = await self.rpc.devs()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
if web_ipreport is None:
|
||||||
|
try:
|
||||||
|
web_ipreport = await self.web.ipreport()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_devs is not None:
|
||||||
|
try:
|
||||||
|
for board in rpc_devs["DEVS"]:
|
||||||
|
b_id = board["ID"] - 1
|
||||||
|
hashboards[b_id].hashrate = round(
|
||||||
|
float(float(board["MHS 5s"]) / 1000000), 2
|
||||||
|
)
|
||||||
|
hashboards[b_id].temp = round(float(float(board["Temperature"])), 2)
|
||||||
|
hashboards[b_id].missing = False
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_ipreport is not None:
|
||||||
|
try:
|
||||||
|
for board, sn in enumerate(web_ipreport["IPReport"][0]["HBSerialNo"]):
|
||||||
|
hashboards[board].serial_number = sn
|
||||||
|
hashboards[board].missing = False
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return hashboards
|
||||||
|
|
||||||
|
async def _get_wattage(self, web_psu: dict = None) -> Optional[int]:
|
||||||
|
if web_psu is None:
|
||||||
|
try:
|
||||||
|
web_psu = await self.web.get_psu()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_psu is not None:
|
||||||
|
try:
|
||||||
|
return int(float(web_psu["PSU"][0]["PowerIn"].replace("W", "")))
|
||||||
|
except (LookupError, TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_wattage_limit(
|
||||||
|
self, web_mode: dict = None, web_psu: dict = None
|
||||||
|
) -> Optional[int]:
|
||||||
|
if web_mode is None:
|
||||||
|
try:
|
||||||
|
web_mode = await self.web.get_mode()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_mode is not None:
|
||||||
|
try:
|
||||||
|
return web_mode["Mode"][0]["Power"]
|
||||||
|
except (LookupError, TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_psu is None:
|
||||||
|
try:
|
||||||
|
web_psu = await self.web.get_psu()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_psu is not None:
|
||||||
|
try:
|
||||||
|
return int(float(web_psu["PSU"][0]["PoutMax"].replace("W", "")))
|
||||||
|
except (LookupError, TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_fans(self, web_fan: dict = None) -> List[Fan]:
|
||||||
|
if web_fan is None:
|
||||||
|
try:
|
||||||
|
web_fan = await self.web.get_fan()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
fans = []
|
||||||
|
if web_fan is not None:
|
||||||
|
try:
|
||||||
|
for fan in web_fan["Fan"]:
|
||||||
|
fans.append(Fan(round(fan["Speed"])))
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
return fans
|
||||||
|
|
||||||
|
async def _get_fault_light(self, web_led: dict = None) -> Optional[bool]:
|
||||||
|
if web_led is None:
|
||||||
|
try:
|
||||||
|
web_led = await self.web.get_led()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_led is not None:
|
||||||
|
try:
|
||||||
|
return web_led["LED"][0]["Code"] == int(AuradineLEDCodes.LOCATE_MINER)
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _is_mining(self, web_mode: dict = None) -> Optional[bool]:
|
||||||
|
if web_mode is None:
|
||||||
|
try:
|
||||||
|
web_mode = await self.web.get_mode()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_mode is not None:
|
||||||
|
try:
|
||||||
|
return web_mode["Mode"][0]["Sleep"] == "off"
|
||||||
|
except (LookupError, TypeError, ValueError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
||||||
|
if rpc_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_summary = await self.rpc.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_summary is not None:
|
||||||
|
try:
|
||||||
|
return rpc_summary["SUMMARY"][0]["Elapsed"]
|
||||||
|
except LookupError:
|
||||||
|
pass
|
||||||
@@ -17,66 +17,69 @@
|
|||||||
import re
|
import re
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends import CGMiner
|
from pyasic.miners.backends.cgminer import CGMiner
|
||||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
|
|
||||||
AVALON_DATA_LOC = DataLocations(
|
AVALON_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [RPCAPICommand("api_version", "version")]
|
"_get_mac",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_devs", "devs")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_devs", "devs")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp", [RPCAPICommand("api_stats", "stats")]
|
"_get_env_temp",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit", [RPCAPICommand("api_stats", "stats")]
|
"_get_wattage_limit",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light", [RPCAPICommand("api_stats", "stats")]
|
"_get_fault_light",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction("_get_uptime"),
|
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CGMinerAvalon(CGMiner):
|
class AvalonMiner(CGMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for Avalon Miners"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
|
|
||||||
# data gathering locations
|
data_locations = AVALON_DATA_LOC
|
||||||
self.data_locations = AVALON_DATA_LOC
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.ascset(0, "led", "1-1")
|
data = await self.rpc.ascset(0, "led", "1-1")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||||
@@ -85,7 +88,7 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.ascset(0, "led", "1-0")
|
data = await self.rpc.ascset(0, "led", "1-0")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
if data["STATUS"][0]["Msg"] == "ASC 0 set OK":
|
||||||
@@ -94,7 +97,7 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.restart()
|
data = await self.rpc.restart()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -105,26 +108,6 @@ class CGMinerAvalon(CGMiner):
|
|||||||
return False
|
return False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
pass
|
|
||||||
# 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( # noqa
|
|
||||||
# 0, "setpool", f"root,root,{conf}"
|
|
||||||
# ) # this should work but doesn't
|
|
||||||
# except APIError:
|
|
||||||
# pass
|
|
||||||
# return data
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_stats(stats):
|
def parse_stats(stats):
|
||||||
_stats_items = re.findall(".+?\\[*?]", stats)
|
_stats_items = re.findall(".+?\\[*?]", stats)
|
||||||
@@ -142,9 +125,9 @@ class CGMinerAvalon(CGMiner):
|
|||||||
# --avalon args
|
# --avalon args
|
||||||
for arg_item in data_list:
|
for arg_item in data_list:
|
||||||
item_data = arg_item[0].split(" ")
|
item_data = arg_item[0].split(" ")
|
||||||
for idx in range(len(item_data)):
|
for idx, val in enumerate(item_data):
|
||||||
if idx % 2 == 0 or idx == 0:
|
if idx % 2 == 0 or idx == 0:
|
||||||
data_dict[item_data[idx]] = item_data[idx + 1]
|
data_dict[val] = item_data[idx + 1]
|
||||||
|
|
||||||
raw_data = [data[0].strip(), data_dict]
|
raw_data = [data[0].strip(), data_dict]
|
||||||
else:
|
else:
|
||||||
@@ -172,16 +155,16 @@ class CGMinerAvalon(CGMiner):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, api_version: dict = None) -> Optional[str]:
|
async def _get_mac(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
if not api_version:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
base_mac = api_version["VERSION"][0]["MAC"]
|
base_mac = rpc_version["VERSION"][0]["MAC"]
|
||||||
base_mac = base_mac.upper()
|
base_mac = base_mac.upper()
|
||||||
mac = ":".join(
|
mac = ":".join(
|
||||||
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
[base_mac[i : (i + 2)] for i in range(0, len(base_mac), 2)]
|
||||||
@@ -190,42 +173,34 @@ class CGMinerAvalon(CGMiner):
|
|||||||
except (KeyError, ValueError):
|
except (KeyError, ValueError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hostname(self) -> Optional[str]:
|
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
|
||||||
return None
|
if rpc_devs is None:
|
||||||
# if not mac:
|
|
||||||
# mac = await self.get_mac()
|
|
||||||
#
|
|
||||||
# if mac:
|
|
||||||
# return f"Avalon{mac.replace(':', '')[-6:]}"
|
|
||||||
|
|
||||||
async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
|
||||||
if not api_devs:
|
|
||||||
try:
|
try:
|
||||||
api_devs = await self.api.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_devs:
|
if rpc_devs is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
|
return round(float(rpc_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
|
||||||
except (KeyError, IndexError, ValueError, TypeError):
|
except (KeyError, IndexError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.expected_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
return hashboards
|
return hashboards
|
||||||
@@ -259,65 +234,62 @@ class CGMinerAvalon(CGMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return round(float(parsed_stats["GHSmm"]) / 1000, 2)
|
return round(float(parsed_stats["GHSmm"]) / 1000, 2)
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return float(parsed_stats["Temp"])
|
return float(parsed_stats["Temp"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage(self) -> Optional[int]:
|
async def _get_wattage_limit(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
return None
|
if rpc_stats is None:
|
||||||
|
|
||||||
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
return int(parsed_stats["MPO"])
|
return int(parsed_stats["MPO"])
|
||||||
except (IndexError, KeyError, ValueError, TypeError):
|
except (IndexError, KeyError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
return fans_data
|
return fans_data
|
||||||
@@ -329,21 +301,18 @@ class CGMinerAvalon(CGMiner):
|
|||||||
pass
|
pass
|
||||||
return fans_data
|
return fans_data
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
async def _get_fault_light(self, rpc_stats: dict = None) -> Optional[bool]:
|
||||||
return []
|
|
||||||
|
|
||||||
async def _get_fault_light(self, api_stats: dict = None) -> bool: # noqa
|
|
||||||
if self.light:
|
if self.light:
|
||||||
return self.light
|
return self.light
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||||
parsed_stats = self.parse_stats(unparsed_stats)
|
parsed_stats = self.parse_stats(unparsed_stats)
|
||||||
led = int(parsed_stats["Led"])
|
led = int(parsed_stats["Led"])
|
||||||
return True if led == 1 else False
|
return True if led == 1 else False
|
||||||
@@ -351,7 +320,7 @@ class CGMinerAvalon(CGMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await self.api.ascset(0, "led", "1-255")
|
data = await self.rpc.ascset(0, "led", "1-255")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
try:
|
try:
|
||||||
@@ -360,9 +329,3 @@ class CGMinerAvalon(CGMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_uptime(self) -> Optional[int]:
|
|
||||||
return None
|
|
||||||
@@ -16,50 +16,39 @@
|
|||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic.API.bfgminer import BFGMinerAPI
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
DataFunction,
|
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
RPCAPICommand,
|
|
||||||
)
|
|
||||||
|
|
||||||
BFGMINER_DATA_LOC = DataLocations(
|
BFGMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction("_get_uptime"),
|
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,123 +56,82 @@ BFGMINER_DATA_LOC = DataLocations(
|
|||||||
class BFGMiner(BaseMiner):
|
class BFGMiner(BaseMiner):
|
||||||
"""Base handler for BFGMiner based miners."""
|
"""Base handler for BFGMiner based miners."""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
_rpc_cls = BFGMinerRPCAPI
|
||||||
super().__init__(ip)
|
rpc: BFGMinerRPCAPI
|
||||||
# interfaces
|
|
||||||
self.api = BFGMinerAPI(ip, api_ver)
|
|
||||||
|
|
||||||
# static data
|
data_locations = BFGMINER_DATA_LOC
|
||||||
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:
|
async def get_config(self) -> MinerConfig:
|
||||||
# get pool data
|
# get pool data
|
||||||
try:
|
try:
|
||||||
pools = await self.api.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
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}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
return None
|
if rpc_version is None:
|
||||||
|
|
||||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
|
||||||
if not api_version:
|
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.api_ver = api_version["VERSION"][0]["API"]
|
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
if not api_version:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
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
|
# get hr from API
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
return round(float(rpc_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = []
|
hashboards = []
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
boards = api_stats["STATS"]
|
boards = rpc_stats["STATS"]
|
||||||
if len(boards) > 1:
|
if len(boards) > 1:
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
@@ -225,37 +173,28 @@ class BFGMiner(BaseMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self) -> Optional[float]:
|
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||||
return None
|
if rpc_stats is 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:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans_data = [None, None, None, None]
|
fans_data = [None, None, None, None]
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
|
|
||||||
for fan_num in range(0, 8, 4):
|
for fan_num in range(0, 8, 4):
|
||||||
for _f_num in range(4):
|
for _f_num in range(4):
|
||||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||||
if not f == 0 and fan_offset == -1:
|
if not f == 0 and fan_offset == -1:
|
||||||
fan_offset = fan_num
|
fan_offset = fan_num
|
||||||
if fan_offset == -1:
|
if fan_offset == -1:
|
||||||
fan_offset = 1
|
fan_offset = 1
|
||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans_data[fan] = api_stats["STATS"][1].get(
|
fans_data[fan] = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset+fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -264,25 +203,19 @@ class BFGMiner(BaseMiner):
|
|||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
return []
|
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
@@ -293,9 +226,3 @@ class BFGMiner(BaseMiner):
|
|||||||
return round(expected_rate, 2)
|
return round(expected_rate, 2)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -14,55 +14,45 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic.API.bmminer import BMMinerAPI
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
DataFunction,
|
from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
RPCAPICommand,
|
|
||||||
)
|
|
||||||
|
|
||||||
BMMINER_DATA_LOC = DataLocations(
|
BMMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -70,157 +60,82 @@ BMMINER_DATA_LOC = DataLocations(
|
|||||||
class BMMiner(BaseMiner):
|
class BMMiner(BaseMiner):
|
||||||
"""Base handler for BMMiner based miners."""
|
"""Base handler for BMMiner based miners."""
|
||||||
|
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
_rpc_cls = BMMinerRPCAPI
|
||||||
super().__init__(ip)
|
rpc: BMMinerRPCAPI
|
||||||
# interfaces
|
|
||||||
self.api = BMMinerAPI(ip, api_ver)
|
|
||||||
|
|
||||||
# static data
|
data_locations = BMMINER_DATA_LOC
|
||||||
self.api_type = "BMMiner"
|
|
||||||
# data gathering locations
|
|
||||||
self.data_locations = BMMINER_DATA_LOC
|
|
||||||
|
|
||||||
# data storage
|
|
||||||
self.api_ver = api_ver
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
|
||||||
result = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn = await self._get_ssh_connection()
|
|
||||||
except ConnectionError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# open an ssh connection
|
|
||||||
async with conn:
|
|
||||||
# 3 retries
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
# run the command and get the result
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
result = result.stdout
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# if the command fails, log it
|
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
|
||||||
|
|
||||||
# on the 3rd retry, return None
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
# return the result, either command output or None
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# get pool data
|
# get pool data
|
||||||
try:
|
try:
|
||||||
pools = await self.api.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
|
||||||
ret = await self.send_ssh_command("reboot")
|
|
||||||
logging.debug(f"{self}: Reboot command completed.")
|
|
||||||
if ret is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
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}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
return None
|
if rpc_version is None:
|
||||||
|
|
||||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
|
||||||
if not api_version:
|
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.api_ver = api_version["VERSION"][0]["API"]
|
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
if not api_version:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
self.fw_ver = rpc_version["VERSION"][0]["CompileTime"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_fan_psu(self):
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_hostname(self) -> Optional[str]:
|
|
||||||
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
|
||||||
return hn
|
|
||||||
|
|
||||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
|
||||||
# get hr from API
|
# get hr from API
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
return round(float(rpc_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = []
|
hashboards = []
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
boards = api_stats["STATS"]
|
boards = rpc_stats["STATS"]
|
||||||
if len(boards) > 1:
|
if len(boards) > 1:
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
@@ -275,37 +190,28 @@ class BMMiner(BaseMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self) -> Optional[float]:
|
async def _get_fans(self, rpc_stats: dict = None) -> List[Fan]:
|
||||||
return None
|
if rpc_stats is 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:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans = [Fan() for _ in range(self.expected_fans)]
|
fans = [Fan() for _ in range(self.expected_fans)]
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
fan_offset = -1
|
fan_offset = -1
|
||||||
|
|
||||||
for fan_num in range(1, 8, 4):
|
for fan_num in range(1, 8, 4):
|
||||||
for _f_num in range(4):
|
for _f_num in range(4):
|
||||||
f = api_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
f = rpc_stats["STATS"][1].get(f"fan{fan_num + _f_num}", 0)
|
||||||
if f and not f == 0 and fan_offset == -1:
|
if f and not f == 0 and fan_offset == -1:
|
||||||
fan_offset = fan_num
|
fan_offset = fan_num
|
||||||
if fan_offset == -1:
|
if fan_offset == -1:
|
||||||
fan_offset = 1
|
fan_offset = 1
|
||||||
|
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
fans[fan].speed = api_stats["STATS"][1].get(
|
fans[fan].speed = rpc_stats["STATS"][1].get(
|
||||||
f"fan{fan_offset+fan}", 0
|
f"fan{fan_offset+fan}", 0
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -313,25 +219,19 @@ class BMMiner(BaseMiner):
|
|||||||
|
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
return []
|
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
|
||||||
# X19 method, not sure compatibility
|
# X19 method, not sure compatibility
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
@@ -343,18 +243,15 @@ class BMMiner(BaseMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
return None
|
if rpc_stats is None:
|
||||||
|
|
||||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -17,114 +17,112 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic.API.btminer import BTMinerAPI
|
|
||||||
from pyasic.config import MinerConfig, MiningModeConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
DataFunction,
|
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
RPCAPICommand,
|
|
||||||
)
|
|
||||||
|
|
||||||
BTMINER_DATA_LOC = DataLocations(
|
BTMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac",
|
"_get_mac",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
RPCAPICommand("api_get_miner_info", "get_miner_info"),
|
RPCAPICommand("rpc_get_miner_info", "get_miner_info"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_get_version", "get_version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_get_version", "get_version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver",
|
"_get_fw_ver",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_get_version", "get_version"),
|
RPCAPICommand("rpc_get_version", "get_version"),
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")]
|
"_get_hostname",
|
||||||
|
[RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_devs", "devs")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_devs", "devs")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||||
"_get_env_temp", [RPCAPICommand("api_summary", "summary")]
|
"_get_env_temp",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage", [RPCAPICommand("api_summary", "summary")]
|
"_get_wattage",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit", [RPCAPICommand("api_summary", "summary")]
|
"_get_wattage_limit",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans",
|
"_get_fans",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
RPCAPICommand("api_get_psu", "get_psu"),
|
RPCAPICommand("rpc_get_psu", "get_psu"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction(
|
str(DataOptions.FAN_PSU): DataFunction(
|
||||||
"_get_fan_psu",
|
"_get_fan_psu",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
RPCAPICommand("api_get_psu", "get_psu"),
|
RPCAPICommand("rpc_get_psu", "get_psu"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.ERRORS): DataFunction(
|
str(DataOptions.ERRORS): DataFunction(
|
||||||
"_get_errors",
|
"_get_errors",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_get_error_code", "get_error_code"),
|
RPCAPICommand("rpc_get_error_code", "get_error_code"),
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light",
|
"_get_fault_light",
|
||||||
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
|
[RPCAPICommand("rpc_get_miner_info", "get_miner_info")],
|
||||||
),
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction(
|
str(DataOptions.IS_MINING): DataFunction(
|
||||||
"_is_mining", [RPCAPICommand("api_status", "status")]
|
"_is_mining",
|
||||||
|
[RPCAPICommand("rpc_status", "status")],
|
||||||
),
|
),
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_summary", "summary")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BTMiner(BaseMiner):
|
class BTMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Base handler for BTMiner based miners."""
|
||||||
super().__init__(ip)
|
|
||||||
# interfaces
|
|
||||||
self.api = BTMinerAPI(ip, api_ver)
|
|
||||||
|
|
||||||
# static data
|
_rpc_cls = BTMinerRPCAPI
|
||||||
self.api_type = "BTMiner"
|
rpc: BTMinerRPCAPI
|
||||||
# data gathering locations
|
|
||||||
self.data_locations = BTMINER_DATA_LOC
|
|
||||||
# autotuning/shutdown support
|
|
||||||
self.supports_shutdown = True
|
|
||||||
|
|
||||||
# data storage
|
data_locations = BTMINER_DATA_LOC
|
||||||
self.api_ver = api_ver
|
|
||||||
|
|
||||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
supports_shutdown = True
|
||||||
|
|
||||||
|
async def _reset_rpc_pwd_to_admin(self, pwd: str):
|
||||||
try:
|
try:
|
||||||
data = await self.api.update_pwd(pwd, "admin")
|
data = await self.rpc.update_pwd(pwd, "admin")
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data:
|
if data:
|
||||||
@@ -135,7 +133,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.set_led(auto=True)
|
data = await self.rpc.set_led(auto=True)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data:
|
if data:
|
||||||
@@ -147,8 +145,8 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.set_led(auto=False)
|
data = await self.rpc.set_led(auto=False)
|
||||||
await self.api.set_led(
|
await self.rpc.set_led(
|
||||||
auto=False, color="green", start=0, period=1, duration=0
|
auto=False, color="green", start=0, period=1, duration=0
|
||||||
)
|
)
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -162,7 +160,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.reboot()
|
data = await self.rpc.reboot()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
@@ -172,7 +170,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.restart()
|
data = await self.rpc.restart()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
@@ -182,7 +180,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.power_off(respbefore=True)
|
data = await self.rpc.power_off(respbefore=True)
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
@@ -192,7 +190,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
try:
|
try:
|
||||||
data = await self.api.power_on()
|
data = await self.rpc.power_on()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
if data.get("Msg"):
|
if data.get("Msg"):
|
||||||
@@ -207,16 +205,16 @@ class BTMiner(BaseMiner):
|
|||||||
pools_conf = conf["pools"]
|
pools_conf = conf["pools"]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.api.update_pools(**pools_conf)
|
await self.rpc.update_pools(**pools_conf)
|
||||||
|
|
||||||
if conf["mode"] == "normal":
|
if conf["mode"] == "normal":
|
||||||
await self.api.set_normal_power()
|
await self.rpc.set_normal_power()
|
||||||
elif conf["mode"] == "high":
|
elif conf["mode"] == "high":
|
||||||
await self.api.set_high_power()
|
await self.rpc.set_high_power()
|
||||||
elif conf["mode"] == "low":
|
elif conf["mode"] == "low":
|
||||||
await self.api.set_low_power()
|
await self.rpc.set_low_power()
|
||||||
elif conf["mode"] == "power_tuning":
|
elif conf["mode"] == "power_tuning":
|
||||||
await self.api.adjust_power_limit(conf["power_tuning"]["wattage"])
|
await self.rpc.adjust_power_limit(conf["power_tuning"]["wattage"])
|
||||||
except APIError:
|
except APIError:
|
||||||
# cannot update, no API access usually
|
# cannot update, no API access usually
|
||||||
pass
|
pass
|
||||||
@@ -226,7 +224,7 @@ class BTMiner(BaseMiner):
|
|||||||
summary = None
|
summary = None
|
||||||
status = None
|
status = None
|
||||||
try:
|
try:
|
||||||
data = await self.api.multicommand("pools", "summary", "status")
|
data = await self.rpc.multicommand("pools", "summary", "status")
|
||||||
pools = data["pools"][0]
|
pools = data["pools"][0]
|
||||||
summary = data["summary"][0]
|
summary = data["summary"][0]
|
||||||
status = data["status"][0]
|
status = data["status"][0]
|
||||||
@@ -273,7 +271,7 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
try:
|
try:
|
||||||
await self.api.adjust_power_limit(wattage)
|
await self.rpc.adjust_power_limit(wattage)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.warning(f"{self} set_power_limit: {e}")
|
logging.warning(f"{self} set_power_limit: {e}")
|
||||||
return False
|
return False
|
||||||
@@ -285,85 +283,85 @@ class BTMiner(BaseMiner):
|
|||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(
|
async def _get_mac(
|
||||||
self, api_summary: dict = None, api_get_miner_info: dict = None
|
self, rpc_summary: dict = None, rpc_get_miner_info: dict = None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
if not api_get_miner_info:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
api_get_miner_info = await self.api.get_miner_info()
|
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_get_miner_info:
|
if rpc_get_miner_info is not None:
|
||||||
try:
|
try:
|
||||||
mac = api_get_miner_info["Msg"]["mac"]
|
mac = rpc_get_miner_info["Msg"]["mac"]
|
||||||
return str(mac).upper()
|
return str(mac).upper()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
mac = api_summary["SUMMARY"][0]["MAC"]
|
mac = rpc_summary["SUMMARY"][0]["MAC"]
|
||||||
return str(mac).upper()
|
return str(mac).upper()
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
|
async def _get_api_ver(self, rpc_get_version: dict = None) -> Optional[str]:
|
||||||
if not api_get_version:
|
if rpc_get_version is None:
|
||||||
try:
|
try:
|
||||||
api_get_version = await self.api.get_version()
|
rpc_get_version = await self.rpc.get_version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_get_version:
|
if rpc_get_version is not None:
|
||||||
if "Code" in api_get_version.keys():
|
if "Code" in rpc_get_version.keys():
|
||||||
if api_get_version["Code"] == 131:
|
if rpc_get_version["Code"] == 131:
|
||||||
try:
|
try:
|
||||||
api_ver = api_get_version["Msg"]
|
rpc_ver = rpc_get_version["Msg"]
|
||||||
if not isinstance(api_ver, str):
|
if not isinstance(rpc_ver, str):
|
||||||
api_ver = api_ver["api_ver"]
|
rpc_ver = rpc_ver["rpc_ver"]
|
||||||
self.api_ver = api_ver.replace("whatsminer v", "")
|
self.api_ver = rpc_ver.replace("whatsminer v", "")
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.api.api_ver = self.api_ver
|
self.rpc.rpc_ver = self.api_ver
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(
|
async def _get_fw_ver(
|
||||||
self, api_get_version: dict = None, api_summary: dict = None
|
self, rpc_get_version: dict = None, rpc_summary: dict = None
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
if not api_get_version:
|
if rpc_get_version is None:
|
||||||
try:
|
try:
|
||||||
api_get_version = await self.api.get_version()
|
rpc_get_version = await self.rpc.get_version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_get_version:
|
if rpc_get_version is not None:
|
||||||
if "Code" in api_get_version.keys():
|
if "Code" in rpc_get_version.keys():
|
||||||
if api_get_version["Code"] == 131:
|
if rpc_get_version["Code"] == 131:
|
||||||
try:
|
try:
|
||||||
self.fw_ver = api_get_version["Msg"]["fw_ver"]
|
self.fw_ver = rpc_get_version["Msg"]["fw_ver"]
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary:
|
||||||
try:
|
try:
|
||||||
self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace(
|
self.fw_ver = rpc_summary["SUMMARY"][0]["Firmware Version"].replace(
|
||||||
"'", ""
|
"'", ""
|
||||||
)
|
)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
@@ -371,51 +369,50 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
|
async def _get_hostname(self, rpc_get_miner_info: dict = None) -> Optional[str]:
|
||||||
hostname = None
|
hostname = None
|
||||||
if not api_get_miner_info:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
api_get_miner_info = await self.api.get_miner_info()
|
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None # only one way to get this
|
return None # only one way to get this
|
||||||
|
|
||||||
if api_get_miner_info:
|
if rpc_get_miner_info is not None:
|
||||||
try:
|
try:
|
||||||
hostname = api_get_miner_info["Msg"]["hostname"]
|
hostname = rpc_get_miner_info["Msg"]["hostname"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return hostname
|
return hostname
|
||||||
|
|
||||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
# get hr from API
|
if rpc_summary is None:
|
||||||
if not api_summary:
|
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_devs: dict = None) -> List[HashBoard]:
|
||||||
hashboards = [
|
hashboards = [
|
||||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
for i in range(self.expected_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_devs:
|
if rpc_devs is None:
|
||||||
try:
|
try:
|
||||||
api_devs = await self.api.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_devs:
|
if rpc_devs is not None:
|
||||||
try:
|
try:
|
||||||
for board in api_devs["DEVS"]:
|
for board in rpc_devs["DEVS"]:
|
||||||
if len(hashboards) < board["ASC"] + 1:
|
if len(hashboards) < board["ASC"] + 1:
|
||||||
hashboards.append(
|
hashboards.append(
|
||||||
HashBoard(
|
HashBoard(
|
||||||
@@ -436,62 +433,62 @@ class BTMiner(BaseMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]:
|
async def _get_env_temp(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return api_summary["SUMMARY"][0]["Env Temp"]
|
return rpc_summary["SUMMARY"][0]["Env Temp"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]:
|
async def _get_wattage(self, rpc_summary: dict = None) -> Optional[int]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
wattage = api_summary["SUMMARY"][0]["Power"]
|
wattage = rpc_summary["SUMMARY"][0]["Power"]
|
||||||
return wattage if not wattage == -1 else None
|
return wattage if not wattage == -1 else None
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, rpc_summary: dict = None) -> Optional[int]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return api_summary["SUMMARY"][0]["Power Limit"]
|
return rpc_summary["SUMMARY"][0]["Power Limit"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fans(
|
async def _get_fans(
|
||||||
self, api_summary: dict = None, api_get_psu: dict = None
|
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
||||||
) -> List[Fan]:
|
) -> List[Fan]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans = [Fan() for _ in range(self.expected_fans)]
|
fans = [Fan() for _ in range(self.expected_fans)]
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
if self.expected_fans > 0:
|
if self.expected_fans > 0:
|
||||||
fans = [
|
fans = [
|
||||||
Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
||||||
Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
||||||
]
|
]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
@@ -499,92 +496,95 @@ class BTMiner(BaseMiner):
|
|||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_fan_psu(
|
async def _get_fan_psu(
|
||||||
self, api_summary: dict = None, api_get_psu: dict = None
|
self, rpc_summary: dict = None, rpc_get_psu: dict = None
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
|
return int(rpc_summary["SUMMARY"][0]["Power Fanspeed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not api_get_psu:
|
if rpc_get_psu is None:
|
||||||
try:
|
try:
|
||||||
api_get_psu = await self.api.get_psu()
|
rpc_get_psu = await self.rpc.get_psu()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_get_psu:
|
if rpc_get_psu is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_get_psu["Msg"]["fan_speed"])
|
return int(rpc_get_psu["Msg"]["fan_speed"])
|
||||||
except (KeyError, TypeError):
|
except (KeyError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_errors(
|
async def _get_errors(
|
||||||
self, api_summary: dict = None, api_get_error_code: dict = None
|
self, rpc_summary: dict = None, rpc_get_error_code: dict = None
|
||||||
) -> List[MinerErrorData]:
|
) -> List[MinerErrorData]:
|
||||||
errors = []
|
errors = []
|
||||||
if not api_get_error_code and not api_summary:
|
if rpc_get_error_code is None and rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_get_error_code = await self.api.get_error_code()
|
rpc_get_error_code = await self.rpc.get_error_code()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_get_error_code:
|
if rpc_get_error_code is not None:
|
||||||
for err in api_get_error_code["Msg"]["error_code"]:
|
|
||||||
if isinstance(err, dict):
|
|
||||||
for code in err:
|
|
||||||
errors.append(WhatsminerError(error_code=int(code)))
|
|
||||||
else:
|
|
||||||
errors.append(WhatsminerError(error_code=int(err)))
|
|
||||||
|
|
||||||
if not api_summary:
|
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
for err in rpc_get_error_code["Msg"]["error_code"]:
|
||||||
|
if isinstance(err, dict):
|
||||||
|
for code in err:
|
||||||
|
errors.append(WhatsminerError(error_code=int(code)))
|
||||||
|
else:
|
||||||
|
errors.append(WhatsminerError(error_code=int(err)))
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if rpc_summary is None:
|
||||||
|
try:
|
||||||
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
|
for i in range(rpc_summary["SUMMARY"][0]["Error Code Count"]):
|
||||||
err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
|
err = rpc_summary["SUMMARY"][0].get(f"Error Code {i}")
|
||||||
if err:
|
if err:
|
||||||
errors.append(WhatsminerError(error_code=err))
|
errors.append(WhatsminerError(error_code=err))
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_summary: dict = None):
|
async def _get_expected_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
|
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
|
||||||
if expected_hashrate:
|
if expected_hashrate:
|
||||||
return round(expected_hashrate / 1000, 2)
|
return round(expected_hashrate / 1000, 2)
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool:
|
async def _get_fault_light(self, rpc_get_miner_info: dict = None) -> Optional[bool]:
|
||||||
if not api_get_miner_info:
|
if rpc_get_miner_info is None:
|
||||||
try:
|
try:
|
||||||
api_get_miner_info = await self.api.get_miner_info()
|
rpc_get_miner_info = await self.rpc.get_miner_info()
|
||||||
except APIError:
|
except APIError:
|
||||||
if not self.light:
|
if not self.light:
|
||||||
self.light = False
|
self.light = False
|
||||||
|
|
||||||
if api_get_miner_info:
|
if rpc_get_miner_info is not None:
|
||||||
try:
|
try:
|
||||||
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto")
|
self.light = not (rpc_get_miner_info["Msg"]["ledstat"] == "auto")
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -600,46 +600,46 @@ class BTMiner(BaseMiner):
|
|||||||
):
|
):
|
||||||
if not hostname:
|
if not hostname:
|
||||||
hostname = await self.get_hostname()
|
hostname = await self.get_hostname()
|
||||||
await self.api.net_config(
|
await self.rpc.net_config(
|
||||||
ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
|
ip=ip, mask=subnet_mask, dns=dns, gate=gateway, host=hostname, dhcp=False
|
||||||
)
|
)
|
||||||
|
|
||||||
async def set_dhcp(self, hostname: str = None):
|
async def set_dhcp(self, hostname: str = None):
|
||||||
if hostname:
|
if hostname:
|
||||||
await self.set_hostname(hostname)
|
await self.set_hostname(hostname)
|
||||||
await self.api.net_config()
|
await self.rpc.net_config()
|
||||||
|
|
||||||
async def set_hostname(self, hostname: str):
|
async def set_hostname(self, hostname: str):
|
||||||
await self.api.set_hostname(hostname)
|
await self.rpc.set_hostname(hostname)
|
||||||
|
|
||||||
async def _is_mining(self, api_status: dict = None) -> Optional[bool]:
|
async def _is_mining(self, rpc_status: dict = None) -> Optional[bool]:
|
||||||
if not api_status:
|
if rpc_status is None:
|
||||||
try:
|
try:
|
||||||
api_status = await self.api.status()
|
rpc_status = await self.rpc.status()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_status:
|
if rpc_status is not None:
|
||||||
try:
|
try:
|
||||||
if api_status["Msg"].get("btmineroff"):
|
if rpc_status["Msg"].get("btmineroff"):
|
||||||
try:
|
try:
|
||||||
await self.api.devdetails()
|
await self.rpc.devdetails()
|
||||||
except APIError:
|
except APIError:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
return True if api_status["Msg"]["mineroff"] == "false" else False
|
return True if rpc_status["Msg"]["mineroff"] == "false" else False
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, rpc_summary: dict = None) -> Optional[int]:
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
return int(rpc_summary["SUMMARY"][0]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -14,360 +14,124 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
import logging
|
from typing import Optional
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.API.cgminer import CGMinerAPI
|
|
||||||
from pyasic.config import MinerConfig
|
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.errors import APIError
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
DataFunction,
|
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
RPCAPICommand,
|
|
||||||
)
|
|
||||||
|
|
||||||
CGMINER_DATA_LOC = DataLocations(
|
CGMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class CGMiner(BaseMiner):
|
class CGMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Base handler for CGMiner based miners"""
|
||||||
super().__init__(ip)
|
|
||||||
# interfaces
|
|
||||||
self.api = CGMinerAPI(ip, api_ver)
|
|
||||||
|
|
||||||
# static data
|
_rpc_cls = CGMinerRPCAPI
|
||||||
self.api_type = "CGMiner"
|
rpc: CGMinerRPCAPI
|
||||||
# data gathering locations
|
|
||||||
self.data_locations = CGMINER_DATA_LOC
|
|
||||||
|
|
||||||
# data storage
|
data_locations = CGMINER_DATA_LOC
|
||||||
self.api_ver = api_ver
|
|
||||||
|
|
||||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
|
||||||
result = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
conn = await self._get_ssh_connection()
|
|
||||||
except ConnectionError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
# open an ssh connection
|
|
||||||
async with conn:
|
|
||||||
# 3 retries
|
|
||||||
for i in range(3):
|
|
||||||
try:
|
|
||||||
# run the command and get the result
|
|
||||||
result = await conn.run(cmd)
|
|
||||||
result = result.stdout
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# if the command fails, log it
|
|
||||||
logging.warning(f"{self} command {cmd} error: {e}")
|
|
||||||
|
|
||||||
# on the 3rd retry, return None
|
|
||||||
if i == 3:
|
|
||||||
return
|
|
||||||
continue
|
|
||||||
# return the result, either command output or None
|
|
||||||
return result
|
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
|
||||||
return await self.restart_cgminer()
|
|
||||||
|
|
||||||
async def restart_cgminer(self) -> bool:
|
|
||||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
ret = await self.send_ssh_command(commands)
|
|
||||||
if ret is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def reboot(self) -> bool:
|
|
||||||
logging.debug(f"{self}: Sending reboot command.")
|
|
||||||
ret = await self.send_ssh_command("reboot")
|
|
||||||
if ret is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
commands = [
|
|
||||||
"mkdir -p /etc/tmp/",
|
|
||||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
|
||||||
"crontab -u root /etc/tmp/root",
|
|
||||||
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
|
|
||||||
]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
ret = await self.send_ssh_command(commands)
|
|
||||||
if ret is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
commands = [
|
|
||||||
"mkdir -p /etc/tmp/",
|
|
||||||
'echo "" > /etc/tmp/root',
|
|
||||||
"crontab -u root /etc/tmp/root",
|
|
||||||
"killall cgminer",
|
|
||||||
]
|
|
||||||
commands = ";".join(commands)
|
|
||||||
ret = await self.send_ssh_command(commands)
|
|
||||||
if ret is None:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# get pool data
|
# get pool data
|
||||||
try:
|
try:
|
||||||
pools = await self.api.pools()
|
pools = await self.rpc.pools()
|
||||||
except APIError:
|
except APIError:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
self.config = MinerConfig.from_api(pools)
|
self.config = MinerConfig.from_api(pools)
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self) -> Optional[str]:
|
async def _get_api_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
return None
|
if rpc_version is None:
|
||||||
|
|
||||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
|
||||||
if not api_version:
|
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.api_ver = api_version["VERSION"][0]["API"]
|
self.api_ver = rpc_version["VERSION"][0]["API"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.api_ver
|
return self.api_ver
|
||||||
|
|
||||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, rpc_version: dict = None) -> Optional[str]:
|
||||||
if not api_version:
|
if rpc_version is None:
|
||||||
try:
|
try:
|
||||||
api_version = await self.api.version()
|
rpc_version = await self.rpc.version()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_version:
|
if rpc_version is not None:
|
||||||
try:
|
try:
|
||||||
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
|
self.fw_ver = rpc_version["VERSION"][0]["CGMiner"]
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
return self.fw_ver
|
return self.fw_ver
|
||||||
|
|
||||||
async def _get_hostname(self) -> Optional[str]:
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
if rpc_summary is None:
|
||||||
return hn
|
|
||||||
|
|
||||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
|
||||||
# get hr from API
|
|
||||||
if not api_summary:
|
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(
|
return round(
|
||||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
float(float(rpc_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||||
)
|
)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
hashboards = []
|
if rpc_stats is None:
|
||||||
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
board_offset = -1
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
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.expected_hashboards
|
|
||||||
):
|
|
||||||
hashboard = HashBoard(
|
|
||||||
slot=i - board_offset, expected_chips=self.expected_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 (LookupError, 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 = [Fan() for _ in range(self.expected_fans)]
|
|
||||||
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
|
|
||||||
if fan_offset == -1:
|
|
||||||
fan_offset = 1
|
|
||||||
|
|
||||||
for fan in range(self.expected_fans):
|
|
||||||
fans[fan].speed = api_stats["STATS"][1].get(
|
|
||||||
f"fan{fan_offset+fan}", 0
|
|
||||||
)
|
|
||||||
except LookupError:
|
|
||||||
pass
|
|
||||||
return fans
|
|
||||||
|
|
||||||
async def _get_fan_psu(self) -> Optional[int]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
|
||||||
return []
|
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _get_expected_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:
|
|
||||||
expected_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(expected_rate / 1000, 2)
|
|
||||||
if rate_unit == "MH":
|
|
||||||
return round(expected_rate / 1000000, 2)
|
|
||||||
else:
|
|
||||||
return round(expected_rate, 2)
|
|
||||||
except LookupError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
|
||||||
api_stats = await self.api.stats()
|
|
||||||
except APIError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
if api_stats:
|
|
||||||
try:
|
|
||||||
return int(api_stats["STATS"][1]["Elapsed"])
|
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -21,32 +21,31 @@ from pyasic.data import Fan, HashBoard
|
|||||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||||
DataFunction,
|
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
WebAPICommand,
|
|
||||||
)
|
|
||||||
from pyasic.web.epic import ePICWebAPI
|
from pyasic.web.epic import ePICWebAPI
|
||||||
|
|
||||||
EPIC_DATA_LOC = DataLocations(
|
EPIC_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [WebAPICommand("web_network", "network")]
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_network", "network")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction("_get_api_ver"),
|
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [WebAPICommand("web_summary", "summary")]
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [WebAPICommand("web_summary", "summary")]
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [WebAPICommand("web_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [WebAPICommand("web_summary", "summary")]
|
"_get_expected_hashrate",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
@@ -55,41 +54,41 @@ EPIC_DATA_LOC = DataLocations(
|
|||||||
WebAPICommand("web_hashrate", "hashrate"),
|
WebAPICommand("web_hashrate", "hashrate"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage", [WebAPICommand("web_summary", "summary")]
|
"_get_wattage",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [WebAPICommand("web_summary", "summary")]
|
"_get_fans",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction(
|
str(DataOptions.ERRORS): DataFunction(
|
||||||
"_get_errors", [WebAPICommand("web_summary", "summary")]
|
"_get_errors",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||||
"_get_fault_light", [WebAPICommand("web_summary", "summary")]
|
"_get_fault_light",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [WebAPICommand("web_summary", "summary")]
|
"_get_uptime",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ePIC(BaseMiner):
|
class ePIC(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for miners with the ePIC board"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = ePICWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = ePICWebAPI
|
||||||
self.api_type = "ePIC"
|
web: ePICWebAPI
|
||||||
self.fw_str = "ePIC"
|
|
||||||
# data gathering locations
|
firmware = "ePIC"
|
||||||
self.data_locations = EPIC_DATA_LOC
|
|
||||||
|
data_locations = EPIC_DATA_LOC
|
||||||
|
|
||||||
|
supports_shutdown = True
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
summary = None
|
summary = None
|
||||||
@@ -108,6 +107,31 @@ class ePIC(BaseMiner):
|
|||||||
self.config = cfg
|
self.config = cfg
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
|
self.config = config
|
||||||
|
conf = self.config.as_epic(user_suffix=user_suffix)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Temps
|
||||||
|
if not conf.get("temps", {}) == {}:
|
||||||
|
await self.web.set_shutdown_temp(conf["temps"]["shutdown"])
|
||||||
|
# Fans
|
||||||
|
# set with sub-keys instead of conf["fans"] because sometimes both can be set
|
||||||
|
if not conf["fans"].get("Manual", {}) == {}:
|
||||||
|
await self.web.set_fan({"Manual": conf["fans"]["Manual"]})
|
||||||
|
elif not conf["fans"].get("Auto", {}) == {}:
|
||||||
|
await self.web.set_fan({"Auto": conf["fans"]["Auto"]})
|
||||||
|
|
||||||
|
# Mining Mode -- Need to handle that you may not be able to change while miner is tuning
|
||||||
|
if conf["ptune"].get("enabled", True):
|
||||||
|
await self.web.set_ptune_enable(True)
|
||||||
|
await self.web.set_ptune_algo(**conf["ptune"])
|
||||||
|
|
||||||
|
## Pools
|
||||||
|
await self.web.set_pools(conf["pools"])
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
data = await self.web.restart_epic()
|
data = await self.web.restart_epic()
|
||||||
if data:
|
if data:
|
||||||
@@ -144,10 +168,14 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_mac(self, web_network: dict = None) -> str:
|
async def _get_mac(self, web_network: dict = None) -> Optional[str]:
|
||||||
if not web_network:
|
if web_network is None:
|
||||||
web_network = await self.web.network()
|
try:
|
||||||
if web_network:
|
web_network = await self.web.network()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_network is not None:
|
||||||
try:
|
try:
|
||||||
for network in web_network:
|
for network in web_network:
|
||||||
mac = web_network[network]["mac_address"]
|
mac = web_network[network]["mac_address"]
|
||||||
@@ -155,10 +183,14 @@ class ePIC(BaseMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hostname(self, web_summary: dict = None) -> str:
|
async def _get_hostname(self, web_summary: dict = None) -> Optional[str]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
if web_summary:
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
hostname = web_summary["Hostname"]
|
hostname = web_summary["Hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
@@ -166,10 +198,13 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
wattage = web_summary["Power Supply Stats"]["Input Power"]
|
wattage = web_summary["Power Supply Stats"]["Input Power"]
|
||||||
wattage = round(wattage)
|
wattage = round(wattage)
|
||||||
@@ -178,14 +213,13 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||||
# get hr from API
|
if web_summary is None:
|
||||||
if not web_summary:
|
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
hashrate = 0
|
hashrate = 0
|
||||||
if web_summary["HBs"] is not None:
|
if web_summary["HBs"] is not None:
|
||||||
@@ -196,14 +230,13 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||||
# get hr from API
|
if web_summary is None:
|
||||||
if not web_summary:
|
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
hashrate = 0
|
hashrate = 0
|
||||||
if web_summary.get("HBs") is not None:
|
if web_summary.get("HBs") is not None:
|
||||||
@@ -219,10 +252,13 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
fw_ver = web_summary["Software"]
|
fw_ver = web_summary["Software"]
|
||||||
fw_ver = fw_ver.split(" ")[1].replace("v", "")
|
fw_ver = fw_ver.split(" ")[1].replace("v", "")
|
||||||
@@ -231,7 +267,7 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
|
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -239,7 +275,7 @@ class ePIC(BaseMiner):
|
|||||||
|
|
||||||
fans = []
|
fans = []
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
for fan in web_summary["Fans Rpm"]:
|
for fan in web_summary["Fans Rpm"]:
|
||||||
try:
|
try:
|
||||||
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
||||||
@@ -250,12 +286,13 @@ class ePIC(BaseMiner):
|
|||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, web_summary: dict = None, web_hashrate: dict = None
|
self, web_summary: dict = None, web_hashrate: dict = None
|
||||||
) -> List[HashBoard]:
|
) -> List[HashBoard]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
try:
|
try:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
if not web_hashrate:
|
|
||||||
|
if web_hashrate is not None:
|
||||||
try:
|
try:
|
||||||
web_hashrate = await self.web.hashrate()
|
web_hashrate = await self.web.hashrate()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -283,9 +320,13 @@ class ePIC(BaseMiner):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
if web_summary:
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
uptime = web_summary["Session"]["Uptime"]
|
uptime = web_summary["Session"]["Uptime"]
|
||||||
return uptime
|
return uptime
|
||||||
@@ -293,10 +334,14 @@ class ePIC(BaseMiner):
|
|||||||
pass
|
pass
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def _get_fault_light(self, web_summary: dict = None) -> bool:
|
async def _get_fault_light(self, web_summary: dict = None) -> Optional[bool]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
if web_summary:
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
light = web_summary["Misc"]["Locate Miner State"]
|
light = web_summary["Misc"]["Locate Miner State"]
|
||||||
return light
|
return light
|
||||||
@@ -306,9 +351,13 @@ class ePIC(BaseMiner):
|
|||||||
|
|
||||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||||
if not web_summary:
|
if not web_summary:
|
||||||
web_summary = await self.web.summary()
|
try:
|
||||||
|
web_summary = await self.web.summary()
|
||||||
|
except APIError:
|
||||||
|
pass
|
||||||
|
|
||||||
errors = []
|
errors = []
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
error = web_summary["Status"]["Last Error"]
|
error = web_summary["Status"]["Last Error"]
|
||||||
if error is not None:
|
if error is not None:
|
||||||
@@ -317,27 +366,3 @@ class ePIC(BaseMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def _get_api_ver(self, *args, **kwargs) -> Optional[str]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_env_temp(self, *args, **kwargs) -> Optional[float]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|||||||
@@ -13,14 +13,14 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List, Optional
|
from typing import List
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig, MiningModeConfig
|
||||||
from pyasic.data import HashBoard
|
from pyasic.data import HashBoard
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
from pyasic.miners.backends import BFGMiner
|
from pyasic.miners.backends import BFGMiner
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
DataOptions,
|
DataOptions,
|
||||||
@@ -32,53 +32,49 @@ from pyasic.web.goldshell import GoldshellWebAPI
|
|||||||
GOLDSHELL_DATA_LOC = DataLocations(
|
GOLDSHELL_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [WebAPICommand("web_setting", "setting")]
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_setting", "setting")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [WebAPICommand("web_status", "status")]
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_status", "status")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_devs", "devs"),
|
RPCAPICommand("rpc_devs", "devs"),
|
||||||
RPCAPICommand("api_devdetails", "devdetails"),
|
RPCAPICommand("rpc_devdetails", "devdetails"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction("_get_uptime"),
|
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BFGMinerGoldshell(BFGMiner):
|
class GoldshellMiner(BFGMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for goldshell miners"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = GoldshellWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = GoldshellWebAPI
|
||||||
# data gathering locations
|
web: GoldshellWebAPI
|
||||||
self.data_locations = GOLDSHELL_DATA_LOC
|
|
||||||
|
data_locations = GOLDSHELL_DATA_LOC
|
||||||
|
|
||||||
|
supports_shutdown = True
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# get pool data
|
# get pool data
|
||||||
@@ -102,45 +98,51 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self.config = config
|
self.config = config
|
||||||
|
cfg = config.as_goldshell(user_suffix=user_suffix)
|
||||||
# send them back 1 at a time
|
# send them back 1 at a time
|
||||||
for pool in config.as_goldshell(user_suffix=user_suffix)["pools"]:
|
for pool in cfg["pools"]:
|
||||||
await self.web.newpool(
|
await self.web.newpool(
|
||||||
url=pool["url"], user=pool["user"], password=pool["pass"]
|
url=pool["url"], user=pool["user"], password=pool["pass"]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
settings = await self.web.setting()
|
||||||
|
for idx, plan in settings["powerplans"]:
|
||||||
|
if plan["level"] == cfg["settings"]["level"]:
|
||||||
|
settings["select"] = idx
|
||||||
|
await self.web.set_setting(settings)
|
||||||
|
|
||||||
async def _get_mac(self, web_setting: dict = None) -> str:
|
async def _get_mac(self, web_setting: dict = None) -> str:
|
||||||
if not web_setting:
|
if web_setting is None:
|
||||||
try:
|
try:
|
||||||
web_setting = await self.web.setting()
|
web_setting = await self.web.setting()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_setting:
|
if web_setting is not None:
|
||||||
try:
|
try:
|
||||||
return web_setting["name"]
|
return web_setting["name"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_status: dict = None) -> str:
|
async def _get_fw_ver(self, web_status: dict = None) -> str:
|
||||||
if not web_status:
|
if web_status is None:
|
||||||
try:
|
try:
|
||||||
web_status = await self.web.setting()
|
web_status = await self.web.setting()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_status:
|
if web_status is not None:
|
||||||
try:
|
try:
|
||||||
return web_status["firmware"]
|
return web_status["firmware"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, api_devs: dict = None, api_devdetails: dict = None
|
self, rpc_devs: dict = None, rpc_devdetails: dict = None
|
||||||
) -> List[HashBoard]:
|
) -> List[HashBoard]:
|
||||||
if not api_devs:
|
if rpc_devs is None:
|
||||||
try:
|
try:
|
||||||
api_devs = await self.api.devs()
|
rpc_devs = await self.rpc.devs()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -149,9 +151,9 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
for i in range(self.expected_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if api_devs:
|
if rpc_devs is not None:
|
||||||
if api_devs.get("DEVS"):
|
if rpc_devs.get("DEVS"):
|
||||||
for board in api_devs["DEVS"]:
|
for board in rpc_devs["DEVS"]:
|
||||||
if board.get("ID") is not None:
|
if board.get("ID") is not None:
|
||||||
try:
|
try:
|
||||||
b_id = board["ID"]
|
b_id = board["ID"]
|
||||||
@@ -163,17 +165,17 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.error(self, api_devs)
|
logger.error(self, rpc_devs)
|
||||||
|
|
||||||
if not api_devdetails:
|
if rpc_devdetails is None:
|
||||||
try:
|
try:
|
||||||
api_devdetails = await self.api.devdetails()
|
rpc_devdetails = await self.rpc.devdetails()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_devdetails:
|
if rpc_devdetails is not None:
|
||||||
if api_devdetails.get("DEVS"):
|
if rpc_devdetails.get("DEVS"):
|
||||||
for board in api_devdetails["DEVS"]:
|
for board in rpc_devdetails["DEVS"]:
|
||||||
if board.get("ID") is not None:
|
if board.get("ID") is not None:
|
||||||
try:
|
try:
|
||||||
b_id = board["ID"]
|
b_id = board["ID"]
|
||||||
@@ -181,12 +183,28 @@ class BFGMinerGoldshell(BFGMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
logger.error(self, api_devdetails)
|
logger.error(self, rpc_devdetails)
|
||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
async def stop_mining(self) -> bool:
|
||||||
return None
|
settings = await self.web.setting()
|
||||||
|
mode = MiningModeConfig.sleep()
|
||||||
|
cfg = mode.as_goldshell()
|
||||||
|
level = cfg["settings"]["level"]
|
||||||
|
for idx, plan in settings["powerplans"]:
|
||||||
|
if plan["level"] == level:
|
||||||
|
settings["select"] = idx
|
||||||
|
await self.web.set_setting(settings)
|
||||||
|
return True
|
||||||
|
|
||||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
async def resume_mining(self) -> bool:
|
||||||
return None
|
settings = await self.web.setting()
|
||||||
|
mode = MiningModeConfig.normal()
|
||||||
|
cfg = mode.as_goldshell()
|
||||||
|
level = cfg["settings"]["level"]
|
||||||
|
for idx, plan in settings["powerplans"]:
|
||||||
|
if plan["level"] == level:
|
||||||
|
settings["select"] = idx
|
||||||
|
await self.web.set_setting(settings)
|
||||||
|
return True
|
||||||
@@ -14,67 +14,8 @@
|
|||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from pyasic.data import HashBoard
|
|
||||||
from pyasic.miners.backends import BMMiner
|
from pyasic.miners.backends import BMMiner
|
||||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
|
||||||
|
|
||||||
HIVEON_DATA_LOC = DataLocations(
|
|
||||||
**{
|
|
||||||
str(DataOptions.MAC): DataFunction("get_mac"),
|
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
|
||||||
),
|
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
|
||||||
),
|
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
|
||||||
),
|
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
|
||||||
"_get_env_temp", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
|
||||||
"_get_wattage", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
|
||||||
),
|
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Hiveon(BMMiner):
|
class Hiveon(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
firmware = "Hive"
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.pwd = "admin"
|
|
||||||
# static data
|
|
||||||
self.api_type = "Hiveon"
|
|
||||||
# data gathering locations
|
|
||||||
self.data_locations = HIVEON_DATA_LOC
|
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from pyasic.data.error_codes import MinerErrorData
|
|||||||
from pyasic.data.error_codes.innosilicon import InnosiliconError
|
from pyasic.data.error_codes.innosilicon import InnosiliconError
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.backends import CGMiner
|
from pyasic.miners.backends import CGMiner
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
DataOptions,
|
DataOptions,
|
||||||
@@ -40,35 +40,32 @@ INNOSILICON_DATA_LOC = DataLocations(
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
"_get_fw_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate",
|
"_get_hashrate",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_summary", "summary"),
|
RPCAPICommand("rpc_summary", "summary"),
|
||||||
WebAPICommand("web_get_all", "getAll"),
|
WebAPICommand("web_get_all", "getAll"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
|
||||||
"_get_expected_hashrate",
|
|
||||||
),
|
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards",
|
"_get_hashboards",
|
||||||
[
|
[
|
||||||
RPCAPICommand("api_stats", "stats"),
|
RPCAPICommand("rpc_stats", "stats"),
|
||||||
WebAPICommand("web_get_all", "getAll"),
|
WebAPICommand("web_get_all", "getAll"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage",
|
"_get_wattage",
|
||||||
[
|
[
|
||||||
WebAPICommand("web_get_all", "getAll"),
|
WebAPICommand("web_get_all", "getAll"),
|
||||||
RPCAPICommand("api_stats", "stats"),
|
RPCAPICommand("rpc_stats", "stats"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
@@ -83,43 +80,29 @@ INNOSILICON_DATA_LOC = DataLocations(
|
|||||||
WebAPICommand("web_get_all", "getAll"),
|
WebAPICommand("web_get_all", "getAll"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction(
|
str(DataOptions.ERRORS): DataFunction(
|
||||||
"_get_errors",
|
"_get_errors",
|
||||||
[
|
[
|
||||||
WebAPICommand("web_get_error_detail", "getErrorDetail"),
|
WebAPICommand("web_get_error_detail", "getErrorDetail"),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class Innosilicon(CGMiner):
|
class Innosilicon(CGMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Base handler for Innosilicon miners"""
|
||||||
super().__init__(ip, api_ver=api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = InnosiliconWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = InnosiliconWebAPI
|
||||||
# data gathering locations
|
web: InnosiliconWebAPI
|
||||||
self.data_locations = INNOSILICON_DATA_LOC
|
|
||||||
# autotuning/shutdown support
|
|
||||||
self.supports_shutdown = True
|
|
||||||
|
|
||||||
# data storage
|
data_locations = INNOSILICON_DATA_LOC
|
||||||
self.api_ver = api_ver
|
|
||||||
|
|
||||||
async def fault_light_on(self) -> bool:
|
supports_shutdown = True
|
||||||
return False
|
|
||||||
|
|
||||||
async def fault_light_off(self) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# get pool data
|
# get pool data
|
||||||
@@ -150,23 +133,6 @@ class Innosilicon(CGMiner):
|
|||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
return await self.restart_cgminer()
|
return await self.restart_cgminer()
|
||||||
|
|
||||||
async def stop_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
# data = await self.web.poweroff()
|
|
||||||
# try:
|
|
||||||
# return data["success"]
|
|
||||||
# except KeyError:
|
|
||||||
# return False
|
|
||||||
|
|
||||||
async def resume_mining(self) -> bool:
|
|
||||||
return False
|
|
||||||
# data = await self.web.restart_cgminer()
|
|
||||||
# print(data)
|
|
||||||
# try:
|
|
||||||
# return data["success"]
|
|
||||||
# except KeyError:
|
|
||||||
# return False
|
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
self.config = config
|
self.config = config
|
||||||
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
|
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
|
||||||
@@ -181,20 +147,20 @@ class Innosilicon(CGMiner):
|
|||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if not web_get_all and not web_overview:
|
if web_get_all is None and web_overview is None:
|
||||||
try:
|
try:
|
||||||
web_overview = await self.web.overview()
|
web_overview = await self.web.overview()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
try:
|
try:
|
||||||
mac = web_get_all["mac"]
|
mac = web_get_all["mac"]
|
||||||
return mac.upper()
|
return mac.upper()
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_overview:
|
if web_overview is not None:
|
||||||
try:
|
try:
|
||||||
mac = web_overview["version"]["ethaddr"]
|
mac = web_overview["version"]["ethaddr"]
|
||||||
return mac.upper()
|
return mac.upper()
|
||||||
@@ -202,18 +168,18 @@ class Innosilicon(CGMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashrate(
|
async def _get_hashrate(
|
||||||
self, api_summary: dict = None, web_get_all: dict = None
|
self, rpc_summary: dict = None, web_get_all: dict = None
|
||||||
) -> Optional[float]:
|
) -> Optional[float]:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if not api_summary and not web_get_all:
|
if rpc_summary is None and web_get_all is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
try:
|
try:
|
||||||
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
||||||
return round(
|
return round(
|
||||||
@@ -227,14 +193,14 @@ class Innosilicon(CGMiner):
|
|||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
return round(float(rpc_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||||
except (KeyError, IndexError):
|
except (KeyError, IndexError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(
|
async def _get_hashboards(
|
||||||
self, api_stats: dict = None, web_get_all: dict = None
|
self, rpc_stats: dict = None, web_get_all: dict = None
|
||||||
) -> List[HashBoard]:
|
) -> List[HashBoard]:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
@@ -244,13 +210,13 @@ class Innosilicon(CGMiner):
|
|||||||
for i in range(self.expected_hashboards)
|
for i in range(self.expected_hashboards)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not web_get_all:
|
if web_get_all is None:
|
||||||
try:
|
try:
|
||||||
web_get_all = await self.web.get_all()
|
web_get_all = await self.web.get_all()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -258,9 +224,9 @@ class Innosilicon(CGMiner):
|
|||||||
else:
|
else:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
if api_stats.get("STATS"):
|
if rpc_stats.get("STATS"):
|
||||||
for board in api_stats["STATS"]:
|
for board in rpc_stats["STATS"]:
|
||||||
try:
|
try:
|
||||||
idx = board["Chain ID"]
|
idx = board["Chain ID"]
|
||||||
chips = board["Num active chips"]
|
chips = board["Num active chips"]
|
||||||
@@ -270,7 +236,7 @@ class Innosilicon(CGMiner):
|
|||||||
hashboards[idx].chips = chips
|
hashboards[idx].chips = chips
|
||||||
hashboards[idx].missing = False
|
hashboards[idx].missing = False
|
||||||
|
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
if web_get_all.get("chain"):
|
if web_get_all.get("chain"):
|
||||||
for board in web_get_all["chain"]:
|
for board in web_get_all["chain"]:
|
||||||
idx = board.get("ASC")
|
idx = board.get("ASC")
|
||||||
@@ -292,12 +258,12 @@ class Innosilicon(CGMiner):
|
|||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_wattage(
|
async def _get_wattage(
|
||||||
self, web_get_all: dict = None, api_stats: dict = None
|
self, web_get_all: dict = None, rpc_stats: dict = None
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if not web_get_all:
|
if web_get_all is None:
|
||||||
try:
|
try:
|
||||||
web_get_all = await self.web.get_all()
|
web_get_all = await self.web.get_all()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -305,21 +271,21 @@ class Innosilicon(CGMiner):
|
|||||||
else:
|
else:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
try:
|
try:
|
||||||
return web_get_all["power"]
|
return web_get_all["power"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
if api_stats.get("STATS"):
|
if rpc_stats.get("STATS"):
|
||||||
for board in api_stats["STATS"]:
|
for board in rpc_stats["STATS"]:
|
||||||
try:
|
try:
|
||||||
wattage = board["power"]
|
wattage = board["power"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -332,7 +298,7 @@ class Innosilicon(CGMiner):
|
|||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if not web_get_all:
|
if web_get_all is None:
|
||||||
try:
|
try:
|
||||||
web_get_all = await self.web.get_all()
|
web_get_all = await self.web.get_all()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -341,7 +307,7 @@ class Innosilicon(CGMiner):
|
|||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
fans = [Fan() for _ in range(self.expected_fans)]
|
fans = [Fan() for _ in range(self.expected_fans)]
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
try:
|
try:
|
||||||
spd = web_get_all["fansSpeed"]
|
spd = web_get_all["fansSpeed"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -357,13 +323,13 @@ class Innosilicon(CGMiner):
|
|||||||
self, web_get_error_detail: dict = None
|
self, web_get_error_detail: dict = None
|
||||||
) -> List[MinerErrorData]:
|
) -> List[MinerErrorData]:
|
||||||
errors = []
|
errors = []
|
||||||
if not web_get_error_detail:
|
if web_get_error_detail is None:
|
||||||
try:
|
try:
|
||||||
web_get_error_detail = await self.web.get_error_detail()
|
web_get_error_detail = await self.web.get_error_detail()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_get_error_detail:
|
if web_get_error_detail is not None:
|
||||||
try:
|
try:
|
||||||
# only 1 error?
|
# only 1 error?
|
||||||
# TODO: check if this should be a loop, can't remember.
|
# TODO: check if this should be a loop, can't remember.
|
||||||
@@ -380,7 +346,7 @@ class Innosilicon(CGMiner):
|
|||||||
if web_get_all:
|
if web_get_all:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if not web_get_all:
|
if web_get_all is None:
|
||||||
try:
|
try:
|
||||||
web_get_all = await self.web.get_all()
|
web_get_all = await self.web.get_all()
|
||||||
except APIError:
|
except APIError:
|
||||||
@@ -388,7 +354,7 @@ class Innosilicon(CGMiner):
|
|||||||
else:
|
else:
|
||||||
web_get_all = web_get_all["all"]
|
web_get_all = web_get_all["all"]
|
||||||
|
|
||||||
if web_get_all:
|
if web_get_all is not None:
|
||||||
try:
|
try:
|
||||||
level = web_get_all["running_mode"]["level"]
|
level = web_get_all["running_mode"]["level"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
@@ -398,6 +364,3 @@ class Innosilicon(CGMiner):
|
|||||||
level = int(level)
|
level = int(level)
|
||||||
limit = 1250 + (250 * level)
|
limit = 1250 + (250 * level)
|
||||||
return limit
|
return limit
|
||||||
|
|
||||||
async def _get_expected_hashrate(self) -> Optional[float]:
|
|
||||||
pass
|
|
||||||
|
|||||||
@@ -13,87 +13,68 @@
|
|||||||
# See the License for the specific language governing permissions and -
|
# See the License for the specific language governing permissions and -
|
||||||
# limitations under the License. -
|
# limitations under the License. -
|
||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
from typing import List, Optional, Tuple, Union
|
from typing import List, Optional
|
||||||
|
|
||||||
from pyasic.API.luxminer import LUXMinerAPI
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard
|
from pyasic.data import Fan, HashBoard
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.base import BaseMiner
|
||||||
BaseMiner,
|
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||||
DataFunction,
|
from pyasic.rpc.luxminer import LUXMinerRPCAPI
|
||||||
DataLocations,
|
|
||||||
DataOptions,
|
|
||||||
RPCAPICommand,
|
|
||||||
)
|
|
||||||
|
|
||||||
LUXMINER_DATA_LOC = DataLocations(
|
LUXMINER_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [RPCAPICommand("api_config", "config")]
|
"_get_mac",
|
||||||
|
[RPCAPICommand("rpc_config", "config")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction("_get_api_ver"),
|
|
||||||
str(DataOptions.FW_VERSION): DataFunction("_get_fw_ver"),
|
|
||||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage", [RPCAPICommand("api_power", "power")]
|
"_get_wattage",
|
||||||
|
[RPCAPICommand("rpc_power", "power")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_fans", "fans")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_fans", "fans")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction(
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
"_get_uptime", [RPCAPICommand("rpc_stats", "stats")]
|
||||||
),
|
),
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class LUXMiner(BaseMiner):
|
class LUXMiner(BaseMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for LuxOS miners"""
|
||||||
super().__init__(ip)
|
|
||||||
# interfaces
|
|
||||||
self.api = LUXMinerAPI(ip, api_ver)
|
|
||||||
# self.web = BOSMinerWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_rpc_cls = LUXMinerRPCAPI
|
||||||
self.api_type = "LUXMiner"
|
rpc: LUXMinerRPCAPI
|
||||||
self.fw_str = "LuxOS"
|
|
||||||
# data gathering locations
|
|
||||||
self.data_locations = LUXMINER_DATA_LOC
|
|
||||||
# autotuning/shutdown support
|
|
||||||
# self.supports_autotuning = True
|
|
||||||
# self.supports_shutdown = True
|
|
||||||
|
|
||||||
# data storage
|
firmware = "LuxOS"
|
||||||
self.api_ver = api_ver
|
|
||||||
|
data_locations = LUXMINER_DATA_LOC
|
||||||
|
|
||||||
async def _get_session(self) -> Optional[str]:
|
async def _get_session(self) -> Optional[str]:
|
||||||
try:
|
try:
|
||||||
data = await self.api.session()
|
data = await self.rpc.session()
|
||||||
if not data["SESSION"][0]["SessionID"] == "":
|
if not data["SESSION"][0]["SessionID"] == "":
|
||||||
return data["SESSION"][0]["SessionID"]
|
return data["SESSION"][0]["SessionID"]
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
try:
|
try:
|
||||||
data = await self.api.logon()
|
data = await self.rpc.logon()
|
||||||
return data["SESSION"][0]["SessionID"]
|
return data["SESSION"][0]["SessionID"]
|
||||||
except (LookupError, APIError):
|
except (LookupError, APIError):
|
||||||
return
|
return
|
||||||
@@ -102,7 +83,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.ledset(session_id, "red", "blink")
|
await self.rpc.ledset(session_id, "red", "blink")
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -112,7 +93,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.ledset(session_id, "red", "off")
|
await self.rpc.ledset(session_id, "red", "off")
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -125,7 +106,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.resetminer(session_id)
|
await self.rpc.resetminer(session_id)
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -135,7 +116,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.curtail(session_id)
|
await self.rpc.curtail(session_id)
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -145,7 +126,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.wakeup(session_id)
|
await self.rpc.wakeup(session_id)
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -154,7 +135,7 @@ class LUXMiner(BaseMiner):
|
|||||||
try:
|
try:
|
||||||
session_id = await self._get_session()
|
session_id = await self._get_session()
|
||||||
if session_id:
|
if session_id:
|
||||||
await self.api.rebootdevice(session_id)
|
await self.rpc.rebootdevice(session_id)
|
||||||
return True
|
return True
|
||||||
except (APIError, LookupError):
|
except (APIError, LookupError):
|
||||||
pass
|
pass
|
||||||
@@ -163,70 +144,52 @@ class LUXMiner(BaseMiner):
|
|||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
return self.config
|
return self.config
|
||||||
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
|
||||||
return False
|
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def _get_mac(self, api_config: dict = None) -> Optional[str]:
|
async def _get_mac(self, rpc_config: dict = None) -> Optional[str]:
|
||||||
mac = None
|
mac = None
|
||||||
if not api_config:
|
if rpc_config is None:
|
||||||
try:
|
try:
|
||||||
api_config = await self.api.config()
|
rpc_config = await self.rpc.config()
|
||||||
except APIError:
|
except APIError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if api_config:
|
if rpc_config is not None:
|
||||||
try:
|
try:
|
||||||
mac = api_config["CONFIG"][0]["MACAddr"]
|
mac = rpc_config["CONFIG"][0]["MACAddr"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
return mac
|
return mac
|
||||||
|
|
||||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
pass
|
if rpc_summary is None:
|
||||||
|
|
||||||
async def _get_api_ver(self) -> Optional[str]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_fw_ver(self) -> Optional[str]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_hostname(self) -> Union[str, None]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
|
||||||
if not api_summary:
|
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
return round(float(rpc_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||||
hashboards = []
|
hashboards = []
|
||||||
|
|
||||||
if not api_stats:
|
if rpc_stats is None:
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
board_offset = -1
|
board_offset = -1
|
||||||
boards = api_stats["STATS"]
|
boards = rpc_stats["STATS"]
|
||||||
if len(boards) > 1:
|
if len(boards) > 1:
|
||||||
for board_num in range(1, 16, 5):
|
for board_num in range(1, 16, 5):
|
||||||
for _b_num in range(5):
|
for _b_num in range(5):
|
||||||
@@ -268,63 +231,48 @@ class LUXMiner(BaseMiner):
|
|||||||
|
|
||||||
return hashboards
|
return hashboards
|
||||||
|
|
||||||
async def _get_env_temp(self) -> Optional[float]:
|
async def _get_wattage(self, rpc_power: dict = None) -> Optional[int]:
|
||||||
return None
|
if rpc_power is None:
|
||||||
|
|
||||||
async def _get_wattage(self, api_power: dict) -> Optional[int]:
|
|
||||||
if not api_power:
|
|
||||||
try:
|
try:
|
||||||
api_power = await self.api.power()
|
rpc_power = await self.rpc.power()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_power:
|
if rpc_power is not None:
|
||||||
try:
|
try:
|
||||||
return api_power["POWER"][0]["Watts"]
|
return rpc_power["POWER"][0]["Watts"]
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage_limit(self) -> Optional[int]:
|
async def _get_fans(self, rpc_fans: dict = None) -> List[Fan]:
|
||||||
return None
|
if rpc_fans is None:
|
||||||
|
|
||||||
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
|
|
||||||
if not api_fans:
|
|
||||||
try:
|
try:
|
||||||
api_fans = await self.api.fans()
|
rpc_fans = await self.rpc.fans()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
fans = []
|
fans = []
|
||||||
|
|
||||||
if api_fans:
|
if rpc_fans is not None:
|
||||||
for fan in range(self.expected_fans):
|
for fan in range(self.expected_fans):
|
||||||
try:
|
try:
|
||||||
fans.append(Fan(api_fans["FANS"][fan]["RPM"]))
|
fans.append(Fan(rpc_fans["FANS"][fan]["RPM"]))
|
||||||
except (LookupError, ValueError, TypeError):
|
except (LookupError, ValueError, TypeError):
|
||||||
fans.append(Fan())
|
fans.append(Fan())
|
||||||
return fans
|
return fans
|
||||||
|
|
||||||
async def _get_fan_psu(self) -> Optional[int]:
|
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||||
return None
|
if rpc_stats is None:
|
||||||
|
|
||||||
async def _get_errors(self) -> List[MinerErrorData]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_fault_light(self) -> bool:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||||
try:
|
try:
|
||||||
rate_unit = api_stats["STATS"][1]["rate_unit"]
|
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
rate_unit = "GH"
|
rate_unit = "GH"
|
||||||
if rate_unit == "GH":
|
if rate_unit == "GH":
|
||||||
@@ -336,18 +284,15 @@ class LUXMiner(BaseMiner):
|
|||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _is_mining(self) -> Optional[bool]:
|
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||||
pass
|
if rpc_stats is None:
|
||||||
|
|
||||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
|
||||||
if not api_stats:
|
|
||||||
try:
|
try:
|
||||||
api_stats = await self.api.stats()
|
rpc_stats = await self.rpc.stats()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_stats:
|
if rpc_stats is not None:
|
||||||
try:
|
try:
|
||||||
return int(api_stats["STATS"][1]["Elapsed"])
|
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||||
except LookupError:
|
except LookupError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,26 +1,24 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# Copyright 2022 Upstream Data Inc
|
||||||
# Copyright 2022 Upstream Data Inc -
|
#
|
||||||
# -
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License"); -
|
# you may not use this file except in compliance with the License.
|
||||||
# you may not use this file except in compliance with the License. -
|
# You may obtain a copy of the License at
|
||||||
# You may obtain a copy of the License at -
|
#
|
||||||
# -
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0 -
|
#
|
||||||
# -
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
# Unless required by applicable law or agreed to in writing, software -
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS, -
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
|
# See the License for the specific language governing permissions and
|
||||||
# See the License for the specific language governing permissions and -
|
# limitations under the License.
|
||||||
# limitations under the License. -
|
|
||||||
# ------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
from typing import List, Optional, Tuple
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
from pyasic.API.unknown import UnknownAPI
|
|
||||||
from pyasic.config import MinerConfig
|
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.data.error_codes import MinerErrorData
|
||||||
from pyasic.miners.base import BaseMiner
|
from pyasic.miners.base import BaseMiner
|
||||||
|
from pyasic.rpc.unknown import UnknownRPCAPI
|
||||||
|
|
||||||
|
|
||||||
class UnknownMiner(BaseMiner):
|
class UnknownMiner(BaseMiner):
|
||||||
@@ -32,7 +30,7 @@ class UnknownMiner(BaseMiner):
|
|||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
self.api = UnknownAPI(ip)
|
self.rpc = UnknownRPCAPI(ip)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"Unknown: {str(self.ip)}"
|
return f"Unknown: {str(self.ip)}"
|
||||||
@@ -73,60 +71,53 @@ class UnknownMiner(BaseMiner):
|
|||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
##################################################
|
##################################################
|
||||||
|
|
||||||
async def get_mac(self) -> Optional[str]:
|
async def _get_mac(self) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
async def _get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
async def get_hostname(self) -> Optional[str]:
|
async def _get_hostname(self) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_hashrate(self) -> Optional[float]:
|
async def _get_hashrate(self) -> Optional[float]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_hashboards(self) -> List[HashBoard]:
|
async def _get_hashboards(self) -> List[HashBoard]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_env_temp(self) -> Optional[float]:
|
async def _get_env_temp(self) -> Optional[float]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_wattage(self) -> Optional[int]:
|
async def _get_wattage(self) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_wattage_limit(self) -> Optional[int]:
|
async def _get_wattage_limit(self) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_fans(
|
async def _get_fans(self) -> List[Fan]:
|
||||||
self,
|
|
||||||
) -> List[Fan]:
|
|
||||||
return [Fan(), Fan(), Fan(), Fan()]
|
return [Fan(), Fan(), Fan(), Fan()]
|
||||||
|
|
||||||
async def get_fan_psu(self) -> Optional[int]:
|
async def _get_fan_psu(self) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_api_ver(self) -> Optional[str]:
|
async def _get_api_ver(self) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_fw_ver(self) -> Optional[str]:
|
async def _get_fw_ver(self) -> Optional[str]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_errors(self) -> List[MinerErrorData]:
|
async def _get_errors(self) -> List[MinerErrorData]:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
async def get_fault_light(self) -> bool:
|
async def _get_fault_light(self) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
async def get_expected_hashrate(self) -> Optional[float]:
|
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def is_mining(self, *args, **kwargs) -> Optional[bool]:
|
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_uptime(self, *args, **kwargs) -> Optional[int]:
|
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def get_data(
|
|
||||||
self, allow_warning: bool = False, data_to_get: list = None, **kwargs
|
|
||||||
) -> MinerData:
|
|
||||||
return MinerData(ip=str(self.ip))
|
|
||||||
@@ -18,9 +18,8 @@ from typing import Optional
|
|||||||
|
|
||||||
from pyasic import MinerConfig
|
from pyasic import MinerConfig
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.logger import logger
|
|
||||||
from pyasic.miners.backends.bmminer import BMMiner
|
from pyasic.miners.backends.bmminer import BMMiner
|
||||||
from pyasic.miners.base import (
|
from pyasic.miners.data import (
|
||||||
DataFunction,
|
DataFunction,
|
||||||
DataLocations,
|
DataLocations,
|
||||||
DataOptions,
|
DataOptions,
|
||||||
@@ -32,57 +31,62 @@ from pyasic.web.vnish import VNishWebAPI
|
|||||||
VNISH_DATA_LOC = DataLocations(
|
VNISH_DATA_LOC = DataLocations(
|
||||||
**{
|
**{
|
||||||
str(DataOptions.MAC): DataFunction(
|
str(DataOptions.MAC): DataFunction(
|
||||||
"_get_mac", [WebAPICommand("web_summary", "summary")]
|
"_get_mac",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.API_VERSION): DataFunction(
|
str(DataOptions.API_VERSION): DataFunction(
|
||||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
"_get_api_ver",
|
||||||
|
[RPCAPICommand("rpc_version", "version")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FW_VERSION): DataFunction(
|
str(DataOptions.FW_VERSION): DataFunction(
|
||||||
"_get_fw_ver", [WebAPICommand("web_summary", "summary")]
|
"_get_fw_ver",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HOSTNAME): DataFunction(
|
str(DataOptions.HOSTNAME): DataFunction(
|
||||||
"_get_hostname", [WebAPICommand("web_summary", "summary")]
|
"_get_hostname",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHRATE): DataFunction(
|
str(DataOptions.HASHRATE): DataFunction(
|
||||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
"_get_hashrate",
|
||||||
|
[RPCAPICommand("rpc_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
"_get_expected_hashrate",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.HASHBOARDS): DataFunction(
|
str(DataOptions.HASHBOARDS): DataFunction(
|
||||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
"_get_hashboards",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
|
||||||
str(DataOptions.WATTAGE): DataFunction(
|
str(DataOptions.WATTAGE): DataFunction(
|
||||||
"_get_wattage", [WebAPICommand("web_summary", "summary")]
|
"_get_wattage",
|
||||||
|
[WebAPICommand("web_summary", "summary")],
|
||||||
),
|
),
|
||||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||||
"_get_wattage_limit", [WebAPICommand("web_settings", "settings")]
|
"_get_wattage_limit",
|
||||||
|
[WebAPICommand("web_settings", "settings")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FANS): DataFunction(
|
str(DataOptions.FANS): DataFunction(
|
||||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
"_get_fans",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
|
),
|
||||||
|
str(DataOptions.UPTIME): DataFunction(
|
||||||
|
"_get_uptime",
|
||||||
|
[RPCAPICommand("rpc_stats", "stats")],
|
||||||
),
|
),
|
||||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
|
||||||
str(DataOptions.ERRORS): DataFunction("_get_errors"),
|
|
||||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
|
||||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
|
||||||
str(DataOptions.UPTIME): DataFunction("_get_uptime"),
|
|
||||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class VNish(BMMiner):
|
class VNish(BMMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
"""Handler for VNish miners"""
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
# interfaces
|
|
||||||
self.web = VNishWebAPI(ip)
|
|
||||||
|
|
||||||
# static data
|
_web_cls = VNishWebAPI
|
||||||
self.api_type = "VNish"
|
web: VNishWebAPI
|
||||||
self.fw_str = "VNish"
|
|
||||||
# data gathering locations
|
firmware = "VNish"
|
||||||
self.data_locations = VNISH_DATA_LOC
|
|
||||||
|
data_locations = VNISH_DATA_LOC
|
||||||
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
data = await self.web.restart_vnish()
|
data = await self.web.restart_vnish()
|
||||||
@@ -121,17 +125,17 @@ class VNish(BMMiner):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
async def _get_mac(self, web_summary: dict = None) -> str:
|
async def _get_mac(self, web_summary: dict = None) -> str:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_info = await self.web.info()
|
web_info = await self.web.info()
|
||||||
|
|
||||||
if web_info:
|
if web_info is not None:
|
||||||
try:
|
try:
|
||||||
mac = web_info["system"]["network_status"]["mac"]
|
mac = web_info["system"]["network_status"]["mac"]
|
||||||
return mac
|
return mac
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
mac = web_summary["system"]["network_status"]["mac"]
|
mac = web_summary["system"]["network_status"]["mac"]
|
||||||
return mac
|
return mac
|
||||||
@@ -139,17 +143,17 @@ class VNish(BMMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hostname(self, web_summary: dict = None) -> str:
|
async def _get_hostname(self, web_summary: dict = None) -> str:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_info = await self.web.info()
|
web_info = await self.web.info()
|
||||||
|
|
||||||
if web_info:
|
if web_info is not None:
|
||||||
try:
|
try:
|
||||||
hostname = web_info["system"]["network_status"]["hostname"]
|
hostname = web_info["system"]["network_status"]["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
hostname = web_summary["system"]["network_status"]["hostname"]
|
hostname = web_summary["system"]["network_status"]["hostname"]
|
||||||
return hostname
|
return hostname
|
||||||
@@ -157,39 +161,38 @@ class VNish(BMMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
if web_summary:
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
wattage = web_summary["miner"]["power_usage"]
|
wattage = web_summary["miner"]["power_usage"]
|
||||||
wattage = round(wattage * 1000)
|
wattage = round(wattage)
|
||||||
return wattage
|
return wattage
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||||
# get hr from API
|
# get hr from API
|
||||||
if not api_summary:
|
if rpc_summary is None:
|
||||||
try:
|
try:
|
||||||
api_summary = await self.api.summary()
|
rpc_summary = await self.rpc.summary()
|
||||||
except APIError:
|
except APIError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if api_summary:
|
if rpc_summary is not None:
|
||||||
try:
|
try:
|
||||||
return round(
|
return round(
|
||||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
float(float(rpc_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||||
)
|
)
|
||||||
except (LookupError, ValueError, TypeError) as e:
|
except (LookupError, ValueError, TypeError):
|
||||||
logger.error(e)
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
|
async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
|
||||||
if not web_settings:
|
if web_settings is None:
|
||||||
web_settings = await self.web.summary()
|
web_settings = await self.web.summary()
|
||||||
|
|
||||||
if web_settings:
|
if web_settings is not None:
|
||||||
try:
|
try:
|
||||||
wattage_limit = web_settings["miner"]["overclock"]["preset"]
|
wattage_limit = web_settings["miner"]["overclock"]["preset"]
|
||||||
if wattage_limit == "disabled":
|
if wattage_limit == "disabled":
|
||||||
@@ -199,22 +202,17 @@ class VNish(BMMiner):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||||
if not web_summary:
|
if web_summary is None:
|
||||||
web_summary = await self.web.summary()
|
web_summary = await self.web.summary()
|
||||||
|
|
||||||
if web_summary:
|
fw_ver = None
|
||||||
|
if web_summary is not None:
|
||||||
try:
|
try:
|
||||||
fw_ver = web_summary["miner"]["miner_type"]
|
fw_ver = web_summary["miner"]["miner_type"]
|
||||||
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
|
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
|
||||||
return fw_ver
|
return fw_ver
|
||||||
except KeyError:
|
except LookupError:
|
||||||
pass
|
return fw_ver
|
||||||
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
return None
|
|
||||||
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -17,21 +17,15 @@ from pyasic.miners.backends.btminer import BTMiner
|
|||||||
|
|
||||||
|
|
||||||
class M6X(BTMiner):
|
class M6X(BTMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_autotuning = True
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_autotuning = True
|
|
||||||
|
|
||||||
|
|
||||||
class M5X(BTMiner):
|
class M5X(BTMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_autotuning = True
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_autotuning = True
|
|
||||||
|
|
||||||
|
|
||||||
class M3X(BTMiner):
|
class M3X(BTMiner):
|
||||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
supports_autotuning = True
|
||||||
super().__init__(ip, api_ver)
|
|
||||||
self.supports_autotuning = True
|
|
||||||
|
|
||||||
|
|
||||||
class M2X(BTMiner):
|
class M2X(BTMiner):
|
||||||
|
|||||||
@@ -15,119 +15,44 @@
|
|||||||
# ------------------------------------------------------------------------------
|
# ------------------------------------------------------------------------------
|
||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import warnings
|
||||||
from abc import ABC, abstractmethod
|
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||||
from dataclasses import dataclass, field, make_dataclass
|
|
||||||
from enum import Enum
|
|
||||||
from typing import List, Optional, Tuple, TypeVar, Union
|
|
||||||
|
|
||||||
import asyncssh
|
|
||||||
|
|
||||||
from pyasic.config import MinerConfig
|
from pyasic.config import MinerConfig
|
||||||
from pyasic.data import Fan, HashBoard, MinerData
|
from pyasic.data import Fan, HashBoard, MinerData
|
||||||
from pyasic.data.error_codes import MinerErrorData
|
from pyasic.data.error_codes import MinerErrorData
|
||||||
from pyasic.errors import APIError
|
from pyasic.errors import APIError
|
||||||
from pyasic.logger import logger
|
from pyasic.logger import logger
|
||||||
|
from pyasic.miners.data import DataLocations, DataOptions, RPCAPICommand, WebAPICommand
|
||||||
|
|
||||||
|
|
||||||
class DataOptions(Enum):
|
class MinerProtocol(Protocol):
|
||||||
MAC = "mac"
|
_rpc_cls: Type = None
|
||||||
API_VERSION = "api_ver"
|
_web_cls: Type = None
|
||||||
FW_VERSION = "fw_ver"
|
_ssh_cls: Type = None
|
||||||
HOSTNAME = "hostname"
|
|
||||||
HASHRATE = "hashrate"
|
|
||||||
EXPECTED_HASHRATE = "expected_hashrate"
|
|
||||||
HASHBOARDS = "hashboards"
|
|
||||||
ENVIRONMENT_TEMP = "env_temp"
|
|
||||||
WATTAGE = "wattage"
|
|
||||||
WATTAGE_LIMIT = "wattage_limit"
|
|
||||||
FANS = "fans"
|
|
||||||
FAN_PSU = "fan_psu"
|
|
||||||
ERRORS = "errors"
|
|
||||||
FAULT_LIGHT = "fault_light"
|
|
||||||
IS_MINING = "is_mining"
|
|
||||||
UPTIME = "uptime"
|
|
||||||
CONFIG = "config"
|
|
||||||
|
|
||||||
def __str__(self):
|
ip: str = None
|
||||||
return self.value
|
rpc: _rpc_cls = None
|
||||||
|
web: _web_cls = None
|
||||||
|
ssh: _ssh_cls = None
|
||||||
|
|
||||||
|
make: str = None
|
||||||
|
raw_model: str = None
|
||||||
|
firmware: str = None
|
||||||
|
|
||||||
@dataclass
|
expected_hashboards: int = 3
|
||||||
class RPCAPICommand:
|
expected_chips: int = None
|
||||||
name: str
|
expected_fans: int = 2
|
||||||
cmd: str
|
|
||||||
|
|
||||||
|
data_locations: DataLocations = None
|
||||||
|
|
||||||
@dataclass
|
supports_shutdown: bool = False
|
||||||
class WebAPICommand:
|
supports_autotuning: bool = False
|
||||||
name: str
|
|
||||||
cmd: str
|
|
||||||
|
|
||||||
|
api_ver: str = None
|
||||||
@dataclass
|
fw_ver: str = None
|
||||||
class GRPCCommand(WebAPICommand):
|
light: bool = None
|
||||||
name: str
|
config: MinerConfig = None
|
||||||
cmd: str
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class GraphQLCommand(WebAPICommand):
|
|
||||||
name: str
|
|
||||||
cmd: dict
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DataFunction:
|
|
||||||
cmd: str
|
|
||||||
kwargs: list[
|
|
||||||
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
|
|
||||||
] = field(default_factory=list)
|
|
||||||
|
|
||||||
|
|
||||||
DataLocations = make_dataclass(
|
|
||||||
"DataLocations",
|
|
||||||
[(enum_value.value, str) for enum_value in DataOptions],
|
|
||||||
)
|
|
||||||
# add default value with
|
|
||||||
# [(enum_value.value, str, , DataFunction(enum_value.value)) for enum_value in DataOptions],
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMiner(ABC):
|
|
||||||
def __init__(self, ip: str, *args, **kwargs) -> None:
|
|
||||||
# interfaces
|
|
||||||
self.api = None
|
|
||||||
self.web = None
|
|
||||||
|
|
||||||
self.ssh_pwd = "root"
|
|
||||||
|
|
||||||
# static data
|
|
||||||
self.ip = ip
|
|
||||||
self.api_type = None
|
|
||||||
# type
|
|
||||||
self.make = None
|
|
||||||
self.raw_model = None
|
|
||||||
self.fw_str = None
|
|
||||||
# physical attributes
|
|
||||||
self.expected_hashboards = 3
|
|
||||||
self.expected_chips = 0
|
|
||||||
self.expected_fans = 2
|
|
||||||
# data gathering locations
|
|
||||||
self.data_locations: DataLocations = 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
|
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
|
||||||
if cls is BaseMiner:
|
|
||||||
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
|
||||||
return object.__new__(cls)
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"{self.model}: {str(self.ip)}"
|
return f"{self.model}: {str(self.ip)}"
|
||||||
@@ -142,110 +67,35 @@ class BaseMiner(ABC):
|
|||||||
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
|
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def model(self):
|
def model(self) -> str:
|
||||||
model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
|
model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
|
||||||
if self.fw_str is not None:
|
if self.firmware is not None:
|
||||||
model_data.append(f"({self.fw_str})")
|
model_data.append(f"({self.firmware})")
|
||||||
return " ".join(model_data)
|
return " ".join(model_data)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pwd(self): # noqa - Skip PyCharm inspection
|
def api(self):
|
||||||
data = []
|
return self.rpc
|
||||||
try:
|
|
||||||
if self.web is not None:
|
|
||||||
data.append(f"web={self.web.pwd}")
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if self.api is not None:
|
|
||||||
data.append(f"api={self.api.pwd}")
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
return ",".join(data)
|
|
||||||
|
|
||||||
@pwd.setter
|
|
||||||
def pwd(self, val):
|
|
||||||
self.ssh_pwd = val
|
|
||||||
try:
|
|
||||||
if self.web is not None:
|
|
||||||
self.web.pwd = val
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
if self.api is not None:
|
|
||||||
self.api.pwd = val
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def username(self): # noqa - Skip PyCharm inspection
|
|
||||||
data = []
|
|
||||||
try:
|
|
||||||
if self.web is not None:
|
|
||||||
data.append(f"web={self.web.username}")
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
return ",".join(data)
|
|
||||||
|
|
||||||
@username.setter
|
|
||||||
def username(self, val):
|
|
||||||
try:
|
|
||||||
if self.web is not None:
|
|
||||||
self.web.username = val
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async def _get_ssh_connection(self) -> asyncssh.connect:
|
|
||||||
"""Create a new asyncssh connection"""
|
|
||||||
try:
|
|
||||||
conn = await asyncssh.connect(
|
|
||||||
str(self.ip),
|
|
||||||
known_hosts=None,
|
|
||||||
username="root",
|
|
||||||
password=self.ssh_pwd,
|
|
||||||
server_host_key_algs=["ssh-rsa"],
|
|
||||||
)
|
|
||||||
return conn
|
|
||||||
except asyncssh.misc.PermissionDenied:
|
|
||||||
try:
|
|
||||||
conn = await asyncssh.connect(
|
|
||||||
str(self.ip),
|
|
||||||
known_hosts=None,
|
|
||||||
username="root",
|
|
||||||
password="admin",
|
|
||||||
server_host_key_algs=["ssh-rsa"],
|
|
||||||
)
|
|
||||||
return conn
|
|
||||||
except Exception as e:
|
|
||||||
raise ConnectionError from e
|
|
||||||
except OSError as e:
|
|
||||||
logging.warning(f"Connection refused: {self}")
|
|
||||||
raise ConnectionError from e
|
|
||||||
except Exception as e:
|
|
||||||
raise ConnectionError from e
|
|
||||||
|
|
||||||
async def check_light(self) -> bool:
|
async def check_light(self) -> bool:
|
||||||
return await self.get_fault_light()
|
return await self.get_fault_light()
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def fault_light_on(self) -> bool:
|
async def fault_light_on(self) -> bool:
|
||||||
"""Turn the fault light of the miner on and return success as a boolean.
|
"""Turn the fault light of the miner on and return success as a boolean.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of turning the light on.
|
A boolean value of the success of turning the light on.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def fault_light_off(self) -> bool:
|
async def fault_light_off(self) -> bool:
|
||||||
"""Turn the fault light of the miner off and return success as a boolean.
|
"""Turn the fault light of the miner off and return success as a boolean.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of turning the light off.
|
A boolean value of the success of turning the light off.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def get_config(self) -> MinerConfig:
|
async def get_config(self) -> MinerConfig:
|
||||||
# Not a data gathering function, since this is used for configuration
|
# Not a data gathering function, since this is used for configuration
|
||||||
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
|
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
|
||||||
@@ -253,27 +103,24 @@ class BaseMiner(ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
|
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
|
||||||
"""
|
"""
|
||||||
pass
|
return MinerConfig()
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def reboot(self) -> bool:
|
async def reboot(self) -> bool:
|
||||||
"""Reboot the miner and return success as a boolean.
|
"""Reboot the miner and return success as a boolean.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of rebooting the miner.
|
A boolean value of the success of rebooting the miner.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def restart_backend(self) -> bool:
|
async def restart_backend(self) -> bool:
|
||||||
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
|
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of restarting the mining process.
|
A boolean value of the success of restarting the mining process.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||||
"""Set the mining configuration of the miner.
|
"""Set the mining configuration of the miner.
|
||||||
|
|
||||||
@@ -283,25 +130,22 @@ class BaseMiner(ABC):
|
|||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def stop_mining(self) -> bool:
|
async def stop_mining(self) -> bool:
|
||||||
"""Stop the mining process of the miner.
|
"""Stop the mining process of the miner.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of stopping the mining process.
|
A boolean value of the success of stopping the mining process.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def resume_mining(self) -> bool:
|
async def resume_mining(self) -> bool:
|
||||||
"""Resume the mining process of the miner.
|
"""Resume the mining process of the miner.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of resuming the mining process.
|
A boolean value of the success of resuming the mining process.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def set_power_limit(self, wattage: int) -> bool:
|
async def set_power_limit(self, wattage: int) -> bool:
|
||||||
"""Set the power limit to be used by the miner.
|
"""Set the power limit to be used by the miner.
|
||||||
|
|
||||||
@@ -311,7 +155,7 @@ class BaseMiner(ABC):
|
|||||||
Returns:
|
Returns:
|
||||||
A boolean value of the success of setting the power limit.
|
A boolean value of the success of setting the power limit.
|
||||||
"""
|
"""
|
||||||
pass
|
return False
|
||||||
|
|
||||||
##################################################
|
##################################################
|
||||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||||
@@ -463,68 +307,52 @@ class BaseMiner(ABC):
|
|||||||
"""
|
"""
|
||||||
return await self._get_uptime()
|
return await self._get_uptime()
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_mac(self) -> Optional[str]:
|
||||||
async def _get_mac(self, *args, **kwargs) -> Optional[str]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_api_ver(self) -> Optional[str]:
|
||||||
async def _get_api_ver(self, *args, **kwargs) -> Optional[str]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_fw_ver(self) -> Optional[str]:
|
||||||
async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_hostname(self) -> Optional[str]:
|
||||||
async def _get_hostname(self, *args, **kwargs) -> Optional[str]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_hashrate(self) -> Optional[float]:
|
||||||
async def _get_hashrate(self, *args, **kwargs) -> Optional[float]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
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]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_wattage(self) -> Optional[int]:
|
||||||
async def _get_env_temp(self, *args, **kwargs) -> Optional[float]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_wattage_limit(self) -> Optional[int]:
|
||||||
async def _get_wattage(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_fans(self) -> List[Fan]:
|
||||||
async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
|
return []
|
||||||
|
|
||||||
|
async def _get_fan_psu(self) -> Optional[int]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_errors(self) -> List[MinerErrorData]:
|
||||||
async def _get_fans(self, *args, **kwargs) -> List[Fan]:
|
return []
|
||||||
|
|
||||||
|
async def _get_fault_light(self) -> Optional[bool]:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||||
async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _is_mining(self) -> Optional[bool]:
|
||||||
async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
async def _get_uptime(self) -> Optional[int]:
|
||||||
async def _get_fault_light(self, *args, **kwargs) -> bool:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def _get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
|
||||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
async def _get_data(
|
async def _get_data(
|
||||||
@@ -633,7 +461,9 @@ class BaseMiner(ABC):
|
|||||||
ip=str(self.ip),
|
ip=str(self.ip),
|
||||||
make=self.make,
|
make=self.make,
|
||||||
model=self.model,
|
model=self.model,
|
||||||
expected_chips=self.expected_chips * self.expected_hashboards,
|
expected_chips=self.expected_chips * self.expected_hashboards
|
||||||
|
if self.expected_chips is not None
|
||||||
|
else 0,
|
||||||
expected_hashboards=self.expected_hashboards,
|
expected_hashboards=self.expected_hashboards,
|
||||||
hashboards=[
|
hashboards=[
|
||||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||||
@@ -651,4 +481,23 @@ class BaseMiner(ABC):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
|
class BaseMiner(MinerProtocol):
|
||||||
|
def __init__(self, ip: str) -> None:
|
||||||
|
self.ip = ip
|
||||||
|
|
||||||
|
if self.expected_chips is None and self.raw_model is not None:
|
||||||
|
warnings.warn(
|
||||||
|
f"Unknown chip count for miner type {self.raw_model}, "
|
||||||
|
f"please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
|
||||||
|
)
|
||||||
|
|
||||||
|
# interfaces
|
||||||
|
if self._rpc_cls is not None:
|
||||||
|
self.rpc = self._rpc_cls(ip)
|
||||||
|
if self._web_cls is not None:
|
||||||
|
self.web = self._web_cls(ip)
|
||||||
|
if self._ssh_cls is not None:
|
||||||
|
self.ssh = self._ssh_cls(ip)
|
||||||
|
|
||||||
|
|
||||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user