Compare commits

...

14 Commits

Author SHA1 Message Date
UpstreamData
595467487b fixed a bug with the way the hashrate total works when getting new data on a small subset of miners 2022-01-05 09:42:37 -07:00
UpstreamData
8cba08a900 removed "Identifying..." from scanning 2022-01-05 09:31:09 -07:00
UpstreamData
89c009ab11 improved the functionality of the scan 2022-01-05 09:27:31 -07:00
UpstreamData
38f93fa212 improved the functionality of get data greatly 2022-01-05 09:17:45 -07:00
UpstreamData
eac2d64468 changed the 2 listboxes with IPs and data into a table, and fixed all functions using this 2022-01-05 08:59:38 -07:00
UpstreamData
8a2cef15b2 fixed a bug with the build because importing from passlib is buggy 2022-01-04 10:23:08 -07:00
UpstreamData
c075f3f66a added more doocstrings and improved the readme 2022-01-04 09:16:17 -07:00
UpstreamData
d138778f0a added BTMiner docstrings 2022-01-04 09:01:38 -07:00
UpstreamData
cf3aefc201 updated btminer API to use cryptography instead of pycryptodome because it's painful to set up, and updated requirements.txt 2022-01-03 16:18:57 -07:00
UpstreamData
d974be5329 finished bosminer docs 2022-01-03 15:17:09 -07:00
UpstreamData
8c147283ba fixed a bug with sending commands which led to a pattern of recursive commands blocking the program forever 2022-01-03 15:13:33 -07:00
UpstreamData
f72ba6582d added docstrings for CGMiner API, and improved BMMiner docstrings 2022-01-03 13:44:15 -07:00
UpstreamData
b65badf097 improved the build process and added a readme that gets added into the cfg util and its builds 2022-01-03 13:17:32 -07:00
UpstreamData
cea71d8ca1 added a new window to generate configs 2022-01-03 13:16:38 -07:00
15 changed files with 1005 additions and 153 deletions

View File

@@ -53,7 +53,22 @@ class BaseMinerAPI:
# standard multicommand format is "command1+command2"
# doesnt work for S19 which is dealt with in the send command function
command = "+".join(commands)
return await self.send_command(command)
data = None
try:
data = await self.send_command(command)
except APIError:
try:
data = {}
# S19 handler, try again
for cmd in command.split("+"):
data[cmd] = []
data[cmd].append(await self.send_command(cmd))
except APIError as e:
raise APIError(e)
except Exception as e:
print(e)
if data:
return data
async def send_command(self, command: str, parameters: str or int or bool = None) -> dict:
"""Send an API command to the miner and return the result."""
@@ -95,18 +110,6 @@ class BaseMinerAPI:
await writer.wait_closed()
# validate the command suceeded
# also handle for S19 not liking "command1+command2" format
if not self.validate_command_output(data):
try:
data = {}
# S19 handler, try again
for cmd in command.split("+"):
data[cmd] = []
data[cmd].append(await self.send_command(cmd))
except Exception as e:
print(e)
# check again after second try
if not self.validate_command_output(data):
raise APIError(data["STATUS"][0]["Msg"])

View File

@@ -29,7 +29,7 @@ class BMMinerAPI(BaseMinerAPI):
"""
API 'config' command.
Returns some miner configuration information:
Returns a dict containing some miner configuration information:
ASC Count <- the number of ASCs
PGA Count <- the number of PGAs
Pool Count <- the number of Pools
@@ -446,7 +446,6 @@ class BMMinerAPI(BaseMinerAPI):
Parameters:
n: the number of the ASC to disable.
"""
return await self.send_command("ascdisable", parameters=n)

View File

@@ -2,49 +2,146 @@ from API import BaseMinerAPI
class BOSMinerAPI(BaseMinerAPI):
"""
A class that abstracts the BOSMiner API in the miners.
Each method corresponds to an API command in BOSMiner.
BOSMiner API documentation:
https://docs.braiins.com/os/plus-en/Development/1_api.html
Parameters:
ip: the IP address of the miner.
port (optional): the port of the API on the miner (standard is 4028)
"""
def __init__(self, ip, port=4028):
super().__init__(ip, port)
async def asccount(self) -> dict:
"""
API 'asccount' command.
Returns a dict containing the number of ASC devices.
"""
return await self.send_command("asccount")
async def asc(self, n: int) -> dict:
"""
API 'asc' command.
Returns a dict containing the details of a single ASC of number N.
n: the ASC device to get details of.
"""
return await self.send_command("asc", parameters=n)
async def devdetails(self) -> dict:
"""
API 'devdetails' command.
Returns a dict containing all devices with their static details.
"""
return await self.send_command("devdetails")
async def devs(self) -> dict:
"""
API 'devs' command.
Returns a dict containing each PGA/ASC with their details.
"""
return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict:
"""
API 'edevs' command.
Returns a dict containing each PGA/ASC with their details,
ignoring blacklisted devices and zombie devices.
Parameters:
old (optional): include zombie devices that became zombies less than 'old' seconds ago
"""
if old:
return await self.send_command("edevs", parameters="old")
else:
return await self.send_command("edevs")
async def pools(self) -> dict:
"""
API 'pools' command.
Returns a dict containing the status of each pool.
"""
return await self.send_command("pools")
async def summary(self) -> dict:
"""
API 'summary' command.
Returns a dict containing the status summary of the miner.
"""
return await self.send_command("summary")
async def stats(self) -> dict:
"""
API 'stats' command.
Returns a dict containing stats for all device/pool with more than 1 getwork.
"""
return await self.send_command("stats")
async def version(self) -> dict:
"""
API 'version' command.
Returns a dict containing version information.
"""
return await self.send_command("version")
async def estats(self) -> dict:
"""
API 'estats' command.
Returns a dict containing stats for all device/pool with more than 1 getwork,
ignoring zombie devices.
Parameters:
old (optional): include zombie devices that became zombies less than 'old' seconds ago.
"""
return await self.send_command("estats")
async def check(self) -> dict:
return await self.send_command("check")
async def check(self, command: str) -> dict:
"""
API 'check' command.
Returns information about a command:
Exists (Y/N) <- the command exists in this version
Access (Y/N) <- you have access to use the command
Parameters:
command: the command to get information about.
"""
return await self.send_command("check", parameters=command)
async def coin(self) -> dict:
"""
API 'coin' command.
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
"""
return await self.send_command("coin")
async def lcd(self) -> dict:
"""
API 'lcd' command.
Returns a dict containing an all in one status summary of the miner.
"""
return await self.send_command("lcd")
async def switchpool(self, n: int) -> dict:
@@ -73,19 +170,53 @@ class BOSMinerAPI(BaseMinerAPI):
# return await self.send_command("removepool", parameters=n)
async def fans(self) -> dict:
"""
API 'fans' command.
Returns a dict containing information on fans and fan speeds.
"""
return await self.send_command("fans")
async def tempctrl(self) -> dict:
"""
API 'tempctrl' command.
Returns a dict containing temp control configuration.
"""
return await self.send_command("tempctrl")
async def temps(self) -> dict:
"""
API 'temps' command.
Returns a dict containing temperature information.
"""
return await self.send_command("temps")
async def tunerstatus(self) -> dict:
"""
API 'tunerstatus' command.
Returns a dict containing tuning stats.
"""
return await self.send_command("tunerstatus")
async def pause(self) -> dict:
"""
API 'pause' command.
Pauses mining and stops power consumption and waits for resume command.
Returns a dict stating that the miner paused mining.
"""
return await self.send_command("pause")
async def resume(self) -> dict:
"""
API 'pause' command.
Resumes mining on the miner.
Returns a dict stating that the miner resumed mining.
"""
return await self.send_command("resume")

View File

@@ -1,12 +1,12 @@
from API import BaseMinerAPI, APIError
from passlib.hash import md5_crypt
from passlib.handlers import md5_crypt
import asyncio
import re
import json
import hashlib
import binascii
from Crypto.Cipher import AES
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
import base64
@@ -42,13 +42,19 @@ def _add_to_16(s: str) -> bytes:
def parse_btminer_priviledge_data(token_data, data):
# get the encoded data from the dict
enc_data = data['enc']
# get the aes key from the token data
aeskey = hashlib.sha256(token_data['host_passwd_md5'].encode()).hexdigest()
# unhexlify the aes key
aeskey = binascii.unhexlify(aeskey.encode())
aes = AES.new(aeskey, AES.MODE_ECB)
ret_msg = json.loads(str(
aes.decrypt(base64.decodebytes(bytes(
enc_data, encoding='utf8'))).rstrip(b'\0').decode("utf8")))
# create the required decryptor
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
decryptor = aes.decryptor()
# decode the message with the decryptor
ret_msg = json.loads(decryptor.update(
base64.decodebytes(bytes(enc_data, encoding='utf8'))
).rstrip(b'\0').decode("utf8"))
return ret_msg
@@ -60,13 +66,12 @@ def create_privileged_cmd(token_data: dict, command: dict) -> bytes:
# unhexlify the encoded host_passwd
aeskey = binascii.unhexlify(aeskey.encode())
# create a new AES key
aes = AES.new(aeskey, AES.MODE_ECB)
aes = Cipher(algorithms.AES(aeskey), modes.ECB())
encryptor = aes.encryptor()
# dump the command to json
api_json_str = json.dumps(command)
# encode the json command with the aes key
api_json_str_enc = str(base64.encodebytes(
aes.encrypt(_add_to_16(api_json_str))),
encoding='utf8').replace('\n', '')
api_json_str_enc = base64.encodebytes(encryptor.update(_add_to_16(api_json_str))).decode("utf-8").replace("\n", "")
# label the data as being encoded
data_enc = {'enc': 1, 'data': api_json_str_enc}
# dump the labeled data to json
@@ -82,7 +87,9 @@ class BTMinerAPI(BaseMinerAPI):
async def send_command(self, command: str | bytes, **kwargs) -> dict:
"""Send an API command to the miner and return the result."""
# check if command is a string, if its bytes its encoded and needs to be send raw
if isinstance(command, str):
# if it is a string, put it into the standard command format
command = json.dumps({"command": command}).encode("utf-8")
try:
# get reader and writer streams
@@ -116,18 +123,27 @@ class BTMinerAPI(BaseMinerAPI):
writer.close()
await writer.wait_closed()
# check if th returned data is encoded
if 'enc' in data.keys():
# try to parse the encoded data
try:
data = parse_btminer_priviledge_data(self.current_token, data)
except Exception as e:
print(e)
# if it fails to validate, it is likely an error
if not self.validate_command_output(data):
raise APIError(data["Msg"])
# return the parsed json as a dict
return data
async def get_token(self):
"""
API 'get_token' command.
Returns an encoded token and md5 password, which are used for the privileged API.
"""
data = await self.send_command("get_token")
pwd = _crypt(self.admin_pwd, "$1$" + data["Msg"]["salt"] + '$')
pwd = pwd.split('$')
@@ -138,7 +154,7 @@ class BTMinerAPI(BaseMinerAPI):
self.current_token = {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
return {'host_sign': host_sign, 'host_passwd_md5': host_passwd_md5}
#### privileged COMMANDS ####
#### PRIVILEGED COMMANDS ####
# Please read the top of this file to learn
# how to configure the whatsminer API to
# use these commands.
@@ -147,8 +163,13 @@ class BTMinerAPI(BaseMinerAPI):
pool_1: str, worker_1: str, passwd_1: str,
pool_2: str = None, worker_2: str = None, passwd_2: str = None,
pool_3: str = None, worker_3: str = None, passwd_3: str = None):
# get the token and password from the miner
token_data = await self.get_token()
if pool_2 and pool_3:
# parse pool data
if not pool_1:
raise APIError("No pools set.")
elif pool_2 and pool_3:
command = {
"cmd": "update_pools",
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
@@ -166,16 +187,33 @@ class BTMinerAPI(BaseMinerAPI):
"cmd": "update_pools",
"pool1": pool_1, "worker1": worker_1, "passwd1": passwd_1,
}
# encode the command with the token data
enc_command = create_privileged_cmd(token_data, command)
# send the command
return await self.send_command(enc_command)
async def restart_btminer(self):
async def restart(self):
"""
API 'restart_btminer' command
Returns a reply informing of the restart and restarts BTMiner.
"""
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):
"""
API 'power_off' command.
Powers off the mining of the miner.
Returns info on the power off.
Parameters:
respbefore (optional): respond before powering off.
"""
if respbefore:
command = {"cmd": "power_off", "respbefore": "true"}
else:
@@ -185,24 +223,58 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_command(enc_command)
async def power_on(self):
"""
API 'power_on' command.
Powers on the mining of the miner.
Returns info on the power on.
"""
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):
"""
API 'reset_led' command.
Resets the LED flashing to normal.
Returns a confirmation of resetting the LED.
"""
command = {"cmd": "set_led", "param": "auto"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def set_led(self, color: str = "red", period: int = 2000, duration: int = 1000, start: int = 0):
"""
API 'set_led' command.
Sets the LED to do some pattern set with parameters.
Returns a confirmation of setting the LED.
Parameters:
color: 'red' or 'green'
period: flash cycle in ms
duration: led on time in the cycle in ms
start: led on time offset in the cycle in ms
"""
command = {"cmd": "set_led", "color": color, "period": period, "duration": duration, "start": start}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
return await self.send_command(enc_command)
async def set_low_power(self):
"""
API 'set_low_power' command.
Sets the miner to low power mode.
Returns the status of setting the miner to low power mode.
"""
command = {"cmd": "set_low_power"}
token_data = await self.get_token()
enc_command = create_privileged_cmd(token_data, command)
@@ -214,18 +286,43 @@ class BTMinerAPI(BaseMinerAPI):
return NotImplementedError
async def reboot(self):
"""
API 'reboot' command.
Reboots the miner.
Returns the status of the command then reboots.
"""
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):
"""
API 'factory_reset' command.
Resets the miner to factory defaults.
Returns the status of the command then resets.
"""
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):
"""
API 'update_pwd' command.
Updates the admin user's password.
Returns the status of setting the password to the new password.
Parameters:
old_pwd: the old admin password.
new_pwd: the new password to set. Max length of 8 bytes, using letters, numbers, and underscores.
"""
# check if password length is greater than 8 bytes
if len(new_pwd.encode('utf-8')) > 8:
return APIError(
@@ -236,6 +333,13 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_command(enc_command)
async def set_target_freq(self, percent: int):
"""
API 'set_target_freq' command.
Sets the frequency for the miner ot use.
Returns the status of setting the frequency.
"""
if not -10 < percent < 100:
return APIError(f"Frequency % is outside of the allowed range. Please set a % between -10 and 100")
command = {"cmd": "set_target_freq", "percent": str(percent)}
@@ -244,36 +348,82 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_command(enc_command)
async def enable_fast_boot(self):
"""
API 'enable_fast_boot' command.
Turns on the fast boot feature on the miner.
Returns the status of setting the fast boot to on.
"""
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):
"""
API 'disable'_fast_boot' command.
Turns off the fast boot feature on the miner.
Returns the status of setting the fast boot to off.
"""
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):
"""
API 'enable_web_pools' command.
Turns on the ability to change the pools through the web interface.
Returns the status of setting the web pools to enabled.
"""
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):
"""
API 'disable_web_pools' command.
Turns off the ability to change the pools through the web interface.
Returns the status of setting the web pools to disabled.
"""
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):
"""
API 'set_hostname' command.
Sets the hostname of the miner.
Returns the status of setting the hostname.
"""
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):
"""
API 'set_power_pct' command.
Sets the percent of power the miner should use.
Returns the status of setting the power usage to this percent.
Parameters:
percent: the percent to set the power usage to, between 0 and 100.
"""
if not 0 < percent < 100:
return APIError(f"Power PCT % is outside of the allowed range. Please set a % between 0 and 100")
command = {"cmd": "set_power_pct", "percent": str(percent)}
@@ -282,6 +432,18 @@ class BTMinerAPI(BaseMinerAPI):
return await self.send_command(enc_command)
async def pre_power_on(self, complete: bool, msg: str):
"""
API 'pre_power_on' command.
Preheats the miner for the 'power_on' command. Can also be used to query the status of pre powering on.
Returns status of pre powering on.
Parameters:
complete: check whether or not it is complete.
msg: the message to check. "wait for adjust temp" or "adjust complete" or "adjust continue"
"""
if not msg == "wait for adjust temp" or "adjust complete" or "adjust continue":
return APIError(
'Message is incorrect, please choose one of '
@@ -299,28 +461,88 @@ class BTMinerAPI(BaseMinerAPI):
#### END privileged COMMANDS ####
async def summary(self):
"""
API 'summary' command.
Returns a dict containing the status summary of the miner.
"""
return await self.send_command("summary")
async def pools(self):
"""
API 'pools' command.
Returns a dict containing the status of each pool.
"""
return await self.send_command("pools")
async def devs(self):
"""
API 'devs' command.
Returns a dict containing each PGA/ASC with their details.
"""
return await self.send_command("devs")
async def edevs(self):
"""
API 'edevs' command.
Returns a dict containing each PGA/ASC with their details,
ignoring blacklisted devices and zombie devices.
"""
return await self.send_command("edevs")
async def devdetails(self):
"""
API 'devdetails' command.
Returns a dict containing all devices with their static details.
"""
return await self.send_command("devdetails")
async def get_psu(self):
"""
API 'get_psu' command.
Returns a dict containing PSU and power information.
"""
return await self.send_command("get_psu")
async def version(self):
"""
API 'get_version' command.
Returns a dict containing version information.
"""
return await self.send_command("get_version")
async def status(self):
"""
API 'status' command.
Returns a dict containing BTMiner status and firmware version.
"""
return await self.send_command("status")
async def get_miner_info(self):
return await self.send_command("get_miner_info")
async def get_miner_info(self, info: str | list):
"""
API 'get_miner_info' command.
Returns a dict containing requested information.
Parameters:
info: the info that you want to get.
"ip",
"proto",
"netmask",
"gateway",
"dns",
"hostname",
"mac",
"ledstat".
"""
if isinstance(info, str):
return await self.send_command("get_miner_info", parameters=info)
else:
return await self.send_command("get_miner_info", parameters=f"{','.join([str(item) for item in info])}")

View File

@@ -2,6 +2,18 @@ from API import BaseMinerAPI
class CGMinerAPI(BaseMinerAPI):
"""
A class that abstracts the CGMiner API in the miners.
Each method corresponds to an API command in CGMiner.
CGMiner API documentation:
https://github.com/ckolivas/cgminer/blob/master/API-README
Parameters:
ip: the IP address of the miner.
port (optional): the port of the API on the miner (standard is 4028)
"""
def __init__(self, ip, port=4028):
super().__init__(ip, port)
@@ -29,139 +41,492 @@ class CGMinerAPI(BaseMinerAPI):
return await self.send_command("config")
async def summary(self) -> dict:
"""
API 'summary' command.
Returns a dict containing the status summary of the miner.
"""
return await self.send_command("summary")
async def pools(self) -> dict:
"""
API 'pools' command.
Returns a dict containing the status of each pool.
"""
return await self.send_command("pools")
async def devs(self) -> dict:
"""
API 'devs' command.
Returns a dict containing each PGA/ASC with their details.
"""
return await self.send_command("devs")
async def edevs(self, old: bool = False) -> dict:
"""
API 'edevs' command.
Returns a dict containing each PGA/ASC with their details,
ignoring blacklisted devices and zombie devices.
Parameters:
old (optional): include zombie devices that became zombies less than 'old' seconds ago
"""
if old:
return await self.send_command("edevs", parameters="old")
else:
return await self.send_command("edevs")
async def pga(self, n: int) -> dict:
"""
API 'pga' command.
Returns a dict containing the details of a single PGA of number N.
Parameters:
n: the number of the PGA to get details of.
"""
return await self.send_command("pga", parameters=n)
async def pgacount(self) -> dict:
"""
API 'pgacount' command.
Returns a dict containing the number of PGA devices.
"""
return await self.send_command("pgacount")
async def switchpool(self, n: int) -> dict:
"""
API 'switchpool' command.
Returns the STATUS section with the results of switching pools.
Parameters:
n: the number of the pool to switch to.
"""
return await self.send_command("switchpool", parameters=n)
async def enablepool(self, n: int) -> dict:
"""
API 'enablepool' command.
Returns the STATUS section with the results of enabling the pool.
Parameters:
n: the number of the pool to enable.
"""
return await self.send_command("enablepool", parameters=n)
async def addpool(self, url: str, username: str, password: str) -> dict:
"""
API 'addpool' command.
Returns the STATUS section with the results of adding the pool.
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.
"""
return await self.send_command("addpool", parameters=f"{url}, {username}, {password}")
async def poolpriority(self, *n: int) -> dict:
"""
API 'poolpriority' command.
Returns the STATUS section with the results of setting pool priority.
Parameters:
n: pool numbers in order of priority.
"""
return await self.send_command("poolpriority", parameters=f"{','.join([str(item) for item in n])}")
async def poolquota(self, n: int, q: int) -> dict:
"""
API 'poolquota' command.
Returns the STATUS section with the results of setting pool quota.
Parameters:
n: pool number to set quota on.
q: quota to set the pool to.
"""
return await self.send_command("poolquota", parameters=f"{n}, {q}")
async def disablepool(self, n: int) -> dict:
"""
API 'disablepool' command.
Returns the STATUS section with the results of disabling the pool.
Parameters:
n: the number of the pool to disable.
"""
return await self.send_command("disablepool", parameters=n)
async def removepool(self, n: int) -> dict:
"""
API 'removepool' command.
Returns the STATUS section with the results of removing the pool.
Parameters:
n: the number of the pool to remove.
"""
return await self.send_command("removepool", parameters=n)
async def save(self, filename: str = None) -> dict:
"""
API 'save' command.
Returns the STATUS section with the results of saving the config file..
Parameters:
filename (optional): the filename to save the config as.
"""
if filename:
return await self.send_command("save", parameters=filename)
else:
return await self.send_command("save")
async def quit(self) -> dict:
"""
API 'quit' command.
Returns a single "BYE" before CGMiner quits.
"""
return await self.send_command("quit")
async def notify(self) -> dict:
"""
API 'notify' command.
Returns a dict containing the last status and count of each devices problem(s).
"""
return await self.send_command("notify")
async def privileged(self) -> dict:
"""
API 'privileged' command.
Returns the STATUS section with an error if you have no privileged access.
"""
return await self.send_command("privileged")
async def pgaenable(self, n: int) -> dict:
"""
API 'pgaenable' command.
Returns the STATUS section with the results of enabling the PGA device N.
Parameters:
n: the number of the PGA to enable.
"""
return await self.send_command("pgaenable", parameters=n)
async def pgadisable(self, n: int) -> dict:
"""
API 'pgadisable' command.
Returns the STATUS section with the results of disabling the PGA device N.
Parameters:
n: the number of the PGA to disable.
"""
return await self.send_command("pgadisable", parameters=n)
async def pgaidentify(self, n: int) -> dict:
"""
API 'pgaidentify' command.
Returns the STATUS section with the results of identifying the PGA device N.
Parameters:
n: the number of the PGA to identify.
"""
return await self.send_command("pgaidentify", parameters=n)
async def devdetails(self) -> dict:
"""
API 'devdetails' command.
Returns a dict containing all devices with their static details.
"""
return await self.send_command("devdetails")
async def restart(self) -> dict:
"""
API 'restart' command.
Returns a single "RESTART" before CGMiner restarts.
"""
return await self.send_command("restart")
async def stats(self) -> dict:
"""
API 'stats' command.
Returns a dict containing stats for all device/pool with more than 1 getwork.
"""
return await self.send_command("stats")
async def estats(self, old: bool = False) -> dict:
"""
API 'estats' command.
Returns a dict containing stats for all device/pool with more than 1 getwork,
ignoring zombie devices.
Parameters:
old (optional): include zombie devices that became zombies less than 'old' seconds ago.
"""
if old:
return await self.send_command("estats", parameters="old")
else:
return await self.send_command("estats")
async def check(self, command) -> dict:
"""
API 'check' command.
Returns information about a command:
Exists (Y/N) <- the command exists in this version
Access (Y/N) <- you have access to use the command
Parameters:
command: the command to get information about.
"""
return await self.send_command("check", parameters=command)
async def failover_only(self, failover: bool) -> dict:
"""
API 'failover-only' command.
Returns the STATUS section with what failover-only was set to.
Parameters:
failover: what to set failover-only to.
"""
return await self.send_command("failover-only", parameters=failover)
async def coin(self) -> dict:
"""
API 'coin' command.
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
"""
return await self.send_command("coin")
async def debug(self, setting: str) -> dict:
"""
API 'debug' command.
Returns which debug setting was enabled or disabled.
Parameters:
setting: which setting to switch to. Options are:
Silent,
Quiet,
Verbose,
Debug,
RPCProto,
PerDevice,
WorkTime,
Normal.
"""
return await self.send_command("debug", parameters=setting)
async def setconfig(self, name: str, n: int) -> dict:
"""
API 'setconfig' command.
Returns the STATUS section with the results of setting 'name' to N.
Parameters:
name: name of the config setting to set. Options are:
queue,
scantime,
expiry.
n: the value to set the 'name' setting to.
"""
return await self.send_command("setconfig", parameters=f"{name}, {n}")
async def usbstats(self) -> dict:
"""
API 'usbstats' command.
Returns a dict containing 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:
"""
API 'pgaset' command.
Returns the STATUS section with the results of setting PGA N with opt[,val].
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.
Options:
MMQ -
opt: clock
val: 160 - 230 (multiple of 2)
CMR -
opt: clock
val: 100 - 220
"""
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, value: bool) -> dict:
return await self.send_command("zero", parameters=f"{which}, {value}")
async def zero(self, which: str, summary: bool) -> dict:
"""
API 'zero' command.
Returns the STATUS section with info on the zero and optional 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.
"""
return await self.send_command("zero", parameters=f"{which}, {summary}")
async def hotplug(self, n: int) -> dict:
"""
API 'hotplug' command.
Returns the STATUS section with whether or not hotplug was enabled.
"""
return await self.send_command("hotplug", parameters=n)
async def asc(self, n: int) -> dict:
"""
API 'asc' command.
Returns a dict containing the details of a single ASC of number N.
n: the ASC device to get details of.
"""
return await self.send_command("asc", parameters=n)
async def ascenable(self, n: int) -> dict:
"""
API 'ascenable' command.
Returns the STATUS section with the results of enabling the ASC device N.
Parameters:
n: the number of the ASC to enable.
"""
return await self.send_command("ascenable", parameters=n)
async def ascdisable(self, n: int) -> dict:
"""
API 'ascdisable' command.
Returns the STATUS section with the results of disabling the ASC device N.
Parameters:
n: the number of the ASC to disable.
"""
return await self.send_command("ascdisable", parameters=n)
async def ascidentify(self, n: int) -> dict:
"""
API 'ascidentify' command.
Returns the STATUS section with the results of identifying the ASC device N.
Parameters:
n: the number of the PGA to identify.
"""
return await self.send_command("ascidentify", parameters=n)
async def asccount(self) -> dict:
"""
API 'asccount' command.
Returns a dict containing the number of ASC devices.
"""
return await self.send_command("asccount")
async def ascset(self, n: int, opt: str, val: int = None) -> dict:
"""
API 'ascset' command.
Returns the STATUS section with the results of setting ASC N with opt[,val].
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.
Options:
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
"""
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:
"""
API 'lcd' command.
Returns a dict containing an all in one status summary of the miner.
"""
return await self.send_command("lcd")
async def lockstats(self) -> dict:
"""
API 'lockstats' command.
Returns the STATUS section with the result of writing the lock stats to STDERR.
"""
return await self.send_command("lockstats")

44
CFG-Util-README.md Normal file
View File

@@ -0,0 +1,44 @@
# CFG-Util
## Interact with bitcoin mining ASICs using a simple GUI.
---
## Input Fields
### Network IP:
* Defaults to 192.168.1.0/24 (192.168.1.0 - 192.168.1.255)
* Enter any IP on your local network and it will automatically load your entire network with a /24 subnet (255 IP addresses)
* You can also add a subnet mask by adding a / after the IP and entering the subnet mask
* Press Scan to scan the selected network for miners
### IP List File:
* Use the Browse button to select a file
* Use the Import button to import all IP addresses from a file, regardless of where they are located in the file
* Use the Export button to export all IP addresses (or all selected IP addresses if you select some) to a file, with each seperated by a new line
### Config File:
* Use the Browse button to select a file
* Use the Import button to import the config file (only toml format is implemented right now)
* Use the Export button to export the config file in toml format
---
## Data Fields
### IP List:
* This field contains all the IP addresses of miners that were either imported from a file or scanned
* Select one by clicking, mutiple by holding CTRL and clicking, and select all between 2 chosen miners by holding SHIFT as you select them
* Use the ALL button to select all IP addresses in the field, or unselect all if they are selected
### Data:
* This field contains all data that is collected by selecting IP addresses and hitting GET
* The GET button gets data on all selected IP addresses
* The SORT IP button sorts the data list by IP address, as well as the IP List
* The SORT HR button sorts the data list by hashrate, as well as the IP List
* The SORT USER button sorts the data list by pool username, as well as the IP List
* The SORT W button sorts the data list by wattage, as well as the IP List
### Config:
* This field contains the configuration file either imported from a miner or from a file
* The IMPORT button imports the configuration file from any 1 selected miner to the config textbox
* The CONFIG button configures all selected miners with the config in the config textbox
* The LIGHT button turns on the fault light/locator light on miners that support it (Only BraiinsOS for now)
* The GENERATE button generates a new basic config in the config textbox

View File

@@ -21,6 +21,21 @@ if __name__ == '__main__':
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.
### 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
# need to import these 2 libraries, you need asyncio anyway so make sure you have sys imported
import sys
import asyncio
# 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())
```
##### It is likely a good idea to use this code in your program anyway to be preventative.
<br>
To write your own custom programs with this repo, you have many options.

View File

@@ -1,21 +1,17 @@
import asyncio
import ipaddress
import os
import re
import time
from operator import itemgetter
import asyncio
import aiofiles
import toml
from cfg_util.miner_factory import miner_factory
from cfg_util.layout import window
from cfg_util.func.data import safe_parse_api_data
from config.bos import bos_config_convert, general_config_convert_bos
from API import APIError
from cfg_util.func.data import safe_parse_api_data
from cfg_util.layout import window
from cfg_util.miner_factory import miner_factory
from config.bos import bos_config_convert, general_config_convert_bos
from settings import CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS
@@ -36,10 +32,12 @@ async def update_prog_bar(amount):
async def set_progress_bar_len(amount):
window["progress"].Update(0, max=amount)
window["progress"].maxlen = amount
window["progress_percent"].Update("0.0 %")
async def scan_network(network):
await update_ui_with_data("status", "Scanning")
await update_ui_with_data("hr_total", "")
network_size = len(network)
miner_generator = network.scan_network_generator()
await set_progress_bar_len(2 * network_size)
@@ -48,6 +46,10 @@ async def scan_network(network):
async for miner in miner_generator:
if miner:
miners.append(miner)
# can output "Identifying" for each found item, but it gets a bit cluttered
# and could possibly be confusing for the end user because of timing on
# adding the IPs
# window["ip_table"].update([["Identifying...", "", "", "", ""] for miner in miners])
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
progress_bar_len += network_size - len(miners)
@@ -56,10 +58,10 @@ async def scan_network(network):
all_miners = []
async for found_miner in get_miner_genenerator:
all_miners.append(found_miner)
all_miners.sort(key=lambda x: x.ip)
window["ip_table"].update([[str(miner.ip), "", "", "", ""] for miner in all_miners])
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
all_miners.sort(key=lambda x: x.ip)
window["ip_list"].update([str(miner.ip) for miner in all_miners])
await update_ui_with_data("ip_count", str(len(all_miners)))
await update_ui_with_data("status", "")
@@ -69,21 +71,24 @@ async def miner_light(ips: list):
async def flip_light(ip):
listbox = window['ip_list'].Widget
ip_list = window['ip_table'].Widget
miner = await miner_factory.get_miner(ip)
if ip in window["ip_list"].Values:
index = window["ip_list"].Values.index(ip)
if listbox.itemcget(index, "background") == 'red':
listbox.itemconfigure(index, bg='#f0f3f7', fg='#000000')
await miner.fault_light_off()
else:
listbox.itemconfigure(index, bg='red', fg='white')
await miner.fault_light_on()
index = [item[0] for item in window["ip_table"].Values].index(ip)
index_tags = ip_list.item(index)['tags']
if "light" not in index_tags:
ip_list.item(index, tags=([*index_tags, "light"]))
window['ip_table'].update(row_colors=[(index, "white", "red")])
await miner.fault_light_on()
else:
index_tags.remove("light")
ip_list.item(index, tags=index_tags)
window['ip_table'].update(row_colors=[(index, "black", "white")])
await miner.fault_light_off()
async def import_config(ip):
async def import_config(idx):
await update_ui_with_data("status", "Importing")
miner = await miner_factory.get_miner(ipaddress.ip_address(*ip))
miner = await miner_factory.get_miner(ipaddress.ip_address(window["ip_table"].Values[idx[0]][0]))
await miner.get_config()
config = miner.config
await update_ui_with_data("config", str(config))
@@ -104,7 +109,7 @@ async def import_iplist(file_location):
if ip not in ip_list:
ip_list.append(ipaddress.ip_address(ip))
ip_list.sort()
window["ip_list"].update([str(ip) for ip in ip_list])
window["ip_table"].update([[str(ip), "", "", "", ""] for ip in ip_list])
await update_ui_with_data("ip_count", str(len(ip_list)))
await update_ui_with_data("status", "")
@@ -120,8 +125,8 @@ async def export_iplist(file_location, ip_list_selected):
await file.write(str(item) + "\n")
else:
async with aiofiles.open(file_location, mode='w') as file:
for item in window['ip_list'].Values:
await file.write(str(item) + "\n")
for item in window['ip_table'].Values:
await file.write(str(item[0]) + "\n")
await update_ui_with_data("status", "")
@@ -183,27 +188,28 @@ async def export_config_file(file_location, config):
async def get_data(ip_list: list):
await update_ui_with_data("status", "Getting Data")
ips = [ipaddress.ip_address(ip) for ip in ip_list]
if len(ips) == 0:
ips = [ipaddress.ip_address(ip) for ip in [item[0] for item in window["ip_table"].Values]]
await set_progress_bar_len(len(ips))
progress_bar_len = 0
data_gen = asyncio.as_completed([get_formatted_data(miner) for miner in ips])
miner_data = []
ip_table_data = window["ip_table"].Values
ordered_all_ips = [item[0] for item in ip_table_data]
for all_data in data_gen:
miner_data.append(await all_data)
data_point = await all_data
if data_point["IP"] in ordered_all_ips:
ip_table_index = ordered_all_ips.index(data_point["IP"])
ip_table_data[ip_table_index] = [
data_point["IP"], data_point["host"], str(data_point['TH/s']) + " TH/s", data_point['user'], str(data_point['wattage']) + " W"
]
window["ip_table"].update(ip_table_data)
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
miner_data.sort(key=lambda x: ipaddress.ip_address(x['IP']))
total_hr = round(sum(d.get('TH/s', 0) for d in miner_data), 2)
hashrate_list = [float(item[2].replace(" TH/s", "")) for item in window["ip_table"].Values]
total_hr = round(sum(hashrate_list), 2)
window["hr_total"].update(f"{total_hr} TH/s")
window["hr_list"].update(disabled=False)
window["hr_list"].update([item['IP'] + " | "
+ item['host'] + " | "
+ str(item['TH/s']) + " TH/s | "
+ item['user'] + " | "
+ str(item['wattage']) + " W"
for item in miner_data])
window["hr_list"].update(disabled=True)
await update_ui_with_data("status", "")
@@ -224,7 +230,8 @@ async def get_formatted_data(ip: ipaddress.ip_address):
th5s = round(await safe_parse_api_data(miner_data, 'summary', 0, 'SUMMARY', 0, 'MHS 5s') / 1000000, 2)
elif 'GHS 5s' in miner_data['summary'][0]['SUMMARY'][0].keys():
if not miner_data['summary'][0]['SUMMARY'][0]['GHS 5s'] == "":
th5s = round(float(await safe_parse_api_data(miner_data, 'summary', 0, 'SUMMARY', 0, 'GHS 5s')) / 1000, 2)
th5s = round(float(await safe_parse_api_data(miner_data, 'summary', 0, 'SUMMARY', 0, 'GHS 5s')) / 1000,
2)
else:
th5s = 0
else:
@@ -240,21 +247,37 @@ async def get_formatted_data(ip: ipaddress.ip_address):
return {'TH/s': th5s, 'IP': str(miner.ip), 'host': host, 'user': user, 'wattage': wattage}
async def generate_config():
async def generate_config(username, workername, v2_allowed):
if username and workername:
user = f"{username}.{workername}"
elif username and not workername:
user = username
else:
return
if v2_allowed:
url_1 = 'stratum2+tcp://v2.us-east.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt'
url_2 = 'stratum2+tcp://v2.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt'
url_3 = 'stratum+tcp://stratum.slushpool.com:3333'
else:
url_1 = 'stratum+tcp://ca.stratum.slushpool.com:3333'
url_2 = 'stratum+tcp://us-east.stratum.slushpool.com:3333'
url_3 = 'stratum+tcp://stratum.slushpool.com:3333'
config = {'group': [{
'name': 'group',
'quota': 1,
'pool': [{
'url': 'stratum2+tcp://us-east.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt',
'user': 'UpstreamDataInc.test',
'url': url_1,
'user': user,
'password': '123'
}, {
'url': 'stratum2+tcp://stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt',
'user': 'UpstreamDataInc.test',
'url': url_2,
'user': user,
'password': '123'
}, {
'url': 'stratum+tcp://stratum.slushpool.com:3333',
'user': 'UpstreamDataInc.test',
'url': url_3,
'user': user,
'password': '123'
}]
}],
@@ -279,45 +302,23 @@ async def generate_config():
async def sort_data(index: int or str):
await update_ui_with_data("status", "Sorting Data")
data_list = window['hr_list'].Values
new_list = []
indexes = {}
for item in data_list:
item_data = [part.strip() for part in item.split("|")]
for idx, part in enumerate(item_data):
if re.match("[0-9]* W", part):
item_data[idx] = item_data[idx].replace(" W", "")
indexes['wattage'] = idx
elif re.match("[0-9]*\.?[0-9]* TH\/s", part):
item_data[idx] = item_data[idx].replace(" TH/s", "")
indexes['hr'] = idx
elif re.match("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)", part):
item_data[idx] = ipaddress.ip_address(item_data[idx])
indexes['ip'] = idx
new_list.append(item_data)
if not isinstance(index, str):
if index == indexes['hr']:
new_data_list = sorted(new_list, key=lambda x: float(x[index]))
else:
new_data_list = sorted(new_list, key=itemgetter(index))
data_list = window['ip_table'].Values
# wattage
if re.match("[0-9]* W", data_list[0][index]):
new_list = sorted(data_list, key=lambda x: int(x[index].replace(" W", "")))
# hashrate
elif re.match("[0-9]*\.?[0-9]* TH\/s", data_list[0][index]):
new_list = sorted(data_list, key=lambda x: float(x[index].replace(" TH/s", "")))
# ip addresses
elif re.match("^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)",
data_list[0][index]):
new_list = sorted(data_list, key=lambda x: ipaddress.ip_address(x[index]))
# everything else, hostname and user
else:
if index.lower() not in indexes.keys():
return
elif index.lower() == 'hr':
new_data_list = sorted(new_list, key=lambda x: float(x[indexes[index]]))
else:
new_data_list = sorted(new_list, key=itemgetter(indexes[index]))
new_ip_list = []
for item in new_data_list:
new_ip_list.append(item[indexes['ip']])
new_data_list = [str(item[indexes['ip']]) + " | "
+ item[1] + " | "
+ item[indexes['hr']] + " TH/s | "
+ item[3] + " | "
+ str(item[indexes['wattage']]) + " W"
for item in new_data_list]
window["hr_list"].update(disabled=False)
window["hr_list"].update(new_data_list)
window['ip_list'].update(new_ip_list)
window["hr_list"].update(disabled=True)
new_list = sorted(data_list, key=lambda x: x[index])
await update_ui_with_data("ip_table", new_list)
await update_ui_with_data("status", "")

File diff suppressed because one or more lines are too long

View File

@@ -1,3 +1,7 @@
"""
This file stores the MinerFactory instance used by the ConfigUtility for use in other files.
"""
from miners.miner_factory import MinerFactory
miner_factory = MinerFactory()

View File

@@ -1,7 +1,8 @@
import asyncio
import sys
import PySimpleGUI as sg
from cfg_util.layout import window
from cfg_util.layout import window, generate_config_layout
from cfg_util.func import scan_network, sort_data, send_config, miner_light, get_data, export_config_file, \
generate_config, import_config, import_iplist, import_config_file, export_iplist
@@ -11,7 +12,7 @@ from network import MinerNetwork
async def ui():
while True:
event, value = window.read(timeout=10)
if event in (None, 'Close'):
if event in (None, 'Close', sg.WIN_CLOSED):
sys.exit()
if event == 'scan':
if len(value['miner_network'].split("/")) > 1:
@@ -21,36 +22,51 @@ async def ui():
miner_network = MinerNetwork(value['miner_network'])
asyncio.create_task(scan_network(miner_network))
if event == 'select_all_ips':
if value['ip_list'] == window['ip_list'].Values:
window['ip_list'].set_value([])
if len(value["ip_table"]) == len(window["ip_table"].Values):
window["ip_table"].update(select_rows=())
else:
window['ip_list'].set_value(window['ip_list'].Values)
window["ip_table"].update(select_rows=([row for row in range(len(window["ip_table"].Values))]))
if event == 'import_config':
if 2 > len(value['ip_list']) > 0:
asyncio.create_task(import_config(value['ip_list']))
if 2 > len(value['ip_table']) > 0:
asyncio.create_task(import_config(value['ip_table']))
if event == 'light':
asyncio.create_task(miner_light(value['ip_list']))
asyncio.create_task(miner_light([window['ip_table'].Values[item][0] for item in value['ip_table']]))
if event == "import_iplist":
asyncio.create_task(import_iplist(value["file_iplist"]))
if event == "export_iplist":
asyncio.create_task(export_iplist(value["file_iplist"], value['ip_list']))
asyncio.create_task(export_iplist(value["file_iplist"], [window['ip_table'].Values[item][0] for item in value['ip_table']]))
if event == "send_config":
asyncio.create_task(send_config(value['ip_list'], value['config']))
asyncio.create_task(send_config([window['ip_table'].Values[item][0] for item in value['ip_table']], value['config']))
if event == "import_file_config":
asyncio.create_task(import_config_file(value['file_config']))
if event == "export_file_config":
asyncio.create_task(export_config_file(value['file_config'], value["config"]))
if event == "get_data":
asyncio.create_task(get_data(value['ip_list']))
asyncio.create_task(get_data([window["ip_table"].Values[item][0] for item in value["ip_table"]]))
if event == "generate_config":
asyncio.create_task(generate_config())
await generate_config_ui()
if event == "sort_data_ip":
asyncio.create_task(sort_data('ip'))
asyncio.create_task(sort_data(0)) # ip index in table
if event == "sort_data_hr":
asyncio.create_task(sort_data('hr'))
asyncio.create_task(sort_data(2)) # HR index in table
if event == "sort_data_user":
asyncio.create_task(sort_data(3))
asyncio.create_task(sort_data(3)) # user index in table
if event == "sort_data_w":
asyncio.create_task(sort_data('wattage'))
asyncio.create_task(sort_data(4)) # wattage index in table
if event == "__TIMEOUT__":
await asyncio.sleep(0)
async def generate_config_ui():
generate_config_window = sg.Window("Generate Config", generate_config_layout(), modal=True)
while True:
event, values = generate_config_window.read()
if event in (None, 'Close', sg.WIN_CLOSED):
break
if event == "generate_config_window_generate":
if values['generate_config_window_username']:
await generate_config(values['generate_config_window_username'],
values['generate_config_window_workername'],
values['generate_config_window_allow_v2'])
generate_config_window.close()
break

View File

@@ -8,7 +8,6 @@ The build will show up in the build directory.
import datetime
import sys
import os
from cx_Freeze import setup, Executable
base = None
@@ -18,9 +17,15 @@ if sys.platform == "win32":
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}\\"}},
options={"build_exe": {"build_exe": f"{os.getcwd()}\\build\\UpstreamCFGUtil-{version}-{sys.platform}\\",
"include_files": [os.path.join(os.getcwd(), "settings.toml"),
os.path.join(os.getcwd(), "CFG-Util-README.md")],
},
},
executables=[Executable("config_tool.py", base=base, icon="icon.ico", target_name="UpstreamCFGUtil.exe")]
)

View File

@@ -16,6 +16,14 @@ class MinerFactory:
self.miners = {}
async def get_miner_generator(self, ips: list):
"""
Get Miner objects from ip addresses using an async generator.
Returns an asynchronous generator containing Miners.
Parameters:
ips: a list of ip addresses to get miners for.
"""
loop = asyncio.get_event_loop()
scan_tasks = []
for miner in ips:

View File

@@ -31,7 +31,7 @@ class MinerNetwork:
return ipaddress.ip_network(f"{default_gateway}/{subnet_mask}", strict=False)
async def scan_network_for_miners(self) -> None or list:
"""Scan the network for miners, and """
"""Scan the network for miners, and return found miners as a list."""
local_network = self.get_network()
print(f"Scanning {local_network} for miners...")
scan_tasks = []
@@ -55,6 +55,11 @@ class MinerNetwork:
return miners
async def scan_network_generator(self):
"""
Scan the network for miners using an async generator.
Returns an asynchronous generator containing found miners.
"""
loop = asyncio.get_event_loop()
local_network = self.get_network()
scan_tasks = []

Binary file not shown.