Dev (#12)
* changed over to package format and removed tools, added poetry * reformat into miner_interface project * add dist to .gitignore * update readme and finish reformatting * Added couple missing imports. (#13) * change name to pyasic Co-authored-by: upstreamdata <brett@upstreamdata.ca> Co-authored-by: Mika Impola <mika@impola.fi>
This commit is contained in:
104
pyasic/miners/__init__.py
Normal file
104
pyasic/miners/__init__.py
Normal file
@@ -0,0 +1,104 @@
|
||||
import asyncssh
|
||||
import logging
|
||||
import ipaddress
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
|
||||
class BaseMiner:
|
||||
def __init__(self, *args) -> None:
|
||||
self.ip = None
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.api = None
|
||||
self.api_type = None
|
||||
self.model = None
|
||||
self.light = None
|
||||
self.hostname = None
|
||||
self.nominal_chips = 1
|
||||
self.version = None
|
||||
self.fan_count = 2
|
||||
self.config = None
|
||||
|
||||
def __repr__(self):
|
||||
return f"{'' if not self.api_type else self.api_type} {'' if not self.model else self.model}: {str(self.ip)}"
|
||||
|
||||
def __lt__(self, other):
|
||||
return ipaddress.ip_address(self.ip) < ipaddress.ip_address(other.ip)
|
||||
|
||||
def __gt__(self, other):
|
||||
return ipaddress.ip_address(self.ip) > ipaddress.ip_address(other.ip)
|
||||
|
||||
def __eq__(self, other):
|
||||
return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip)
|
||||
|
||||
async def _get_ssh_connection(self) -> asyncssh.connect:
|
||||
"""Create a new asyncssh connection"""
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username=self.uname,
|
||||
password=self.pwd,
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except asyncssh.misc.PermissionDenied:
|
||||
try:
|
||||
conn = await asyncssh.connect(
|
||||
str(self.ip),
|
||||
known_hosts=None,
|
||||
username="root",
|
||||
password="admin",
|
||||
server_host_key_algs=["ssh-rsa"],
|
||||
)
|
||||
return conn
|
||||
except Exception as e:
|
||||
# logging.warning(f"{self} raised an exception: {e}")
|
||||
raise e
|
||||
except OSError as e:
|
||||
logging.warning(f"Connection refused: {self}")
|
||||
raise e
|
||||
except Exception as e:
|
||||
# logging.warning(f"{self} raised an exception: {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 with (await self._get_ssh_connection()) as conn:
|
||||
await asyncssh.scp(src, (conn, dest))
|
||||
|
||||
async def check_light(self):
|
||||
return self.light
|
||||
|
||||
async def get_board_info(self):
|
||||
return None
|
||||
|
||||
async def get_config(self):
|
||||
return None
|
||||
|
||||
async def get_hostname(self):
|
||||
return None
|
||||
|
||||
async def get_model(self):
|
||||
return None
|
||||
|
||||
async def reboot(self):
|
||||
return False
|
||||
|
||||
async def restart_backend(self):
|
||||
return False
|
||||
|
||||
async def send_config(self, *args, **kwargs):
|
||||
return None
|
||||
|
||||
async def get_mac(self):
|
||||
return None
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
return MinerData(ip=str(self.ip))
|
||||
5
pyasic/miners/_backends/__init__.py
Normal file
5
pyasic/miners/_backends/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .bmminer import BMMiner
|
||||
from .bosminer import BOSMiner
|
||||
from .btminer import BTMiner
|
||||
from .cgminer import CGMiner
|
||||
from .hiveon import Hiveon
|
||||
264
pyasic/miners/_backends/bmminer.py
Normal file
264
pyasic/miners/_backends/bmminer.py
Normal file
@@ -0,0 +1,264 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from pyasic.API.bmminer import BMMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BMMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BMMinerAPI(ip)
|
||||
self.api_type = "BMMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def get_model(self) -> str or None:
|
||||
"""Get miner model.
|
||||
|
||||
:return: Miner model or None.
|
||||
"""
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
# get devdetails data
|
||||
version_data = await self.api.devdetails()
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
# handle Antminer BMMiner as a base
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
|
||||
# if we don't get devdetails, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
"""Get miner hostname.
|
||||
|
||||
:return: The hostname of the miner as a string or "?"
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# if we get the connection, check hostname
|
||||
if conn is not None:
|
||||
# get output of the hostname file
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
|
||||
# return hostname data
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
# return ? if we fail to get hostname with no ssh connection
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
except Exception:
|
||||
# return ? if we fail to get hostname with an exception
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
:param cmd: The command to run.
|
||||
|
||||
:return: Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return result
|
||||
|
||||
async def get_config(self) -> list or None:
|
||||
"""Get the pool configuration of the miner.
|
||||
|
||||
:return: Pool config data or None.
|
||||
"""
|
||||
# get pool data
|
||||
pools = await self.api.pools()
|
||||
pool_data = []
|
||||
|
||||
# ensure we got pool data
|
||||
if not pools:
|
||||
return
|
||||
|
||||
# parse all the pools
|
||||
for pool in pools["POOLS"]:
|
||||
pool_data.append({"url": pool["URL"], "user": pool["User"], "pwd": "123"})
|
||||
return pool_data
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
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 get_data(self) -> MinerData:
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "pools", "stats", ignore_x19_error=True
|
||||
)
|
||||
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 av")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000, 2)
|
||||
|
||||
if stats:
|
||||
boards = stats.get("STATS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
for board_num in range(1, 16, 5):
|
||||
for _b_num in range(5):
|
||||
b = boards[1].get(f"chain_acn{board_num + _b_num}")
|
||||
|
||||
if b and not b == 0 and board_offset == -1:
|
||||
board_offset = board_num
|
||||
if board_offset == -1:
|
||||
board_offset = 1
|
||||
|
||||
data.left_chips = boards[1].get(f"chain_acn{board_offset}")
|
||||
data.center_chips = boards[1].get(f"chain_acn{board_offset+1}")
|
||||
data.right_chips = boards[1].get(f"chain_acn{board_offset+2}")
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
if temp:
|
||||
if len(temp) > 1:
|
||||
for fan_num in range(1, 8, 4):
|
||||
for _f_num in range(4):
|
||||
f = temp[1].get(f"fan{fan_num + _f_num}")
|
||||
if f and not f == 0 and fan_offset == -1:
|
||||
fan_offset = fan_num
|
||||
if fan_offset == -1:
|
||||
fan_offset = 1
|
||||
for fan in range(self.fan_count):
|
||||
setattr(
|
||||
data, f"fan_{fan + 1}", temp[1].get(f"fan{fan_offset+fan}")
|
||||
)
|
||||
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
env_temp_list = []
|
||||
for item in range(3):
|
||||
board_temp = temp[1].get(f"temp{item + board_offset}")
|
||||
chip_temp = temp[1].get(f"temp2_{item + board_offset}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_temp)
|
||||
if f"temp_pcb{item}" in temp[1].keys():
|
||||
env_temp = temp[1][f"temp_pcb{item}"].split("-")[0]
|
||||
if not env_temp == 0:
|
||||
env_temp_list.append(int(env_temp))
|
||||
data.env_temp = sum(env_temp_list) / len(env_temp_list)
|
||||
|
||||
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://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
403
pyasic/miners/_backends/bosminer.py
Normal file
403
pyasic/miners/_backends/bosminer.py
Normal file
@@ -0,0 +1,403 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
import json
|
||||
|
||||
import toml
|
||||
|
||||
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BOSMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
:return: Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return str(result)
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
"""Sends command to turn on fault light on the miner."""
|
||||
logging.debug(f"{self}: Sending fault_light on command.")
|
||||
self.light = True
|
||||
_ret = await self.send_ssh_command("miner fault_light on")
|
||||
logging.debug(f"{self}: fault_light on command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
"""Sends command to turn off fault light on the miner."""
|
||||
logging.debug(f"{self}: Sending fault_light off command.")
|
||||
self.light = False
|
||||
_ret = await self.send_ssh_command("miner fault_light off")
|
||||
logging.debug(f"{self}: fault_light off command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
return await self.restart_bosminer()
|
||||
|
||||
async def restart_bosminer(self) -> bool:
|
||||
"""Restart bosminer hashing process."""
|
||||
logging.debug(f"{self}: Sending bosminer restart command.")
|
||||
_ret = await self.send_ssh_command("/etc/init.d/bosminer restart")
|
||||
logging.debug(f"{self}: bosminer restart command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
"""Reboots power to the physical miner."""
|
||||
logging.debug(f"{self}: Sending reboot command.")
|
||||
_ret = await self.send_ssh_command("/sbin/reboot")
|
||||
logging.debug(f"{self}: Reboot command completed.")
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def get_config(self) -> None:
|
||||
logging.debug(f"{self}: Getting config.")
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Reading config file.")
|
||||
async with sftp.open("/etc/bosminer.toml") as file:
|
||||
toml_data = toml.loads(await file.read())
|
||||
logging.debug(f"{self}: Converting config file.")
|
||||
cfg = MinerConfig().from_raw(toml_data)
|
||||
self.config = cfg
|
||||
return self.config
|
||||
|
||||
async def get_hostname(self) -> str:
|
||||
"""Get miner hostname.
|
||||
|
||||
:return: The hostname of the miner as a string or "?"
|
||||
"""
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
if conn is not None:
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return "?"
|
||||
|
||||
async def get_model(self) -> str or None:
|
||||
"""Get miner model.
|
||||
|
||||
:return: Miner model or None.
|
||||
"""
|
||||
# check if model is cached
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# get devdetails data
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError as e:
|
||||
version_data = None
|
||||
if e.message == "Not ready":
|
||||
cfg = json.loads(await self.send_ssh_command("bosminer config --data"))
|
||||
model = cfg.get("data").get("format").get("model")
|
||||
if model:
|
||||
model = model.replace("Antminer ", "")
|
||||
self.model = model
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we get data back, parse it for model
|
||||
if version_data:
|
||||
if not version_data["DEVDETAILS"] == []:
|
||||
# handle Antminer BOSMiner as a base
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace(
|
||||
"Antminer ", ""
|
||||
)
|
||||
logging.debug(f"Found model for {self.ip}: {self.model} (BOS)")
|
||||
return self.model + " (BOS)"
|
||||
|
||||
# if we don't get devdetails, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_version(self):
|
||||
"""Get miner firmware version.
|
||||
|
||||
:return: Miner firmware version or None.
|
||||
"""
|
||||
# check if version is cached
|
||||
if self.version:
|
||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
||||
return self.version
|
||||
|
||||
# get output of bos version file
|
||||
version_data = await self.send_ssh_command("cat /etc/bos_version")
|
||||
|
||||
# if we get the version data, parse it
|
||||
if version_data:
|
||||
self.version = version_data.split("-")[5]
|
||||
logging.debug(f"Found version for {self.ip}: {self.version}")
|
||||
return self.version
|
||||
|
||||
# if we fail to get version, log a failed attempt
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
|
||||
"""Configures miner with yaml config."""
|
||||
logging.debug(f"{self}: Sending config.")
|
||||
if ip_user:
|
||||
suffix = str(self.ip).split(".")[-1]
|
||||
toml_conf = (
|
||||
MinerConfig()
|
||||
.from_yaml(yaml_config)
|
||||
.as_bos(model=self.model.replace(" (BOS)", ""), user_suffix=suffix)
|
||||
)
|
||||
else:
|
||||
toml_conf = (
|
||||
MinerConfig()
|
||||
.from_yaml(yaml_config)
|
||||
.as_bos(model=self.model.replace(" (BOS)", ""))
|
||||
)
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
logging.debug(f"{self}: Opening SFTP connection.")
|
||||
async with conn.start_sftp_client() as sftp:
|
||||
logging.debug(f"{self}: Opening config file.")
|
||||
async with sftp.open("/etc/bosminer.toml", "w+") as file:
|
||||
await file.write(toml_conf)
|
||||
logging.debug(f"{self}: Restarting BOSMiner")
|
||||
await conn.run("/etc/init.d/bosminer restart")
|
||||
|
||||
async def get_board_info(self) -> dict:
|
||||
"""Gets data on each board and chain in the miner."""
|
||||
logging.debug(f"{self}: Getting board info.")
|
||||
devdetails = await self.api.devdetails()
|
||||
if not devdetails.get("DEVDETAILS"):
|
||||
print("devdetails error", devdetails)
|
||||
return {0: [], 1: [], 2: []}
|
||||
devs = devdetails["DEVDETAILS"]
|
||||
boards = {}
|
||||
offset = devs[0]["ID"]
|
||||
for board in devs:
|
||||
boards[board["ID"] - offset] = []
|
||||
if not board["Chips"] == self.nominal_chips:
|
||||
nominal = False
|
||||
else:
|
||||
nominal = True
|
||||
boards[board["ID"] - offset].append(
|
||||
{
|
||||
"chain": board["ID"] - offset,
|
||||
"chip_count": board["Chips"],
|
||||
"chip_status": "o" * board["Chips"],
|
||||
"nominal": nominal,
|
||||
}
|
||||
)
|
||||
logging.debug(f"Found board data for {self}: {boards}")
|
||||
return boards
|
||||
|
||||
async def get_bad_boards(self) -> dict:
|
||||
"""Checks for and provides list of non working boards."""
|
||||
boards = await self.get_board_info()
|
||||
bad_boards = {}
|
||||
for board in boards.keys():
|
||||
for chain in boards[board]:
|
||||
if not chain["chip_count"] == 63:
|
||||
if board not in bad_boards.keys():
|
||||
bad_boards[board] = []
|
||||
bad_boards[board].append(chain)
|
||||
return bad_boards
|
||||
|
||||
async def check_good_boards(self) -> str:
|
||||
"""Checks for and provides list for working boards."""
|
||||
devs = await self.api.devdetails()
|
||||
bad = 0
|
||||
chains = devs["DEVDETAILS"]
|
||||
for chain in chains:
|
||||
if chain["Chips"] == 0:
|
||||
bad += 1
|
||||
if not bad > 0:
|
||||
return str(self.ip)
|
||||
|
||||
async def get_data(self) -> MinerData:
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
board_offset = -1
|
||||
fan_offset = -1
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "temps", "tunerstatus", "pools", "devdetails", "fans"
|
||||
)
|
||||
except APIError as e:
|
||||
if str(e.message) == "Not ready":
|
||||
miner_data = await self.api.multicommand(
|
||||
"summary", "tunerstatus", "pools", "fans"
|
||||
)
|
||||
if miner_data:
|
||||
break
|
||||
if not miner_data:
|
||||
return data
|
||||
summary = miner_data.get("summary")
|
||||
temps = miner_data.get("temps")
|
||||
tunerstatus = miner_data.get("tunerstatus")
|
||||
pools = miner_data.get("pools")
|
||||
devdetails = miner_data.get("devdetails")
|
||||
fans = miner_data.get("fans")
|
||||
|
||||
if summary:
|
||||
hr = summary[0].get("SUMMARY")
|
||||
if hr:
|
||||
if len(hr) > 0:
|
||||
hr = hr[0].get("MHS 1m")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000000, 2)
|
||||
|
||||
if temps:
|
||||
temp = temps[0].get("TEMPS")
|
||||
if temp:
|
||||
if len(temp) > 0:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
offset = 6 if temp[0]["ID"] in [6, 7, 8] else temp[0]["ID"]
|
||||
for board in temp:
|
||||
_id = board["ID"] - offset
|
||||
chip_temp = round(board["Chip"])
|
||||
board_temp = round(board["Board"])
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
|
||||
if fans:
|
||||
fan_data = fans[0].get("FANS")
|
||||
if fan_data:
|
||||
for fan in range(self.fan_count):
|
||||
setattr(data, f"fan_{fan+1}", fan_data[fan]["RPM"])
|
||||
|
||||
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[0].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://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if tunerstatus:
|
||||
tuner = tunerstatus[0].get("TUNERSTATUS")
|
||||
if tuner:
|
||||
if len(tuner) > 0:
|
||||
wattage = tuner[0].get("PowerLimit")
|
||||
if wattage:
|
||||
data.wattage = wattage
|
||||
|
||||
if devdetails:
|
||||
boards = devdetails[0].get("DEVDETAILS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
offset = 6 if boards[0]["ID"] in [6, 7, 8] else boards[0]["ID"]
|
||||
for board in boards:
|
||||
_id = board["ID"] - offset
|
||||
chips = board["Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
|
||||
return data
|
||||
|
||||
async def get_mac(self):
|
||||
result = await self.send_ssh_command("cat /sys/class/net/eth0/address")
|
||||
return result.upper().strip()
|
||||
50
pyasic/miners/_backends/bosminer_old.py
Normal file
50
pyasic/miners/_backends/bosminer_old.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import logging
|
||||
|
||||
import ipaddress
|
||||
|
||||
from pyasic.API.bosminer import BOSMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class BOSMinerOld(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BOSMinerAPI(ip)
|
||||
self.api_type = "BOSMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def send_ssh_command(self, cmd: str) -> str or None:
|
||||
"""Send a command to the miner over ssh.
|
||||
|
||||
:return: Result of the command or None.
|
||||
"""
|
||||
result = None
|
||||
|
||||
# open an ssh connection
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
# 3 retries
|
||||
for i in range(3):
|
||||
try:
|
||||
# run the command and get the result
|
||||
result = await conn.run(cmd)
|
||||
if result.stdout:
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
if e == "SSH connection closed":
|
||||
return "Update completed."
|
||||
# if the command fails, log it
|
||||
logging.warning(f"{self} command {cmd} error: {e}")
|
||||
|
||||
# on the 3rd retry, return None
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
# return the result, either command output or None
|
||||
return str(result)
|
||||
|
||||
|
||||
async def update_to_plus(self):
|
||||
result = await self.send_ssh_command("opkg update && opkg install bos_plus")
|
||||
return result
|
||||
246
pyasic/miners/_backends/btminer.py
Normal file
246
pyasic/miners/_backends/btminer.py
Normal file
@@ -0,0 +1,246 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from pyasic.API.btminer import BTMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class BTMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = BTMinerAPI(ip)
|
||||
self.api_type = "BTMiner"
|
||||
|
||||
async def get_model(self):
|
||||
if self.model:
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
version_data = await self.api.devdetails()
|
||||
if version_data:
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].split("V")[0]
|
||||
logging.debug(f"Found model for {self.ip}: {self.model}")
|
||||
return self.model
|
||||
logging.warning(f"Failed to get model for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str or None:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
host_data = await self.api.get_miner_info()
|
||||
if host_data:
|
||||
host = host_data["Msg"]["hostname"]
|
||||
logging.debug(f"Found hostname for {self.ip}: {host}")
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
except APIError:
|
||||
logging.info(f"Failed to get hostname for miner: {self}")
|
||||
return None
|
||||
except Exception:
|
||||
logging.warning(f"Failed to get hostname for miner: {self}")
|
||||
return None
|
||||
|
||||
async def get_board_info(self) -> dict:
|
||||
"""Gets data on each board and chain in the miner."""
|
||||
logging.debug(f"{self}: Getting board info.")
|
||||
devs = await self.api.devs()
|
||||
if not devs.get("DEVS"):
|
||||
print("devs error", devs)
|
||||
return {0: [], 1: [], 2: []}
|
||||
devs = devs["DEVS"]
|
||||
boards = {}
|
||||
offset = devs[0]["ID"]
|
||||
for board in devs:
|
||||
boards[board["ID"] - offset] = []
|
||||
if "Effective Chips" in board.keys():
|
||||
if not board["Effective Chips"] in self.nominal_chips:
|
||||
nominal = False
|
||||
else:
|
||||
nominal = True
|
||||
boards[board["ID"] - offset].append(
|
||||
{
|
||||
"chain": board["ID"] - offset,
|
||||
"chip_count": board["Effective Chips"],
|
||||
"chip_status": "o" * board["Effective Chips"],
|
||||
"nominal": nominal,
|
||||
}
|
||||
)
|
||||
else:
|
||||
logging.warning(f"Incorrect board data from {self}: {board}")
|
||||
print(board)
|
||||
logging.debug(f"Found board data for {self}: {boards}")
|
||||
return boards
|
||||
|
||||
async def get_mac(self):
|
||||
mac = ""
|
||||
data = await self.api.summary()
|
||||
if data:
|
||||
if data.get("SUMMARY"):
|
||||
if len(data["SUMMARY"]) > 0:
|
||||
_mac = data["SUMMARY"][0].get("MAC")
|
||||
if _mac:
|
||||
mac = _mac
|
||||
if mac == "":
|
||||
try:
|
||||
data = await self.api.get_miner_info()
|
||||
if data:
|
||||
if "Msg" in data.keys():
|
||||
if "mac" in data["Msg"].keys():
|
||||
mac = data["Msg"]["mac"]
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
return str(mac).upper()
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
mac = None
|
||||
|
||||
try:
|
||||
model = await self.get_model()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get model: {self}")
|
||||
model = None
|
||||
data.model = "Whatsminer"
|
||||
|
||||
try:
|
||||
hostname = await self.get_hostname()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get hostname: {self}")
|
||||
hostname = None
|
||||
data.hostname = "Whatsminer"
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
miner_data = None
|
||||
for i in range(DATA_RETRIES):
|
||||
try:
|
||||
miner_data = await self.api.multicommand("summary", "devs", "pools")
|
||||
if miner_data:
|
||||
break
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
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:
|
||||
if summary_data[0].get("MAC"):
|
||||
mac = summary_data[0]["MAC"]
|
||||
|
||||
if summary_data[0].get("Env Temp"):
|
||||
data.env_temp = summary_data[0]["Env Temp"]
|
||||
|
||||
data.fan_1 = summary_data[0]["Fan Speed In"]
|
||||
data.fan_2 = summary_data[0]["Fan Speed Out"]
|
||||
|
||||
hr = summary_data[0].get("MHS 1m")
|
||||
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:
|
||||
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
|
||||
for board in temp_data:
|
||||
_id = board["ASC"]
|
||||
chip_temp = round(board["Chip Temp Avg"])
|
||||
board_temp = round(board["Temperature"])
|
||||
setattr(data, f"{board_map[_id]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[_id]}_temp", board_temp)
|
||||
|
||||
if devs:
|
||||
boards = devs.get("DEVS")
|
||||
if boards:
|
||||
if len(boards) > 0:
|
||||
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
|
||||
if "ID" in boards[0].keys():
|
||||
id_key = "ID"
|
||||
else:
|
||||
id_key = "ASC"
|
||||
offset = boards[0][id_key]
|
||||
for board in boards:
|
||||
_id = board[id_key] - offset
|
||||
chips = board["Effective Chips"]
|
||||
setattr(data, board_map[_id], chips)
|
||||
|
||||
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://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
if not mac:
|
||||
try:
|
||||
mac = await self.get_mac()
|
||||
except APIError:
|
||||
logging.info(f"Failed to get mac: {self}")
|
||||
mac = None
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
return data
|
||||
210
pyasic/miners/_backends/cgminer.py
Normal file
210
pyasic/miners/_backends/cgminer.py
Normal file
@@ -0,0 +1,210 @@
|
||||
import ipaddress
|
||||
import logging
|
||||
|
||||
|
||||
from pyasic.API.cgminer import CGMinerAPI
|
||||
from pyasic.miners import BaseMiner
|
||||
from pyasic.API import APIError
|
||||
|
||||
from pyasic.data import MinerData
|
||||
|
||||
from pyasic.settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
|
||||
|
||||
|
||||
class CGMiner(BaseMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api = CGMinerAPI(ip)
|
||||
self.api_type = "CGMiner"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
self.config = None
|
||||
|
||||
async def get_model(self):
|
||||
if self.model:
|
||||
return self.model
|
||||
try:
|
||||
version_data = await self.api.devdetails()
|
||||
except APIError:
|
||||
return None
|
||||
if version_data:
|
||||
self.model = version_data["DEVDETAILS"][0]["Model"].replace("Antminer ", "")
|
||||
return self.model
|
||||
return None
|
||||
|
||||
async def get_hostname(self) -> str or None:
|
||||
if self.hostname:
|
||||
return self.hostname
|
||||
try:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
if conn is not None:
|
||||
data = await conn.run("cat /proc/sys/kernel/hostname")
|
||||
host = data.stdout.strip()
|
||||
self.hostname = host
|
||||
return self.hostname
|
||||
else:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def send_ssh_command(self, cmd):
|
||||
result = None
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
for i in range(3):
|
||||
try:
|
||||
result = await conn.run(cmd)
|
||||
result = result.stdout
|
||||
except Exception as e:
|
||||
print(f"{cmd} error: {e}")
|
||||
if i == 3:
|
||||
return
|
||||
continue
|
||||
return result
|
||||
|
||||
async def restart_backend(self) -> bool:
|
||||
return await self.restart_cgminer()
|
||||
|
||||
async def restart_cgminer(self) -> bool:
|
||||
commands = ["cgminer-api restart", "/usr/bin/cgminer-monitor >/dev/null 2>&1"]
|
||||
commands = ";".join(commands)
|
||||
_ret = await self.send_ssh_command(commands)
|
||||
if isinstance(_ret, str):
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
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:
|
||||
commands = [
|
||||
"mkdir -p /etc/tmp/",
|
||||
'echo "*/3 * * * * /usr/bin/cgminer-monitor" > /etc/tmp/root',
|
||||
"crontab -u root /etc/tmp/root",
|
||||
"/usr/bin/cgminer-monitor >/dev/null 2>&1",
|
||||
]
|
||||
commands = ";".join(commands)
|
||||
await self.send_ssh_command(commands)
|
||||
|
||||
async def stop_cgminer(self) -> None:
|
||||
commands = [
|
||||
"mkdir -p /etc/tmp/",
|
||||
'echo "" > /etc/tmp/root',
|
||||
"crontab -u root /etc/tmp/root",
|
||||
"killall cgminer",
|
||||
]
|
||||
commands = ";".join(commands)
|
||||
await self.send_ssh_command(commands)
|
||||
|
||||
async def get_config(self) -> None:
|
||||
async with (await self._get_ssh_connection()) as conn:
|
||||
command = "cat /etc/config/cgminer"
|
||||
result = await conn.run(command, check=True)
|
||||
self.config = result.stdout
|
||||
print(str(self.config))
|
||||
|
||||
async def get_data(self):
|
||||
data = MinerData(ip=str(self.ip), ideal_chips=self.nominal_chips * 3)
|
||||
|
||||
model = await self.get_model()
|
||||
hostname = await self.get_hostname()
|
||||
mac = await self.get_mac()
|
||||
|
||||
if model:
|
||||
data.model = model
|
||||
|
||||
if hostname:
|
||||
data.hostname = hostname
|
||||
|
||||
if mac:
|
||||
data.mac = mac
|
||||
|
||||
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 1m")
|
||||
if hr:
|
||||
data.hashrate = round(hr / 1000, 2)
|
||||
|
||||
if stats:
|
||||
temp = stats.get("STATS")
|
||||
if temp:
|
||||
if len(temp) > 1:
|
||||
data.fan_1 = temp[1].get("fan1")
|
||||
data.fan_2 = temp[1].get("fan2")
|
||||
data.fan_3 = temp[1].get("fan3")
|
||||
data.fan_4 = temp[1].get("fan4")
|
||||
|
||||
board_map = {1: "left_board", 2: "center_board", 3: "right_board"}
|
||||
for item in range(1, 4):
|
||||
board_temp = temp[1].get(f"temp{item}")
|
||||
chip_temp = temp[1].get(f"temp2_{item}")
|
||||
setattr(data, f"{board_map[item]}_chip_temp", chip_temp)
|
||||
setattr(data, f"{board_map[item]}_temp", board_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://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_1_url = pool_1
|
||||
|
||||
if pool_1_user:
|
||||
data.pool_1_user = pool_1_user
|
||||
|
||||
if pool_2:
|
||||
pool_2 = pool_2.replace("stratum+tcp://", "").replace(
|
||||
"stratum2+tcp://", ""
|
||||
)
|
||||
data.pool_2_url = pool_2
|
||||
|
||||
if pool_2_user:
|
||||
data.pool_2_user = pool_2_user
|
||||
|
||||
if quota:
|
||||
data.pool_split = str(quota)
|
||||
|
||||
return data
|
||||
48
pyasic/miners/_backends/hiveon.py
Normal file
48
pyasic/miners/_backends/hiveon.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from pyasic.miners._backends import BMMiner
|
||||
import ipaddress
|
||||
|
||||
|
||||
class Hiveon(BMMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ipaddress.ip_address(ip)
|
||||
self.api_type = "Hiveon"
|
||||
self.uname = "root"
|
||||
self.pwd = "admin"
|
||||
|
||||
async def get_board_info(self) -> dict:
|
||||
"""Gets data on each board and chain in the miner."""
|
||||
board_stats = await self.api.stats()
|
||||
stats = board_stats["STATS"][1]
|
||||
boards = {}
|
||||
board_chains = {0: [2, 9, 10], 1: [3, 11, 12], 2: [4, 13, 14]}
|
||||
for idx, board in enumerate(board_chains):
|
||||
boards[board] = []
|
||||
for chain in board_chains[board]:
|
||||
count = stats[f"chain_acn{chain}"]
|
||||
chips = stats[f"chain_acs{chain}"].replace(" ", "")
|
||||
if not count == 18 or "x" in chips:
|
||||
nominal = False
|
||||
else:
|
||||
nominal = True
|
||||
boards[board].append(
|
||||
{
|
||||
"chain": chain,
|
||||
"chip_count": count,
|
||||
"chip_status": chips,
|
||||
"nominal": nominal,
|
||||
}
|
||||
)
|
||||
return boards
|
||||
|
||||
async def get_bad_boards(self) -> dict:
|
||||
"""Checks for and provides list of non working boards."""
|
||||
boards = await self.get_board_info()
|
||||
bad_boards = {}
|
||||
for board in boards.keys():
|
||||
for chain in boards[board]:
|
||||
if not chain["chip_count"] == 18 or "x" in chain["chip_status"]:
|
||||
if board not in bad_boards.keys():
|
||||
bad_boards[board] = []
|
||||
bad_boards[board].append(chain)
|
||||
return bad_boards
|
||||
3
pyasic/miners/_types/__init__.py
Normal file
3
pyasic/miners/_types/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .antminer import *
|
||||
from .avalonminer import *
|
||||
from .whatsminer import *
|
||||
10
pyasic/miners/_types/antminer/X17/S17.py
Normal file
10
pyasic/miners/_types/antminer/X17/S17.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17"
|
||||
self.nominal_chips = 48
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/S17_Plus.py
Normal file
10
pyasic/miners/_types/antminer/X17/S17_Plus.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Plus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17+"
|
||||
self.nominal_chips = 65
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/S17_Pro.py
Normal file
10
pyasic/miners/_types/antminer/X17/S17_Pro.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17Pro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17 Pro"
|
||||
self.nominal_chips = 48
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/S17e.py
Normal file
10
pyasic/miners/_types/antminer/X17/S17e.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S17e(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S17e"
|
||||
self.nominal_chips = 135
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/T17.py
Normal file
10
pyasic/miners/_types/antminer/X17/T17.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17"
|
||||
self.nominal_chips = 30
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/T17_Plus.py
Normal file
10
pyasic/miners/_types/antminer/X17/T17_Plus.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17Plus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17+"
|
||||
self.nominal_chips = 44
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X17/T17e.py
Normal file
10
pyasic/miners/_types/antminer/X17/T17e.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T17e(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T17e"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 4
|
||||
8
pyasic/miners/_types/antminer/X17/__init__.py
Normal file
8
pyasic/miners/_types/antminer/X17/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .S17 import S17
|
||||
from .S17_Plus import S17Plus
|
||||
from .S17_Pro import S17Pro
|
||||
from .S17e import S17e
|
||||
|
||||
from .T17 import T17
|
||||
from .T17_Plus import T17Plus
|
||||
from .T17e import T17e
|
||||
10
pyasic/miners/_types/antminer/X19/S19.py
Normal file
10
pyasic/miners/_types/antminer/X19/S19.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19"
|
||||
self.nominal_chips = 76
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X19/S19_Pro.py
Normal file
10
pyasic/miners/_types/antminer/X19/S19_Pro.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19Pro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19 Pro"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X19/S19a.py
Normal file
10
pyasic/miners/_types/antminer/X19/S19a.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19a(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19a"
|
||||
self.nominal_chips = 72
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X19/S19j.py
Normal file
10
pyasic/miners/_types/antminer/X19/S19j.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19j(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19j"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X19/S19j_Pro.py
Normal file
10
pyasic/miners/_types/antminer/X19/S19j_Pro.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S19jPro(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S19j Pro"
|
||||
self.nominal_chips = 126
|
||||
self.fan_count = 4
|
||||
10
pyasic/miners/_types/antminer/X19/T19.py
Normal file
10
pyasic/miners/_types/antminer/X19/T19.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T19(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T19"
|
||||
self.nominal_chips = 76
|
||||
self.fan_count = 4
|
||||
9
pyasic/miners/_types/antminer/X19/__init__.py
Normal file
9
pyasic/miners/_types/antminer/X19/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .S19 import S19
|
||||
from .S19_Pro import S19Pro
|
||||
|
||||
from .S19j import S19j
|
||||
from .S19j_Pro import S19jPro
|
||||
|
||||
from .S19a import S19a
|
||||
|
||||
from .T19 import T19
|
||||
10
pyasic/miners/_types/antminer/X9/S9.py
Normal file
10
pyasic/miners/_types/antminer/X9/S9.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S9(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S9"
|
||||
self.nominal_chips = 63
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/antminer/X9/S9i.py
Normal file
10
pyasic/miners/_types/antminer/X9/S9i.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class S9i(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "S9i"
|
||||
self.nominal_chips = 63
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/antminer/X9/T9.py
Normal file
10
pyasic/miners/_types/antminer/X9/T9.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class T9(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "T9"
|
||||
self.nominal_chips = 57
|
||||
self.fan_count = 2
|
||||
3
pyasic/miners/_types/antminer/X9/__init__.py
Normal file
3
pyasic/miners/_types/antminer/X9/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .S9 import S9
|
||||
from .S9i import S9i
|
||||
from .T9 import T9
|
||||
3
pyasic/miners/_types/antminer/__init__.py
Normal file
3
pyasic/miners/_types/antminer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
10
pyasic/miners/_types/avalonminer/A10X/A1026.py
Normal file
10
pyasic/miners/_types/avalonminer/A10X/A1026.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1026(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1026"
|
||||
self.nominal_chips = 80
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/avalonminer/A10X/A1047.py
Normal file
10
pyasic/miners/_types/avalonminer/A10X/A1047.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1047(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1047"
|
||||
self.nominal_chips = 80
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/avalonminer/A10X/A1066.py
Normal file
10
pyasic/miners/_types/avalonminer/A10X/A1066.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon1066(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 1066"
|
||||
self.nominal_chips = 114
|
||||
self.fan_count = 4
|
||||
3
pyasic/miners/_types/avalonminer/A10X/__init__.py
Normal file
3
pyasic/miners/_types/avalonminer/A10X/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .A1026 import Avalon1026
|
||||
from .A1047 import Avalon1047
|
||||
from .A1066 import Avalon1066
|
||||
10
pyasic/miners/_types/avalonminer/A7X/A721.py
Normal file
10
pyasic/miners/_types/avalonminer/A7X/A721.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon721(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 721"
|
||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
10
pyasic/miners/_types/avalonminer/A7X/A741.py
Normal file
10
pyasic/miners/_types/avalonminer/A7X/A741.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon741(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 741"
|
||||
self.chip_count = 22 # This miner has 4 boards totaling 88
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
10
pyasic/miners/_types/avalonminer/A7X/A761.py
Normal file
10
pyasic/miners/_types/avalonminer/A7X/A761.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon761(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 761"
|
||||
self.chip_count = 18 # This miner has 4 boards totaling 72
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
3
pyasic/miners/_types/avalonminer/A7X/__init__.py
Normal file
3
pyasic/miners/_types/avalonminer/A7X/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .A721 import Avalon721
|
||||
from .A741 import Avalon741
|
||||
from .A761 import Avalon761
|
||||
10
pyasic/miners/_types/avalonminer/A8X/A821.py
Normal file
10
pyasic/miners/_types/avalonminer/A8X/A821.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon821(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 821"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
10
pyasic/miners/_types/avalonminer/A8X/A841.py
Normal file
10
pyasic/miners/_types/avalonminer/A8X/A841.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon841(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 841"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
10
pyasic/miners/_types/avalonminer/A8X/A851.py
Normal file
10
pyasic/miners/_types/avalonminer/A8X/A851.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon851(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 851"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
3
pyasic/miners/_types/avalonminer/A8X/__init__.py
Normal file
3
pyasic/miners/_types/avalonminer/A8X/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .A821 import Avalon821
|
||||
from .A841 import Avalon841
|
||||
from .A851 import Avalon851
|
||||
10
pyasic/miners/_types/avalonminer/A9X/A921.py
Normal file
10
pyasic/miners/_types/avalonminer/A9X/A921.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class Avalon921(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "Avalon 921"
|
||||
self.chip_count = 26 # This miner has 4 boards totaling 104
|
||||
self.fan_count = 1 # also only 1 fan
|
||||
1
pyasic/miners/_types/avalonminer/A9X/__init__.py
Normal file
1
pyasic/miners/_types/avalonminer/A9X/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .A921 import Avalon921
|
||||
4
pyasic/miners/_types/avalonminer/__init__.py
Normal file
4
pyasic/miners/_types/avalonminer/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .A7X import *
|
||||
from .A8X import *
|
||||
from .A9X import *
|
||||
from .A10X import *
|
||||
10
pyasic/miners/_types/whatsminer/M2X/M20S.py
Normal file
10
pyasic/miners/_types/whatsminer/M2X/M20S.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M20S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M20S"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/whatsminer/M2X/M20S_Plus.py
Normal file
10
pyasic/miners/_types/whatsminer/M2X/M20S_Plus.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M20SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M20S+"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/whatsminer/M2X/M21.py
Normal file
10
pyasic/miners/_types/whatsminer/M2X/M21.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M21(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
28
pyasic/miners/_types/whatsminer/M2X/M21S.py
Normal file
28
pyasic/miners/_types/whatsminer/M2X/M21S.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M21S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M21SV60(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S V60"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M21SV20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S V20"
|
||||
self.nominal_chips = 66
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/whatsminer/M2X/M21S_Plus.py
Normal file
10
pyasic/miners/_types/whatsminer/M2X/M21S_Plus.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M21SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M21S+"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
6
pyasic/miners/_types/whatsminer/M2X/__init__.py
Normal file
6
pyasic/miners/_types/whatsminer/M2X/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from .M20S import M20S
|
||||
from .M20S_Plus import M20SPlus
|
||||
|
||||
from .M21 import M21
|
||||
from .M21S import M21S, M21SV20, M21SV60
|
||||
from .M21S_Plus import M21SPlus
|
||||
46
pyasic/miners/_types/whatsminer/M3X/M30S.py
Normal file
46
pyasic/miners/_types/whatsminer/M3X/M30S.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M30S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S"
|
||||
self.nominal_chips = 148
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SV50(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S V50"
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SVG20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S VG20"
|
||||
self.nominal_chips = 70
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SVE20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S VE20"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SVE10(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S VE10"
|
||||
self.nominal_chips = 105
|
||||
self.fan_count = 2
|
||||
37
pyasic/miners/_types/whatsminer/M3X/M30S_Plus.py
Normal file
37
pyasic/miners/_types/whatsminer/M3X/M30S_Plus.py
Normal file
@@ -0,0 +1,37 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M30SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+"
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusVG60(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VG60"
|
||||
self.nominal_chips = 86
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusVE40(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VE40"
|
||||
self.nominal_chips = 156
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusVF20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S+ VF20"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
28
pyasic/miners/_types/whatsminer/M3X/M30S_Plus_Plus.py
Normal file
28
pyasic/miners/_types/whatsminer/M3X/M30S_Plus_Plus.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M30SPlusPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S++"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusPlusVG30(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S++ V30"
|
||||
self.nominal_chips = 111
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M30SPlusPlusVG40(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M30S++ V40"
|
||||
self.nominal_chips = 117
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/whatsminer/M3X/M31S.py
Normal file
10
pyasic/miners/_types/whatsminer/M3X/M31S.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M31S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S"
|
||||
# TODO: Add chip count for this miner (per board) - self.nominal_chips
|
||||
self.fan_count = 2
|
||||
19
pyasic/miners/_types/whatsminer/M3X/M31S_Plus.py
Normal file
19
pyasic/miners/_types/whatsminer/M3X/M31S_Plus.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M31SPlus(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S+"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
|
||||
|
||||
class M31SPlusVE20(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M31S+ VE20"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
10
pyasic/miners/_types/whatsminer/M3X/M32S.py
Normal file
10
pyasic/miners/_types/whatsminer/M3X/M32S.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.miners import BaseMiner
|
||||
|
||||
|
||||
class M32S(BaseMiner):
|
||||
def __init__(self, ip: str):
|
||||
super().__init__()
|
||||
self.ip = ip
|
||||
self.model = "M32S"
|
||||
self.nominal_chips = 78
|
||||
self.fan_count = 2
|
||||
8
pyasic/miners/_types/whatsminer/M3X/__init__.py
Normal file
8
pyasic/miners/_types/whatsminer/M3X/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .M30S import M30S, M30SVE10, M30SVE20, M30SVG20, M30SV50
|
||||
from .M30S_Plus import M30SPlus, M30SPlusVG60, M30SPlusVE40, M30SPlusVF20
|
||||
from .M30S_Plus_Plus import M30SPlusPlus, M30SPlusPlusVG30, M30SPlusPlusVG40
|
||||
|
||||
from .M31S import M31S
|
||||
from .M31S_Plus import M31SPlus, M31SPlusVE20
|
||||
|
||||
from .M32S import M32S
|
||||
2
pyasic/miners/_types/whatsminer/__init__.py
Normal file
2
pyasic/miners/_types/whatsminer/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .M2X import *
|
||||
from .M3X import *
|
||||
4
pyasic/miners/antminer/__init__.py
Normal file
4
pyasic/miners/antminer/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .bmminer import *
|
||||
from .bosminer import *
|
||||
from .cgminer import *
|
||||
from .hiveon import *
|
||||
8
pyasic/miners/antminer/bmminer/X17/S17.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/S17.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17(BMMinerX17, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/S17_Plus.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/S17_Plus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17Plus(BMMinerX17, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/S17_Pro.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/S17_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17Pro(BMMinerX17, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/S17e.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/S17e.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS17e(BMMinerX17, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/T17.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/T17.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17(BMMinerX17, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/T17_Plus.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/T17_Plus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17Plus(BMMinerX17, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X17/T17e.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/T17e.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X17 import BMMinerX17
|
||||
from pyasic.miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT17e(BMMinerX17, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
83
pyasic/miners/antminer/bmminer/X17/X17.py
Normal file
83
pyasic/miners/antminer/bmminer/X17/X17.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
|
||||
import httpx
|
||||
|
||||
|
||||
class BMMinerX17(BMMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_hostname(self) -> str or None:
|
||||
hostname = None
|
||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if len(data.keys()) > 0:
|
||||
if "hostname" in data.keys():
|
||||
hostname = data["hostname"]
|
||||
return hostname
|
||||
|
||||
async def get_mac(self):
|
||||
mac = None
|
||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if len(data.keys()) > 0:
|
||||
if "macaddr" in data.keys():
|
||||
mac = data["macaddr"]
|
||||
return mac
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
await client.post(url, data={"action": "startBlink"}, auth=auth)
|
||||
except httpx.ReadTimeout:
|
||||
# Expected behaviour
|
||||
pass
|
||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if data["isBlinking"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(url, data={"action": "stopBlink"}, auth=auth)
|
||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if not data["isBlinking"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def check_light(self):
|
||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.post(url, data={"action": "onPageLoaded"}, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if data["isBlinking"]:
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
return True
|
||||
return False
|
||||
8
pyasic/miners/antminer/bmminer/X17/__init__.py
Normal file
8
pyasic/miners/antminer/bmminer/X17/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .S17 import BMMinerS17
|
||||
from .S17_Plus import BMMinerS17Plus
|
||||
from .S17_Pro import BMMinerS17Pro
|
||||
from .S17e import BMMinerS17e
|
||||
|
||||
from .T17 import BMMinerT17
|
||||
from .T17_Plus import BMMinerT17Plus
|
||||
from .T17e import BMMinerT17e
|
||||
8
pyasic/miners/antminer/bmminer/X19/S19.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/S19.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19(BMMinerX19, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X19/S19_Pro.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/S19_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19Pro(BMMinerX19, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X19/S19a.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/S19a.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import S19a # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19a(BMMinerX19, S19a):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X19/S19j.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/S19j.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19j(BMMinerX19, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X19/S19j_Pro.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/S19j_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS19jPro(BMMinerX19, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X19/T19.py
Normal file
8
pyasic/miners/antminer/bmminer/X19/T19.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .X19 import BMMinerX19
|
||||
from pyasic.miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT19(BMMinerX19, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
102
pyasic/miners/antminer/bmminer/X19/X19.py
Normal file
102
pyasic/miners/antminer/bmminer/X19/X19.py
Normal file
@@ -0,0 +1,102 @@
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
|
||||
import httpx
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
|
||||
class BMMinerX19(BMMiner):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
url = f"http://{self.ip}/cgi-bin/get_miner_conf.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
self.config = MinerConfig().from_raw(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, yaml_config, ip_user: bool = False) -> None:
|
||||
url = f"http://{self.ip}/cgi-bin/set_miner_conf.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
if ip_user:
|
||||
suffix = str(self.ip).split(".")[-1]
|
||||
conf = MinerConfig().from_yaml(yaml_config).as_x19(user_suffix=suffix)
|
||||
else:
|
||||
conf = MinerConfig().from_yaml(yaml_config).as_x19()
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
await client.post(url, data=conf, auth=auth)
|
||||
except httpx.ReadTimeout:
|
||||
pass
|
||||
for i in range(7):
|
||||
data = await self.get_config()
|
||||
if data.as_x19() == conf:
|
||||
break
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def get_hostname(self) -> str or None:
|
||||
hostname = None
|
||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if len(data.keys()) > 0:
|
||||
if "hostname" in data.keys():
|
||||
hostname = data["hostname"]
|
||||
return hostname
|
||||
|
||||
async def get_mac(self):
|
||||
mac = None
|
||||
url = f"http://{self.ip}/cgi-bin/get_system_info.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if len(data.keys()) > 0:
|
||||
if "macaddr" in data.keys():
|
||||
mac = data["macaddr"]
|
||||
return mac
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
data = json.dumps({"blink": "true"})
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.post(url, data=data, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if data.get("code") == "B000":
|
||||
return True
|
||||
return False
|
||||
|
||||
async def fault_light_off(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/blink.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
data = json.dumps({"blink": "false"})
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.post(url, data=data, auth=auth)
|
||||
if data.status_code == 200:
|
||||
data = data.json()
|
||||
if data.get("code") == "B100":
|
||||
return True
|
||||
return False
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
url = f"http://{self.ip}/cgi-bin/reboot.cgi"
|
||||
auth = httpx.DigestAuth("root", "root")
|
||||
async with httpx.AsyncClient() as client:
|
||||
data = await client.get(url, auth=auth)
|
||||
if data.status_code == 200:
|
||||
return True
|
||||
return False
|
||||
9
pyasic/miners/antminer/bmminer/X19/__init__.py
Normal file
9
pyasic/miners/antminer/bmminer/X19/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .S19 import BMMinerS19
|
||||
from .S19_Pro import BMMinerS19Pro
|
||||
|
||||
from .S19j import BMMinerS19j
|
||||
from .S19j_Pro import BMMinerS19jPro
|
||||
|
||||
from .S19a import BMMinerS19a
|
||||
|
||||
from .T19 import BMMinerT19
|
||||
8
pyasic/miners/antminer/bmminer/X9/S9.py
Normal file
8
pyasic/miners/antminer/bmminer/X9/S9.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9(BMMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X9/S9i.py
Normal file
8
pyasic/miners/antminer/bmminer/X9/S9i.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S9i # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerS9i(BMMiner, S9i):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bmminer/X9/T9.py
Normal file
8
pyasic/miners/antminer/bmminer/X9/T9.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BMMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BMMinerT9(BMMiner, T9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
3
pyasic/miners/antminer/bmminer/X9/__init__.py
Normal file
3
pyasic/miners/antminer/bmminer/X9/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .S9 import BMMinerS9
|
||||
from .S9i import BMMinerS9i
|
||||
from .T9 import BMMinerT9
|
||||
3
pyasic/miners/antminer/bmminer/__init__.py
Normal file
3
pyasic/miners/antminer/bmminer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
8
pyasic/miners/antminer/bosminer/X17/S17.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/S17.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17(BOSMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/S17_Plus.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/S17_Plus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Plus(BOSMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/S17_Pro.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/S17_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17Pro(BOSMiner, S17Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/S17e.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/S17e.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS17e(BOSMiner, S17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/T17.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/T17.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17(BOSMiner, T17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/T17_Plus.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/T17_Plus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17Plus(BOSMiner, T17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/T17e.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/T17e.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T17e # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT17e(BOSMiner, T17e):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X17/__init__.py
Normal file
8
pyasic/miners/antminer/bosminer/X17/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .S17 import BOSMinerS17
|
||||
from .S17_Plus import BOSMinerS17Plus
|
||||
from .S17_Pro import BOSMinerS17Pro
|
||||
from .S17e import BOSMinerS17e
|
||||
|
||||
from .T17 import BOSMinerT17
|
||||
from .T17_Plus import BOSMinerT17Plus
|
||||
from .T17e import BOSMinerT17e
|
||||
8
pyasic/miners/antminer/bosminer/X19/S19.py
Normal file
8
pyasic/miners/antminer/bosminer/X19/S19.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19(BOSMiner, S19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X19/S19_Pro.py
Normal file
8
pyasic/miners/antminer/bosminer/X19/S19_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S19Pro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19Pro(BOSMiner, S19Pro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X19/S19j.py
Normal file
8
pyasic/miners/antminer/bosminer/X19/S19j.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S19j # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19j(BOSMiner, S19j):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X19/S19j_Pro.py
Normal file
8
pyasic/miners/antminer/bosminer/X19/S19j_Pro.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S19jPro # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS19jPro(BOSMiner, S19jPro):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/bosminer/X19/T19.py
Normal file
8
pyasic/miners/antminer/bosminer/X19/T19.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import T19 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerT19(BOSMiner, T19):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
7
pyasic/miners/antminer/bosminer/X19/__init__.py
Normal file
7
pyasic/miners/antminer/bosminer/X19/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .S19 import BOSMinerS19
|
||||
from .S19_Pro import BOSMinerS19Pro
|
||||
|
||||
from .S19j import BOSMinerS19j
|
||||
from .S19j_Pro import BOSMinerS19jPro
|
||||
|
||||
from .T19 import BOSMinerT19
|
||||
8
pyasic/miners/antminer/bosminer/X9/S9.py
Normal file
8
pyasic/miners/antminer/bosminer/X9/S9.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import BOSMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S9 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class BOSMinerS9(BOSMiner, S9):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
1
pyasic/miners/antminer/bosminer/X9/__init__.py
Normal file
1
pyasic/miners/antminer/bosminer/X9/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .S9 import BOSMinerS9
|
||||
3
pyasic/miners/antminer/bosminer/__init__.py
Normal file
3
pyasic/miners/antminer/bosminer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .X9 import *
|
||||
from .X17 import *
|
||||
from .X19 import *
|
||||
8
pyasic/miners/antminer/cgminer/X17/S17.py
Normal file
8
pyasic/miners/antminer/cgminer/X17/S17.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17 # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17(CGMiner, S17):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
8
pyasic/miners/antminer/cgminer/X17/S17_Plus.py
Normal file
8
pyasic/miners/antminer/cgminer/X17/S17_Plus.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from pyasic.miners._backends import CGMiner # noqa - Ignore access to _module
|
||||
from pyasic.miners._types import S17Plus # noqa - Ignore access to _module
|
||||
|
||||
|
||||
class CGMinerS17Plus(CGMiner, S17Plus):
|
||||
def __init__(self, ip: str) -> None:
|
||||
super().__init__(ip)
|
||||
self.ip = ip
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user