Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aaf0d7fa75 | ||
|
|
a8cbb6394e | ||
|
|
ca6980b1ad | ||
|
|
c6c87a864d | ||
|
|
ed17a0f436 | ||
|
|
36fead3dd1 | ||
|
|
ecb16c10ca | ||
|
|
a540db3246 | ||
|
|
81a2f99fbf | ||
|
|
1dd9f742ad | ||
|
|
7bd6a0f136 | ||
|
|
7297f12e88 | ||
|
|
0e009c3a16 | ||
|
|
95b0cc364b | ||
|
|
2dcc4f0cfc | ||
|
|
d7e9498018 | ||
|
|
0324a21e79 | ||
|
|
5700bd1c9c | ||
|
|
abc6494f18 | ||
|
|
5de8fc064e | ||
|
|
c9d620105b | ||
|
|
5d6fc5b26d | ||
|
|
6bd319355d | ||
|
|
31827e7dd1 | ||
|
|
26961a5d8c | ||
|
|
2ff09a3765 | ||
|
|
18c26adbb6 | ||
|
|
4bfafabe9d | ||
|
|
19e6ed90ec | ||
|
|
eca60d1eae | ||
|
|
8b8a592308 | ||
|
|
c3de4188d6 | ||
|
|
490138fd1a | ||
|
|
f566b7fcb9 | ||
|
|
7fb4237e51 | ||
|
|
eeffdecde1 | ||
|
|
477a411c87 | ||
|
|
ca77573624 | ||
|
|
3ec147990b | ||
|
|
082240bdb6 | ||
|
|
7a7fc2c5a6 | ||
|
|
dcc3e07998 | ||
|
|
5261b00aad | ||
|
|
f18d37a19e | ||
|
|
7c3af3da41 | ||
|
|
8948af55f2 | ||
|
|
dd8fe41ad1 | ||
|
|
198eedcd43 | ||
|
|
f7309decdb | ||
|
|
078579d8e1 | ||
|
|
39eeb13409 | ||
|
|
dfccd67ccb | ||
|
|
10949225c0 | ||
|
|
3a60a3584a | ||
|
|
480aab550c | ||
|
|
fa83e61249 | ||
|
|
2f3411e12d | ||
|
|
3e7311687e | ||
|
|
bc2d549ce5 | ||
|
|
3d31d89c9e | ||
|
|
15fc27e6fa | ||
|
|
943ebc77a1 | ||
|
|
733437ef03 | ||
|
|
b444245e98 | ||
|
|
481d31a0f1 | ||
|
|
264db3bdd6 | ||
|
|
d292b9c195 | ||
|
|
dce25a679f | ||
|
|
c903631742 | ||
|
|
e70bfdc886 | ||
|
|
8e1803add1 | ||
|
|
7d61056ea3 | ||
|
|
0d497baa45 | ||
|
|
d3a71c5a93 | ||
|
|
895a5b7ac8 | ||
|
|
7a5a0b287c | ||
|
|
c7d73276c8 | ||
|
|
4bbb9d0b08 | ||
|
|
3ee49e6fd7 | ||
|
|
dcd3e99d73 | ||
|
|
64018cdad8 | ||
|
|
e7d269008c | ||
|
|
7dfe25e5d2 | ||
|
|
382f9cff76 | ||
|
|
a5195ff1db | ||
|
|
b1ec726d18 | ||
|
|
5ae2cb2b22 | ||
|
|
472a15f4ca | ||
|
|
7cc7973587 | ||
|
|
ab964e4c88 | ||
|
|
4087874b4a | ||
|
|
844deec0d3 | ||
|
|
d36eef4c33 | ||
|
|
69d4ee5570 | ||
|
|
e6d3ec01fe | ||
|
|
e7b01ccdab | ||
|
|
38506903ea |
@@ -1,8 +0,0 @@
|
||||
# Ignore VENV
|
||||
venv
|
||||
|
||||
# Ignore builds
|
||||
build
|
||||
|
||||
# Ignore github files
|
||||
.github
|
||||
22
.github/workflows/python-publish.yml
vendored
Normal file
22
.github/workflows/python-publish.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
name: PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
- 'docs/**'
|
||||
- 'docsrc/**'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Publish GH release
|
||||
uses: softprops/action-gh-release@v0.1.14
|
||||
- name: Build using poetry and publish to PyPi
|
||||
uses: JRubics/poetry-publish@v1.11
|
||||
with:
|
||||
pypi_token: ${{ secrets.PYPI_API_KEY }}
|
||||
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
venv/
|
||||
build/
|
||||
dist/
|
||||
__pycache__/
|
||||
pyvenv.cfg
|
||||
.env/
|
||||
bin/
|
||||
lib/
|
||||
20
.readthedocs.yaml
Normal file
20
.readthedocs.yaml
Normal file
@@ -0,0 +1,20 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-20.04
|
||||
tools:
|
||||
python: "3.9"
|
||||
|
||||
mkdocs:
|
||||
configuration: mkdocs.yml
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/requirements.txt
|
||||
493
API/bmminer.py
493
API/bmminer.py
@@ -1,493 +0,0 @@
|
||||
from API import 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.
|
||||
|
||||
:param ip: The IP of the miner to reference the API on.
|
||||
:param port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip: str, port: int = 4028) -> None:
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
:return: Miner version information.
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
|
||||
:return: 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
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
|
||||
:return: The status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
:return: Miner pool information.
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
|
||||
:return: Data on each PGA/ASC with their 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.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago
|
||||
|
||||
:return: Data on each PGA/ASC with their 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.
|
||||
|
||||
:param n: The PGA number to get data from.
|
||||
|
||||
:return: Data on the PGA n.
|
||||
"""
|
||||
return await self.send_command("pga", parameters=n)
|
||||
|
||||
async def pgacount(self) -> dict:
|
||||
"""Get data fon all PGAs.
|
||||
|
||||
:return: Data on the PGAs connected.
|
||||
"""
|
||||
return await self.send_command("pgacount")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
"""Switch pools to pool n.
|
||||
|
||||
:param n: The pool to switch to.
|
||||
|
||||
:return: A confirmation of switching to pool n.
|
||||
"""
|
||||
return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
"""Enable pool n.
|
||||
|
||||
:param n: The pool to enable.
|
||||
|
||||
:return: A confirmation of enabling pool n.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param url: The URL of the new pool to add.
|
||||
:param username: The users username on the new pool.
|
||||
:param password: The worker password on the new pool.
|
||||
|
||||
:return: A confirmation of adding the pool.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url}, " f"{username}, " f"{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
"""Set pool priority.
|
||||
|
||||
:param n: Pools in order of priority.
|
||||
|
||||
:return: A confirmation of setting pool priority.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param n: Pool number to set quota on.
|
||||
:param q: Quota to set the pool to.
|
||||
|
||||
:return: A confirmation of setting pool quota.
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
|
||||
:param n: Pool to disable.
|
||||
|
||||
:return: A confirmation of diabling the pool.
|
||||
"""
|
||||
return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
"""Remove a pool.
|
||||
|
||||
:param n: Pool to remove.
|
||||
|
||||
:return: A confirmation of removing the pool.
|
||||
"""
|
||||
return await self.send_command("removepool", parameters=n)
|
||||
|
||||
async def save(self, filename: str = None) -> dict:
|
||||
"""Save the config.
|
||||
|
||||
:param filename: Filename to save the config as.
|
||||
|
||||
:return: A confirmation of saving the config.
|
||||
"""
|
||||
if filename:
|
||||
return await self.send_command("save", parameters=filename)
|
||||
else:
|
||||
return await self.send_command("save")
|
||||
|
||||
async def quit(self) -> dict:
|
||||
"""Quit BMMiner.
|
||||
|
||||
:return: A single "BYE" before BMMiner quits.
|
||||
"""
|
||||
return await self.send_command("quit")
|
||||
|
||||
async def notify(self) -> dict:
|
||||
"""Notify the user of past errors.
|
||||
|
||||
:return: The last status and count of each devices problem(s).
|
||||
"""
|
||||
return await self.send_command("notify")
|
||||
|
||||
async def privileged(self) -> dict:
|
||||
"""Check if you have privileged access.
|
||||
|
||||
:return: The STATUS section with an error if you have no
|
||||
privileged access, or success if you have privileged access.
|
||||
"""
|
||||
return await self.send_command("privileged")
|
||||
|
||||
async def pgaenable(self, n: int) -> dict:
|
||||
"""Enable PGA n.
|
||||
|
||||
:param n: The PGA to enable.
|
||||
|
||||
:return: A confirmation of enabling PGA n.
|
||||
"""
|
||||
return await self.send_command("pgaenable", parameters=n)
|
||||
|
||||
async def pgadisable(self, n: int) -> dict:
|
||||
"""Disable PGA n.
|
||||
|
||||
:param n: The PGA to disable.
|
||||
|
||||
:return: A confirmation of disabling PGA n.
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgaidentify(self, n: int) -> dict:
|
||||
"""Identify PGA n.
|
||||
|
||||
:param n: The PGA to identify.
|
||||
|
||||
:return: A confirmation of identifying PGA n.
|
||||
"""
|
||||
return await self.send_command("pgaidentify", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
|
||||
:return: Data on all devices with their static details.
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart BMMiner using the API.
|
||||
|
||||
:return: A reply informing of the restart.
|
||||
"""
|
||||
return await self.send_command("restart")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork,
|
||||
ignoring zombie devices.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param command: The command to check.
|
||||
|
||||
:return: Information about a command:
|
||||
Exists (Y/N) <- the command exists in this version
|
||||
Access (Y/N) <- you have access to use the command
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def failover_only(self, failover: bool) -> dict:
|
||||
"""Set failover-only.
|
||||
|
||||
|
||||
:param failover: What to set failover-only to.
|
||||
|
||||
:return: Confirmation of setting failover-only.
|
||||
"""
|
||||
return await self.send_command("failover-only", parameters=failover)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
|
||||
:return: 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
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def debug(self, setting: str) -> dict:
|
||||
"""Set a debug setting.
|
||||
|
||||
:param setting: Which setting to switch to. Options are:
|
||||
Silent,
|
||||
Quiet,
|
||||
Verbose,
|
||||
Debug,
|
||||
RPCProto,
|
||||
PerDevice,
|
||||
WorkTime,
|
||||
Normal.
|
||||
|
||||
:return: Data on which debug setting was enabled or disabled.
|
||||
"""
|
||||
return await self.send_command("debug", parameters=setting)
|
||||
|
||||
async def setconfig(self, name: str, n: int) -> dict:
|
||||
"""Set config of name to value n.
|
||||
|
||||
:param name: The name of the config setting to set. Options are:
|
||||
queue,
|
||||
scantime,
|
||||
expiry.
|
||||
:param n: The value to set the 'name' setting to.
|
||||
|
||||
:return: The results of setting config of name to n.
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}")
|
||||
|
||||
async def usbstats(self) -> dict:
|
||||
"""Get stats of all USB devices except ztex.
|
||||
|
||||
:return: The stats of all USB devices except ztex.
|
||||
"""
|
||||
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.
|
||||
|
||||
Options:
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 160 - 230 (multiple of 2)
|
||||
CMR -
|
||||
opt: clock
|
||||
val: 100 - 220
|
||||
|
||||
:param n: The PGA to set the options on.
|
||||
:param opt: The option to set. Setting this to 'help'
|
||||
returns a help message.
|
||||
:param val: The value to set the option to.
|
||||
|
||||
:return: Confirmation of setting PGA n with opt[,val].
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
|
||||
:param 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.
|
||||
:param summary: Whether or not to show a full summary.
|
||||
|
||||
|
||||
:return: the STATUS section with info on the zero and optional
|
||||
summary.
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which}, {summary}")
|
||||
|
||||
async def hotplug(self, n: int) -> dict:
|
||||
"""Enable hotplug.
|
||||
|
||||
:param n: The device number to set hotplug on.
|
||||
|
||||
:return: Information on hotplug status.
|
||||
"""
|
||||
return await self.send_command("hotplug", parameters=n)
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
"""Get data for ASC device n.
|
||||
|
||||
:param n: The device to get data for.
|
||||
|
||||
:return: The data for ASC device n.
|
||||
"""
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def ascenable(self, n: int) -> dict:
|
||||
"""Enable ASC device n.
|
||||
|
||||
:param n: The device to enable.
|
||||
|
||||
:return: Confirmation of enabling ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascenable", parameters=n)
|
||||
|
||||
async def ascdisable(self, n: int) -> dict:
|
||||
"""Disable ASC device n.
|
||||
|
||||
:param n: The device to disable.
|
||||
|
||||
:return: Confirmation of disabling ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascdisable", parameters=n)
|
||||
|
||||
async def ascidentify(self, n: int) -> dict:
|
||||
"""Identify ASC device n.
|
||||
|
||||
:param n: The device to identify.
|
||||
|
||||
:return: Confirmation of identifying ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascidentify", parameters=n)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
|
||||
:return: Data on all ASC devices.
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
:param n: The ASC to set the options on.
|
||||
:param opt: The option to set. Setting this to 'help' returns a
|
||||
help message.
|
||||
:param val: The value to set the option to.
|
||||
|
||||
:return: Confirmation of setting option opt to value val.
|
||||
"""
|
||||
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.
|
||||
|
||||
:return: An all-in-one status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def lockstats(self) -> dict:
|
||||
"""Write lockstats to STDERR.
|
||||
|
||||
:return: The result of writing the lock stats to STDERR.
|
||||
"""
|
||||
return await self.send_command("lockstats")
|
||||
208
API/bosminer.py
208
API/bosminer.py
@@ -1,208 +0,0 @@
|
||||
from API import BaseMinerAPI
|
||||
|
||||
|
||||
class BOSMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the BOSMiner API.
|
||||
|
||||
Each method corresponds to an API command in BOSMiner.
|
||||
|
||||
BOSMiner API documentation:
|
||||
https://docs.braiins.com/os/plus-en/Development/1_api.html
|
||||
|
||||
This class abstracts use of the BOSMiner 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.
|
||||
|
||||
:param ip: The IP of the miner to reference the API on.
|
||||
:param port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip, port=4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
|
||||
:return: Data on all ASC devices.
|
||||
"""
|
||||
return await self.send_command("asccount")
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
"""Get data for ASC device n.
|
||||
|
||||
:param n: The device to get data for.
|
||||
|
||||
:return: The data for ASC device n.
|
||||
"""
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
|
||||
:return: Data on all devices with their static details.
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
|
||||
:return: Data on each PGA/ASC with their 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.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago
|
||||
|
||||
:return: Data on each PGA/ASC with their details.
|
||||
"""
|
||||
if old:
|
||||
return await self.send_command("edevs", parameters="old")
|
||||
else:
|
||||
return await self.send_command("edevs")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
:return: Miner pool information.
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
|
||||
:return: The status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork.
|
||||
"""
|
||||
return await self.send_command("stats")
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
:return: Miner version information.
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def estats(self, old: bool = False) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork,
|
||||
ignoring zombie devices.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork,
|
||||
ignoring zombie devices.
|
||||
"""
|
||||
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 BOSMiner.
|
||||
|
||||
:param command: The command to check.
|
||||
|
||||
:return: Information about a command:
|
||||
Exists (Y/N) <- the command exists in this version
|
||||
Access (Y/N) <- you have access to use the command
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
|
||||
:return: 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
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner.
|
||||
|
||||
:return: An all-in-one status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("enablepool", parameters=n)
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}")
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("removepool", parameters=n)
|
||||
|
||||
async def fans(self) -> dict:
|
||||
"""Get fan data.
|
||||
|
||||
:return: Data on the fans of the miner.
|
||||
"""
|
||||
return await self.send_command("fans")
|
||||
|
||||
async def tempctrl(self) -> dict:
|
||||
"""Get temperature control data.
|
||||
|
||||
:return: Data about the temp control settings of the miner.
|
||||
"""
|
||||
return await self.send_command("tempctrl")
|
||||
|
||||
async def temps(self) -> dict:
|
||||
"""Get temperature data.
|
||||
|
||||
:return: Data on the temps of the miner.
|
||||
"""
|
||||
return await self.send_command("temps")
|
||||
|
||||
async def tunerstatus(self) -> dict:
|
||||
"""Get tuner status data
|
||||
|
||||
:return: Data on the status of autotuning.
|
||||
"""
|
||||
return await self.send_command("tunerstatus")
|
||||
|
||||
async def pause(self) -> dict:
|
||||
"""Pause mining.
|
||||
|
||||
:return: Confirmation of pausing mining.
|
||||
"""
|
||||
return await self.send_command("pause")
|
||||
|
||||
async def resume(self) -> dict:
|
||||
"""Resume mining.
|
||||
|
||||
:return: Confirmation of resuming mining.
|
||||
"""
|
||||
return await self.send_command("resume")
|
||||
491
API/cgminer.py
491
API/cgminer.py
@@ -1,491 +0,0 @@
|
||||
from API import BaseMinerAPI
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the CGMiner API.
|
||||
|
||||
Each method corresponds to an API command in GGMiner.
|
||||
|
||||
CGMiner API documentation:
|
||||
https://github.com/ckolivas/cgminer/blob/master/API-README
|
||||
|
||||
This class abstracts use of the CGMiner 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.
|
||||
|
||||
:param ip: The IP of the miner to reference the API on.
|
||||
:param port: The port to reference the API on. Default is 4028.
|
||||
"""
|
||||
|
||||
def __init__(self, ip, port=4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
async def version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
:return: Miner version information.
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
async def config(self) -> dict:
|
||||
"""Get some basic configuration info.
|
||||
|
||||
:return: 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
|
||||
"""
|
||||
return await self.send_command("config")
|
||||
|
||||
async def summary(self) -> dict:
|
||||
"""Get the status summary of the miner.
|
||||
|
||||
:return: The status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
:return: Miner pool information.
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
|
||||
:return: Data on each PGA/ASC with their 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.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago
|
||||
|
||||
:return: Data on each PGA/ASC with their 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.
|
||||
|
||||
:param n: The PGA number to get data from.
|
||||
|
||||
:return: Data on the PGA n.
|
||||
"""
|
||||
return await self.send_command("pga", parameters=n)
|
||||
|
||||
async def pgacount(self) -> dict:
|
||||
"""Get data fon all PGAs.
|
||||
|
||||
:return: Data on the PGAs connected.
|
||||
"""
|
||||
return await self.send_command("pgacount")
|
||||
|
||||
async def switchpool(self, n: int) -> dict:
|
||||
"""Switch pools to pool n.
|
||||
|
||||
:param n: The pool to switch to.
|
||||
|
||||
:return: A confirmation of switching to pool n.
|
||||
"""
|
||||
return await self.send_command("switchpool", parameters=n)
|
||||
|
||||
async def enablepool(self, n: int) -> dict:
|
||||
"""Enable pool n.
|
||||
|
||||
:param n: The pool to enable.
|
||||
|
||||
:return: A confirmation of enabling pool n.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param url: The URL of the new pool to add.
|
||||
:param username: The users username on the new pool.
|
||||
:param password: The worker password on the new pool.
|
||||
|
||||
:return: A confirmation of adding the pool.
|
||||
"""
|
||||
return await self.send_command(
|
||||
"addpool", parameters=f"{url}, " f"{username}, " f"{password}"
|
||||
)
|
||||
|
||||
async def poolpriority(self, *n: int) -> dict:
|
||||
"""Set pool priority.
|
||||
|
||||
:param n: Pools in order of priority.
|
||||
|
||||
:return: A confirmation of setting pool priority.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param n: Pool number to set quota on.
|
||||
:param q: Quota to set the pool to.
|
||||
|
||||
:return: A confirmation of setting pool quota.
|
||||
"""
|
||||
return await self.send_command("poolquota", parameters=f"{n}, " f"{q}")
|
||||
|
||||
async def disablepool(self, n: int) -> dict:
|
||||
"""Disable a pool.
|
||||
|
||||
:param n: Pool to disable.
|
||||
|
||||
:return: A confirmation of diabling the pool.
|
||||
"""
|
||||
return await self.send_command("disablepool", parameters=n)
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
"""Remove a pool.
|
||||
|
||||
:param n: Pool to remove.
|
||||
|
||||
:return: A confirmation of removing the pool.
|
||||
"""
|
||||
return await self.send_command("removepool", parameters=n)
|
||||
|
||||
async def save(self, filename: str = None) -> dict:
|
||||
"""Save the config.
|
||||
|
||||
:param filename: Filename to save the config as.
|
||||
|
||||
:return: A confirmation of saving the config.
|
||||
"""
|
||||
if filename:
|
||||
return await self.send_command("save", parameters=filename)
|
||||
else:
|
||||
return await self.send_command("save")
|
||||
|
||||
async def quit(self) -> dict:
|
||||
"""Quit CGMiner.
|
||||
|
||||
:return: A single "BYE" before CGMiner quits.
|
||||
"""
|
||||
return await self.send_command("quit")
|
||||
|
||||
async def notify(self) -> dict:
|
||||
"""Notify the user of past errors.
|
||||
|
||||
:return: The last status and count of each devices problem(s).
|
||||
"""
|
||||
return await self.send_command("notify")
|
||||
|
||||
async def privileged(self) -> dict:
|
||||
"""Check if you have privileged access.
|
||||
|
||||
:return: The STATUS section with an error if you have no
|
||||
privileged access, or success if you have privileged access.
|
||||
"""
|
||||
return await self.send_command("privileged")
|
||||
|
||||
async def pgaenable(self, n: int) -> dict:
|
||||
"""Enable PGA n.
|
||||
|
||||
:param n: The PGA to enable.
|
||||
|
||||
:return: A confirmation of enabling PGA n.
|
||||
"""
|
||||
return await self.send_command("pgaenable", parameters=n)
|
||||
|
||||
async def pgadisable(self, n: int) -> dict:
|
||||
"""Disable PGA n.
|
||||
|
||||
:param n: The PGA to disable.
|
||||
|
||||
:return: A confirmation of disabling PGA n.
|
||||
"""
|
||||
return await self.send_command("pgadisable", parameters=n)
|
||||
|
||||
async def pgaidentify(self, n: int) -> dict:
|
||||
"""Identify PGA n.
|
||||
|
||||
:param n: The PGA to identify.
|
||||
|
||||
:return: A confirmation of identifying PGA n.
|
||||
"""
|
||||
return await self.send_command("pgaidentify", parameters=n)
|
||||
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
|
||||
:return: Data on all devices with their static details.
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def restart(self) -> dict:
|
||||
"""Restart CGMiner using the API.
|
||||
|
||||
:return: A reply informing of the restart.
|
||||
"""
|
||||
return await self.send_command("restart")
|
||||
|
||||
async def stats(self) -> dict:
|
||||
"""Get stats of each device/pool with more than 1 getwork.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork.
|
||||
"""
|
||||
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.
|
||||
|
||||
:param old: Include zombie devices that became zombies less
|
||||
than 'old' seconds ago.
|
||||
|
||||
:return: Stats of each device/pool with more than 1 getwork,
|
||||
ignoring zombie devices.
|
||||
"""
|
||||
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 CGMiner.
|
||||
|
||||
:param command: The command to check.
|
||||
|
||||
:return: Information about a command:
|
||||
Exists (Y/N) <- the command exists in this version
|
||||
Access (Y/N) <- you have access to use the command
|
||||
"""
|
||||
return await self.send_command("check", parameters=command)
|
||||
|
||||
async def failover_only(self, failover: bool) -> dict:
|
||||
"""Set failover-only.
|
||||
|
||||
|
||||
:param failover: What to set failover-only to.
|
||||
|
||||
:return: Confirmation of setting failover-only.
|
||||
"""
|
||||
return await self.send_command("failover-only", parameters=failover)
|
||||
|
||||
async def coin(self) -> dict:
|
||||
"""Get information on the current coin.
|
||||
|
||||
:return: 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
|
||||
"""
|
||||
return await self.send_command("coin")
|
||||
|
||||
async def debug(self, setting: str) -> dict:
|
||||
"""Set a debug setting.
|
||||
|
||||
:param setting: Which setting to switch to. Options are:
|
||||
Silent,
|
||||
Quiet,
|
||||
Verbose,
|
||||
Debug,
|
||||
RPCProto,
|
||||
PerDevice,
|
||||
WorkTime,
|
||||
Normal.
|
||||
|
||||
:return: Data on which debug setting was enabled or disabled.
|
||||
"""
|
||||
return await self.send_command("debug", parameters=setting)
|
||||
|
||||
async def setconfig(self, name: str, n: int) -> dict:
|
||||
"""Set config of name to value n.
|
||||
|
||||
:param name: The name of the config setting to set. Options are:
|
||||
queue,
|
||||
scantime,
|
||||
expiry.
|
||||
:param n: The value to set the 'name' setting to.
|
||||
|
||||
:return: The results of setting config of name to n.
|
||||
"""
|
||||
return await self.send_command("setconfig", parameters=f"{name}, " f"{n}")
|
||||
|
||||
async def usbstats(self) -> dict:
|
||||
"""Get stats of all USB devices except ztex.
|
||||
|
||||
:return: The stats of all USB devices except ztex.
|
||||
"""
|
||||
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.
|
||||
|
||||
Options:
|
||||
MMQ -
|
||||
opt: clock
|
||||
val: 160 - 230 (multiple of 2)
|
||||
CMR -
|
||||
opt: clock
|
||||
val: 100 - 220
|
||||
|
||||
:param n: The PGA to set the options on.
|
||||
:param opt: The option to set. Setting this to 'help'
|
||||
returns a help message.
|
||||
:param val: The value to set the option to.
|
||||
|
||||
:return: Confirmation of setting PGA n with opt[,val].
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"pgaset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
else:
|
||||
return await self.send_command("pgaset", parameters=f"{n}, " f"{opt}")
|
||||
|
||||
async def zero(self, which: str, summary: bool) -> dict:
|
||||
"""Zero a device.
|
||||
|
||||
:param 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.
|
||||
:param summary: Whether or not to show a full summary.
|
||||
|
||||
|
||||
:return: the STATUS section with info on the zero and optional
|
||||
summary.
|
||||
"""
|
||||
return await self.send_command("zero", parameters=f"{which}, " f"{summary}")
|
||||
|
||||
async def hotplug(self, n: int) -> dict:
|
||||
"""Enable hotplug.
|
||||
|
||||
:param n: The device number to set hotplug on.
|
||||
|
||||
:return: Information on hotplug status.
|
||||
"""
|
||||
return await self.send_command("hotplug", parameters=n)
|
||||
|
||||
async def asc(self, n: int) -> dict:
|
||||
"""Get data for ASC device n.
|
||||
|
||||
:param n: The device to get data for.
|
||||
|
||||
:return: The data for ASC device n.
|
||||
"""
|
||||
return await self.send_command("asc", parameters=n)
|
||||
|
||||
async def ascenable(self, n: int) -> dict:
|
||||
"""Enable ASC device n.
|
||||
|
||||
:param n: The device to enable.
|
||||
|
||||
:return: Confirmation of enabling ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascenable", parameters=n)
|
||||
|
||||
async def ascdisable(self, n: int) -> dict:
|
||||
"""Disable ASC device n.
|
||||
|
||||
:param n: The device to disable.
|
||||
|
||||
:return: Confirmation of disabling ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascdisable", parameters=n)
|
||||
|
||||
async def ascidentify(self, n: int) -> dict:
|
||||
"""Identify ASC device n.
|
||||
|
||||
:param n: The device to identify.
|
||||
|
||||
:return: Confirmation of identifying ASC device n.
|
||||
"""
|
||||
return await self.send_command("ascidentify", parameters=n)
|
||||
|
||||
async def asccount(self) -> dict:
|
||||
"""Get data on the number of ASC devices and their info.
|
||||
|
||||
:return: Data on all ASC devices.
|
||||
"""
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
:param n: The ASC to set the options on.
|
||||
:param opt: The option to set. Setting this to 'help' returns a
|
||||
help message.
|
||||
:param val: The value to set the option to.
|
||||
|
||||
:return: Confirmation of setting option opt to value val.
|
||||
"""
|
||||
if val:
|
||||
return await self.send_command(
|
||||
"ascset", parameters=f"{n}, " f"{opt}, " f"{val}"
|
||||
)
|
||||
else:
|
||||
return await self.send_command("ascset", parameters=f"{n}, " f"{opt}")
|
||||
|
||||
async def lcd(self) -> dict:
|
||||
"""Get a general all-in-one status summary of the miner.
|
||||
|
||||
:return: An all-in-one status summary of the miner.
|
||||
"""
|
||||
return await self.send_command("lcd")
|
||||
|
||||
async def lockstats(self) -> dict:
|
||||
"""Write lockstats to STDERR.
|
||||
|
||||
:return: The result of writing the lock stats to STDERR.
|
||||
"""
|
||||
return await self.send_command("lockstats")
|
||||
13
Dockerfile
13
Dockerfile
@@ -1,13 +0,0 @@
|
||||
FROM python:3.10-slim-buster
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
WORKDIR /minerInterface-web_monitor
|
||||
|
||||
COPY tools/web_monitor/requirements.txt .
|
||||
|
||||
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
||||
|
||||
COPY . .
|
||||
|
||||
CMD ["uvicorn", "tools.web_monitor.app:app", "--host", "0.0.0.0", "--port", "80"]
|
||||
359
README.md
359
README.md
@@ -1,29 +1,26 @@
|
||||
# minerInterface
|
||||
# pyasic
|
||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
## Documentation
|
||||
Documentation is located on Read the Docs as [pyasic](https://pyasic.readthedocs.io/en/latest/)
|
||||
|
||||
## Usage
|
||||
|
||||
### Standard Usage
|
||||
You can install pyasic directly from pip with the command `pip install pyasic`
|
||||
|
||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of GUI applications that use this library here -> (https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing)
|
||||
|
||||
### Developers
|
||||
To use this repo, first download it, create a virtual environment, enter the virtual environment, and install relevant packages by navigating to this directory and running ```pip install -r requirements.txt``` on Windows or ```pip3 install -r requirements.txt``` on Mac or UNIX if the first command fails.
|
||||
|
||||
For those of you who aren't comfortable with code and developer tools, there are windows builds of the GUI applications here -> (https://drive.google.com/drive/folders/1DjR8UOS_g0ehfiJcgmrV0FFoqFvE9akW?usp=sharing)
|
||||
|
||||
### CFG Util
|
||||
*CFG Util is a GUI for interfacing with the miners easily, it is mostly self-explanatory.*
|
||||
|
||||
To use CFG Util you have 2 options -
|
||||
1. Run it directly with the file ```config_tool.py``` or import it with ```from cfg_util import main```, then run the ```main()``` function in an asyncio event loop like -
|
||||
|
||||
```python
|
||||
from tools.cfg_util import main
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
```
|
||||
2. Make a build of the CFG Util for your system using cx_freeze and ```make_cfg_tool_exe.py```
|
||||
(Alternatively, you can get a build made by me here -> https://drive.google.com/drive/folders/147vBXbuaX85inataXeSAiKk8IKf-7xtR)
|
||||
1. Open either Command Prompt on Windows or Terminal on Mac or UNIX.
|
||||
2. Navigate to this directory, and run ```make_cfg_tool_exe.py build``` on Windows or ```python3 make_cfg_tool_exe.py``` on Mac or UNIX.
|
||||
You can also use poetry by initializing and running ```poetry install```
|
||||
|
||||
### Interfacing with miners programmatically
|
||||
<br>
|
||||
|
||||
##### Note: If you are trying to interface with Whatsminers, there is a bug in the way they are interacted with on Windows, so to fix that you need to change the event loop policy using this code:
|
||||
```python
|
||||
@@ -43,238 +40,130 @@ To write your own custom programs with this repo, you have many options.
|
||||
|
||||
It is recommended that you explore the files in this repo to familiarize yourself with them, try starting with the miners module and going from there.
|
||||
|
||||
A basic script to find all miners on the network and get the hashrate from them looks like this -
|
||||
There are 2 main ways to get a miner and it's functions via scanning or via the MinerFactory.
|
||||
|
||||
#### Scanning for miners
|
||||
```python
|
||||
import asyncio
|
||||
from network import MinerNetwork
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
import sys
|
||||
|
||||
from pyasic.network import MinerNetwork
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
async def get_hashrate():
|
||||
# Miner Network class allows for easy scanning of a network
|
||||
# Give it any IP on a network and it will find the whole subnet
|
||||
# It can also be passed a subnet mask:
|
||||
# miner_network = MinerNetwork('192.168.1.55', mask=23)
|
||||
miner_network = MinerNetwork('192.168.1.1')
|
||||
# Miner Network scan function returns Miner classes for all miners found
|
||||
miners = await miner_network.scan_network_for_miners()
|
||||
# Each miner will return with its own set of functions, and an API class instance
|
||||
tasks = [miner.api.summary() for miner in miners]
|
||||
# define asynchronous function to scan for miners
|
||||
async def scan_and_get_data():
|
||||
# Define network range to be used for scanning
|
||||
# This can take a list of IPs, a constructor string, or an IP and subnet mask
|
||||
# The standard mask is /24, and you can pass any IP address in the subnet
|
||||
net = MinerNetwork("192.168.1.69", mask=24)
|
||||
# Scan the network for miners
|
||||
# This function returns a list of miners of the correct type as a class
|
||||
miners: list = await net.scan_network_for_miners()
|
||||
|
||||
# We can now get data from any of these miners
|
||||
# To do them all we have to create a list of tasks and gather them
|
||||
tasks = [miner.get_data() for miner in miners]
|
||||
# Gather all tasks asynchronously and run them
|
||||
data = await asyncio.gather(*tasks)
|
||||
parse_tasks = []
|
||||
for item in data:
|
||||
# safe_parse_api_data parses the data from a miner API
|
||||
# It will raise an APIError (from API import APIError) if there is a problem
|
||||
parse_tasks.append(safe_parse_api_data(item, 'SUMMARY', 0, 'MHS 5s'))
|
||||
# Gather all tasks asynchronously and run them
|
||||
data = await asyncio.gather(*parse_tasks)
|
||||
# Print a list of all the hashrates
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(get_hashrate())
|
||||
```
|
||||
<br>
|
||||
You can also create your own miner without scanning if you know the IP:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
|
||||
|
||||
async def get_miner_hashrate(ip: str):
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the API data
|
||||
summary = await miner.api.summary()
|
||||
# safe_parse_api_data parses the data from a miner API
|
||||
# It will raise an APIError (from API import APIError) if there is a problem
|
||||
data = await safe_parse_api_data(summary, 'SUMMARY', 0, 'MHS 5s')
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_hashrate(str("192.168.1.69")))
|
||||
```
|
||||
|
||||
|
||||
Now that you know that, lets move on to some common API functions that you might want to use.
|
||||
|
||||
### Common commands:
|
||||
* Get the data used by the config utility, this includes pool data, wattage use, temperature, hashrate, etc:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_pool_data(ip: str):
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the data
|
||||
data = await miner.get_data()
|
||||
|
||||
# Data is now a list of MinerData, and we can reference any part of that
|
||||
# Print out all data for now
|
||||
for item in data:
|
||||
print(item)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_and_get_data())
|
||||
```
|
||||
|
||||
</br>
|
||||
|
||||
#### Getting a miner if you know the IP
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
# define asynchronous function to get miner and data
|
||||
async def get_miner_data(miner_ip: str):
|
||||
# Use MinerFactory to get miner
|
||||
# MinerFactory is a singleton, so we can just get the instance in place
|
||||
miner = await MinerFactory().get_miner(miner_ip)
|
||||
|
||||
# Get data from the miner
|
||||
data = await miner.get_data()
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_pool_data(str("192.168.1.69")))
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miner_data("192.168.1.69"))
|
||||
```
|
||||
|
||||
### Advanced data gathering
|
||||
|
||||
If needed, this library exposes a wrapper for the miner API that can be used for advanced data gathering.
|
||||
|
||||
#### List available API commands
|
||||
```python
|
||||
import asyncio
|
||||
import sys
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
* Getting pool data:
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await MinerFactory().get_miner(miner_ip)
|
||||
|
||||
# List all available commands
|
||||
print(miner.api.get_commands())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
```
|
||||
|
||||
#### Use miner API commands to gather data
|
||||
|
||||
The miner API commands will raise an `APIError` if they fail with a bad status code, to bypass this you must send them manually by using `miner.api.send_command(command, ignore_errors=True)`
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
import sys
|
||||
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
# Fix whatsminer bug
|
||||
# if the computer is windows, set the event loop policy to a WindowsSelector policy
|
||||
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
|
||||
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||
|
||||
|
||||
async def get_miner_pool_data(ip: str):
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the API data
|
||||
pools = await miner.api.pools()
|
||||
# safe_parse_api_data parses the data from a miner API
|
||||
# It will raise an APIError (from API import APIError) if there is a problem
|
||||
data = pools["POOLS"]
|
||||
# parse further from here to get all the pool info you want.
|
||||
# each pool is on a different index eg:
|
||||
# data[0] is pool 1
|
||||
# data[1] is pool 2
|
||||
# etc
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_pool_data(str("192.168.1.69")))
|
||||
```
|
||||
|
||||
* Getting temperature data:
|
||||
|
||||
This one is a bit tougher, lots of miners do this a different way, you might need to experiment a bit to find what works for you.
|
||||
BraiinsOS uses the "temps" command, Whatsminers has it in "devs", Avalonminers put it in "stats" as well as some other miners,
|
||||
but the spot I like to try first is in "summary".
|
||||
|
||||
A pretty good example of really trying to make this robust is in ```cfg_util.func.miners``` in the ```get_formatted_data()``` function.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_temperature_data(ip: str):
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the API data
|
||||
summary = await miner.api.summary()
|
||||
|
||||
data = summary['SUMMARY'][0]["Temperature"]
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_temperature_data(str("192.168.1.69")))
|
||||
```
|
||||
|
||||
* Getting power data:
|
||||
|
||||
How about data on the power usage of the miner? This one only works for Whatsminers and BraiinsOS for now, and the Braiins one just uses the tuning setting, but its good enough for basic uses.
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
|
||||
|
||||
async def get_miner_power_data(ip: str):
|
||||
data = None
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# check if this can be sent the "tunerstatus" command, BraiinsOS only
|
||||
if "tunerstatus" in miner.api.get_commands():
|
||||
# send the command
|
||||
tunerstatus = await miner.api.tunerstatus()
|
||||
# parse the return
|
||||
data = tunerstatus['TUNERSTATUS'][0]["PowerLimit"]
|
||||
else:
|
||||
# send the command
|
||||
# whatsminers have the power info in summary
|
||||
summary = await miner.api.summary()
|
||||
# parse the return
|
||||
data = summary['SUMMARY'][0]["Power"]
|
||||
|
||||
if data:
|
||||
print(data)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_power_data(str("192.168.1.69")))
|
||||
```
|
||||
|
||||
* Multicommands:
|
||||
|
||||
Multicommands make it much easier to get many types of data all at once. The multicommand function will also remove any commands that your API can't handle automatically.
|
||||
How about we get the current pool user and hashrate in 1 command?
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import ipaddress
|
||||
from miners.miner_factory import MinerFactory
|
||||
from tools.cfg_util_old.func.parse_data import safe_parse_api_data
|
||||
|
||||
|
||||
async def get_miner_hashrate_and_pool(ip: str):
|
||||
# Instantiate a Miner Factory to generate miners from their IP
|
||||
miner_factory = MinerFactory()
|
||||
# Make the string IP into an IP address
|
||||
miner_ip = ipaddress.ip_address(ip)
|
||||
# Wait for the factory to return the miner
|
||||
miner = await miner_factory.get_miner(miner_ip)
|
||||
# Get the API data
|
||||
api_data = await miner.api.multicommand("pools", "summary")
|
||||
if "pools" in api_data.keys():
|
||||
user = api_data["pools"][0]["POOLS"][0]["User"]
|
||||
print(user)
|
||||
if "summary" in api_data.keys():
|
||||
hashrate = api_data["summary"][0]["SUMMARY"][0]["MHS av"]
|
||||
print(hashrate)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.new_event_loop().run_until_complete(
|
||||
get_miner_hashrate_and_pool(str("192.168.1.9")))
|
||||
async def get_api_commands(miner_ip: str):
|
||||
# Get the miner
|
||||
miner = await MinerFactory().get_miner(miner_ip)
|
||||
|
||||
# Run the devdetails command
|
||||
# This is equivalent to await miner.api.send_command("devdetails")
|
||||
devdetails: dict = await miner.api.devdetails()
|
||||
print(devdetails)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_api_commands("192.168.1.69"))
|
||||
```
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
"""
|
||||
SAMPLE CONFIG
|
||||
-------------------
|
||||
{
|
||||
"format": {
|
||||
"version": "1.2+", # -> (default = "1.2+", str, (bos: format.version))
|
||||
"model": "Antminer S9", # -> (default = "Antminer S9", str, (bos: format.model))
|
||||
"generator": "upstream_config_util", # -> (hidden, always = "upstream_config_util", str, (bos: format.generator))
|
||||
"timestamp": 1606842000, # -> (hidden, always = int(time.time()) (current unix time), int, (bos: format.timestamp))
|
||||
},
|
||||
"temperature": {
|
||||
"mode": "auto", # -> (default = "auto", str["auto", "manual", "disabled"], (bos: temp_control.mode))
|
||||
"target": 70.0, # -> (default = 70.0, float, (bos: temp_control.target_temp))
|
||||
"hot": 80.0, # -> (default = 80.0, float, (bos: temp_control.hot_temp))
|
||||
"danger": 90.0, # -> (default = 90.0, float, (bos: temp_control.dangerous_temp))
|
||||
},
|
||||
"fans": { # -> (optional, required if temperature["mode"] == "disabled", (bos: fan_control))
|
||||
"min_fans": 1, # -> (default = 1, int, (bos: fan_control.min_fans))
|
||||
"speed": 100, # -> (default = 100, 0 < int < 100, (bos: fan_control.speed))
|
||||
},
|
||||
"asicboost": True, # -> (default = True, bool, (bos : hash_chain_global.asic_boost))
|
||||
"pool_groups": [
|
||||
{
|
||||
"group_name": "Upstream", # -> (default = "group_{index}" (group_0), str, (bos: group.[index].name))
|
||||
"quota": 1, # -> (default = 1, int, (bos: group.[index].quota))
|
||||
"pools": [
|
||||
{
|
||||
"url": "stratum+tcp://stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://us-east.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://ca.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataInc.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
"group_name": "Upstream2", # -> (default = "group_{index}" (group_1), str, (bos: group.[index].name))
|
||||
"quota": 4, # -> (default = 1, int, (bos: group.[index].quota))
|
||||
"pools": [
|
||||
{
|
||||
"url": "stratum+tcp://stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://us-east.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://ca.stratum.slushpool.com:3333", # -> (str, (bos: group.[index].pool.[index].url))
|
||||
"username": "UpstreamDataTesting.test", # -> (str, (bos: group.[index].pool.[index].user))
|
||||
"password": "123", # -> (str, (bos: group.[index].pool.[index].password))
|
||||
},
|
||||
]
|
||||
},
|
||||
],
|
||||
"autotuning": {
|
||||
"enabled": True, # -> (default = True, bool), (bos: autotuning.enabled)
|
||||
"wattage": 900, # -> (default = 900, int, (bos: autotuning.psu_power_limit))
|
||||
},
|
||||
"power_scaling": {
|
||||
"enabled": False, # -> (default = False, bool, (bos: power_scaling.enabled))
|
||||
"power_step": 100, # -> (default = 100, int, (bos: power_scaling.power_step))
|
||||
"min_psu_power_limit": 800, # -> (default = 800, int, (bos: power_scaling.min_psu_power_limit))
|
||||
"shutdown_enabled": True, # -> (default = False, bool, (bos: power_scaling.shutdown_enabled))
|
||||
"shutdown_duration": 3.0, # -> (default = 3.0, float, (bos: power_scaling.shutdown_duration))
|
||||
}
|
||||
}
|
||||
"""
|
||||
221
config/bos.py
221
config/bos.py
@@ -1,221 +0,0 @@
|
||||
import time
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
def bos_config_convert(config: dict):
|
||||
out_config = {}
|
||||
for opt in config:
|
||||
if opt == "format":
|
||||
out_config["format"] = config[opt]
|
||||
out_config["format"]["generator"] = "upstream_config_util"
|
||||
out_config["format"]["timestamp"] = int(time.time())
|
||||
elif opt == "temp_control":
|
||||
out_config["temperature"] = {}
|
||||
if "mode" in config[opt].keys():
|
||||
out_config["temperature"]["mode"] = config[opt]["mode"]
|
||||
else:
|
||||
out_config["temperature"]["mode"] = "auto"
|
||||
|
||||
if "target_temp" in config[opt].keys():
|
||||
out_config["temperature"]["target"] = config[opt]["target_temp"]
|
||||
else:
|
||||
out_config["temperature"]["target"] = 70.0
|
||||
|
||||
if "hot_temp" in config[opt].keys():
|
||||
out_config["temperature"]["hot"] = config[opt]["hot_temp"]
|
||||
else:
|
||||
out_config["temperature"]["hot"] = 80.0
|
||||
|
||||
if "dangerous_temp" in config[opt].keys():
|
||||
out_config["temperature"]["danger"] = config[opt]["dangerous_temp"]
|
||||
else:
|
||||
out_config["temperature"]["danger"] = 90.0
|
||||
elif opt == "fan_control":
|
||||
out_config["fans"] = {}
|
||||
if "min_fans" in config[opt].keys():
|
||||
out_config["fans"]["min_fans"] = config[opt]["min_fans"]
|
||||
else:
|
||||
out_config["fans"]["min_fans"] = 1
|
||||
if "speed" in config[opt].keys():
|
||||
out_config["fans"]["speed"] = config[opt]["speed"]
|
||||
else:
|
||||
out_config["fans"]["speed"] = 100
|
||||
elif opt == "group":
|
||||
out_config["pool_groups"] = [{} for _item in range(len(config[opt]))]
|
||||
for idx in range(len(config[opt])):
|
||||
out_config["pool_groups"][idx]["pools"] = []
|
||||
out_config["pool_groups"][idx] = {}
|
||||
if "name" in config[opt][idx].keys():
|
||||
out_config["pool_groups"][idx]["group_name"] = config[opt][idx][
|
||||
"name"
|
||||
]
|
||||
else:
|
||||
out_config["pool_groups"][idx]["group_name"] = f"group_{idx}"
|
||||
if "quota" in config[opt][idx].keys():
|
||||
out_config["pool_groups"][idx]["quota"] = config[opt][idx]["quota"]
|
||||
else:
|
||||
out_config["pool_groups"][idx]["quota"] = 1
|
||||
out_config["pool_groups"][idx]["pools"] = [
|
||||
{} for _item in range(len(config[opt][idx]["pool"]))
|
||||
]
|
||||
for pool_idx in range(len(config[opt][idx]["pool"])):
|
||||
out_config["pool_groups"][idx]["pools"][pool_idx]["url"] = config[
|
||||
opt
|
||||
][idx]["pool"][pool_idx]["url"]
|
||||
out_config["pool_groups"][idx]["pools"][pool_idx][
|
||||
"username"
|
||||
] = config[opt][idx]["pool"][pool_idx]["user"]
|
||||
out_config["pool_groups"][idx]["pools"][pool_idx][
|
||||
"password"
|
||||
] = config[opt][idx]["pool"][pool_idx]["password"]
|
||||
elif opt == "autotuning":
|
||||
out_config["autotuning"] = {}
|
||||
if "enabled" in config[opt].keys():
|
||||
out_config["autotuning"]["enabled"] = config[opt]["enabled"]
|
||||
else:
|
||||
out_config["autotuning"]["enabled"] = True
|
||||
if "psu_power_limit" in config[opt].keys():
|
||||
out_config["autotuning"]["wattage"] = config[opt]["psu_power_limit"]
|
||||
else:
|
||||
out_config["autotuning"]["wattage"] = 900
|
||||
elif opt == "power_scaling":
|
||||
out_config["power_scaling"] = {}
|
||||
if "enabled" in config[opt].keys():
|
||||
out_config["power_scaling"]["enabled"] = config[opt]["enabled"]
|
||||
else:
|
||||
out_config["power_scaling"]["enabled"] = False
|
||||
if "power_step" in config[opt].keys():
|
||||
out_config["power_scaling"]["power_step"] = config[opt]["power_step"]
|
||||
else:
|
||||
out_config["power_scaling"]["power_step"] = 100
|
||||
if "min_psu_power_limit" in config[opt].keys():
|
||||
out_config["power_scaling"]["min_psu_power_limit"] = config[opt][
|
||||
"min_psu_power_limit"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["min_psu_power_limit"] = 800
|
||||
if "shutdown_enabled" in config[opt].keys():
|
||||
out_config["power_scaling"]["shutdown_enabled"] = config[opt][
|
||||
"shutdown_enabled"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["shutdown_enabled"] = False
|
||||
if "shutdown_duration" in config[opt].keys():
|
||||
out_config["power_scaling"]["shutdown_duration"] = config[opt][
|
||||
"shutdown_duration"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["shutdown_duration"] = 3.0
|
||||
return yaml.dump(out_config, sort_keys=False)
|
||||
|
||||
|
||||
def general_config_convert_bos(yaml_config, user_suffix: str = None):
|
||||
config = yaml.load(yaml_config, Loader=yaml.SafeLoader)
|
||||
out_config = {}
|
||||
for opt in config:
|
||||
if opt == "format":
|
||||
out_config["format"] = config[opt]
|
||||
out_config["format"]["generator"] = "upstream_config_util"
|
||||
out_config["format"]["timestamp"] = int(time.time())
|
||||
elif opt == "temperature":
|
||||
out_config["temp_control"] = {}
|
||||
if "mode" in config[opt].keys():
|
||||
out_config["temp_control"]["mode"] = config[opt]["mode"]
|
||||
else:
|
||||
out_config["temp_control"]["mode"] = "auto"
|
||||
|
||||
if "target" in config[opt].keys():
|
||||
out_config["temp_control"]["target_temp"] = config[opt]["target"]
|
||||
else:
|
||||
out_config["temp_control"]["target_temp"] = 70.0
|
||||
|
||||
if "hot" in config[opt].keys():
|
||||
out_config["temp_control"]["hot_temp"] = config[opt]["hot"]
|
||||
else:
|
||||
out_config["temp_control"]["hot_temp"] = 80.0
|
||||
|
||||
if "danger" in config[opt].keys():
|
||||
out_config["temp_control"]["dangerous_temp"] = config[opt]["danger"]
|
||||
else:
|
||||
out_config["temp_control"]["dangerous_temp"] = 90.0
|
||||
elif opt == "fans":
|
||||
out_config["fan_control"] = {}
|
||||
if "min_fans" in config[opt].keys():
|
||||
out_config["fan_control"]["min_fans"] = config[opt]["min_fans"]
|
||||
else:
|
||||
out_config["fan_control"]["min_fans"] = 1
|
||||
if "speed" in config[opt].keys():
|
||||
out_config["fan_control"]["speed"] = config[opt]["speed"]
|
||||
else:
|
||||
out_config["fan_control"]["speed"] = 100
|
||||
elif opt == "pool_groups":
|
||||
out_config["group"] = [{} for _item in range(len(config[opt]))]
|
||||
for idx in range(len(config[opt])):
|
||||
out_config["group"][idx]["pools"] = []
|
||||
out_config["group"][idx] = {}
|
||||
if "group_name" in config[opt][idx].keys():
|
||||
out_config["group"][idx]["name"] = config[opt][idx]["group_name"]
|
||||
else:
|
||||
out_config["group"][idx]["name"] = f"group_{idx}"
|
||||
if "quota" in config[opt][idx].keys():
|
||||
out_config["group"][idx]["quota"] = config[opt][idx]["quota"]
|
||||
else:
|
||||
out_config["group"][idx]["quota"] = 1
|
||||
out_config["group"][idx]["pool"] = [
|
||||
{} for _item in range(len(config[opt][idx]["pools"]))
|
||||
]
|
||||
for pool_idx in range(len(config[opt][idx]["pools"])):
|
||||
out_config["group"][idx]["pool"][pool_idx]["url"] = config[opt][
|
||||
idx
|
||||
]["pools"][pool_idx]["url"]
|
||||
username = config[opt][idx]["pools"][pool_idx]["username"]
|
||||
if user_suffix:
|
||||
if "." in username:
|
||||
username = f"{username}x{user_suffix}"
|
||||
else:
|
||||
username = f"{username}.{user_suffix}"
|
||||
out_config["group"][idx]["pool"][pool_idx]["user"] = username
|
||||
|
||||
out_config["group"][idx]["pool"][pool_idx]["password"] = config[
|
||||
opt
|
||||
][idx]["pools"][pool_idx]["password"]
|
||||
elif opt == "autotuning":
|
||||
out_config["autotuning"] = {}
|
||||
if "enabled" in config[opt].keys():
|
||||
out_config["autotuning"]["enabled"] = config[opt]["enabled"]
|
||||
else:
|
||||
out_config["autotuning"]["enabled"] = True
|
||||
if "wattage" in config[opt].keys():
|
||||
out_config["autotuning"]["psu_power_limit"] = config[opt]["wattage"]
|
||||
else:
|
||||
out_config["autotuning"]["psu_power_limit"] = 900
|
||||
elif opt == "power_scaling":
|
||||
out_config["power_scaling"] = {}
|
||||
if "enabled" in config[opt].keys():
|
||||
out_config["power_scaling"]["enabled"] = config[opt]["enabled"]
|
||||
else:
|
||||
out_config["power_scaling"]["enabled"] = False
|
||||
if "power_step" in config[opt].keys():
|
||||
out_config["power_scaling"]["power_step"] = config[opt]["power_step"]
|
||||
else:
|
||||
out_config["power_scaling"]["power_step"] = 100
|
||||
if "min_psu_power_limit" in config[opt].keys():
|
||||
out_config["power_scaling"]["min_psu_power_limit"] = config[opt][
|
||||
"min_psu_power_limit"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["min_psu_power_limit"] = 800
|
||||
if "shutdown_enabled" in config[opt].keys():
|
||||
out_config["power_scaling"]["shutdown_enabled"] = config[opt][
|
||||
"shutdown_enabled"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["shutdown_enabled"] = False
|
||||
if "shutdown_duration" in config[opt].keys():
|
||||
out_config["power_scaling"]["shutdown_duration"] = config[opt][
|
||||
"shutdown_duration"
|
||||
]
|
||||
else:
|
||||
out_config["power_scaling"]["shutdown_duration"] = 3.0
|
||||
return out_config
|
||||
@@ -1,17 +0,0 @@
|
||||
|
||||
config cgminer 'default'
|
||||
option pool1pw 'x'
|
||||
option pool2pw 'x'
|
||||
option pool3pw 'x'
|
||||
option voltage_level_offset '0'
|
||||
option fan '10'
|
||||
option api_allow 'W:0/0'
|
||||
option power_mode 'balance'
|
||||
option pool1url 'stratum+tcp://ca.stratum.slushpool.com:3333'
|
||||
option pool1user 'poolacct.worker1'
|
||||
option pool2url 'stratum+tcp://ca.stratum.slushpool.com:3333'
|
||||
option pool2user 'poolacct.worker2'
|
||||
option pool3url 'stratum+tcp://ca.stratum.slushpool.com:3333'
|
||||
option pool3user 'poolacct.worker3'
|
||||
option ntp_enable 'openwrt'
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
from tools.cfg_util import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,80 +0,0 @@
|
||||
from dataclasses import dataclass, field, asdict
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerData:
|
||||
ip: str
|
||||
model: str = "Unknown"
|
||||
hostname: str = "Unknown"
|
||||
hashrate: float = 0
|
||||
temperature_avg: int = field(init=False)
|
||||
left_board_temp: int = 0
|
||||
left_board_chip_temp: int = 0
|
||||
center_board_temp: int = 0
|
||||
center_board_chip_temp: int = 0
|
||||
right_board_temp: int = 0
|
||||
right_board_chip_temp: int = 0
|
||||
wattage: int = 0
|
||||
fan_1: int = -1
|
||||
fan_2: int = -1
|
||||
fan_3: int = -1
|
||||
fan_4: int = -1
|
||||
left_chips: int = 0
|
||||
center_chips: int = 0
|
||||
right_chips: int = 0
|
||||
total_chips: int = field(init=False)
|
||||
ideal_chips: int = 1
|
||||
percent_ideal: float = field(init=False)
|
||||
nominal: int = field(init=False)
|
||||
pool_split: str = 0
|
||||
pool_1_url: str = "Unknown"
|
||||
pool_1_user: str = "Unknown"
|
||||
pool_2_url: str = ""
|
||||
pool_2_user: str = ""
|
||||
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
return self.right_chips + self.center_chips + self.left_chips
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def nominal(self): # noqa - Skip PyCharm inspection
|
||||
return self.ideal_chips == self.total_chips
|
||||
|
||||
@nominal.setter
|
||||
def nominal(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def percent_ideal(self): # noqa - Skip PyCharm inspection
|
||||
return round((self.total_chips / self.ideal_chips) * 100)
|
||||
|
||||
@percent_ideal.setter
|
||||
def percent_ideal(self, val):
|
||||
pass
|
||||
|
||||
@property
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for temp in [
|
||||
self.left_board_chip_temp,
|
||||
self.center_board_chip_temp,
|
||||
self.right_board_chip_temp,
|
||||
]:
|
||||
if not temp == 0:
|
||||
total_temp += temp
|
||||
temp_count += 1
|
||||
if not temp_count > 0:
|
||||
return 0
|
||||
return round(total_temp / temp_count)
|
||||
|
||||
@temperature_avg.setter
|
||||
def temperature_avg(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
Binary file not shown.
24
docs/API/api.md
Normal file
24
docs/API/api.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# pyasic
|
||||
## Miner APIs
|
||||
Each miner has a unique API that is used to communicate with it.
|
||||
Each of these API types has commands that differ between them, and some commands have data that others do not.
|
||||
Each miner that is a subclass of `BaseMiner` should have an API linked to it as `Miner.api`.
|
||||
|
||||
All API implementations inherit from [`BaseMinerAPI`][pyasic.API.BaseMinerAPI], which implements the basic communications protocols.
|
||||
|
||||
BaseMinerAPI should never be used unless inheriting to create a new miner API class for a new type of miner (which should be exceedingly rare).
|
||||
Use these instead -
|
||||
|
||||
#### [BMMiner API][pyasic.API.bmminer.BMMinerAPI]
|
||||
#### [BOSMiner API][pyasic.API.bosminer.BOSMinerAPI]
|
||||
#### [BTMiner API][pyasic.API.btminer.BTMinerAPI]
|
||||
#### [CGMiner API][pyasic.API.cgminer.CGMinerAPI]
|
||||
#### [Unknown API][pyasic.API.unknown.UnknownAPI]
|
||||
|
||||
<br>
|
||||
|
||||
## BaseMinerAPI
|
||||
::: pyasic.API.BaseMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
heading_level: 4
|
||||
7
docs/API/bmminer.md
Normal file
7
docs/API/bmminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BMMinerAPI
|
||||
::: pyasic.API.bmminer.BMMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/API/bosminer.md
Normal file
7
docs/API/bosminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BOSMinerAPI
|
||||
::: pyasic.API.bosminer.BOSMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/API/btminer.md
Normal file
7
docs/API/btminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## BTMinerAPI
|
||||
::: pyasic.API.btminer.BTMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/API/cgminer.md
Normal file
7
docs/API/cgminer.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## CGMinerAPI
|
||||
::: pyasic.API.cgminer.CGMinerAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
7
docs/API/unknown.md
Normal file
7
docs/API/unknown.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# pyasic
|
||||
## UnknownAPI
|
||||
::: pyasic.API.unknown.UnknownAPI
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
24
docs/config/miner_config.md
Normal file
24
docs/config/miner_config.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# pyasic
|
||||
## Miner Config
|
||||
|
||||
::: pyasic.config.MinerConfig
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Pool Groups
|
||||
|
||||
::: pyasic.config._PoolGroup
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
|
||||
## Pools
|
||||
|
||||
::: pyasic.config._Pool
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
8
docs/data/miner_data.md
Normal file
8
docs/data/miner_data.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## Miner Data
|
||||
|
||||
::: pyasic.data.MinerData
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
99
docs/index.md
Normal file
99
docs/index.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# pyasic
|
||||
*A set of modules for interfacing with many common types of ASIC bitcoin miners, using both their API and SSH.*
|
||||
|
||||
[](https://github.com/psf/black)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pypi.org/project/pyasic/)
|
||||
[](https://pyasic.readthedocs.io/en/latest/)
|
||||
|
||||
## Intro
|
||||
Welcome to pyasic! Pyasic uses an asynchronous method of communicating with asic miners on your network, which makes it super fast.
|
||||
|
||||
Getting started with pyasic is easy. First, find your miner (or miners) on the network by scanning for them or getting the correct class automatically for them if you know the IP.
|
||||
|
||||
<br>
|
||||
|
||||
## Scanning for miners
|
||||
To scan for miners in pyasic, we use the class [`MinerNetwork`][pyasic.network.MinerNetwork], which abstracts the search, communication, identification, setup, and return of a miner to 1 command.
|
||||
The command [`MinerNetwork().scan_network_for_miners()`][pyasic.network.MinerNetwork.scan_network_for_miners] returns a list that contains any miners found.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
|
||||
|
||||
async def scan_miners(): # define async scan function to allow awaiting
|
||||
# create a miner network
|
||||
# you can pass in any IP and it will use that in a subnet with a /24 mask (255 IPs).
|
||||
network = MinerNetwork("192.168.1.50") # this uses the 192.168.1.0-255 network
|
||||
|
||||
# scan for miners asynchronously
|
||||
# this will return the correct type of miners if they are supported with all functionality.
|
||||
miners = await network.scan_network_for_miners()
|
||||
print(miners)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(scan_miners()) # run the scan asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Creating miners based on IP
|
||||
If you already know the IP address of your miner or miners, you can use the [`MinerFactory`][pyasic.miners.miner_factory.MinerFactory] to communicate and identify the miners.
|
||||
The function [`MinerFactory().get_miner()`][pyasic.miners.miner_factory.MinerFactory.get_miner] will return any miner it found at the IP address specified, or an `UnknownMiner` if it cannot identify the miner.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.miners.miner_factory import MinerFactory # miner factory handles miners creation
|
||||
|
||||
|
||||
async def get_miners(): # define async scan function to allow awaiting
|
||||
# get the miner with miner factory
|
||||
# miner factory is a singleton, and will always use the same object and cache
|
||||
# this means you can always call it as MinerFactory().get_miner()
|
||||
miner_1 = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_2 = await MinerFactory().get_miner("192.168.1.76")
|
||||
print(miner_1, miner_2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(get_miners()) # get the miners asynchronously with asyncio.run()
|
||||
```
|
||||
|
||||
<br>
|
||||
|
||||
## Getting data from miners
|
||||
|
||||
Once you have your miner(s) identified, you will likely want to get data from the miner(s). You can do this using a built in function in each miner called `get_data()`.
|
||||
This function will return a instance of the dataclass [`MinerData`][pyasic.data.MinerData] with all data it can gather from the miner.
|
||||
Each piece of data in a [`MinerData`][pyasic.data.MinerData] instance can be referenced by getting it as an attribute, such as [`MinerData().hashrate`][pyasic.data.MinerData].
|
||||
```python
|
||||
import asyncio
|
||||
from pyasic.miners.miner_factory import MinerFactory
|
||||
|
||||
async def gather_miner_data():
|
||||
miner = await MinerFactory().get_miner("192.168.1.75")
|
||||
miner_data = await miner.get_data()
|
||||
print(miner_data) # all data from the dataclass
|
||||
print(miner_data.hashrate) # hashrate of the miner in TH/s
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
|
||||
You can do something similar with multiple miners, with only needing to make a small change to get all the data at once.
|
||||
```python
|
||||
import asyncio # asyncio for handling the async part
|
||||
from pyasic.network import MinerNetwork # miner network handles the scanning
|
||||
|
||||
|
||||
async def gather_miner_data(): # define async scan function to allow awaiting
|
||||
network = MinerNetwork("192.168.1.50")
|
||||
miners = await network.scan_network_for_miners()
|
||||
|
||||
# we need to asyncio.gather() all the miners get_data() functions to make them run together
|
||||
all_miner_data = await asyncio.gather(*[miner.get_data() for miner in miners])
|
||||
|
||||
for miner_data in all_miner_data:
|
||||
print(miner_data) # print out all the data one by one
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(gather_miner_data())
|
||||
```
|
||||
8
docs/miners/miner_factory.md
Normal file
8
docs/miners/miner_factory.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## Miner Factory
|
||||
|
||||
::: pyasic.miners.miner_factory.MinerFactory
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
8
docs/network/miner_network.md
Normal file
8
docs/network/miner_network.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# pyasic
|
||||
## Miner Network
|
||||
|
||||
::: pyasic.network.MinerNetwork
|
||||
handler: python
|
||||
options:
|
||||
show_root_heading: false
|
||||
heading_level: 4
|
||||
3
docs/requirements.txt
Normal file
3
docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
jinja2<3.1.0
|
||||
mkdocs
|
||||
mkdocstrings[python]
|
||||
@@ -1,18 +0,0 @@
|
||||
import logging
|
||||
from settings import DEBUG
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
# filename="logfile.txt",
|
||||
# filemode="a",
|
||||
format="[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
|
||||
datefmt="%x %X",
|
||||
)
|
||||
logger = logging.getLogger()
|
||||
|
||||
if DEBUG:
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
|
||||
else:
|
||||
logger.setLevel(logging.INFO)
|
||||
logging.getLogger("asyncssh").setLevel(logging.WARNING)
|
||||
@@ -1,46 +0,0 @@
|
||||
"""
|
||||
Make a build of the config tool.
|
||||
|
||||
Usage: make_config_tool.py build
|
||||
|
||||
The build will show up in the build directory.
|
||||
"""
|
||||
import datetime
|
||||
import sys
|
||||
import os
|
||||
from cx_Freeze import setup, Executable
|
||||
|
||||
base = None
|
||||
if sys.platform == "win32":
|
||||
base = "Win32GUI"
|
||||
|
||||
version = datetime.datetime.now()
|
||||
version = version.strftime("%y.%m.%d")
|
||||
print(version)
|
||||
|
||||
|
||||
setup(
|
||||
name="UpstreamCFGUtil.exe",
|
||||
version=version,
|
||||
description="Upstream Data Config Utility Build",
|
||||
options={
|
||||
"build_exe": {
|
||||
"build_exe": f"{os.getcwd()}\\build\\UpstreamCFGUtil-{version}-{sys.platform}\\",
|
||||
"include_files": [
|
||||
os.path.join(os.getcwd(), "settings/settings.toml"),
|
||||
os.path.join(os.getcwd(), "static/CFG-Util-README.md"),
|
||||
],
|
||||
"excludes": [
|
||||
os.path.join(os.getcwd(), "tools/web_testbench/files"),
|
||||
],
|
||||
},
|
||||
},
|
||||
executables=[
|
||||
Executable(
|
||||
"config_tool.py",
|
||||
base=base,
|
||||
icon="icon.ico",
|
||||
target_name="UpstreamCFGUtil.exe",
|
||||
)
|
||||
],
|
||||
)
|
||||
@@ -1,9 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon821(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 821"
|
||||
self.fan_count = 2
|
||||
@@ -1,9 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon841(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 841"
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M21S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M30S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S"
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M30SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+"
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
@@ -1,10 +0,0 @@
|
||||
from miners import BaseMiner
|
||||
|
||||
|
||||
class M31SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S+"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
@@ -1,8 +0,0 @@
|
||||
from .M30S import M30S
|
||||
from .M30S_Plus import M30SPlus
|
||||
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40
|
||||
|
||||
from .M31S import M31S
|
||||
from .M31S_Plus import M31SPlus
|
||||
|
||||
from .M32S import M32S
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17(BMMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17Plus(BMMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17Pro(BMMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17e(BMMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17(BMMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17Plus(BMMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17e(BMMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19(BMMiner, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19Pro(BMMiner, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19a # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19a(BMMiner, S19a):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19j(BMMiner, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19jPro(BMMiner, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,11 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT19(BMMiner, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
return "?"
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9(BMMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9i # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9i(BMMiner, S9i):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT9(BMMiner, T9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17(BOSMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17e(BOSMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17(BOSMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17e(BOSMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19(BOSMiner, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19j(BOSMiner, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT19(BOSMiner, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS9(BOSMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17(CGMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17Plus(CGMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17Pro(CGMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17e(CGMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17(CGMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17Plus(CGMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerT17e(CGMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS9(CGMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import Hiveon # noqa - Ignore access to _module
|
||||
from miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class HiveonT9(Hiveon, T9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon1047 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon1047(CGMiner, Avalon1047):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon1066 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon1066(CGMiner, Avalon1066):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon821 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon821(CGMiner, Avalon821):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from miners._types import Avalon841 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerAvalon841(CGMiner, Avalon841):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M20S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM20S(BTMiner, M20S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M20SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM20SPlus(BTMiner, M20SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21(BTMiner, M21):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21S(BTMiner, M21S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M21SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM21SPlus(BTMiner, M21SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30S(BTMiner, M30S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M30SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM30SPlus(BTMiner, M30SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M31S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31S(BTMiner, M31S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M31SPlus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM31SPlus(BTMiner, M31SPlus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from miners._backends import BTMiner # noqa - Ignore access to _module
|
||||
from miners._types import M32S # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BTMinerM32S(BTMiner, M32S):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
@@ -1,8 +0,0 @@
|
||||
from .M30S import BTMinerM30S
|
||||
from .M30S_Plus import BTMinerM30SPlus
|
||||
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30
|
||||
|
||||
from .M31S import BTMinerM31S
|
||||
from .M31S_Plus import BTMinerM31SPlus
|
||||
|
||||
from .M32S import BTMinerM32S
|
||||
24
mkdocs.yml
Normal file
24
mkdocs.yml
Normal file
@@ -0,0 +1,24 @@
|
||||
site_name: pyasic
|
||||
repo_url: https://github.com/UpstreamData/pyasic
|
||||
nav:
|
||||
- Introduction: "index.md"
|
||||
- Miners:
|
||||
- Miner Factory: "miners/miner_factory.md"
|
||||
- Network:
|
||||
- Miner Network: "network/miner_network.md"
|
||||
- Data:
|
||||
- Miner Data: "data/miner_data.md"
|
||||
- Config:
|
||||
- Miner Config: "config/miner_config.md"
|
||||
- Advanced:
|
||||
- Miner APIs:
|
||||
- Base: "API/api.md"
|
||||
- BMMiner: "API/bmminer.md"
|
||||
- BOSMiner: "API/bosminer.md"
|
||||
- BTMiner: "API/btminer.md"
|
||||
- CGMiner: "API/cgminer.md"
|
||||
- Unknown: "API/unknown.md"
|
||||
|
||||
plugins:
|
||||
- mkdocstrings
|
||||
- search
|
||||
396
poetry.lock
generated
Normal file
396
poetry.lock
generated
Normal file
@@ -0,0 +1,396 @@
|
||||
[[package]]
|
||||
name = "anyio"
|
||||
version = "3.6.1"
|
||||
description = "High level compatibility layer for multiple asynchronous event loop implementations"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6.2"
|
||||
|
||||
[package.dependencies]
|
||||
idna = ">=2.8"
|
||||
sniffio = ">=1.1"
|
||||
|
||||
[package.extras]
|
||||
doc = ["packaging", "sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"]
|
||||
test = ["coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "contextlib2", "uvloop (<0.15)", "mock (>=4)", "uvloop (>=0.15)"]
|
||||
trio = ["trio (>=0.16)"]
|
||||
|
||||
[[package]]
|
||||
name = "asyncssh"
|
||||
version = "2.11.0"
|
||||
description = "AsyncSSH: Asynchronous SSHv2 client and server library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">= 3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cryptography = ">=3.1"
|
||||
typing-extensions = ">=3.6"
|
||||
|
||||
[package.extras]
|
||||
bcrypt = ["bcrypt (>=3.1.3)"]
|
||||
fido2 = ["fido2 (>=0.9.2)"]
|
||||
gssapi = ["gssapi (>=1.2.0)"]
|
||||
libnacl = ["libnacl (>=1.4.2)"]
|
||||
pkcs11 = ["python-pkcs11 (>=0.7.0)"]
|
||||
pyopenssl = ["pyOpenSSL (>=17.0.0)"]
|
||||
pywin32 = ["pywin32 (>=227)"]
|
||||
|
||||
[[package]]
|
||||
name = "certifi"
|
||||
version = "2022.6.15"
|
||||
description = "Python package for providing Mozilla's CA Bundle."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "cffi"
|
||||
version = "1.15.1"
|
||||
description = "Foreign Function Interface for Python calling C code."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pycparser = "*"
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
version = "37.0.4"
|
||||
description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[package.dependencies]
|
||||
cffi = ">=1.12"
|
||||
|
||||
[package.extras]
|
||||
docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"]
|
||||
docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"]
|
||||
pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"]
|
||||
sdist = ["setuptools_rust (>=0.11.4)"]
|
||||
ssh = ["bcrypt (>=3.1.5)"]
|
||||
test = ["pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"]
|
||||
|
||||
[[package]]
|
||||
name = "h11"
|
||||
version = "0.12.0"
|
||||
description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "httpcore"
|
||||
version = "0.15.0"
|
||||
description = "A minimal low-level HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
anyio = ">=3.0.0,<4.0.0"
|
||||
certifi = "*"
|
||||
h11 = ">=0.11,<0.13"
|
||||
sniffio = ">=1.0.0,<2.0.0"
|
||||
|
||||
[package.extras]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "httpx"
|
||||
version = "0.23.0"
|
||||
description = "The next generation HTTP client."
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[package.dependencies]
|
||||
certifi = "*"
|
||||
httpcore = ">=0.15.0,<0.16.0"
|
||||
rfc3986 = {version = ">=1.3,<2", extras = ["idna2008"]}
|
||||
sniffio = "*"
|
||||
|
||||
[package.extras]
|
||||
brotli = ["brotlicffi", "brotli"]
|
||||
cli = ["click (>=8.0.0,<9.0.0)", "rich (>=10,<13)", "pygments (>=2.0.0,<3.0.0)"]
|
||||
http2 = ["h2 (>=3,<5)"]
|
||||
socks = ["socksio (>=1.0.0,<2.0.0)"]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "3.3"
|
||||
description = "Internationalized Domain Names in Applications (IDNA)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "passlib"
|
||||
version = "1.7.4"
|
||||
description = "comprehensive password hashing framework supporting over 30 schemes"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.extras]
|
||||
argon2 = ["argon2-cffi (>=18.2.0)"]
|
||||
bcrypt = ["bcrypt (>=3.1.0)"]
|
||||
build_docs = ["sphinx (>=1.6)", "sphinxcontrib-fulltoc (>=1.2.0)", "cloud-sptheme (>=1.10.1)"]
|
||||
totp = ["cryptography"]
|
||||
|
||||
[[package]]
|
||||
name = "pyaml"
|
||||
version = "21.10.1"
|
||||
description = "PyYAML-based module to produce pretty and readable YAML-serialized data"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
PyYAML = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pycparser"
|
||||
version = "2.21"
|
||||
description = "C parser in Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
||||
|
||||
[[package]]
|
||||
name = "pyyaml"
|
||||
version = "6.0"
|
||||
description = "YAML parser and emitter for Python"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
[[package]]
|
||||
name = "rfc3986"
|
||||
version = "1.5.0"
|
||||
description = "Validating URI References per RFC 3986"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
idna = {version = "*", optional = true, markers = "extra == \"idna2008\""}
|
||||
|
||||
[package.extras]
|
||||
idna2008 = ["idna"]
|
||||
|
||||
[[package]]
|
||||
name = "sniffio"
|
||||
version = "1.2.0"
|
||||
description = "Sniff out which async library your code is running under"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.5"
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.10.2"
|
||||
description = "Python Library for Tom's Obvious, Minimal Language"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
|
||||
[[package]]
|
||||
name = "typing-extensions"
|
||||
version = "4.3.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "8d93eafd928d7fed4b0a00d13e46982c2d4310c37acb2faec7e7a477b3f35e9c"
|
||||
|
||||
[metadata.files]
|
||||
anyio = [
|
||||
{file = "anyio-3.6.1-py3-none-any.whl", hash = "sha256:cb29b9c70620506a9a8f87a309591713446953302d7d995344d0d7c6c0c9a7be"},
|
||||
{file = "anyio-3.6.1.tar.gz", hash = "sha256:413adf95f93886e442aea925f3ee43baa5a765a64a0f52c6081894f9992fdd0b"},
|
||||
]
|
||||
asyncssh = [
|
||||
{file = "asyncssh-2.11.0-py3-none-any.whl", hash = "sha256:7302348cbd54c58d3259da17f13e77912de1b005e366b15c8b183d948c8a91a8"},
|
||||
{file = "asyncssh-2.11.0.tar.gz", hash = "sha256:59c36ce77ba9dda8dd57ad875776e7105ddb1fa851bc039bb3aeadeac4f67b56"},
|
||||
]
|
||||
certifi = [
|
||||
{file = "certifi-2022.6.15-py3-none-any.whl", hash = "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412"},
|
||||
{file = "certifi-2022.6.15.tar.gz", hash = "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d"},
|
||||
]
|
||||
cffi = [
|
||||
{file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"},
|
||||
{file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"},
|
||||
{file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"},
|
||||
{file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"},
|
||||
{file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"},
|
||||
{file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"},
|
||||
{file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"},
|
||||
{file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"},
|
||||
{file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"},
|
||||
{file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"},
|
||||
]
|
||||
cryptography = [
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"},
|
||||
{file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"},
|
||||
{file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"},
|
||||
{file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"},
|
||||
{file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"},
|
||||
{file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"},
|
||||
]
|
||||
h11 = [
|
||||
{file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6"},
|
||||
{file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"},
|
||||
]
|
||||
httpcore = [
|
||||
{file = "httpcore-0.15.0-py3-none-any.whl", hash = "sha256:1105b8b73c025f23ff7c36468e4432226cbb959176eab66864b8e31c4ee27fa6"},
|
||||
{file = "httpcore-0.15.0.tar.gz", hash = "sha256:18b68ab86a3ccf3e7dc0f43598eaddcf472b602aba29f9aa6ab85fe2ada3980b"},
|
||||
]
|
||||
httpx = [
|
||||
{file = "httpx-0.23.0-py3-none-any.whl", hash = "sha256:42974f577483e1e932c3cdc3cd2303e883cbfba17fe228b0f63589764d7b9c4b"},
|
||||
{file = "httpx-0.23.0.tar.gz", hash = "sha256:f28eac771ec9eb4866d3fb4ab65abd42d38c424739e80c08d8d20570de60b0ef"},
|
||||
]
|
||||
idna = [
|
||||
{file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"},
|
||||
{file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"},
|
||||
]
|
||||
passlib = [
|
||||
{file = "passlib-1.7.4-py2.py3-none-any.whl", hash = "sha256:aa6bca462b8d8bda89c70b382f0c298a20b5560af6cbfa2dce410c0a2fb669f1"},
|
||||
{file = "passlib-1.7.4.tar.gz", hash = "sha256:defd50f72b65c5402ab2c573830a6978e5f202ad0d984793c8dde2c4152ebe04"},
|
||||
]
|
||||
pyaml = [
|
||||
{file = "pyaml-21.10.1-py2.py3-none-any.whl", hash = "sha256:19985ed303c3a985de4cf8fd329b6d0a5a5b5c9035ea240eccc709ebacbaf4a0"},
|
||||
{file = "pyaml-21.10.1.tar.gz", hash = "sha256:c6519fee13bf06e3bb3f20cacdea8eba9140385a7c2546df5dbae4887f768383"},
|
||||
]
|
||||
pycparser = [
|
||||
{file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"},
|
||||
{file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"},
|
||||
]
|
||||
pyyaml = [
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"},
|
||||
{file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"},
|
||||
{file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"},
|
||||
{file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"},
|
||||
{file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"},
|
||||
{file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"},
|
||||
{file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"},
|
||||
]
|
||||
rfc3986 = [
|
||||
{file = "rfc3986-1.5.0-py2.py3-none-any.whl", hash = "sha256:a86d6e1f5b1dc238b218b012df0aa79409667bb209e58da56d0b94704e712a97"},
|
||||
{file = "rfc3986-1.5.0.tar.gz", hash = "sha256:270aaf10d87d0d4e095063c65bf3ddbc6ee3d0b226328ce21e036f946e421835"},
|
||||
]
|
||||
sniffio = [
|
||||
{file = "sniffio-1.2.0-py3-none-any.whl", hash = "sha256:471b71698eac1c2112a40ce2752bb2f4a4814c22a54a3eed3676bc0f5ca9f663"},
|
||||
{file = "sniffio-1.2.0.tar.gz", hash = "sha256:c4666eecec1d3f50960c6bdf61ab7bc350648da6c126e3cf6898d8cd4ddcd3de"},
|
||||
]
|
||||
toml = [
|
||||
{file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"},
|
||||
{file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"},
|
||||
]
|
||||
typing-extensions = [
|
||||
{file = "typing_extensions-4.3.0-py3-none-any.whl", hash = "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02"},
|
||||
{file = "typing_extensions-4.3.0.tar.gz", hash = "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6"},
|
||||
]
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import ipaddress
|
||||
import warnings
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
|
||||
class APIError(Exception):
|
||||
@@ -98,8 +99,8 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
|
||||
async def send_command(
|
||||
self,
|
||||
command: str or bytes,
|
||||
parameters: str or int or bool = None,
|
||||
command: Union[str, bytes],
|
||||
parameters: Union[str, int, bool] = None,
|
||||
ignore_errors: bool = False,
|
||||
x19_command: bool = False,
|
||||
) -> dict:
|
||||
@@ -171,7 +172,10 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
return False, data["Msg"]
|
||||
else:
|
||||
# make sure the command succeeded
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
if type(data["STATUS"]) == str:
|
||||
if data["STATUS"] in ["RESTART"]:
|
||||
return True, None
|
||||
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
# this is an error
|
||||
if data["STATUS"][0]["STATUS"] not in ("S", "I"):
|
||||
return False, data["STATUS"][0]["Msg"]
|
||||
@@ -197,8 +201,12 @@ If you are sure you want to use this command please use API.send_command("{item}
|
||||
str_data = str_data.replace("}{", "},{")
|
||||
# fix an error with a bmminer return having a specific comma that breaks json.loads()
|
||||
str_data = str_data.replace("[,{", "[{")
|
||||
# fix an error with a btminer return having a specific comma that breaks json.loads()
|
||||
# fix an error with Avalonminers returning inf and nan
|
||||
str_data = str_data.replace("inf", "0")
|
||||
str_data = str_data.replace("nan", "0")
|
||||
# fix whatever this garbage from avalonminers is `,"id":1}`
|
||||
if str_data.startswith(","):
|
||||
str_data = f"{{{str_data[1:]}"
|
||||
# parse the json
|
||||
parsed_data = json.loads(str_data)
|
||||
# handle bad json
|
||||
680
pyasic/API/bmminer.py
Normal file
680
pyasic/API/bmminer.py
Normal file
@@ -0,0 +1,680 @@
|
||||
from pyasic.API import 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, port: int = 4028) -> None:
|
||||
super().__init__(ip, port)
|
||||
|
||||
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")
|
||||
261
pyasic/API/bosminer.py
Normal file
261
pyasic/API/bosminer.py
Normal file
@@ -0,0 +1,261 @@
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class BOSMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the BOSMiner API.
|
||||
|
||||
Each method corresponds to an API command in BOSMiner.
|
||||
|
||||
[BOSMiner API documentation](https://docs.braiins.com/os/plus-en/Development/1_api.html)
|
||||
|
||||
This class abstracts use of the BOSMiner 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, port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
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 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 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 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 pools(self) -> dict:
|
||||
"""Get pool information.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner pool information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
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 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 version(self) -> dict:
|
||||
"""Get miner version info.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Miner version information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("version")
|
||||
|
||||
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 BOSMiner.
|
||||
<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 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 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 fans(self) -> dict:
|
||||
"""Get fan data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the fans of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("fans")
|
||||
|
||||
async def tempctrl(self) -> dict:
|
||||
"""Get temperature control data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data about the temp control settings of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("tempctrl")
|
||||
|
||||
async def temps(self) -> dict:
|
||||
"""Get temperature data.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the temps of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("temps")
|
||||
|
||||
async def tunerstatus(self) -> dict:
|
||||
"""Get tuner status data
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Data on the status of autotuning.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("tunerstatus")
|
||||
|
||||
async def pause(self) -> dict:
|
||||
"""Pause mining.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Confirmation of pausing mining.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pause")
|
||||
|
||||
async def resume(self) -> dict:
|
||||
"""Resume mining.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Returns:
|
||||
Confirmation of resuming mining.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("resume")
|
||||
@@ -9,8 +9,8 @@ import logging
|
||||
from passlib.handlers.md5_crypt import md5_crypt
|
||||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
|
||||
from API import BaseMinerAPI, APIError
|
||||
from settings import WHATSMINER_PWD
|
||||
from pyasic.API import BaseMinerAPI, APIError
|
||||
from pyasic.settings import WHATSMINER_PWD
|
||||
|
||||
|
||||
### IMPORTANT ###
|
||||
@@ -29,9 +29,12 @@ def _crypt(word: str, salt: str) -> str:
|
||||
'\s*\$(\d+)\$([\w\./]*)\$'. If this format is not used, a
|
||||
ValueError is raised.
|
||||
|
||||
:param word: The word to be encrypted.
|
||||
:param salt: The salt to encrypt the word.
|
||||
:return: An MD5 hash of the word with the salt.
|
||||
Parameters:
|
||||
word: The word to be encrypted.
|
||||
salt: The salt to encrypt the word.
|
||||
|
||||
Returns:
|
||||
An MD5 hash of the word with the salt.
|
||||
"""
|
||||
# compile a standard format for the salt
|
||||
standard_salt = re.compile("\s*\$(\d+)\$([\w\./]*)\$")
|
||||
@@ -50,11 +53,11 @@ def _crypt(word: str, salt: str) -> str:
|
||||
def _add_to_16(string: str) -> bytes:
|
||||
"""Add null bytes to a string until the length is a multiple 16
|
||||
|
||||
:param string: The string to lengthen to a multiple of 16 and
|
||||
encode.
|
||||
Parameters:
|
||||
string: The string to lengthen to a multiple of 16 and encode.
|
||||
|
||||
:return: The input string as bytes with a multiple of 16 as the
|
||||
length.
|
||||
Returns:
|
||||
The input string as bytes with a multiple of 16 as the length.
|
||||
"""
|
||||
while len(string) % 16 != 0:
|
||||
string += "\0"
|
||||
@@ -67,10 +70,12 @@ def parse_btminer_priviledge_data(token_data: dict, data: dict):
|
||||
Parses data from the BTMiner privileged API using the the token
|
||||
from the API in an AES format.
|
||||
|
||||
:param token_data: The token information from self.get_token().
|
||||
:param data: The data to parse, returned from the API.
|
||||
Parameters:
|
||||
token_data: The token information from self.get_token().
|
||||
data: The data to parse, returned from the API.
|
||||
|
||||
:return: A decoded dict version of the privileged command output.
|
||||
Returns:
|
||||
A decoded dict version of the privileged command output.
|
||||
"""
|
||||
# get the encoded data from the dict
|
||||
enc_data = data["enc"]
|
||||
@@ -97,10 +102,12 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
|
||||
command as a dict of {'command': cmd}, with cmd being any command
|
||||
that the miner API accepts.
|
||||
|
||||
:param token_data: The token information from self.get_token().
|
||||
:param command: The command to turn into a privileged command.
|
||||
Parameters:
|
||||
token_data: The token information from self.get_token().
|
||||
command: The command to turn into a privileged command.
|
||||
|
||||
:return: The encrypted privileged command to be sent to the miner.
|
||||
Returns:
|
||||
The encrypted privileged command to be sent to the miner.
|
||||
"""
|
||||
# add token to command
|
||||
command["token"] = token_data["host_sign"]
|
||||
@@ -132,7 +139,7 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
Each method corresponds to an API command in BMMiner.
|
||||
|
||||
This class abstracts use of the BTMiner API, as well as the
|
||||
methods for sending commands to it. The self.send_command()
|
||||
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.
|
||||
@@ -148,12 +155,13 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
this automatically for you and will decode the output to look like
|
||||
a normal output from a miner API.
|
||||
|
||||
:param ip: The IP of the miner to reference the API on.
|
||||
:param port: The port to reference the API on. Default is 4028.
|
||||
:param pwd: The admin password of the miner. Default is admin.
|
||||
Parameters:
|
||||
ip: The IP of the miner to reference the API on.
|
||||
port: The port to reference the API on. Default is 4028.
|
||||
pwd: The admin password of the miner. Default is admin.
|
||||
"""
|
||||
|
||||
def __init__(self, ip, port=4028, pwd: str = WHATSMINER_PWD):
|
||||
def __init__(self, ip: str, port: int = 4028, pwd: str = WHATSMINER_PWD):
|
||||
super().__init__(ip, port)
|
||||
self.admin_pwd = pwd
|
||||
self.current_token = None
|
||||
@@ -165,19 +173,6 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
ignore_errors: bool = False,
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
"""Send a command to the miner API.
|
||||
|
||||
Send a command using an asynchronous connection, load the data,
|
||||
parse encoded data if needed, and return the result.
|
||||
|
||||
:param command: The command to send to the miner.
|
||||
:param parameters: Parameters to pass to the command.
|
||||
:param ignore_errors: Ignore the E (Error) status code from the
|
||||
API.
|
||||
|
||||
:return: The data received from the API after sending the
|
||||
command.
|
||||
"""
|
||||
# check if command is a string
|
||||
# if its bytes its encoded and needs to be sent raw
|
||||
if isinstance(command, str):
|
||||
@@ -232,11 +227,14 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
# return the parsed json as a dict
|
||||
return data
|
||||
|
||||
async def get_token(self):
|
||||
async def get_token(self) -> dict:
|
||||
"""Gets token information from the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: An encoded token and md5 password, which are used
|
||||
for the privileged API.
|
||||
Returns:
|
||||
An encoded token and md5 password, which are used for the privileged API.
|
||||
</details>
|
||||
"""
|
||||
# get the token
|
||||
data = await self.send_command("get_token")
|
||||
@@ -278,22 +276,28 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
pool_3: str = None,
|
||||
worker_3: str = None,
|
||||
passwd_3: str = None,
|
||||
):
|
||||
) -> dict:
|
||||
"""Update the pools of the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the pools of the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
:param pool_1: The URL to update pool 1 to.
|
||||
:param worker_1: The worker name for pool 1 to update to.
|
||||
:param passwd_1: The password for pool 1 to update to.
|
||||
:param pool_2: The URL to update pool 2 to.
|
||||
:param worker_2: The worker name for pool 2 to update to.
|
||||
:param passwd_2: The password for pool 2 to update to.
|
||||
:param pool_3: The URL to update pool 3 to.
|
||||
:param worker_3: The worker name for pool 3 to update to.
|
||||
:param passwd_3: The password for pool 3 to update to.
|
||||
:return: A dict from the API to confirm the pools were updated.
|
||||
Parameters:
|
||||
pool_1: The URL to update pool 1 to.
|
||||
worker_1: The worker name for pool 1 to update to.
|
||||
passwd_1: The password for pool 1 to update to.
|
||||
pool_2: The URL to update pool 2 to.
|
||||
worker_2: The worker name for pool 2 to update to.
|
||||
passwd_2: The password for pool 2 to update to.
|
||||
pool_3: The URL to update pool 3 to.
|
||||
worker_3: The worker name for pool 3 to update to.
|
||||
passwd_3: The password for pool 3 to update to.
|
||||
|
||||
Returns:
|
||||
A dict from the API to confirm the pools were updated.
|
||||
</details>
|
||||
"""
|
||||
# get the token and password from the miner
|
||||
token_data = await self.get_token()
|
||||
@@ -336,27 +340,36 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
# send the command
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def restart(self):
|
||||
async def restart(self) -> dict:
|
||||
"""Restart BTMiner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Restart BTMiner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the restart.
|
||||
Returns:
|
||||
A reply informing of the restart.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "restart_btminer"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def power_off(self, respbefore: bool = True):
|
||||
async def power_off(self, respbefore: bool = True) -> dict:
|
||||
"""Power off the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Power off the miner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
:param respbefore: Whether to respond before powering off.
|
||||
:return: A reply informing of the status of powering off.
|
||||
Parameters:
|
||||
respbefore: Whether to respond before powering off.
|
||||
Returns:
|
||||
A reply informing of the status of powering off.
|
||||
</details>
|
||||
"""
|
||||
if respbefore:
|
||||
command = {"cmd": "power_off", "respbefore": "true"}
|
||||
@@ -366,26 +379,36 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def power_on(self):
|
||||
async def power_on(self) -> dict:
|
||||
"""Power on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Power on the miner using the API, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of powering on.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of powering on.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "power_on"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def reset_led(self):
|
||||
async def reset_led(self) -> dict:
|
||||
"""Reset the LED on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Reset the LED on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of resetting the LED.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of resetting the LED.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_led", "param": "auto"}
|
||||
token_data = await self.get_token()
|
||||
@@ -398,17 +421,23 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
period: int = 2000,
|
||||
duration: int = 1000,
|
||||
start: int = 0,
|
||||
):
|
||||
) -> dict:
|
||||
"""Set the LED on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the LED on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
:param color: The LED color to set, either 'red' or 'green'.
|
||||
:param period: The flash cycle in ms.
|
||||
:param duration: LED on time in the cycle in ms.
|
||||
:param start: LED on time offset in the cycle in ms.
|
||||
:return: A reply informing of the status of setting the LED.
|
||||
Parameters:
|
||||
color: The LED color to set, either 'red' or 'green'.
|
||||
period: The flash cycle in ms.
|
||||
duration: LED on time in the cycle in ms.
|
||||
start: LED on time offset in the cycle in ms.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the LED.
|
||||
</details>
|
||||
"""
|
||||
command = {
|
||||
"cmd": "set_led",
|
||||
@@ -421,14 +450,18 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_low_power(self):
|
||||
async def set_low_power(self) -> dict:
|
||||
"""Set low power mode on the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set low power mode on the miner using the API, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of setting low power
|
||||
mode.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting low power mode.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_low_power"}
|
||||
token_data = await self.get_token()
|
||||
@@ -436,46 +469,62 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def update_firmware(self): # noqa - static
|
||||
"""Not implemented."""
|
||||
# to be determined if this will be added later
|
||||
# requires a file stream in bytes
|
||||
return NotImplementedError
|
||||
|
||||
async def reboot(self):
|
||||
async def reboot(self) -> dict:
|
||||
"""Reboot the miner using the API.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: A reply informing of the status of the reboot.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reboot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "reboot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def factory_reset(self):
|
||||
async def factory_reset(self) -> dict:
|
||||
"""Reset the miner to factory defaults.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: A reply informing of the status of the reset.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of the reset.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "factory_reset"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def update_pwd(self, old_pwd: str, new_pwd: str):
|
||||
async def update_pwd(self, old_pwd: str, new_pwd: str) -> dict:
|
||||
"""Update the admin user's password.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the admin user's password, only works after changing the
|
||||
password of the miner using the Whatsminer tool. New password
|
||||
has a max length of 8 bytes, using letters, numbers, and
|
||||
underscores.
|
||||
|
||||
:param old_pwd: The old admin password.
|
||||
:param new_pwd: The new password to set.
|
||||
:return: A reply informing of the status of setting the
|
||||
password.
|
||||
Parameters:
|
||||
old_pwd: The old admin password.
|
||||
new_pwd: The new password to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the password.
|
||||
"""
|
||||
# check if password length is greater than 8 bytes
|
||||
if len(new_pwd.encode("utf-8")) > 8:
|
||||
return APIError(
|
||||
raise APIError(
|
||||
f"New password too long, the max length is 8. "
|
||||
f"Password size: {len(new_pwd.encode('utf-8'))}"
|
||||
)
|
||||
@@ -484,19 +533,25 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_target_freq(self, percent: int):
|
||||
async def set_target_freq(self, percent: int) -> dict:
|
||||
"""Update the target frequency.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Update the target frequency, only works after changing the
|
||||
password of the miner using the Whatsminer tool. The new
|
||||
frequency must be between -10% and 100%.
|
||||
|
||||
:param percent: The frequency % to set.
|
||||
:return: A reply informing of the status of setting the
|
||||
frequency.
|
||||
Parameters:
|
||||
percent: The frequency % to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the frequency.
|
||||
</details>
|
||||
"""
|
||||
if not -10 < percent < 100:
|
||||
return APIError(
|
||||
raise APIError(
|
||||
f"Frequency % is outside of the allowed "
|
||||
f"range. Please set a % between -10 and "
|
||||
f"100"
|
||||
@@ -506,88 +561,122 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def enable_fast_boot(self):
|
||||
async def enable_fast_boot(self) -> dict:
|
||||
"""Turn on fast boot.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn on fast boot, only works after changing the password of
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of enabling fast boot.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "enable_btminer_fast_boot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def disable_fast_boot(self):
|
||||
async def disable_fast_boot(self) -> dict:
|
||||
"""Turn off fast boot.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn off fast boot, only works after changing the password of
|
||||
the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of disabling fast boot.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling fast boot.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "disable_btminer_fast_boot"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def enable_web_pools(self):
|
||||
async def enable_web_pools(self) -> dict:
|
||||
"""Turn on web pool updates.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn on web pool updates, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of enabling web pools.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of enabling web pools.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "enable_web_pools"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def disable_web_pools(self):
|
||||
async def disable_web_pools(self) -> dict:
|
||||
"""Turn off web pool updates.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Turn off web pool updates, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
:return: A reply informing of the status of disabling web
|
||||
pools.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of disabling web pools.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "disable_web_pools"}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_hostname(self, hostname: str):
|
||||
async def set_hostname(self, hostname: str) -> dict:
|
||||
"""Set the hostname of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the hostname of the miner, only works after changing the
|
||||
password of the miner using the Whatsminer tool.
|
||||
|
||||
Parameters:
|
||||
hostname: The new hostname to use.
|
||||
Returns:
|
||||
|
||||
:param hostname: The new hostname to use.
|
||||
:return: A reply informing of the status of setting the
|
||||
hostname.
|
||||
A reply informing of the status of setting the hostname.
|
||||
</details>
|
||||
"""
|
||||
command = {"cmd": "set_hostname", "hostname": hostname}
|
||||
token_data = await self.get_token()
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def set_power_pct(self, percent: int):
|
||||
async def set_power_pct(self, percent: int) -> dict:
|
||||
"""Set the power percentage of the miner.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Set the power percentage of the miner, only works after changing
|
||||
the password of the miner using the Whatsminer tool.
|
||||
|
||||
:param percent: The power percentage to set.
|
||||
:return: A reply informing of the status of setting the
|
||||
power percentage.
|
||||
Parameters:
|
||||
percent: The power percentage to set.
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of setting the power percentage.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not 0 < percent < 100:
|
||||
return APIError(
|
||||
raise APIError(
|
||||
f"Power PCT % is outside of the allowed "
|
||||
f"range. Please set a % between 0 and "
|
||||
f"100"
|
||||
@@ -597,22 +686,29 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
enc_command = create_privileged_cmd(token_data, command)
|
||||
return await self.send_command(enc_command)
|
||||
|
||||
async def pre_power_on(self, complete: bool, msg: str):
|
||||
async def pre_power_on(self, complete: bool, msg: str) -> dict:
|
||||
"""Configure or check status of pre power on.
|
||||
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Configure or check status of pre power on, only works after
|
||||
changing the password of the miner using the Whatsminer tool.
|
||||
|
||||
:param complete: check whether pre power on is complete.
|
||||
:param msg: the message to check.
|
||||
"wait for adjust temp" or
|
||||
"adjust complete" or
|
||||
"adjust continue"
|
||||
:return: A reply informing of the status of pre power on.
|
||||
Parameters:
|
||||
complete: check whether pre power on is complete.
|
||||
msg: ## the message to check.
|
||||
* `wait for adjust temp`
|
||||
* `adjust complete`
|
||||
* `adjust continue`
|
||||
Returns:
|
||||
|
||||
A reply informing of the status of pre power on.
|
||||
</details>
|
||||
"""
|
||||
|
||||
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
|
||||
return APIError(
|
||||
raise APIError(
|
||||
"Message is incorrect, please choose one of "
|
||||
'["wait for adjust temp", '
|
||||
'"adjust complete", '
|
||||
@@ -629,77 +725,126 @@ class BTMinerAPI(BaseMinerAPI):
|
||||
|
||||
#### END privileged COMMANDS ####
|
||||
|
||||
async def summary(self):
|
||||
async def summary(self) -> dict:
|
||||
"""Get the summary status from the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Summary status of the miner.
|
||||
Returns:
|
||||
|
||||
Summary status of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("summary")
|
||||
|
||||
async def pools(self):
|
||||
async def pools(self) -> dict:
|
||||
"""Get the pool status from the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Pool status of the miner.
|
||||
Returns:
|
||||
|
||||
Pool status of the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
async def devs(self):
|
||||
async def devs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Data on each PGA/ASC with their details.
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devs")
|
||||
|
||||
async def edevs(self):
|
||||
"""Get data on each PGA/ASC with their details, ignoring
|
||||
blacklisted and zombie devices.
|
||||
async def edevs(self) -> dict:
|
||||
"""Get data on each PGA/ASC with their details, ignoring blacklisted and zombie devices.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Data on each PGA/ASC with their details.
|
||||
Returns:
|
||||
|
||||
Data on each PGA/ASC with their details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("edevs")
|
||||
|
||||
async def devdetails(self):
|
||||
async def devdetails(self) -> dict:
|
||||
"""Get data on all devices with their static details.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Data on all devices with their static details.
|
||||
Returns:
|
||||
|
||||
Data on all devices with their static details.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("devdetails")
|
||||
|
||||
async def get_psu(self):
|
||||
async def get_psu(self) -> dict:
|
||||
"""Get data on the PSU and power information.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Data on the PSU and power information.
|
||||
Returns:
|
||||
|
||||
Data on the PSU and power information.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_psu")
|
||||
|
||||
async def version(self):
|
||||
"""Get version data for the miner.
|
||||
async def version(self) -> dict:
|
||||
"""Get version data for the miner. Wraps `self.get_version()`.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
Get version data for the miner. This calls another function,
|
||||
self.get_version(), but is named version to stay consistent
|
||||
with the other miner APIs.
|
||||
|
||||
:return: Version data for the miner.
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.get_version()
|
||||
|
||||
async def get_version(self):
|
||||
async def get_version(self) -> dict:
|
||||
"""Get version data for the miner.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: Version data for the miner.
|
||||
Returns:
|
||||
|
||||
Version data for the miner.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_version")
|
||||
|
||||
async def status(self):
|
||||
async def status(self) -> dict:
|
||||
"""Get BTMiner status and firmware version.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: BTMiner status and firmware version.
|
||||
Returns:
|
||||
|
||||
BTMiner status and firmware version.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("status")
|
||||
|
||||
async def get_miner_info(self):
|
||||
async def get_miner_info(self) -> dict:
|
||||
"""Get general miner info.
|
||||
<details>
|
||||
<summary>Expand</summary>
|
||||
|
||||
:return: General miner info.
|
||||
Returns:
|
||||
|
||||
General miner info.
|
||||
</details>
|
||||
"""
|
||||
return await self.send_command("get_miner_info")
|
||||
680
pyasic/API/cgminer.py
Normal file
680
pyasic/API/cgminer.py
Normal file
@@ -0,0 +1,680 @@
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class CGMinerAPI(BaseMinerAPI):
|
||||
"""An abstraction of the CGMiner API.
|
||||
|
||||
Each method corresponds to an API command in GGMiner.
|
||||
|
||||
[CGMiner API documentation](https://github.com/ckolivas/cgminer/blob/master/API-README)
|
||||
|
||||
This class abstracts use of the CGMiner 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, port: int = 4028):
|
||||
super().__init__(ip, port)
|
||||
|
||||
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 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 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 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 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 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 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")
|
||||
@@ -1,4 +1,4 @@
|
||||
from API import BaseMinerAPI
|
||||
from pyasic.API import BaseMinerAPI
|
||||
|
||||
|
||||
class UnknownAPI(BaseMinerAPI):
|
||||
@@ -72,7 +72,7 @@ class UnknownAPI(BaseMinerAPI):
|
||||
async def addpool(self, url: str, username: str, password: str) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
raise NotImplementedError
|
||||
# return await self.send_command("addpool", parameters=f"{url}, {username}, {password}")
|
||||
# return await self.send_command("addpool", parameters=f"{url},{username},{password}")
|
||||
|
||||
async def removepool(self, n: int) -> dict:
|
||||
# BOS has not implemented this yet, they will in the future
|
||||
396
pyasic/config/__init__.py
Normal file
396
pyasic/config/__init__.py
Normal file
@@ -0,0 +1,396 @@
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Literal, List
|
||||
import random
|
||||
import string
|
||||
|
||||
import toml
|
||||
import yaml
|
||||
import json
|
||||
import time
|
||||
|
||||
|
||||
@dataclass
|
||||
class _Pool:
|
||||
"""A dataclass for pool information.
|
||||
|
||||
Attributes:
|
||||
url: URL of the pool.
|
||||
username: Username on the pool.
|
||||
password: Worker password on the pool.
|
||||
"""
|
||||
|
||||
url: str = ""
|
||||
username: str = ""
|
||||
password: str = ""
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool data as a dict to usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
for key in data.keys():
|
||||
if key == "url":
|
||||
self.url = data[key]
|
||||
if key in ["user", "username"]:
|
||||
self.username = data[key]
|
||||
if key in ["pass", "password"]:
|
||||
self.password = data[key]
|
||||
return self
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "pass": self.password}
|
||||
return pool
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a string usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = ",".join([self.url, username, self.password])
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
username = self.username
|
||||
if user_suffix:
|
||||
username = f"{username}{user_suffix}"
|
||||
|
||||
pool = {"url": self.url, "user": username, "password": self.password}
|
||||
return pool
|
||||
|
||||
|
||||
@dataclass
|
||||
class _PoolGroup:
|
||||
"""A dataclass for pool group information.
|
||||
|
||||
Attributes:
|
||||
quota: The group quota.
|
||||
group_name: The name of the pool group.
|
||||
pools: A list of pools in this group.
|
||||
"""
|
||||
|
||||
quota: int = 1
|
||||
group_name: str = None
|
||||
pools: List[_Pool] = None
|
||||
|
||||
def __post_init__(self):
|
||||
if not self.group_name:
|
||||
self.group_name = "".join(
|
||||
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
|
||||
) # generate random pool group name in case it isn't set
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert raw pool group data as a dict to usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
pools = []
|
||||
for key in data.keys():
|
||||
if key in ["name", "group_name"]:
|
||||
self.group_name = data[key]
|
||||
if key == "quota":
|
||||
self.quota = data[key]
|
||||
if key in ["pools", "pool"]:
|
||||
for pool in data[key]:
|
||||
pools.append(_Pool().from_dict(pool))
|
||||
self.pools = pools
|
||||
return self
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> List[dict]:
|
||||
"""Convert the data in this class to a list usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pools = []
|
||||
for pool in self.pools[:3]:
|
||||
pools.append(pool.as_x19(user_suffix=user_suffix))
|
||||
return pools
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a dict usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
pool = self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return pool
|
||||
|
||||
def as_bos(self, user_suffix: str = None) -> dict:
|
||||
"""Convert the data in this class to a dict usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
group = {
|
||||
"name": self.group_name,
|
||||
"quota": self.quota,
|
||||
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
|
||||
}
|
||||
return group
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
"""A dataclass for miner configuration information.
|
||||
|
||||
Attributes:
|
||||
pool_groups: A list of pool groups in this config.
|
||||
temp_mode: The temperature control mode.
|
||||
temp_target: The target temp.
|
||||
temp_hot: The hot temp (100% fans).
|
||||
temp_dangerous: The dangerous temp (shutdown).
|
||||
minimum_fans: The minimum numbers of fans to run the miner.
|
||||
fan_speed: Manual fan speed to run the fan at (only if temp_mode == "manual").
|
||||
asicboost: Whether or not to enable asicboost.
|
||||
autotuning_enabled: Whether or not to enable autotuning.
|
||||
autotuning_wattage: The wattage to use when autotuning.
|
||||
dps_enabled: Whether or not to enable dynamic power scaling.
|
||||
dps_power_step: The amount of power to reduce autotuning by when the miner reaches dangerous temp.
|
||||
dps_min_power: The minimum power to reduce autotuning to.
|
||||
dps_shutdown_enabled: Whether or not to shutdown the miner when `dps_min_power` is reached.
|
||||
dps_shutdown_duration: The amount of time to shutdown for (in hours).
|
||||
"""
|
||||
|
||||
pool_groups: List[_PoolGroup] = None
|
||||
|
||||
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
|
||||
temp_target: float = 70.0
|
||||
temp_hot: float = 80.0
|
||||
temp_dangerous: float = 10.0
|
||||
|
||||
minimum_fans: int = None
|
||||
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
|
||||
|
||||
asicboost: bool = None
|
||||
|
||||
autotuning_enabled: bool = True
|
||||
autotuning_wattage: int = 900
|
||||
|
||||
dps_enabled: bool = None
|
||||
dps_power_step: int = None
|
||||
dps_min_power: int = None
|
||||
dps_shutdown_enabled: bool = None
|
||||
dps_shutdown_duration: float = None
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Convert the data in this class to a dict."""
|
||||
data_dict = asdict(self)
|
||||
for key in asdict(self).keys():
|
||||
if data_dict[key] is None:
|
||||
del data_dict[key]
|
||||
return data_dict
|
||||
|
||||
def as_toml(self) -> str:
|
||||
"""Convert the data in this class to toml."""
|
||||
return toml.dumps(self.as_dict())
|
||||
|
||||
def as_yaml(self) -> str:
|
||||
"""Convert the data in this class to yaml."""
|
||||
return yaml.dump(self.as_dict(), sort_keys=False)
|
||||
|
||||
def from_raw(self, data: dict):
|
||||
"""Convert raw config data as a dict to usable data and save it to this class.
|
||||
This should be able to handle any raw config file from any miner supported by pyasic.
|
||||
|
||||
Parameters:
|
||||
data: The raw config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for key in data.keys():
|
||||
if key == "pools":
|
||||
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
|
||||
elif key == "group":
|
||||
for group in data[key]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
|
||||
if key == "bitmain-fan-ctrl":
|
||||
if data[key]:
|
||||
self.temp_mode = "manual"
|
||||
if data.get("bitmain-fan-pwm"):
|
||||
self.fan_speed = int(data["bitmain-fan-pwm"])
|
||||
elif key == "fan_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "min_fans":
|
||||
self.minimum_fans = data[key][_key]
|
||||
elif _key == "speed":
|
||||
self.fan_speed = data[key][_key]
|
||||
elif key == "temp_control":
|
||||
for _key in data[key].keys():
|
||||
if _key == "mode":
|
||||
self.temp_mode = data[key][_key]
|
||||
elif _key == "target_temp":
|
||||
self.temp_target = data[key][_key]
|
||||
elif _key == "hot_temp":
|
||||
self.temp_hot = data[key][_key]
|
||||
elif _key == "dangerous_temp":
|
||||
self.temp_dangerous = data[key][_key]
|
||||
|
||||
if key == "hash_chain_global":
|
||||
if data[key].get("asic_boost"):
|
||||
self.asicboost = data[key]["asic_boost"]
|
||||
|
||||
if key == "autotuning":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.autotuning_enabled = data[key][_key]
|
||||
elif _key == "psu_power_limit":
|
||||
self.autotuning_wattage = data[key][_key]
|
||||
|
||||
if key == "power_scaling":
|
||||
for _key in data[key].keys():
|
||||
if _key == "enabled":
|
||||
self.dps_enabled = data[key][_key]
|
||||
elif _key == "power_step":
|
||||
self.dps_power_step = data[key][_key]
|
||||
elif _key == "min_psu_power_limit":
|
||||
self.dps_min_power = data[key][_key]
|
||||
elif _key == "shutdown_enabled":
|
||||
self.dps_shutdown_enabled = data[key][_key]
|
||||
elif _key == "shutdown_duration":
|
||||
self.dps_shutdown_duration = data[key][_key]
|
||||
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_dict(self, data: dict):
|
||||
"""Convert an output dict of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The dict config data to convert.
|
||||
"""
|
||||
pool_groups = []
|
||||
for group in data["pool_groups"]:
|
||||
pool_groups.append(_PoolGroup().from_dict(group))
|
||||
for key in data.keys():
|
||||
if getattr(self, key) and not key == "pool_groups":
|
||||
setattr(self, key, data[key])
|
||||
self.pool_groups = pool_groups
|
||||
return self
|
||||
|
||||
def from_toml(self, data: str):
|
||||
"""Convert output toml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The toml config data to convert.
|
||||
"""
|
||||
return self.from_dict(toml.loads(data))
|
||||
|
||||
def from_yaml(self, data: str):
|
||||
"""Convert output yaml of this class back into usable data and save it to this class.
|
||||
|
||||
Parameters:
|
||||
data: The yaml config data to convert.
|
||||
"""
|
||||
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
|
||||
|
||||
def as_x19(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an X19 device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
|
||||
"bitmain-fan-ctrl": False,
|
||||
"bitmain-fan-pwn": 100,
|
||||
}
|
||||
|
||||
if not self.temp_mode == "auto":
|
||||
cfg["bitmain-fan-ctrl"] = True
|
||||
|
||||
if self.fan_speed:
|
||||
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
|
||||
|
||||
return json.dumps(cfg)
|
||||
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an Avalonminer device.
|
||||
|
||||
Parameters:
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = self.pool_groups[0].as_avalon(user_suffix=user_suffix)
|
||||
return cfg
|
||||
|
||||
def as_bos(self, model: str = "S9", user_suffix: str = None) -> str:
|
||||
"""Convert the data in this class to a config usable by an BOSMiner device.
|
||||
|
||||
Parameters:
|
||||
model: The model of the miner to be used in the format portion of the config.
|
||||
user_suffix: The suffix to append to username.
|
||||
"""
|
||||
cfg = {
|
||||
"format": {
|
||||
"version": "1.2+",
|
||||
"model": f"Antminer {model}",
|
||||
"generator": "Upstream Config Utility",
|
||||
"timestamp": int(time.time()),
|
||||
},
|
||||
"group": [
|
||||
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
|
||||
],
|
||||
"temp_control": {
|
||||
"mode": self.temp_mode,
|
||||
"target_temp": self.temp_target,
|
||||
"hot_temp": self.temp_hot,
|
||||
"dangerous_temp": self.temp_dangerous,
|
||||
},
|
||||
}
|
||||
|
||||
if self.autotuning_enabled or self.autotuning_wattage:
|
||||
cfg["autotuning"] = {}
|
||||
if self.autotuning_enabled:
|
||||
cfg["autotuning"]["enabled"] = self.autotuning_enabled
|
||||
if self.autotuning_wattage:
|
||||
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
|
||||
|
||||
if self.asicboost:
|
||||
cfg["hash_chain_global"] = {}
|
||||
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
|
||||
|
||||
if any(
|
||||
[
|
||||
getattr(self, item)
|
||||
for item in [
|
||||
"dps_enabled",
|
||||
"dps_power_step",
|
||||
"dps_min_power",
|
||||
"dps_shutdown_enabled",
|
||||
"dps_shutdown_duration",
|
||||
]
|
||||
]
|
||||
):
|
||||
cfg["power_scaling"] = {}
|
||||
if self.dps_enabled:
|
||||
cfg["power_scaling"]["enabled"] = self.dps_enabled
|
||||
if self.dps_power_step:
|
||||
cfg["power_scaling"]["power_step"] = self.dps_power_step
|
||||
if self.dps_min_power:
|
||||
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
|
||||
if self.dps_shutdown_enabled:
|
||||
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
|
||||
if self.dps_shutdown_duration:
|
||||
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
|
||||
|
||||
return toml.dumps(cfg)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user