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.
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.
"""
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:
all_miners.append(found_miner)
all_miners.sort(key=lambda x: x.ip)
window["ip_table"].update([[str(miner.ip), "", "", "", ""] for miner in all_miners])
window["ip_table"].update([[str(miner.ip)] for miner in all_miners])
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
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
# and could possibly be confusing for the end user because of timing on
# adding the IPs
# window["ip_table"].update([["Identifying...", "", "", "", ""] for miner in miners])
# window["ip_table"].update([["Identifying..."] for miner in miners])
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
progress_bar_len += network_size - len(miners)
@@ -157,7 +157,7 @@ async def scan_and_get_data(network):
async for found_miner in get_miner_genenerator:
all_miners.append(found_miner)
all_miners.sort(key=lambda x: x.ip)
window["ip_table"].update([[str(miner.ip), "", "", "", ""] for miner in all_miners])
window["ip_table"].update([[str(miner.ip)] for miner in all_miners])
progress_bar_len += 1
asyncio.create_task(update_prog_bar(progress_bar_len))
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
class BOSminerX17(BaseMiner):
class BOSMinerX17(BaseMiner):
def __init__(self, ip: str) -> None:
api = BOSMinerAPI(ip)
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.bmminer import BMMiner
from miners.cgminer import CGMiner
@@ -32,37 +41,53 @@ class MinerFactory:
for miner in scanned:
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."""
# check if the miner already exists in cache
if ip in self.miners:
return self.miners[ip]
# get the version data
version = None
miner = UnknownMiner(str(ip))
api = None
for i in range(GET_VERSION_RETRIES):
version_data = await self._get_version_data(ip)
if version_data:
# 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())
api = await self._get_api_type(ip)
if api:
break
if version:
# check version against different return miner types
if "BOSminer" in version or "BOSminer+" in version:
miner = BOSminer(str(ip))
elif "CGMiner" in version:
model = None
for i in range(GET_VERSION_RETRIES):
model = await self._get_miner_model(ip)
if model:
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))
elif "BMMiner" in version:
elif "BMMiner" in api:
miner = BMMiner(str(ip))
elif "BTMiner" in version:
miner = BTMiner(str(ip))
else:
print(f"Bad API response: {version}")
miner = UnknownMiner(str(ip))
else:
# if we don't get version, miner type is unknown
print(f"No API response: {str(ip)}")
miner = UnknownMiner(str(ip))
# save the miner in cache
elif "17" in model:
if "BOSMiner" in api:
miner = BOSMinerX17(str(ip))
elif "CGMiner" in api:
miner = CGMiner(str(ip))
elif "BMMiner" in api:
miner = BMMiner(str(ip))
elif "19" in model:
if "CGMiner" in api:
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
return miner
@@ -70,101 +95,107 @@ class MinerFactory:
"""Clear the miner factory cache."""
self.miners = {}
@staticmethod
async def _get_version_data(ip: ipaddress.ip_address) -> dict or None:
"""Get data on the version of the miner to return the right miner."""
for i in range(3):
async def _get_miner_model(self, ip: ipaddress.ip_address or str) -> dict or None:
model = None
try:
# open a connection to the miner
fut = asyncio.open_connection(str(ip), 4028)
# get reader and writer streams
data = await self._send_api_command(str(ip), "devdetails")
if data.get("STATUS"):
if data["STATUS"][0].get("STATUS") not in ["I", "S"]:
try:
reader, writer = await asyncio.wait_for(fut, timeout=7)
except asyncio.exceptions.TimeoutError:
return None
# 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])
data = await self._send_api_command(str(ip), "version")
model = data["VERSION"][0]["Type"]
except:
print(f"Get Model Exception: {ip}")
else:
# some stupid whatsminers need a different command
fut = asyncio.open_connection(str(ip), 4028)
# get reader and writer streams
try:
reader, writer = await asyncio.wait_for(fut, timeout=7)
except asyncio.exceptions.TimeoutError:
return None
# create the command
cmd = {"command": "get_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
data = data.decode('utf-8').replace("\n", "")
data = json.loads(data)
# close the connection
writer.close()
await writer.wait_closed()
# check if the data returned is correct or an error
# 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 data[key][0]["STATUS"][0]["STATUS"] not in ["S", "I"]:
# this is an error
raise APIError(data["STATUS"][0]["Msg"])
else:
# check for stupid whatsminer formatting
if not isinstance(data["STATUS"], list):
if data["STATUS"] not in ("S", "I"):
raise APIError(data["Msg"])
else:
if "whatsminer" in data["Description"]:
return {"VERSION": [{"BTMiner": data["Description"]}]}
# make sure the command succeeded
elif data["STATUS"][0]["STATUS"] not in ("S", "I"):
# this is an error
raise APIError(data["STATUS"][0]["Msg"])
# return the data
return data
model = data["DEVDETAILS"][0]["Model"]
if model:
return model
except OSError as e:
if e.winerror == 121:
return None
else:
print(ip, e)
return None
async def _send_api_command(self, ip: ipaddress.ip_address or str, command: str):
try:
# get reader and writer streams
reader, writer = await asyncio.open_connection(str(ip), 4028)
# handle OSError 121
except OSError as e:
if e.winerror == "121":
print("Semaphore Timeout has Expired.")
return {}
# create the command
cmd = {"command": command}
# send the command
writer.write(json.dumps(cmd).encode('utf-8'))
await writer.drain()
# instantiate data
data = b""
# loop to receive all the data
try:
while True:
d = await reader.read(4096)
if not d:
break
data += d
except Exception as e:
print(e)
try:
# some json from the API returns with a null byte (\x00) on the end
if data.endswith(b"\x00"):
# handle the null byte
str_data = data.decode('utf-8')[:-1]
else:
# no null byte
str_data = data.decode('utf-8')
# fix an error with a btminer return having an extra comma that breaks json.loads()
str_data = str_data.replace(",}", "}")
# fix an error with a btminer return having a newline that breaks json.loads()
str_data = str_data.replace("\n", "")
# fix an error with a bmminer return not having a specific comma that breaks json.loads()
str_data = str_data.replace("}{", "},{")
# 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:
if e.winerror == 121:
return None
else:
print(ip, e)
# except json.decoder.JSONDecodeError:
# print("Decode Error @ " + str(ip) + str(data))
# except Exception as e:
# print(ip, e)
return None