set up config tool for braiins OS
This commit is contained in:
188
config_tool.py
Normal file
188
config_tool.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import asyncio
|
||||||
|
import ipaddress
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import PySimpleGUI as sg
|
||||||
|
import aiofiles
|
||||||
|
import toml
|
||||||
|
|
||||||
|
from miners.miner_factory import MinerFactory
|
||||||
|
from network import MinerNetwork
|
||||||
|
|
||||||
|
layout = [
|
||||||
|
[sg.Text('Network IP: '), sg.InputText(key='miner_network', do_not_clear=True, size=(70, 1)),
|
||||||
|
sg.Button('Scan', key='scan'),
|
||||||
|
sg.Text("", key="status")],
|
||||||
|
[sg.Text('IP List File: '), sg.Input(key="file_iplist", do_not_clear=True, size=(70, 1)), sg.FileBrowse(),
|
||||||
|
sg.Button('Import', key="import_iplist")],
|
||||||
|
[sg.Text('Config File: '), sg.Input(key="file_config", do_not_clear=True, size=(70, 1)), sg.FileBrowse(),
|
||||||
|
sg.Button('Import', key="import_file_config"), sg.Button('Export', key="export_file_config")],
|
||||||
|
[
|
||||||
|
sg.Column([
|
||||||
|
[sg.Text("IP List:", pad=(0, 0)), sg.Text("", key="ip_count", pad=(1, 0), size=(3, 1)),
|
||||||
|
sg.Button('ALL', key="select_all_ips")],
|
||||||
|
[sg.Listbox([], size=(20, 32), key="ip_list", select_mode='extended')]
|
||||||
|
]),
|
||||||
|
sg.Column([
|
||||||
|
[sg.Text("Hashrates: ", pad=(0, 0)), sg.Button('GET', key="get_hashrates")],
|
||||||
|
[sg.Listbox([], size=(25, 32), key="hr_list")]
|
||||||
|
]),
|
||||||
|
sg.Column([
|
||||||
|
[sg.Text("Config"), sg.Button("IMPORT", key="import_config"), sg.Button("CONFIG", key="send_config"),
|
||||||
|
sg.Button("LIGHT", key="light")],
|
||||||
|
[sg.Multiline(size=(50, 34), key="config", do_not_clear=True)],
|
||||||
|
])
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
window = sg.Window('Test', layout)
|
||||||
|
miner_factory = MinerFactory()
|
||||||
|
|
||||||
|
|
||||||
|
async def update_ui_with_data(key, data, append=False):
|
||||||
|
if append:
|
||||||
|
data = window[key].get_text() + data
|
||||||
|
window[key].update(data)
|
||||||
|
|
||||||
|
|
||||||
|
async def scan_network(network):
|
||||||
|
global window
|
||||||
|
await update_ui_with_data("status", "Scanning")
|
||||||
|
miners = await network.scan_network_for_miners()
|
||||||
|
window["ip_list"].update([str(miner.ip) for miner in miners])
|
||||||
|
await update_ui_with_data("ip_count", str(len(miners)))
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def miner_light(ips: list):
|
||||||
|
listbox = window['ip_list']
|
||||||
|
for ip in ips:
|
||||||
|
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')
|
||||||
|
else:
|
||||||
|
listbox.itemconfigure(index, bg='red', fg='white')
|
||||||
|
|
||||||
|
|
||||||
|
async def import_config(ip):
|
||||||
|
await update_ui_with_data("status", "Importing")
|
||||||
|
miner = await miner_factory.get_miner(ipaddress.ip_address(*ip))
|
||||||
|
await miner.get_config()
|
||||||
|
config = miner.config
|
||||||
|
await update_ui_with_data("config", toml.dumps(config))
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def import_iplist(file_location):
|
||||||
|
await update_ui_with_data("status", "Importing")
|
||||||
|
if not os.path.exists(file_location):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
ip_list = []
|
||||||
|
async with aiofiles.open(file_location, mode='r') as file:
|
||||||
|
async for line in file:
|
||||||
|
ips = [x.group() for x in re.finditer("^((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]?)", line)]
|
||||||
|
for ip in ips:
|
||||||
|
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])
|
||||||
|
await update_ui_with_data("ip_count", str(len(ip_list)))
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def send_config(ips: list, config):
|
||||||
|
await update_ui_with_data("status", "Configuring")
|
||||||
|
tasks = []
|
||||||
|
for ip in ips:
|
||||||
|
tasks.append(miner_factory.get_miner(ip))
|
||||||
|
miners = await asyncio.gather(*tasks)
|
||||||
|
tasks = []
|
||||||
|
for miner in miners:
|
||||||
|
tasks.append(miner.send_config(config))
|
||||||
|
await asyncio.gather(*tasks)
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def import_config_file(file_location):
|
||||||
|
await update_ui_with_data("status", "Importing")
|
||||||
|
if not os.path.exists(file_location):
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
async with aiofiles.open(file_location, mode='r') as file:
|
||||||
|
config = await file.read()
|
||||||
|
await update_ui_with_data("config", str(config))
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def export_config_file(file_location, config):
|
||||||
|
await update_ui_with_data("status", "Exporting")
|
||||||
|
async with aiofiles.open(file_location, mode='w+') as file:
|
||||||
|
await file.write(config)
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Add get hashrates function to get hashrates in form of IP | HR (192.168.1.1 | 13.5 TH/s)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_hashrates(ip_list: list):
|
||||||
|
await update_ui_with_data("status", "Getting HR")
|
||||||
|
ips = [ipaddress.ip_address(ip) for ip in ip_list]
|
||||||
|
ips.sort()
|
||||||
|
data = await asyncio.gather(*[get_formatted_hashrate(miner) for miner in ips])
|
||||||
|
window["hr_list"].update(disabled=False)
|
||||||
|
window["hr_list"].update([item['IP'] + " | " + str(item['TH/s']) + " TH/s" for item in data])
|
||||||
|
window["hr_list"].update(disabled=True)
|
||||||
|
|
||||||
|
await update_ui_with_data("status", "")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_formatted_hashrate(ip: ipaddress.ip_address):
|
||||||
|
miner = await miner_factory.get_miner(ip)
|
||||||
|
data = await miner.api.summary()
|
||||||
|
mh5s = round(data['SUMMARY'][0]['MHS 5s'] / 1000000, 2)
|
||||||
|
return {'TH/s': mh5s, 'IP': str(miner.ip)}
|
||||||
|
|
||||||
|
|
||||||
|
async def ui():
|
||||||
|
while True:
|
||||||
|
event, value = window.read(timeout=10)
|
||||||
|
if event in (None, 'Close'):
|
||||||
|
sys.exit()
|
||||||
|
if event == 'scan':
|
||||||
|
if len(value['miner_network'].split("/")) > 1:
|
||||||
|
network = value['miner_network'].split("/")
|
||||||
|
miner_network = MinerNetwork(ip_addr=network[0], mask=network[1])
|
||||||
|
else:
|
||||||
|
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([])
|
||||||
|
else:
|
||||||
|
window['ip_list'].set_value(window['ip_list'].Values)
|
||||||
|
if event == 'import_config':
|
||||||
|
if 2 > len(value['ip_list']) > 0:
|
||||||
|
asyncio.create_task(import_config(value['ip_list']))
|
||||||
|
if event == 'light':
|
||||||
|
asyncio.create_task(miner_light(value['ip_list']))
|
||||||
|
if event == "import_iplist":
|
||||||
|
asyncio.create_task(import_iplist(value["file_iplist"]))
|
||||||
|
if event == "send_config":
|
||||||
|
asyncio.create_task(send_config(value['ip_list'], toml.loads(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_hashrates":
|
||||||
|
asyncio.create_task(get_hashrates(value['ip_list']))
|
||||||
|
if event == "__TIMEOUT__":
|
||||||
|
await asyncio.sleep(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
loop = asyncio.new_event_loop()
|
||||||
|
loop.run_until_complete(ui())
|
||||||
17
main.py
17
main.py
@@ -5,10 +5,19 @@ import asyncio
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
miner_network = MinerNetwork('192.168.1.1')
|
miner_network = MinerNetwork('192.168.1.1')
|
||||||
data = await miner_network.scan_network_for_miners()
|
miners = await miner_network.scan_network_for_miners()
|
||||||
await data[0].get_config()
|
# print("\n".join([str(miner.ip) for miner in miners]))
|
||||||
config = await data[0].set_config_format()
|
good_list = list(filter(None, await asyncio.gather(*[miner.check_good_boards() for miner in miners])))
|
||||||
print(config)
|
print("\n".join(good_list))
|
||||||
|
print(len(good_list))
|
||||||
|
print('\n'.join([f"{str(miner.ip)}" for miner in miners]))
|
||||||
|
|
||||||
|
async def main_bad():
|
||||||
|
miner_network = MinerNetwork('192.168.1.1')
|
||||||
|
miners = await miner_network.scan_network_for_miners()
|
||||||
|
bad_list = list(filter(None, await asyncio.gather(*[miner.get_bad_boards() for miner in miners])))
|
||||||
|
print(bad_list)
|
||||||
|
print(len(bad_list))
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
asyncio.get_event_loop().run_until_complete(main())
|
asyncio.get_event_loop().run_until_complete(main())
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
from API.bosminer import BOSMinerAPI
|
from API import BaseMinerAPI
|
||||||
from API.cgminer import CGMinerAPI
|
|
||||||
from API.bmminer import BMMinerAPI
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class BaseMiner:
|
class BaseMiner:
|
||||||
def __init__(self, ip: str, api: BOSMinerAPI or CGMinerAPI or BMMinerAPI):
|
def __init__(self, ip: str, api: typing.Type[BaseMinerAPI]):
|
||||||
self.ip = ipaddress.ip_address(ip)
|
self.ip = ipaddress.ip_address(ip)
|
||||||
self.api = api
|
self.api = api
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ class BMMiner(BaseMiner):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"BMMiner: {str(self.ip)}"
|
return f"BMMiner: {str(self.ip)}"
|
||||||
|
|
||||||
|
async def send_config(self):
|
||||||
|
return None
|
||||||
|
|||||||
@@ -97,3 +97,32 @@ class BOSminer(BaseMiner):
|
|||||||
if update_config:
|
if update_config:
|
||||||
self.update_config(config)
|
self.update_config(config)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
async def send_config(self, config):
|
||||||
|
toml_conf = toml.dumps(config)
|
||||||
|
async with asyncssh.connect(str(self.ip), known_hosts=None, username='root', password='admin',
|
||||||
|
server_host_key_algs=['ssh-rsa']) as conn:
|
||||||
|
async with conn.start_sftp_client() as sftp:
|
||||||
|
async with sftp.open('/etc/bosminer.toml', 'w+') as file:
|
||||||
|
await file.write(toml_conf)
|
||||||
|
await conn.run("/etc/init.d/bosminer restart")
|
||||||
|
|
||||||
|
async def get_bad_boards(self):
|
||||||
|
devs = await self.api.devdetails()
|
||||||
|
bad = 0
|
||||||
|
chains = devs['DEVDETAILS']
|
||||||
|
for chain in chains:
|
||||||
|
if chain['Chips'] == 0:
|
||||||
|
bad += 1
|
||||||
|
if bad > 0:
|
||||||
|
return (str(self.ip), bad)
|
||||||
|
|
||||||
|
async def check_good_boards(self):
|
||||||
|
devs = await self.api.devdetails()
|
||||||
|
bad = 0
|
||||||
|
chains = devs['DEVDETAILS']
|
||||||
|
for chain in chains:
|
||||||
|
if chain['Chips'] == 0:
|
||||||
|
bad += 1
|
||||||
|
if not bad > 0:
|
||||||
|
return str(self.ip)
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ class CGMiner(BaseMiner):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"CGMiner: {str(self.ip)}"
|
return f"CGMiner: {str(self.ip)}"
|
||||||
|
|
||||||
|
async def send_config(self):
|
||||||
|
return None
|
||||||
|
|||||||
@@ -6,28 +6,41 @@ from API import APIError
|
|||||||
import asyncio
|
import asyncio
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import json
|
import json
|
||||||
|
import typing
|
||||||
|
|
||||||
|
|
||||||
class MinerFactory:
|
class MinerFactory:
|
||||||
|
def __init__(self):
|
||||||
|
self.miners = {}
|
||||||
|
|
||||||
async def get_miner(self, ip: ipaddress.ip_address):
|
async def get_miner(self, ip: ipaddress.ip_address):
|
||||||
|
if ip in self.miners:
|
||||||
|
return self.miners[ip]
|
||||||
version_data = await self._get_version_data(ip)
|
version_data = await self._get_version_data(ip)
|
||||||
version = None
|
version = None
|
||||||
if version_data:
|
if version_data:
|
||||||
version = list(version_data['VERSION'][0].keys())
|
version = list(version_data['VERSION'][0].keys())
|
||||||
if version:
|
if version:
|
||||||
if "BOSminer" in version or "BOSminer+" in version:
|
if "BOSminer" in version or "BOSminer+" in version:
|
||||||
return BOSminer(str(ip))
|
miner = BOSminer(str(ip))
|
||||||
elif version == "CGMiner":
|
elif version == "CGMiner":
|
||||||
return CGMiner(str(ip))
|
miner = CGMiner(str(ip))
|
||||||
elif version == "BMMiner":
|
elif version == "BMMiner":
|
||||||
return BMMiner(str(ip))
|
miner = BMMiner(str(ip))
|
||||||
return UnknownMiner(str(ip))
|
else:
|
||||||
|
miner = UnknownMiner(str(ip))
|
||||||
|
else:
|
||||||
|
miner = UnknownMiner(str(ip))
|
||||||
|
self.miners[ip] = miner
|
||||||
|
return miner
|
||||||
|
|
||||||
async def _get_version_data(self, ip: ipaddress.ip_address):
|
@staticmethod
|
||||||
|
async def _get_version_data(ip: ipaddress.ip_address):
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
try:
|
try:
|
||||||
|
fut = asyncio.open_connection(str(ip), 4028)
|
||||||
# get reader and writer streams
|
# get reader and writer streams
|
||||||
reader, writer = await asyncio.open_connection(str(ip), 4028)
|
reader, writer = await asyncio.wait_for(fut, timeout=5)
|
||||||
|
|
||||||
# create the command
|
# create the command
|
||||||
cmd = {"command": "version"}
|
cmd = {"command": "version"}
|
||||||
@@ -70,6 +83,11 @@ class MinerFactory:
|
|||||||
|
|
||||||
# return the data
|
# return the data
|
||||||
return data
|
return data
|
||||||
|
except OSError as e:
|
||||||
|
if e.winerror == 121:
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
print(ip, e)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(ip, e)
|
print(ip, e)
|
||||||
return None
|
return None
|
||||||
|
|||||||
@@ -9,3 +9,6 @@ class UnknownMiner(BaseMiner):
|
|||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Unknown: {str(self.ip)}"
|
return f"Unknown: {str(self.ip)}"
|
||||||
|
|
||||||
|
async def send_config(self):
|
||||||
|
return None
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ PING_TIMEOUT: int = 1
|
|||||||
|
|
||||||
|
|
||||||
class MinerNetwork:
|
class MinerNetwork:
|
||||||
def __init__(self, ip_addr=None):
|
def __init__(self, ip_addr=None, mask=None):
|
||||||
self.network = None
|
self.network = None
|
||||||
self.miner_factory = MinerFactory()
|
self.miner_factory = MinerFactory()
|
||||||
self.ip_addr = ip_addr
|
self.ip_addr = ip_addr
|
||||||
self.connected_miners = {}
|
self.connected_miners = {}
|
||||||
|
self.mask = mask
|
||||||
|
|
||||||
def get_network(self):
|
def get_network(self):
|
||||||
if self.network:
|
if self.network:
|
||||||
@@ -22,7 +23,10 @@ class MinerNetwork:
|
|||||||
default_gateway = gateways['default'][netifaces.AF_INET][0]
|
default_gateway = gateways['default'][netifaces.AF_INET][0]
|
||||||
else:
|
else:
|
||||||
default_gateway = self.ip_addr
|
default_gateway = self.ip_addr
|
||||||
subnet_mask = netifaces.ifaddresses(gateways['default'][netifaces.AF_INET][1])[netifaces.AF_INET][0]['netmask']
|
if self.mask:
|
||||||
|
subnet_mask = self.mask
|
||||||
|
else:
|
||||||
|
subnet_mask = netifaces.ifaddresses(gateways['default'][netifaces.AF_INET][1])[netifaces.AF_INET][0]['netmask']
|
||||||
return ipaddress.ip_network(f"{default_gateway}/{subnet_mask}", strict=False)
|
return ipaddress.ip_network(f"{default_gateway}/{subnet_mask}", strict=False)
|
||||||
|
|
||||||
async def scan_network_for_miners(self):
|
async def scan_network_for_miners(self):
|
||||||
|
|||||||
Reference in New Issue
Block a user