Files
pyasic/pyasic/miners/miner_factory.py

803 lines
28 KiB
Python

# ------------------------------------------------------------------------------
# Copyright 2022 Upstream Data Inc -
# -
# Licensed under the Apache License, Version 2.0 (the "License"); -
# you may not use this file except in compliance with the License. -
# You may obtain a copy of the License at -
# -
# http://www.apache.org/licenses/LICENSE-2.0 -
# -
# Unless required by applicable law or agreed to in writing, software -
# distributed under the License is distributed on an "AS IS" BASIS, -
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -
# See the License for the specific language governing permissions and -
# limitations under the License. -
# ------------------------------------------------------------------------------
import asyncio
import enum
import ipaddress
import json
import re
from ipaddress import IPv4Address
from typing import Callable, List, Optional, Tuple, Union
import aiohttp
from pyasic.logger import logger
from pyasic.miners.backends import (
BFGMiner,
BMMiner,
BOSMiner,
BTMiner,
CGMiner,
CGMinerAvalon,
Hiveon,
VNish,
)
from pyasic.miners.base import AnyMiner
from pyasic.miners.btc import *
from pyasic.miners.ckb import *
from pyasic.miners.dcr import *
from pyasic.miners.dsh import *
from pyasic.miners.etc import *
from pyasic.miners.hns import *
from pyasic.miners.kda import *
from pyasic.miners.ltc import *
from pyasic.miners.unknown import UnknownMiner
from pyasic.miners.zec import *
TIMEOUT = 20
RETRIES = 3
class MinerTypes(enum.Enum):
ANTMINER = 0
WHATSMINER = 1
AVALONMINER = 2
INNOSILICON = 3
GOLDSHELL = 4
BRAIINS_OS = 5
VNISH = 6
HIVEON = 7
MINER_CLASSES = {
MinerTypes.ANTMINER: {
None: BMMiner,
"ANTMINER DR5": CGMinerDR5,
"ANTMINER D3": CGMinerD3,
"ANTMINER HS3": CGMinerHS3,
"ANTMINER L3+": BMMinerL3Plus,
"ANTMINER L7": BMMinerL7,
"ANTMINER E9 PRO": CGMinerE9Pro,
"ANTMINER S9": BMMinerS9,
"ANTMINER S9I": BMMinerS9i,
"ANTMINER S9J": BMMinerS9j,
"ANTMINER T9": BMMinerT9,
"ANTMINER Z15": CGMinerZ15,
"ANTMINER S17": BMMinerS17,
"ANTMINER S17+": BMMinerS17Plus,
"ANTMINER S17 PRO": BMMinerS17Pro,
"ANTMINER S17E": BMMinerS17e,
"ANTMINER T17": BMMinerT17,
"ANTMINER T17+": BMMinerT17Plus,
"ANTMINER T17E": BMMinerT17e,
"ANTMINER S19": BMMinerS19,
"ANTMINER S19L": BMMinerS19L,
"ANTMINER S19 PRO": BMMinerS19Pro,
"ANTMINER S19J": BMMinerS19j,
"ANTMINER S19J88NOPIC": BMMinerS19jNoPIC,
"ANTMINER S19PRO+": BMMinerS19ProPlus,
"ANTMINER S19J PRO": BMMinerS19jPro,
"ANTMINER S19 XP": BMMinerS19XP,
"ANTMINER S19A": BMMinerS19a,
"ANTMINER S19A PRO": BMMinerS19aPro,
"ANTMINER T19": BMMinerT19,
},
MinerTypes.WHATSMINER: {
None: BTMiner,
"M20V10": BTMinerM20V10,
"M20SV10": BTMinerM20SV10,
"M20SV20": BTMinerM20SV20,
"M20SV30": BTMinerM20SV30,
"M20S+V30": BTMinerM20SPlusV30,
"M21V10": BTMinerM21V10,
"M21SV20": BTMinerM21SV20,
"M21SV60": BTMinerM21SV60,
"M21SV70": BTMinerM21SV70,
"M21S+V20": BTMinerM21SPlusV20,
"M29V10": BTMinerM29V10,
"M30V10": BTMinerM30V10,
"M30V20": BTMinerM30V20,
"M30SV10": BTMinerM30SV10,
"M30SV20": BTMinerM30SV20,
"M30SV30": BTMinerM30SV30,
"M30SV40": BTMinerM30SV40,
"M30SV50": BTMinerM30SV50,
"M30SV60": BTMinerM30SV60,
"M30SV70": BTMinerM30SV70,
"M30SV80": BTMinerM30SV80,
"M30SVE10": BTMinerM30SVE10,
"M30SVE20": BTMinerM30SVE20,
"M30SVE30": BTMinerM30SVE30,
"M30SVE40": BTMinerM30SVE40,
"M30SVE50": BTMinerM30SVE50,
"M30SVE60": BTMinerM30SVE60,
"M30SVE70": BTMinerM30SVE70,
"M30SVF10": BTMinerM30SVF10,
"M30SVF20": BTMinerM30SVF20,
"M30SVF30": BTMinerM30SVF30,
"M30SVG10": BTMinerM30SVG10,
"M30SVG20": BTMinerM30SVG20,
"M30SVG30": BTMinerM30SVG30,
"M30SVG40": BTMinerM30SVG40,
"M30SVH10": BTMinerM30SVH10,
"M30SVH20": BTMinerM30SVH20,
"M30SVH30": BTMinerM30SVH30,
"M30SVH40": BTMinerM30SVH40,
"M30SVH50": BTMinerM30SVH50,
"M30SVH60": BTMinerM30SVH60,
"M30SVI20": BTMinerM30SVI20,
"M30S+V10": BTMinerM30SPlusV10,
"M30S+V20": BTMinerM30SPlusV20,
"M30S+V30": BTMinerM30SPlusV30,
"M30S+V40": BTMinerM30SPlusV40,
"M30S+V50": BTMinerM30SPlusV50,
"M30S+V60": BTMinerM30SPlusV60,
"M30S+V70": BTMinerM30SPlusV70,
"M30S+V80": BTMinerM30SPlusV80,
"M30S+V90": BTMinerM30SPlusV90,
"M30S+V100": BTMinerM30SPlusV100,
"M30S+VE30": BTMinerM30SPlusVE30,
"M30S+VE40": BTMinerM30SPlusVE40,
"M30S+VE50": BTMinerM30SPlusVE50,
"M30S+VE60": BTMinerM30SPlusVE60,
"M30S+VE70": BTMinerM30SPlusVE70,
"M30S+VE80": BTMinerM30SPlusVE80,
"M30S+VE90": BTMinerM30SPlusVE90,
"M30S+VE100": BTMinerM30SPlusVE100,
"M30S+VF20": BTMinerM30SPlusVF20,
"M30S+VF30": BTMinerM30SPlusVF30,
"M30S+VG30": BTMinerM30SPlusVG30,
"M30S+VG40": BTMinerM30SPlusVG40,
"M30S+VG50": BTMinerM30SPlusVG50,
"M30S+VG60": BTMinerM30SPlusVG60,
"M30S+VH10": BTMinerM30SPlusVH10,
"M30S+VH20": BTMinerM30SPlusVH20,
"M30S+VH30": BTMinerM30SPlusVH30,
"M30S+VH40": BTMinerM30SPlusVH40,
"M30S+VH50": BTMinerM30SPlusVH50,
"M30S+VH60": BTMinerM30SPlusVH60,
"M30S++V10": BTMinerM30SPlusPlusV10,
"M30S++V20": BTMinerM30SPlusPlusV20,
"M30S++VE30": BTMinerM30SPlusPlusVE30,
"M30S++VE40": BTMinerM30SPlusPlusVE40,
"M30S++VE50": BTMinerM30SPlusPlusVE50,
"M30S++VF40": BTMinerM30SPlusPlusVF40,
"M30S++VG30": BTMinerM30SPlusPlusVG30,
"M30S++VG40": BTMinerM30SPlusPlusVG40,
"M30S++VG50": BTMinerM30SPlusPlusVG50,
"M30S++VH10": BTMinerM30SPlusPlusVH10,
"M30S++VH20": BTMinerM30SPlusPlusVH20,
"M30S++VH30": BTMinerM30SPlusPlusVH30,
"M30S++VH40": BTMinerM30SPlusPlusVH40,
"M30S++VH50": BTMinerM30SPlusPlusVH50,
"M30S++VH60": BTMinerM30SPlusPlusVH60,
"M30S++VH70": BTMinerM30SPlusPlusVH70,
"M30S++VH80": BTMinerM30SPlusPlusVH80,
"M30S++VH90": BTMinerM30SPlusPlusVH90,
"M30S++VH100": BTMinerM30SPlusPlusVH100,
"M30S++VJ20": BTMinerM30SPlusPlusVJ20,
"M30S++VJ30": BTMinerM30SPlusPlusVJ30,
"M31V10": BTMinerM31V10,
"M31V20": BTMinerM31V20,
"M31SV10": BTMinerM31SV10,
"M31SV20": BTMinerM31SV20,
"M31SV30": BTMinerM31SV30,
"M31SV40": BTMinerM31SV40,
"M31SV50": BTMinerM31SV50,
"M31SV60": BTMinerM31SV60,
"M31SV70": BTMinerM31SV70,
"M31SV80": BTMinerM31SV80,
"M31SV90": BTMinerM31SV90,
"M31SVE10": BTMinerM31SVE10,
"M31SVE20": BTMinerM31SVE20,
"M31SVE30": BTMinerM31SVE30,
"M31SEV10": BTMinerM31SEV10,
"M31SEV20": BTMinerM31SEV20,
"M31SEV30": BTMinerM31SEV30,
"M31HV40": BTMinerM31HV40,
"M31S+V10": BTMinerM31SPlusV10,
"M31S+V20": BTMinerM31SPlusV20,
"M31S+V30": BTMinerM31SPlusV30,
"M31S+V40": BTMinerM31SPlusV40,
"M31S+V50": BTMinerM31SPlusV50,
"M31S+V60": BTMinerM31SPlusV60,
"M31S+V80": BTMinerM31SPlusV80,
"M31S+V90": BTMinerM31SPlusV90,
"M31S+V100": BTMinerM31SPlusV100,
"M31S+VE10": BTMinerM31SPlusVE10,
"M31S+VE20": BTMinerM31SPlusVE20,
"M31S+VE30": BTMinerM31SPlusVE30,
"M31S+VE40": BTMinerM31SPlusVE40,
"M31S+VE50": BTMinerM31SPlusVE50,
"M31S+VE60": BTMinerM31SPlusVE60,
"M31S+VE80": BTMinerM31SPlusVE80,
"M31S+VF20": BTMinerM31SPlusVF20,
"M31S+VF30": BTMinerM31SPlusVF30,
"M31S+VG20": BTMinerM31SPlusVG20,
"M31S+VG30": BTMinerM31SPlusVG30,
"M32V10": BTMinerM32V10,
"M32V20": BTMinerM32V20,
"M33V10": BTMinerM33V10,
"M33V20": BTMinerM33V20,
"M33V30": BTMinerM33V30,
"M33SVG30": BTMinerM33SVG30,
"M33S+VH20": BTMinerM33SPlusVH20,
"M33S+VH30": BTMinerM33SPlusVH30,
"M33S++VH20": BTMinerM33SPlusPlusVH20,
"M33S++VH30": BTMinerM33SPlusPlusVH30,
"M33S++VG40": BTMinerM33SPlusPlusVG40,
"M34S+VE10": BTMinerM34SPlusVE10,
"M36SVE10": BTMinerM36SVE10,
"M36S+VG30": BTMinerM36SPlusVG30,
"M36S++VH30": BTMinerM36SPlusPlusVH30,
"M39V20": BTMinerM39V20,
"M50VG30": BTMinerM50VG30,
"M50VH10": BTMinerM50VH10,
"M50VH20": BTMinerM50VH20,
"M50VH30": BTMinerM50VH30,
"M50VH40": BTMinerM50VH40,
"M50VH50": BTMinerM50VH50,
"M50VH60": BTMinerM50VH60,
"M50VH70": BTMinerM50VH70,
"M50VH80": BTMinerM50VH80,
"M50VJ10": BTMinerM50VJ10,
"M50VJ20": BTMinerM50VJ20,
"M50VJ30": BTMinerM50VJ30,
"M50SVJ10": BTMinerM50SVJ10,
"M50SVJ20": BTMinerM50SVJ20,
"M50SVJ30": BTMinerM50SVJ30,
"M50SVH10": BTMinerM50SVH10,
"M50SVH20": BTMinerM50SVH20,
"M50SVH30": BTMinerM50SVH30,
"M50SVH40": BTMinerM50SVH40,
"M50SVH50": BTMinerM50SVH50,
"M50S+VH30": BTMinerM50SPlusVH30,
"M50S+VH40": BTMinerM50SPlusVH40,
"M50S+VJ30": BTMinerM50SPlusVJ30,
"M50S++VK10": BTMinerM50SPlusPlusVK10,
"M50S++VK20": BTMinerM50SPlusPlusVK20,
"M50S++VK30": BTMinerM50SPlusPlusVK30,
"M53VH30": BTMinerM53VH30,
"M53SVH30": BTMinerM53SVH30,
"M53S+VJ30": BTMinerM53SPlusVJ30,
"M56VH30": BTMinerM56VH30,
"M56SVH30": BTMinerM56SVH30,
"M56S+VJ30": BTMinerM56SPlusVJ30,
"M59VH30": BTMinerM59VH30,
},
MinerTypes.AVALONMINER: {
None: CGMinerAvalon,
"AVALONMINER 721": CGMinerAvalon721,
"AVALONMINER 741": CGMinerAvalon741,
"AVALONMINER 761": CGMinerAvalon761,
"AVALONMINER 821": CGMinerAvalon821,
"AVALONMINER 841": CGMinerAvalon841,
"AVALONMINER 851": CGMinerAvalon851,
"AVALONMINER 921": CGMinerAvalon921,
"AVALONMINER 1026": CGMinerAvalon1026,
"AVALONMINER 1047": CGMinerAvalon1047,
"AVALONMINER 1066": CGMinerAvalon1066,
"AVALONMINER 1166PRO": CGMinerAvalon1166Pro,
"AVALONMINER 1246": CGMinerAvalon1246,
},
MinerTypes.INNOSILICON: {
None: CGMiner,
"T3H+": CGMinerInnosiliconT3HPlus,
"A10X": CGMinerA10X,
},
MinerTypes.GOLDSHELL: {
None: BFGMiner,
"GOLDSHELL CK5": BFGMinerCK5,
"GOLDSHELL HS5": BFGMinerHS5,
"GOLDSHELL KD5": BFGMinerKD5,
"GOLDSHELL KDMAX": BFGMinerKDMax,
},
MinerTypes.BRAIINS_OS: {
None: BOSMiner,
"ANTMINER S9": BOSMinerS9,
"ANTMINER S17": BOSMinerS17,
"ANTMINER S17+": BOSMinerS17Plus,
"ANTMINER S17 PRO": BOSMinerS17Pro,
"ANTMINER S17E": BOSMinerS17e,
"ANTMINER T17": BOSMinerT17,
"ANTMINER T17+": BOSMinerT17Plus,
"ANTMINER T17E": BOSMinerT17e,
"ANTMINER S19": BOSMinerS19,
"ANTMINER S19 PRO": BOSMinerS19Pro,
"ANTMINER S19J": BOSMinerS19j,
"ANTMINER S19J88NOPIC": BOSMinerS19jNoPIC,
"ANTMINER S19J PRO": BOSMinerS19jPro,
"ANTMINER T19": BOSMinerT19,
},
MinerTypes.VNISH: {
None: VNish,
"ANTMINER L3+": VnishL3Plus,
"ANTMINER S19": VNishS19,
"ANTMINER S19 PRO": VNishS19Pro,
"ANTMINER S19J": VNishS19j,
"ANTMINER S19J PRO": VNishS19jPro,
"ANTMINER S19A": VNishS19a,
"ANTMINER S19A PRO": VNishS19aPro,
"ANTMINER T19": VNishT19,
},
MinerTypes.HIVEON: {
None: Hiveon,
"ANTMINER T9": HiveonT9,
},
}
async def concurrent_get_first_result(tasks: list, verification_func: Callable):
while True:
await asyncio.sleep(0)
if len(tasks) == 0:
return
for task in tasks:
if task.done():
try:
result = await task
except asyncio.CancelledError:
for t in tasks:
t.cancel()
raise
else:
if not verification_func(result):
continue
for t in tasks:
t.cancel()
return result
class MinerFactory:
async def get_multiple_miners(self, ips: List[str], limit: int = 200):
tasks = []
results = []
semaphore = asyncio.Semaphore(limit)
for ip in ips:
tasks.append(asyncio.create_task(self.get_miner(ip)))
for task in tasks:
await semaphore.acquire()
try:
result = await task
if result is not None:
results.append(result)
finally:
semaphore.release()
return results
async def get_miner(self, ip: str):
miner_type = None
for _ in range(RETRIES):
task = asyncio.create_task(self._get_miner_type(ip))
try:
miner_type = await asyncio.wait_for(task, timeout=TIMEOUT)
except asyncio.TimeoutError:
task.cancel()
else:
if miner_type is not None:
break
if miner_type is not None:
if miner_type == MinerTypes.ANTMINER:
return await self.get_miner_antminer(ip)
if miner_type == MinerTypes.WHATSMINER:
return await self.get_miner_whatsminer(ip)
if miner_type == MinerTypes.AVALONMINER:
return await self.get_miner_avalonminer(ip)
if miner_type == MinerTypes.INNOSILICON:
return await self.get_miner_innosilicon(ip)
if miner_type == MinerTypes.GOLDSHELL:
return await self.get_miner_goldshell(ip)
if miner_type == MinerTypes.BRAIINS_OS:
return await self.get_miner_braiins_os(ip)
if miner_type == MinerTypes.VNISH:
return await self.get_miner_vnish(ip)
if miner_type == MinerTypes.HIVEON:
return await self.get_miner_hiveon(ip)
async def _get_miner_type(self, ip: str):
tasks = [
asyncio.create_task(self._get_miner_web(ip)),
asyncio.create_task(self._get_miner_socket(ip)),
]
return await concurrent_get_first_result(tasks, lambda x: x is not None)
async def _get_miner_web(self, ip: str):
urls = [f"http://{ip}/", f"https://{ip}/"]
async with aiohttp.ClientSession() as session:
tasks = [asyncio.create_task(self._web_ping(session, url)) for url in urls]
text, resp = await concurrent_get_first_result(
tasks, lambda x: x[0] is not None
)
if text is not None:
return self._parse_web_type(text, resp)
@staticmethod
async def _web_ping(
session: aiohttp.ClientSession, url: str
) -> Tuple[Optional[str], Optional[aiohttp.ClientResponse]]:
try:
resp = await session.get(url)
return await resp.text(), resp
except aiohttp.ClientError:
pass
return None, None
@staticmethod
def _parse_web_type(web_text: str, web_resp: aiohttp.ClientResponse) -> MinerTypes:
if web_resp.status == 401 and 'realm="antMiner' in web_resp.headers.get(
"www-authenticate", ""
):
return MinerTypes.ANTMINER
if web_resp.status == 307 and "https://" in web_resp.headers.get(
"location", ""
):
return MinerTypes.WHATSMINER
if "Braiins OS" in web_text or 'href="/cgi-bin/luci"' in web_text:
return MinerTypes.BRAIINS_OS
if "cloud-box" in web_text:
return MinerTypes.GOLDSHELL
if "AnthillOS" in web_text:
return MinerTypes.VNISH
if "Avalon" in web_text:
return MinerTypes.AVALONMINER
async def _get_miner_socket(self, ip: str):
commands = ["devdetails", "version"]
tasks = [asyncio.create_task(self._socket_ping(ip, cmd)) for cmd in commands]
data = await concurrent_get_first_result(
tasks, lambda x: x is not None and self._parse_socket_type(x) is not None
)
if data is not None:
return self._parse_socket_type(data)
@staticmethod
async def _socket_ping(ip: str, cmd: str) -> Optional[str]:
data = b""
try:
reader, writer = await asyncio.wait_for(
asyncio.open_connection(str(ip), 4028), timeout=30
)
except (ConnectionError, OSError, asyncio.TimeoutError):
return
cmd = {"command": cmd}
try:
# send the command
writer.write(json.dumps(cmd).encode("utf-8"))
await writer.drain()
# loop to receive all the data
while True:
try:
d = await asyncio.wait_for(reader.read(4096), timeout=1)
if not d:
break
data += d
except asyncio.TimeoutError:
pass
except ConnectionResetError:
return
except asyncio.CancelledError:
raise
except (ConnectionError, OSError):
return
finally:
# Handle cancellation explicitly
if writer.transport.is_closing():
writer.transport.close()
else:
writer.close()
try:
await writer.wait_closed()
except (ConnectionError, OSError):
return
if data:
return data.decode("utf-8")
@staticmethod
def _parse_socket_type(data: str) -> MinerTypes:
upper_data = data.upper()
if "BOSMINER" in upper_data or "BOSER" in upper_data:
return MinerTypes.BRAIINS_OS
if "BTMINER" in upper_data or "BITMICRO" in upper_data:
return MinerTypes.WHATSMINER
if "VNISH" in upper_data:
return MinerTypes.VNISH
if "HIVEON" in upper_data:
return MinerTypes.HIVEON
if "ANTMINER" in upper_data:
return MinerTypes.ANTMINER
if "INTCHAINS_QOMO" in upper_data:
return MinerTypes.GOLDSHELL
if "AVALON" in upper_data:
return MinerTypes.AVALONMINER
async def send_web_command(
self,
ip: Union[ipaddress.ip_address, str],
location: str,
auth: Optional[aiohttp.BasicAuth] = None,
) -> Optional[dict]:
async with aiohttp.ClientSession() as session:
try:
data = await session.get(
f"http://{str(ip)}{location}",
auth=auth,
timeout=30,
)
except (aiohttp.ClientError, asyncio.TimeoutError):
logger.info(f"{ip}: Web command timeout.")
return
if data is None:
return
try:
json_data = await data.json()
except (aiohttp.ContentTypeError, asyncio.TimeoutError):
try:
return json.loads(await data.text())
except (json.JSONDecodeError, aiohttp.ClientError):
return
else:
return json_data
async def send_api_command(
self, ip: Union[ipaddress.ip_address, str], command: str
) -> Optional[dict]:
data = b""
try:
reader, writer = await asyncio.open_connection(str(ip), 4028)
except (ConnectionError, OSError):
return
cmd = {"command": command}
try:
# send the command
writer.write(json.dumps(cmd).encode("utf-8"))
await writer.drain()
# loop to receive all the data
while True:
d = await reader.read(4096)
if not d:
break
data += d
writer.close()
await writer.wait_closed()
except asyncio.CancelledError:
writer.close()
await writer.wait_closed()
return
except (ConnectionError, OSError):
return
data = await self._fix_api_data(data)
data = json.loads(data)
return data
@staticmethod
async def _fix_api_data(data: bytes):
if data.endswith(b"\x00"):
str_data = data.decode("utf-8")[:-1]
else:
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("}{", "},{")
# fix an error with a bmminer return having a specific comma that breaks json.loads()
str_data = str_data.replace("[,{", "[{")
# fix an error with a btminer return having a missing comma. (2023-01-06 version)
str_data = str_data.replace('""temp0', '","temp0')
# fix an error with Avalonminers returning inf and nan
str_data = str_data.replace("info", "1nfo")
str_data = str_data.replace("inf", "0")
str_data = str_data.replace("1nfo", "info")
str_data = str_data.replace("nan", "0")
# fix whatever this garbage from avalonminers is `,"id":1}`
if str_data.startswith(","):
str_data = f"{{{str_data[1:]}"
# try to fix an error with overflowing the recieve buffer
# this can happen in cases such as bugged btminers returning arbitrary length error info with 100s of errors.
if not str_data.endswith("}"):
str_data = ",".join(str_data.split(",")[:-1]) + "}"
# fix a really nasty bug with whatsminer API v2.0.4 where they return a list structured like a dict
if re.search(r"\"error_code\":\[\".+\"\]", str_data):
str_data = str_data.replace("[", "{").replace("]", "}")
return str_data
@staticmethod
def _select_miner_from_classes(
ip: ipaddress.ip_address,
miner_model: Union[str, None],
miner_type: Union[MinerTypes, None],
) -> AnyMiner:
try:
return MINER_CLASSES[miner_type][str(miner_model).upper()](ip)
except LookupError:
if miner_type in MINER_CLASSES:
return MINER_CLASSES[miner_type][None](ip)
return UnknownMiner(str(ip))
async def get_miner_antminer(self, ip: str):
sock_json_data = await self.send_api_command(ip, "version")
try:
miner_model = sock_json_data["VERSION"][0]["Type"]
return self._select_miner_from_classes(
ip=ip, miner_model=miner_model, miner_type=MinerTypes.ANTMINER
)
except (TypeError, LookupError):
pass
# last resort, this is slow
auth = aiohttp.BasicAuth("root", "root")
web_json_data = await self.send_web_command(
ip, "/cgi-bin/get_system_info.cgi", auth=auth
)
if web_json_data:
if web_json_data.get("minertype") is not None:
miner_model = web_json_data["minertype"]
return self._select_miner_from_classes(
ip=ip, miner_model=miner_model, miner_type=MinerTypes.ANTMINER
)
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.ANTMINER
)
async def get_miner_goldshell(self, ip: str):
json_data = await self.send_web_command(ip, "/mcb/status")
if json_data:
if json_data.get("model") is not None:
miner_model = json_data["model"].replace("-", " ")
return self._select_miner_from_classes(
ip=ip, miner_model=miner_model, miner_type=MinerTypes.GOLDSHELL
)
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.GOLDSHELL
)
async def get_miner_whatsminer(self, ip: str):
sock_json_data = await self.send_api_command(ip, "devdetails")
try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"]
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_model,
miner_type=MinerTypes.WHATSMINER,
)
except (TypeError, LookupError):
pass
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.WHATSMINER
)
async def get_miner_avalonminer(self, ip: str):
sock_json_data = await self.send_api_command(ip, "version")
try:
miner_model = sock_json_data["VERSION"][0]["PROD"]
if "-" in miner_model:
miner_model = miner_model.split("-")
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_model,
miner_type=MinerTypes.AVALONMINER,
)
except (TypeError, LookupError):
pass
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.AVALONMINER
)
async def get_miner_innosilicon(self, ip: str):
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.INNOSILICON
)
async def get_miner_braiins_os(self, ip: str):
sock_json_data = await self.send_api_command(ip, "devdetails")
try:
miner_model = sock_json_data["DEVDETAILS"][0]["Model"]
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_model,
miner_type=MinerTypes.BRAIINS_OS,
)
except (TypeError, LookupError):
pass
try:
async with aiohttp.ClientSession as session:
d = await session.post(
url, json={"query": "{bosminer {info{modelName}}}"}
)
if d.status == 200:
json_data = await d.json()
miner_model = json_data["data"]["bosminer"]["info"]["modelName"]
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_model,
miner_type=MinerTypes.BRAIINS_OS,
)
except aiohttp.ClientError:
pass
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.BRAIINS_OS
)
async def get_miner_vnish(self, ip: str):
sock_json_data = await self.send_api_command(ip, "stats")
try:
miner_model = sock_json_data["STATS"][0]["Type"].upper()
if " (VNISH" in miner_model:
split_miner_model = miner_model.split(" (VNISH ")
miner_model = split_miner_model[0]
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_model,
miner_type=MinerTypes.VNISH,
)
except (TypeError, LookupError):
pass
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.VNISH
)
async def get_miner_hiveon(self, ip: str):
sock_json_data = await self.send_api_command(ip, "version")
try:
miner_type = sock_json_data["VERSION"][0]["Type"]
return self._select_miner_from_classes(
ip=IPv4Address(ip),
miner_model=miner_type.replace(" HIVEON", ""),
miner_type=MinerTypes.HIVEON,
)
except (TypeError, LookupError):
pass
return self._select_miner_from_classes(
ip=ip, miner_model=None, miner_type=MinerTypes.HIVEON
)
miner_factory = MinerFactory()