Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8677eff491 | ||
|
|
63a21ea9aa | ||
|
|
1c9d3dc84d | ||
|
|
0dacd3d294 | ||
|
|
6fa74613b4 | ||
|
|
f7fb7a3acb | ||
|
|
666c5bfc64 | ||
|
|
1f8d92f6bb | ||
|
|
ef336a9e23 | ||
|
|
7fe6fd47fb | ||
|
|
91a0298d96 | ||
|
|
ed3d8fc815 | ||
|
|
4f2d630746 | ||
|
|
a8c685a883 | ||
|
|
09660e1934 | ||
|
|
c01908ff9a | ||
|
|
267c388a95 | ||
|
|
8215d33241 | ||
|
|
f4258a304a | ||
|
|
514fafea58 | ||
|
|
e324369fe0 | ||
|
|
3bc9287668 | ||
|
|
d90bf190c5 | ||
|
|
8cc6f66458 | ||
|
|
a2b071af4f | ||
|
|
b7b589802f | ||
|
|
93912a6df6 | ||
|
|
ffce15f653 | ||
|
|
725b14e583 | ||
|
|
26c6e47f1e | ||
|
|
51dae7375f | ||
|
|
801cfc4ff8 | ||
|
|
ac3ff7a63e | ||
|
|
1b22810f4b | ||
|
|
b756c9e4a1 | ||
|
|
64b5e6c032 | ||
|
|
a13f5dd2d1 | ||
|
|
e6ea8d3e16 | ||
|
|
af37850289 | ||
|
|
6ecdfa1cf8 | ||
|
|
c0b21ebc23 | ||
|
|
184ada417f | ||
|
|
b636860ecb | ||
|
|
0107fdacde |
48
README.md
48
README.md
@@ -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,15 +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")))
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 False
|
||||||
|
|
||||||
|
async def send_config(self, *args, **kwargs):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
async def send_config(self, yaml_config):
|
async def get_data(self):
|
||||||
return None
|
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
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ class CGMinerAvalon10(CGMiner):
|
|||||||
super().__init__(ip)
|
super().__init__(ip)
|
||||||
self.model = "Avalon 10"
|
self.model = "Avalon 10"
|
||||||
self.api_type = "CGMiner"
|
self.api_type = "CGMiner"
|
||||||
|
self.nominal_chips = 114
|
||||||
|
|
||||||
async def get_hostname(self):
|
async def get_hostname(self):
|
||||||
try:
|
try:
|
||||||
@@ -22,3 +23,39 @@ class CGMinerAvalon10(CGMiner):
|
|||||||
return "?"
|
return "?"
|
||||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||||
return "?"
|
return "?"
|
||||||
|
|
||||||
|
async def get_board_info(self):
|
||||||
|
boards_chips = 0
|
||||||
|
logging.debug(f"{self}: Getting board info.")
|
||||||
|
stats = await self.api.stats()
|
||||||
|
if not stats.get("STATS") and not stats.get("STATS") == []:
|
||||||
|
print("stats error", stats)
|
||||||
|
return {0: [], 1: [], 2: []}
|
||||||
|
stats = stats["STATS"][0]
|
||||||
|
for key in stats.keys():
|
||||||
|
if key.startswith("MM") and not stats[key] == 1:
|
||||||
|
data = stats[key]
|
||||||
|
for line in data.split("]"):
|
||||||
|
if "TA[" in line:
|
||||||
|
total_chips = line.replace("TA[", "")
|
||||||
|
boards_chips = round(int(total_chips)/3)
|
||||||
|
boards = {}
|
||||||
|
for board in [0, 1, 2]:
|
||||||
|
if not boards_chips == self.nominal_chips:
|
||||||
|
nominal = False
|
||||||
|
else:
|
||||||
|
nominal = True
|
||||||
|
boards[board] = []
|
||||||
|
boards[board].append({
|
||||||
|
"chain": board,
|
||||||
|
"chip_count": boards_chips,
|
||||||
|
"chip_status": "o" * boards_chips,
|
||||||
|
"nominal": nominal,
|
||||||
|
})
|
||||||
|
return boards
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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"] = str(quota)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -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"] = str(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
|
||||||
|
|||||||
@@ -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"] = str(quota)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -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"] = str(quota)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|||||||
@@ -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
|
|
||||||
|
|||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
@@ -1 +1,20 @@
|
|||||||
from tools.cfg_util.cfg_util_sg import main
|
import asyncio
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .ui import ui
|
||||||
|
|
||||||
|
# Fix bug with some whatsminers and asyncio because of a socket not being shut down:
|
||||||
|
if (
|
||||||
|
sys.version_info[0] == 3
|
||||||
|
and sys.version_info[1] >= 8
|
||||||
|
and sys.platform.startswith("win")
|
||||||
|
):
|
||||||
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
asyncio.run(ui())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
78
tools/cfg_util/commands/__init__.py
Normal file
78
tools/cfg_util/commands/__init__.py
Normal 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()
|
||||||
160
tools/cfg_util/configure/__init__.py
Normal file
160
tools/cfg_util/configure/__init__.py
Normal 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
|
||||||
24
tools/cfg_util/decorators.py
Normal file
24
tools/cfg_util/decorators.py
Normal 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
|
||||||
85
tools/cfg_util/general/__init__.py
Normal file
85
tools/cfg_util/general/__init__.py
Normal 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
27
tools/cfg_util/imgs.py
Normal file
File diff suppressed because one or more lines are too long
661
tools/cfg_util/layout.py
Normal file
661
tools/cfg_util/layout.py
Normal file
@@ -0,0 +1,661 @@
|
|||||||
|
import PySimpleGUI as sg
|
||||||
|
|
||||||
|
from .imgs import WINDOW_ICON
|
||||||
|
|
||||||
|
WINDOW_BG = "#0F4C75"
|
||||||
|
|
||||||
|
PROGRESS_BG = "#FFFFFF"
|
||||||
|
PROGRESS_FULL = "#00A8CC"
|
||||||
|
|
||||||
|
MAIN_TABS_BG = "#0F4C75"
|
||||||
|
MAIN_TABS_SELECTED = MAIN_TABS_BG
|
||||||
|
MAIN_TABS_NORMAL = "#BBE1FA"
|
||||||
|
MAIN_TABS_TEXT_SELECTED = "#FFFFFF"
|
||||||
|
MAIN_TABS_TEXT_NORMAL = "#000000"
|
||||||
|
|
||||||
|
TAB_PAD = 0
|
||||||
|
|
||||||
|
TEXT_COLOR = "#FFFFFF"
|
||||||
|
BTN_TEXT_COLOR = "#000000"
|
||||||
|
BTN_COLOR = "#3282B8"
|
||||||
|
BTN_DISABLED_COLOR = "#BBE1FA"
|
||||||
|
BTN_DISABLED_TEXT_COLOR = "#1B262C"
|
||||||
|
BTN_DISABLED = BTN_DISABLED_TEXT_COLOR, BTN_DISABLED_COLOR
|
||||||
|
BTN_BORDER = 1
|
||||||
|
|
||||||
|
INFO_BTN_TEXT_COLOR = "#000000"
|
||||||
|
INFO_BTN_BG = "#FFFFFF"
|
||||||
|
|
||||||
|
INPUT_BG = "#BBE1FA"
|
||||||
|
INPUT_TEXT = "#000000"
|
||||||
|
|
||||||
|
POOLS_TABS_BG = "#3282B8"
|
||||||
|
POOLS_TABS_SELECTED = POOLS_TABS_BG
|
||||||
|
POOLS_TABS_NORMAL = "#BBE1FA"
|
||||||
|
POOLS_TABS_TEXT_SELECTED = "#FFFFFF"
|
||||||
|
POOLS_TABS_TEXT_NORMAL = "#000000"
|
||||||
|
POOLS_TABLE_PAD = 0
|
||||||
|
|
||||||
|
TABLE_BG = "#BBE1FA"
|
||||||
|
TABLE_TEXT = "#000000"
|
||||||
|
TABLE_HEADERS_COLOR = "#3282B8"
|
||||||
|
TABLE_HEADERS_TEXT_COLOR = "#000000"
|
||||||
|
TABLE_HEADERS_HOVER = "#27496D"
|
||||||
|
TABLE_BORDER = 1
|
||||||
|
TABLE_HEADER_BORDER = 3
|
||||||
|
TABLE_PAD = 0
|
||||||
|
|
||||||
|
SCROLLBAR_TROUGH_COLOR = "#BBE1FA"
|
||||||
|
SCROLLBAR_BACKGROUND_COLOR = "#3282B8"
|
||||||
|
SCROLLBAR_ARROW_COLOR = "#0F4C75"
|
||||||
|
SCROLLBAR_WIDTH = 16
|
||||||
|
SCROLLBAR_ARROW_WIDTH = 16
|
||||||
|
SCROLLBAR_RELIEF = sg.RELIEF_RIDGE
|
||||||
|
|
||||||
|
POOLS_TABLE_BG = TABLE_BG
|
||||||
|
POOLS_TABLE_TEXT = TABLE_TEXT
|
||||||
|
POOLS_TABLE_HEADERS_COLOR = TABLE_HEADERS_COLOR
|
||||||
|
POOLS_TABLE_HEADERS_TEXT_COLOR = TABLE_HEADERS_TEXT_COLOR
|
||||||
|
POOLS_TABLE_HEADERS_HOVER = TABLE_HEADERS_HOVER
|
||||||
|
POOLS_TABLE_BORDER = 1
|
||||||
|
POOLS_TABLE_HEADER_BORDER = 3
|
||||||
|
|
||||||
|
sg.set_options(font=("Noto Mono", 10))
|
||||||
|
# Add your new theme colors and settings
|
||||||
|
|
||||||
|
|
||||||
|
sg.LOOK_AND_FEEL_TABLE["cfg_util_theme"] = {
|
||||||
|
"BACKGROUND": WINDOW_BG,
|
||||||
|
"TEXT": TEXT_COLOR,
|
||||||
|
"INPUT": INPUT_BG,
|
||||||
|
"TEXT_INPUT": INPUT_TEXT,
|
||||||
|
"SCROLL": "#142850",
|
||||||
|
"BUTTON": (BTN_TEXT_COLOR, BTN_COLOR), # Text Color, Background
|
||||||
|
"PROGRESS": (PROGRESS_FULL, PROGRESS_BG), # Filled, Empty
|
||||||
|
"BORDER": 1,
|
||||||
|
"SLIDER_DEPTH": 0,
|
||||||
|
"PROGRESS_DEPTH": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Switch to use your newly created theme
|
||||||
|
sg.theme("cfg_util_theme")
|
||||||
|
|
||||||
|
TABLE_HEADERS = {
|
||||||
|
"SCAN": [
|
||||||
|
"IP",
|
||||||
|
"Model",
|
||||||
|
"Hostname",
|
||||||
|
"Hashrate",
|
||||||
|
"Temperature",
|
||||||
|
"Pool User",
|
||||||
|
"Wattage",
|
||||||
|
],
|
||||||
|
"CMD": ["IP", "Model", "Output"],
|
||||||
|
"POOLS_ALL": [
|
||||||
|
"IP",
|
||||||
|
"Split",
|
||||||
|
"Pool 1 User",
|
||||||
|
"Pool 2 User",
|
||||||
|
],
|
||||||
|
"POOLS_1": [
|
||||||
|
"IP",
|
||||||
|
"Split",
|
||||||
|
"Pool 1",
|
||||||
|
"Pool 1 User",
|
||||||
|
],
|
||||||
|
"POOLS_2": [
|
||||||
|
"IP",
|
||||||
|
"Split",
|
||||||
|
"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 = [
|
||||||
|
"miner_count",
|
||||||
|
]
|
||||||
|
|
||||||
|
HASHRATE_TOTAL_BUTTONS = [
|
||||||
|
"total_hashrate",
|
||||||
|
]
|
||||||
|
|
||||||
|
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 = 8
|
||||||
|
IP_COL_WIDTH = 17
|
||||||
|
MODEL_COL_WIDTH = 15
|
||||||
|
HOST_COL_WIDTH = 15
|
||||||
|
HASHRATE_COL_WIDTH = 12
|
||||||
|
TEMP_COL_WIDTH = 14
|
||||||
|
USER_COL_WIDTH = 27
|
||||||
|
WATTAGE_COL_WIDTH = 10
|
||||||
|
SPLIT_COL_WIDTH = 8
|
||||||
|
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", background_color=MAIN_TABS_BG, pad=((0, 5), (1, 1))),
|
||||||
|
sg.InputText(key="scan_ip", size=(31, 1)),
|
||||||
|
sg.Button(
|
||||||
|
"Scan",
|
||||||
|
key="btn_scan",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
mouseover_colors=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
"ALL",
|
||||||
|
key="scan_all",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (1, 1)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"REFRESH DATA",
|
||||||
|
key="scan_refresh",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"OPEN IN WEB",
|
||||||
|
key="scan_web",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
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=TABLE_BG,
|
||||||
|
text_color=TABLE_TEXT,
|
||||||
|
header_background_color=TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
border_width=TABLE_BORDER,
|
||||||
|
header_border_width=TABLE_HEADER_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
size=(TABLE_TOTAL_WIDTH, TABLE_HEIGHT),
|
||||||
|
expand_x=True,
|
||||||
|
enable_click_events=True,
|
||||||
|
pad=TABLE_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
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",
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
pad=((0, 1), (1, 1)),
|
||||||
|
),
|
||||||
|
sg.InputText(key="cmd_txt", expand_x=True),
|
||||||
|
sg.Button(
|
||||||
|
"Send Command",
|
||||||
|
key="btn_cmd",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
"ALL",
|
||||||
|
key="cmd_all",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 1), (1, 1)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"LIGHT",
|
||||||
|
key="cmd_light",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"REBOOT",
|
||||||
|
key="cmd_reboot",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"RESTART BACKEND",
|
||||||
|
key="cmd_backend",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
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=TABLE_BG,
|
||||||
|
text_color=TABLE_TEXT,
|
||||||
|
header_background_color=TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
border_width=TABLE_BORDER,
|
||||||
|
header_border_width=TABLE_HEADER_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
expand_x=True,
|
||||||
|
expand_y=True,
|
||||||
|
col0_heading="Light",
|
||||||
|
col0_width=IMAGE_COL_WIDTH,
|
||||||
|
enable_events=True,
|
||||||
|
pad=TABLE_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
return command_layout
|
||||||
|
|
||||||
|
|
||||||
|
def get_pools_layout():
|
||||||
|
pool_col_width = int((TABLE_TOTAL_WIDTH - (IP_COL_WIDTH + SPLIT_COL_WIDTH)) / 2)
|
||||||
|
col_widths = [
|
||||||
|
IP_COL_WIDTH,
|
||||||
|
SPLIT_COL_WIDTH,
|
||||||
|
pool_col_width,
|
||||||
|
pool_col_width,
|
||||||
|
]
|
||||||
|
pools_layout = [
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
"ALL",
|
||||||
|
key="pools_all",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (6, 7)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"REFRESH DATA",
|
||||||
|
key="pools_refresh",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"OPEN IN WEB",
|
||||||
|
key="pools_web",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.TabGroup(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"All",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
sg.Table(
|
||||||
|
values=[],
|
||||||
|
headings=[
|
||||||
|
heading
|
||||||
|
for heading in TABLE_HEADERS["POOLS_ALL"]
|
||||||
|
],
|
||||||
|
auto_size_columns=False,
|
||||||
|
max_col_width=15,
|
||||||
|
justification="center",
|
||||||
|
key="pools_table",
|
||||||
|
background_color=POOLS_TABLE_BG,
|
||||||
|
text_color=POOLS_TABLE_TEXT,
|
||||||
|
header_background_color=POOLS_TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=POOLS_TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
border_width=POOLS_TABLE_BORDER,
|
||||||
|
header_border_width=POOLS_TABLE_HEADER_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
col_widths=col_widths,
|
||||||
|
size=(0, TABLE_HEIGHT),
|
||||||
|
expand_x=True,
|
||||||
|
enable_click_events=True,
|
||||||
|
pad=POOLS_TABLE_PAD,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
background_color=POOLS_TABS_BG,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Pool 1",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
sg.Table(
|
||||||
|
values=[],
|
||||||
|
headings=[
|
||||||
|
heading
|
||||||
|
for heading in TABLE_HEADERS["POOLS_1"]
|
||||||
|
],
|
||||||
|
auto_size_columns=False,
|
||||||
|
max_col_width=15,
|
||||||
|
justification="center",
|
||||||
|
key="pools_1_table",
|
||||||
|
background_color=POOLS_TABLE_BG,
|
||||||
|
text_color=POOLS_TABLE_TEXT,
|
||||||
|
header_background_color=POOLS_TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=POOLS_TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
border_width=POOLS_TABLE_BORDER,
|
||||||
|
header_border_width=POOLS_TABLE_HEADER_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
col_widths=col_widths,
|
||||||
|
size=(0, TABLE_HEIGHT),
|
||||||
|
expand_x=True,
|
||||||
|
enable_click_events=True,
|
||||||
|
pad=POOLS_TABLE_PAD,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
background_color=POOLS_TABS_BG,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Pool 2",
|
||||||
|
[
|
||||||
|
[
|
||||||
|
sg.Table(
|
||||||
|
values=[],
|
||||||
|
headings=[
|
||||||
|
heading
|
||||||
|
for heading in TABLE_HEADERS["POOLS_2"]
|
||||||
|
],
|
||||||
|
auto_size_columns=False,
|
||||||
|
max_col_width=15,
|
||||||
|
justification="center",
|
||||||
|
key="pools_2_table",
|
||||||
|
background_color=POOLS_TABLE_BG,
|
||||||
|
text_color=POOLS_TABLE_TEXT,
|
||||||
|
header_background_color=POOLS_TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=POOLS_TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
border_width=POOLS_TABLE_BORDER,
|
||||||
|
header_border_width=POOLS_TABLE_HEADER_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
col_widths=col_widths,
|
||||||
|
size=(0, TABLE_HEIGHT),
|
||||||
|
expand_x=True,
|
||||||
|
enable_click_events=True,
|
||||||
|
pad=POOLS_TABLE_PAD,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
],
|
||||||
|
background_color=POOLS_TABS_BG,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
title_color=POOLS_TABS_TEXT_NORMAL,
|
||||||
|
tab_background_color=POOLS_TABS_NORMAL,
|
||||||
|
selected_background_color=POOLS_TABS_SELECTED,
|
||||||
|
selected_title_color=POOLS_TABS_TEXT_SELECTED,
|
||||||
|
border_width=0,
|
||||||
|
tab_border_width=2,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
]
|
||||||
|
return pools_layout
|
||||||
|
|
||||||
|
|
||||||
|
def get_config_layout():
|
||||||
|
config_layout = [
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
"IMPORT",
|
||||||
|
key="cfg_import",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (5, 0)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"CONFIG",
|
||||||
|
key="cfg_config",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (5, 0)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"GENERATE",
|
||||||
|
key="cfg_generate",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (5, 0)),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Button(
|
||||||
|
"ALL",
|
||||||
|
key="cfg_all",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
pad=((0, 5), (1, 1)),
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"OPEN IN WEB",
|
||||||
|
key="cfg_web",
|
||||||
|
border_width=BTN_BORDER,
|
||||||
|
disabled_button_color=BTN_DISABLED,
|
||||||
|
),
|
||||||
|
sg.Push(background_color=MAIN_TABS_BG),
|
||||||
|
sg.Checkbox(
|
||||||
|
"Append IP to Username",
|
||||||
|
key="cfg_append_ip",
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
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=TABLE_BG,
|
||||||
|
text_color=TABLE_TEXT,
|
||||||
|
header_background_color=TABLE_HEADERS_COLOR,
|
||||||
|
header_text_color=TABLE_HEADERS_TEXT_COLOR,
|
||||||
|
header_border_width=TABLE_HEADER_BORDER,
|
||||||
|
border_width=TABLE_BORDER,
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
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,
|
||||||
|
pad=TABLE_PAD,
|
||||||
|
),
|
||||||
|
sg.Multiline(
|
||||||
|
size=(40, TABLE_HEIGHT + 1),
|
||||||
|
key="cfg_config_txt",
|
||||||
|
sbar_trough_color=SCROLLBAR_TROUGH_COLOR,
|
||||||
|
sbar_background_color=SCROLLBAR_BACKGROUND_COLOR,
|
||||||
|
sbar_arrow_color=SCROLLBAR_ARROW_COLOR,
|
||||||
|
sbar_width=SCROLLBAR_WIDTH,
|
||||||
|
sbar_arrow_width=SCROLLBAR_ARROW_WIDTH,
|
||||||
|
sbar_relief=SCROLLBAR_RELIEF,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
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.Push(),
|
||||||
|
sg.Button(
|
||||||
|
"Hashrate: 0 TH/s",
|
||||||
|
disabled=True,
|
||||||
|
button_color=("black", "white smoke"),
|
||||||
|
disabled_button_color=("black", "white smoke"),
|
||||||
|
key="total_hashrate",
|
||||||
|
),
|
||||||
|
sg.Button(
|
||||||
|
"Miners: 0",
|
||||||
|
disabled=True,
|
||||||
|
button_color=("black", "white smoke"),
|
||||||
|
disabled_button_color=("black", "white smoke"),
|
||||||
|
key="miner_count",
|
||||||
|
),
|
||||||
|
sg.Push(),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.TabGroup(
|
||||||
|
[
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Scan",
|
||||||
|
get_scan_layout(),
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Pools",
|
||||||
|
get_pools_layout(),
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Configure",
|
||||||
|
get_config_layout(),
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
sg.Tab(
|
||||||
|
"Command",
|
||||||
|
get_command_layout(),
|
||||||
|
background_color=MAIN_TABS_BG,
|
||||||
|
pad=TAB_PAD,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
],
|
||||||
|
tab_background_color=MAIN_TABS_NORMAL,
|
||||||
|
title_color=MAIN_TABS_TEXT_NORMAL,
|
||||||
|
selected_background_color=MAIN_TABS_BG,
|
||||||
|
selected_title_color=MAIN_TABS_TEXT_SELECTED,
|
||||||
|
border_width=0,
|
||||||
|
tab_border_width=2,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
]
|
||||||
|
|
||||||
|
window = sg.Window("Upstream Config Util", layout, icon=WINDOW_ICON)
|
||||||
95
tools/cfg_util/scan/__init__.py
Normal file
95
tools/cfg_util/scan/__init__.py
Normal 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()
|
||||||
211
tools/cfg_util/tables.py
Normal file
211
tools/cfg_util/tables.py
Normal file
@@ -0,0 +1,211 @@
|
|||||||
|
from tools.cfg_util.layout import (
|
||||||
|
MINER_COUNT_BUTTONS,
|
||||||
|
HASHRATE_TOTAL_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_total_hr(hashrate: float):
|
||||||
|
if hashrate > 999:
|
||||||
|
hashrate = f"{round(hashrate/1000, 2)} PH/s"
|
||||||
|
else:
|
||||||
|
hashrate = f"{round(hashrate)} TH/s"
|
||||||
|
for button in HASHRATE_TOTAL_BUTTONS:
|
||||||
|
window[button].update(f"Hashrate: {hashrate}")
|
||||||
|
|
||||||
|
|
||||||
|
def update_tables(data: list or None = None):
|
||||||
|
TableManager().update_data(data)
|
||||||
|
|
||||||
|
|
||||||
|
def clear_tables():
|
||||||
|
TableManager().clear_tables()
|
||||||
|
|
||||||
|
|
||||||
|
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 "▲" in sort_key or "▼" in sort_key:
|
||||||
|
sort_key = sort_key[:-1]
|
||||||
|
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_ALL": [["" for _ in TABLE_HEADERS["POOLS_ALL"]] for _ in self.data],
|
||||||
|
"POOLS_1": [["" for _ in TABLE_HEADERS["POOLS_1"]] for _ in self.data],
|
||||||
|
"POOLS_2": [["" for _ in TABLE_HEADERS["POOLS_2"]] 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)
|
||||||
|
)
|
||||||
|
|
||||||
|
table_names = {
|
||||||
|
"SCAN": "scan_table",
|
||||||
|
"POOLS_ALL": "pools_table",
|
||||||
|
"POOLS_1": "pools_1_table",
|
||||||
|
"POOLS_2": "pools_2_table",
|
||||||
|
"CONFIG": "cfg_table",
|
||||||
|
"CMD": "cmd_table",
|
||||||
|
}
|
||||||
|
|
||||||
|
for table in TABLE_HEADERS.keys():
|
||||||
|
widget = window[table_names[table]].Widget
|
||||||
|
for idx, header in enumerate(TABLE_HEADERS[table]):
|
||||||
|
_header = header
|
||||||
|
if header == self.sort_key:
|
||||||
|
if self.sort_reverse:
|
||||||
|
_header = f"{header}▼"
|
||||||
|
else:
|
||||||
|
_header = f"{header}▲"
|
||||||
|
widget.heading(idx, text=_header)
|
||||||
|
|
||||||
|
# reset light
|
||||||
|
window["cmd_table"].Widget.heading("#0", text="Light")
|
||||||
|
|
||||||
|
# handle light sort key
|
||||||
|
if self.sort_key == "Light":
|
||||||
|
widget = window["cmd_table"].Widget
|
||||||
|
idx = "#0"
|
||||||
|
if self.sort_reverse:
|
||||||
|
_header = f"Light▼"
|
||||||
|
else:
|
||||||
|
_header = f"Light▲"
|
||||||
|
widget.heading(idx, text=_header)
|
||||||
|
|
||||||
|
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_ALL"])
|
||||||
|
window["pools_1_table"].update(tables["POOLS_1"])
|
||||||
|
window["pools_2_table"].update(tables["POOLS_2"])
|
||||||
|
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))
|
||||||
|
total_hr = 0
|
||||||
|
for key in self.data.keys():
|
||||||
|
hashrate = 0
|
||||||
|
if not self.data[key]["Hashrate"] == "":
|
||||||
|
hashrate = (
|
||||||
|
self.data[key]["Hashrate"].replace(" ", "").replace("TH/s", "")
|
||||||
|
)
|
||||||
|
total_hr += float(hashrate)
|
||||||
|
update_total_hr(round(total_hr))
|
||||||
|
|
||||||
|
def _get_sort(self, data_key: str):
|
||||||
|
if self.sort_key not in self.data[data_key]:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
if self.sort_key == "IP":
|
||||||
|
return ipaddress.ip_address(self.data[data_key]["IP"])
|
||||||
|
|
||||||
|
if self.sort_key == "Hashrate":
|
||||||
|
if self.data[data_key]["Hashrate"] == "":
|
||||||
|
return -1
|
||||||
|
if not isinstance(self.data[data_key]["Hashrate"], str):
|
||||||
|
return self.data[data_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):
|
||||||
|
return -300
|
||||||
|
|
||||||
|
if self.sort_key == "Split":
|
||||||
|
if self.data[data_key][self.sort_key] == "":
|
||||||
|
return -1
|
||||||
|
if "/" not in self.data[data_key][self.sort_key]:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if not self.sort_reverse:
|
||||||
|
return int(self.data[data_key][self.sort_key].split("/")[0])
|
||||||
|
else:
|
||||||
|
return int(self.data[data_key][self.sort_key].split("/")[1])
|
||||||
|
|
||||||
|
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)
|
||||||
139
tools/cfg_util/ui.py
Normal file
139
tools/cfg_util/ui.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
def _tree_header_click_handler(event, table):
|
||||||
|
region = table.Widget.identify("region", event.x, event.y)
|
||||||
|
if region == "heading":
|
||||||
|
col = int(table.Widget.identify_column(event.x)[1:]) - 1
|
||||||
|
|
||||||
|
if col == -1:
|
||||||
|
# handle the "Light" column, which needs a key of #0
|
||||||
|
col = "#0"
|
||||||
|
|
||||||
|
heading = table.Widget.heading(col)["text"]
|
||||||
|
|
||||||
|
mgr = TableManager()
|
||||||
|
mgr.update_sort_key(heading)
|
||||||
|
|
||||||
|
|
||||||
|
async def ui():
|
||||||
|
window.read(0)
|
||||||
|
TableManager().update_tables()
|
||||||
|
|
||||||
|
# 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: _tree_header_click_handler(x, window["cmd_table"])
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
event, value = window.read(0)
|
||||||
|
if event in (None, "Close", sg.WIN_CLOSED):
|
||||||
|
sys.exit()
|
||||||
|
|
||||||
|
if isinstance(event, tuple):
|
||||||
|
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())
|
||||||
1
tools/cfg_util_old/__init__.py
Normal file
1
tools/cfg_util_old/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
from tools.cfg_util_old.cfg_util_sg import main
|
||||||
@@ -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
|
||||||
@@ -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):
|
||||||
@@ -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())
|
||||||
@@ -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))
|
||||||
@@ -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
|
||||||
|
|
||||||
@@ -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,
|
||||||
@@ -127,7 +127,7 @@ class TestbenchMiner:
|
|||||||
await self.add_to_output("Running install...")
|
await self.add_to_output("Running install...")
|
||||||
error = None
|
error = None
|
||||||
proc = await asyncio.create_subprocess_shell(
|
proc = await asyncio.create_subprocess_shell(
|
||||||
f'{os.path.join(os.path.dirname(__file__), "files", "bos-toolbox", "bos-toolbox.bat")} install {str(self.host)} --no-keep-pools --psu-power-limit 900 --no-nand-backup --feeds-url file:./feeds/ -p root',
|
f'{os.path.join(os.path.dirname(__file__), "files", "bos-toolbox", "bos-toolbox.bat")} install {str(self.host)} --no-keep-pools --psu-power-limit 900 --no-nand-backup --feeds-url file:./feeds/',
|
||||||
stdout=asyncio.subprocess.PIPE,
|
stdout=asyncio.subprocess.PIPE,
|
||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
stdin=asyncio.subprocess.PIPE
|
stdin=asyncio.subprocess.PIPE
|
||||||
@@ -137,7 +137,7 @@ class TestbenchMiner:
|
|||||||
await self.add_to_output("Getting output...")
|
await self.add_to_output("Getting output...")
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
stdout = await asyncio.wait_for(proc.stderr.readuntil(b"\r"), 10)
|
stdout = await asyncio.wait_for(proc.stderr.readuntil(b"\r"), 20)
|
||||||
except asyncio.exceptions.IncompleteReadError:
|
except asyncio.exceptions.IncompleteReadError:
|
||||||
break
|
break
|
||||||
except asyncio.exceptions.TimeoutError:
|
except asyncio.exceptions.TimeoutError:
|
||||||
@@ -259,6 +259,13 @@ class TestbenchMiner:
|
|||||||
devs_raw["DEVS"][board]["MHS 5s"] / 1000000, 2
|
devs_raw["DEVS"][board]["MHS 5s"] / 1000000, 2
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if len(hr_data.keys()) < 3:
|
||||||
|
print(devs_raw["DEVS"])
|
||||||
|
for board in [6, 7, 8]:
|
||||||
|
if f"board_{board}" not in hr_data.keys():
|
||||||
|
hr_data[f"board_{board}"] = {"HR": 0}
|
||||||
|
|
||||||
|
|
||||||
# parse fan data
|
# parse fan data
|
||||||
fans_data = {}
|
fans_data = {}
|
||||||
for fan in range(len(fans_raw["FANS"])):
|
for fan in range(len(fans_raw["FANS"])):
|
||||||
|
|||||||
@@ -89,10 +89,10 @@ async def get_latest_update_file(session, update_file):
|
|||||||
|
|
||||||
async def get_latest_install_file(session, version, feeds_path, install_file):
|
async def get_latest_install_file(session, version, feeds_path, install_file):
|
||||||
install_file_loc = f"http://feeds.braiins-os.com/{version}/{install_file}"
|
install_file_loc = f"http://feeds.braiins-os.com/{version}/{install_file}"
|
||||||
feeds_file_path = os.path.join(feeds_path, "toolbox_bos_am1-s9")
|
feeds_file_path = os.path.join(feeds_path, "toolbox_bos_install_am1-s9")
|
||||||
|
|
||||||
with open(feeds_file_path, "a+") as feeds_file:
|
with open(feeds_file_path, "a+") as feeds_file:
|
||||||
feeds_file.write(version + "\t" + install_file)
|
feeds_file.write(version + "\t" + install_file.strip() + "\n")
|
||||||
|
|
||||||
install_file_folder = os.path.join(feeds_path, version)
|
install_file_folder = os.path.join(feeds_path, version)
|
||||||
if os.path.exists(install_file_folder):
|
if os.path.exists(install_file_folder):
|
||||||
@@ -133,7 +133,7 @@ async def get_local_versions():
|
|||||||
if not os.path.exists(feeds_path):
|
if not os.path.exists(feeds_path):
|
||||||
os.mkdir(feeds_path)
|
os.mkdir(feeds_path)
|
||||||
|
|
||||||
feeds_file_path = os.path.join(feeds_path, "toolbox_bos_am1-s9")
|
feeds_file_path = os.path.join(feeds_path, "toolbox_bos_install_am1-s9")
|
||||||
|
|
||||||
if not os.path.exists(feeds_file_path):
|
if not os.path.exists(feeds_file_path):
|
||||||
feeds_file = open(feeds_file_path, "w+")
|
feeds_file = open(feeds_file_path, "w+")
|
||||||
|
|||||||
Reference in New Issue
Block a user