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

@@ -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:
miner = CGMiner(str(ip))
elif "BMMiner" in version:
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
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 api:
miner = BMMiner(str(ip))
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):
try:
# open a connection to the miner
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": "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:
# some stupid whatsminers need a different command
fut = asyncio.open_connection(str(ip), 4028)
# get reader and writer streams
async def _get_miner_model(self, ip: ipaddress.ip_address or str) -> dict or None:
model = None
try:
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": "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"])
data = await self._send_api_command(str(ip), "version")
model = data["VERSION"][0]["Type"]
except:
print(f"Get Model Exception: {ip}")
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
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)
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)
return None