Merge branch 'pyqt_gui_cfg_util'

This commit is contained in:
UpstreamData
2022-05-06 16:22:28 -06:00
30 changed files with 1750 additions and 204 deletions

View File

@@ -48,7 +48,7 @@ A basic script to find all miners on the network and get the hashrate from them
```python ```python
import asyncio import asyncio
from network import MinerNetwork from network import MinerNetwork
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_hashrate(): async def get_hashrate():
@@ -84,7 +84,7 @@ You can also create your own miner without scanning if you know the IP:
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate(ip: str): async def get_miner_hashrate(ip: str):
@@ -103,7 +103,8 @@ async def get_miner_hashrate(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_hashrate(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_hashrate(str("192.168.1.69")))
``` ```
<br> <br>
@@ -112,7 +113,7 @@ Or generate a miner directly without the factory:
```python ```python
import asyncio import asyncio
from miners.bosminer import BOSMiner from miners.bosminer import BOSMiner
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate(ip: str): async def get_miner_hashrate(ip: str):
@@ -127,7 +128,8 @@ async def get_miner_hashrate(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_hashrate(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_hashrate(str("192.168.1.69")))
``` ```
<br> <br>
@@ -136,7 +138,7 @@ Or finally, just get the API directly:
```python ```python
import asyncio import asyncio
from API.bosminer import BOSMinerAPI from API.bosminer import BOSMinerAPI
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate(ip: str): async def get_miner_hashrate(ip: str):
@@ -152,7 +154,8 @@ async def get_miner_hashrate(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_hashrate(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_hashrate(str("192.168.1.69")))
``` ```
@@ -165,7 +168,7 @@ Now that you know that, lets move on to some common API functions that you might
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_pool_data(ip: str): async def get_miner_pool_data(ip: str):
@@ -189,7 +192,8 @@ async def get_miner_pool_data(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_pool_data(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_pool_data(str("192.168.1.69")))
``` ```
* Getting temperature data: * Getting temperature data:
@@ -204,7 +208,7 @@ A pretty good example of really trying to make this robust is in ```cfg_util.fun
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_temperature_data(ip: str): async def get_miner_temperature_data(ip: str):
@@ -223,7 +227,8 @@ async def get_miner_temperature_data(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_temperature_data(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_temperature_data(str("192.168.1.69")))
``` ```
* Getting power data: * Getting power data:
@@ -234,7 +239,7 @@ How about data on the power usage of the miner? This one only works for Whatsmi
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_power_data(ip: str): async def get_miner_power_data(ip: str):
@@ -249,7 +254,8 @@ async def get_miner_power_data(ip: str):
# send the command # send the command
tunerstatus = await miner.api.tunerstatus() tunerstatus = await miner.api.tunerstatus()
# parse the return # parse the return
data = await safe_parse_api_data(tunerstatus, 'TUNERSTATUS', 0, "PowerLimit") data = await safe_parse_api_data(tunerstatus, 'TUNERSTATUS', 0,
"PowerLimit")
else: else:
# send the command # send the command
# whatsminers have the power info in summary # whatsminers have the power info in summary
@@ -261,7 +267,8 @@ async def get_miner_power_data(ip: str):
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_power_data(str("192.168.1.69"))) asyncio.new_event_loop().run_until_complete(
get_miner_power_data(str("192.168.1.69")))
``` ```
* Multicommands: * Multicommands:
@@ -273,7 +280,7 @@ How about we get the current pool user and hashrate in 1 command?
import asyncio import asyncio
import ipaddress import ipaddress
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from tools.cfg_util.func.parse_data import safe_parse_api_data from tools.cfg_util_old.func.parse_data import safe_parse_api_data
async def get_miner_hashrate_and_pool(ip: str): async def get_miner_hashrate_and_pool(ip: str):
@@ -286,13 +293,16 @@ async def get_miner_hashrate_and_pool(ip: str):
# Get the API data # Get the API data
api_data = await miner.api.multicommand("pools", "summary") api_data = await miner.api.multicommand("pools", "summary")
if "pools" in api_data.keys(): if "pools" in api_data.keys():
user = await safe_parse_api_data(api_data, "pools", 0, "POOLS", 0, "User") user = await safe_parse_api_data(api_data, "pools", 0, "POOLS", 0,
"User")
print(user) print(user)
if "summary" in api_data.keys(): if "summary" in api_data.keys():
hashrate = await safe_parse_api_data(api_data, "summary", 0, "SUMMARY", 0, "MHS av") hashrate = await safe_parse_api_data(api_data, "summary", 0, "SUMMARY",
0, "MHS av")
print(hashrate) print(hashrate)
if __name__ == '__main__': if __name__ == '__main__':
asyncio.new_event_loop().run_until_complete(get_miner_hashrate_and_pool(str("192.168.1.9"))) asyncio.new_event_loop().run_until_complete(
get_miner_hashrate_and_pool(str("192.168.1.9")))
``` ```

View File

@@ -3,7 +3,7 @@ import yaml
import toml import toml
async def bos_config_convert(config: dict): def bos_config_convert(config: dict):
out_config = {} out_config = {}
for opt in config: for opt in config:
if opt == "format": if opt == "format":
@@ -110,7 +110,7 @@ async def bos_config_convert(config: dict):
return yaml.dump(out_config, sort_keys=False) return yaml.dump(out_config, sort_keys=False)
async def general_config_convert_bos(yaml_config, user_suffix: str = None): def general_config_convert_bos(yaml_config, user_suffix: str = None):
config = yaml.load(yaml_config, Loader=yaml.SafeLoader) config = yaml.load(yaml_config, Loader=yaml.SafeLoader)
out_config = {} out_config = {}
for opt in config: for opt in config:

View File

@@ -21,6 +21,7 @@ class BaseMiner:
self.api_type = None self.api_type = None
self.model = None self.model = None
self.light = None self.light = None
self.hostname = None
self.nominal_chips = 1 self.nominal_chips = 1
async def _get_ssh_connection(self) -> asyncssh.connect: async def _get_ssh_connection(self) -> asyncssh.connect:
@@ -45,15 +46,21 @@ class BaseMiner:
) )
return conn return conn
except Exception as e: except Exception as e:
logging.warning(f"{self} raised an exception: {e}") # logging.warning(f"{self} raised an exception: {e}")
raise e raise e
except OSError: except OSError:
logging.warning(f"Connection refused: {self}") logging.warning(f"Connection refused: {self}")
return None return None
except Exception as e: except Exception as e:
logging.warning(f"{self} raised an exception: {e}") # logging.warning(f"{self} raised an exception: {e}")
raise e raise e
async def fault_light_on(self) -> bool:
return False
async def fault_light_off(self) -> bool:
return False
async def send_file(self, src, dest): async def send_file(self, src, dest):
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
await asyncssh.scp(src, (conn, dest)) await asyncssh.scp(src, (conn, dest))
@@ -74,10 +81,27 @@ class BaseMiner:
return None return None
async def reboot(self): async def reboot(self):
return None return False
async def restart_backend(self): async def restart_backend(self):
return None return False
async def send_config(self, yaml_config): async def send_config(self, yaml_config):
return None return None
async def get_data(self):
data = {
"IP": str(self.ip),
"Model": "Unknown",
"Hostname": "Unknown",
"Hashrate": 0,
"Temperature": 0,
"Pool User": "Unknown",
"Wattage": 0,
"Split": 0,
"Pool 1": "Unknown",
"Pool 1 User": "Unknown",
"Pool 2": "",
"Pool 2 User": "",
}
return data

View File

@@ -1,6 +1,7 @@
from API.bmminer import BMMinerAPI from API.bmminer import BMMinerAPI
from miners import BaseMiner from miners import BaseMiner
import logging import logging
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
class BMMiner(BaseMiner): class BMMiner(BaseMiner):
@@ -44,6 +45,8 @@ class BMMiner(BaseMiner):
:return: The hostname of the miner as a string or "?" :return: The hostname of the miner as a string or "?"
""" """
if self.hostname:
return self.hostname
try: try:
# open an ssh connection # open an ssh connection
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
@@ -55,7 +58,8 @@ class BMMiner(BaseMiner):
# return hostname data # return hostname data
logging.debug(f"Found hostname for {self.ip}: {host}") logging.debug(f"Found hostname for {self.ip}: {host}")
return host self.hostname = host
return self.hostname
else: else:
# return ? if we fail to get hostname with no ssh connection # return ? if we fail to get hostname with no ssh connection
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
@@ -81,6 +85,8 @@ class BMMiner(BaseMiner):
try: try:
# run the command and get the result # run the command and get the result
result = await conn.run(cmd) result = await conn.run(cmd)
result = result.stdout
except Exception as e: except Exception as e:
# if the command fails, log it # if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}") logging.warning(f"{self} command {cmd} error: {e}")
@@ -110,7 +116,116 @@ class BMMiner(BaseMiner):
pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"}) pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"})
return pool_data return pool_data
async def reboot(self) -> None: async def reboot(self) -> bool:
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
await self.send_ssh_command("reboot") _ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
if isinstance(_ret, str):
return True
return False
async def get_data(self):
data = {
"IP": str(self.ip),
"Model": "Unknown",
"Hostname": "Unknown",
"Hashrate": 0,
"Temperature": 0,
"Pool User": "Unknown",
"Wattage": 0,
"Split": 0,
"Pool 1": "Unknown",
"Pool 1 User": "Unknown",
"Pool 2": "",
"Pool 2 User": "",
}
model = await self.get_model()
hostname = await self.get_hostname()
if model:
data["Model"] = model
if hostname:
data["Hostname"] = hostname
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand("summary", "pools", "stats")
if miner_data:
break
if not miner_data:
return data
summary = miner_data.get("summary")[0]
pools = miner_data.get("pools")[0]
stats = miner_data.get("stats")[0]
if summary:
hr = summary.get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("GHS 5s")
if hr:
data["Hashrate"] = round(hr / 1000, 2)
if stats:
temp = stats.get("STATS")
if temp:
if len(temp) > 1:
for item in ["temp2", "temp1", "temp3"]:
temperature = temp[1].get(item)
if temperature and not temperature == 0.0:
data["Temperature"] = round(temperature)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools.get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
if pool_1.startswith("stratum+tcp://"):
pool_1.replace("stratum+tcp://", "")
if pool_1.startswith("stratum2+tcp://"):
pool_1.replace("stratum2+tcp://", "")
data["Pool 1"] = pool_1
if pool_1_user:
data["Pool 1 User"] = pool_1_user
data["Pool User"] = pool_1_user
if pool_2:
if pool_2.startswith("stratum+tcp://"):
pool_2.replace("stratum+tcp://", "")
if pool_2.startswith("stratum2+tcp://"):
pool_2.replace("stratum2+tcp://", "")
data["Pool 2"] = pool_2
if pool_2_user:
data["Pool 2 User"] = pool_2_user
if quota:
data["Split"] = quota
return data

View File

@@ -3,6 +3,7 @@ from API.bosminer import BOSMinerAPI
import toml import toml
from config.bos import bos_config_convert, general_config_convert_bos from config.bos import bos_config_convert, general_config_convert_bos
import logging import logging
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
class BOSMiner(BaseMiner): class BOSMiner(BaseMiner):
@@ -33,6 +34,7 @@ class BOSMiner(BaseMiner):
try: try:
# run the command and get the result # run the command and get the result
result = await conn.run(cmd) result = await conn.run(cmd)
result = result.stdout
except Exception as e: except Exception as e:
# if the command fails, log it # if the command fails, log it
logging.warning(f"{self} command {cmd} error: {e}") logging.warning(f"{self} command {cmd} error: {e}")
@@ -42,36 +44,48 @@ class BOSMiner(BaseMiner):
return return
continue continue
# return the result, either command output or None # return the result, either command output or None
return result return str(result)
async def fault_light_on(self) -> None: async def fault_light_on(self) -> bool:
"""Sends command to turn on fault light on the miner.""" """Sends command to turn on fault light on the miner."""
logging.debug(f"{self}: Sending fault_light on command.") logging.debug(f"{self}: Sending fault_light on command.")
self.light = True self.light = True
await self.send_ssh_command("miner fault_light on") _ret = await self.send_ssh_command("miner fault_light on")
logging.debug(f"{self}: fault_light on command completed.") logging.debug(f"{self}: fault_light on command completed.")
if isinstance(_ret, str):
return True
return False
async def fault_light_off(self) -> None: async def fault_light_off(self) -> bool:
"""Sends command to turn off fault light on the miner.""" """Sends command to turn off fault light on the miner."""
logging.debug(f"{self}: Sending fault_light off command.") logging.debug(f"{self}: Sending fault_light off command.")
self.light = False self.light = False
await self.send_ssh_command("miner fault_light off") _ret = await self.send_ssh_command("miner fault_light off")
logging.debug(f"{self}: fault_light off command completed.") logging.debug(f"{self}: fault_light off command completed.")
if isinstance(_ret, str):
return True
return False
async def restart_backend(self) -> None: async def restart_backend(self) -> bool:
await self.restart_bosminer() return await self.restart_bosminer()
async def restart_bosminer(self) -> None: async def restart_bosminer(self) -> bool:
"""Restart bosminer hashing process.""" """Restart bosminer hashing process."""
logging.debug(f"{self}: Sending bosminer restart command.") logging.debug(f"{self}: Sending bosminer restart command.")
await self.send_ssh_command("/etc/init.d/bosminer restart") _ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
logging.debug(f"{self}: bosminer restart command completed.") logging.debug(f"{self}: bosminer restart command completed.")
if isinstance(_ret, str):
return True
return False
async def reboot(self) -> None: async def reboot(self) -> bool:
"""Reboots power to the physical miner.""" """Reboots power to the physical miner."""
logging.debug(f"{self}: Sending reboot command.") logging.debug(f"{self}: Sending reboot command.")
await self.send_ssh_command("/sbin/reboot") _ret = await self.send_ssh_command("/sbin/reboot")
logging.debug(f"{self}: Reboot command completed.") logging.debug(f"{self}: Reboot command completed.")
if isinstance(_ret, str):
return True
return False
async def get_config(self) -> None: async def get_config(self) -> None:
logging.debug(f"{self}: Getting config.") logging.debug(f"{self}: Getting config.")
@@ -82,7 +96,7 @@ class BOSMiner(BaseMiner):
async with sftp.open("/etc/bosminer.toml") as file: async with sftp.open("/etc/bosminer.toml") as file:
toml_data = toml.loads(await file.read()) toml_data = toml.loads(await file.read())
logging.debug(f"{self}: Converting config file.") logging.debug(f"{self}: Converting config file.")
cfg = await bos_config_convert(toml_data) cfg = bos_config_convert(toml_data)
self.config = cfg self.config = cfg
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
@@ -90,13 +104,16 @@ class BOSMiner(BaseMiner):
:return: The hostname of the miner as a string or "?" :return: The hostname of the miner as a string or "?"
""" """
if self.hostname:
return self.hostname
try: try:
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
if conn is not None: if conn is not None:
data = await conn.run("cat /proc/sys/kernel/hostname") data = await conn.run("cat /proc/sys/kernel/hostname")
host = data.stdout.strip() host = data.stdout.strip()
logging.debug(f"Found hostname for {self.ip}: {host}") logging.debug(f"Found hostname for {self.ip}: {host}")
return host self.hostname = host
return self.hostname
else: else:
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return "?" return "?"
@@ -160,10 +177,10 @@ class BOSMiner(BaseMiner):
if ip_user: if ip_user:
suffix = str(self.ip).split(".")[-1] suffix = str(self.ip).split(".")[-1]
toml_conf = toml.dumps( toml_conf = toml.dumps(
await general_config_convert_bos(yaml_config, user_suffix=suffix) general_config_convert_bos(yaml_config, user_suffix=suffix)
) )
else: else:
toml_conf = toml.dumps(await general_config_convert_bos(yaml_config)) toml_conf = toml.dumps(general_config_convert_bos(yaml_config))
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
logging.debug(f"{self}: Opening SFTP connection.") logging.debug(f"{self}: Opening SFTP connection.")
async with conn.start_sftp_client() as sftp: async with conn.start_sftp_client() as sftp:
@@ -222,3 +239,112 @@ class BOSMiner(BaseMiner):
bad += 1 bad += 1
if not bad > 0: if not bad > 0:
return str(self.ip) return str(self.ip)
async def get_data(self):
data = {
"IP": str(self.ip),
"Model": "Unknown",
"Hostname": "Unknown",
"Hashrate": 0,
"Temperature": 0,
"Pool User": "Unknown",
"Wattage": 0,
"Split": 0,
"Pool 1": "Unknown",
"Pool 1 User": "Unknown",
"Pool 2": "",
"Pool 2 User": "",
}
model = await self.get_model()
hostname = await self.get_hostname()
if model:
data["Model"] = model
if hostname:
data["Hostname"] = hostname
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand(
"summary", "temps", "tunerstatus", "pools"
)
if miner_data:
break
if not miner_data:
return data
summary = miner_data.get("summary")[0]
temps = miner_data.get("temps")[0]
tunerstatus = miner_data.get("tunerstatus")[0]
pools = miner_data.get("pools")[0]
if summary:
hr = summary.get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 5s")
if hr:
data["Hashrate"] = round(hr / 1000000, 2)
if temps:
temp = temps.get("TEMPS")
if temp:
if len(temp) > 0:
temp = temp[0].get("Chip")
if temp:
data["Temperature"] = round(temp)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools.get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
pool_1 = pool_1.replace("stratum+tcp://", "")
pool_1 = pool_1.replace("stratum2+tcp://", "")
data["Pool 1"] = pool_1
if pool_1_user:
data["Pool 1 User"] = pool_1_user
data["Pool User"] = pool_1_user
if pool_2:
pool_2 = pool_2.replace("stratum+tcp://", "")
pool_2 = pool_2.replace("stratum2+tcp://", "")
data["Pool 2"] = pool_2
if pool_2_user:
data["Pool 2 User"] = pool_2_user
if quota:
data["Split"] = quota
if tunerstatus:
tuner = tunerstatus.get("TUNERSTATUS")
if tuner:
if len(tuner) > 0:
wattage = tuner[0].get("PowerLimit")
if wattage:
data["Wattage"] = wattage
return data

View File

@@ -2,6 +2,7 @@ from API.btminer import BTMinerAPI
from miners import BaseMiner from miners import BaseMiner
from API import APIError from API import APIError
import logging import logging
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
class BTMiner(BaseMiner): class BTMiner(BaseMiner):
@@ -27,12 +28,15 @@ class BTMiner(BaseMiner):
return None return None
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
if self.hostname:
return self.hostname
try: try:
host_data = await self.api.get_miner_info() host_data = await self.api.get_miner_info()
if host_data: if host_data:
host = host_data["Msg"]["hostname"] host = host_data["Msg"]["hostname"]
logging.debug(f"Found hostname for {self.ip}: {host}") logging.debug(f"Found hostname for {self.ip}: {host}")
return host self.hostname = host
return self.hostname
except APIError: except APIError:
logging.warning(f"Failed to get hostname for miner: {self}") logging.warning(f"Failed to get hostname for miner: {self}")
return "?" return "?"
@@ -70,3 +74,112 @@ class BTMiner(BaseMiner):
print(board) print(board)
logging.debug(f"Found board data for {self}: {boards}") logging.debug(f"Found board data for {self}: {boards}")
return boards return boards
async def get_data(self):
data = {
"IP": str(self.ip),
"Model": "Unknown",
"Hostname": "Unknown",
"Hashrate": 0,
"Temperature": 0,
"Pool User": "Unknown",
"Wattage": 0,
"Split": 0,
"Pool 1": "Unknown",
"Pool 1 User": "Unknown",
"Pool 2": "",
"Pool 2 User": "",
}
model = await self.get_model()
hostname = await self.get_hostname()
if model:
data["Model"] = model
if hostname:
data["Hostname"] = hostname
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand("summary", "devs", "pools")
if miner_data:
break
if not miner_data:
return data
summary = miner_data.get("summary")[0]
devs = miner_data.get("devs")[0]
pools = miner_data.get("pools")[0]
if summary:
summary_data = summary.get("SUMMARY")
if summary_data:
if len(summary_data) > 0:
hr = summary_data[0].get("MHS 5s")
if hr:
data["Hashrate"] = round(hr / 1000000, 2)
wattage = summary_data[0].get("Power")
if wattage:
data["Wattage"] = round(wattage)
if devs:
temp_data = devs.get("DEVS")
if temp_data:
for board in temp_data:
temp = board.get("Chip Temp Avg")
if temp and not temp == 0.0:
data["Temperature"] = round(temp)
break
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools.get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
if pool_1.startswith("stratum+tcp://"):
pool_1.replace("stratum+tcp://", "")
if pool_1.startswith("stratum2+tcp://"):
pool_1.replace("stratum2+tcp://", "")
data["Pool 1"] = pool_1
if pool_1_user:
data["Pool 1 User"] = pool_1_user
data["Pool User"] = pool_1_user
if pool_2:
if pool_2.startswith("stratum+tcp://"):
pool_2.replace("stratum+tcp://", "")
if pool_2.startswith("stratum2+tcp://"):
pool_2.replace("stratum2+tcp://", "")
data["Pool 2"] = pool_2
if pool_2_user:
data["Pool 2 User"] = pool_2_user
if quota:
data["Split"] = quota
return data

View File

@@ -1,6 +1,8 @@
from miners import BaseMiner from miners import BaseMiner
from API.cgminer import CGMinerAPI from API.cgminer import CGMinerAPI
from API import APIError from API import APIError
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
import logging
class CGMiner(BaseMiner): class CGMiner(BaseMiner):
@@ -28,11 +30,15 @@ class CGMiner(BaseMiner):
return None return None
async def get_hostname(self) -> str: async def get_hostname(self) -> str:
if self.hostname:
return self.hostname
try: try:
async with (await self._get_ssh_connection()) as conn: async with (await self._get_ssh_connection()) as conn:
if conn is not None: if conn is not None:
data = await conn.run("cat /proc/sys/kernel/hostname") data = await conn.run("cat /proc/sys/kernel/hostname")
return data.stdout.strip() host = data.stdout.strip()
self.hostname = host
return self.hostname
else: else:
return "?" return "?"
except Exception: except Exception:
@@ -44,6 +50,7 @@ class CGMiner(BaseMiner):
for i in range(3): for i in range(3):
try: try:
result = await conn.run(cmd) result = await conn.run(cmd)
result = result.stdout
except Exception as e: except Exception as e:
print(f"{cmd} error: {e}") print(f"{cmd} error: {e}")
if i == 3: if i == 3:
@@ -51,16 +58,24 @@ class CGMiner(BaseMiner):
continue continue
return result return result
async def restart_backend(self) -> None: async def restart_backend(self) -> bool:
await self.restart_cgminer() return await self.restart_cgminer()
async def restart_cgminer(self) -> None: async def restart_cgminer(self) -> bool:
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"] commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
commands = ";".join(commands) commands = ";".join(commands)
await self.send_ssh_command(commands) _ret = await self.send_ssh_command(commands)
if isinstance(_ret, str):
return True
return False
async def reboot(self) -> None: async def reboot(self) -> bool:
await self.send_ssh_command("reboot") logging.debug(f"{self}: Sending reboot command.")
_ret = await self.send_ssh_command("reboot")
logging.debug(f"{self}: Reboot command completed.")
if isinstance(_ret, str):
return True
return False
async def start_cgminer(self) -> None: async def start_cgminer(self) -> None:
commands = [ commands = [
@@ -88,3 +103,108 @@ class CGMiner(BaseMiner):
result = await conn.run(command, check=True) result = await conn.run(command, check=True)
self.config = result.stdout self.config = result.stdout
print(str(self.config)) print(str(self.config))
async def get_data(self):
data = {
"IP": str(self.ip),
"Model": "Unknown",
"Hostname": "Unknown",
"Hashrate": 0,
"Temperature": 0,
"Pool User": "Unknown",
"Wattage": 0,
"Split": 0,
"Pool 1": "Unknown",
"Pool 1 User": "Unknown",
"Pool 2": "",
"Pool 2 User": "",
}
model = await self.get_model()
hostname = await self.get_hostname()
if model:
data["Model"] = model
if hostname:
data["Hostname"] = hostname
miner_data = None
for i in range(DATA_RETRIES):
miner_data = await self.api.multicommand("summary", "pools", "stats")
if miner_data:
break
if not miner_data:
return data
summary = miner_data.get("summary")[0]
pools = miner_data.get("pools")[0]
stats = miner_data.get("stats")[0]
if summary:
hr = summary.get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("GHS 5s")
if hr:
data["Hashrate"] = round(hr / 1000, 2)
if stats:
temp = stats.get("STATS")
if temp:
if len(temp) > 1:
for item in ["temp2", "temp1", "temp3"]:
temperature = temp[1].get(item)
if temperature and not temperature == 0.0:
data["Temperature"] = round(temperature)
if pools:
pool_1 = None
pool_2 = None
pool_1_user = None
pool_2_user = None
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools.get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
pool_1_quota = pool["Quota"]
elif not pool_2_user:
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if not pool.get("User") == pool_1_user:
if not pool_2_user == pool.get("User"):
pool_2_user = pool.get("User")
pool_2 = pool["URL"]
pool_2_quota = pool["Quota"]
if pool_2_user and not pool_2_user == pool_1_user:
quota = f"{pool_1_quota}/{pool_2_quota}"
if pool_1:
if pool_1.startswith("stratum+tcp://"):
pool_1.replace("stratum+tcp://", "")
if pool_1.startswith("stratum2+tcp://"):
pool_1.replace("stratum2+tcp://", "")
data["Pool 1"] = pool_1
if pool_1_user:
data["Pool 1 User"] = pool_1_user
data["Pool User"] = pool_1_user
if pool_2:
if pool_2.startswith("stratum+tcp://"):
pool_2.replace("stratum+tcp://", "")
if pool_2.startswith("stratum2+tcp://"):
pool_2.replace("stratum2+tcp://", "")
data["Pool 2"] = pool_2
if pool_2_user:
data["Pool 2 User"] = pool_2_user
if quota:
data["Split"] = quota
return data

View File

@@ -36,20 +36,25 @@ import ipaddress
import json import json
import logging import logging
from settings import MINER_FACTORY_GET_VERSION_RETRIES as GET_VERSION_RETRIES from settings import (
MINER_FACTORY_GET_VERSION_RETRIES as GET_VERSION_RETRIES,
NETWORK_PING_TIMEOUT as PING_TIMEOUT,
)
class MinerFactory: class Singleton(type):
_instance = None _instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class MinerFactory(metaclass=Singleton):
def __init__(self): def __init__(self):
self.miners = {} self.miners = {}
def __new__(cls):
if not cls._instance:
cls._instance = super(MinerFactory, cls).__new__(cls)
return cls._instance
async def get_miner_generator(self, ips: list): async def get_miner_generator(self, ips: list):
""" """
Get Miner objects from ip addresses using an async generator. Get Miner objects from ip addresses using an async generator.
@@ -72,8 +77,10 @@ class MinerFactory:
for miner in scanned: for miner in scanned:
yield await miner yield await miner
async def get_miner(self, ip: ipaddress.ip_address): async def get_miner(self, ip: ipaddress.ip_address or str):
"""Decide a miner type using the IP address of the miner.""" """Decide a miner type using the IP address of the miner."""
if isinstance(ip, str):
ip = ipaddress.ip_address(ip)
# check if the miner already exists in cache # check if the miner already exists in cache
if ip in self.miners: if ip in self.miners:
return self.miners[ip] return self.miners[ip]
@@ -84,31 +91,35 @@ class MinerFactory:
# try to get the API multiple times based on retries # try to get the API multiple times based on retries
for i in range(GET_VERSION_RETRIES): for i in range(GET_VERSION_RETRIES):
# get the API type, should be BOSMiner, CGMiner, BMMiner, BTMiner, or None try:
api = await self._get_api_type(ip) # get the API type, should be BOSMiner, CGMiner, BMMiner, BTMiner, or None
# if we find the API type, dont need to loop anymore new_model, new_api = await asyncio.wait_for(
if api: self._get_miner_type(ip), timeout=PING_TIMEOUT
break )
# keep track of the API and model we found first
if new_api and not api:
api = new_api
if new_model and not model:
model = new_model
# if we find the API and model, dont need to loop anymore
if api and model:
break
except asyncio.TimeoutError:
pass
# try to get the model multiple times based on retries
for i in range(GET_VERSION_RETRIES):
# get the model, should return some miner model type, e.g. Antminer S9
model = await self._get_miner_model(ip)
# if we find the model type, dont need to loop anymore
if model:
break
# make sure we have model information # make sure we have model information
if model: if model:
# check if the miner is an Antminer # check if the miner is an Antminer
if "Antminer" in model: if "Antminer" in model:
# S9 logic # S9 logic
if "Antminer S9" in model: if "Antminer S9" in model:
# handle the different API types # handle the different API types
if not api: if not api:
logging.warning(f"{str(ip)}: No API data found, using BraiinsOS.") logging.warning(
f"{str(ip)}: No API data found, using BraiinsOS."
)
miner = BOSMinerS9(str(ip)) miner = BOSMinerS9(str(ip))
elif "BOSMiner" in api: elif "BOSMiner" in api:
miner = BOSMinerS9(str(ip)) miner = BOSMinerS9(str(ip))
@@ -129,7 +140,6 @@ class MinerFactory:
# X17 model logic # X17 model logic
elif "17" in model: elif "17" in model:
# handle the different API types # handle the different API types
if "BOSMiner" in api: if "BOSMiner" in api:
miner = BOSMinerX17(str(ip)) miner = BOSMinerX17(str(ip))
@@ -190,65 +200,102 @@ class MinerFactory:
# empty out self.miners # empty out self.miners
self.miners = {} self.miners = {}
async def _get_miner_model(self, ip: ipaddress.ip_address or str) -> str or None: async def _get_miner_type(self, ip: ipaddress.ip_address or str) -> tuple:
# instantiate model as being nothing if getting it fails
model = None model = None
api = None
devdetails = None
version = None
# try block in case of APIError or OSError 121 (Semaphore timeout)
try: try:
data = await self._send_api_command(str(ip), "devdetails+version")
# send the devdetails command to the miner (will fail with no boards/devices) validation = await self._validate_command(data)
data = await self._send_api_command(str(ip), "devdetails") if not validation[0]:
# sometimes data is b'', check for that raise APIError(validation[1])
if data:
# status check, make sure the command succeeded
if data.get("STATUS"):
if not isinstance(data["STATUS"], str):
# if status is E, its an error
if data["STATUS"][0].get("STATUS") not in ["I", "S"]:
# try an alternate method if devdetails fails devdetails = data["devdetails"][0]
data = await self._send_api_command(str(ip), "version") version = data["version"][0]
# make sure we have data
if data:
# check the keys are there to get the version
if data.get("VERSION"):
if data["VERSION"][0].get("Type"):
# save the model to be returned later
model = data["VERSION"][0]["Type"]
else:
# make sure devdetails actually contains data, if its empty, there are no devices
if (
"DEVDETAILS" in data.keys()
and not data["DEVDETAILS"] == []
):
# check for model, for most miners
if not data["DEVDETAILS"][0]["Model"] == "":
# model of most miners
model = data["DEVDETAILS"][0]["Model"]
# if model fails, try driver
else:
# some avalonminers have model in driver
model = data["DEVDETAILS"][0]["Driver"]
else:
# if all that fails, try just version
data = await self._send_api_command(str(ip), "version")
if "VERSION" in data.keys():
model = data["VERSION"][0]["Type"]
else:
print(data)
return model
# if there are errors, we just return None
except APIError as e: except APIError as e:
logging.debug(f"{str(ip)}: {e}") data = None
except OSError as e:
logging.debug(f"{str(ip)}: {e}") if not data:
return model try:
devdetails = await self._send_api_command(str(ip), "devdetails")
validation = await self._validate_command(devdetails)
if not validation[0]:
version = await self._send_api_command(str(ip), "version")
validation = await self._validate_command(version)
if not validation[0]:
raise APIError(validation[1])
except APIError as e:
logging.warning(f"{ip}: API Command Error: {e}")
return None, None
if devdetails:
if "DEVDETAILS" in devdetails.keys() and not devdetails["DEVDETAILS"] == []:
# check for model, for most miners
if not devdetails["DEVDETAILS"][0]["Model"] == "":
# model of most miners
model = devdetails["DEVDETAILS"][0]["Model"]
# if model fails, try driver
else:
# some avalonminers have model in driver
model = devdetails["DEVDETAILS"][0]["Driver"]
if version:
# check if there are any BMMiner strings in any of the dict keys
if any("BMMiner" in string for string in version["VERSION"][0].keys()):
api = "BMMiner"
# check if there are any CGMiner strings in any of the dict keys
elif any("CGMiner" in string for string in version["VERSION"][0].keys()):
api = "CGMiner"
# check if there are any BOSMiner strings in any of the dict keys
elif any("BOSminer" in string for string in version["VERSION"][0].keys()):
api = "BOSMiner"
# if all that fails, check the Description to see if it is a whatsminer
elif version.get("Description") and "whatsminer" in version.get("Description"):
api = "BTMiner"
if version and not model:
if (
"VERSION" in version.keys()
and version.get("VERSION")
and not version.get("VERSION") == []
):
model = version["VERSION"][0]["Type"]
return model, api
async def _validate_command(self, data: dict) -> tuple:
"""Check if the returned command output is correctly formatted."""
# check if the data returned is correct or an error
if not data:
return False, "No API data."
# if status isn't a key, it is a multicommand
if "STATUS" not in data.keys():
for key in data.keys():
# make sure not to try to turn id into a dict
if not key == "id":
# make sure they succeeded
if "STATUS" in data[key][0].keys():
if data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]:
# this is an error
return False, f"{key}: " + data[key][0]["STATUS"][0]["Msg"]
elif "id" not in data.keys():
if data["STATUS"] not in ["S", "I"]:
return False, data["Msg"]
else:
# make sure the command succeeded
if 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"]
return True, None
async def _send_api_command(self, ip: ipaddress.ip_address or str, command: str): async def _send_api_command(self, ip: ipaddress.ip_address or str, command: str):
try: try:
@@ -304,55 +351,3 @@ class MinerFactory:
await writer.wait_closed() await writer.wait_closed()
return data return data
async def _get_api_type(self, ip: ipaddress.ip_address or str) -> dict or None:
"""Get data on the version of the miner to return the right miner."""
# instantiate API as None in case something fails
api = None
# try block to handle OSError 121 (Semaphore timeout)
try:
# try the version command,works on most miners
data = await self._send_api_command(str(ip), "version")
# if we got data back, try to parse it
if data:
# make sure the command succeeded
if data.get("STATUS") and not data.get("STATUS") == "E":
if data["STATUS"][0].get("STATUS") in ["I", "S"]:
# check if there are any BMMiner strings in any of the dict keys
if any(
"BMMiner" in string for string in data["VERSION"][0].keys()
):
api = "BMMiner"
# check if there are any CGMiner strings in any of the dict keys
elif any(
"CGMiner" in string for string in data["VERSION"][0].keys()
):
api = "CGMiner"
# check if there are any BOSMiner strings in any of the dict keys
elif any(
"BOSminer" in string for string in data["VERSION"][0].keys()
):
api = "BOSMiner"
# if all that fails, check the Description to see if it is a whatsminer
elif data.get("Description") and "whatsminer" in data.get(
"Description"
):
api = "BTMiner"
# return the API if we found it
if api:
return api
# if there are errors, return None
except OSError as e:
if e.winerror == 121:
return None
else:
logging.debug(f"{str(ip)}: {e}")
return None

View File

@@ -1 +1,10 @@
from tools.cfg_util.cfg_util_sg import main from .ui import ui
import asyncio
def main():
asyncio.run(ui())
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,78 @@
from miners.miner_factory import MinerFactory
from tools.cfg_util.layout import window
from tools.cfg_util.tables import TableManager
from tools.cfg_util.decorators import disable_buttons
@disable_buttons("Flashing Lights")
async def btn_light(ip_idxs: list):
table_manager = TableManager()
_table = window["cmd_table"].Widget
iids = _table.get_children()
for idx in ip_idxs:
item = _table.item(iids[idx])
ip = item["values"][0]
new_light_val = not table_manager.data[ip]["Light"]
miner = await MinerFactory().get_miner(ip)
if new_light_val:
success = await miner.fault_light_on()
else:
success = await miner.fault_light_off()
if success:
table_manager.data[ip]["Light"] = new_light_val
table_manager.data[ip]["Command Output"] = "Fault Light command succeeded."
else:
table_manager.data[ip]["Command Output"] = "Fault Light command failed."
table_manager.update_tables()
@disable_buttons("Rebooting")
async def btn_reboot(ip_idxs: list):
table_manager = TableManager()
_table = window["cmd_table"].Widget
iids = _table.get_children()
for idx in ip_idxs:
item = _table.item(iids[idx])
ip = item["values"][0]
miner = await MinerFactory().get_miner(ip)
success = await miner.reboot()
if success:
table_manager.data[ip]["Command Output"] = "Reboot command succeeded."
else:
table_manager.data[ip]["Command Output"] = "Reboot command failed."
table_manager.update_tables()
@disable_buttons("Restarting Backend")
async def btn_backend(ip_idxs: list):
table_manager = TableManager()
_table = window["cmd_table"].Widget
iids = _table.get_children()
for idx in ip_idxs:
item = _table.item(iids[idx])
ip = item["values"][0]
miner = await MinerFactory().get_miner(ip)
success = await miner.restart_backend()
if success:
table_manager.data[ip][
"Command Output"
] = "Restart Backend command succeeded."
else:
table_manager.data[ip]["Command Output"] = "Restart Backend command failed."
table_manager.update_tables()
@disable_buttons("Sending Command")
async def btn_command(ip_idxs: list, command: str):
table_manager = TableManager()
_table = window["cmd_table"].Widget
iids = _table.get_children()
for idx in ip_idxs:
item = _table.item(iids[idx])
ip = item["values"][0]
miner = await MinerFactory().get_miner(ip)
success = await miner.send_ssh_command(command)
if not isinstance(success, str):
success = f"Command {command} failed."
table_manager.data[ip]["Command Output"] = success
table_manager.update_tables()

View File

@@ -0,0 +1,160 @@
import PySimpleGUI as sg
from config.bos import bos_config_convert
import time
from tools.cfg_util.layout import window, update_prog_bar
from tools.cfg_util.decorators import disable_buttons
from miners.miner_factory import MinerFactory
import asyncio
from settings import CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS
from tools.cfg_util.general import update_miners_data
progress_bar_len = 0
@disable_buttons("Importing")
async def btn_import(table, selected):
if not len(selected) > 0:
return
ip = [window[table].Values[row][0] for row in selected][0]
miner = await MinerFactory().get_miner(ip)
await miner.get_config()
config = miner.config
window["cfg_config_txt"].update(config)
@disable_buttons("Configuring")
async def btn_config(table, selected, config: str, last_oct_ip: bool):
ips = [window[table].Values[row][0] for row in selected]
await send_config(ips, config, last_oct_ip)
async def send_config(ips: list, config: str, last_octet_ip: bool):
global progress_bar_len
progress_bar_len = 0
await update_prog_bar(progress_bar_len, max=(2 * len(ips)))
get_miner_genenerator = MinerFactory().get_miner_generator(ips)
all_miners = []
async for miner in get_miner_genenerator:
all_miners.append(miner)
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
config_sender_generator = send_config_generator(
all_miners, config, last_octet_ip_user=last_octet_ip
)
async for _config_sender in config_sender_generator:
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
await asyncio.sleep(3)
await update_miners_data(ips)
async def send_config_generator(miners: list, config, last_octet_ip_user: bool):
loop = asyncio.get_event_loop()
config_tasks = []
for miner in miners:
if len(config_tasks) >= CONFIG_THREADS:
configured = asyncio.as_completed(config_tasks)
config_tasks = []
for sent_config in configured:
yield await sent_config
config_tasks.append(
loop.create_task(miner.send_config(config, ip_user=last_octet_ip_user))
)
configured = asyncio.as_completed(config_tasks)
for sent_config in configured:
yield await sent_config
def generate_config(username: str, workername: str, v2_allowed: bool):
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": url_1, "user": user, "password": "123"},
{"url": url_2, "user": user, "password": "123"},
{"url": url_3, "user": user, "password": "123"},
],
}
],
"format": {
"version": "1.2+",
"model": "Antminer S9",
"generator": "upstream_config_util",
"timestamp": int(time.time()),
},
"temp_control": {
"target_temp": 80.0,
"hot_temp": 90.0,
"dangerous_temp": 120.0,
},
"autotuning": {"enabled": True, "psu_power_limit": 900},
}
window["cfg_config_txt"].update(bos_config_convert(config))
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"]:
generate_config(
values["generate_config_window_username"],
values["generate_config_window_workername"],
values["generate_config_window_allow_v2"],
)
generate_config_window.close()
break
def generate_config_layout():
config_layout = [
[
sg.Text(
"Enter your pool username and password below to generate a config for SlushPool."
)
],
[sg.Text("")],
[
sg.Text("Username:", size=(19, 1)),
sg.InputText(
key="generate_config_window_username", do_not_clear=True, size=(45, 1)
),
],
[
sg.Text("Worker Name (OPT):", size=(19, 1)),
sg.InputText(
key="generate_config_window_workername", do_not_clear=True, size=(45, 1)
),
],
[
sg.Text("Allow Stratum V2?:", size=(19, 1)),
sg.Checkbox("", key="generate_config_window_allow_v2", default=True),
],
[sg.Button("Generate", key="generate_config_window_generate")],
]
return config_layout

View File

@@ -0,0 +1,24 @@
from tools.cfg_util.layout import window
from tools.cfg_util.layout import BUTTON_KEYS
def disable_buttons(status: str = ""):
def decorator(func):
# handle the inner function that the decorator is wrapping
async def inner(*args, **kwargs):
# disable the buttons
for button in BUTTON_KEYS:
window[button].Update(disabled=True)
window["status"].update(status)
# call the original wrapped function
await func(*args, **kwargs)
# re-enable the buttons after the wrapped function completes
for button in BUTTON_KEYS:
window[button].Update(disabled=False)
window["status"].update("")
return inner
return decorator

View File

@@ -0,0 +1,85 @@
import asyncio
import webbrowser
from miners.miner_factory import MinerFactory
from tools.cfg_util.decorators import disable_buttons
from tools.cfg_util.layout import TABLE_KEYS
from tools.cfg_util.layout import window, update_prog_bar
from tools.cfg_util.tables import TableManager
progress_bar_len = 0
DEFAULT_DATA = [
"Model",
"Hostname",
"Hashrate",
"Temperature",
"Pool User",
"Pool 1",
"Pool 1 User",
"Pool 2",
"Pool 2 User",
"Wattage",
"Split",
]
def btn_all(table, selected):
if table in TABLE_KEYS["table"]:
if len(selected) == len(window[table].Values):
window[table].update(select_rows=())
else:
window[table].update(
select_rows=([row for row in range(len(window[table].Values))])
)
if table in TABLE_KEYS["tree"]:
if len(selected) == len(window[table].Widget.get_children()):
_tree = window[table]
_tree.Widget.selection_set([])
else:
_tree = window[table]
rows_to_select = [i for i in _tree.Widget.get_children()]
_tree.Widget.selection_set(rows_to_select)
def btn_web(table, selected):
for row in selected:
webbrowser.open("http://" + window[table].Values[row][0])
@disable_buttons("Refreshing")
async def btn_refresh(table, selected):
ips = [window[table].Values[row][0] for row in selected]
if not len(selected) > 0:
ips = [window[table].Values[row][0] for row in range(len(window[table].Values))]
await update_miners_data(ips)
async def update_miners_data(miners: list):
data = []
for miner in miners:
_data = {}
for key in DEFAULT_DATA:
_data[key] = ""
_data["IP"] = str(miner)
data.append(_data)
TableManager().update_data(data)
global progress_bar_len
progress_bar_len = 0
await update_prog_bar(progress_bar_len, max=len(miners))
data_generator = asyncio.as_completed(
[_get_data(await MinerFactory().get_miner(miner)) for miner in miners]
)
for all_data in data_generator:
data = await all_data
TableManager().update_item(data)
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
async def _get_data(miner):
return await miner.get_data()

27
tools/cfg_util/imgs.py Normal file

File diff suppressed because one or more lines are too long

302
tools/cfg_util/layout.py Normal file
View File

@@ -0,0 +1,302 @@
import PySimpleGUI as sg
from .imgs import WINDOW_ICON
sg.set_options(font=("Liberation Mono", 10))
TABLE_HEADERS = {
"SCAN": [
"IP",
"Model",
"Hostname",
"Hashrate",
"Temperature",
"Pool User",
"Wattage",
],
"CMD": ["IP", "Model", "Command Output"],
"POOLS": [
"IP",
"Split",
"Pool 1",
"Pool 1 User",
"Pool 2",
"Pool 2 User",
],
"CONFIG": ["IP", "Model", "Pool 1 User"],
}
TABLE_KEYS = {
"table": ["scan_table", "pools_table", "cfg_table"],
"tree": ["cmd_table"],
}
MINER_COUNT_BUTTONS = [
"scan_miner_count",
"cmd_miner_count",
"cfg_miner_count",
"pools_miner_count",
]
BUTTON_KEYS = [
"btn_scan",
"btn_cmd",
"scan_all",
"scan_refresh",
"scan_web",
"cmd_all",
"cmd_light",
"cmd_reboot",
"cmd_backend",
"pools_all",
"pools_refresh",
"pools_web",
"cfg_import",
"cfg_config",
"cfg_generate",
"cfg_all",
"cfg_web",
]
TABLE_HEIGHT = 27
IMAGE_COL_WIDTH = 6
IP_COL_WIDTH = 17
MODEL_COL_WIDTH = 15
HOST_COL_WIDTH = 15
HASHRATE_COL_WIDTH = 12
TEMP_COL_WIDTH = 12
USER_COL_WIDTH = 31
WATTAGE_COL_WIDTH = 8
SPLIT_COL_WIDTH = 6
SCAN_COL_WIDTHS = [
IP_COL_WIDTH,
MODEL_COL_WIDTH,
HOST_COL_WIDTH,
HASHRATE_COL_WIDTH,
TEMP_COL_WIDTH,
USER_COL_WIDTH,
WATTAGE_COL_WIDTH,
]
TABLE_TOTAL_WIDTH = sum(SCAN_COL_WIDTHS)
async def update_prog_bar(count: int, max: int = None):
bar = window["progress_bar"]
bar.update_bar(count, max=max)
if max:
bar.maxlen = max
if not hasattr(bar, "maxlen"):
if not max:
max = 100
bar.maxlen = max
percent_done = 100 * (count / bar.maxlen)
window["progress_percent"].Update(f"{round(percent_done, 2)} %")
if percent_done == 100:
window["progress_percent"].Update("")
def get_scan_layout():
scan_layout = [
[
sg.Text("Scan IP"),
sg.InputText(key="scan_ip", size=(31, 1)),
sg.Button("Scan", key="btn_scan"),
sg.Push(),
sg.Button(
"Miners: 0",
disabled=True,
button_color=("black", "white smoke"),
disabled_button_color=("black", "white smoke"),
key="scan_miner_count",
),
],
[
sg.Button("ALL", key="scan_all"),
sg.Button("REFRESH DATA", key="scan_refresh"),
sg.Button("OPEN IN WEB", key="scan_web"),
],
[
sg.Table(
values=[],
headings=[heading for heading in TABLE_HEADERS["SCAN"]],
auto_size_columns=False,
max_col_width=15,
justification="center",
key="scan_table",
col_widths=SCAN_COL_WIDTHS,
background_color="white",
text_color="black",
size=(TABLE_TOTAL_WIDTH, TABLE_HEIGHT),
expand_x=True,
enable_click_events=True,
)
],
]
return scan_layout
def get_command_layout():
data = sg.TreeData()
col_widths = [
IP_COL_WIDTH,
MODEL_COL_WIDTH,
TABLE_TOTAL_WIDTH - (IP_COL_WIDTH + MODEL_COL_WIDTH + IMAGE_COL_WIDTH + 4),
]
command_layout = [
[
sg.Text("Custom Command"),
sg.InputText(key="cmd_txt", expand_x=True),
sg.Button("Send Command", key="btn_cmd"),
sg.Push(),
sg.Button(
"Miners: 0",
disabled=True,
button_color=("black", "white smoke"),
disabled_button_color=("black", "white smoke"),
key="cmd_miner_count",
),
],
[
sg.Button("ALL", key="cmd_all"),
sg.Button("LIGHT", key="cmd_light"),
sg.Button("REBOOT", key="cmd_reboot"),
sg.Button("RESTART BACKEND", key="cmd_backend"),
],
[
sg.Tree(
data,
headings=[heading for heading in TABLE_HEADERS["CMD"]],
auto_size_columns=False,
max_col_width=15,
justification="center",
key="cmd_table",
col_widths=col_widths,
background_color="white",
text_color="black",
expand_x=True,
expand_y=True,
col0_heading="Light",
col0_width=IMAGE_COL_WIDTH,
enable_events=True,
)
],
]
return command_layout
def get_pools_layout():
pool_col_width = int((TABLE_TOTAL_WIDTH - (IP_COL_WIDTH + SPLIT_COL_WIDTH)) / 4)
col_widths = [
IP_COL_WIDTH,
SPLIT_COL_WIDTH,
pool_col_width + 5,
pool_col_width - 5,
pool_col_width + 5,
pool_col_width - 5,
]
pools_layout = [
[
sg.Push(),
sg.Button(
"Miners: 0",
disabled=True,
button_color=("black", "white smoke"),
disabled_button_color=("black", "white smoke"),
key="pools_miner_count",
),
],
[
sg.Button("ALL", key="pools_all"),
sg.Button("REFRESH DATA", key="pools_refresh"),
sg.Button("OPEN IN WEB", key="pools_web"),
],
[
sg.Table(
values=[],
headings=[heading for heading in TABLE_HEADERS["POOLS"]],
auto_size_columns=False,
max_col_width=15,
justification="center",
key="pools_table",
background_color="white",
text_color="black",
col_widths=col_widths,
size=(0, TABLE_HEIGHT),
expand_x=True,
enable_click_events=True,
)
],
]
return pools_layout
def get_config_layout():
config_layout = [
[
sg.Button("IMPORT", key="cfg_import"),
sg.Button("CONFIG", key="cfg_config"),
sg.Button("GENERATE", key="cfg_generate"),
sg.Push(),
sg.Button(
"Miners: 0",
disabled=True,
button_color=("black", "white smoke"),
disabled_button_color=("black", "white smoke"),
key="cfg_miner_count",
),
],
[
sg.Button("ALL", key="cfg_all"),
sg.Button("OPEN IN WEB", key="cfg_web"),
sg.Push(),
sg.Checkbox("Append IP to Username", key="cfg_append_ip"),
],
[
sg.Table(
values=[],
headings=[heading for heading in TABLE_HEADERS["CONFIG"]],
auto_size_columns=False,
max_col_width=15,
justification="center",
key="cfg_table",
background_color="white",
text_color="black",
col_widths=[
IP_COL_WIDTH,
MODEL_COL_WIDTH,
TABLE_TOTAL_WIDTH - ((2 * 40) - 4),
],
size=(0, TABLE_HEIGHT),
expand_x=True,
enable_click_events=True,
),
sg.Multiline(size=(40, TABLE_HEIGHT + 1), key="cfg_config_txt"),
],
]
return config_layout
layout = [
[
sg.Text("", size=(20, 1), key="status"),
sg.ProgressBar(
max_value=100, size_px=(0, 20), expand_x=True, key="progress_bar"
),
sg.Text("", size=(20, 1), key="progress_percent", justification="r"),
],
[
sg.TabGroup(
[
[sg.Tab("Scan", get_scan_layout())],
[sg.Tab("Pools", get_pools_layout())],
[sg.Tab("Configure", get_config_layout())],
[sg.Tab("Command", get_command_layout())],
]
)
],
]
window = sg.Window("Upstream Config Util", layout, icon=WINDOW_ICON)

View File

@@ -0,0 +1,95 @@
import asyncio
from miners.miner_factory import MinerFactory
from network import MinerNetwork
from tools.cfg_util.decorators import disable_buttons
from tools.cfg_util.layout import window, update_prog_bar
from tools.cfg_util.tables import clear_tables, TableManager
progress_bar_len = 0
DEFAULT_DATA = [
"Model",
"Hostname",
"Hashrate",
"Temperature",
"Pool User",
"Pool 1",
"Pool 1 User",
"Pool 2",
"Pool 2 User",
"Wattage",
"Split",
]
async def btn_all():
table = "scan_table"
window[table].update(
select_rows=([row for row in range(len(window[table].Values))])
)
async def btn_scan(scan_ip: str):
network = MinerNetwork("192.168.1.0")
if scan_ip:
if "/" in scan_ip:
ip, mask = scan_ip.split("/")
network = MinerNetwork(ip, mask=mask)
else:
network = MinerNetwork(scan_ip)
asyncio.create_task(_scan_miners(network))
@disable_buttons("Scanning")
async def _scan_miners(network: MinerNetwork):
clear_tables()
scan_generator = network.scan_network_generator()
MinerFactory().clear_cached_miners()
global progress_bar_len
progress_bar_len = 0
network_size = len(network)
await update_prog_bar(progress_bar_len, max=(3 * network_size))
scanned_miners = []
async for miner in scan_generator:
if miner:
scanned_miners.append(miner)
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
progress_bar_len += network_size - len(scanned_miners)
await update_prog_bar(progress_bar_len)
get_miner_genenerator = MinerFactory().get_miner_generator(scanned_miners)
resolved_miners = []
async for found_miner in get_miner_genenerator:
resolved_miners.append(found_miner)
resolved_miners.sort(key=lambda x: x.ip)
_data = {}
for key in DEFAULT_DATA:
_data[key] = ""
_data["IP"] = str(found_miner.ip)
TableManager().update_item(_data)
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
progress_bar_len += network_size - len(resolved_miners)
await update_prog_bar(progress_bar_len)
await _get_miners_data(resolved_miners)
async def _get_miners_data(miners: list):
global progress_bar_len
data_generator = asyncio.as_completed([_get_data(miner) for miner in miners])
for all_data in data_generator:
data = await all_data
TableManager().update_item(data)
progress_bar_len += 1
await update_prog_bar(progress_bar_len)
async def _get_data(miner):
return await miner.get_data()

146
tools/cfg_util/tables.py Normal file
View File

@@ -0,0 +1,146 @@
from tools.cfg_util.layout import (
MINER_COUNT_BUTTONS,
TABLE_KEYS,
TABLE_HEADERS,
window,
)
from tools.cfg_util.imgs import TkImages, LIGHT, FAULT_LIGHT
import PySimpleGUI as sg
import ipaddress
def update_miner_count(count):
for button in MINER_COUNT_BUTTONS:
window[button].update(f"Miners: {count}")
def update_tables(data: list or None = None):
TableManager().update_data(data)
def clear_tables():
TableManager().clear_tables()
async def update_tree(data: list):
for item in data:
if not item.get("IP"):
continue
table_manager = TableManager()
table_manager.update_tree_by_key(item, "IP")
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class TableManager(metaclass=Singleton):
_instance = None
def __init__(self):
self.images = TkImages()
self.data = {}
self.sort_key = "IP"
self.sort_reverse = False
def update_data(self, data: list):
if not data:
return
for line in data:
self.update_item(line)
def update_sort_key(self, sort_key):
if self.sort_key == sort_key:
self.sort_reverse = not self.sort_reverse
self.sort_key = sort_key
self.update_tables()
def update_item(self, data: dict):
if not data or data == {} or not data.get("IP"):
return
if not data.get("Light"):
data["Light"] = False
if not data["IP"] in self.data.keys():
self.data[data["IP"]] = {}
for key in data.keys():
self.data[data["IP"]][key] = data[key]
self.update_tables()
def update_tables(self):
tables = {
"SCAN": [["" for _ in TABLE_HEADERS["SCAN"]] for _ in self.data],
"CMD": [["" for _ in TABLE_HEADERS["CMD"]] for _ in self.data],
"POOLS": [["" for _ in TABLE_HEADERS["POOLS"]] for _ in self.data],
"CONFIG": [["" for _ in TABLE_HEADERS["CONFIG"]] for _ in self.data],
}
ip_sorted_keys = sorted(self.data.keys(), key=lambda x: ipaddress.ip_address(x))
sorted_keys = sorted(
ip_sorted_keys, reverse=self.sort_reverse, key=lambda x: self._get_sort(x)
)
for data_idx, key in enumerate(sorted_keys):
item = self.data[key]
keys = item.keys()
if "Hashrate" in keys:
if not isinstance(item["Hashrate"], str):
item[
"Hashrate"
] = f"{format(float(item['Hashrate']), '.2f').rjust(6, ' ')} TH/s"
for key in keys:
for table in TABLE_HEADERS.keys():
for idx, header in enumerate(TABLE_HEADERS[table]):
if key == header:
tables[table][data_idx][idx] = item[key]
window["scan_table"].update(tables["SCAN"])
window["pools_table"].update(tables["POOLS"])
window["cfg_table"].update(tables["CONFIG"])
treedata = sg.TreeData()
for idx, item in enumerate(tables["CMD"]):
ico = LIGHT
if self.data[item[0]]["Light"]:
ico = FAULT_LIGHT
treedata.insert("", idx, "", item, icon=ico)
window["cmd_table"].update(treedata)
update_miner_count(len(self.data))
def _get_sort(self, data_key: str):
if self.sort_key == "IP":
return ipaddress.ip_address(self.data[data_key]["IP"])
if self.sort_key == "Hashrate":
return float(
self.data[data_key]["Hashrate"].replace(" ", "").replace("TH/s", "")
)
if self.sort_key in ["Wattage", "Temperature"]:
if isinstance(self.data[data_key][self.sort_key], str):
if self.sort_reverse:
return -100000000 # large negative number to place it at the bottom
else:
return 1000000000 # large number to place it at the bottom
return self.data[data_key][self.sort_key]
def clear_tables(self):
self.data = {}
for table in TABLE_KEYS["table"]:
window[table].update([])
for tree in TABLE_KEYS["tree"]:
window[tree].update(sg.TreeData())
update_miner_count(0)

122
tools/cfg_util/ui.py Normal file
View File

@@ -0,0 +1,122 @@
import PySimpleGUI as sg
import asyncio
import sys
from tools.cfg_util.imgs import TkImages
from tools.cfg_util.scan import btn_scan
from tools.cfg_util.commands import (
btn_light,
btn_reboot,
btn_backend,
btn_command,
)
from tools.cfg_util.configure import (
generate_config_ui,
btn_import,
btn_config,
)
from tools.cfg_util.layout import window
from tools.cfg_util.general import btn_all, btn_web, btn_refresh
from tools.cfg_util.tables import TableManager
import tkinter as tk
async def ui():
window.read(0)
# create images used in the table, they will not show if not saved here
tk_imgs = TkImages()
# left justify hostnames
window["scan_table"].Widget.column(2, anchor=tk.W)
# cmd table sort event
# window["cmd_table"].Widget.bind("<Button-1>", lambda x: print("clicked"))
while True:
event, value = window.read(0)
if event in (None, "Close", sg.WIN_CLOSED):
sys.exit()
if isinstance(event, tuple):
if len(window["scan_table"].Values) > 0:
if event[0].endswith("_table"):
if event[2][0] == -1:
mgr = TableManager()
table = window[event[0]].Widget
mgr.update_sort_key(table.heading(event[2][1])["text"])
# scan tab
if event == "scan_all":
_table = "scan_table"
btn_all(_table, value[_table])
if event == "scan_web":
_table = "scan_table"
btn_web(_table, value[_table])
if event == "scan_refresh":
_table = "scan_table"
asyncio.create_task(btn_refresh(_table, value[_table]))
if event == "btn_scan":
asyncio.create_task(btn_scan(value["scan_ip"]))
# pools tab
if event == "pools_all":
_table = "pools_table"
btn_all(_table, value[_table])
if event == "pools_web":
_table = "pools_table"
btn_web(_table, value[_table])
if event == "pools_refresh":
_table = "pools_table"
asyncio.create_task(btn_refresh(_table, value[_table]))
# configure tab
if event == "cfg_all":
_table = "cfg_table"
btn_all(_table, value[_table])
if event == "cfg_web":
_table = "cfg_table"
btn_web(_table, value[_table])
if event == "cfg_generate":
await generate_config_ui()
if event == "cfg_import":
_table = "cfg_table"
asyncio.create_task(btn_import(_table, value[_table]))
if event == "cfg_config":
_table = "cfg_table"
asyncio.create_task(
btn_config(
_table,
value[_table],
value["cfg_config_txt"],
value["cfg_append_ip"],
)
)
# commands tab
if event == "cmd_all":
_table = "cmd_table"
btn_all(_table, value[_table])
if event == "cmd_light":
_table = "cmd_table"
_ips = value[_table]
asyncio.create_task(btn_light(_ips))
if event == "cmd_reboot":
_table = "cmd_table"
_ips = value[_table]
asyncio.create_task(btn_reboot(_ips))
if event == "cmd_backend":
_table = "cmd_table"
_ips = value[_table]
asyncio.create_task(btn_backend(_ips))
if event == "btn_cmd":
_table = "cmd_table"
_ips = value[_table]
asyncio.create_task(btn_command(_ips, value["cmd_txt"]))
if event == "__TIMEOUT__":
await asyncio.sleep(0)
if __name__ == "__main__":
asyncio.run(ui())

View File

@@ -0,0 +1 @@
from tools.cfg_util_old.cfg_util_sg import main

View File

@@ -1,14 +1,8 @@
# TODO: Add Logging
# TODO: Add an option to append the last octet of the IP
# address to the workername when configuring
import asyncio import asyncio
import sys import sys
import logging import logging
from tools.cfg_util.cfg_util_sg.ui import ui from tools.cfg_util_old.cfg_util_sg.ui import ui
# initialize logger and get settings # initialize logger and get settings

View File

@@ -1,4 +1,4 @@
from tools.cfg_util.cfg_util_sg.layout import window from tools.cfg_util_old.cfg_util_sg.layout import window
def disable_buttons(func): def disable_buttons(func):

View File

@@ -6,8 +6,8 @@ import time
import aiofiles import aiofiles
import toml import toml
from tools.cfg_util.cfg_util_sg.func.ui import update_ui_with_data from tools.cfg_util_old.cfg_util_sg.func.ui import update_ui_with_data
from tools.cfg_util.cfg_util_sg.layout import window from tools.cfg_util_old.cfg_util_sg.layout import window
from config.bos import bos_config_convert, general_config_convert_bos from config.bos import bos_config_convert, general_config_convert_bos
@@ -80,13 +80,13 @@ async def import_config_file(file_location):
else: else:
async with aiofiles.open(file_location, mode="r") as file: async with aiofiles.open(file_location, mode="r") as file:
config = await file.read() config = await file.read()
await update_ui_with_data("config", await bos_config_convert(toml.loads(config))) await update_ui_with_data("config", bos_config_convert(toml.loads(config)))
await update_ui_with_data("status", "") await update_ui_with_data("status", "")
async def export_config_file(file_location, config): async def export_config_file(file_location, config):
await update_ui_with_data("status", "Exporting") await update_ui_with_data("status", "Exporting")
config = toml.dumps(await general_config_convert_bos(config)) config = toml.dumps(general_config_convert_bos(config))
config = toml.loads(config) config = toml.loads(config)
config["format"]["generator"] = "upstream_config_util" config["format"]["generator"] = "upstream_config_util"
config["format"]["timestamp"] = int(time.time()) config["format"]["timestamp"] = int(time.time())

View File

@@ -5,16 +5,16 @@ import warnings
import logging import logging
from API import APIError from API import APIError
from tools.cfg_util.cfg_util_sg.func.parse_data import safe_parse_api_data from tools.cfg_util_old.cfg_util_sg.func.parse_data import safe_parse_api_data
from tools.cfg_util.cfg_util_sg.func.ui import ( from tools.cfg_util_old.cfg_util_sg.func.ui import (
update_ui_with_data, update_ui_with_data,
update_prog_bar, update_prog_bar,
set_progress_bar_len, set_progress_bar_len,
) )
from tools.cfg_util.cfg_util_sg.layout import window from tools.cfg_util_old.cfg_util_sg.layout import window
from miners.miner_factory import MinerFactory from miners.miner_factory import MinerFactory
from config.bos import bos_config_convert from config.bos import bos_config_convert
from tools.cfg_util.cfg_util_sg.func.decorators import disable_buttons from tools.cfg_util_old.cfg_util_sg.func.decorators import disable_buttons
from settings import ( from settings import (
CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS, CFG_UTIL_CONFIG_THREADS as CONFIG_THREADS,
CFG_UTIL_REBOOT_THREADS as REBOOT_THREADS, CFG_UTIL_REBOOT_THREADS as REBOOT_THREADS,
@@ -580,4 +580,4 @@ async def generate_config(username, workername, v2_allowed):
}, },
"autotuning": {"enabled": True, "psu_power_limit": 900}, "autotuning": {"enabled": True, "psu_power_limit": 900},
} }
window["config"].update(await bos_config_convert(config)) window["config"].update(bos_config_convert(config))

View File

@@ -1,7 +1,7 @@
import ipaddress import ipaddress
import re import re
from tools.cfg_util.cfg_util_sg.layout import window from tools.cfg_util_old.cfg_util_sg.layout import window
import pyperclip import pyperclip

View File

@@ -3,12 +3,12 @@ import sys
import PySimpleGUI as sg import PySimpleGUI as sg
import tkinter as tk import tkinter as tk
from tools.cfg_util.cfg_util_sg.layout import ( from tools.cfg_util_old.cfg_util_sg.layout import (
window, window,
generate_config_layout, generate_config_layout,
send_ssh_cmd_layout, send_ssh_cmd_layout,
) )
from tools.cfg_util.cfg_util_sg.func.miners import ( from tools.cfg_util_old.cfg_util_sg.func.miners import (
send_config, send_config,
miner_light, miner_light,
refresh_data, refresh_data,
@@ -19,15 +19,15 @@ from tools.cfg_util.cfg_util_sg.func.miners import (
reboot_miners, reboot_miners,
send_miners_ssh_commands, send_miners_ssh_commands,
) )
from tools.cfg_util.cfg_util_sg.func.files import ( from tools.cfg_util_old.cfg_util_sg.func.files import (
import_iplist, import_iplist,
import_config_file, import_config_file,
export_iplist, export_iplist,
export_config_file, export_config_file,
export_csv, export_csv,
) )
from tools.cfg_util.cfg_util_sg.func.decorators import disable_buttons from tools.cfg_util_old.cfg_util_sg.func.decorators import disable_buttons
from tools.cfg_util.cfg_util_sg.func.ui import ( from tools.cfg_util_old.cfg_util_sg.func.ui import (
sort_data, sort_data,
copy_from_table, copy_from_table,
table_select_all, table_select_all,