Merge pull request #95 from UpstreamData/dev_quality
Improve overall code quality, move ssh to `miner.ssh`, remove `pwd` for miners.
This commit is contained in:
@@ -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
|
||||
@@ -122,24 +122,24 @@ if __name__ == "__main__":
|
||||
## Miner control
|
||||
---
|
||||
`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
|
||||
[`check_light`][pyasic.miners.BaseMiner.check_light],
|
||||
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off],
|
||||
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on],
|
||||
[`get_config`][pyasic.miners.BaseMiner.get_config],
|
||||
[`get_data`][pyasic.miners.BaseMiner.get_data],
|
||||
[`get_errors`][pyasic.miners.BaseMiner.get_errors],
|
||||
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname],
|
||||
[`get_model`][pyasic.miners.BaseMiner.get_model],
|
||||
[`reboot`][pyasic.miners.BaseMiner.reboot],
|
||||
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend],
|
||||
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining],
|
||||
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining],
|
||||
[`is_mining`][pyasic.miners.BaseMiner.is_mining],
|
||||
[`send_config`][pyasic.miners.BaseMiner.send_config], and
|
||||
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit].
|
||||
[`check_light`][pyasic.miners.base.MinerProtocol.check_light],
|
||||
[`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
|
||||
[`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
|
||||
[`get_config`][pyasic.miners.base.MinerProtocol.get_config],
|
||||
[`get_data`][pyasic.miners.base.MinerProtocol.get_data],
|
||||
[`get_errors`][pyasic.miners.base.MinerProtocol.get_errors],
|
||||
[`get_hostname`][pyasic.miners.base.MinerProtocol.get_hostname],
|
||||
[`get_model`][pyasic.miners.base.MinerProtocol.get_model],
|
||||
[`reboot`][pyasic.miners.base.MinerProtocol.reboot],
|
||||
[`restart_backend`][pyasic.miners.base.MinerProtocol.restart_backend],
|
||||
[`stop_mining`][pyasic.miners.base.MinerProtocol.stop_mining],
|
||||
[`resume_mining`][pyasic.miners.base.MinerProtocol.resume_mining],
|
||||
[`is_mining`][pyasic.miners.base.MinerProtocol.is_mining],
|
||||
[`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
|
||||
[`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
|
||||
|
||||
##### Usage
|
||||
```python
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
# pyasic
|
||||
## 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
|
||||
options:
|
||||
show_root_heading: false
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
# pyasic
|
||||
## 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
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
## Control functionality
|
||||
|
||||
### Check Light
|
||||
::: pyasic.miners.BaseMiner.check_light
|
||||
::: pyasic.miners.base.MinerProtocol.check_light
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Fault Light Off
|
||||
::: pyasic.miners.BaseMiner.fault_light_off
|
||||
::: pyasic.miners.base.MinerProtocol.fault_light_off
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Fault Light On
|
||||
::: pyasic.miners.BaseMiner.fault_light_on
|
||||
::: pyasic.miners.base.MinerProtocol.fault_light_on
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Config
|
||||
::: pyasic.miners.BaseMiner.get_config
|
||||
::: pyasic.miners.base.MinerProtocol.get_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Data
|
||||
::: pyasic.miners.BaseMiner.get_data
|
||||
::: pyasic.miners.base.MinerProtocol.get_data
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Errors
|
||||
::: pyasic.miners.BaseMiner.get_errors
|
||||
::: pyasic.miners.base.MinerProtocol.get_errors
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Hostname
|
||||
::: pyasic.miners.BaseMiner.get_hostname
|
||||
::: pyasic.miners.base.MinerProtocol.get_hostname
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Get Model
|
||||
::: pyasic.miners.BaseMiner.get_model
|
||||
::: pyasic.miners.base.MinerProtocol.get_model
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Reboot
|
||||
::: pyasic.miners.BaseMiner.reboot
|
||||
::: pyasic.miners.base.MinerProtocol.reboot
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Restart Backend
|
||||
::: pyasic.miners.BaseMiner.restart_backend
|
||||
::: pyasic.miners.base.MinerProtocol.restart_backend
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Stop Mining
|
||||
::: pyasic.miners.BaseMiner.stop_mining
|
||||
::: pyasic.miners.base.MinerProtocol.stop_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Resume Mining
|
||||
::: pyasic.miners.BaseMiner.resume_mining
|
||||
::: pyasic.miners.base.MinerProtocol.resume_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Is Mining
|
||||
::: pyasic.miners.BaseMiner.is_mining
|
||||
::: pyasic.miners.base.MinerProtocol.is_mining
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Send Config
|
||||
::: pyasic.miners.BaseMiner.send_config
|
||||
::: pyasic.miners.base.MinerProtocol.send_config
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
### Set Power Limit
|
||||
::: pyasic.miners.BaseMiner.set_power_limit
|
||||
::: pyasic.miners.base.MinerProtocol.set_power_limit
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
|
||||
@@ -2,23 +2,22 @@
|
||||
## X5 Models
|
||||
|
||||
## CK5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## HS5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## KD5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5
|
||||
::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
@@ -2,9 +2,8 @@
|
||||
## XMax Models
|
||||
|
||||
## KD Max
|
||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax
|
||||
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.KDMax
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
|
||||
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.BaseMinerRPCAPI], which implements the basic communications protocols.
|
||||
|
||||
[`BaseMinerRPCAPI`][pyasic.rpc.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.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.BaseMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BFGMinerAPI
|
||||
::: pyasic.API.bfgminer.BFGMinerAPI
|
||||
## BFGMinerRPCAPI
|
||||
::: pyasic.rpc.bfgminer.BFGMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BMMinerAPI
|
||||
::: pyasic.API.bmminer.BMMinerAPI
|
||||
## BMMinerRPCAPI
|
||||
::: pyasic.rpc.bmminer.BMMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BOSMinerAPI
|
||||
::: pyasic.API.bosminer.BOSMinerAPI
|
||||
## BOSMinerRPCAPI
|
||||
::: pyasic.rpc.bosminer.BOSMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## BTMinerAPI
|
||||
::: pyasic.API.btminer.BTMinerAPI
|
||||
## BTMinerRPCAPI
|
||||
::: pyasic.rpc.btminer.BTMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## CGMinerAPI
|
||||
::: pyasic.API.cgminer.CGMinerAPI
|
||||
## CGMinerRPCAPI
|
||||
::: pyasic.rpc.cgminer.CGMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## LUXMinerAPI
|
||||
::: pyasic.API.luxminer.LUXMinerAPI
|
||||
## LUXMinerRPCAPI
|
||||
::: pyasic.rpc.luxminer.LUXMinerRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
@@ -1,6 +1,6 @@
|
||||
# pyasic
|
||||
## UnknownAPI
|
||||
::: pyasic.API.unknown.UnknownAPI
|
||||
## UnknownRPCAPI
|
||||
::: pyasic.rpc.unknown.UnknownRPCAPI
|
||||
handler: python
|
||||
options:
|
||||
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)`.
|
||||
|
||||
Settings options:
|
||||
|
||||
- `network_ping_retries`
|
||||
- `network_ping_timeout`
|
||||
- `network_scan_threads`
|
||||
|
||||
18
mkdocs.yml
18
mkdocs.yml
@@ -13,15 +13,15 @@ nav:
|
||||
- Error Codes: "data/error_codes.md"
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Intro: "API/api.md"
|
||||
- BFGMiner: "API/bfgminer.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- LUXMiner: "API/luxminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
- RPC APIs:
|
||||
- Intro: "rpc/api.md"
|
||||
- BFGMiner: "rpc/bfgminer.md"
|
||||
- BMMiner: "rpc/bmminer.md"
|
||||
- BOSMiner: "rpc/bosminer.md"
|
||||
- BTMiner: "rpc/btminer.md"
|
||||
- CGMiner: "rpc/cgminer.md"
|
||||
- LUXMiner: "rpc/luxminer.md"
|
||||
- Unknown: "rpc/unknown.md"
|
||||
- Backends:
|
||||
- BMMiner: "miners/backends/bmminer.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,11 +14,6 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
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.data import (
|
||||
BraiinsOSError,
|
||||
@@ -33,13 +28,18 @@ 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.rpc.bmminer import BMMinerRPCAPI
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
from pyasic.rpc.unknown import UnknownRPCAPI
|
||||
|
||||
__all__ = [
|
||||
"BMMinerAPI",
|
||||
"BOSMinerAPI",
|
||||
"BTMinerAPI",
|
||||
"CGMinerAPI",
|
||||
"UnknownAPI",
|
||||
"BMMinerRPCAPI",
|
||||
"BOSMinerRPCAPI",
|
||||
"BTMinerRPCAPI",
|
||||
"CGMinerRPCAPI",
|
||||
"UnknownRPCAPI",
|
||||
"MinerConfig",
|
||||
"MinerData",
|
||||
"BraiinsOSError",
|
||||
|
||||
@@ -19,7 +19,7 @@ from dataclasses import asdict, dataclass, field
|
||||
from pyasic.config.fans import FanModeConfig
|
||||
from pyasic.config.mining import MiningModeConfig
|
||||
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
|
||||
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
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):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
@@ -49,7 +49,7 @@ class Pool(MinerConfigValue):
|
||||
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):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"_ant_pool{idx}url": self.url,
|
||||
@@ -76,7 +76,7 @@ class Pool(MinerConfigValue):
|
||||
return ",".join([self.url, f"{self.user}{user_suffix}", 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):
|
||||
if user_suffix is not None:
|
||||
return {
|
||||
f"Pool{idx}": self.url,
|
||||
|
||||
@@ -59,18 +59,17 @@ class TemperatureConfig(MinerConfigValue):
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
|
||||
dangerous_temp = None
|
||||
try:
|
||||
hot_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||
dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
|
||||
except KeyError:
|
||||
hot_temp = None
|
||||
dangerous_temp = None
|
||||
# Need to do this in two blocks to avoid KeyError if one is missing
|
||||
try:
|
||||
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
|
||||
except KeyError:
|
||||
target_temp = None
|
||||
|
||||
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
|
||||
return cls(target=target_temp, danger=dangerous_temp)
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict):
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import copy
|
||||
import json
|
||||
import logging
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
@@ -351,7 +350,6 @@ class MinerData:
|
||||
pass
|
||||
|
||||
def asdict(self) -> dict:
|
||||
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
|
||||
return asdict(self, dict_factory=self.dict_factory)
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
@@ -368,7 +366,6 @@ class MinerData:
|
||||
Returns:
|
||||
A JSON version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
return json.dumps(data)
|
||||
@@ -379,7 +376,6 @@ class MinerData:
|
||||
Returns:
|
||||
A CSV version of this class with no headers.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
|
||||
data = self.asdict()
|
||||
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
|
||||
errs = []
|
||||
@@ -398,7 +394,6 @@ class MinerData:
|
||||
Returns:
|
||||
A influxdb line protocol version of this class.
|
||||
"""
|
||||
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
|
||||
tag_data = [measurement_name]
|
||||
field_data = []
|
||||
|
||||
|
||||
@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
|
||||
not self.miners[data_point.ip]["shutdown"]
|
||||
):
|
||||
# cant do anything with it so need to find a semi-accurate power limit
|
||||
if not data_point.wattage_limit == 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]["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]["min"] = int(data_point.wattage)
|
||||
|
||||
@@ -183,13 +183,19 @@ class _MinerPhaseBalancer:
|
||||
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
|
||||
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
|
||||
raise APIError(
|
||||
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
|
||||
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W")
|
||||
)
|
||||
|
||||
# should now know wattage limits and which can be tuned/shutdown
|
||||
# check if 1/2 max of the miners which can be tuned is low enough
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
from pyasic.miners.backends import AntminerModern
|
||||
from pyasic.miners.types import T19
|
||||
|
||||
# noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT19(AntminerModern, T19):
|
||||
pass
|
||||
|
||||
@@ -19,6 +19,4 @@ from pyasic.miners.types import HS3
|
||||
|
||||
|
||||
class BMMinerHS3(AntminerModern, HS3):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -18,6 +18,4 @@ from pyasic.miners.types import L7
|
||||
|
||||
|
||||
class BMMinerL7(AntminerModern, L7):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -19,6 +19,4 @@ from pyasic.miners.types import E9Pro
|
||||
|
||||
|
||||
class BMMinerE9Pro(AntminerModern, E9Pro):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -19,6 +19,4 @@ from pyasic.miners.types import Z15
|
||||
|
||||
|
||||
class CGMinerZ15(AntminerOld, Z15):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -18,6 +18,4 @@ from pyasic.miners.types import D3
|
||||
|
||||
|
||||
class CGMinerD3(AntminerOld, D3):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -19,6 +19,4 @@ from pyasic.miners.types import DR5
|
||||
|
||||
|
||||
class CGMinerDR5(AntminerOld, DR5):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_shutdown = False
|
||||
supports_shutdown = False
|
||||
|
||||
@@ -21,10 +21,53 @@ import asyncssh
|
||||
from pyasic.data import HashBoard
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import Hiveon
|
||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.types import T9
|
||||
|
||||
HIVEON_T9_DATA_LOC = DataLocations(
|
||||
**{
|
||||
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.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.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class HiveonT9(Hiveon, T9):
|
||||
data_locations = HIVEON_T9_DATA_LOC
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
|
||||
@@ -19,5 +19,4 @@ from pyasic.miners.types import L3Plus
|
||||
|
||||
|
||||
class VnishL3Plus(VNish, L3Plus):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1026
|
||||
|
||||
|
||||
class CGMinerAvalon1026(CGMinerAvalon, Avalon1026):
|
||||
class CGMinerAvalon1026(AvalonMiner, Avalon1026):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1047
|
||||
|
||||
|
||||
class CGMinerAvalon1047(CGMinerAvalon, Avalon1047):
|
||||
class CGMinerAvalon1047(AvalonMiner, Avalon1047):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1066
|
||||
|
||||
|
||||
class CGMinerAvalon1066(CGMinerAvalon, Avalon1066):
|
||||
class CGMinerAvalon1066(AvalonMiner, Avalon1066):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1166Pro
|
||||
|
||||
|
||||
class CGMinerAvalon1166Pro(CGMinerAvalon, Avalon1166Pro):
|
||||
class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon1246
|
||||
|
||||
|
||||
class CGMinerAvalon1246(CGMinerAvalon, Avalon1246):
|
||||
class CGMinerAvalon1246(AvalonMiner, Avalon1246):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon721
|
||||
|
||||
|
||||
class CGMinerAvalon721(CGMinerAvalon, Avalon721):
|
||||
class CGMinerAvalon721(AvalonMiner, Avalon721):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon741
|
||||
|
||||
|
||||
class CGMinerAvalon741(CGMinerAvalon, Avalon741):
|
||||
class CGMinerAvalon741(AvalonMiner, Avalon741):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon761
|
||||
|
||||
|
||||
class CGMinerAvalon761(CGMinerAvalon, Avalon761):
|
||||
class CGMinerAvalon761(AvalonMiner, Avalon761):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon821
|
||||
|
||||
|
||||
class CGMinerAvalon821(CGMinerAvalon, Avalon821):
|
||||
class CGMinerAvalon821(AvalonMiner, Avalon821):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon841
|
||||
|
||||
|
||||
class CGMinerAvalon841(CGMinerAvalon, Avalon841):
|
||||
class CGMinerAvalon841(AvalonMiner, Avalon841):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon851
|
||||
|
||||
|
||||
class CGMinerAvalon851(CGMinerAvalon, Avalon851):
|
||||
class CGMinerAvalon851(AvalonMiner, Avalon851):
|
||||
pass
|
||||
|
||||
@@ -14,9 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.backends import CGMinerAvalon
|
||||
from pyasic.miners.backends import AvalonMiner
|
||||
from pyasic.miners.types import Avalon921
|
||||
|
||||
|
||||
class CGMinerAvalon921(CGMinerAvalon, Avalon921):
|
||||
class CGMinerAvalon921(AvalonMiner, Avalon921):
|
||||
pass
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .antminer import AntminerModern, AntminerOld
|
||||
from .avalonminer import AvalonMiner
|
||||
from .bfgminer import BFGMiner
|
||||
from .bfgminer_goldshell import BFGMinerGoldshell
|
||||
from .bmminer import BMMiner
|
||||
from .braiins_os import BOSer, BOSMiner
|
||||
from .btminer import BTMiner
|
||||
from .cgminer import CGMiner
|
||||
from .cgminer_avalon import CGMinerAvalon
|
||||
from .epic import ePIC
|
||||
from .goldshell import GoldshellMiner
|
||||
from .hiveon import Hiveon
|
||||
from .luxminer import LUXMiner
|
||||
from .vnish import VNish
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from pyasic.API import APIError
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
@@ -29,65 +28,72 @@ from pyasic.miners.base import (
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc import APIError
|
||||
from pyasic.ssh.antminer import AntminerModernSSH
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
|
||||
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
**{
|
||||
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(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
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(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_errors",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction(
|
||||
"_get_fault_light",
|
||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||
),
|
||||
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(
|
||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AntminerModern(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = AntminerModernWebAPI(ip)
|
||||
"""Handler for AntMiners with the modern web interface, such as S19"""
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = ANTMINER_MODERN_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_shutdown = True
|
||||
_web_cls = AntminerModernWebAPI
|
||||
web: AntminerModernWebAPI
|
||||
|
||||
_ssh_cls = AntminerModernSSH
|
||||
ssh: AntminerModernSSH
|
||||
|
||||
data_locations = ANTMINER_MODERN_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
@@ -141,26 +147,26 @@ class AntminerModern(BMMiner):
|
||||
return True
|
||||
|
||||
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:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info:
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
return web_get_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
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:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info:
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
return web_get_system_info["macaddr"]
|
||||
except KeyError:
|
||||
@@ -174,14 +180,14 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
|
||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
if not web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
errors = []
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
for item in web_summary["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
@@ -204,7 +210,7 @@ class AntminerModern(BMMiner):
|
||||
except APIError:
|
||||
return hashboards
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
for board in api_stats["STATS"][0]["chain"]:
|
||||
hashboards[board["index"]].hashrate = round(
|
||||
@@ -229,17 +235,19 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
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:
|
||||
return self.light
|
||||
|
||||
if not web_get_blink_status:
|
||||
if web_get_blink_status is None:
|
||||
try:
|
||||
web_get_blink_status = await self.web.get_blink_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_blink_status:
|
||||
if web_get_blink_status is not None:
|
||||
try:
|
||||
self.light = web_get_blink_status["blink"]
|
||||
except KeyError:
|
||||
@@ -247,13 +255,13 @@ class AntminerModern(BMMiner):
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
@@ -312,13 +320,13 @@ class AntminerModern(BMMiner):
|
||||
)
|
||||
|
||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||
if not web_get_conf:
|
||||
if web_get_conf is None:
|
||||
try:
|
||||
web_get_conf = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_conf:
|
||||
if web_get_conf is not None:
|
||||
try:
|
||||
if web_get_conf["bitmain-work-mode"].isdigit():
|
||||
return (
|
||||
@@ -329,13 +337,13 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
@@ -344,57 +352,53 @@ class AntminerModern(BMMiner):
|
||||
|
||||
ANTMINER_OLD_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
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(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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",
|
||||
[WebAPICommand("web_get_blink_status", "get_blink_status")],
|
||||
),
|
||||
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(
|
||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class AntminerOld(CGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = AntminerOldWebAPI(ip)
|
||||
"""Handler for AntMiners with the old web interface, such as S17"""
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = ANTMINER_OLD_DATA_LOC
|
||||
_web_cls = AntminerOldWebAPI
|
||||
web: AntminerOldWebAPI
|
||||
|
||||
data_locations = ANTMINER_OLD_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
@@ -443,17 +447,19 @@ class AntminerOld(CGMiner):
|
||||
return True
|
||||
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:
|
||||
return self.light
|
||||
|
||||
if not web_get_blink_status:
|
||||
if web_get_blink_status is None:
|
||||
try:
|
||||
web_get_blink_status = await self.web.get_blink_status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_blink_status:
|
||||
if web_get_blink_status is not None:
|
||||
try:
|
||||
self.light = web_get_blink_status["isBlinking"]
|
||||
except KeyError:
|
||||
@@ -461,27 +467,27 @@ class AntminerOld(CGMiner):
|
||||
return self.light
|
||||
|
||||
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:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_system_info:
|
||||
if web_get_system_info is not None:
|
||||
try:
|
||||
return web_get_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
@@ -504,13 +510,13 @@ class AntminerOld(CGMiner):
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
@@ -556,13 +562,13 @@ class AntminerOld(CGMiner):
|
||||
return hashboards
|
||||
|
||||
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
|
||||
if not web_get_conf:
|
||||
if web_get_conf is None:
|
||||
try:
|
||||
web_get_conf = await self.web.get_miner_conf()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_conf:
|
||||
if web_get_conf is not None:
|
||||
try:
|
||||
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
|
||||
except LookupError:
|
||||
@@ -581,13 +587,13 @@ class AntminerOld(CGMiner):
|
||||
return False
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -17,62 +17,61 @@
|
||||
import re
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends import CGMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
|
||||
AVALON_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac", [RPCAPICommand("api_version", "version")]
|
||||
"_get_mac",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_devs", "devs")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction("_get_wattage"),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_fault_light",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
|
||||
str(DataOptions.UPTIME): DataFunction("_get_uptime"),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CGMinerAvalon(CGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
class AvalonMiner(CGMiner):
|
||||
"""Handler for Avalon Miners"""
|
||||
|
||||
# data gathering locations
|
||||
self.data_locations = AVALON_DATA_LOC
|
||||
data_locations = AVALON_DATA_LOC
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
try:
|
||||
@@ -105,26 +104,6 @@ class CGMinerAvalon(CGMiner):
|
||||
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
|
||||
def parse_stats(stats):
|
||||
_stats_items = re.findall(".+?\\[*?]", stats)
|
||||
@@ -142,9 +121,9 @@ class CGMinerAvalon(CGMiner):
|
||||
# --avalon args
|
||||
for arg_item in data_list:
|
||||
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:
|
||||
data_dict[item_data[idx]] = item_data[idx + 1]
|
||||
data_dict[val] = item_data[idx + 1]
|
||||
|
||||
raw_data = [data[0].strip(), data_dict]
|
||||
else:
|
||||
@@ -173,13 +152,13 @@ class CGMinerAvalon(CGMiner):
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
base_mac = api_version["VERSION"][0]["MAC"]
|
||||
base_mac = base_mac.upper()
|
||||
@@ -190,22 +169,14 @@ class CGMinerAvalon(CGMiner):
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
return 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:
|
||||
if api_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs:
|
||||
if api_devs is not None:
|
||||
try:
|
||||
return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
@@ -217,13 +188,13 @@ class CGMinerAvalon(CGMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -260,13 +231,13 @@ class CGMinerAvalon(CGMiner):
|
||||
return hashboards
|
||||
|
||||
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -275,13 +246,13 @@ class CGMinerAvalon(CGMiner):
|
||||
pass
|
||||
|
||||
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -289,17 +260,14 @@ class CGMinerAvalon(CGMiner):
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -308,14 +276,14 @@ class CGMinerAvalon(CGMiner):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -329,19 +297,16 @@ class CGMinerAvalon(CGMiner):
|
||||
pass
|
||||
return fans_data
|
||||
|
||||
async def _get_errors(self) -> List[MinerErrorData]:
|
||||
return []
|
||||
|
||||
async def _get_fault_light(self, api_stats: dict = None) -> bool: # noqa
|
||||
async def _get_fault_light(self, api_stats: dict = None) -> Optional[bool]:
|
||||
if self.light:
|
||||
return self.light
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
unparsed_stats = api_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
@@ -360,9 +325,3 @@ class CGMinerAvalon(CGMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def _get_uptime(self) -> Optional[int]:
|
||||
return None
|
||||
@@ -16,10 +16,8 @@
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.API.bfgminer import BFGMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
@@ -28,38 +26,34 @@ from pyasic.miners.base import (
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
|
||||
|
||||
BFGMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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"),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -67,18 +61,10 @@ BFGMINER_DATA_LOC = DataLocations(
|
||||
class BFGMiner(BaseMiner):
|
||||
"""Base handler for BFGMiner based miners."""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BFGMinerAPI(ip, api_ver)
|
||||
_api_cls = BFGMinerRPCAPI
|
||||
api: BFGMinerRPCAPI
|
||||
|
||||
# static data
|
||||
self.api_type = "BFGMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BFGMINER_DATA_LOC
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
data_locations = BFGMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
@@ -90,42 +76,18 @@ class BFGMiner(BaseMiner):
|
||||
self.config = MinerConfig.from_api(pools)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
return None
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
return False
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
return False
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
return False
|
||||
|
||||
async def resume_mining(self) -> bool:
|
||||
return False
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
return False
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
@@ -134,13 +96,13 @@ class BFGMiner(BaseMiner):
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||
except LookupError:
|
||||
@@ -148,24 +110,15 @@ class BFGMiner(BaseMiner):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
return False
|
||||
|
||||
async def _get_fan_psu(self):
|
||||
return None
|
||||
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
@@ -174,13 +127,13 @@ class BFGMiner(BaseMiner):
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
@@ -225,24 +178,15 @@ class BFGMiner(BaseMiner):
|
||||
|
||||
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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans_data = [None, None, None, None]
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
@@ -264,21 +208,15 @@ class BFGMiner(BaseMiner):
|
||||
|
||||
return fans
|
||||
|
||||
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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
@@ -293,9 +231,3 @@ class BFGMiner(BaseMiner):
|
||||
return round(expected_rate, 2)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
@@ -29,40 +26,38 @@ from pyasic.miners.base import (
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.rpc.bmminer import BMMinerRPCAPI
|
||||
|
||||
BMMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -70,46 +65,10 @@ BMMINER_DATA_LOC = DataLocations(
|
||||
class BMMiner(BaseMiner):
|
||||
"""Base handler for BMMiner based miners."""
|
||||
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BMMinerAPI(ip, api_ver)
|
||||
_api_cls = BMMinerRPCAPI
|
||||
api: BMMinerRPCAPI
|
||||
|
||||
# static data
|
||||
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
|
||||
data_locations = BMMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
@@ -121,50 +80,18 @@ class BMMiner(BaseMiner):
|
||||
self.config = MinerConfig.from_api(pools)
|
||||
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}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
@@ -173,13 +100,13 @@ class BMMiner(BaseMiner):
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CompileTime"]
|
||||
except LookupError:
|
||||
@@ -187,22 +114,15 @@ class BMMiner(BaseMiner):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_fan_psu(self):
|
||||
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
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
@@ -211,13 +131,13 @@ class BMMiner(BaseMiner):
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
@@ -275,24 +195,15 @@ class BMMiner(BaseMiner):
|
||||
|
||||
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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
fan_offset = -1
|
||||
|
||||
@@ -313,21 +224,15 @@ class BMMiner(BaseMiner):
|
||||
|
||||
return fans
|
||||
|
||||
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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
@@ -343,17 +248,14 @@ class BMMiner(BaseMiner):
|
||||
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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -13,14 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import logging
|
||||
import time
|
||||
from typing import List, Optional, Union
|
||||
|
||||
import toml
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data import Fan, HashBoard
|
||||
@@ -35,6 +33,8 @@ from pyasic.miners.base import (
|
||||
RPCAPICommand,
|
||||
WebAPICommand,
|
||||
)
|
||||
from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.ssh.braiins_os import BOSMinerSSH
|
||||
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
|
||||
BOSMINER_DATA_LOC = DataLocations(
|
||||
@@ -44,18 +44,20 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
[WebAPICommand("web_net_conf", "admin/network/iface_status/lan")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [WebAPICommand("web_bos_info", "bos/info")]
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_bos_info", "bos/info")],
|
||||
),
|
||||
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_devs", "devs")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
@@ -65,7 +67,6 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
RPCAPICommand("api_devs", "devs"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
@@ -78,87 +79,50 @@ BOSMINER_DATA_LOC = DataLocations(
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_fans", "fans")],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
),
|
||||
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining", [RPCAPICommand("api_devdetails", "devdetails")]
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_devdetails", "devdetails")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerAPI(ip, api_ver)
|
||||
self.web = BOSMinerWebAPI(ip)
|
||||
"""Handler for old versions of BraiinsOS+ (pre-gRPC)"""
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BOSMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = True
|
||||
self.supports_shutdown = True
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
api: BOSMinerRPCAPI
|
||||
_web_cls = BOSMinerWebAPI
|
||||
web: BOSMinerWebAPI
|
||||
_ssh_cls = BOSMinerSSH
|
||||
ssh: BOSMinerSSH
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
firmware = "BOS+"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> Optional[str]:
|
||||
result = None
|
||||
data_locations = BOSMINER_DATA_LOC
|
||||
|
||||
try:
|
||||
conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10)
|
||||
except (ConnectionError, asyncio.TimeoutError):
|
||||
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)
|
||||
stderr = result.stderr
|
||||
result = result.stdout
|
||||
|
||||
if len(stderr) > len(result):
|
||||
result = stderr
|
||||
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return result
|
||||
supports_shutdown = True
|
||||
supports_autotuning = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
logging.debug(f"{self}: Sending fault_light on command.")
|
||||
ret = await self.send_ssh_command("miner fault_light on")
|
||||
logging.debug(f"{self}: fault_light on command completed.")
|
||||
ret = await self.ssh.fault_light_on()
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = True
|
||||
return self.light
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
logging.debug(f"{self}: Sending fault_light off command.")
|
||||
self.light = False
|
||||
ret = await self.send_ssh_command("miner fault_light off")
|
||||
logging.debug(f"{self}: fault_light off command completed.")
|
||||
ret = await self.ssh.fault_light_off()
|
||||
|
||||
if isinstance(ret, str):
|
||||
self.light = False
|
||||
return True
|
||||
@@ -168,9 +132,8 @@ class BOSMiner(BaseMiner):
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
logging.debug(f"{self}: Sending bosminer restart command.")
|
||||
ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
||||
logging.debug(f"{self}: bosminer restart command completed.")
|
||||
ret = await self.ssh.restart_bosminer()
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
return False
|
||||
@@ -180,6 +143,7 @@ class BOSMiner(BaseMiner):
|
||||
data = await self.api.pause()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
if data.get("PAUSE"):
|
||||
if data["PAUSE"][0]:
|
||||
return True
|
||||
@@ -190,40 +154,32 @@ class BOSMiner(BaseMiner):
|
||||
data = await self.api.resume()
|
||||
except APIError:
|
||||
return False
|
||||
|
||||
if data.get("RESUME"):
|
||||
if data["RESUME"][0]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
ret = await self.send_ssh_command("/sbin/reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
ret = await self.ssh.reboot()
|
||||
|
||||
if isinstance(ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
logging.debug(f"{self}: Getting config.")
|
||||
raw_data = await self.ssh.get_config_file()
|
||||
|
||||
try:
|
||||
conn = await self._get_ssh_connection()
|
||||
except ConnectionError:
|
||||
conn = None
|
||||
|
||||
if conn:
|
||||
async with conn:
|
||||
# good ol' BBB compatibility :/
|
||||
toml_data = toml.loads(
|
||||
(await conn.run("cat /etc/bosminer.toml")).stdout
|
||||
)
|
||||
logging.debug(f"{self}: Converting config file.")
|
||||
toml_data = toml.loads(raw_data)
|
||||
cfg = MinerConfig.from_bosminer(toml_data)
|
||||
self.config = cfg
|
||||
except toml.TomlDecodeError as e:
|
||||
raise APIError("Failed to decode toml when getting config.") from e
|
||||
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
self.config = config
|
||||
|
||||
toml_conf = toml.dumps(
|
||||
@@ -238,34 +194,16 @@ class BOSMiner(BaseMiner):
|
||||
}
|
||||
)
|
||||
try:
|
||||
conn = await self._get_ssh_connection()
|
||||
conn = await self.ssh._get_connection()
|
||||
except ConnectionError as e:
|
||||
raise APIError("SSH connection failed when sending config.") from e
|
||||
|
||||
async with conn:
|
||||
# BBB check because bitmain suxx
|
||||
bbb_check = await conn.run(
|
||||
"if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;"
|
||||
)
|
||||
|
||||
bbb = bbb_check.stdout.strip() == "1"
|
||||
|
||||
if not bbb:
|
||||
await conn.run("/etc/init.d/bosminer stop")
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Opening config file.")
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
logging.debug(f"{self}: Restarting BOSMiner")
|
||||
await conn.run("/etc/init.d/bosminer start")
|
||||
|
||||
# I really hate BBB, please get rid of it if you have it
|
||||
else:
|
||||
await conn.run("/etc/init.d/S99bosminer stop")
|
||||
logging.debug(f"{self}: BBB sending config")
|
||||
await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml")
|
||||
logging.debug(f"{self}: BBB restarting bosminer.")
|
||||
await conn.run("/etc/init.d/S99bosminer start")
|
||||
await conn.run("/etc/init.d/bosminer stop")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
await conn.run("/etc/init.d/bosminer start")
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
@@ -274,8 +212,10 @@ class BOSMiner(BaseMiner):
|
||||
return False
|
||||
cfg.mining_mode = MiningModePowerTune(wattage)
|
||||
await self.send_config(cfg)
|
||||
except APIError:
|
||||
raise
|
||||
except Exception as e:
|
||||
logging.warning(f"{self} set_power_limit: {e}")
|
||||
logging.warning(f"{self} - Failed to set power limit: {e}")
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
@@ -299,18 +239,15 @@ class BOSMiner(BaseMiner):
|
||||
f"option dns '{dns}'",
|
||||
]
|
||||
)
|
||||
data = await self.send_ssh_command("cat /etc/config/network")
|
||||
data = await self.ssh.get_network_config()
|
||||
|
||||
split_data = data.split("\n\n")
|
||||
for idx in range(len(split_data)):
|
||||
if "config interface 'lan'" in split_data[idx]:
|
||||
for idx, val in enumerate(split_data):
|
||||
if "config interface 'lan'" in val:
|
||||
split_data[idx] = cfg_data_lan
|
||||
config = "\n\n".join(split_data)
|
||||
|
||||
conn = await self._get_ssh_connection()
|
||||
|
||||
async with conn:
|
||||
await conn.run("echo '" + config + "' > /etc/config/network")
|
||||
await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
|
||||
|
||||
async def set_dhcp(self):
|
||||
cfg_data_lan = "\n\t".join(
|
||||
@@ -321,25 +258,22 @@ class BOSMiner(BaseMiner):
|
||||
"option proto 'dhcp'",
|
||||
]
|
||||
)
|
||||
data = await self.send_ssh_command("cat /etc/config/network")
|
||||
data = await self.ssh.get_network_config()
|
||||
|
||||
split_data = data.split("\n\n")
|
||||
for idx in range(len(split_data)):
|
||||
if "config interface 'lan'" in split_data[idx]:
|
||||
for idx, val in enumerate(split_data):
|
||||
if "config interface 'lan'" in val:
|
||||
split_data[idx] = cfg_data_lan
|
||||
config = "\n\n".join(split_data)
|
||||
|
||||
conn = await self._get_ssh_connection()
|
||||
|
||||
async with conn:
|
||||
await conn.run("echo '" + config + "' > /etc/config/network")
|
||||
await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]:
|
||||
if not web_net_conf:
|
||||
if web_net_conf is None:
|
||||
try:
|
||||
web_net_conf = await self.web.luci.get_net_conf()
|
||||
except APIError:
|
||||
@@ -349,7 +283,7 @@ class BOSMiner(BaseMiner):
|
||||
if "admin/network/iface_status/lan" in web_net_conf.keys():
|
||||
web_net_conf = web_net_conf["admin/network/iface_status/lan"]
|
||||
|
||||
if web_net_conf:
|
||||
if web_net_conf is not None:
|
||||
try:
|
||||
return web_net_conf[0]["macaddr"]
|
||||
except LookupError:
|
||||
@@ -360,14 +294,14 @@ class BOSMiner(BaseMiner):
|
||||
# return result.upper().strip()
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
# Now get the API version
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
api_ver = api_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
@@ -377,7 +311,7 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, web_bos_info: dict) -> Optional[str]:
|
||||
async def _get_fw_ver(self, web_bos_info: dict = None) -> Optional[str]:
|
||||
if web_bos_info is None:
|
||||
try:
|
||||
web_bos_info = await self.web.luci.get_bos_info()
|
||||
@@ -392,7 +326,6 @@ class BOSMiner(BaseMiner):
|
||||
ver = web_bos_info["version"].split("-")[5]
|
||||
if "." in ver:
|
||||
self.fw_ver = ver
|
||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
||||
except (LookupError, AttributeError):
|
||||
return None
|
||||
|
||||
@@ -400,23 +333,20 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
async def _get_hostname(self) -> Union[str, None]:
|
||||
try:
|
||||
hostname = (
|
||||
await self.send_ssh_command("cat /proc/sys/kernel/hostname")
|
||||
).strip()
|
||||
hostname = (await self.ssh.get_hostname()).strip()
|
||||
except Exception as e:
|
||||
logging.error(f"BOSMiner get_hostname failed with error: {e}")
|
||||
logging.error(f"{self} - Getting hostname failed: {e}")
|
||||
return None
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
@@ -427,18 +357,18 @@ class BOSMiner(BaseMiner):
|
||||
api_temps: dict = None,
|
||||
api_devdetails: dict = None,
|
||||
api_devs: dict = None,
|
||||
):
|
||||
) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
cmds = []
|
||||
if not api_temps:
|
||||
if api_temps is None:
|
||||
cmds.append("temps")
|
||||
if not api_devdetails:
|
||||
if api_devdetails is None:
|
||||
cmds.append("devdetails")
|
||||
if not api_devs:
|
||||
if api_devs is None:
|
||||
cmds.append("devs")
|
||||
if len(cmds) > 0:
|
||||
try:
|
||||
@@ -457,7 +387,7 @@ class BOSMiner(BaseMiner):
|
||||
api_devs = d["devs"][0]
|
||||
except LookupError:
|
||||
api_devs = None
|
||||
if api_temps:
|
||||
if api_temps is not None:
|
||||
try:
|
||||
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
@@ -470,7 +400,7 @@ class BOSMiner(BaseMiner):
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
if api_devdetails is not None:
|
||||
try:
|
||||
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
@@ -482,7 +412,7 @@ class BOSMiner(BaseMiner):
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
if api_devs:
|
||||
if api_devs is not None:
|
||||
try:
|
||||
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
|
||||
|
||||
@@ -495,17 +425,14 @@ class BOSMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
|
||||
if not api_tunerstatus:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus:
|
||||
if api_tunerstatus is not None:
|
||||
try:
|
||||
return api_tunerstatus["TUNERSTATUS"][0][
|
||||
"ApproximateMinerPowerConsumption"
|
||||
@@ -514,26 +441,26 @@ class BOSMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]:
|
||||
if not api_tunerstatus:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus:
|
||||
if api_tunerstatus is not None:
|
||||
try:
|
||||
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
|
||||
if not api_fans:
|
||||
if api_fans is None:
|
||||
try:
|
||||
api_fans = await self.api.fans()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_fans:
|
||||
if api_fans is not None:
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
@@ -543,17 +470,14 @@ class BOSMiner(BaseMiner):
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if not api_tunerstatus:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus:
|
||||
if api_tunerstatus is not None:
|
||||
errors = []
|
||||
try:
|
||||
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
@@ -580,9 +504,7 @@ class BOSMiner(BaseMiner):
|
||||
if self.light:
|
||||
return self.light
|
||||
try:
|
||||
data = (
|
||||
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
).strip()
|
||||
data = (await self.ssh.get_led_status()).strip()
|
||||
self.light = False
|
||||
if data == "50":
|
||||
self.light = True
|
||||
@@ -591,19 +513,17 @@ class BOSMiner(BaseMiner):
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
|
||||
if not api_devs:
|
||||
if api_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs:
|
||||
if api_devs is not None:
|
||||
try:
|
||||
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0
|
||||
hr_list = []
|
||||
|
||||
for board in api_devs["DEVS"]:
|
||||
_id = board["ID"] - offset
|
||||
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
|
||||
if expected_hashrate:
|
||||
hr_list.append(expected_hashrate)
|
||||
@@ -617,7 +537,7 @@ class BOSMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||
if not api_devdetails:
|
||||
if api_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.send_command(
|
||||
"devdetails", ignore_errors=True, allow_warning=False
|
||||
@@ -625,20 +545,20 @@ class BOSMiner(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
if api_devdetails is not None:
|
||||
try:
|
||||
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
@@ -652,7 +572,8 @@ BOSER_DATA_LOC = DataLocations(
|
||||
[GRPCCommand("grpc_miner_details", "get_miner_details")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [GRPCCommand("api_version", "get_api_version")]
|
||||
"_get_api_ver",
|
||||
[GRPCCommand("api_version", "get_api_version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
@@ -674,7 +595,6 @@ BOSER_DATA_LOC = DataLocations(
|
||||
"_get_hashboards",
|
||||
[GRPCCommand("grpc_hashboards", "get_hashboards")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[GRPCCommand("grpc_miner_stats", "get_miner_stats")],
|
||||
@@ -691,7 +611,6 @@ BOSER_DATA_LOC = DataLocations(
|
||||
"_get_fans",
|
||||
[GRPCCommand("grpc_cooling_state", "get_cooling_state")],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[RPCAPICommand("api_tunerstatus", "tunerstatus")],
|
||||
@@ -701,33 +620,29 @@ BOSER_DATA_LOC = DataLocations(
|
||||
[GRPCCommand("grpc_locate_device_status", "get_locate_device_status")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining", [RPCAPICommand("api_devdetails", "devdetails")]
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_devdetails", "devdetails")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BOSer(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BOSMinerAPI(ip, api_ver)
|
||||
self.web = BOSerWebAPI(ip)
|
||||
"""Handler for new versions of BraiinsOS+ (post-gRPC)"""
|
||||
|
||||
# static data
|
||||
self.api_type = "BOSMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BOSER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_autotuning = True
|
||||
self.supports_shutdown = True
|
||||
_api_cls = BOSMinerRPCAPI
|
||||
web: BOSMinerRPCAPI
|
||||
_web_cls = BOSerWebAPI
|
||||
web: BOSerWebAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
data_locations = BOSER_DATA_LOC
|
||||
|
||||
supports_autotuning = True
|
||||
supports_shutdown = True
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
resp = await self.web.grpc.set_locate_device_status(True)
|
||||
@@ -745,7 +660,7 @@ class BOSer(BaseMiner):
|
||||
return await self.restart_boser()
|
||||
|
||||
async def restart_boser(self) -> bool:
|
||||
ret = await self.web.grpc.restart()
|
||||
await self.web.grpc.restart()
|
||||
return True
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
@@ -773,11 +688,6 @@ class BOSer(BaseMiner):
|
||||
|
||||
return MinerConfig.from_boser(grpc_conf)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
raise NotImplementedError
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
self.config = config
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
try:
|
||||
result = await self.web.grpc.set_power_target(wattage)
|
||||
@@ -796,27 +706,26 @@ class BOSer(BaseMiner):
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if not grpc_miner_details:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_miner_details:
|
||||
if grpc_miner_details is not None:
|
||||
try:
|
||||
return grpc_miner_details["macAddress"].upper()
|
||||
except (LookupError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
# Now get the API version
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
api_ver = api_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
@@ -827,7 +736,7 @@ class BOSer(BaseMiner):
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if not grpc_miner_details:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
except APIError:
|
||||
@@ -835,7 +744,7 @@ class BOSer(BaseMiner):
|
||||
|
||||
fw_ver = None
|
||||
|
||||
if grpc_miner_details:
|
||||
if grpc_miner_details is not None:
|
||||
try:
|
||||
fw_ver = grpc_miner_details["bosVersion"]["current"]
|
||||
except (KeyError, TypeError):
|
||||
@@ -846,31 +755,30 @@ class BOSer(BaseMiner):
|
||||
ver = fw_ver.split("-")[5]
|
||||
if "." in ver:
|
||||
self.fw_ver = ver
|
||||
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]:
|
||||
if not grpc_miner_details:
|
||||
async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_miner_details:
|
||||
if grpc_miner_details is not None:
|
||||
try:
|
||||
return grpc_miner_details["hostname"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
@@ -879,19 +787,19 @@ class BOSer(BaseMiner):
|
||||
async def _get_expected_hashrate(
|
||||
self, grpc_miner_details: dict = None
|
||||
) -> Optional[float]:
|
||||
if not grpc_miner_details:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.grpc.get_miner_details()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_miner_details:
|
||||
if grpc_miner_details is not None:
|
||||
try:
|
||||
return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, grpc_hashboards: dict = None):
|
||||
async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
for i in range(self.expected_hashboards)
|
||||
@@ -927,9 +835,6 @@ class BOSer(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
|
||||
if grpc_miner_stats is None:
|
||||
try:
|
||||
@@ -937,7 +842,7 @@ class BOSer(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_miner_stats:
|
||||
if grpc_miner_stats is not None:
|
||||
try:
|
||||
return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
|
||||
except KeyError:
|
||||
@@ -954,7 +859,7 @@ class BOSer(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_active_performance_mode:
|
||||
if grpc_active_performance_mode is not None:
|
||||
try:
|
||||
return grpc_active_performance_mode["tunerMode"]["powerTarget"][
|
||||
"powerTarget"
|
||||
@@ -969,7 +874,7 @@ class BOSer(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if grpc_cooling_state:
|
||||
if grpc_cooling_state is not None:
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
@@ -979,17 +884,14 @@ class BOSer(BaseMiner):
|
||||
return fans
|
||||
return [Fan() for _ in range(self.expected_fans)]
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
|
||||
if not api_tunerstatus:
|
||||
if api_tunerstatus is None:
|
||||
try:
|
||||
api_tunerstatus = await self.api.tunerstatus()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_tunerstatus:
|
||||
if api_tunerstatus is not None:
|
||||
errors = []
|
||||
try:
|
||||
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
|
||||
@@ -1016,7 +918,7 @@ class BOSer(BaseMiner):
|
||||
if self.light is not None:
|
||||
return self.light
|
||||
|
||||
if not grpc_locate_device_status:
|
||||
if grpc_locate_device_status is None:
|
||||
try:
|
||||
grpc_locate_device_status = (
|
||||
await self.web.grpc.get_locate_device_status()
|
||||
@@ -1033,7 +935,7 @@ class BOSer(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
|
||||
if not api_devdetails:
|
||||
if api_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.send_command(
|
||||
"devdetails", ignore_errors=True, allow_warning=False
|
||||
@@ -1041,20 +943,20 @@ class BOSer(BaseMiner):
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
if api_devdetails is not None:
|
||||
try:
|
||||
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData, WhatsminerError
|
||||
@@ -29,6 +28,7 @@ from pyasic.miners.base import (
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.rpc.btminer import BTMinerRPCAPI
|
||||
|
||||
BTMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -40,7 +40,8 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_get_version", "get_version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_get_version", "get_version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
@@ -50,25 +51,32 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")]
|
||||
"_get_hostname",
|
||||
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_devs", "devs")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_devs", "devs")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
|
||||
"_get_env_temp", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_env_temp",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_wattage_limit",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
@@ -96,31 +104,26 @@ BTMINER_DATA_LOC = DataLocations(
|
||||
[RPCAPICommand("api_get_miner_info", "get_miner_info")],
|
||||
),
|
||||
str(DataOptions.IS_MINING): DataFunction(
|
||||
"_is_mining", [RPCAPICommand("api_status", "status")]
|
||||
"_is_mining",
|
||||
[RPCAPICommand("api_status", "status")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = BTMinerAPI(ip, api_ver)
|
||||
"""Base handler for BTMiner based miners."""
|
||||
|
||||
# static data
|
||||
self.api_type = "BTMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = BTMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_shutdown = True
|
||||
_api_cls = BTMinerRPCAPI
|
||||
api: BTMinerRPCAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
data_locations = BTMINER_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
|
||||
async def _reset_api_pwd_to_admin(self, pwd: str):
|
||||
try:
|
||||
@@ -287,26 +290,26 @@ class BTMiner(BaseMiner):
|
||||
async def _get_mac(
|
||||
self, api_summary: dict = None, api_get_miner_info: dict = None
|
||||
) -> Optional[str]:
|
||||
if not api_get_miner_info:
|
||||
if api_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_miner_info:
|
||||
if api_get_miner_info is not None:
|
||||
try:
|
||||
mac = api_get_miner_info["Msg"]["mac"]
|
||||
return str(mac).upper()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
mac = api_summary["SUMMARY"][0]["MAC"]
|
||||
return str(mac).upper()
|
||||
@@ -314,13 +317,13 @@ class BTMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]:
|
||||
if not api_get_version:
|
||||
if api_get_version is None:
|
||||
try:
|
||||
api_get_version = await self.api.get_version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_version:
|
||||
if api_get_version is not None:
|
||||
if "Code" in api_get_version.keys():
|
||||
if api_get_version["Code"] == 131:
|
||||
try:
|
||||
@@ -339,13 +342,13 @@ class BTMiner(BaseMiner):
|
||||
async def _get_fw_ver(
|
||||
self, api_get_version: dict = None, api_summary: dict = None
|
||||
) -> Optional[str]:
|
||||
if not api_get_version:
|
||||
if api_get_version is None:
|
||||
try:
|
||||
api_get_version = await self.api.get_version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_version:
|
||||
if api_get_version is not None:
|
||||
if "Code" in api_get_version.keys():
|
||||
if api_get_version["Code"] == 131:
|
||||
try:
|
||||
@@ -355,7 +358,7 @@ class BTMiner(BaseMiner):
|
||||
else:
|
||||
return self.fw_ver
|
||||
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
@@ -373,13 +376,13 @@ class BTMiner(BaseMiner):
|
||||
|
||||
async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
|
||||
hostname = None
|
||||
if not api_get_miner_info:
|
||||
if api_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
except APIError:
|
||||
return None # only one way to get this
|
||||
|
||||
if api_get_miner_info:
|
||||
if api_get_miner_info is not None:
|
||||
try:
|
||||
hostname = api_get_miner_info["Msg"]["hostname"]
|
||||
except KeyError:
|
||||
@@ -388,14 +391,13 @@ class BTMiner(BaseMiner):
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except LookupError:
|
||||
@@ -407,13 +409,13 @@ class BTMiner(BaseMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if not api_devs:
|
||||
if api_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devs:
|
||||
if api_devs is not None:
|
||||
try:
|
||||
for board in api_devs["DEVS"]:
|
||||
if len(hashboards) < board["ASC"] + 1:
|
||||
@@ -437,26 +439,26 @@ class BTMiner(BaseMiner):
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Env Temp"]
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
wattage = api_summary["SUMMARY"][0]["Power"]
|
||||
return wattage if not wattage == -1 else None
|
||||
@@ -464,13 +466,13 @@ class BTMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return api_summary["SUMMARY"][0]["Power Limit"]
|
||||
except LookupError:
|
||||
@@ -479,14 +481,14 @@ class BTMiner(BaseMiner):
|
||||
async def _get_fans(
|
||||
self, api_summary: dict = None, api_get_psu: dict = None
|
||||
) -> List[Fan]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
if self.expected_fans > 0:
|
||||
fans = [
|
||||
@@ -501,25 +503,25 @@ class BTMiner(BaseMiner):
|
||||
async def _get_fan_psu(
|
||||
self, api_summary: dict = None, api_get_psu: dict = None
|
||||
) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
if not api_get_psu:
|
||||
if api_get_psu is None:
|
||||
try:
|
||||
api_get_psu = await self.api.get_psu()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_psu:
|
||||
if api_get_psu is not None:
|
||||
try:
|
||||
return int(api_get_psu["Msg"]["fan_speed"])
|
||||
except (KeyError, TypeError):
|
||||
@@ -529,27 +531,30 @@ class BTMiner(BaseMiner):
|
||||
self, api_summary: dict = None, api_get_error_code: dict = None
|
||||
) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
if not api_get_error_code and not api_summary:
|
||||
if api_get_error_code is None and api_summary is None:
|
||||
try:
|
||||
api_get_error_code = await self.api.get_error_code()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_get_error_code:
|
||||
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 api_get_error_code is not None:
|
||||
try:
|
||||
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)))
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
|
||||
err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
|
||||
@@ -559,14 +564,14 @@ class BTMiner(BaseMiner):
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def _get_expected_hashrate(self, api_summary: dict = None):
|
||||
if not api_summary:
|
||||
async def _get_expected_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
|
||||
if expected_hashrate:
|
||||
@@ -574,15 +579,15 @@ class BTMiner(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool:
|
||||
if not api_get_miner_info:
|
||||
async def _get_fault_light(self, api_get_miner_info: dict = None) -> Optional[bool]:
|
||||
if api_get_miner_info is None:
|
||||
try:
|
||||
api_get_miner_info = await self.api.get_miner_info()
|
||||
except APIError:
|
||||
if not self.light:
|
||||
self.light = False
|
||||
|
||||
if api_get_miner_info:
|
||||
if api_get_miner_info is not None:
|
||||
try:
|
||||
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto")
|
||||
except KeyError:
|
||||
@@ -613,13 +618,13 @@ class BTMiner(BaseMiner):
|
||||
await self.api.set_hostname(hostname)
|
||||
|
||||
async def _is_mining(self, api_status: dict = None) -> Optional[bool]:
|
||||
if not api_status:
|
||||
if api_status is None:
|
||||
try:
|
||||
api_status = await self.api.status()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_status:
|
||||
if api_status is not None:
|
||||
try:
|
||||
if api_status["Msg"].get("btmineroff"):
|
||||
try:
|
||||
@@ -632,13 +637,13 @@ class BTMiner(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return int(api_summary["SUMMARY"][0]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -14,13 +14,9 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
from typing import Optional
|
||||
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
@@ -29,129 +25,49 @@ from pyasic.miners.base import (
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.rpc.cgminer import CGMinerRPCAPI
|
||||
|
||||
CGMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction("_get_mac"),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class CGMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = CGMinerAPI(ip, api_ver)
|
||||
"""Base handler for CGMiner based miners"""
|
||||
|
||||
# static data
|
||||
self.api_type = "CGMiner"
|
||||
# data gathering locations
|
||||
self.data_locations = CGMINER_DATA_LOC
|
||||
_api_cls = CGMinerRPCAPI
|
||||
api: CGMinerRPCAPI
|
||||
|
||||
# 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 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
|
||||
data_locations = CGMINER_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
@@ -163,33 +79,18 @@ class CGMiner(BaseMiner):
|
||||
self.config = MinerConfig.from_api(pools)
|
||||
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}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.api_ver = api_version["VERSION"][0]["API"]
|
||||
except LookupError:
|
||||
@@ -198,13 +99,13 @@ class CGMiner(BaseMiner):
|
||||
return self.api_ver
|
||||
|
||||
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
|
||||
if not api_version:
|
||||
if api_version is None:
|
||||
try:
|
||||
api_version = await self.api.version()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_version:
|
||||
if api_version is not None:
|
||||
try:
|
||||
self.fw_ver = api_version["VERSION"][0]["CGMiner"]
|
||||
except LookupError:
|
||||
@@ -212,19 +113,14 @@ class CGMiner(BaseMiner):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
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
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
@@ -232,141 +128,14 @@ class CGMiner(BaseMiner):
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
if len(boards) > 1:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
for i in range(
|
||||
board_offset, board_offset + self.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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -33,20 +33,24 @@ from pyasic.web.epic import ePICWebAPI
|
||||
EPIC_DATA_LOC = DataLocations(
|
||||
**{
|
||||
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(
|
||||
"_get_fw_ver", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_expected_hashrate",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
@@ -55,41 +59,41 @@ EPIC_DATA_LOC = DataLocations(
|
||||
WebAPICommand("web_hashrate", "hashrate"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
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(
|
||||
"_get_fans", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_errors",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
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(
|
||||
"_get_uptime", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class ePIC(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = ePICWebAPI(ip)
|
||||
"""Handler for miners with the ePIC board"""
|
||||
|
||||
# static data
|
||||
self.api_type = "ePIC"
|
||||
self.fw_str = "ePIC"
|
||||
# data gathering locations
|
||||
self.data_locations = EPIC_DATA_LOC
|
||||
_web_cls = ePICWebAPI
|
||||
web: ePICWebAPI
|
||||
|
||||
firmware = "ePIC"
|
||||
|
||||
data_locations = EPIC_DATA_LOC
|
||||
|
||||
supports_shutdown = True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
summary = None
|
||||
@@ -144,10 +148,14 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def _get_mac(self, web_network: dict = None) -> str:
|
||||
if not web_network:
|
||||
web_network = await self.web.network()
|
||||
if web_network:
|
||||
async def _get_mac(self, web_network: dict = None) -> Optional[str]:
|
||||
if web_network is None:
|
||||
try:
|
||||
web_network = await self.web.network()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_network is not None:
|
||||
try:
|
||||
for network in web_network:
|
||||
mac = web_network[network]["mac_address"]
|
||||
@@ -155,10 +163,14 @@ class ePIC(BaseMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hostname(self, web_summary: dict = None) -> str:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
async def _get_hostname(self, web_summary: dict = None) -> Optional[str]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
hostname = web_summary["Hostname"]
|
||||
return hostname
|
||||
@@ -166,10 +178,13 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
wattage = web_summary["Power Supply Stats"]["Input Power"]
|
||||
wattage = round(wattage)
|
||||
@@ -178,14 +193,13 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
hashrate = 0
|
||||
if web_summary["HBs"] is not None:
|
||||
@@ -196,14 +210,13 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
hashrate = 0
|
||||
if web_summary.get("HBs") is not None:
|
||||
@@ -219,10 +232,13 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
fw_ver = web_summary["Software"]
|
||||
fw_ver = fw_ver.split(" ")[1].replace("v", "")
|
||||
@@ -231,7 +247,7 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
|
||||
async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
|
||||
if not web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
@@ -239,7 +255,7 @@ class ePIC(BaseMiner):
|
||||
|
||||
fans = []
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
for fan in web_summary["Fans Rpm"]:
|
||||
try:
|
||||
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
||||
@@ -250,12 +266,13 @@ class ePIC(BaseMiner):
|
||||
async def _get_hashboards(
|
||||
self, web_summary: dict = None, web_hashrate: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if not web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
if not web_hashrate:
|
||||
|
||||
if web_hashrate is not None:
|
||||
try:
|
||||
web_hashrate = await self.web.hashrate()
|
||||
except APIError:
|
||||
@@ -283,9 +300,13 @@ class ePIC(BaseMiner):
|
||||
return None
|
||||
|
||||
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
uptime = web_summary["Session"]["Uptime"]
|
||||
return uptime
|
||||
@@ -293,10 +314,14 @@ class ePIC(BaseMiner):
|
||||
pass
|
||||
return None
|
||||
|
||||
async def _get_fault_light(self, web_summary: dict = None) -> bool:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
if web_summary:
|
||||
async def _get_fault_light(self, web_summary: dict = None) -> Optional[bool]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_summary is not None:
|
||||
try:
|
||||
light = web_summary["Misc"]["Locate Miner State"]
|
||||
return light
|
||||
@@ -306,9 +331,13 @@ class ePIC(BaseMiner):
|
||||
|
||||
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
|
||||
if not web_summary:
|
||||
web_summary = await self.web.summary()
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
errors = []
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
error = web_summary["Status"]["Last Error"]
|
||||
if error is not None:
|
||||
@@ -317,27 +346,3 @@ class ePIC(BaseMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
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,7 +13,7 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List, Optional
|
||||
from typing import List
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import HashBoard
|
||||
@@ -32,20 +32,24 @@ from pyasic.web.goldshell import GoldshellWebAPI
|
||||
GOLDSHELL_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac", [WebAPICommand("web_setting", "setting")]
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_setting", "setting")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
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(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
@@ -54,31 +58,21 @@ GOLDSHELL_DATA_LOC = DataLocations(
|
||||
RPCAPICommand("api_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(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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"),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BFGMinerGoldshell(BFGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = GoldshellWebAPI(ip)
|
||||
class GoldshellMiner(BFGMiner):
|
||||
"""Handler for goldshell miners"""
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = GOLDSHELL_DATA_LOC
|
||||
_web_cls = GoldshellWebAPI
|
||||
web: GoldshellWebAPI
|
||||
|
||||
data_locations = GOLDSHELL_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
@@ -110,26 +104,26 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
)
|
||||
|
||||
async def _get_mac(self, web_setting: dict = None) -> str:
|
||||
if not web_setting:
|
||||
if web_setting is None:
|
||||
try:
|
||||
web_setting = await self.web.setting()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_setting:
|
||||
if web_setting is not None:
|
||||
try:
|
||||
return web_setting["name"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_status: dict = None) -> str:
|
||||
if not web_status:
|
||||
if web_status is None:
|
||||
try:
|
||||
web_status = await self.web.setting()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_status:
|
||||
if web_status is not None:
|
||||
try:
|
||||
return web_status["firmware"]
|
||||
except KeyError:
|
||||
@@ -138,7 +132,7 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
async def _get_hashboards(
|
||||
self, api_devs: dict = None, api_devdetails: dict = None
|
||||
) -> List[HashBoard]:
|
||||
if not api_devs:
|
||||
if api_devs is None:
|
||||
try:
|
||||
api_devs = await self.api.devs()
|
||||
except APIError:
|
||||
@@ -149,7 +143,7 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if api_devs:
|
||||
if api_devs is not None:
|
||||
if api_devs.get("DEVS"):
|
||||
for board in api_devs["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
@@ -165,13 +159,13 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
else:
|
||||
logger.error(self, api_devs)
|
||||
|
||||
if not api_devdetails:
|
||||
if api_devdetails is None:
|
||||
try:
|
||||
api_devdetails = await self.api.devdetails()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_devdetails:
|
||||
if api_devdetails is not None:
|
||||
if api_devdetails.get("DEVS"):
|
||||
for board in api_devdetails["DEVS"]:
|
||||
if board.get("ID") is not None:
|
||||
@@ -184,9 +178,3 @@ class BFGMinerGoldshell(BFGMiner):
|
||||
logger.error(self, api_devdetails)
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
return None
|
||||
|
||||
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
|
||||
return None
|
||||
@@ -14,67 +14,8 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.data import HashBoard
|
||||
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):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
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
|
||||
firmware = "Hive"
|
||||
|
||||
@@ -40,12 +40,13 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_fw_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[
|
||||
@@ -53,9 +54,6 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate",
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[
|
||||
@@ -63,7 +61,6 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[
|
||||
@@ -83,43 +80,29 @@ INNOSILICON_DATA_LOC = DataLocations(
|
||||
WebAPICommand("web_get_all", "getAll"),
|
||||
],
|
||||
),
|
||||
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
|
||||
str(DataOptions.ERRORS): DataFunction(
|
||||
"_get_errors",
|
||||
[
|
||||
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(
|
||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_uptime",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class Innosilicon(CGMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver=api_ver)
|
||||
# interfaces
|
||||
self.web = InnosiliconWebAPI(ip)
|
||||
"""Base handler for Innosilicon miners"""
|
||||
|
||||
# static data
|
||||
# data gathering locations
|
||||
self.data_locations = INNOSILICON_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
self.supports_shutdown = True
|
||||
_web_cls = InnosiliconWebAPI
|
||||
web: InnosiliconWebAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
data_locations = INNOSILICON_DATA_LOC
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
return False
|
||||
supports_shutdown = True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# get pool data
|
||||
@@ -150,23 +133,6 @@ class Innosilicon(CGMiner):
|
||||
async def restart_backend(self) -> bool:
|
||||
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:
|
||||
self.config = config
|
||||
await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
|
||||
@@ -181,20 +147,20 @@ class Innosilicon(CGMiner):
|
||||
if web_get_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:
|
||||
web_overview = await self.web.overview()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
try:
|
||||
mac = web_get_all["mac"]
|
||||
return mac.upper()
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if web_overview:
|
||||
if web_overview is not None:
|
||||
try:
|
||||
mac = web_overview["version"]["ethaddr"]
|
||||
return mac.upper()
|
||||
@@ -207,13 +173,13 @@ class Innosilicon(CGMiner):
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if not api_summary and not web_get_all:
|
||||
if api_summary is None and web_get_all is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
try:
|
||||
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
||||
return round(
|
||||
@@ -227,7 +193,7 @@ class Innosilicon(CGMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
|
||||
except (KeyError, IndexError):
|
||||
@@ -244,13 +210,13 @@ class Innosilicon(CGMiner):
|
||||
for i in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if not web_get_all:
|
||||
if web_get_all is None:
|
||||
try:
|
||||
web_get_all = await self.web.get_all()
|
||||
except APIError:
|
||||
@@ -258,7 +224,7 @@ class Innosilicon(CGMiner):
|
||||
else:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
if api_stats.get("STATS"):
|
||||
for board in api_stats["STATS"]:
|
||||
try:
|
||||
@@ -270,7 +236,7 @@ class Innosilicon(CGMiner):
|
||||
hashboards[idx].chips = chips
|
||||
hashboards[idx].missing = False
|
||||
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
if web_get_all.get("chain"):
|
||||
for board in web_get_all["chain"]:
|
||||
idx = board.get("ASC")
|
||||
@@ -297,7 +263,7 @@ class Innosilicon(CGMiner):
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if not web_get_all:
|
||||
if web_get_all is None:
|
||||
try:
|
||||
web_get_all = await self.web.get_all()
|
||||
except APIError:
|
||||
@@ -305,19 +271,19 @@ class Innosilicon(CGMiner):
|
||||
else:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
try:
|
||||
return web_get_all["power"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
if api_stats.get("STATS"):
|
||||
for board in api_stats["STATS"]:
|
||||
try:
|
||||
@@ -332,7 +298,7 @@ class Innosilicon(CGMiner):
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if not web_get_all:
|
||||
if web_get_all is None:
|
||||
try:
|
||||
web_get_all = await self.web.get_all()
|
||||
except APIError:
|
||||
@@ -341,7 +307,7 @@ class Innosilicon(CGMiner):
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
fans = [Fan() for _ in range(self.expected_fans)]
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
try:
|
||||
spd = web_get_all["fansSpeed"]
|
||||
except KeyError:
|
||||
@@ -357,13 +323,13 @@ class Innosilicon(CGMiner):
|
||||
self, web_get_error_detail: dict = None
|
||||
) -> List[MinerErrorData]:
|
||||
errors = []
|
||||
if not web_get_error_detail:
|
||||
if web_get_error_detail is None:
|
||||
try:
|
||||
web_get_error_detail = await self.web.get_error_detail()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_get_error_detail:
|
||||
if web_get_error_detail is not None:
|
||||
try:
|
||||
# only 1 error?
|
||||
# TODO: check if this should be a loop, can't remember.
|
||||
@@ -380,7 +346,7 @@ class Innosilicon(CGMiner):
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if not web_get_all:
|
||||
if web_get_all is None:
|
||||
try:
|
||||
web_get_all = await self.web.get_all()
|
||||
except APIError:
|
||||
@@ -388,7 +354,7 @@ class Innosilicon(CGMiner):
|
||||
else:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
if web_get_all:
|
||||
if web_get_all is not None:
|
||||
try:
|
||||
level = web_get_all["running_mode"]["level"]
|
||||
except KeyError:
|
||||
@@ -398,6 +364,3 @@ class Innosilicon(CGMiner):
|
||||
level = int(level)
|
||||
limit = 1250 + (250 * level)
|
||||
return limit
|
||||
|
||||
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||
pass
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# 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.data import Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.base import (
|
||||
BaseMiner,
|
||||
@@ -27,62 +25,50 @@ from pyasic.miners.base import (
|
||||
DataOptions,
|
||||
RPCAPICommand,
|
||||
)
|
||||
from pyasic.rpc.luxminer import LUXMinerRPCAPI
|
||||
|
||||
LUXMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac", [RPCAPICommand("api_config", "config")]
|
||||
"_get_mac",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage", [RPCAPICommand("api_power", "power")]
|
||||
"_get_wattage",
|
||||
[RPCAPICommand("api_power", "power")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans", [RPCAPICommand("api_fans", "fans")]
|
||||
"_get_fans",
|
||||
[RPCAPICommand("api_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(
|
||||
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
|
||||
),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class LUXMiner(BaseMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip)
|
||||
# interfaces
|
||||
self.api = LUXMinerAPI(ip, api_ver)
|
||||
# self.web = BOSMinerWebAPI(ip)
|
||||
"""Handler for LuxOS miners"""
|
||||
|
||||
# static data
|
||||
self.api_type = "LUXMiner"
|
||||
self.fw_str = "LuxOS"
|
||||
# data gathering locations
|
||||
self.data_locations = LUXMINER_DATA_LOC
|
||||
# autotuning/shutdown support
|
||||
# self.supports_autotuning = True
|
||||
# self.supports_shutdown = True
|
||||
_api_cls = LUXMinerRPCAPI
|
||||
api: LUXMinerRPCAPI
|
||||
|
||||
# data storage
|
||||
self.api_ver = api_ver
|
||||
firmware = "LuxOS"
|
||||
|
||||
data_locations = LUXMINER_DATA_LOC
|
||||
|
||||
async def _get_session(self) -> Optional[str]:
|
||||
try:
|
||||
@@ -163,25 +149,19 @@ class LUXMiner(BaseMiner):
|
||||
async def get_config(self) -> MinerConfig:
|
||||
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}) ###
|
||||
##################################################
|
||||
|
||||
async def _get_mac(self, api_config: dict = None) -> Optional[str]:
|
||||
mac = None
|
||||
if not api_config:
|
||||
if api_config is None:
|
||||
try:
|
||||
api_config = await self.api.config()
|
||||
except APIError:
|
||||
return None
|
||||
|
||||
if api_config:
|
||||
if api_config is not None:
|
||||
try:
|
||||
mac = api_config["CONFIG"][0]["MACAddr"]
|
||||
except KeyError:
|
||||
@@ -189,26 +169,14 @@ class LUXMiner(BaseMiner):
|
||||
|
||||
return mac
|
||||
|
||||
async def get_version(self) -> Tuple[Optional[str], Optional[str]]:
|
||||
pass
|
||||
|
||||
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:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
@@ -217,13 +185,13 @@ class LUXMiner(BaseMiner):
|
||||
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = []
|
||||
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
board_offset = -1
|
||||
boards = api_stats["STATS"]
|
||||
@@ -268,27 +236,21 @@ class LUXMiner(BaseMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
return None
|
||||
|
||||
async def _get_wattage(self, api_power: dict) -> Optional[int]:
|
||||
if not api_power:
|
||||
async def _get_wattage(self, api_power: dict = None) -> Optional[int]:
|
||||
if api_power is None:
|
||||
try:
|
||||
api_power = await self.api.power()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_power:
|
||||
if api_power is not None:
|
||||
try:
|
||||
return api_power["POWER"][0]["Watts"]
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_wattage_limit(self) -> Optional[int]:
|
||||
return None
|
||||
|
||||
async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
|
||||
if not api_fans:
|
||||
if api_fans is None:
|
||||
try:
|
||||
api_fans = await self.api.fans()
|
||||
except APIError:
|
||||
@@ -296,7 +258,7 @@ class LUXMiner(BaseMiner):
|
||||
|
||||
fans = []
|
||||
|
||||
if api_fans:
|
||||
if api_fans is not None:
|
||||
for fan in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(api_fans["FANS"][fan]["RPM"]))
|
||||
@@ -304,23 +266,14 @@ class LUXMiner(BaseMiner):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
return 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:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
expected_rate = api_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
@@ -336,17 +289,14 @@ class LUXMiner(BaseMiner):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _is_mining(self) -> Optional[bool]:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
|
||||
if not api_stats:
|
||||
if api_stats is None:
|
||||
try:
|
||||
api_stats = await self.api.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_stats:
|
||||
if api_stats is not None:
|
||||
try:
|
||||
return int(api_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
|
||||
@@ -18,7 +18,6 @@ from typing import Optional
|
||||
|
||||
from pyasic import MinerConfig
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.base import (
|
||||
DataFunction,
|
||||
@@ -32,57 +31,58 @@ from pyasic.web.vnish import VNishWebAPI
|
||||
VNISH_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.MAC): DataFunction(
|
||||
"_get_mac", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_mac",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver", [RPCAPICommand("api_version", "version")]
|
||||
"_get_api_ver",
|
||||
[RPCAPICommand("api_version", "version")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
|
||||
"_get_hashrate",
|
||||
[RPCAPICommand("api_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
|
||||
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_expected_hashrate",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
|
||||
"_get_hashboards",
|
||||
[RPCAPICommand("api_stats", "stats")],
|
||||
),
|
||||
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage", [WebAPICommand("web_summary", "summary")]
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.WATTAGE_LIMIT): DataFunction(
|
||||
"_get_wattage_limit", [WebAPICommand("web_settings", "settings")]
|
||||
"_get_wattage_limit",
|
||||
[WebAPICommand("web_settings", "settings")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans", [RPCAPICommand("api_stats", "stats")]
|
||||
"_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"),
|
||||
str(DataOptions.CONFIG): DataFunction("get_config"),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class VNish(BMMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None:
|
||||
super().__init__(ip, api_ver)
|
||||
# interfaces
|
||||
self.web = VNishWebAPI(ip)
|
||||
"""Handler for VNish miners"""
|
||||
|
||||
# static data
|
||||
self.api_type = "VNish"
|
||||
self.fw_str = "VNish"
|
||||
# data gathering locations
|
||||
self.data_locations = VNISH_DATA_LOC
|
||||
_web_cls = VNishWebAPI
|
||||
web: VNishWebAPI
|
||||
|
||||
firmware = "VNish"
|
||||
|
||||
data_locations = VNISH_DATA_LOC
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
data = await self.web.restart_vnish()
|
||||
@@ -121,17 +121,17 @@ class VNish(BMMiner):
|
||||
return False
|
||||
|
||||
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()
|
||||
|
||||
if web_info:
|
||||
if web_info is not None:
|
||||
try:
|
||||
mac = web_info["system"]["network_status"]["mac"]
|
||||
return mac
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
mac = web_summary["system"]["network_status"]["mac"]
|
||||
return mac
|
||||
@@ -139,17 +139,17 @@ class VNish(BMMiner):
|
||||
pass
|
||||
|
||||
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()
|
||||
|
||||
if web_info:
|
||||
if web_info is not None:
|
||||
try:
|
||||
hostname = web_info["system"]["network_status"]["hostname"]
|
||||
return hostname
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
hostname = web_summary["system"]["network_status"]["hostname"]
|
||||
return hostname
|
||||
@@ -157,10 +157,10 @@ class VNish(BMMiner):
|
||||
pass
|
||||
|
||||
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()
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
wattage = web_summary["miner"]["power_usage"]
|
||||
wattage = round(wattage * 1000)
|
||||
@@ -170,26 +170,25 @@ class VNish(BMMiner):
|
||||
|
||||
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
|
||||
# get hr from API
|
||||
if not api_summary:
|
||||
if api_summary is None:
|
||||
try:
|
||||
api_summary = await self.api.summary()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if api_summary:
|
||||
if api_summary is not None:
|
||||
try:
|
||||
return round(
|
||||
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
|
||||
)
|
||||
except (LookupError, ValueError, TypeError) as e:
|
||||
logger.error(e)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
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()
|
||||
|
||||
if web_settings:
|
||||
if web_settings is not None:
|
||||
try:
|
||||
wattage_limit = web_settings["miner"]["overclock"]["preset"]
|
||||
if wattage_limit == "disabled":
|
||||
@@ -199,10 +198,10 @@ class VNish(BMMiner):
|
||||
pass
|
||||
|
||||
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()
|
||||
|
||||
if web_summary:
|
||||
if web_summary is not None:
|
||||
try:
|
||||
fw_ver = web_summary["miner"]["miner_type"]
|
||||
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
|
||||
@@ -210,12 +209,6 @@ class VNish(BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
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:
|
||||
try:
|
||||
web_settings = await self.web.settings()
|
||||
|
||||
@@ -17,21 +17,15 @@ from pyasic.miners.backends.btminer import BTMiner
|
||||
|
||||
|
||||
class M6X(BTMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_autotuning = True
|
||||
supports_autotuning = True
|
||||
|
||||
|
||||
class M5X(BTMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_autotuning = True
|
||||
supports_autotuning = True
|
||||
|
||||
|
||||
class M3X(BTMiner):
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.supports_autotuning = True
|
||||
supports_autotuning = True
|
||||
|
||||
|
||||
class M2X(BTMiner):
|
||||
|
||||
@@ -15,13 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
import asyncio
|
||||
import ipaddress
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
import warnings
|
||||
from dataclasses import dataclass, field, make_dataclass
|
||||
from enum import Enum
|
||||
from typing import List, Optional, Tuple, TypeVar, Union
|
||||
|
||||
import asyncssh
|
||||
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
@@ -52,6 +49,14 @@ class DataOptions(Enum):
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
def default_command(self):
|
||||
if str(self.value) == "config":
|
||||
return "get_config"
|
||||
elif str(self.value) == "is_mining":
|
||||
return "_is_mining"
|
||||
else:
|
||||
return f"_get_{str(self.value)}"
|
||||
|
||||
|
||||
@dataclass
|
||||
class RPCAPICommand:
|
||||
@@ -80,54 +85,51 @@ class GraphQLCommand(WebAPICommand):
|
||||
@dataclass
|
||||
class DataFunction:
|
||||
cmd: str
|
||||
kwargs: list[
|
||||
kwargs: List[
|
||||
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
|
||||
] = field(default_factory=list)
|
||||
|
||||
|
||||
DataLocations = make_dataclass(
|
||||
"DataLocations",
|
||||
[(enum_value.value, str) for enum_value in DataOptions],
|
||||
[
|
||||
(
|
||||
enum_value.value,
|
||||
DataFunction,
|
||||
field(default_factory=lambda: DataFunction(enum_value.default_command())),
|
||||
)
|
||||
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
|
||||
class MinerProtocol(Protocol):
|
||||
_api_cls: Type = None
|
||||
_web_cls: Type = None
|
||||
_ssh_cls: Type = None
|
||||
|
||||
self.ssh_pwd = "root"
|
||||
ip: str = None
|
||||
api: _api_cls = None
|
||||
web: _web_cls = None
|
||||
ssh: _ssh_cls = None
|
||||
|
||||
# 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
|
||||
make: str = None
|
||||
raw_model: str = None
|
||||
firmware: str = None
|
||||
|
||||
# data storage
|
||||
self.api_ver = None
|
||||
self.fw_ver = None
|
||||
self.light = None
|
||||
self.config = None
|
||||
expected_hashboards: int = 3
|
||||
expected_chips: int = None
|
||||
expected_fans: int = 2
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
if cls is BaseMiner:
|
||||
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated")
|
||||
return object.__new__(cls)
|
||||
data_locations: DataLocations = None
|
||||
|
||||
supports_shutdown: bool = False
|
||||
supports_autotuning: bool = False
|
||||
|
||||
api_ver: str = None
|
||||
fw_ver: str = None
|
||||
light: bool = None
|
||||
config: MinerConfig = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"{self.model}: {str(self.ip)}"
|
||||
@@ -142,110 +144,31 @@ class BaseMiner(ABC):
|
||||
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
|
||||
|
||||
@property
|
||||
def model(self):
|
||||
def model(self) -> str:
|
||||
model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
|
||||
if self.fw_str is not None:
|
||||
model_data.append(f"({self.fw_str})")
|
||||
if self.firmware is not None:
|
||||
model_data.append(f"({self.firmware})")
|
||||
return " ".join(model_data)
|
||||
|
||||
@property
|
||||
def pwd(self): # noqa - Skip PyCharm inspection
|
||||
data = []
|
||||
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:
|
||||
return await self.get_fault_light()
|
||||
|
||||
@abstractmethod
|
||||
async def fault_light_on(self) -> bool:
|
||||
"""Turn the fault light of the miner on and return success as a boolean.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of turning the light on.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
async def fault_light_off(self) -> bool:
|
||||
"""Turn the fault light of the miner off and return success as a boolean.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of turning the light off.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
async def get_config(self) -> MinerConfig:
|
||||
# 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].
|
||||
@@ -253,27 +176,24 @@ class BaseMiner(ABC):
|
||||
Returns:
|
||||
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
|
||||
"""
|
||||
pass
|
||||
return MinerConfig()
|
||||
|
||||
@abstractmethod
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboot the miner and return success as a boolean.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of rebooting the miner.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
async def restart_backend(self) -> bool:
|
||||
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
|
||||
|
||||
Returns:
|
||||
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:
|
||||
"""Set the mining configuration of the miner.
|
||||
|
||||
@@ -283,25 +203,22 @@ class BaseMiner(ABC):
|
||||
"""
|
||||
return None
|
||||
|
||||
@abstractmethod
|
||||
async def stop_mining(self) -> bool:
|
||||
"""Stop the mining process of the miner.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of stopping the mining process.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
async def resume_mining(self) -> bool:
|
||||
"""Resume the mining process of the miner.
|
||||
|
||||
Returns:
|
||||
A boolean value of the success of resuming the mining process.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
@abstractmethod
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
"""Set the power limit to be used by the miner.
|
||||
|
||||
@@ -311,7 +228,7 @@ class BaseMiner(ABC):
|
||||
Returns:
|
||||
A boolean value of the success of setting the power limit.
|
||||
"""
|
||||
pass
|
||||
return False
|
||||
|
||||
##################################################
|
||||
### DATA GATHERING FUNCTIONS (get_{some_data}) ###
|
||||
@@ -463,68 +380,52 @@ class BaseMiner(ABC):
|
||||
"""
|
||||
return await self._get_uptime()
|
||||
|
||||
@abstractmethod
|
||||
async def _get_mac(self, *args, **kwargs) -> Optional[str]:
|
||||
async def _get_mac(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_api_ver(self, *args, **kwargs) -> Optional[str]:
|
||||
async def _get_api_ver(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]:
|
||||
async def _get_fw_ver(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_hostname(self, *args, **kwargs) -> Optional[str]:
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_hashrate(self, *args, **kwargs) -> Optional[float]:
|
||||
async def _get_hashrate(self) -> Optional[float]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_hashboards(self, *args, **kwargs) -> List[HashBoard]:
|
||||
async def _get_hashboards(self) -> List[HashBoard]:
|
||||
return []
|
||||
|
||||
async def _get_env_temp(self) -> Optional[float]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_env_temp(self, *args, **kwargs) -> Optional[float]:
|
||||
async def _get_wattage(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_wattage(self, *args, **kwargs) -> Optional[int]:
|
||||
async def _get_wattage_limit(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
|
||||
async def _get_fans(self) -> List[Fan]:
|
||||
return []
|
||||
|
||||
async def _get_fan_psu(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_fans(self, *args, **kwargs) -> List[Fan]:
|
||||
async def _get_errors(self) -> List[MinerErrorData]:
|
||||
return []
|
||||
|
||||
async def _get_fault_light(self) -> Optional[bool]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]:
|
||||
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
|
||||
async def _is_mining(self) -> Optional[bool]:
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
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]:
|
||||
async def _get_uptime(self) -> Optional[int]:
|
||||
pass
|
||||
|
||||
async def _get_data(
|
||||
@@ -633,7 +534,9 @@ class BaseMiner(ABC):
|
||||
ip=str(self.ip),
|
||||
make=self.make,
|
||||
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,
|
||||
hashboards=[
|
||||
HashBoard(slot=i, expected_chips=self.expected_chips)
|
||||
@@ -651,4 +554,23 @@ class BaseMiner(ABC):
|
||||
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._api_cls is not None:
|
||||
self.api = self._api_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)
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import BFGMinerGoldshell
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import CK5
|
||||
|
||||
|
||||
class BFGMinerCK5(BFGMinerGoldshell, CK5):
|
||||
class GoldshellCK5(GoldshellMiner, CK5):
|
||||
pass
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import BFGMinerGoldshell
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import HS5
|
||||
|
||||
|
||||
class BFGMinerHS5(BFGMinerGoldshell, HS5):
|
||||
class GoldshellHS5(GoldshellMiner, HS5):
|
||||
pass
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import BFGMinerGoldshell
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import KD5
|
||||
|
||||
|
||||
class BFGMinerKD5(BFGMinerGoldshell, KD5):
|
||||
class GoldshellKD5(GoldshellMiner, KD5):
|
||||
pass
|
||||
|
||||
@@ -13,6 +13,6 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .CK5 import BFGMinerCK5
|
||||
from .HS5 import BFGMinerHS5
|
||||
from .KD5 import BFGMinerKD5
|
||||
from .CK5 import GoldshellCK5
|
||||
from .HS5 import GoldshellHS5
|
||||
from .KD5 import GoldshellKD5
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.backends import BFGMinerGoldshell
|
||||
from pyasic.miners.backends import GoldshellMiner
|
||||
from pyasic.miners.types import KDMax
|
||||
|
||||
|
||||
class BFGMinerKDMax(BFGMinerGoldshell, KDMax):
|
||||
class GoldshellKDMax(GoldshellMiner, KDMax):
|
||||
pass
|
||||
|
||||
@@ -13,4 +13,4 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from .KDMax import BFGMinerKDMax
|
||||
from .KDMax import GoldshellKDMax
|
||||
|
||||
@@ -17,31 +17,21 @@
|
||||
from pyasic.miners.base import BaseMiner
|
||||
|
||||
|
||||
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.make = "WhatsMiner"
|
||||
class WhatsMinerMake(BaseMiner):
|
||||
make = "WhatsMiner"
|
||||
|
||||
|
||||
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.make = "AntMiner"
|
||||
class AntMinerMake(BaseMiner):
|
||||
make = "AntMiner"
|
||||
|
||||
|
||||
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.make = "AvalonMiner"
|
||||
class AvalonMinerMake(BaseMiner):
|
||||
make = "AvalonMiner"
|
||||
|
||||
|
||||
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.make = "Innosilicon"
|
||||
class InnosiliconMake(BaseMiner):
|
||||
make = "Innosilicon"
|
||||
|
||||
|
||||
class GoldshellMiner(BaseMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.make = "Goldshell"
|
||||
class GoldshellMake(BaseMiner):
|
||||
make = "Goldshell"
|
||||
|
||||
@@ -28,17 +28,17 @@ from pyasic.logger import logger
|
||||
from pyasic.miners.antminer import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
from pyasic.miners.backends import (
|
||||
BFGMiner,
|
||||
AvalonMiner,
|
||||
BMMiner,
|
||||
BOSMiner,
|
||||
BTMiner,
|
||||
CGMiner,
|
||||
CGMinerAvalon,
|
||||
GoldshellMiner,
|
||||
Hiveon,
|
||||
LUXMiner,
|
||||
VNish,
|
||||
ePIC,
|
||||
)
|
||||
from pyasic.miners.backends.innosilicon import Innosilicon
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.goldshell import *
|
||||
from pyasic.miners.innosilicon import *
|
||||
@@ -310,7 +310,7 @@ MINER_CLASSES = {
|
||||
"M66SVK40": BTMinerM66SVK40,
|
||||
},
|
||||
MinerTypes.AVALONMINER: {
|
||||
None: CGMinerAvalon,
|
||||
None: AvalonMiner,
|
||||
"AVALONMINER 721": CGMinerAvalon721,
|
||||
"AVALONMINER 741": CGMinerAvalon741,
|
||||
"AVALONMINER 761": CGMinerAvalon761,
|
||||
@@ -325,16 +325,16 @@ MINER_CLASSES = {
|
||||
"AVALONMINER 1246": CGMinerAvalon1246,
|
||||
},
|
||||
MinerTypes.INNOSILICON: {
|
||||
None: CGMiner,
|
||||
None: Innosilicon,
|
||||
"T3H+": InnosiliconT3HPlus,
|
||||
"A10X": InnosiliconA10X,
|
||||
},
|
||||
MinerTypes.GOLDSHELL: {
|
||||
None: BFGMiner,
|
||||
"GOLDSHELL CK5": BFGMinerCK5,
|
||||
"GOLDSHELL HS5": BFGMinerHS5,
|
||||
"GOLDSHELL KD5": BFGMinerKD5,
|
||||
"GOLDSHELL KDMAX": BFGMinerKDMax,
|
||||
None: GoldshellMiner,
|
||||
"GOLDSHELL CK5": GoldshellCK5,
|
||||
"GOLDSHELL HS5": GoldshellHS5,
|
||||
"GOLDSHELL KD5": GoldshellKD5,
|
||||
"GOLDSHELL KDMAX": GoldshellKDMax,
|
||||
},
|
||||
MinerTypes.BRAIINS_OS: {
|
||||
None: BOSMiner,
|
||||
@@ -654,7 +654,7 @@ class MinerFactory:
|
||||
return MinerTypes.HIVEON
|
||||
if "LUXMINER" in upper_data:
|
||||
return MinerTypes.LUX_OS
|
||||
if "ANTMINER" in upper_data and not "DEVDETAILS" in upper_data:
|
||||
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
|
||||
return MinerTypes.ANTMINER
|
||||
if "INTCHAINS_QOMO" in upper_data:
|
||||
return MinerTypes.GOLDSHELL
|
||||
@@ -761,7 +761,7 @@ class MinerFactory:
|
||||
str_data = ",".join(str_data.split(",")[:-1]) + "}"
|
||||
|
||||
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
|
||||
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
|
||||
if re.search(r"\"error_code\":\[\".+\"]", str_data):
|
||||
str_data = str_data.replace("[", "{").replace("]", "}")
|
||||
|
||||
return str_data
|
||||
|
||||
@@ -55,11 +55,11 @@ class MinerListener(metaclass=Singleton):
|
||||
|
||||
loop = asyncio.get_running_loop()
|
||||
|
||||
transport_14235, protocol_14235 = await loop.create_datagram_endpoint(
|
||||
lambda: _MinerListener(), local_addr=("0.0.0.0", 14235) # noqa
|
||||
transport_14235, _ = await loop.create_datagram_endpoint(
|
||||
_MinerListener, local_addr=("0.0.0.0", 14235)
|
||||
)
|
||||
transport_8888, protocol_8888 = await loop.create_datagram_endpoint(
|
||||
lambda: _MinerListener(), local_addr=("0.0.0.0", 8888) # noqa
|
||||
transport_8888, _ = await loop.create_datagram_endpoint(
|
||||
_MinerListener, local_addr=("0.0.0.0", 8888)
|
||||
)
|
||||
|
||||
while True:
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class Z15(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Z15"
|
||||
self.expected_chips = 3
|
||||
self.fan_count = 2
|
||||
class Z15(AntMinerMake):
|
||||
raw_model = "Z15"
|
||||
expected_chips = 3
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,39 +14,28 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class S17(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S17"
|
||||
self.expected_chips = 48
|
||||
self.fan_count = 4
|
||||
class S17(AntMinerMake):
|
||||
raw_model = "S17"
|
||||
expected_chips = 48
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.raw_model = "S17+"
|
||||
self.expected_chips = 65
|
||||
self.fan_count = 4
|
||||
class S17Plus(AntMinerMake):
|
||||
raw_model = "S17+"
|
||||
expected_chips = 65
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S17Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S17 Pro"
|
||||
self.expected_chips = 48
|
||||
self.fan_count = 4
|
||||
class S17Pro(AntMinerMake):
|
||||
raw_model = "S17 Pro"
|
||||
expected_chips = 48
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S17e(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S17e"
|
||||
self.expected_chips = 135
|
||||
self.fan_count = 4
|
||||
class S17e(AntMinerMake):
|
||||
raw_model = "S17e"
|
||||
expected_chips = 135
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,31 +14,22 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class T17(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "T17"
|
||||
self.expected_chips = 30
|
||||
self.fan_count = 4
|
||||
class T17(AntMinerMake):
|
||||
raw_model = "T17"
|
||||
expected_chips = 30
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class T17Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "T17+"
|
||||
self.expected_chips = 44
|
||||
self.fan_count = 4
|
||||
class T17Plus(AntMinerMake):
|
||||
raw_model = "T17+"
|
||||
expected_chips = 44
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class T17e(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "T17e"
|
||||
self.expected_chips = 78
|
||||
self.fan_count = 4
|
||||
class T17e(AntMinerMake):
|
||||
raw_model = "T17e"
|
||||
expected_chips = 78
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,158 +14,107 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class S19(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19"
|
||||
self.expected_chips = 76
|
||||
self.fan_count = 4
|
||||
class S19(AntMinerMake):
|
||||
raw_model = "S19"
|
||||
expected_chips = 76
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19 No PIC"
|
||||
self.expected_chips = 88
|
||||
self.fan_count = 4
|
||||
class S19NoPIC(AntMinerMake):
|
||||
raw_model = "S19 No PIC"
|
||||
expected_chips = 88
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19 Pro"
|
||||
self.expected_chips = 114
|
||||
self.fan_count = 4
|
||||
class S19Pro(AntMinerMake):
|
||||
raw_model = "S19 Pro"
|
||||
expected_chips = 114
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19i(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19i"
|
||||
self.expected_chips = 80
|
||||
self.fan_count = 4
|
||||
class S19i(AntMinerMake):
|
||||
raw_model = "S19i"
|
||||
expected_chips = 80
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19+"
|
||||
self.expected_chips = 80
|
||||
self.fan_count = 4
|
||||
class S19Plus(AntMinerMake):
|
||||
raw_model = "S19+"
|
||||
expected_chips = 80
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19 Pro+"
|
||||
self.expected_chips = 120
|
||||
self.fan_count = 4
|
||||
class S19ProPlus(AntMinerMake):
|
||||
raw_model = "S19 Pro+"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19XP(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19 XP"
|
||||
self.expected_chips = 110
|
||||
self.fan_count = 4
|
||||
class S19XP(AntMinerMake):
|
||||
raw_model = "S19 XP"
|
||||
expected_chips = 110
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19a(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19a"
|
||||
self.expected_chips = 72
|
||||
self.fan_count = 4
|
||||
class S19a(AntMinerMake):
|
||||
raw_model = "S19a"
|
||||
expected_chips = 72
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19aPro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19a Pro"
|
||||
self.expected_chips = 100
|
||||
self.fan_count = 4
|
||||
class S19aPro(AntMinerMake):
|
||||
raw_model = "S19a Pro"
|
||||
expected_chips = 100
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19j(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19j"
|
||||
self.expected_chips = 114
|
||||
self.fan_count = 4
|
||||
class S19j(AntMinerMake):
|
||||
raw_model = "S19j"
|
||||
expected_chips = 114
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19j No PIC"
|
||||
self.expected_chips = 88
|
||||
self.fan_count = 4
|
||||
class S19jNoPIC(AntMinerMake):
|
||||
raw_model = "S19j No PIC"
|
||||
expected_chips = 88
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jPro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19j Pro"
|
||||
self.expected_chips = 126
|
||||
self.fan_count = 4
|
||||
class S19jPro(AntMinerMake):
|
||||
raw_model = "S19j Pro"
|
||||
expected_chips = 126
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19j Pro+"
|
||||
self.expected_chips = 120
|
||||
self.fan_count = 4
|
||||
class S19jProPlus(AntMinerMake):
|
||||
raw_model = "S19j Pro+"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19kPro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19k Pro"
|
||||
self.expected_chips = 77
|
||||
self.fan_count = 4
|
||||
class S19kPro(AntMinerMake):
|
||||
raw_model = "S19k Pro"
|
||||
expected_chips = 77
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19L(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19L"
|
||||
self.expected_chips = 76
|
||||
self.fan_count = 4
|
||||
class S19L(AntMinerMake):
|
||||
raw_model = "S19L"
|
||||
expected_chips = 76
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19k Pro No PIC"
|
||||
self.expected_chips = 77
|
||||
self.fan_count = 4
|
||||
class S19kProNoPIC(AntMinerMake):
|
||||
raw_model = "S19k Pro No PIC"
|
||||
expected_chips = 77
|
||||
expected_fans = 4
|
||||
|
||||
|
||||
class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S19 Pro Hydro"
|
||||
self.expected_chips = 180
|
||||
self.expected_hashboards = 4
|
||||
self.fan_count = 0
|
||||
class S19ProHydro(AntMinerMake):
|
||||
raw_model = "S19 Pro Hydro"
|
||||
expected_chips = 180
|
||||
expected_hashboards = 4
|
||||
expected_fans = 0
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class T19(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "T19"
|
||||
self.expected_chips = 76
|
||||
self.fan_count = 4
|
||||
class T19(AntMinerMake):
|
||||
raw_model = "T19"
|
||||
expected_chips = 76
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,14 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class D3(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "D3"
|
||||
self.expected_chips = 60
|
||||
self.expected_hashboards = 3
|
||||
self.fan_count = 2
|
||||
class D3(AntMinerMake):
|
||||
raw_model = "D3"
|
||||
|
||||
expected_chips = 60
|
||||
expected_hashboards = 3
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,14 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class HS3(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "HS3"
|
||||
self.expected_chips = 92
|
||||
self.expected_hashboards = 3
|
||||
self.fan_count = 2
|
||||
class HS3(AntMinerMake):
|
||||
raw_model = "HS3"
|
||||
|
||||
expected_chips = 92
|
||||
expected_hashboards = 3
|
||||
expected_fans = 2
|
||||
|
||||
@@ -13,13 +13,10 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class L3Plus(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "L3+"
|
||||
self.expected_chips = 72
|
||||
self.fan_count = 2
|
||||
class L3Plus(AntMinerMake):
|
||||
raw_model = "L3+"
|
||||
expected_chips = 72
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class DR5(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "DR5"
|
||||
self.expected_chips = 72
|
||||
self.expected_hashboards = 3
|
||||
self.fan_count = 2
|
||||
class DR5(AntMinerMake):
|
||||
raw_model = "DR5"
|
||||
expected_chips = 72
|
||||
expected_hashboards = 3
|
||||
expected_fans = 2
|
||||
|
||||
@@ -13,13 +13,10 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class L7(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "L7"
|
||||
self.expected_chips = 120
|
||||
self.fan_count = 4
|
||||
class L7(AntMinerMake):
|
||||
raw_model = "L7"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class E9Pro(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "E9Pro"
|
||||
self.expected_chips = 8
|
||||
self.expected_hashboards = 2
|
||||
self.fan_count = 4
|
||||
class E9Pro(AntMinerMake):
|
||||
raw_model = "E9Pro"
|
||||
expected_chips = 8
|
||||
expected_hashboards = 2
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,31 +14,22 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class S9(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S9"
|
||||
self.expected_chips = 63
|
||||
self.fan_count = 2
|
||||
class S9(AntMinerMake):
|
||||
raw_model = "S9"
|
||||
expected_chips = 63
|
||||
expected_fans = 2
|
||||
|
||||
|
||||
class S9i(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S9i"
|
||||
self.expected_chips = 63
|
||||
self.fan_count = 2
|
||||
class S9i(AntMinerMake):
|
||||
raw_model = "S9i"
|
||||
expected_chips = 63
|
||||
expected_fans = 2
|
||||
|
||||
|
||||
class S9j(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "S9j"
|
||||
self.expected_chips = 63
|
||||
self.fan_count = 2
|
||||
class S9j(AntMinerMake):
|
||||
raw_model = "S9j"
|
||||
expected_chips = 63
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AntMiner
|
||||
from pyasic.miners.makes import AntMinerMake
|
||||
|
||||
|
||||
class T9(AntMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "T9"
|
||||
self.expected_chips = 54
|
||||
self.fan_count = 2
|
||||
class T9(AntMinerMake):
|
||||
raw_model = "T9"
|
||||
expected_chips = 54
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 1026"
|
||||
self.expected_chips = 80
|
||||
self.fan_count = 2
|
||||
class Avalon1026(AvalonMinerMake):
|
||||
raw_model = "Avalon 1026"
|
||||
expected_chips = 80
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 1047"
|
||||
self.expected_chips = 80
|
||||
self.fan_count = 2
|
||||
class Avalon1047(AvalonMinerMake):
|
||||
raw_model = "Avalon 1047"
|
||||
expected_chips = 80
|
||||
expected_fans = 2
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 1066"
|
||||
self.expected_chips = 114
|
||||
self.fan_count = 4
|
||||
class Avalon1066(AvalonMinerMake):
|
||||
raw_model = "Avalon 1066"
|
||||
expected_chips = 114
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1166Pro(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 1166 Pro"
|
||||
self.expected_chips = 120
|
||||
self.fan_count = 4
|
||||
class Avalon1166Pro(AvalonMinerMake):
|
||||
raw_model = "Avalon 1166 Pro"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,13 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon1246(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 1246"
|
||||
self.expected_chips = 120
|
||||
self.fan_count = 4
|
||||
class Avalon1246(AvalonMinerMake):
|
||||
raw_model = "Avalon 1246"
|
||||
expected_chips = 120
|
||||
expected_fans = 4
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 721"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 18
|
||||
self.fan_count = 1
|
||||
class Avalon721(AvalonMinerMake):
|
||||
raw_model = "Avalon 721"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 18
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 741"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 22
|
||||
self.fan_count = 1
|
||||
class Avalon741(AvalonMinerMake):
|
||||
raw_model = "Avalon 741"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 22
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 761"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 18
|
||||
self.fan_count = 1
|
||||
class Avalon761(AvalonMinerMake):
|
||||
raw_model = "Avalon 761"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 18
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 821"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 26
|
||||
self.fan_count = 1
|
||||
class Avalon821(AvalonMinerMake):
|
||||
raw_model = "Avalon 821"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 26
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 841"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 26
|
||||
self.fan_count = 1
|
||||
class Avalon841(AvalonMinerMake):
|
||||
raw_model = "Avalon 841"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 26
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 851"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 26
|
||||
self.fan_count = 1
|
||||
class Avalon851(AvalonMinerMake):
|
||||
raw_model = "Avalon 851"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 26
|
||||
expected_fans = 1
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pyasic.miners.makes import AvalonMiner
|
||||
from pyasic.miners.makes import AvalonMinerMake
|
||||
|
||||
|
||||
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "Avalon 921"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 26
|
||||
self.fan_count = 1
|
||||
class Avalon921(AvalonMinerMake):
|
||||
raw_model = "Avalon 921"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 26
|
||||
expected_fans = 1
|
||||
|
||||
@@ -13,14 +13,11 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import GoldshellMiner
|
||||
from pyasic.miners.makes import GoldshellMake
|
||||
|
||||
|
||||
class CK5(GoldshellMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "CK5"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 46
|
||||
self.fan_count = 4
|
||||
class CK5(GoldshellMake):
|
||||
raw_model = "CK5"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 46
|
||||
expected_fans = 4
|
||||
|
||||
@@ -13,14 +13,11 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import GoldshellMiner
|
||||
from pyasic.miners.makes import GoldshellMake
|
||||
|
||||
|
||||
class HS5(GoldshellMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "HS5"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 46
|
||||
self.fan_count = 4
|
||||
class HS5(GoldshellMake):
|
||||
raw_model = "HS5"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 46
|
||||
expected_fans = 4
|
||||
|
||||
@@ -13,14 +13,11 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import GoldshellMiner
|
||||
from pyasic.miners.makes import GoldshellMake
|
||||
|
||||
|
||||
class KD5(GoldshellMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "KD5"
|
||||
self.expected_hashboards = 4
|
||||
self.expected_chips = 46
|
||||
self.fan_count = 4
|
||||
class KD5(GoldshellMake):
|
||||
raw_model = "KD5"
|
||||
expected_hashboards = 4
|
||||
expected_chips = 46
|
||||
expected_fans = 4
|
||||
|
||||
@@ -13,14 +13,11 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pyasic.miners.makes import GoldshellMiner
|
||||
from pyasic.miners.makes import GoldshellMake
|
||||
|
||||
|
||||
class KDMax(GoldshellMiner): # noqa - ignore ABC method implementation
|
||||
def __init__(self, ip: str, api_ver: str = "0.0.0"):
|
||||
super().__init__(ip, api_ver)
|
||||
self.ip = ip
|
||||
self.raw_model = "KD Max"
|
||||
self.expected_hashboards = 3
|
||||
self.expected_chips = 84
|
||||
self.fan_count = 4
|
||||
class KDMax(GoldshellMake):
|
||||
raw_model = "KD Max"
|
||||
expected_hashboards = 3
|
||||
expected_chips = 84
|
||||
expected_fans = 4
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user