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:
b-rowan
2024-01-21 10:39:01 -07:00
committed by GitHub
174 changed files with 2567 additions and 5587 deletions

View File

@@ -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

View File

@@ -122,24 +122,24 @@ if __name__ == "__main__":
## Miner control ## Miner control
--- ---
`pyasic` exposes a standard interface for each miner using control functions. `pyasic` exposes a standard interface for each miner using control functions.
Every miner class in `pyasic` must implement all the control functions defined in [`BaseMiner`][pyasic.miners.BaseMiner]. Every miner class in `pyasic` must implement all the control functions defined in [`MinerProtocol`][pyasic.miners.base.MinerProtocol].
These functions are These functions are
[`check_light`][pyasic.miners.BaseMiner.check_light], [`check_light`][pyasic.miners.base.MinerProtocol.check_light],
[`fault_light_off`][pyasic.miners.BaseMiner.fault_light_off], [`fault_light_off`][pyasic.miners.base.MinerProtocol.fault_light_off],
[`fault_light_on`][pyasic.miners.BaseMiner.fault_light_on], [`fault_light_on`][pyasic.miners.base.MinerProtocol.fault_light_on],
[`get_config`][pyasic.miners.BaseMiner.get_config], [`get_config`][pyasic.miners.base.MinerProtocol.get_config],
[`get_data`][pyasic.miners.BaseMiner.get_data], [`get_data`][pyasic.miners.base.MinerProtocol.get_data],
[`get_errors`][pyasic.miners.BaseMiner.get_errors], [`get_errors`][pyasic.miners.base.MinerProtocol.get_errors],
[`get_hostname`][pyasic.miners.BaseMiner.get_hostname], [`get_hostname`][pyasic.miners.base.MinerProtocol.get_hostname],
[`get_model`][pyasic.miners.BaseMiner.get_model], [`get_model`][pyasic.miners.base.MinerProtocol.get_model],
[`reboot`][pyasic.miners.BaseMiner.reboot], [`reboot`][pyasic.miners.base.MinerProtocol.reboot],
[`restart_backend`][pyasic.miners.BaseMiner.restart_backend], [`restart_backend`][pyasic.miners.base.MinerProtocol.restart_backend],
[`stop_mining`][pyasic.miners.BaseMiner.stop_mining], [`stop_mining`][pyasic.miners.base.MinerProtocol.stop_mining],
[`resume_mining`][pyasic.miners.BaseMiner.resume_mining], [`resume_mining`][pyasic.miners.base.MinerProtocol.resume_mining],
[`is_mining`][pyasic.miners.BaseMiner.is_mining], [`is_mining`][pyasic.miners.base.MinerProtocol.is_mining],
[`send_config`][pyasic.miners.BaseMiner.send_config], and [`send_config`][pyasic.miners.base.MinerProtocol.send_config], and
[`set_power_limit`][pyasic.miners.BaseMiner.set_power_limit]. [`set_power_limit`][pyasic.miners.base.MinerProtocol.set_power_limit].
##### Usage ##### Usage
```python ```python

View File

@@ -1,7 +1,15 @@
# pyasic # pyasic
## BOSMiner Backend ## BOSMiner Backend
::: pyasic.miners.backends.bosminer.BOSMiner ::: pyasic.miners.backends.braiins_os.BOSMiner
handler: python
options:
show_root_heading: false
heading_level: 4
## BOSer Backend
::: pyasic.miners.backends.braiins_os.BOSer
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,10 +1,17 @@
# pyasic # pyasic
## Base Miner ## Base Miner
[`BaseMiner`][pyasic.miners.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class. [`BaseMiner`][pyasic.miners.base.BaseMiner] is the basis for all miner classes, they all subclass (usually indirectly) from this class.
You may not instantiate this class on its own, only subclass from it. Trying to instantiate an instance of this class will raise `TypeError`. This class inherits from the [`MinerProtocol`][pyasic.miners.base.MinerProtocol], which outlines functionality for miners.
::: pyasic.miners.BaseMiner You may not instantiate this class on its own, only subclass from it.
::: pyasic.miners.base.BaseMiner
handler: python
options:
heading_level: 4
::: pyasic.miners.base.MinerProtocol
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4

View File

@@ -1,91 +1,91 @@
## Control functionality ## Control functionality
### Check Light ### Check Light
::: pyasic.miners.BaseMiner.check_light ::: pyasic.miners.base.MinerProtocol.check_light
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Fault Light Off ### Fault Light Off
::: pyasic.miners.BaseMiner.fault_light_off ::: pyasic.miners.base.MinerProtocol.fault_light_off
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Fault Light On ### Fault Light On
::: pyasic.miners.BaseMiner.fault_light_on ::: pyasic.miners.base.MinerProtocol.fault_light_on
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Get Config ### Get Config
::: pyasic.miners.BaseMiner.get_config ::: pyasic.miners.base.MinerProtocol.get_config
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Get Data ### Get Data
::: pyasic.miners.BaseMiner.get_data ::: pyasic.miners.base.MinerProtocol.get_data
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Get Errors ### Get Errors
::: pyasic.miners.BaseMiner.get_errors ::: pyasic.miners.base.MinerProtocol.get_errors
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Get Hostname ### Get Hostname
::: pyasic.miners.BaseMiner.get_hostname ::: pyasic.miners.base.MinerProtocol.get_hostname
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Get Model ### Get Model
::: pyasic.miners.BaseMiner.get_model ::: pyasic.miners.base.MinerProtocol.get_model
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Reboot ### Reboot
::: pyasic.miners.BaseMiner.reboot ::: pyasic.miners.base.MinerProtocol.reboot
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Restart Backend ### Restart Backend
::: pyasic.miners.BaseMiner.restart_backend ::: pyasic.miners.base.MinerProtocol.restart_backend
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Stop Mining ### Stop Mining
::: pyasic.miners.BaseMiner.stop_mining ::: pyasic.miners.base.MinerProtocol.stop_mining
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Resume Mining ### Resume Mining
::: pyasic.miners.BaseMiner.resume_mining ::: pyasic.miners.base.MinerProtocol.resume_mining
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Is Mining ### Is Mining
::: pyasic.miners.BaseMiner.is_mining ::: pyasic.miners.base.MinerProtocol.is_mining
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Send Config ### Send Config
::: pyasic.miners.BaseMiner.send_config ::: pyasic.miners.base.MinerProtocol.send_config
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4
### Set Power Limit ### Set Power Limit
::: pyasic.miners.BaseMiner.set_power_limit ::: pyasic.miners.base.MinerProtocol.set_power_limit
handler: python handler: python
options: options:
heading_level: 4 heading_level: 4

View File

@@ -2,23 +2,22 @@
## X5 Models ## X5 Models
## CK5 ## CK5
::: pyasic.miners.goldshell.bfgminer.X5.CK5.BFGMinerCK5 ::: pyasic.miners.goldshell.bfgminer.X5.CK5.GoldshellCK5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## HS5 ## HS5
::: pyasic.miners.goldshell.bfgminer.X5.HS5.BFGMinerHS5 ::: pyasic.miners.goldshell.bfgminer.X5.HS5.GoldshellHS5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4
## KD5 ## KD5
::: pyasic.miners.goldshell.bfgminer.X5.KD5.BFGMinerKD5 ::: pyasic.miners.goldshell.bfgminer.X5.KD5.GoldshellKD5
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

View File

@@ -2,9 +2,8 @@
## XMax Models ## XMax Models
## KD Max ## KD Max
::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.BFGMinerKDMax ::: pyasic.miners.goldshell.bfgminer.XMax.KDMax.KDMax
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false
heading_level: 4 heading_level: 4

27
docs/rpc/api.md Normal file
View 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

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## BFGMinerAPI ## BFGMinerRPCAPI
::: pyasic.API.bfgminer.BFGMinerAPI ::: pyasic.rpc.bfgminer.BFGMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## BMMinerAPI ## BMMinerRPCAPI
::: pyasic.API.bmminer.BMMinerAPI ::: pyasic.rpc.bmminer.BMMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## BOSMinerAPI ## BOSMinerRPCAPI
::: pyasic.API.bosminer.BOSMinerAPI ::: pyasic.rpc.bosminer.BOSMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## BTMinerAPI ## BTMinerRPCAPI
::: pyasic.API.btminer.BTMinerAPI ::: pyasic.rpc.btminer.BTMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## CGMinerAPI ## CGMinerRPCAPI
::: pyasic.API.cgminer.CGMinerAPI ::: pyasic.rpc.cgminer.CGMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## LUXMinerAPI ## LUXMinerRPCAPI
::: pyasic.API.luxminer.LUXMinerAPI ::: pyasic.rpc.luxminer.LUXMinerRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -1,6 +1,6 @@
# pyasic # pyasic
## UnknownAPI ## UnknownRPCAPI
::: pyasic.API.unknown.UnknownAPI ::: pyasic.rpc.unknown.UnknownRPCAPI
handler: python handler: python
options: options:
show_root_heading: false show_root_heading: false

View File

@@ -4,6 +4,7 @@
All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`. All settings here are global settings for all of pyasic. Set these settings with `update(key, value)`.
Settings options: Settings options:
- `network_ping_retries` - `network_ping_retries`
- `network_ping_timeout` - `network_ping_timeout`
- `network_scan_threads` - `network_scan_threads`

View File

@@ -13,15 +13,15 @@ nav:
- Error Codes: "data/error_codes.md" - Error Codes: "data/error_codes.md"
- Miner Config: "config/miner_config.md" - Miner Config: "config/miner_config.md"
- Advanced: - Advanced:
- Miner APIs: - RPC APIs:
- Intro: "API/api.md" - Intro: "rpc/api.md"
- BFGMiner: "API/bfgminer.md" - BFGMiner: "rpc/bfgminer.md"
- BMMiner: "API/bmminer.md" - BMMiner: "rpc/bmminer.md"
- BOSMiner: "API/bosminer.md" - BOSMiner: "rpc/bosminer.md"
- BTMiner: "API/btminer.md" - BTMiner: "rpc/btminer.md"
- CGMiner: "API/cgminer.md" - CGMiner: "rpc/cgminer.md"
- LUXMiner: "API/luxminer.md" - LUXMiner: "rpc/luxminer.md"
- Unknown: "API/unknown.md" - Unknown: "rpc/unknown.md"
- Backends: - Backends:
- BMMiner: "miners/backends/bmminer.md" - BMMiner: "miners/backends/bmminer.md"
- BOSMiner: "miners/backends/bosminer.md" - BOSMiner: "miners/backends/bosminer.md"

View File

@@ -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}")

View File

@@ -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")

View File

@@ -14,11 +14,6 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic import settings from pyasic import settings
from pyasic.API.bmminer import BMMinerAPI
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.API.btminer import BTMinerAPI
from pyasic.API.cgminer import CGMinerAPI
from pyasic.API.unknown import UnknownAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import ( from pyasic.data import (
BraiinsOSError, 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_factory import MinerFactory, miner_factory
from pyasic.miners.miner_listener import MinerListener from pyasic.miners.miner_listener import MinerListener
from pyasic.network import MinerNetwork 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__ = [ __all__ = [
"BMMinerAPI", "BMMinerRPCAPI",
"BOSMinerAPI", "BOSMinerRPCAPI",
"BTMinerAPI", "BTMinerRPCAPI",
"CGMinerAPI", "CGMinerRPCAPI",
"UnknownAPI", "UnknownRPCAPI",
"MinerConfig", "MinerConfig",
"MinerData", "MinerData",
"BraiinsOSError", "BraiinsOSError",

View File

@@ -19,7 +19,7 @@ from dataclasses import asdict, dataclass, field
from pyasic.config.fans import FanModeConfig from pyasic.config.fans import FanModeConfig
from pyasic.config.mining import MiningModeConfig from pyasic.config.mining import MiningModeConfig
from pyasic.config.pools import PoolConfig from pyasic.config.pools import PoolConfig
from pyasic.config.power_scaling import PowerScalingConfig, PowerScalingShutdown from pyasic.config.power_scaling import PowerScalingConfig
from pyasic.config.temperature import TemperatureConfig from pyasic.config.temperature import TemperatureConfig

View File

@@ -36,7 +36,7 @@ class Pool(MinerConfigValue):
} }
return {"url": self.url, "user": self.user, "pass": self.password} return {"url": self.url, "user": self.user, "pass": self.password}
def as_wm(self, idx: int, user_suffix: str = None): def as_wm(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None: if user_suffix is not None:
return { return {
f"pool_{idx}": self.url, f"pool_{idx}": self.url,
@@ -49,7 +49,7 @@ class Pool(MinerConfigValue):
f"passwd_{idx}": self.password, f"passwd_{idx}": self.password,
} }
def as_am_old(self, idx: int, user_suffix: str = None): def as_am_old(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None: if user_suffix is not None:
return { return {
f"_ant_pool{idx}url": self.url, f"_ant_pool{idx}url": self.url,
@@ -76,7 +76,7 @@ class Pool(MinerConfigValue):
return ",".join([self.url, f"{self.user}{user_suffix}", self.password]) return ",".join([self.url, f"{self.user}{user_suffix}", self.password])
return ",".join([self.url, self.user, self.password]) return ",".join([self.url, self.user, self.password])
def as_inno(self, idx: int, user_suffix: str = None): def as_inno(self, idx: int = 1, user_suffix: str = None):
if user_suffix is not None: if user_suffix is not None:
return { return {
f"Pool{idx}": self.url, f"Pool{idx}": self.url,

View File

@@ -59,18 +59,17 @@ class TemperatureConfig(MinerConfigValue):
@classmethod @classmethod
def from_epic(cls, web_conf: dict) -> "TemperatureConfig": def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
dangerous_temp = None
try: try:
hot_temp = web_conf["Misc"]["Shutdown Temp"] dangerous_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError: except KeyError:
hot_temp = None dangerous_temp = None
# Need to do this in two blocks to avoid KeyError if one is missing # Need to do this in two blocks to avoid KeyError if one is missing
try: try:
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"] target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
except KeyError: except KeyError:
target_temp = None target_temp = None
return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp) return cls(target=target_temp, danger=dangerous_temp)
@classmethod @classmethod
def from_vnish(cls, web_settings: dict): def from_vnish(cls, web_settings: dict):

View File

@@ -16,7 +16,6 @@
import copy import copy
import json import json
import logging
import time import time
from dataclasses import asdict, dataclass, field, fields from dataclasses import asdict, dataclass, field, fields
from datetime import datetime, timezone from datetime import datetime, timezone
@@ -351,7 +350,6 @@ class MinerData:
pass pass
def asdict(self) -> dict: def asdict(self) -> dict:
logging.debug(f"MinerData - (To Dict) - Dumping Dict data")
return asdict(self, dict_factory=self.dict_factory) return asdict(self, dict_factory=self.dict_factory)
def as_dict(self) -> dict: def as_dict(self) -> dict:
@@ -368,7 +366,6 @@ class MinerData:
Returns: Returns:
A JSON version of this class. A JSON version of this class.
""" """
logging.debug(f"MinerData - (To JSON) - Dumping JSON data")
data = self.asdict() data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple()))) data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
return json.dumps(data) return json.dumps(data)
@@ -379,7 +376,6 @@ class MinerData:
Returns: Returns:
A CSV version of this class with no headers. A CSV version of this class with no headers.
""" """
logging.debug(f"MinerData - (To CSV) - Dumping CSV data")
data = self.asdict() data = self.asdict()
data["datetime"] = str(int(time.mktime(data["datetime"].timetuple()))) data["datetime"] = str(int(time.mktime(data["datetime"].timetuple())))
errs = [] errs = []
@@ -398,7 +394,6 @@ class MinerData:
Returns: Returns:
A influxdb line protocol version of this class. A influxdb line protocol version of this class.
""" """
logging.debug(f"MinerData - (To InfluxDB) - Dumping InfluxDB data")
tag_data = [measurement_name] tag_data = [measurement_name]
field_data = [] field_data = []

View File

@@ -149,10 +149,10 @@ class _MinerPhaseBalancer:
not self.miners[data_point.ip]["shutdown"] not self.miners[data_point.ip]["shutdown"]
): ):
# cant do anything with it so need to find a semi-accurate power limit # cant do anything with it so need to find a semi-accurate power limit
if not data_point.wattage_limit == None: if data_point.wattage_limit is not None:
self.miners[data_point.ip]["max"] = int(data_point.wattage_limit) self.miners[data_point.ip]["max"] = int(data_point.wattage_limit)
self.miners[data_point.ip]["min"] = int(data_point.wattage_limit) self.miners[data_point.ip]["min"] = int(data_point.wattage_limit)
elif not data_point.wattage == None: elif data_point.wattage is not None:
self.miners[data_point.ip]["max"] = int(data_point.wattage) self.miners[data_point.ip]["max"] = int(data_point.wattage)
self.miners[data_point.ip]["min"] = int(data_point.wattage) self.miners[data_point.ip]["min"] = int(data_point.wattage)
@@ -183,13 +183,19 @@ class _MinerPhaseBalancer:
if (not miner["tune"]) and (miner["shutdown"]) if (not miner["tune"]) and (miner["shutdown"])
] ]
) )
# min_other_wattage = sum([miner["min"] for miner in self.miners.values() if (not miner["tune"]) and (not miner["shutdown"])]) # min_other_wattage = sum(
# [
# miner["min"]
# for miner in self.miners.values()
# if (not miner["tune"]) and (not miner["shutdown"])
# ]
# )
# make sure wattage isnt set too high # make sure wattage isnt set too high
if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage): if wattage > (max_tune_wattage + max_shutdown_wattage + max_other_wattage):
raise APIError( raise APIError(
f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W" f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W"
) # PhaseBalancingError(f"Wattage setpoint is too high, setpoint: {wattage}W, max: {max_tune_wattage + max_shutdown_wattage + max_other_wattage}W") )
# should now know wattage limits and which can be tuned/shutdown # should now know wattage limits and which can be tuned/shutdown
# check if 1/2 max of the miners which can be tuned is low enough # check if 1/2 max of the miners which can be tuned is low enough

View File

@@ -17,8 +17,6 @@
from pyasic.miners.backends import AntminerModern from pyasic.miners.backends import AntminerModern
from pyasic.miners.types import T19 from pyasic.miners.types import T19
# noqa - Ignore access to _module
class BMMinerT19(AntminerModern, T19): class BMMinerT19(AntminerModern, T19):
pass pass

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import HS3
class BMMinerHS3(AntminerModern, HS3): class BMMinerHS3(AntminerModern, HS3):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -18,6 +18,4 @@ from pyasic.miners.types import L7
class BMMinerL7(AntminerModern, L7): class BMMinerL7(AntminerModern, L7):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import E9Pro
class BMMinerE9Pro(AntminerModern, E9Pro): class BMMinerE9Pro(AntminerModern, E9Pro):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import Z15
class CGMinerZ15(AntminerOld, Z15): class CGMinerZ15(AntminerOld, Z15):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -18,6 +18,4 @@ from pyasic.miners.types import D3
class CGMinerD3(AntminerOld, D3): class CGMinerD3(AntminerOld, D3):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -19,6 +19,4 @@ from pyasic.miners.types import DR5
class CGMinerDR5(AntminerOld, DR5): class CGMinerDR5(AntminerOld, DR5):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_shutdown = False
super().__init__(ip, api_ver)
self.supports_shutdown = False

View File

@@ -21,10 +21,53 @@ import asyncssh
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import Hiveon
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.types import T9 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): class HiveonT9(Hiveon, T9):
data_locations = HIVEON_T9_DATA_LOC
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###

View File

@@ -19,5 +19,4 @@ from pyasic.miners.types import L3Plus
class VnishL3Plus(VNish, L3Plus): class VnishL3Plus(VNish, L3Plus):
def __init__(self, ip: str, api_ver: str = "0.0.0"): pass
super().__init__(ip, api_ver)

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1026 from pyasic.miners.types import Avalon1026
class CGMinerAvalon1026(CGMinerAvalon, Avalon1026): class CGMinerAvalon1026(AvalonMiner, Avalon1026):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1047 from pyasic.miners.types import Avalon1047
class CGMinerAvalon1047(CGMinerAvalon, Avalon1047): class CGMinerAvalon1047(AvalonMiner, Avalon1047):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1066 from pyasic.miners.types import Avalon1066
class CGMinerAvalon1066(CGMinerAvalon, Avalon1066): class CGMinerAvalon1066(AvalonMiner, Avalon1066):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1166Pro from pyasic.miners.types import Avalon1166Pro
class CGMinerAvalon1166Pro(CGMinerAvalon, Avalon1166Pro): class CGMinerAvalon1166Pro(AvalonMiner, Avalon1166Pro):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon1246 from pyasic.miners.types import Avalon1246
class CGMinerAvalon1246(CGMinerAvalon, Avalon1246): class CGMinerAvalon1246(AvalonMiner, Avalon1246):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon721 from pyasic.miners.types import Avalon721
class CGMinerAvalon721(CGMinerAvalon, Avalon721): class CGMinerAvalon721(AvalonMiner, Avalon721):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon741 from pyasic.miners.types import Avalon741
class CGMinerAvalon741(CGMinerAvalon, Avalon741): class CGMinerAvalon741(AvalonMiner, Avalon741):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon761 from pyasic.miners.types import Avalon761
class CGMinerAvalon761(CGMinerAvalon, Avalon761): class CGMinerAvalon761(AvalonMiner, Avalon761):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon821 from pyasic.miners.types import Avalon821
class CGMinerAvalon821(CGMinerAvalon, Avalon821): class CGMinerAvalon821(AvalonMiner, Avalon821):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon841 from pyasic.miners.types import Avalon841
class CGMinerAvalon841(CGMinerAvalon, Avalon841): class CGMinerAvalon841(AvalonMiner, Avalon841):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon851 from pyasic.miners.types import Avalon851
class CGMinerAvalon851(CGMinerAvalon, Avalon851): class CGMinerAvalon851(AvalonMiner, Avalon851):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import CGMinerAvalon from pyasic.miners.backends import AvalonMiner
from pyasic.miners.types import Avalon921 from pyasic.miners.types import Avalon921
class CGMinerAvalon921(CGMinerAvalon, Avalon921): class CGMinerAvalon921(AvalonMiner, Avalon921):
pass pass

View File

@@ -14,14 +14,14 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .antminer import AntminerModern, AntminerOld from .antminer import AntminerModern, AntminerOld
from .avalonminer import AvalonMiner
from .bfgminer import BFGMiner from .bfgminer import BFGMiner
from .bfgminer_goldshell import BFGMinerGoldshell
from .bmminer import BMMiner from .bmminer import BMMiner
from .braiins_os import BOSer, BOSMiner from .braiins_os import BOSer, BOSMiner
from .btminer import BTMiner from .btminer import BTMiner
from .cgminer import CGMiner from .cgminer import CGMiner
from .cgminer_avalon import CGMinerAvalon
from .epic import ePIC from .epic import ePIC
from .goldshell import GoldshellMiner
from .hiveon import Hiveon from .hiveon import Hiveon
from .luxminer import LUXMiner from .luxminer import LUXMiner
from .vnish import VNish from .vnish import VNish

View File

@@ -16,7 +16,6 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from pyasic.API import APIError
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.data.error_codes import MinerErrorData, X19Error
@@ -29,65 +28,72 @@ from pyasic.miners.base import (
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
from pyasic.rpc import APIError
from pyasic.ssh.antminer import AntminerModernSSH
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
ANTMINER_MODERN_DATA_LOC = DataLocations( ANTMINER_MODERN_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [WebAPICommand("web_get_system_info", "get_system_info")] "_get_mac",
[WebAPICommand("web_get_system_info", "get_system_info")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [RPCAPICommand("api_version", "version")] "_get_fw_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] "_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( 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( 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( str(DataOptions.ERRORS): DataFunction(
"_get_errors", [WebAPICommand("web_summary", "summary")] "_get_errors",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")], [WebAPICommand("web_get_blink_status", "get_blink_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] "_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
class AntminerModern(BMMiner): class AntminerModern(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for AntMiners with the modern web interface, such as S19"""
super().__init__(ip, api_ver)
# interfaces
self.web = AntminerModernWebAPI(ip)
# static data _web_cls = AntminerModernWebAPI
# data gathering locations web: AntminerModernWebAPI
self.data_locations = ANTMINER_MODERN_DATA_LOC
# autotuning/shutdown support _ssh_cls = AntminerModernSSH
self.supports_shutdown = True ssh: AntminerModernSSH
data_locations = ANTMINER_MODERN_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf() data = await self.web.get_miner_conf()
@@ -141,26 +147,26 @@ class AntminerModern(BMMiner):
return True return True
async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]: async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
if not web_get_system_info: if web_get_system_info is None:
try: try:
web_get_system_info = await self.web.get_system_info() web_get_system_info = await self.web.get_system_info()
except APIError: except APIError:
pass pass
if web_get_system_info: if web_get_system_info is not None:
try: try:
return web_get_system_info["hostname"] return web_get_system_info["hostname"]
except KeyError: except KeyError:
pass pass
async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]: async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
if not web_get_system_info: if web_get_system_info is None:
try: try:
web_get_system_info = await self.web.get_system_info() web_get_system_info = await self.web.get_system_info()
except APIError: except APIError:
pass pass
if web_get_system_info: if web_get_system_info is not None:
try: try:
return web_get_system_info["macaddr"] return web_get_system_info["macaddr"]
except KeyError: except KeyError:
@@ -174,14 +180,14 @@ class AntminerModern(BMMiner):
pass pass
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if not web_summary: if web_summary is None:
try: try:
web_summary = await self.web.summary() web_summary = await self.web.summary()
except APIError: except APIError:
pass pass
errors = [] errors = []
if web_summary: if web_summary is not None:
try: try:
for item in web_summary["SUMMARY"][0]["status"]: for item in web_summary["SUMMARY"][0]["status"]:
try: try:
@@ -204,7 +210,7 @@ class AntminerModern(BMMiner):
except APIError: except APIError:
return hashboards return hashboards
if api_stats: if api_stats is not None:
try: try:
for board in api_stats["STATS"][0]["chain"]: for board in api_stats["STATS"][0]["chain"]:
hashboards[board["index"]].hashrate = round( hashboards[board["index"]].hashrate = round(
@@ -229,17 +235,19 @@ class AntminerModern(BMMiner):
pass pass
return hashboards return hashboards
async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool: async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light: if self.light:
return self.light return self.light
if not web_get_blink_status: if web_get_blink_status is None:
try: try:
web_get_blink_status = await self.web.get_blink_status() web_get_blink_status = await self.web.get_blink_status()
except APIError: except APIError:
pass pass
if web_get_blink_status: if web_get_blink_status is not None:
try: try:
self.light = web_get_blink_status["blink"] self.light = web_get_blink_status["blink"]
except KeyError: except KeyError:
@@ -247,13 +255,13 @@ class AntminerModern(BMMiner):
return self.light return self.light
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = api_stats["STATS"][1]["total_rateideal"]
try: try:
@@ -312,13 +320,13 @@ class AntminerModern(BMMiner):
) )
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]: async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if not web_get_conf: if web_get_conf is None:
try: try:
web_get_conf = await self.web.get_miner_conf() web_get_conf = await self.web.get_miner_conf()
except APIError: except APIError:
pass pass
if web_get_conf: if web_get_conf is not None:
try: try:
if web_get_conf["bitmain-work-mode"].isdigit(): if web_get_conf["bitmain-work-mode"].isdigit():
return ( return (
@@ -329,13 +337,13 @@ class AntminerModern(BMMiner):
pass pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:
@@ -344,57 +352,53 @@ class AntminerModern(BMMiner):
ANTMINER_OLD_DATA_LOC = DataLocations( ANTMINER_OLD_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction("_get_mac"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [RPCAPICommand("api_version", "version")] "_get_fw_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] "_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
), [RPCAPICommand("api_summary", "summary")],
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", [RPCAPICommand("api_stats", "stats")] "_get_hashboards",
[RPCAPICommand("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( 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( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", "_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")], [WebAPICommand("web_get_blink_status", "get_blink_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] "_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
), ),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
class AntminerOld(CGMiner): class AntminerOld(CGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for AntMiners with the old web interface, such as S17"""
super().__init__(ip, api_ver)
# interfaces
self.web = AntminerOldWebAPI(ip)
# static data _web_cls = AntminerOldWebAPI
# data gathering locations web: AntminerOldWebAPI
self.data_locations = ANTMINER_OLD_DATA_LOC
data_locations = ANTMINER_OLD_DATA_LOC
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf() data = await self.web.get_miner_conf()
@@ -443,17 +447,19 @@ class AntminerOld(CGMiner):
return True return True
return False return False
async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool: async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light: if self.light:
return self.light return self.light
if not web_get_blink_status: if web_get_blink_status is None:
try: try:
web_get_blink_status = await self.web.get_blink_status() web_get_blink_status = await self.web.get_blink_status()
except APIError: except APIError:
pass pass
if web_get_blink_status: if web_get_blink_status is not None:
try: try:
self.light = web_get_blink_status["isBlinking"] self.light = web_get_blink_status["isBlinking"]
except KeyError: except KeyError:
@@ -461,27 +467,27 @@ class AntminerOld(CGMiner):
return self.light return self.light
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]: async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
if not web_get_system_info: if web_get_system_info is None:
try: try:
web_get_system_info = await self.web.get_system_info() web_get_system_info = await self.web.get_system_info()
except APIError: except APIError:
pass pass
if web_get_system_info: if web_get_system_info is not None:
try: try:
return web_get_system_info["hostname"] return web_get_system_info["hostname"]
except KeyError: except KeyError:
pass pass
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans_data = [Fan() for _ in range(self.expected_fans)] fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats: if api_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
@@ -504,13 +510,13 @@ class AntminerOld(CGMiner):
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = api_stats["STATS"]
@@ -556,13 +562,13 @@ class AntminerOld(CGMiner):
return hashboards return hashboards
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]: async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if not web_get_conf: if web_get_conf is None:
try: try:
web_get_conf = await self.web.get_miner_conf() web_get_conf = await self.web.get_miner_conf()
except APIError: except APIError:
pass pass
if web_get_conf: if web_get_conf is not None:
try: try:
return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True return False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
except LookupError: except LookupError:
@@ -581,13 +587,13 @@ class AntminerOld(CGMiner):
return False return False
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -17,62 +17,61 @@
import re import re
from typing import List, Optional from typing import List, Optional
from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import CGMiner from pyasic.miners.backends.cgminer import CGMiner
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
AVALON_DATA_LOC = DataLocations( AVALON_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [RPCAPICommand("api_version", "version")] "_get_mac",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_devs", "devs")] "_get_hashrate",
[RPCAPICommand("api_devs", "devs")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", [RPCAPICommand("api_stats", "stats")] "_get_hashboards",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( 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( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", [RPCAPICommand("api_stats", "stats")] "_get_wattage_limit",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.FANS): DataFunction( 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( 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): class AvalonMiner(CGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for Avalon Miners"""
super().__init__(ip, api_ver)
# data gathering locations data_locations = AVALON_DATA_LOC
self.data_locations = AVALON_DATA_LOC
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
try: try:
@@ -105,26 +104,6 @@ class CGMinerAvalon(CGMiner):
return False return False
return False return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
# self.config = config
# return None
# logging.debug(f"{self}: Sending config.") # noqa - This doesnt work...
# conf = config.as_avalon(user_suffix=user_suffix)
# try:
# data = await self.api.ascset( # noqa
# 0, "setpool", f"root,root,{conf}"
# ) # this should work but doesn't
# except APIError:
# pass
# return data
@staticmethod @staticmethod
def parse_stats(stats): def parse_stats(stats):
_stats_items = re.findall(".+?\\[*?]", stats) _stats_items = re.findall(".+?\\[*?]", stats)
@@ -142,9 +121,9 @@ class CGMinerAvalon(CGMiner):
# --avalon args # --avalon args
for arg_item in data_list: for arg_item in data_list:
item_data = arg_item[0].split(" ") item_data = arg_item[0].split(" ")
for idx in range(len(item_data)): for idx, val in enumerate(item_data):
if idx % 2 == 0 or idx == 0: if idx % 2 == 0 or idx == 0:
data_dict[item_data[idx]] = item_data[idx + 1] data_dict[val] = item_data[idx + 1]
raw_data = [data[0].strip(), data_dict] raw_data = [data[0].strip(), data_dict]
else: else:
@@ -173,13 +152,13 @@ class CGMinerAvalon(CGMiner):
################################################## ##################################################
async def _get_mac(self, api_version: dict = None) -> Optional[str]: async def _get_mac(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
base_mac = api_version["VERSION"][0]["MAC"] base_mac = api_version["VERSION"][0]["MAC"]
base_mac = base_mac.upper() base_mac = base_mac.upper()
@@ -190,22 +169,14 @@ class CGMinerAvalon(CGMiner):
except (KeyError, ValueError): except (KeyError, ValueError):
pass 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]: async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if api_devs is None:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
if api_devs: if api_devs is not None:
try: try:
return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2) return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
@@ -217,13 +188,13 @@ class CGMinerAvalon(CGMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -260,13 +231,13 @@ class CGMinerAvalon(CGMiner):
return hashboards return hashboards
async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -275,13 +246,13 @@ class CGMinerAvalon(CGMiner):
pass pass
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -289,17 +260,14 @@ class CGMinerAvalon(CGMiner):
except (IndexError, KeyError, ValueError, TypeError): except (IndexError, KeyError, ValueError, TypeError):
pass pass
async def _get_wattage(self) -> Optional[int]:
return None
async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]: async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -308,14 +276,14 @@ class CGMinerAvalon(CGMiner):
pass pass
async def _get_fans(self, api_stats: dict = None) -> List[Fan]: async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans_data = [Fan() for _ in range(self.expected_fans)] fans_data = [Fan() for _ in range(self.expected_fans)]
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -329,19 +297,16 @@ class CGMinerAvalon(CGMiner):
pass pass
return fans_data return fans_data
async def _get_errors(self) -> List[MinerErrorData]: async def _get_fault_light(self, api_stats: dict = None) -> Optional[bool]:
return []
async def _get_fault_light(self, api_stats: dict = None) -> bool: # noqa
if self.light: if self.light:
return self.light return self.light
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
unparsed_stats = api_stats["STATS"][0]["MM ID0"] unparsed_stats = api_stats["STATS"][0]["MM ID0"]
parsed_stats = self.parse_stats(unparsed_stats) parsed_stats = self.parse_stats(unparsed_stats)
@@ -360,9 +325,3 @@ class CGMinerAvalon(CGMiner):
except LookupError: except LookupError:
pass pass
return False return False
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def _get_uptime(self) -> Optional[int]:
return None

View File

@@ -16,10 +16,8 @@
from typing import List, Optional from typing import List, Optional
from pyasic.API.bfgminer import BFGMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import (
BaseMiner, BaseMiner,
@@ -28,38 +26,34 @@ from pyasic.miners.base import (
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
) )
from pyasic.rpc.bfgminer import BFGMinerRPCAPI
BFGMINER_DATA_LOC = DataLocations( BFGMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction("_get_mac"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( 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( 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): class BFGMiner(BaseMiner):
"""Base handler for BFGMiner based miners.""" """Base handler for BFGMiner based miners."""
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: _api_cls = BFGMinerRPCAPI
super().__init__(ip) api: BFGMinerRPCAPI
# interfaces
self.api = BFGMinerAPI(ip, api_ver)
# static data data_locations = BFGMINER_DATA_LOC
self.api_type = "BFGMiner"
# data gathering locations
self.data_locations = BFGMINER_DATA_LOC
# data storage
self.api_ver = api_ver
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
@@ -90,42 +76,18 @@ class BFGMiner(BaseMiner):
self.config = MinerConfig.from_api(pools) self.config = MinerConfig.from_api(pools)
return self.config return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def set_power_limit(self, wattage: int) -> bool:
return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self) -> Optional[str]:
return None
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = api_version["VERSION"][0]["API"]
except LookupError: except LookupError:
@@ -134,13 +96,13 @@ class BFGMiner(BaseMiner):
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"] self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except LookupError: except LookupError:
@@ -148,24 +110,15 @@ class BFGMiner(BaseMiner):
return self.fw_ver 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]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API # get hr from API
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
@@ -174,13 +127,13 @@ class BFGMiner(BaseMiner):
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = api_stats["STATS"]
@@ -225,24 +178,15 @@ class BFGMiner(BaseMiner):
return hashboards 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]: async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans_data = [None, None, None, None] fans_data = [None, None, None, None]
if api_stats: if api_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
@@ -264,21 +208,15 @@ class BFGMiner(BaseMiner):
return fans 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]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
# X19 method, not sure compatibility # X19 method, not sure compatibility
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = api_stats["STATS"][1]["total_rateideal"]
try: try:
@@ -293,9 +231,3 @@ class BFGMiner(BaseMiner):
return round(expected_rate, 2) return round(expected_rate, 2)
except LookupError: except LookupError:
pass pass
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import logging
from typing import List, Optional from typing import List, Optional
from pyasic.API.bmminer import BMMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import (
BaseMiner, BaseMiner,
@@ -29,40 +26,38 @@ from pyasic.miners.base import (
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
) )
from pyasic.rpc.bmminer import BMMinerRPCAPI
BMMINER_DATA_LOC = DataLocations( BMMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction("_get_mac"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( 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( 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( 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): class BMMiner(BaseMiner):
"""Base handler for BMMiner based miners.""" """Base handler for BMMiner based miners."""
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: _api_cls = BMMinerRPCAPI
super().__init__(ip) api: BMMinerRPCAPI
# interfaces
self.api = BMMinerAPI(ip, api_ver)
# static data data_locations = BMMINER_DATA_LOC
self.api_type = "BMMiner"
# data gathering locations
self.data_locations = BMMINER_DATA_LOC
# data storage
self.api_ver = api_ver
async def send_ssh_command(self, cmd: str) -> Optional[str]:
result = None
try:
conn = await self._get_ssh_connection()
except ConnectionError:
return None
# open an ssh connection
async with conn:
# 3 retries
for i in range(3):
try:
# run the command and get the result
result = await conn.run(cmd)
result = result.stdout
except Exception as e:
# if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}")
# on the 3rd retry, return None
if i == 3:
return
continue
# return the result, either command output or None
return result
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
@@ -121,50 +80,18 @@ class BMMiner(BaseMiner):
self.config = MinerConfig.from_api(pools) self.config = MinerConfig.from_api(pools)
return self.config return self.config
async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.")
if ret is None:
return False
return True
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def restart_backend(self) -> bool:
return False
async def stop_mining(self) -> bool:
return False
async def resume_mining(self) -> bool:
return False
async def set_power_limit(self, wattage: int) -> bool:
return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self) -> Optional[str]:
return None
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = api_version["VERSION"][0]["API"]
except LookupError: except LookupError:
@@ -173,13 +100,13 @@ class BMMiner(BaseMiner):
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CompileTime"] self.fw_ver = api_version["VERSION"][0]["CompileTime"]
except LookupError: except LookupError:
@@ -187,22 +114,15 @@ class BMMiner(BaseMiner):
return self.fw_ver 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]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API # get hr from API
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2) return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
@@ -211,13 +131,13 @@ class BMMiner(BaseMiner):
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = api_stats["STATS"]
@@ -275,24 +195,15 @@ class BMMiner(BaseMiner):
return hashboards 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]: async def _get_fans(self, api_stats: dict = None) -> List[Fan]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
fans = [Fan() for _ in range(self.expected_fans)] fans = [Fan() for _ in range(self.expected_fans)]
if api_stats: if api_stats is not None:
try: try:
fan_offset = -1 fan_offset = -1
@@ -313,21 +224,15 @@ class BMMiner(BaseMiner):
return fans 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]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
# X19 method, not sure compatibility # X19 method, not sure compatibility
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = api_stats["STATS"][1]["total_rateideal"]
try: try:
@@ -343,17 +248,14 @@ class BMMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -13,14 +13,12 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio
import logging import logging
import time import time
from typing import List, Optional, Union from typing import List, Optional, Union
import toml import toml
from pyasic.API.bosminer import BOSMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.config.mining import MiningModePowerTune from pyasic.config.mining import MiningModePowerTune
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
@@ -35,6 +33,8 @@ from pyasic.miners.base import (
RPCAPICommand, RPCAPICommand,
WebAPICommand, WebAPICommand,
) )
from pyasic.rpc.bosminer import BOSMinerRPCAPI
from pyasic.ssh.braiins_os import BOSMinerSSH
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
BOSMINER_DATA_LOC = DataLocations( BOSMINER_DATA_LOC = DataLocations(
@@ -44,18 +44,20 @@ BOSMINER_DATA_LOC = DataLocations(
[WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[RPCAPICommand("api_summary", "summary")], [RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] "_get_expected_hashrate",
[RPCAPICommand("api_devs", "devs")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
@@ -65,7 +67,6 @@ BOSMINER_DATA_LOC = DataLocations(
RPCAPICommand("api_devs", "devs"), RPCAPICommand("api_devs", "devs"),
], ],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
@@ -78,87 +79,50 @@ BOSMINER_DATA_LOC = DataLocations(
"_get_fans", "_get_fans",
[RPCAPICommand("api_fans", "fans")], [RPCAPICommand("api_fans", "fans")],
), ),
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", [RPCAPICommand("api_devdetails", "devdetails")] "_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")],
), ),
str(DataOptions.UPTIME): DataFunction( 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): class BOSMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for old versions of BraiinsOS+ (pre-gRPC)"""
super().__init__(ip)
# interfaces
self.api = BOSMinerAPI(ip, api_ver)
self.web = BOSMinerWebAPI(ip)
# static data _api_cls = BOSMinerRPCAPI
self.api_type = "BOSMiner" api: BOSMinerRPCAPI
# data gathering locations _web_cls = BOSMinerWebAPI
self.data_locations = BOSMINER_DATA_LOC web: BOSMinerWebAPI
# autotuning/shutdown support _ssh_cls = BOSMinerSSH
self.supports_autotuning = True ssh: BOSMinerSSH
self.supports_shutdown = True
# data storage firmware = "BOS+"
self.api_ver = api_ver
async def send_ssh_command(self, cmd: str) -> Optional[str]: data_locations = BOSMINER_DATA_LOC
result = None
try: supports_shutdown = True
conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10) supports_autotuning = True
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
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
logging.debug(f"{self}: Sending fault_light on command.") ret = await self.ssh.fault_light_on()
ret = await self.send_ssh_command("miner fault_light on")
logging.debug(f"{self}: fault_light on command completed.")
if isinstance(ret, str): if isinstance(ret, str):
self.light = True self.light = True
return self.light return self.light
return False return False
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
logging.debug(f"{self}: Sending fault_light off command.") ret = await self.ssh.fault_light_off()
self.light = False
ret = await self.send_ssh_command("miner fault_light off")
logging.debug(f"{self}: fault_light off command completed.")
if isinstance(ret, str): if isinstance(ret, str):
self.light = False self.light = False
return True return True
@@ -168,9 +132,8 @@ class BOSMiner(BaseMiner):
return await self.restart_bosminer() return await self.restart_bosminer()
async def restart_bosminer(self) -> bool: async def restart_bosminer(self) -> bool:
logging.debug(f"{self}: Sending bosminer restart command.") ret = await self.ssh.restart_bosminer()
ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
logging.debug(f"{self}: bosminer restart command completed.")
if isinstance(ret, str): if isinstance(ret, str):
return True return True
return False return False
@@ -180,6 +143,7 @@ class BOSMiner(BaseMiner):
data = await self.api.pause() data = await self.api.pause()
except APIError: except APIError:
return False return False
if data.get("PAUSE"): if data.get("PAUSE"):
if data["PAUSE"][0]: if data["PAUSE"][0]:
return True return True
@@ -190,40 +154,32 @@ class BOSMiner(BaseMiner):
data = await self.api.resume() data = await self.api.resume()
except APIError: except APIError:
return False return False
if data.get("RESUME"): if data.get("RESUME"):
if data["RESUME"][0]: if data["RESUME"][0]:
return True return True
return False return False
async def reboot(self) -> bool: async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.") ret = await self.ssh.reboot()
ret = await self.send_ssh_command("/sbin/reboot")
logging.debug(f"{self}: Reboot command completed.")
if isinstance(ret, str): if isinstance(ret, str):
return True return True
return False return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
logging.debug(f"{self}: Getting config.") raw_data = await self.ssh.get_config_file()
try: try:
conn = await self._get_ssh_connection() toml_data = toml.loads(raw_data)
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.")
cfg = MinerConfig.from_bosminer(toml_data) cfg = MinerConfig.from_bosminer(toml_data)
self.config = cfg self.config = cfg
except toml.TomlDecodeError as e:
raise APIError("Failed to decode toml when getting config.") from e
return self.config return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
logging.debug(f"{self}: Sending config.")
self.config = config self.config = config
toml_conf = toml.dumps( toml_conf = toml.dumps(
@@ -238,34 +194,16 @@ class BOSMiner(BaseMiner):
} }
) )
try: try:
conn = await self._get_ssh_connection() conn = await self.ssh._get_connection()
except ConnectionError as e: except ConnectionError as e:
raise APIError("SSH connection failed when sending config.") from e raise APIError("SSH connection failed when sending config.") from e
async with conn: async with conn:
# BBB check because bitmain suxx await conn.run("/etc/init.d/bosminer stop")
bbb_check = await conn.run( async with conn.start_sftp_client() as sftp:
"if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;" async with sftp.open("/etc/bosminer.toml", "w+") as file:
) await file.write(toml_conf)
await conn.run("/etc/init.d/bosminer start")
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")
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
try: try:
@@ -274,8 +212,10 @@ class BOSMiner(BaseMiner):
return False return False
cfg.mining_mode = MiningModePowerTune(wattage) cfg.mining_mode = MiningModePowerTune(wattage)
await self.send_config(cfg) await self.send_config(cfg)
except APIError:
raise
except Exception as e: except Exception as e:
logging.warning(f"{self} set_power_limit: {e}") logging.warning(f"{self} - Failed to set power limit: {e}")
return False return False
else: else:
return True return True
@@ -299,18 +239,15 @@ class BOSMiner(BaseMiner):
f"option dns '{dns}'", 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") split_data = data.split("\n\n")
for idx in range(len(split_data)): for idx, val in enumerate(split_data):
if "config interface 'lan'" in split_data[idx]: if "config interface 'lan'" in val:
split_data[idx] = cfg_data_lan split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data) config = "\n\n".join(split_data)
conn = await self._get_ssh_connection() await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
async def set_dhcp(self): async def set_dhcp(self):
cfg_data_lan = "\n\t".join( cfg_data_lan = "\n\t".join(
@@ -321,25 +258,22 @@ class BOSMiner(BaseMiner):
"option proto 'dhcp'", "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") split_data = data.split("\n\n")
for idx in range(len(split_data)): for idx, val in enumerate(split_data):
if "config interface 'lan'" in split_data[idx]: if "config interface 'lan'" in val:
split_data[idx] = cfg_data_lan split_data[idx] = cfg_data_lan
config = "\n\n".join(split_data) config = "\n\n".join(split_data)
conn = await self._get_ssh_connection() await self.ssh.send_command("echo '" + config + "' > /etc/config/network")
async with conn:
await conn.run("echo '" + config + "' > /etc/config/network")
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: 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: try:
web_net_conf = await self.web.luci.get_net_conf() web_net_conf = await self.web.luci.get_net_conf()
except APIError: except APIError:
@@ -349,7 +283,7 @@ class BOSMiner(BaseMiner):
if "admin/network/iface_status/lan" in web_net_conf.keys(): if "admin/network/iface_status/lan" in web_net_conf.keys():
web_net_conf = web_net_conf["admin/network/iface_status/lan"] web_net_conf = web_net_conf["admin/network/iface_status/lan"]
if web_net_conf: if web_net_conf is not None:
try: try:
return web_net_conf[0]["macaddr"] return web_net_conf[0]["macaddr"]
except LookupError: except LookupError:
@@ -360,14 +294,14 @@ class BOSMiner(BaseMiner):
# return result.upper().strip() # return result.upper().strip()
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
# Now get the API version # Now get the API version
if api_version: if api_version is not None:
try: try:
api_ver = api_version["VERSION"][0]["API"] api_ver = api_version["VERSION"][0]["API"]
except LookupError: except LookupError:
@@ -377,7 +311,7 @@ class BOSMiner(BaseMiner):
return self.api_ver 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: if web_bos_info is None:
try: try:
web_bos_info = await self.web.luci.get_bos_info() web_bos_info = await self.web.luci.get_bos_info()
@@ -392,7 +326,6 @@ class BOSMiner(BaseMiner):
ver = web_bos_info["version"].split("-")[5] ver = web_bos_info["version"].split("-")[5]
if "." in ver: if "." in ver:
self.fw_ver = ver self.fw_ver = ver
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
except (LookupError, AttributeError): except (LookupError, AttributeError):
return None return None
@@ -400,23 +333,20 @@ class BOSMiner(BaseMiner):
async def _get_hostname(self) -> Union[str, None]: async def _get_hostname(self) -> Union[str, None]:
try: try:
hostname = ( hostname = (await self.ssh.get_hostname()).strip()
await self.send_ssh_command("cat /proc/sys/kernel/hostname")
).strip()
except Exception as e: 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 None
return hostname return hostname
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API if api_summary is None:
if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
@@ -427,18 +357,18 @@ class BOSMiner(BaseMiner):
api_temps: dict = None, api_temps: dict = None,
api_devdetails: dict = None, api_devdetails: dict = None,
api_devs: dict = None, api_devs: dict = None,
): ) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
cmds = [] cmds = []
if not api_temps: if api_temps is None:
cmds.append("temps") cmds.append("temps")
if not api_devdetails: if api_devdetails is None:
cmds.append("devdetails") cmds.append("devdetails")
if not api_devs: if api_devs is None:
cmds.append("devs") cmds.append("devs")
if len(cmds) > 0: if len(cmds) > 0:
try: try:
@@ -457,7 +387,7 @@ class BOSMiner(BaseMiner):
api_devs = d["devs"][0] api_devs = d["devs"][0]
except LookupError: except LookupError:
api_devs = None api_devs = None
if api_temps: if api_temps is not None:
try: try:
offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1 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): except (IndexError, KeyError, ValueError, TypeError):
pass pass
if api_devdetails: if api_devdetails is not None:
try: try:
offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1 offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1
@@ -482,7 +412,7 @@ class BOSMiner(BaseMiner):
except (IndexError, KeyError): except (IndexError, KeyError):
pass pass
if api_devs: if api_devs is not None:
try: try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1 offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1
@@ -495,17 +425,14 @@ class BOSMiner(BaseMiner):
return hashboards return hashboards
async def _get_env_temp(self) -> Optional[float]:
return None
async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]:
if not api_tunerstatus: if api_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus: if api_tunerstatus is not None:
try: try:
return api_tunerstatus["TUNERSTATUS"][0][ return api_tunerstatus["TUNERSTATUS"][0][
"ApproximateMinerPowerConsumption" "ApproximateMinerPowerConsumption"
@@ -514,26 +441,26 @@ class BOSMiner(BaseMiner):
pass pass
async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]:
if not api_tunerstatus: if api_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus: if api_tunerstatus is not None:
try: try:
return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"]
except LookupError: except LookupError:
pass pass
async def _get_fans(self, api_fans: dict = None) -> List[Fan]: async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
if not api_fans: if api_fans is None:
try: try:
api_fans = await self.api.fans() api_fans = await self.api.fans()
except APIError: except APIError:
pass pass
if api_fans: if api_fans is not None:
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
@@ -543,17 +470,14 @@ class BOSMiner(BaseMiner):
return fans return fans
return [Fan() for _ in range(self.expected_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]: async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
if not api_tunerstatus: if api_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus: if api_tunerstatus is not None:
errors = [] errors = []
try: try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
@@ -580,9 +504,7 @@ class BOSMiner(BaseMiner):
if self.light: if self.light:
return self.light return self.light
try: try:
data = ( data = (await self.ssh.get_led_status()).strip()
await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off")
).strip()
self.light = False self.light = False
if data == "50": if data == "50":
self.light = True self.light = True
@@ -591,19 +513,17 @@ class BOSMiner(BaseMiner):
return self.light return self.light
async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]:
if not api_devs: if api_devs is None:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
if api_devs: if api_devs is not None:
try: try:
offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0
hr_list = [] hr_list = []
for board in api_devs["DEVS"]: for board in api_devs["DEVS"]:
_id = board["ID"] - offset
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
if expected_hashrate: if expected_hashrate:
hr_list.append(expected_hashrate) hr_list.append(expected_hashrate)
@@ -617,7 +537,7 @@ class BOSMiner(BaseMiner):
pass pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if not api_devdetails: if api_devdetails is None:
try: try:
api_devdetails = await self.api.send_command( api_devdetails = await self.api.send_command(
"devdetails", ignore_errors=True, allow_warning=False "devdetails", ignore_errors=True, allow_warning=False
@@ -625,20 +545,20 @@ class BOSMiner(BaseMiner):
except APIError: except APIError:
pass pass
if api_devdetails: if api_devdetails is not None:
try: try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:
@@ -652,7 +572,8 @@ BOSER_DATA_LOC = DataLocations(
[GRPCCommand("grpc_miner_details", "get_miner_details")], [GRPCCommand("grpc_miner_details", "get_miner_details")],
), ),
str(DataOptions.API_VERSION): DataFunction( 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( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
@@ -674,7 +595,6 @@ BOSER_DATA_LOC = DataLocations(
"_get_hashboards", "_get_hashboards",
[GRPCCommand("grpc_hashboards", "get_hashboards")], [GRPCCommand("grpc_hashboards", "get_hashboards")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[GRPCCommand("grpc_miner_stats", "get_miner_stats")], [GRPCCommand("grpc_miner_stats", "get_miner_stats")],
@@ -691,7 +611,6 @@ BOSER_DATA_LOC = DataLocations(
"_get_fans", "_get_fans",
[GRPCCommand("grpc_cooling_state", "get_cooling_state")], [GRPCCommand("grpc_cooling_state", "get_cooling_state")],
), ),
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[RPCAPICommand("api_tunerstatus", "tunerstatus")], [RPCAPICommand("api_tunerstatus", "tunerstatus")],
@@ -701,33 +620,29 @@ BOSER_DATA_LOC = DataLocations(
[GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", [RPCAPICommand("api_devdetails", "devdetails")] "_is_mining",
[RPCAPICommand("api_devdetails", "devdetails")],
), ),
str(DataOptions.UPTIME): DataFunction( 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): class BOSer(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for new versions of BraiinsOS+ (post-gRPC)"""
super().__init__(ip)
# interfaces
self.api = BOSMinerAPI(ip, api_ver)
self.web = BOSerWebAPI(ip)
# static data _api_cls = BOSMinerRPCAPI
self.api_type = "BOSMiner" web: BOSMinerRPCAPI
# data gathering locations _web_cls = BOSerWebAPI
self.data_locations = BOSER_DATA_LOC web: BOSerWebAPI
# autotuning/shutdown support
self.supports_autotuning = True
self.supports_shutdown = True
# data storage data_locations = BOSER_DATA_LOC
self.api_ver = api_ver
supports_autotuning = True
supports_shutdown = True
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
resp = await self.web.grpc.set_locate_device_status(True) resp = await self.web.grpc.set_locate_device_status(True)
@@ -745,7 +660,7 @@ class BOSer(BaseMiner):
return await self.restart_boser() return await self.restart_boser()
async def restart_boser(self) -> bool: async def restart_boser(self) -> bool:
ret = await self.web.grpc.restart() await self.web.grpc.restart()
return True return True
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
@@ -773,11 +688,6 @@ class BOSer(BaseMiner):
return MinerConfig.from_boser(grpc_conf) 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: async def set_power_limit(self, wattage: int) -> bool:
try: try:
result = await self.web.grpc.set_power_target(wattage) 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]: async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]:
if not grpc_miner_details: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError: except APIError:
pass pass
if grpc_miner_details: if grpc_miner_details is not None:
try: try:
return grpc_miner_details["macAddress"].upper() return grpc_miner_details["macAddress"].upper()
except (LookupError, TypeError): except (LookupError, TypeError):
pass pass
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
# Now get the API version if api_version is not None:
if api_version:
try: try:
api_ver = api_version["VERSION"][0]["API"] api_ver = api_version["VERSION"][0]["API"]
except LookupError: except LookupError:
@@ -827,7 +736,7 @@ class BOSer(BaseMiner):
return self.api_ver return self.api_ver
async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: 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: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError: except APIError:
@@ -835,7 +744,7 @@ class BOSer(BaseMiner):
fw_ver = None fw_ver = None
if grpc_miner_details: if grpc_miner_details is not None:
try: try:
fw_ver = grpc_miner_details["bosVersion"]["current"] fw_ver = grpc_miner_details["bosVersion"]["current"]
except (KeyError, TypeError): except (KeyError, TypeError):
@@ -846,31 +755,30 @@ class BOSer(BaseMiner):
ver = fw_ver.split("-")[5] ver = fw_ver.split("-")[5]
if "." in ver: if "." in ver:
self.fw_ver = ver self.fw_ver = ver
logging.debug(f"Found version for {self.ip}: {self.fw_ver}")
return self.fw_ver return self.fw_ver
async def _get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]: async def _get_hostname(self, grpc_miner_details: dict = None) -> Optional[str]:
if not grpc_miner_details: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError: except APIError:
pass pass
if grpc_miner_details: if grpc_miner_details is not None:
try: try:
return grpc_miner_details["hostname"] return grpc_miner_details["hostname"]
except LookupError: except LookupError:
pass pass
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError, ValueError, TypeError): except (KeyError, IndexError, ValueError, TypeError):
@@ -879,19 +787,19 @@ class BOSer(BaseMiner):
async def _get_expected_hashrate( async def _get_expected_hashrate(
self, grpc_miner_details: dict = None self, grpc_miner_details: dict = None
) -> Optional[float]: ) -> Optional[float]:
if not grpc_miner_details: if grpc_miner_details is None:
try: try:
grpc_miner_details = await self.web.grpc.get_miner_details() grpc_miner_details = await self.web.grpc.get_miner_details()
except APIError: except APIError:
pass pass
if grpc_miner_details: if grpc_miner_details is not None:
try: try:
return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000 return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000
except LookupError: except LookupError:
pass pass
async def _get_hashboards(self, grpc_hashboards: dict = None): async def _get_hashboards(self, grpc_hashboards: dict = None) -> List[HashBoard]:
hashboards = [ hashboards = [
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
@@ -927,9 +835,6 @@ class BOSer(BaseMiner):
return hashboards return hashboards
async def _get_env_temp(self) -> Optional[float]:
return None
async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]:
if grpc_miner_stats is None: if grpc_miner_stats is None:
try: try:
@@ -937,7 +842,7 @@ class BOSer(BaseMiner):
except APIError: except APIError:
pass pass
if grpc_miner_stats: if grpc_miner_stats is not None:
try: try:
return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"] return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"]
except KeyError: except KeyError:
@@ -954,7 +859,7 @@ class BOSer(BaseMiner):
except APIError: except APIError:
pass pass
if grpc_active_performance_mode: if grpc_active_performance_mode is not None:
try: try:
return grpc_active_performance_mode["tunerMode"]["powerTarget"][ return grpc_active_performance_mode["tunerMode"]["powerTarget"][
"powerTarget" "powerTarget"
@@ -969,7 +874,7 @@ class BOSer(BaseMiner):
except APIError: except APIError:
pass pass
if grpc_cooling_state: if grpc_cooling_state is not None:
fans = [] fans = []
for n in range(self.expected_fans): for n in range(self.expected_fans):
try: try:
@@ -979,17 +884,14 @@ class BOSer(BaseMiner):
return fans return fans
return [Fan() for _ in range(self.expected_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]: async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]:
if not api_tunerstatus: if api_tunerstatus is None:
try: try:
api_tunerstatus = await self.api.tunerstatus() api_tunerstatus = await self.api.tunerstatus()
except APIError: except APIError:
pass pass
if api_tunerstatus: if api_tunerstatus is not None:
errors = [] errors = []
try: try:
chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"]
@@ -1016,7 +918,7 @@ class BOSer(BaseMiner):
if self.light is not None: if self.light is not None:
return self.light return self.light
if not grpc_locate_device_status: if grpc_locate_device_status is None:
try: try:
grpc_locate_device_status = ( grpc_locate_device_status = (
await self.web.grpc.get_locate_device_status() await self.web.grpc.get_locate_device_status()
@@ -1033,7 +935,7 @@ class BOSer(BaseMiner):
pass pass
async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]:
if not api_devdetails: if api_devdetails is None:
try: try:
api_devdetails = await self.api.send_command( api_devdetails = await self.api.send_command(
"devdetails", ignore_errors=True, allow_warning=False "devdetails", ignore_errors=True, allow_warning=False
@@ -1041,20 +943,20 @@ class BOSer(BaseMiner):
except APIError: except APIError:
pass pass
if api_devdetails: if api_devdetails is not None:
try: try:
return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable"
except LookupError: except LookupError:
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -17,7 +17,6 @@
import logging import logging
from typing import List, Optional from typing import List, Optional
from pyasic.API.btminer import BTMinerAPI
from pyasic.config import MinerConfig, MiningModeConfig from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData, WhatsminerError from pyasic.data.error_codes import MinerErrorData, WhatsminerError
@@ -29,6 +28,7 @@ from pyasic.miners.base import (
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
) )
from pyasic.rpc.btminer import BTMinerRPCAPI
BTMINER_DATA_LOC = DataLocations( BTMINER_DATA_LOC = DataLocations(
**{ **{
@@ -40,7 +40,8 @@ BTMINER_DATA_LOC = DataLocations(
], ],
), ),
str(DataOptions.API_VERSION): DataFunction( 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( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", "_get_fw_ver",
@@ -50,25 +51,32 @@ BTMINER_DATA_LOC = DataLocations(
], ],
), ),
str(DataOptions.HOSTNAME): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_expected_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", [RPCAPICommand("api_devs", "devs")] "_get_hashboards",
[RPCAPICommand("api_devs", "devs")],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction( str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp", [RPCAPICommand("api_summary", "summary")] "_get_env_temp",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", [RPCAPICommand("api_summary", "summary")] "_get_wattage",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", [RPCAPICommand("api_summary", "summary")] "_get_wattage_limit",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
@@ -96,31 +104,26 @@ BTMINER_DATA_LOC = DataLocations(
[RPCAPICommand("api_get_miner_info", "get_miner_info")], [RPCAPICommand("api_get_miner_info", "get_miner_info")],
), ),
str(DataOptions.IS_MINING): DataFunction( str(DataOptions.IS_MINING): DataFunction(
"_is_mining", [RPCAPICommand("api_status", "status")] "_is_mining",
[RPCAPICommand("api_status", "status")],
), ),
str(DataOptions.UPTIME): DataFunction( 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): class BTMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Base handler for BTMiner based miners."""
super().__init__(ip)
# interfaces
self.api = BTMinerAPI(ip, api_ver)
# static data _api_cls = BTMinerRPCAPI
self.api_type = "BTMiner" api: BTMinerRPCAPI
# data gathering locations
self.data_locations = BTMINER_DATA_LOC
# autotuning/shutdown support
self.supports_shutdown = True
# data storage data_locations = BTMINER_DATA_LOC
self.api_ver = api_ver
supports_shutdown = True
async def _reset_api_pwd_to_admin(self, pwd: str): async def _reset_api_pwd_to_admin(self, pwd: str):
try: try:
@@ -287,26 +290,26 @@ class BTMiner(BaseMiner):
async def _get_mac( async def _get_mac(
self, api_summary: dict = None, api_get_miner_info: dict = None self, api_summary: dict = None, api_get_miner_info: dict = None
) -> Optional[str]: ) -> Optional[str]:
if not api_get_miner_info: if api_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() api_get_miner_info = await self.api.get_miner_info()
except APIError: except APIError:
pass pass
if api_get_miner_info: if api_get_miner_info is not None:
try: try:
mac = api_get_miner_info["Msg"]["mac"] mac = api_get_miner_info["Msg"]["mac"]
return str(mac).upper() return str(mac).upper()
except KeyError: except KeyError:
pass pass
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
mac = api_summary["SUMMARY"][0]["MAC"] mac = api_summary["SUMMARY"][0]["MAC"]
return str(mac).upper() return str(mac).upper()
@@ -314,13 +317,13 @@ class BTMiner(BaseMiner):
pass pass
async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: 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: try:
api_get_version = await self.api.get_version() api_get_version = await self.api.get_version()
except APIError: except APIError:
pass pass
if api_get_version: if api_get_version is not None:
if "Code" in api_get_version.keys(): if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131: if api_get_version["Code"] == 131:
try: try:
@@ -339,13 +342,13 @@ class BTMiner(BaseMiner):
async def _get_fw_ver( async def _get_fw_ver(
self, api_get_version: dict = None, api_summary: dict = None self, api_get_version: dict = None, api_summary: dict = None
) -> Optional[str]: ) -> Optional[str]:
if not api_get_version: if api_get_version is None:
try: try:
api_get_version = await self.api.get_version() api_get_version = await self.api.get_version()
except APIError: except APIError:
pass pass
if api_get_version: if api_get_version is not None:
if "Code" in api_get_version.keys(): if "Code" in api_get_version.keys():
if api_get_version["Code"] == 131: if api_get_version["Code"] == 131:
try: try:
@@ -355,7 +358,7 @@ class BTMiner(BaseMiner):
else: else:
return self.fw_ver return self.fw_ver
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
@@ -373,13 +376,13 @@ class BTMiner(BaseMiner):
async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]:
hostname = None hostname = None
if not api_get_miner_info: if api_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() api_get_miner_info = await self.api.get_miner_info()
except APIError: except APIError:
return None # only one way to get this return None # only one way to get this
if api_get_miner_info: if api_get_miner_info is not None:
try: try:
hostname = api_get_miner_info["Msg"]["hostname"] hostname = api_get_miner_info["Msg"]["hostname"]
except KeyError: except KeyError:
@@ -388,14 +391,13 @@ class BTMiner(BaseMiner):
return hostname return hostname
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API if api_summary is None:
if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except LookupError: except LookupError:
@@ -407,13 +409,13 @@ class BTMiner(BaseMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if not api_devs: if api_devs is None:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
except APIError: except APIError:
pass pass
if api_devs: if api_devs is not None:
try: try:
for board in api_devs["DEVS"]: for board in api_devs["DEVS"]:
if len(hashboards) < board["ASC"] + 1: if len(hashboards) < board["ASC"] + 1:
@@ -437,26 +439,26 @@ class BTMiner(BaseMiner):
return hashboards return hashboards
async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]: async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return api_summary["SUMMARY"][0]["Env Temp"] return api_summary["SUMMARY"][0]["Env Temp"]
except LookupError: except LookupError:
pass pass
async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: async def _get_wattage(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
wattage = api_summary["SUMMARY"][0]["Power"] wattage = api_summary["SUMMARY"][0]["Power"]
return wattage if not wattage == -1 else None return wattage if not wattage == -1 else None
@@ -464,13 +466,13 @@ class BTMiner(BaseMiner):
pass pass
async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return api_summary["SUMMARY"][0]["Power Limit"] return api_summary["SUMMARY"][0]["Power Limit"]
except LookupError: except LookupError:
@@ -479,14 +481,14 @@ class BTMiner(BaseMiner):
async def _get_fans( async def _get_fans(
self, api_summary: dict = None, api_get_psu: dict = None self, api_summary: dict = None, api_get_psu: dict = None
) -> List[Fan]: ) -> List[Fan]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
fans = [Fan() for _ in range(self.expected_fans)] fans = [Fan() for _ in range(self.expected_fans)]
if api_summary: if api_summary is not None:
try: try:
if self.expected_fans > 0: if self.expected_fans > 0:
fans = [ fans = [
@@ -501,25 +503,25 @@ class BTMiner(BaseMiner):
async def _get_fan_psu( async def _get_fan_psu(
self, api_summary: dict = None, api_get_psu: dict = None self, api_summary: dict = None, api_get_psu: dict = None
) -> Optional[int]: ) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Power Fanspeed"]) return int(api_summary["SUMMARY"][0]["Power Fanspeed"])
except LookupError: except LookupError:
pass pass
if not api_get_psu: if api_get_psu is None:
try: try:
api_get_psu = await self.api.get_psu() api_get_psu = await self.api.get_psu()
except APIError: except APIError:
pass pass
if api_get_psu: if api_get_psu is not None:
try: try:
return int(api_get_psu["Msg"]["fan_speed"]) return int(api_get_psu["Msg"]["fan_speed"])
except (KeyError, TypeError): except (KeyError, TypeError):
@@ -529,27 +531,30 @@ class BTMiner(BaseMiner):
self, api_summary: dict = None, api_get_error_code: dict = None self, api_summary: dict = None, api_get_error_code: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:
errors = [] errors = []
if not api_get_error_code and not api_summary: if api_get_error_code is None and api_summary is None:
try: try:
api_get_error_code = await self.api.get_error_code() api_get_error_code = await self.api.get_error_code()
except APIError: except APIError:
pass pass
if api_get_error_code: if api_get_error_code is not None:
for err in api_get_error_code["Msg"]["error_code"]: try:
if isinstance(err, dict): for err in api_get_error_code["Msg"]["error_code"]:
for code in err: if isinstance(err, dict):
errors.append(WhatsminerError(error_code=int(code))) for code in err:
else: errors.append(WhatsminerError(error_code=int(code)))
errors.append(WhatsminerError(error_code=int(err))) else:
errors.append(WhatsminerError(error_code=int(err)))
except KeyError:
pass
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
for i in range(api_summary["SUMMARY"][0]["Error Code Count"]): for i in range(api_summary["SUMMARY"][0]["Error Code Count"]):
err = api_summary["SUMMARY"][0].get(f"Error Code {i}") err = api_summary["SUMMARY"][0].get(f"Error Code {i}")
@@ -559,14 +564,14 @@ class BTMiner(BaseMiner):
pass pass
return errors return errors
async def _get_expected_hashrate(self, api_summary: dict = None): async def _get_expected_hashrate(self, api_summary: dict = None) -> Optional[float]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"]
if expected_hashrate: if expected_hashrate:
@@ -574,15 +579,15 @@ class BTMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool: async def _get_fault_light(self, api_get_miner_info: dict = None) -> Optional[bool]:
if not api_get_miner_info: if api_get_miner_info is None:
try: try:
api_get_miner_info = await self.api.get_miner_info() api_get_miner_info = await self.api.get_miner_info()
except APIError: except APIError:
if not self.light: if not self.light:
self.light = False self.light = False
if api_get_miner_info: if api_get_miner_info is not None:
try: try:
self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto") self.light = not (api_get_miner_info["Msg"]["ledstat"] == "auto")
except KeyError: except KeyError:
@@ -613,13 +618,13 @@ class BTMiner(BaseMiner):
await self.api.set_hostname(hostname) await self.api.set_hostname(hostname)
async def _is_mining(self, api_status: dict = None) -> Optional[bool]: async def _is_mining(self, api_status: dict = None) -> Optional[bool]:
if not api_status: if api_status is None:
try: try:
api_status = await self.api.status() api_status = await self.api.status()
except APIError: except APIError:
pass pass
if api_status: if api_status is not None:
try: try:
if api_status["Msg"].get("btmineroff"): if api_status["Msg"].get("btmineroff"):
try: try:
@@ -632,13 +637,13 @@ class BTMiner(BaseMiner):
pass pass
async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: async def _get_uptime(self, api_summary: dict = None) -> Optional[int]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return int(api_summary["SUMMARY"][0]["Elapsed"]) return int(api_summary["SUMMARY"][0]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -14,13 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import logging from typing import Optional
from typing import List, Optional
from pyasic.API.cgminer import CGMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import (
BaseMiner, BaseMiner,
@@ -29,129 +25,49 @@ from pyasic.miners.base import (
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
) )
from pyasic.rpc.cgminer import CGMinerRPCAPI
CGMINER_DATA_LOC = DataLocations( CGMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction("_get_mac"),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( 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( 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( 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): class CGMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Base handler for CGMiner based miners"""
super().__init__(ip)
# interfaces
self.api = CGMinerAPI(ip, api_ver)
# static data _api_cls = CGMinerRPCAPI
self.api_type = "CGMiner" api: CGMinerRPCAPI
# data gathering locations
self.data_locations = CGMINER_DATA_LOC
# data storage data_locations = CGMINER_DATA_LOC
self.api_ver = api_ver
async def send_ssh_command(self, cmd: str) -> Optional[str]:
result = None
try:
conn = await self._get_ssh_connection()
except ConnectionError:
return None
# open an ssh connection
async with conn:
# 3 retries
for i in range(3):
try:
# run the command and get the result
result = await conn.run(cmd)
result = result.stdout
except Exception as e:
# if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}")
# on the 3rd retry, return None
if i == 3:
return
continue
# return the result, either command output or None
return result
async def restart_backend(self) -> bool:
return await self.restart_cgminer()
async def restart_cgminer(self) -> bool:
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.")
ret = await self.send_ssh_command("reboot")
if ret is None:
return False
return True
async def resume_mining(self) -> bool:
commands = [
"mkdir -p /etc/tmp/",
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
"crontab -u root /etc/tmp/root",
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
async def stop_mining(self) -> bool:
commands = [
"mkdir -p /etc/tmp/",
'echo "" > /etc/tmp/root',
"crontab -u root /etc/tmp/root",
"killall cgminer",
]
commands = ";".join(commands)
ret = await self.send_ssh_command(commands)
if ret is None:
return False
return True
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
@@ -163,33 +79,18 @@ class CGMiner(BaseMiner):
self.config = MinerConfig.from_api(pools) self.config = MinerConfig.from_api(pools)
return self.config return self.config
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
return None
async def set_power_limit(self, wattage: int) -> bool:
return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self) -> Optional[str]:
return None
async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: async def _get_api_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.api_ver = api_version["VERSION"][0]["API"] self.api_ver = api_version["VERSION"][0]["API"]
except LookupError: except LookupError:
@@ -198,13 +99,13 @@ class CGMiner(BaseMiner):
return self.api_ver return self.api_ver
async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]:
if not api_version: if api_version is None:
try: try:
api_version = await self.api.version() api_version = await self.api.version()
except APIError: except APIError:
pass pass
if api_version: if api_version is not None:
try: try:
self.fw_ver = api_version["VERSION"][0]["CGMiner"] self.fw_ver = api_version["VERSION"][0]["CGMiner"]
except LookupError: except LookupError:
@@ -212,19 +113,14 @@ class CGMiner(BaseMiner):
return self.fw_ver 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]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API if api_summary is None:
if not api_summary:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round( return round(
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
@@ -232,141 +128,14 @@ class CGMiner(BaseMiner):
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass 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]: async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -33,20 +33,24 @@ from pyasic.web.epic import ePICWebAPI
EPIC_DATA_LOC = DataLocations( EPIC_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [WebAPICommand("web_network", "network")] "_get_mac",
[WebAPICommand("web_network", "network")],
), ),
str(DataOptions.API_VERSION): DataFunction("_get_api_ver"),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [WebAPICommand("web_summary", "summary")] "_get_fw_ver",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", [WebAPICommand("web_summary", "summary")] "_get_hostname",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [WebAPICommand("web_summary", "summary")] "_get_hashrate",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [WebAPICommand("web_summary", "summary")] "_get_expected_hashrate",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
@@ -55,41 +59,41 @@ EPIC_DATA_LOC = DataLocations(
WebAPICommand("web_hashrate", "hashrate"), WebAPICommand("web_hashrate", "hashrate"),
], ],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", [WebAPICommand("web_summary", "summary")] "_get_wattage",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", [WebAPICommand("web_summary", "summary")] "_get_fans",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", [WebAPICommand("web_summary", "summary")] "_get_errors",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction( str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light", [WebAPICommand("web_summary", "summary")] "_get_fault_light",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [WebAPICommand("web_summary", "summary")] "_get_uptime",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
class ePIC(BaseMiner): class ePIC(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for miners with the ePIC board"""
super().__init__(ip, api_ver)
# interfaces
self.web = ePICWebAPI(ip)
# static data _web_cls = ePICWebAPI
self.api_type = "ePIC" web: ePICWebAPI
self.fw_str = "ePIC"
# data gathering locations firmware = "ePIC"
self.data_locations = EPIC_DATA_LOC
data_locations = EPIC_DATA_LOC
supports_shutdown = True
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
summary = None summary = None
@@ -144,10 +148,14 @@ class ePIC(BaseMiner):
pass pass
return False return False
async def _get_mac(self, web_network: dict = None) -> str: async def _get_mac(self, web_network: dict = None) -> Optional[str]:
if not web_network: if web_network is None:
web_network = await self.web.network() try:
if web_network: web_network = await self.web.network()
except APIError:
pass
if web_network is not None:
try: try:
for network in web_network: for network in web_network:
mac = web_network[network]["mac_address"] mac = web_network[network]["mac_address"]
@@ -155,10 +163,14 @@ class ePIC(BaseMiner):
except KeyError: except KeyError:
pass pass
async def _get_hostname(self, web_summary: dict = None) -> str: async def _get_hostname(self, web_summary: dict = None) -> Optional[str]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() try:
if web_summary: web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try: try:
hostname = web_summary["Hostname"] hostname = web_summary["Hostname"]
return hostname return hostname
@@ -166,10 +178,13 @@ class ePIC(BaseMiner):
pass pass
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]: async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary: if web_summary is not None:
try: try:
wattage = web_summary["Power Supply Stats"]["Input Power"] wattage = web_summary["Power Supply Stats"]["Input Power"]
wattage = round(wattage) wattage = round(wattage)
@@ -178,14 +193,13 @@ class ePIC(BaseMiner):
pass pass
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
# get hr from API if web_summary is None:
if not web_summary:
try: try:
web_summary = await self.web.summary() web_summary = await self.web.summary()
except APIError: except APIError:
pass pass
if web_summary: if web_summary is not None:
try: try:
hashrate = 0 hashrate = 0
if web_summary["HBs"] is not None: if web_summary["HBs"] is not None:
@@ -196,14 +210,13 @@ class ePIC(BaseMiner):
pass pass
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]: async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
# get hr from API if web_summary is None:
if not web_summary:
try: try:
web_summary = await self.web.summary() web_summary = await self.web.summary()
except APIError: except APIError:
pass pass
if web_summary: if web_summary is not None:
try: try:
hashrate = 0 hashrate = 0
if web_summary.get("HBs") is not None: if web_summary.get("HBs") is not None:
@@ -219,10 +232,13 @@ class ePIC(BaseMiner):
pass pass
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]: async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary: if web_summary is not None:
try: try:
fw_ver = web_summary["Software"] fw_ver = web_summary["Software"]
fw_ver = fw_ver.split(" ")[1].replace("v", "") fw_ver = fw_ver.split(" ")[1].replace("v", "")
@@ -231,7 +247,7 @@ class ePIC(BaseMiner):
pass pass
async def _get_fans(self, web_summary: dict = None) -> List[Fan]: async def _get_fans(self, web_summary: dict = None) -> List[Fan]:
if not web_summary: if web_summary is None:
try: try:
web_summary = await self.web.summary() web_summary = await self.web.summary()
except APIError: except APIError:
@@ -239,7 +255,7 @@ class ePIC(BaseMiner):
fans = [] fans = []
if web_summary: if web_summary is not None:
for fan in web_summary["Fans Rpm"]: for fan in web_summary["Fans Rpm"]:
try: try:
fans.append(Fan(web_summary["Fans Rpm"][fan])) fans.append(Fan(web_summary["Fans Rpm"][fan]))
@@ -250,12 +266,13 @@ class ePIC(BaseMiner):
async def _get_hashboards( async def _get_hashboards(
self, web_summary: dict = None, web_hashrate: dict = None self, web_summary: dict = None, web_hashrate: dict = None
) -> List[HashBoard]: ) -> List[HashBoard]:
if not web_summary: if web_summary is None:
try: try:
web_summary = await self.web.summary() web_summary = await self.web.summary()
except APIError: except APIError:
pass pass
if not web_hashrate:
if web_hashrate is not None:
try: try:
web_hashrate = await self.web.hashrate() web_hashrate = await self.web.hashrate()
except APIError: except APIError:
@@ -283,9 +300,13 @@ class ePIC(BaseMiner):
return None return None
async def _get_uptime(self, web_summary: dict = None) -> Optional[int]: async def _get_uptime(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() try:
if web_summary: web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try: try:
uptime = web_summary["Session"]["Uptime"] uptime = web_summary["Session"]["Uptime"]
return uptime return uptime
@@ -293,10 +314,14 @@ class ePIC(BaseMiner):
pass pass
return None return None
async def _get_fault_light(self, web_summary: dict = None) -> bool: async def _get_fault_light(self, web_summary: dict = None) -> Optional[bool]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() try:
if web_summary: web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try: try:
light = web_summary["Misc"]["Locate Miner State"] light = web_summary["Misc"]["Locate Miner State"]
return light return light
@@ -306,9 +331,13 @@ class ePIC(BaseMiner):
async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]:
if not web_summary: if not web_summary:
web_summary = await self.web.summary() try:
web_summary = await self.web.summary()
except APIError:
pass
errors = [] errors = []
if web_summary: if web_summary is not None:
try: try:
error = web_summary["Status"]["Last Error"] error = web_summary["Status"]["Last Error"]
if error is not None: if error is not None:
@@ -317,27 +346,3 @@ class ePIC(BaseMiner):
except KeyError: except KeyError:
pass pass
return errors return errors
async def fault_light_off(self) -> bool:
return False
async def fault_light_on(self) -> bool:
return False
async def _get_api_ver(self, *args, **kwargs) -> Optional[str]:
pass
async def _get_env_temp(self, *args, **kwargs) -> Optional[float]:
pass
async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]:
pass
async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]:
pass
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
async def set_power_limit(self, wattage: int) -> bool:
return False

View File

@@ -13,7 +13,7 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List, Optional from typing import List
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import HashBoard from pyasic.data import HashBoard
@@ -32,20 +32,24 @@ from pyasic.web.goldshell import GoldshellWebAPI
GOLDSHELL_DATA_LOC = DataLocations( GOLDSHELL_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [WebAPICommand("web_setting", "setting")] "_get_mac",
[WebAPICommand("web_setting", "setting")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [WebAPICommand("web_status", "status")] "_get_fw_ver",
[WebAPICommand("web_status", "status")],
), ),
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
@@ -54,31 +58,21 @@ GOLDSHELL_DATA_LOC = DataLocations(
RPCAPICommand("api_devdetails", "devdetails"), 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( 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): class GoldshellMiner(BFGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for goldshell miners"""
super().__init__(ip, api_ver)
# interfaces
self.web = GoldshellWebAPI(ip)
# static data _web_cls = GoldshellWebAPI
# data gathering locations web: GoldshellWebAPI
self.data_locations = GOLDSHELL_DATA_LOC
data_locations = GOLDSHELL_DATA_LOC
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
@@ -110,26 +104,26 @@ class BFGMinerGoldshell(BFGMiner):
) )
async def _get_mac(self, web_setting: dict = None) -> str: async def _get_mac(self, web_setting: dict = None) -> str:
if not web_setting: if web_setting is None:
try: try:
web_setting = await self.web.setting() web_setting = await self.web.setting()
except APIError: except APIError:
pass pass
if web_setting: if web_setting is not None:
try: try:
return web_setting["name"] return web_setting["name"]
except KeyError: except KeyError:
pass pass
async def _get_fw_ver(self, web_status: dict = None) -> str: async def _get_fw_ver(self, web_status: dict = None) -> str:
if not web_status: if web_status is None:
try: try:
web_status = await self.web.setting() web_status = await self.web.setting()
except APIError: except APIError:
pass pass
if web_status: if web_status is not None:
try: try:
return web_status["firmware"] return web_status["firmware"]
except KeyError: except KeyError:
@@ -138,7 +132,7 @@ class BFGMinerGoldshell(BFGMiner):
async def _get_hashboards( async def _get_hashboards(
self, api_devs: dict = None, api_devdetails: dict = None self, api_devs: dict = None, api_devdetails: dict = None
) -> List[HashBoard]: ) -> List[HashBoard]:
if not api_devs: if api_devs is None:
try: try:
api_devs = await self.api.devs() api_devs = await self.api.devs()
except APIError: except APIError:
@@ -149,7 +143,7 @@ class BFGMinerGoldshell(BFGMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if api_devs: if api_devs is not None:
if api_devs.get("DEVS"): if api_devs.get("DEVS"):
for board in api_devs["DEVS"]: for board in api_devs["DEVS"]:
if board.get("ID") is not None: if board.get("ID") is not None:
@@ -165,13 +159,13 @@ class BFGMinerGoldshell(BFGMiner):
else: else:
logger.error(self, api_devs) logger.error(self, api_devs)
if not api_devdetails: if api_devdetails is None:
try: try:
api_devdetails = await self.api.devdetails() api_devdetails = await self.api.devdetails()
except APIError: except APIError:
pass pass
if api_devdetails: if api_devdetails is not None:
if api_devdetails.get("DEVS"): if api_devdetails.get("DEVS"):
for board in api_devdetails["DEVS"]: for board in api_devdetails["DEVS"]:
if board.get("ID") is not None: if board.get("ID") is not None:
@@ -184,9 +178,3 @@ class BFGMinerGoldshell(BFGMiner):
logger.error(self, api_devdetails) logger.error(self, api_devdetails)
return hashboards return hashboards
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
return None
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
return None

View File

@@ -14,67 +14,8 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List, Optional
from pyasic.data import HashBoard
from pyasic.miners.backends import BMMiner from pyasic.miners.backends import BMMiner
from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand
HIVEON_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction("get_mac"),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")]
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [RPCAPICommand("api_version", "version")]
),
str(DataOptions.HOSTNAME): DataFunction("_get_hostname"),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")]
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction(
"_get_env_temp", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"),
str(DataOptions.FANS): DataFunction(
"_get_fans", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction("_get_errors"),
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("api_stats", "stats")]
),
str(DataOptions.CONFIG): DataFunction("get_config"),
}
)
class Hiveon(BMMiner): class Hiveon(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: firmware = "Hive"
super().__init__(ip, api_ver)
self.pwd = "admin"
# static data
self.api_type = "Hiveon"
# data gathering locations
self.data_locations = HIVEON_DATA_LOC
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
pass
async def _get_wattage(self, api_stats: dict = None) -> Optional[int]:
pass
async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]:
pass

View File

@@ -40,12 +40,13 @@ INNOSILICON_DATA_LOC = DataLocations(
], ],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( 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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", "_get_hashrate",
[ [
@@ -53,9 +54,6 @@ INNOSILICON_DATA_LOC = DataLocations(
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
),
str(DataOptions.HASHBOARDS): DataFunction( str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards", "_get_hashboards",
[ [
@@ -63,7 +61,6 @@ INNOSILICON_DATA_LOC = DataLocations(
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"),
str(DataOptions.WATTAGE): DataFunction( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", "_get_wattage",
[ [
@@ -83,43 +80,29 @@ INNOSILICON_DATA_LOC = DataLocations(
WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_get_all", "getAll"),
], ],
), ),
str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"),
str(DataOptions.ERRORS): DataFunction( str(DataOptions.ERRORS): DataFunction(
"_get_errors", "_get_errors",
[ [
WebAPICommand("web_get_error_detail", "getErrorDetail"), WebAPICommand("web_get_error_detail", "getErrorDetail"),
], ],
), ),
str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"),
str(DataOptions.IS_MINING): DataFunction("_is_mining"),
str(DataOptions.UPTIME): DataFunction( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
class Innosilicon(CGMiner): class Innosilicon(CGMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Base handler for Innosilicon miners"""
super().__init__(ip, api_ver=api_ver)
# interfaces
self.web = InnosiliconWebAPI(ip)
# static data _web_cls = InnosiliconWebAPI
# data gathering locations web: InnosiliconWebAPI
self.data_locations = INNOSILICON_DATA_LOC
# autotuning/shutdown support
self.supports_shutdown = True
# data storage data_locations = INNOSILICON_DATA_LOC
self.api_ver = api_ver
async def fault_light_on(self) -> bool: supports_shutdown = True
return False
async def fault_light_off(self) -> bool:
return False
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# get pool data # get pool data
@@ -150,23 +133,6 @@ class Innosilicon(CGMiner):
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
return await self.restart_cgminer() return await self.restart_cgminer()
async def stop_mining(self) -> bool:
return False
# data = await self.web.poweroff()
# try:
# return data["success"]
# except KeyError:
# return False
async def resume_mining(self) -> bool:
return False
# data = await self.web.restart_cgminer()
# print(data)
# try:
# return data["success"]
# except KeyError:
# return False
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
self.config = config self.config = config
await self.web.update_pools(config.as_inno(user_suffix=user_suffix)) await self.web.update_pools(config.as_inno(user_suffix=user_suffix))
@@ -181,20 +147,20 @@ class Innosilicon(CGMiner):
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if not web_get_all and not web_overview: if web_get_all is None and web_overview is None:
try: try:
web_overview = await self.web.overview() web_overview = await self.web.overview()
except APIError: except APIError:
pass pass
if web_get_all: if web_get_all is not None:
try: try:
mac = web_get_all["mac"] mac = web_get_all["mac"]
return mac.upper() return mac.upper()
except KeyError: except KeyError:
pass pass
if web_overview: if web_overview is not None:
try: try:
mac = web_overview["version"]["ethaddr"] mac = web_overview["version"]["ethaddr"]
return mac.upper() return mac.upper()
@@ -207,13 +173,13 @@ class Innosilicon(CGMiner):
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if not api_summary and not web_get_all: if api_summary is None and web_get_all is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if web_get_all: if web_get_all is not None:
try: try:
if "Hash Rate H" in web_get_all["total_hash"].keys(): if "Hash Rate H" in web_get_all["total_hash"].keys():
return round( return round(
@@ -227,7 +193,7 @@ class Innosilicon(CGMiner):
except KeyError: except KeyError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2)
except (KeyError, IndexError): except (KeyError, IndexError):
@@ -244,13 +210,13 @@ class Innosilicon(CGMiner):
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if not web_get_all: if web_get_all is None:
try: try:
web_get_all = await self.web.get_all() web_get_all = await self.web.get_all()
except APIError: except APIError:
@@ -258,7 +224,7 @@ class Innosilicon(CGMiner):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if api_stats: if api_stats is not None:
if api_stats.get("STATS"): if api_stats.get("STATS"):
for board in api_stats["STATS"]: for board in api_stats["STATS"]:
try: try:
@@ -270,7 +236,7 @@ class Innosilicon(CGMiner):
hashboards[idx].chips = chips hashboards[idx].chips = chips
hashboards[idx].missing = False hashboards[idx].missing = False
if web_get_all: if web_get_all is not None:
if web_get_all.get("chain"): if web_get_all.get("chain"):
for board in web_get_all["chain"]: for board in web_get_all["chain"]:
idx = board.get("ASC") idx = board.get("ASC")
@@ -297,7 +263,7 @@ class Innosilicon(CGMiner):
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if not web_get_all: if web_get_all is None:
try: try:
web_get_all = await self.web.get_all() web_get_all = await self.web.get_all()
except APIError: except APIError:
@@ -305,19 +271,19 @@ class Innosilicon(CGMiner):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if web_get_all: if web_get_all is not None:
try: try:
return web_get_all["power"] return web_get_all["power"]
except KeyError: except KeyError:
pass pass
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
if api_stats.get("STATS"): if api_stats.get("STATS"):
for board in api_stats["STATS"]: for board in api_stats["STATS"]:
try: try:
@@ -332,7 +298,7 @@ class Innosilicon(CGMiner):
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if not web_get_all: if web_get_all is None:
try: try:
web_get_all = await self.web.get_all() web_get_all = await self.web.get_all()
except APIError: except APIError:
@@ -341,7 +307,7 @@ class Innosilicon(CGMiner):
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
fans = [Fan() for _ in range(self.expected_fans)] fans = [Fan() for _ in range(self.expected_fans)]
if web_get_all: if web_get_all is not None:
try: try:
spd = web_get_all["fansSpeed"] spd = web_get_all["fansSpeed"]
except KeyError: except KeyError:
@@ -357,13 +323,13 @@ class Innosilicon(CGMiner):
self, web_get_error_detail: dict = None self, web_get_error_detail: dict = None
) -> List[MinerErrorData]: ) -> List[MinerErrorData]:
errors = [] errors = []
if not web_get_error_detail: if web_get_error_detail is None:
try: try:
web_get_error_detail = await self.web.get_error_detail() web_get_error_detail = await self.web.get_error_detail()
except APIError: except APIError:
pass pass
if web_get_error_detail: if web_get_error_detail is not None:
try: try:
# only 1 error? # only 1 error?
# TODO: check if this should be a loop, can't remember. # TODO: check if this should be a loop, can't remember.
@@ -380,7 +346,7 @@ class Innosilicon(CGMiner):
if web_get_all: if web_get_all:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if not web_get_all: if web_get_all is None:
try: try:
web_get_all = await self.web.get_all() web_get_all = await self.web.get_all()
except APIError: except APIError:
@@ -388,7 +354,7 @@ class Innosilicon(CGMiner):
else: else:
web_get_all = web_get_all["all"] web_get_all = web_get_all["all"]
if web_get_all: if web_get_all is not None:
try: try:
level = web_get_all["running_mode"]["level"] level = web_get_all["running_mode"]["level"]
except KeyError: except KeyError:
@@ -398,6 +364,3 @@ class Innosilicon(CGMiner):
level = int(level) level = int(level)
limit = 1250 + (250 * level) limit = 1250 + (250 * level)
return limit return limit
async def _get_expected_hashrate(self) -> Optional[float]:
pass

View File

@@ -13,12 +13,10 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from typing import List, Optional, Tuple, Union from typing import List, Optional
from pyasic.API.luxminer import LUXMinerAPI
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard from pyasic.data import Fan, HashBoard
from pyasic.data.error_codes import MinerErrorData
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.base import ( from pyasic.miners.base import (
BaseMiner, BaseMiner,
@@ -27,62 +25,50 @@ from pyasic.miners.base import (
DataOptions, DataOptions,
RPCAPICommand, RPCAPICommand,
) )
from pyasic.rpc.luxminer import LUXMinerRPCAPI
LUXMINER_DATA_LOC = DataLocations( LUXMINER_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [RPCAPICommand("api_config", "config")] "_get_mac",
[RPCAPICommand("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( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( 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( 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( 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( str(DataOptions.UPTIME): DataFunction(
"_get_uptime", [RPCAPICommand("api_stats", "stats")] "_get_uptime", [RPCAPICommand("api_stats", "stats")]
), ),
str(DataOptions.CONFIG): DataFunction("get_config"),
} }
) )
class LUXMiner(BaseMiner): class LUXMiner(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for LuxOS miners"""
super().__init__(ip)
# interfaces
self.api = LUXMinerAPI(ip, api_ver)
# self.web = BOSMinerWebAPI(ip)
# static data _api_cls = LUXMinerRPCAPI
self.api_type = "LUXMiner" api: LUXMinerRPCAPI
self.fw_str = "LuxOS"
# data gathering locations
self.data_locations = LUXMINER_DATA_LOC
# autotuning/shutdown support
# self.supports_autotuning = True
# self.supports_shutdown = True
# data storage firmware = "LuxOS"
self.api_ver = api_ver
data_locations = LUXMINER_DATA_LOC
async def _get_session(self) -> Optional[str]: async def _get_session(self) -> Optional[str]:
try: try:
@@ -163,25 +149,19 @@ class LUXMiner(BaseMiner):
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
return self.config return self.config
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
pass
async def set_power_limit(self, wattage: int) -> bool:
return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
################################################## ##################################################
async def _get_mac(self, api_config: dict = None) -> Optional[str]: async def _get_mac(self, api_config: dict = None) -> Optional[str]:
mac = None mac = None
if not api_config: if api_config is None:
try: try:
api_config = await self.api.config() api_config = await self.api.config()
except APIError: except APIError:
return None return None
if api_config: if api_config is not None:
try: try:
mac = api_config["CONFIG"][0]["MACAddr"] mac = api_config["CONFIG"][0]["MACAddr"]
except KeyError: except KeyError:
@@ -189,26 +169,14 @@ class LUXMiner(BaseMiner):
return mac 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]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2) return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2)
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
@@ -217,13 +185,13 @@ class LUXMiner(BaseMiner):
async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]:
hashboards = [] hashboards = []
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
board_offset = -1 board_offset = -1
boards = api_stats["STATS"] boards = api_stats["STATS"]
@@ -268,27 +236,21 @@ class LUXMiner(BaseMiner):
return hashboards return hashboards
async def _get_env_temp(self) -> Optional[float]: async def _get_wattage(self, api_power: dict = None) -> Optional[int]:
return None if api_power is None:
async def _get_wattage(self, api_power: dict) -> Optional[int]:
if not api_power:
try: try:
api_power = await self.api.power() api_power = await self.api.power()
except APIError: except APIError:
pass pass
if api_power: if api_power is not None:
try: try:
return api_power["POWER"][0]["Watts"] return api_power["POWER"][0]["Watts"]
except (LookupError, ValueError, TypeError): except (LookupError, ValueError, TypeError):
pass pass
async def _get_wattage_limit(self) -> Optional[int]:
return None
async def _get_fans(self, api_fans: dict = None) -> List[Fan]: async def _get_fans(self, api_fans: dict = None) -> List[Fan]:
if not api_fans: if api_fans is None:
try: try:
api_fans = await self.api.fans() api_fans = await self.api.fans()
except APIError: except APIError:
@@ -296,7 +258,7 @@ class LUXMiner(BaseMiner):
fans = [] fans = []
if api_fans: if api_fans is not None:
for fan in range(self.expected_fans): for fan in range(self.expected_fans):
try: try:
fans.append(Fan(api_fans["FANS"][fan]["RPM"])) fans.append(Fan(api_fans["FANS"][fan]["RPM"]))
@@ -304,23 +266,14 @@ class LUXMiner(BaseMiner):
fans.append(Fan()) fans.append(Fan())
return fans 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]: async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
expected_rate = api_stats["STATS"][1]["total_rateideal"] expected_rate = api_stats["STATS"][1]["total_rateideal"]
try: try:
@@ -336,17 +289,14 @@ class LUXMiner(BaseMiner):
except LookupError: except LookupError:
pass pass
async def _is_mining(self) -> Optional[bool]:
pass
async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: async def _get_uptime(self, api_stats: dict = None) -> Optional[int]:
if not api_stats: if api_stats is None:
try: try:
api_stats = await self.api.stats() api_stats = await self.api.stats()
except APIError: except APIError:
pass pass
if api_stats: if api_stats is not None:
try: try:
return int(api_stats["STATS"][1]["Elapsed"]) return int(api_stats["STATS"][1]["Elapsed"])
except LookupError: except LookupError:

View File

@@ -18,7 +18,6 @@ from typing import Optional
from pyasic import MinerConfig from pyasic import MinerConfig
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.logger import logger
from pyasic.miners.backends.bmminer import BMMiner from pyasic.miners.backends.bmminer import BMMiner
from pyasic.miners.base import ( from pyasic.miners.base import (
DataFunction, DataFunction,
@@ -32,57 +31,58 @@ from pyasic.web.vnish import VNishWebAPI
VNISH_DATA_LOC = DataLocations( VNISH_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction( str(DataOptions.MAC): DataFunction(
"_get_mac", [WebAPICommand("web_summary", "summary")] "_get_mac",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", [RPCAPICommand("api_version", "version")] "_get_api_ver",
[RPCAPICommand("api_version", "version")],
), ),
str(DataOptions.FW_VERSION): DataFunction( str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver", [WebAPICommand("web_summary", "summary")] "_get_fw_ver",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.HOSTNAME): DataFunction( str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname", [WebAPICommand("web_summary", "summary")] "_get_hostname",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.HASHRATE): DataFunction( str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate", [RPCAPICommand("api_summary", "summary")] "_get_hashrate",
[RPCAPICommand("api_summary", "summary")],
), ),
str(DataOptions.EXPECTED_HASHRATE): DataFunction( str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] "_get_expected_hashrate",
[RPCAPICommand("api_stats", "stats")],
), ),
str(DataOptions.HASHBOARDS): DataFunction( 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( str(DataOptions.WATTAGE): DataFunction(
"_get_wattage", [WebAPICommand("web_summary", "summary")] "_get_wattage",
[WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.WATTAGE_LIMIT): DataFunction( str(DataOptions.WATTAGE_LIMIT): DataFunction(
"_get_wattage_limit", [WebAPICommand("web_settings", "settings")] "_get_wattage_limit",
[WebAPICommand("web_settings", "settings")],
), ),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", [RPCAPICommand("api_stats", "stats")] "_get_fans",
[RPCAPICommand("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): class VNish(BMMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: """Handler for VNish miners"""
super().__init__(ip, api_ver)
# interfaces
self.web = VNishWebAPI(ip)
# static data _web_cls = VNishWebAPI
self.api_type = "VNish" web: VNishWebAPI
self.fw_str = "VNish"
# data gathering locations firmware = "VNish"
self.data_locations = VNISH_DATA_LOC
data_locations = VNISH_DATA_LOC
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
data = await self.web.restart_vnish() data = await self.web.restart_vnish()
@@ -121,17 +121,17 @@ class VNish(BMMiner):
return False return False
async def _get_mac(self, web_summary: dict = None) -> str: async def _get_mac(self, web_summary: dict = None) -> str:
if not web_summary: if web_summary is None:
web_info = await self.web.info() web_info = await self.web.info()
if web_info: if web_info is not None:
try: try:
mac = web_info["system"]["network_status"]["mac"] mac = web_info["system"]["network_status"]["mac"]
return mac return mac
except KeyError: except KeyError:
pass pass
if web_summary: if web_summary is not None:
try: try:
mac = web_summary["system"]["network_status"]["mac"] mac = web_summary["system"]["network_status"]["mac"]
return mac return mac
@@ -139,17 +139,17 @@ class VNish(BMMiner):
pass pass
async def _get_hostname(self, web_summary: dict = None) -> str: async def _get_hostname(self, web_summary: dict = None) -> str:
if not web_summary: if web_summary is None:
web_info = await self.web.info() web_info = await self.web.info()
if web_info: if web_info is not None:
try: try:
hostname = web_info["system"]["network_status"]["hostname"] hostname = web_info["system"]["network_status"]["hostname"]
return hostname return hostname
except KeyError: except KeyError:
pass pass
if web_summary: if web_summary is not None:
try: try:
hostname = web_summary["system"]["network_status"]["hostname"] hostname = web_summary["system"]["network_status"]["hostname"]
return hostname return hostname
@@ -157,10 +157,10 @@ class VNish(BMMiner):
pass pass
async def _get_wattage(self, web_summary: dict = None) -> Optional[int]: async def _get_wattage(self, web_summary: dict = None) -> Optional[int]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() web_summary = await self.web.summary()
if web_summary: if web_summary is not None:
try: try:
wattage = web_summary["miner"]["power_usage"] wattage = web_summary["miner"]["power_usage"]
wattage = round(wattage * 1000) wattage = round(wattage * 1000)
@@ -170,26 +170,25 @@ class VNish(BMMiner):
async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]:
# get hr from API # get hr from API
if not api_summary: if api_summary is None:
try: try:
api_summary = await self.api.summary() api_summary = await self.api.summary()
except APIError: except APIError:
pass pass
if api_summary: if api_summary is not None:
try: try:
return round( return round(
float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2
) )
except (LookupError, ValueError, TypeError) as e: except (LookupError, ValueError, TypeError):
logger.error(e)
pass pass
async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]:
if not web_settings: if web_settings is None:
web_settings = await self.web.summary() web_settings = await self.web.summary()
if web_settings: if web_settings is not None:
try: try:
wattage_limit = web_settings["miner"]["overclock"]["preset"] wattage_limit = web_settings["miner"]["overclock"]["preset"]
if wattage_limit == "disabled": if wattage_limit == "disabled":
@@ -199,10 +198,10 @@ class VNish(BMMiner):
pass pass
async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]: async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]:
if not web_summary: if web_summary is None:
web_summary = await self.web.summary() web_summary = await self.web.summary()
if web_summary: if web_summary is not None:
try: try:
fw_ver = web_summary["miner"]["miner_type"] fw_ver = web_summary["miner"]["miner_type"]
fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "") fw_ver = fw_ver.split("(Vnish ")[1].replace(")", "")
@@ -210,12 +209,6 @@ class VNish(BMMiner):
except KeyError: except KeyError:
pass 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: async def get_config(self) -> MinerConfig:
try: try:
web_settings = await self.web.settings() web_settings = await self.web.settings()

View File

@@ -17,21 +17,15 @@ from pyasic.miners.backends.btminer import BTMiner
class M6X(BTMiner): class M6X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_autotuning = True
super().__init__(ip, api_ver)
self.supports_autotuning = True
class M5X(BTMiner): class M5X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_autotuning = True
super().__init__(ip, api_ver)
self.supports_autotuning = True
class M3X(BTMiner): class M3X(BTMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): supports_autotuning = True
super().__init__(ip, api_ver)
self.supports_autotuning = True
class M2X(BTMiner): class M2X(BTMiner):

View File

@@ -15,13 +15,10 @@
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
import asyncio import asyncio
import ipaddress import ipaddress
import logging import warnings
from abc import ABC, abstractmethod
from dataclasses import dataclass, field, make_dataclass from dataclasses import dataclass, field, make_dataclass
from enum import Enum from enum import Enum
from typing import List, Optional, Tuple, TypeVar, Union from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
import asyncssh
from pyasic.config import MinerConfig from pyasic.config import MinerConfig
from pyasic.data import Fan, HashBoard, MinerData from pyasic.data import Fan, HashBoard, MinerData
@@ -52,6 +49,14 @@ class DataOptions(Enum):
def __str__(self): def __str__(self):
return self.value 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 @dataclass
class RPCAPICommand: class RPCAPICommand:
@@ -80,54 +85,51 @@ class GraphQLCommand(WebAPICommand):
@dataclass @dataclass
class DataFunction: class DataFunction:
cmd: str cmd: str
kwargs: list[ kwargs: List[
Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand] Union[RPCAPICommand, WebAPICommand, GRPCCommand, GraphQLCommand]
] = field(default_factory=list) ] = field(default_factory=list)
DataLocations = make_dataclass( DataLocations = make_dataclass(
"DataLocations", "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): class MinerProtocol(Protocol):
def __init__(self, ip: str, *args, **kwargs) -> None: _api_cls: Type = None
# interfaces _web_cls: Type = None
self.api = None _ssh_cls: Type = None
self.web = None
self.ssh_pwd = "root" ip: str = None
api: _api_cls = None
web: _web_cls = None
ssh: _ssh_cls = None
# static data make: str = None
self.ip = ip raw_model: str = None
self.api_type = None firmware: str = None
# type
self.make = None
self.raw_model = None
self.fw_str = None
# physical attributes
self.expected_hashboards = 3
self.expected_chips = 0
self.expected_fans = 2
# data gathering locations
self.data_locations: DataLocations = None
# autotuning/shutdown support
self.supports_autotuning = False
self.supports_shutdown = False
# data storage expected_hashboards: int = 3
self.api_ver = None expected_chips: int = None
self.fw_ver = None expected_fans: int = 2
self.light = None
self.config = None
def __new__(cls, *args, **kwargs): data_locations: DataLocations = None
if cls is BaseMiner:
raise TypeError(f"Only children of '{cls.__name__}' may be instantiated") supports_shutdown: bool = False
return object.__new__(cls) supports_autotuning: bool = False
api_ver: str = None
fw_ver: str = None
light: bool = None
config: MinerConfig = None
def __repr__(self): def __repr__(self):
return f"{self.model}: {str(self.ip)}" 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) return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
@property @property
def model(self): def model(self) -> str:
model_data = [self.raw_model if self.raw_model is not None else "Unknown"] model_data = [self.raw_model if self.raw_model is not None else "Unknown"]
if self.fw_str is not None: if self.firmware is not None:
model_data.append(f"({self.fw_str})") model_data.append(f"({self.firmware})")
return " ".join(model_data) return " ".join(model_data)
@property
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: async def check_light(self) -> bool:
return await self.get_fault_light() return await self.get_fault_light()
@abstractmethod
async def fault_light_on(self) -> bool: async def fault_light_on(self) -> bool:
"""Turn the fault light of the miner on and return success as a boolean. """Turn the fault light of the miner on and return success as a boolean.
Returns: Returns:
A boolean value of the success of turning the light on. A boolean value of the success of turning the light on.
""" """
pass return False
@abstractmethod
async def fault_light_off(self) -> bool: async def fault_light_off(self) -> bool:
"""Turn the fault light of the miner off and return success as a boolean. """Turn the fault light of the miner off and return success as a boolean.
Returns: Returns:
A boolean value of the success of turning the light off. A boolean value of the success of turning the light off.
""" """
pass return False
@abstractmethod
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
# Not a data gathering function, since this is used for configuration # Not a data gathering function, since this is used for configuration
"""Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig]. """Get the mining configuration of the miner and return it as a [`MinerConfig`][pyasic.config.MinerConfig].
@@ -253,27 +176,24 @@ class BaseMiner(ABC):
Returns: Returns:
A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration. A [`MinerConfig`][pyasic.config.MinerConfig] containing the pool information and mining configuration.
""" """
pass return MinerConfig()
@abstractmethod
async def reboot(self) -> bool: async def reboot(self) -> bool:
"""Reboot the miner and return success as a boolean. """Reboot the miner and return success as a boolean.
Returns: Returns:
A boolean value of the success of rebooting the miner. A boolean value of the success of rebooting the miner.
""" """
pass return False
@abstractmethod
async def restart_backend(self) -> bool: async def restart_backend(self) -> bool:
"""Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean. """Restart the mining process of the miner (bosminer, bmminer, cgminer, etc) and return success as a boolean.
Returns: Returns:
A boolean value of the success of restarting the mining process. A boolean value of the success of restarting the mining process.
""" """
pass return False
@abstractmethod
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
"""Set the mining configuration of the miner. """Set the mining configuration of the miner.
@@ -283,25 +203,22 @@ class BaseMiner(ABC):
""" """
return None return None
@abstractmethod
async def stop_mining(self) -> bool: async def stop_mining(self) -> bool:
"""Stop the mining process of the miner. """Stop the mining process of the miner.
Returns: Returns:
A boolean value of the success of stopping the mining process. A boolean value of the success of stopping the mining process.
""" """
pass return False
@abstractmethod
async def resume_mining(self) -> bool: async def resume_mining(self) -> bool:
"""Resume the mining process of the miner. """Resume the mining process of the miner.
Returns: Returns:
A boolean value of the success of resuming the mining process. A boolean value of the success of resuming the mining process.
""" """
pass return False
@abstractmethod
async def set_power_limit(self, wattage: int) -> bool: async def set_power_limit(self, wattage: int) -> bool:
"""Set the power limit to be used by the miner. """Set the power limit to be used by the miner.
@@ -311,7 +228,7 @@ class BaseMiner(ABC):
Returns: Returns:
A boolean value of the success of setting the power limit. A boolean value of the success of setting the power limit.
""" """
pass return False
################################################## ##################################################
### DATA GATHERING FUNCTIONS (get_{some_data}) ### ### DATA GATHERING FUNCTIONS (get_{some_data}) ###
@@ -463,68 +380,52 @@ class BaseMiner(ABC):
""" """
return await self._get_uptime() return await self._get_uptime()
@abstractmethod async def _get_mac(self) -> Optional[str]:
async def _get_mac(self, *args, **kwargs) -> Optional[str]:
pass pass
@abstractmethod async def _get_api_ver(self) -> Optional[str]:
async def _get_api_ver(self, *args, **kwargs) -> Optional[str]:
pass pass
@abstractmethod async def _get_fw_ver(self) -> Optional[str]:
async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]:
pass pass
@abstractmethod async def _get_hostname(self) -> Optional[str]:
async def _get_hostname(self, *args, **kwargs) -> Optional[str]:
pass pass
@abstractmethod async def _get_hashrate(self) -> Optional[float]:
async def _get_hashrate(self, *args, **kwargs) -> Optional[float]:
pass pass
@abstractmethod async def _get_hashboards(self) -> List[HashBoard]:
async def _get_hashboards(self, *args, **kwargs) -> List[HashBoard]: return []
async def _get_env_temp(self) -> Optional[float]:
pass pass
@abstractmethod async def _get_wattage(self) -> Optional[int]:
async def _get_env_temp(self, *args, **kwargs) -> Optional[float]:
pass pass
@abstractmethod async def _get_wattage_limit(self) -> Optional[int]:
async def _get_wattage(self, *args, **kwargs) -> Optional[int]:
pass pass
@abstractmethod async def _get_fans(self) -> List[Fan]:
async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]: return []
async def _get_fan_psu(self) -> Optional[int]:
pass pass
@abstractmethod async def _get_errors(self) -> List[MinerErrorData]:
async def _get_fans(self, *args, **kwargs) -> List[Fan]: return []
async def _get_fault_light(self) -> Optional[bool]:
pass pass
@abstractmethod async def _get_expected_hashrate(self) -> Optional[float]:
async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]:
pass pass
@abstractmethod async def _is_mining(self) -> Optional[bool]:
async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]:
pass pass
@abstractmethod async def _get_uptime(self) -> Optional[int]:
async def _get_fault_light(self, *args, **kwargs) -> bool:
pass
@abstractmethod
async def _get_expected_hashrate(self, *args, **kwargs) -> Optional[float]:
pass
@abstractmethod
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
pass
@abstractmethod
async def _get_uptime(self, *args, **kwargs) -> Optional[int]:
pass pass
async def _get_data( async def _get_data(
@@ -633,7 +534,9 @@ class BaseMiner(ABC):
ip=str(self.ip), ip=str(self.ip),
make=self.make, make=self.make,
model=self.model, model=self.model,
expected_chips=self.expected_chips * self.expected_hashboards, expected_chips=self.expected_chips * self.expected_hashboards
if self.expected_chips is not None
else 0,
expected_hashboards=self.expected_hashboards, expected_hashboards=self.expected_hashboards,
hashboards=[ hashboards=[
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
@@ -651,4 +554,23 @@ class BaseMiner(ABC):
return data return data
class BaseMiner(MinerProtocol):
def __init__(self, ip: str) -> None:
self.ip = ip
if self.expected_chips is None and self.raw_model is not None:
warnings.warn(
f"Unknown chip count for miner type {self.raw_model}, "
f"please open an issue on GitHub (https://github.com/UpstreamData/pyasic)."
)
# interfaces
if self._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) AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import CK5 from pyasic.miners.types import CK5
class BFGMinerCK5(BFGMinerGoldshell, CK5): class GoldshellCK5(GoldshellMiner, CK5):
pass pass

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import HS5 from pyasic.miners.types import HS5
class BFGMinerHS5(BFGMinerGoldshell, HS5): class GoldshellHS5(GoldshellMiner, HS5):
pass pass

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KD5 from pyasic.miners.types import KD5
class BFGMinerKD5(BFGMinerGoldshell, KD5): class GoldshellKD5(GoldshellMiner, KD5):
pass pass

View File

@@ -13,6 +13,6 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .CK5 import BFGMinerCK5 from .CK5 import GoldshellCK5
from .HS5 import BFGMinerHS5 from .HS5 import GoldshellHS5
from .KD5 import BFGMinerKD5 from .KD5 import GoldshellKD5

View File

@@ -13,9 +13,9 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import BFGMinerGoldshell from pyasic.miners.backends import GoldshellMiner
from pyasic.miners.types import KDMax from pyasic.miners.types import KDMax
class BFGMinerKDMax(BFGMinerGoldshell, KDMax): class GoldshellKDMax(GoldshellMiner, KDMax):
pass pass

View File

@@ -13,4 +13,4 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from .KDMax import BFGMinerKDMax from .KDMax import GoldshellKDMax

View File

@@ -17,31 +17,21 @@
from pyasic.miners.base import BaseMiner from pyasic.miners.base import BaseMiner
class WhatsMiner(BaseMiner): # noqa - ignore ABC method implementation class WhatsMinerMake(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): make = "WhatsMiner"
super().__init__(ip, api_ver)
self.make = "WhatsMiner"
class AntMiner(BaseMiner): # noqa - ignore ABC method implementation class AntMinerMake(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): make = "AntMiner"
super().__init__(ip, api_ver)
self.make = "AntMiner"
class AvalonMiner(BaseMiner): # noqa - ignore ABC method implementation class AvalonMinerMake(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): make = "AvalonMiner"
super().__init__(ip, api_ver)
self.make = "AvalonMiner"
class InnosiliconMiner(BaseMiner): # noqa - ignore ABC method implementation class InnosiliconMake(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): make = "Innosilicon"
super().__init__(ip, api_ver)
self.make = "Innosilicon"
class GoldshellMiner(BaseMiner): # noqa - ignore ABC method implementation class GoldshellMake(BaseMiner):
def __init__(self, ip: str, api_ver: str = "0.0.0"): make = "Goldshell"
super().__init__(ip, api_ver)
self.make = "Goldshell"

View File

@@ -28,17 +28,17 @@ from pyasic.logger import logger
from pyasic.miners.antminer import * from pyasic.miners.antminer import *
from pyasic.miners.avalonminer import * from pyasic.miners.avalonminer import *
from pyasic.miners.backends import ( from pyasic.miners.backends import (
BFGMiner, AvalonMiner,
BMMiner, BMMiner,
BOSMiner, BOSMiner,
BTMiner, BTMiner,
CGMiner, GoldshellMiner,
CGMinerAvalon,
Hiveon, Hiveon,
LUXMiner, LUXMiner,
VNish, VNish,
ePIC, ePIC,
) )
from pyasic.miners.backends.innosilicon import Innosilicon
from pyasic.miners.base import AnyMiner from pyasic.miners.base import AnyMiner
from pyasic.miners.goldshell import * from pyasic.miners.goldshell import *
from pyasic.miners.innosilicon import * from pyasic.miners.innosilicon import *
@@ -310,7 +310,7 @@ MINER_CLASSES = {
"M66SVK40": BTMinerM66SVK40, "M66SVK40": BTMinerM66SVK40,
}, },
MinerTypes.AVALONMINER: { MinerTypes.AVALONMINER: {
None: CGMinerAvalon, None: AvalonMiner,
"AVALONMINER 721": CGMinerAvalon721, "AVALONMINER 721": CGMinerAvalon721,
"AVALONMINER 741": CGMinerAvalon741, "AVALONMINER 741": CGMinerAvalon741,
"AVALONMINER 761": CGMinerAvalon761, "AVALONMINER 761": CGMinerAvalon761,
@@ -325,16 +325,16 @@ MINER_CLASSES = {
"AVALONMINER 1246": CGMinerAvalon1246, "AVALONMINER 1246": CGMinerAvalon1246,
}, },
MinerTypes.INNOSILICON: { MinerTypes.INNOSILICON: {
None: CGMiner, None: Innosilicon,
"T3H+": InnosiliconT3HPlus, "T3H+": InnosiliconT3HPlus,
"A10X": InnosiliconA10X, "A10X": InnosiliconA10X,
}, },
MinerTypes.GOLDSHELL: { MinerTypes.GOLDSHELL: {
None: BFGMiner, None: GoldshellMiner,
"GOLDSHELL CK5": BFGMinerCK5, "GOLDSHELL CK5": GoldshellCK5,
"GOLDSHELL HS5": BFGMinerHS5, "GOLDSHELL HS5": GoldshellHS5,
"GOLDSHELL KD5": BFGMinerKD5, "GOLDSHELL KD5": GoldshellKD5,
"GOLDSHELL KDMAX": BFGMinerKDMax, "GOLDSHELL KDMAX": GoldshellKDMax,
}, },
MinerTypes.BRAIINS_OS: { MinerTypes.BRAIINS_OS: {
None: BOSMiner, None: BOSMiner,
@@ -654,7 +654,7 @@ class MinerFactory:
return MinerTypes.HIVEON return MinerTypes.HIVEON
if "LUXMINER" in upper_data: if "LUXMINER" in upper_data:
return MinerTypes.LUX_OS 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 return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data: if "INTCHAINS_QOMO" in upper_data:
return MinerTypes.GOLDSHELL return MinerTypes.GOLDSHELL
@@ -761,7 +761,7 @@ class MinerFactory:
str_data = ",".join(str_data.split(",")[:-1]) + "}" 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 # 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("]", "}") str_data = str_data.replace("[", "{").replace("]", "}")
return str_data return str_data

View File

@@ -55,11 +55,11 @@ class MinerListener(metaclass=Singleton):
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
transport_14235, protocol_14235 = await loop.create_datagram_endpoint( transport_14235, _ = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 14235) # noqa _MinerListener, local_addr=("0.0.0.0", 14235)
) )
transport_8888, protocol_8888 = await loop.create_datagram_endpoint( transport_8888, _ = await loop.create_datagram_endpoint(
lambda: _MinerListener(), local_addr=("0.0.0.0", 8888) # noqa _MinerListener, local_addr=("0.0.0.0", 8888)
) )
while True: while True:

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class Z15(AntMiner): # noqa - ignore ABC method implementation class Z15(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Z15"
super().__init__(ip, api_ver) expected_chips = 3
self.ip = ip expected_fans = 2
self.raw_model = "Z15"
self.expected_chips = 3
self.fan_count = 2

View File

@@ -14,39 +14,28 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class S17(AntMiner): # noqa - ignore ABC method implementation class S17(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S17"
super().__init__(ip, api_ver) expected_chips = 48
self.ip = ip expected_fans = 4
self.raw_model = "S17"
self.expected_chips = 48
self.fan_count = 4
class S17Plus(AntMiner): # noqa - ignore ABC method implementation class S17Plus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S17+"
super().__init__(ip, api_ver) expected_chips = 65
self.raw_model = "S17+" expected_fans = 4
self.expected_chips = 65
self.fan_count = 4
class S17Pro(AntMiner): # noqa - ignore ABC method implementation class S17Pro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S17 Pro"
super().__init__(ip, api_ver) expected_chips = 48
self.ip = ip expected_fans = 4
self.raw_model = "S17 Pro"
self.expected_chips = 48
self.fan_count = 4
class S17e(AntMiner): # noqa - ignore ABC method implementation class S17e(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S17e"
super().__init__(ip, api_ver) expected_chips = 135
self.ip = ip expected_fans = 4
self.raw_model = "S17e"
self.expected_chips = 135
self.fan_count = 4

View File

@@ -14,31 +14,22 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class T17(AntMiner): # noqa - ignore ABC method implementation class T17(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "T17"
super().__init__(ip, api_ver) expected_chips = 30
self.ip = ip expected_fans = 4
self.raw_model = "T17"
self.expected_chips = 30
self.fan_count = 4
class T17Plus(AntMiner): # noqa - ignore ABC method implementation class T17Plus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "T17+"
super().__init__(ip, api_ver) expected_chips = 44
self.ip = ip expected_fans = 4
self.raw_model = "T17+"
self.expected_chips = 44
self.fan_count = 4
class T17e(AntMiner): # noqa - ignore ABC method implementation class T17e(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "T17e"
super().__init__(ip, api_ver) expected_chips = 78
self.ip = ip expected_fans = 4
self.raw_model = "T17e"
self.expected_chips = 78
self.fan_count = 4

View File

@@ -14,158 +14,107 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class S19(AntMiner): # noqa - ignore ABC method implementation class S19(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19"
super().__init__(ip, api_ver) expected_chips = 76
self.ip = ip expected_fans = 4
self.raw_model = "S19"
self.expected_chips = 76
self.fan_count = 4
class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation class S19NoPIC(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19 No PIC"
super().__init__(ip, api_ver) expected_chips = 88
self.ip = ip expected_fans = 4
self.raw_model = "S19 No PIC"
self.expected_chips = 88
self.fan_count = 4
class S19Pro(AntMiner): # noqa - ignore ABC method implementation class S19Pro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19 Pro"
super().__init__(ip, api_ver) expected_chips = 114
self.ip = ip expected_fans = 4
self.raw_model = "S19 Pro"
self.expected_chips = 114
self.fan_count = 4
class S19i(AntMiner): # noqa - ignore ABC method implementation class S19i(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19i"
super().__init__(ip, api_ver) expected_chips = 80
self.ip = ip expected_fans = 4
self.raw_model = "S19i"
self.expected_chips = 80
self.fan_count = 4
class S19Plus(AntMiner): # noqa - ignore ABC method implementation class S19Plus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19+"
super().__init__(ip, api_ver) expected_chips = 80
self.ip = ip expected_fans = 4
self.raw_model = "S19+"
self.expected_chips = 80
self.fan_count = 4
class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation class S19ProPlus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19 Pro+"
super().__init__(ip, api_ver) expected_chips = 120
self.ip = ip expected_fans = 4
self.raw_model = "S19 Pro+"
self.expected_chips = 120
self.fan_count = 4
class S19XP(AntMiner): # noqa - ignore ABC method implementation class S19XP(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19 XP"
super().__init__(ip, api_ver) expected_chips = 110
self.ip = ip expected_fans = 4
self.raw_model = "S19 XP"
self.expected_chips = 110
self.fan_count = 4
class S19a(AntMiner): # noqa - ignore ABC method implementation class S19a(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19a"
super().__init__(ip, api_ver) expected_chips = 72
self.ip = ip expected_fans = 4
self.raw_model = "S19a"
self.expected_chips = 72
self.fan_count = 4
class S19aPro(AntMiner): # noqa - ignore ABC method implementation class S19aPro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19a Pro"
super().__init__(ip, api_ver) expected_chips = 100
self.ip = ip expected_fans = 4
self.raw_model = "S19a Pro"
self.expected_chips = 100
self.fan_count = 4
class S19j(AntMiner): # noqa - ignore ABC method implementation class S19j(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19j"
super().__init__(ip, api_ver) expected_chips = 114
self.ip = ip expected_fans = 4
self.raw_model = "S19j"
self.expected_chips = 114
self.fan_count = 4
class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation class S19jNoPIC(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19j No PIC"
super().__init__(ip, api_ver) expected_chips = 88
self.ip = ip expected_fans = 4
self.raw_model = "S19j No PIC"
self.expected_chips = 88
self.fan_count = 4
class S19jPro(AntMiner): # noqa - ignore ABC method implementation class S19jPro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19j Pro"
super().__init__(ip, api_ver) expected_chips = 126
self.ip = ip expected_fans = 4
self.raw_model = "S19j Pro"
self.expected_chips = 126
self.fan_count = 4
class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation class S19jProPlus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19j Pro+"
super().__init__(ip, api_ver) expected_chips = 120
self.ip = ip expected_fans = 4
self.raw_model = "S19j Pro+"
self.expected_chips = 120
self.fan_count = 4
class S19kPro(AntMiner): # noqa - ignore ABC method implementation class S19kPro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19k Pro"
super().__init__(ip, api_ver) expected_chips = 77
self.ip = ip expected_fans = 4
self.raw_model = "S19k Pro"
self.expected_chips = 77
self.fan_count = 4
class S19L(AntMiner): # noqa - ignore ABC method implementation class S19L(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19L"
super().__init__(ip, api_ver) expected_chips = 76
self.ip = ip expected_fans = 4
self.raw_model = "S19L"
self.expected_chips = 76
self.fan_count = 4
class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation class S19kProNoPIC(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19k Pro No PIC"
super().__init__(ip, api_ver) expected_chips = 77
self.ip = ip expected_fans = 4
self.raw_model = "S19k Pro No PIC"
self.expected_chips = 77
self.fan_count = 4
class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation class S19ProHydro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S19 Pro Hydro"
super().__init__(ip, api_ver) expected_chips = 180
self.ip = ip expected_hashboards = 4
self.raw_model = "S19 Pro Hydro" expected_fans = 0
self.expected_chips = 180
self.expected_hashboards = 4
self.fan_count = 0

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class T19(AntMiner): # noqa - ignore ABC method implementation class T19(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "T19"
super().__init__(ip, api_ver) expected_chips = 76
self.ip = ip expected_fans = 4
self.raw_model = "T19"
self.expected_chips = 76
self.fan_count = 4

View File

@@ -14,14 +14,12 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class D3(AntMiner): # noqa - ignore ABC method implementation class D3(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "D3"
super().__init__(ip, api_ver)
self.ip = ip expected_chips = 60
self.raw_model = "D3" expected_hashboards = 3
self.expected_chips = 60 expected_fans = 2
self.expected_hashboards = 3
self.fan_count = 2

View File

@@ -14,14 +14,12 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class HS3(AntMiner): # noqa - ignore ABC method implementation class HS3(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "HS3"
super().__init__(ip, api_ver)
self.ip = ip expected_chips = 92
self.raw_model = "HS3" expected_hashboards = 3
self.expected_chips = 92 expected_fans = 2
self.expected_hashboards = 3
self.fan_count = 2

View File

@@ -13,13 +13,10 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class L3Plus(AntMiner): # noqa - ignore ABC method implementation class L3Plus(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "L3+"
super().__init__(ip, api_ver) expected_chips = 72
self.ip = ip expected_fans = 2
self.raw_model = "L3+"
self.expected_chips = 72
self.fan_count = 2

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class DR5(AntMiner): # noqa - ignore ABC method implementation class DR5(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "DR5"
super().__init__(ip, api_ver) expected_chips = 72
self.ip = ip expected_hashboards = 3
self.raw_model = "DR5" expected_fans = 2
self.expected_chips = 72
self.expected_hashboards = 3
self.fan_count = 2

View File

@@ -13,13 +13,10 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class L7(AntMiner): # noqa - ignore ABC method implementation class L7(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "L7"
super().__init__(ip, api_ver) expected_chips = 120
self.ip = ip expected_fans = 4
self.raw_model = "L7"
self.expected_chips = 120
self.fan_count = 4

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class E9Pro(AntMiner): # noqa - ignore ABC method implementation class E9Pro(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "E9Pro"
super().__init__(ip, api_ver) expected_chips = 8
self.ip = ip expected_hashboards = 2
self.raw_model = "E9Pro" expected_fans = 4
self.expected_chips = 8
self.expected_hashboards = 2
self.fan_count = 4

View File

@@ -14,31 +14,22 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class S9(AntMiner): # noqa - ignore ABC method implementation class S9(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S9"
super().__init__(ip, api_ver) expected_chips = 63
self.ip = ip expected_fans = 2
self.raw_model = "S9"
self.expected_chips = 63
self.fan_count = 2
class S9i(AntMiner): # noqa - ignore ABC method implementation class S9i(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S9i"
super().__init__(ip, api_ver) expected_chips = 63
self.ip = ip expected_fans = 2
self.raw_model = "S9i"
self.expected_chips = 63
self.fan_count = 2
class S9j(AntMiner): # noqa - ignore ABC method implementation class S9j(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "S9j"
super().__init__(ip, api_ver) expected_chips = 63
self.ip = ip expected_fans = 2
self.raw_model = "S9j"
self.expected_chips = 63
self.fan_count = 2

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AntMiner from pyasic.miners.makes import AntMinerMake
class T9(AntMiner): # noqa - ignore ABC method implementation class T9(AntMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "T9"
super().__init__(ip, api_ver) expected_chips = 54
self.ip = ip expected_fans = 2
self.raw_model = "T9"
self.expected_chips = 54
self.fan_count = 2

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation class Avalon1026(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 1026"
super().__init__(ip, api_ver) expected_chips = 80
self.ip = ip expected_fans = 2
self.raw_model = "Avalon 1026"
self.expected_chips = 80
self.fan_count = 2

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation class Avalon1047(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 1047"
super().__init__(ip, api_ver) expected_chips = 80
self.ip = ip expected_fans = 2
self.raw_model = "Avalon 1047"
self.expected_chips = 80
self.fan_count = 2

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation class Avalon1066(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 1066"
super().__init__(ip, api_ver) expected_chips = 114
self.ip = ip expected_fans = 4
self.raw_model = "Avalon 1066"
self.expected_chips = 114
self.fan_count = 4

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon1166Pro(AvalonMiner): # noqa - ignore ABC method implementation class Avalon1166Pro(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 1166 Pro"
super().__init__(ip, api_ver) expected_chips = 120
self.ip = ip expected_fans = 4
self.raw_model = "Avalon 1166 Pro"
self.expected_chips = 120
self.fan_count = 4

View File

@@ -14,13 +14,10 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon1246(AvalonMiner): # noqa - ignore ABC method implementation class Avalon1246(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 1246"
super().__init__(ip, api_ver) expected_chips = 120
self.ip = ip expected_fans = 4
self.raw_model = "Avalon 1246"
self.expected_chips = 120
self.fan_count = 4

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation class Avalon721(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 721"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 18
self.raw_model = "Avalon 721" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 18
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation class Avalon741(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 741"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 22
self.raw_model = "Avalon 741" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 22
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation class Avalon761(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 761"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 18
self.raw_model = "Avalon 761" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 18
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation class Avalon821(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 821"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 26
self.raw_model = "Avalon 821" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 26
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation class Avalon841(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 841"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 26
self.raw_model = "Avalon 841" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 26
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation class Avalon851(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 851"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 26
self.raw_model = "Avalon 851" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 26
self.fan_count = 1

View File

@@ -14,14 +14,11 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import AvalonMiner from pyasic.miners.makes import AvalonMinerMake
class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation class Avalon921(AvalonMinerMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "Avalon 921"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 26
self.raw_model = "Avalon 921" expected_fans = 1
self.expected_hashboards = 4
self.expected_chips = 26
self.fan_count = 1

View File

@@ -13,14 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import GoldshellMiner from pyasic.miners.makes import GoldshellMake
class CK5(GoldshellMiner): # noqa - ignore ABC method implementation class CK5(GoldshellMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "CK5"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 46
self.raw_model = "CK5" expected_fans = 4
self.expected_hashboards = 4
self.expected_chips = 46
self.fan_count = 4

View File

@@ -13,14 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import GoldshellMiner from pyasic.miners.makes import GoldshellMake
class HS5(GoldshellMiner): # noqa - ignore ABC method implementation class HS5(GoldshellMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "HS5"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 46
self.raw_model = "HS5" expected_fans = 4
self.expected_hashboards = 4
self.expected_chips = 46
self.fan_count = 4

View File

@@ -13,14 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import GoldshellMiner from pyasic.miners.makes import GoldshellMake
class KD5(GoldshellMiner): # noqa - ignore ABC method implementation class KD5(GoldshellMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "KD5"
super().__init__(ip, api_ver) expected_hashboards = 4
self.ip = ip expected_chips = 46
self.raw_model = "KD5" expected_fans = 4
self.expected_hashboards = 4
self.expected_chips = 46
self.fan_count = 4

View File

@@ -13,14 +13,11 @@
# See the License for the specific language governing permissions and - # See the License for the specific language governing permissions and -
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.makes import GoldshellMiner from pyasic.miners.makes import GoldshellMake
class KDMax(GoldshellMiner): # noqa - ignore ABC method implementation class KDMax(GoldshellMake):
def __init__(self, ip: str, api_ver: str = "0.0.0"): raw_model = "KD Max"
super().__init__(ip, api_ver) expected_hashboards = 3
self.ip = ip expected_chips = 84
self.raw_model = "KD Max" expected_fans = 4
self.expected_hashboards = 3
self.expected_chips = 84
self.fan_count = 4

Some files were not shown because too many files have changed in this diff Show More