added actual miners versions and types to the factory

This commit is contained in:
UpstreamData
2022-01-07 09:34:05 -07:00
parent 1f3ffe96a1
commit 48aa7232b1
4 changed files with 163 additions and 124 deletions

View File

@@ -514,6 +514,14 @@ class BTMinerAPI(BaseMinerAPI):
""" """
API 'get_version' command. API 'get_version' command.
Returns a dict containing version information.
"""
return await self.get_version()
async def get_version(self):
"""
API 'get_version' command.
Returns a dict containing version information. Returns a dict containing version information.
""" """
return await self.send_command("get_version") return await self.send_command("get_version")

View File

@@ -45,7 +45,7 @@ async def scan_network(network):
async for found_miner in get_miner_genenerator: async for found_miner in get_miner_genenerator:
all_miners.append(found_miner) all_miners.append(found_miner)
all_miners.sort(key=lambda x: x.ip) all_miners.sort(key=lambda x: x.ip)
window["ip_table"].update([[str(miner.ip), "", "", "", ""] for miner in all_miners]) window["ip_table"].update([[str(miner.ip)] for miner in all_miners])
progress_bar_len += 1 progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len)) asyncio.create_task(update_prog_bar(progress_bar_len))
await update_ui_with_data("ip_count", str(len(all_miners))) await update_ui_with_data("ip_count", str(len(all_miners)))
@@ -147,7 +147,7 @@ async def scan_and_get_data(network):
# can output "Identifying" for each found item, but it gets a bit cluttered # can output "Identifying" for each found item, but it gets a bit cluttered
# and could possibly be confusing for the end user because of timing on # and could possibly be confusing for the end user because of timing on
# adding the IPs # adding the IPs
# window["ip_table"].update([["Identifying...", "", "", "", ""] for miner in miners]) # window["ip_table"].update([["Identifying..."] for miner in miners])
progress_bar_len += 1 progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len)) asyncio.create_task(update_prog_bar(progress_bar_len))
progress_bar_len += network_size - len(miners) progress_bar_len += network_size - len(miners)
@@ -157,7 +157,7 @@ async def scan_and_get_data(network):
async for found_miner in get_miner_genenerator: async for found_miner in get_miner_genenerator:
all_miners.append(found_miner) all_miners.append(found_miner)
all_miners.sort(key=lambda x: x.ip) all_miners.sort(key=lambda x: x.ip)
window["ip_table"].update([[str(miner.ip), "", "", "", ""] for miner in all_miners]) window["ip_table"].update([[str(miner.ip)] for miner in all_miners])
progress_bar_len += 1 progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len)) asyncio.create_task(update_prog_bar(progress_bar_len))
await update_ui_with_data("ip_count", str(len(all_miners))) await update_ui_with_data("ip_count", str(len(all_miners)))

View File

@@ -5,7 +5,7 @@ import toml
from config.bos import bos_config_convert, general_config_convert_bos from config.bos import bos_config_convert, general_config_convert_bos
class BOSminerX17(BaseMiner): class BOSMinerX17(BaseMiner):
def __init__(self, ip: str) -> None: def __init__(self, ip: str) -> None:
api = BOSMinerAPI(ip) api = BOSMinerAPI(ip)
super().__init__(ip, api) super().__init__(ip, api)

View File

@@ -1,3 +1,12 @@
from miners.antminer.S9.bos import BOSMinerS9
from miners.antminer.X17.bos import BOSMinerX17
from miners.whatsminer.M20 import BTMinerM20
from miners.whatsminer.M21 import BTMinerM21
from miners.whatsminer.M30 import BTMinerM30
from miners.whatsminer.M31 import BTMinerM31
from miners.whatsminer.M32 import BTMinerM32
from miners.bosminer import BOSminer from miners.bosminer import BOSminer
from miners.bmminer import BMMiner from miners.bmminer import BMMiner
from miners.cgminer import CGMiner from miners.cgminer import CGMiner
@@ -32,37 +41,53 @@ 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) -> BOSminer or CGMiner or BMMiner or UnknownMiner: async def get_miner(self, ip: ipaddress.ip_address) -> BOSminer or CGMiner or BMMiner or BTMiner or UnknownMiner:
"""Decide a miner type using the IP address of the miner.""" """Decide a miner type using the IP address of the miner."""
# 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]
# get the version data miner = UnknownMiner(str(ip))
version = None api = None
for i in range(GET_VERSION_RETRIES): for i in range(GET_VERSION_RETRIES):
version_data = await self._get_version_data(ip) api = await self._get_api_type(ip)
if version_data: if api:
# if we got version data, get a list of the keys so we can check type of miner
version = list(version_data['VERSION'][0].keys())
break break
if version: model = None
# check version against different return miner types for i in range(GET_VERSION_RETRIES):
if "BOSminer" in version or "BOSminer+" in version: model = await self._get_miner_model(ip)
miner = BOSminer(str(ip)) if model:
elif "CGMiner" in version: break
if model:
if "Antminer" in model:
if model == "Antminer S9":
if "BOSMiner" in api:
miner = BOSMinerS9(str(ip))
elif "CGMiner" in api:
miner = CGMiner(str(ip)) miner = CGMiner(str(ip))
elif "BMMiner" in version: elif "BMMiner" in api:
miner = BMMiner(str(ip)) miner = BMMiner(str(ip))
elif "BTMiner" in version: elif "17" in model:
miner = BTMiner(str(ip)) if "BOSMiner" in api:
else: miner = BOSMinerX17(str(ip))
print(f"Bad API response: {version}") elif "CGMiner" in api:
miner = UnknownMiner(str(ip)) miner = CGMiner(str(ip))
else: elif "BMMiner" in api:
# if we don't get version, miner type is unknown miner = BMMiner(str(ip))
print(f"No API response: {str(ip)}") elif "19" in model:
miner = UnknownMiner(str(ip)) if "CGMiner" in api:
# save the miner in cache miner = CGMiner(str(ip))
elif "BMMiner" in api:
miner = BMMiner(str(ip))
elif "M20" in model:
miner = BTMinerM20(str(ip))
elif "M21" in model:
miner = BTMinerM21(str(ip))
elif "M30" in model:
miner = BTMinerM30(str(ip))
elif "M31" in model:
miner = BTMinerM31(str(ip))
elif "M32" in model:
miner = BTMinerM32(str(ip))
self.miners[ip] = miner self.miners[ip] = miner
return miner return miner
@@ -70,101 +95,107 @@ class MinerFactory:
"""Clear the miner factory cache.""" """Clear the miner factory cache."""
self.miners = {} self.miners = {}
@staticmethod async def _get_miner_model(self, ip: ipaddress.ip_address or str) -> dict or None:
async def _get_version_data(ip: ipaddress.ip_address) -> dict or None: model = None
"""Get data on the version of the miner to return the right miner."""
for i in range(3):
try: try:
# open a connection to the miner data = await self._send_api_command(str(ip), "devdetails")
fut = asyncio.open_connection(str(ip), 4028) if data.get("STATUS"):
# get reader and writer streams if data["STATUS"][0].get("STATUS") not in ["I", "S"]:
try: try:
reader, writer = await asyncio.wait_for(fut, timeout=7) data = await self._send_api_command(str(ip), "version")
except asyncio.exceptions.TimeoutError: model = data["VERSION"][0]["Type"]
return None except:
print(f"Get Model Exception: {ip}")
# create the command
cmd = {"command": "version"}
# send the command
writer.write(json.dumps(cmd).encode('utf-8'))
await writer.drain()
# instantiate data
data = b""
# loop to receive all the data
while True:
d = await reader.read(4096)
if not d:
break
data += d
if data.endswith(b"\x00"):
data = json.loads(data.decode('utf-8')[:-1])
else: else:
# some stupid whatsminers need a different command model = data["DEVDETAILS"][0]["Model"]
fut = asyncio.open_connection(str(ip), 4028) if model:
# get reader and writer streams return model
try: except OSError as e:
reader, writer = await asyncio.wait_for(fut, timeout=7) if e.winerror == 121:
except asyncio.exceptions.TimeoutError: return None
return None else:
print(ip, e)
# create the command return None
cmd = {"command": "get_version"}
async def _send_api_command(self, ip: ipaddress.ip_address or str, command: str):
# send the command try:
writer.write(json.dumps(cmd).encode('utf-8')) # get reader and writer streams
await writer.drain() reader, writer = await asyncio.open_connection(str(ip), 4028)
# handle OSError 121
# instantiate data except OSError as e:
data = b"" if e.winerror == "121":
print("Semaphore Timeout has Expired.")
# loop to receive all the data return {}
while True:
d = await reader.read(4096) # create the command
if not d: cmd = {"command": command}
break
data += d # send the command
writer.write(json.dumps(cmd).encode('utf-8'))
data = data.decode('utf-8').replace("\n", "") await writer.drain()
data = json.loads(data)
# instantiate data
# close the connection data = b""
writer.close()
await writer.wait_closed() # loop to receive all the data
# check if the data returned is correct or an error try:
# if status isn't a key, it is a multicommand while True:
if "STATUS" not in data.keys(): d = await reader.read(4096)
for key in data.keys(): if not d:
# make sure not to try to turn id into a dict break
if not key == "id": data += d
# make sure they succeeded except Exception as e:
if data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]: print(e)
# this is an error
raise APIError(data["STATUS"][0]["Msg"]) try:
else: # some json from the API returns with a null byte (\x00) on the end
# check for stupid whatsminer formatting if data.endswith(b"\x00"):
if not isinstance(data["STATUS"], list): # handle the null byte
if data["STATUS"] not in ("S", "I"): str_data = data.decode('utf-8')[:-1]
raise APIError(data["Msg"]) else:
else: # no null byte
if "whatsminer" in data["Description"]: str_data = data.decode('utf-8')
return {"VERSION": [{"BTMiner": data["Description"]}]} # fix an error with a btminer return having an extra comma that breaks json.loads()
# make sure the command succeeded str_data = str_data.replace(",}", "}")
elif data["STATUS"][0]["STATUS"] not in ("S", "I"): # fix an error with a btminer return having a newline that breaks json.loads()
# this is an error str_data = str_data.replace("\n", "")
raise APIError(data["STATUS"][0]["Msg"]) # fix an error with a bmminer return not having a specific comma that breaks json.loads()
# return the data str_data = str_data.replace("}{", "},{")
return data # parse the json
parsed_data = json.loads(str_data)
# handle bad json
except json.decoder.JSONDecodeError as e:
print(e)
raise APIError(f"Decode Error: {data}")
data = parsed_data
# close the connection
writer.close()
await writer.wait_closed()
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."""
api = None
try:
data = await self._send_api_command(str(ip), "version")
if data.get("STATUS") and not data.get("STATUS") == "E":
if data["STATUS"][0].get("STATUS") in ["I", "S"]:
if "BMMiner" in data["VERSION"][0].keys():
api = "BMMiner"
elif "CGMiner" in data["VERSION"][0].keys():
api = "CGMiner"
elif "BOSminer" in data["VERSION"][0].keys() or "BOSminer+" in data["VERSION"][0].keys():
api = "BOSMiner"
elif data.get("Description") and "whatsminer" in data.get("Description"):
api = "BTMiner"
if api:
return api
except OSError as e: except OSError as e:
if e.winerror == 121: if e.winerror == 121:
return None return None
else: else:
print(ip, e) print(ip, e)
# except json.decoder.JSONDecodeError:
# print("Decode Error @ " + str(ip) + str(data))
# except Exception as e:
# print(ip, e)
return None return None