Compare commits

...

18 Commits

Author SHA1 Message Date
UpstreamData
15fc27e6fa added configuration for X19 miners 2022-06-07 11:12:26 -06:00
UpstreamData
943ebc77a1 switch braiins miners over to using new config dataclass 2022-06-07 10:49:41 -06:00
UpstreamData
733437ef03 create basic config dataclass to be used to configure miners 2022-06-06 16:05:09 -06:00
UpstreamData
b444245e98 added new whatsminer types to miner factory 2022-06-06 10:09:11 -06:00
UpstreamData
481d31a0f1 added more new whatsminer types 2022-06-06 10:06:17 -06:00
UpstreamData
264db3bdd6 fix a bug with whatsminer M21S missing import 2022-06-06 09:41:10 -06:00
UpstreamData
d292b9c195 improved whatsminer handling, and added VF20 to miner dict 2022-06-06 09:26:38 -06:00
UpstreamData
dce25a679f added new miner type M30S+VF20 2022-06-06 09:17:42 -06:00
UpstreamData
c903631742 improved build process 2022-06-06 09:17:22 -06:00
Colin Crossman
e70bfdc886 Fix indent issue that caused missing MAC addresses (#10) 2022-06-05 15:50:07 -06:00
UpstreamData
8e1803add1 made slight optimizations to get_data and the way the miner gets mac data 2022-06-03 15:30:09 -06:00
UpstreamData
7d61056ea3 added whatsminer M30S+ VE40 2022-06-03 15:00:04 -06:00
UpstreamData
0d497baa45 added mac for M20 series 2022-06-03 14:55:03 -06:00
UpstreamData
d3a71c5a93 added mac addresses to get_data 2022-06-03 14:29:10 -06:00
UpstreamData
895a5b7ac8 fixed more bugs with whatsminers and added more versions 2022-06-03 11:20:34 -06:00
UpstreamData
7a5a0b287c fixed a bug with some versions of whatsminer and improved logging 2022-06-03 09:35:55 -06:00
UpstreamData
c7d73276c8 fixed a small bug with sorting 2022-06-03 08:59:15 -06:00
UpstreamData
4bbb9d0b08 added a basis for configuration of X17 and X19 miners by getting pool info from config file. 2022-06-02 16:06:36 -06:00
42 changed files with 1025 additions and 67 deletions

View File

@@ -76,3 +76,13 @@ SAMPLE CONFIG
}
}
"""
def general_config_convert_pools(config: dict):
out_config = {}
pools = config.get("pool_groups")
if pools:
if len(pools) > 0:
pools = pools[0]
out_config = pools["pools"][:3]
return out_config

317
config/miner_config.py Normal file
View File

@@ -0,0 +1,317 @@
from dataclasses import dataclass, asdict
from typing import List, Literal
import random
import string
import toml
import yaml
import json
import time
@dataclass
class _Pool:
url: str = ""
username: str = ""
password: str = ""
def from_dict(self, data: dict):
for key in data.keys():
if key == "url":
self.url = data[key]
if key in ["user", "username"]:
self.username = data[key]
if key in ["pass", "password"]:
self.password = data[key]
return self
def as_x19(self, user_suffix: str = None):
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "pass": self.password}
return pool
def as_bos(self, user_suffix: str = None):
username = self.username
if user_suffix:
username = f"{username}{user_suffix}"
pool = {"url": self.url, "user": username, "password": self.password}
return pool
@dataclass
class _PoolGroup:
quota: int = 1
group_name: str = None
pools: List[_Pool] = None
def __post_init__(self):
if not self.group_name:
self.group_name = "".join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(6)
) # generate random pool group name in case it isn't set
def from_dict(self, data: dict):
pools = []
for key in data.keys():
if key in ["name", "group_name"]:
self.group_name = data[key]
if key == "quota":
self.quota = data[key]
if key in ["pools", "pool"]:
for pool in data[key]:
pools.append(_Pool().from_dict(pool))
self.pools = pools
return self
def as_x19(self, user_suffix: str = None):
pools = []
for pool in self.pools[:3]:
pools.append(pool.as_x19(user_suffix=user_suffix))
return pools
def as_bos(self, user_suffix: str = None):
group = {
"name": self.group_name,
"quota": self.quota,
"pool": [pool.as_bos(user_suffix=user_suffix) for pool in self.pools],
}
return group
@dataclass
class MinerConfig:
pool_groups: List[_PoolGroup] = None
temp_mode: Literal["auto", "manual", "disabled"] = "auto"
temp_target: float = 70.0
temp_hot: float = 80.0
temp_dangerous: float = 10.0
minimum_fans: int = None
fan_speed: Literal[tuple(range(101))] = None # noqa - Ignore weird Literal usage
asicboost: bool = None
autotuning_enabled: bool = True
autotuning_wattage: int = 900
dps_enabled: bool = None
dps_power_step: int = None
dps_min_power: int = None
dps_shutdown_enabled: bool = None
dps_shutdown_duration: float = None
def as_dict(self):
data_dict = asdict(self)
for key in asdict(self).keys():
if data_dict[key] is None:
del data_dict[key]
return data_dict
def as_toml(self):
return toml.dumps(self.as_dict())
def as_yaml(self):
return yaml.dump(self.as_dict(), sort_keys=False)
def from_raw(self, data: dict):
pool_groups = []
for key in data.keys():
if key == "pools":
pool_groups.append(_PoolGroup().from_dict({"pools": data[key]}))
elif key == "group":
for group in data[key]:
pool_groups.append(_PoolGroup().from_dict(group))
if key == "bitmain-fan-ctrl":
if data[key]:
self.temp_mode = "manual"
if data.get("bitmain-fan-pwm"):
self.fan_speed = int(data["bitmain-fan-pwm"])
elif key == "fan_control":
for _key in data[key].keys():
if _key == "min_fans":
self.minimum_fans = data[key][_key]
elif _key == "speed":
self.fan_speed = data[key][_key]
elif key == "temp_control":
for _key in data[key].keys():
if _key == "mode":
self.temp_mode = data[key][_key]
elif _key == "target_temp":
self.temp_target = data[key][_key]
elif _key == "hot_temp":
self.temp_hot = data[key][_key]
elif _key == "dangerous_temp":
self.temp_dangerous = data[key][_key]
if key == "hash_chain_global":
if data[key].get("asic_boost"):
self.asicboost = data[key]["asic_boost"]
if key == "autotuning":
for _key in data[key].keys():
if _key == "enabled":
self.autotuning_enabled = data[key][_key]
elif _key == "psu_power_limit":
self.autotuning_wattage = data[key][_key]
if key == "power_scaling":
for _key in data[key].keys():
if _key == "enabled":
self.dps_enabled = data[key][_key]
elif _key == "power_step":
self.dps_power_step = data[key][_key]
elif _key == "min_psu_power_limit":
self.dps_min_power = data[key][_key]
elif _key == "shutdown_enabled":
self.dps_shutdown_enabled = data[key][_key]
elif _key == "shutdown_duration":
self.dps_shutdown_duration = data[key][_key]
self.pool_groups = pool_groups
return self
def from_dict(self, data: dict):
pool_groups = []
for group in data["pool_groups"]:
pool_groups.append(_PoolGroup().from_dict(group))
for key in data.keys():
if getattr(self, key) and not key == "pool_groups":
setattr(self, key, data[key])
self.pool_groups = pool_groups
return self
def from_toml(self, data: str):
return self.from_dict(toml.loads(data))
def from_yaml(self, data: str):
return self.from_dict(yaml.load(data, Loader=yaml.SafeLoader))
def as_x19(self, user_suffix: str = None):
cfg = {
"pools": self.pool_groups[0].as_x19(user_suffix=user_suffix),
"bitmain-fan-ctrl": False,
"bitmain-fan-pwn": 100,
}
if not self.temp_mode == "auto":
cfg["bitmain-fan-ctrl"] = True
if self.fan_speed:
cfg["bitmain-fan-ctrl"] = str(self.fan_speed)
return json.dumps(cfg)
def as_bos(self, model: str = "S9", user_suffix: str = None):
cfg = {
"format": {
"version": "1.2+",
"model": f"Antminer {model}",
"generator": "Upstream Config Utility",
"timestamp": int(time.time()),
},
"group": [
group.as_bos(user_suffix=user_suffix) for group in self.pool_groups
],
"temp_control": {
"mode": self.temp_mode,
"target_temp": self.temp_target,
"hot_temp": self.temp_hot,
"dangerous_temp": self.temp_dangerous,
},
}
if self.autotuning_enabled or self.autotuning_wattage:
cfg["autotuning"] = {}
if self.autotuning_enabled:
cfg["autotuning"]["enabled"] = self.autotuning_enabled
if self.autotuning_wattage:
cfg["autotuning"]["psu_power_limit"] = self.autotuning_wattage
if self.asicboost:
cfg["hash_chain_global"] = {}
cfg["hash_chain_global"]["asic_boost"] = self.asicboost
if any(
[
getattr(self, item)
for item in [
"dps_enabled",
"dps_power_step",
"dps_min_power",
"dps_shutdown_enabled",
"dps_shutdown_duration",
]
]
):
cfg["power_scaling"] = {}
if self.dps_enabled:
cfg["power_scaling"]["enabled"] = self.dps_enabled
if self.dps_power_step:
cfg["power_scaling"]["power_step"] = self.dps_power_step
if self.dps_min_power:
cfg["power_scaling"]["min_psu_power_limit"] = self.dps_min_power
if self.dps_shutdown_enabled:
cfg["power_scaling"]["shutdown_enabled"] = self.dps_shutdown_enabled
if self.dps_shutdown_duration:
cfg["power_scaling"]["shutdown_duration"] = self.dps_shutdown_duration
return toml.dumps(cfg)
if __name__ == "__main__":
import pprint
bos_conf = {
"group": [
{
"name": "group",
"pool": [
{
"password": "123",
"url": "stratum2+tcp://v2.us-east.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt",
"user": "UpstreamDataInc.test",
},
{
"password": "123",
"url": "stratum2+tcp://v2.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt",
"user": "UpstreamDataInc.test",
},
{
"password": "123",
"url": "stratum+tcp://stratum.slushpool.com:3333",
"user": "UpstreamDataInc.test",
},
],
"quota": 1,
},
{
"name": "group_2",
"pool": [
{
"password": "123",
"url": "stratum2+tcp://v2.us-east.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt",
"user": "UpstreamDataInc.test",
},
{
"password": "123",
"url": "stratum2+tcp://v2.stratum.slushpool.com/u95GEReVMjK6k5YqiSFNqqTnKU4ypU2Wm8awa6tmbmDmk1bWt",
"user": "UpstreamDataInc.test",
},
{
"password": "123",
"url": "stratum+tcp://stratum.slushpool.com:3333",
"user": "UpstreamDataInc.test",
},
],
"quota": 1,
},
],
}
pprint.pprint(MinerConfig().from_dict(bos_conf).as_dict())

View File

@@ -7,6 +7,7 @@ class MinerData:
"""A Dataclass to standardize data returned from miners (specifically AnyMiner().get_data())
:param ip: The IP of the miner as a str.
:param datetime: The time and date this data was generated.
:param model: The model of the miner as a str.
:param hostname: The network hostname of the miner as a str.
:param hashrate: The hashrate of the miner in TH/s as a int.
@@ -34,6 +35,7 @@ class MinerData:
ip: str
datetime: datetime = None
mac: str = "00:00:00:00:00:00"
model: str = "Unknown"
hostname: str = "Unknown"
hashrate: float = 0

View File

@@ -2,17 +2,23 @@ import logging
from settings import DEBUG
logging.basicConfig(
# filename="logfile.txt",
# filemode="a",
format="[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
datefmt="%x %X",
)
logger = logging.getLogger()
def init_logger():
logging.basicConfig(
# filename="logfile.txt",
# filemode="a",
format="%(pathname)s:%(lineno)d in %(funcName)s\n[%(levelname)s][%(asctime)s](%(name)s) - %(message)s",
datefmt="%x %X",
)
_logger = logging.getLogger()
if DEBUG:
logger.setLevel(logging.DEBUG)
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
else:
logger.setLevel(logging.INFO)
logging.getLogger("asyncssh").setLevel(logging.WARNING)
if DEBUG:
_logger.setLevel(logging.DEBUG)
logging.getLogger("asyncssh").setLevel(logging.DEBUG)
else:
_logger.setLevel(logging.WARNING)
logging.getLogger("asyncssh").setLevel(logging.WARNING)
return _logger
logger = init_logger()

View File

@@ -9,6 +9,7 @@ import datetime
import sys
import os
from cx_Freeze import setup, Executable
from setuptools import find_packages
base = None
if sys.platform == "win32":
@@ -16,7 +17,6 @@ if sys.platform == "win32":
version = datetime.datetime.now()
version = version.strftime("%y.%m.%d")
print(version)
setup(
@@ -30,9 +30,7 @@ setup(
os.path.join(os.getcwd(), "settings/settings.toml"),
os.path.join(os.getcwd(), "static/CFG-Util-README.md"),
],
"excludes": [
os.path.join(os.getcwd(), "tools/web_testbench/files"),
],
"excludes": ["tests", "tools.web_testbench", "tools.web_monitor"],
},
},
executables=[

View File

@@ -18,6 +18,7 @@ class BaseMiner:
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)}"
@@ -99,5 +100,5 @@ class BaseMiner:
async def get_mac(self):
return None
async def get_data(self):
async def get_data(self) -> MinerData:
return MinerData(ip=str(self.ip))

View File

@@ -135,6 +135,7 @@ class BMMiner(BaseMiner):
model = await self.get_model()
hostname = await self.get_hostname()
mac = await self.get_mac()
if model:
data.model = model
@@ -142,6 +143,9 @@ class BMMiner(BaseMiner):
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(

View File

@@ -11,7 +11,7 @@ from API import APIError
from data import MinerData
from config.bos import bos_config_convert, general_config_convert_bos
from config.miner_config import MinerConfig
from settings import MINER_FACTORY_GET_VERSION_RETRIES as DATA_RETRIES
@@ -102,8 +102,9 @@ class BOSMiner(BaseMiner):
async with sftp.open("/etc/bosminer.toml") as file:
toml_data = toml.loads(await file.read())
logging.debug(f"{self}: Converting config file.")
cfg = bos_config_convert(toml_data)
cfg = MinerConfig().from_raw(toml_data)
self.config = cfg
return self.config
async def get_hostname(self) -> str:
"""Get miner hostname.
@@ -192,11 +193,17 @@ class BOSMiner(BaseMiner):
logging.debug(f"{self}: Sending config.")
if ip_user:
suffix = str(self.ip).split(".")[-1]
toml_conf = toml.dumps(
general_config_convert_bos(yaml_config, user_suffix=suffix)
toml_conf = (
MinerConfig()
.from_yaml(yaml_config)
.as_bos(model=self.model.replace(" (BOS)", ""), user_suffix=suffix)
)
else:
toml_conf = toml.dumps(general_config_convert_bos(yaml_config))
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:
@@ -264,6 +271,7 @@ class BOSMiner(BaseMiner):
model = await self.get_model()
hostname = await self.get_hostname()
mac = await self.get_mac()
if model:
data.model = model
@@ -271,6 +279,9 @@ class BOSMiner(BaseMiner):
if hostname:
data.hostname = hostname
if mac:
data.mac = mac
miner_data = None
for i in range(DATA_RETRIES):
try:

View File

@@ -41,7 +41,7 @@ class BTMiner(BaseMiner):
self.hostname = host
return self.hostname
except APIError:
logging.warning(f"Failed to get hostname for miner: {self}")
logging.info(f"Failed to get hostname for miner: {self}")
return "?"
except Exception:
logging.warning(f"Failed to get hostname for miner: {self}")
@@ -80,23 +80,41 @@ class BTMiner(BaseMiner):
async def get_mac(self):
mac = ""
data = await self.api.get_miner_info()
data = await self.api.summary()
if data:
if "Msg" in data.keys():
if "mac" in data["Msg"].keys():
mac = data["Msg"]["mac"]
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()
hostname = await self.get_hostname()
except APIError:
logging.warning(f"Failed to get hostname and model: {self}")
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"
@@ -105,6 +123,7 @@ class BTMiner(BaseMiner):
if hostname:
data.hostname = hostname
miner_data = None
for i in range(DATA_RETRIES):
try:
@@ -125,6 +144,9 @@ class BTMiner(BaseMiner):
summary_data = summary.get("SUMMARY")
if summary_data:
if len(summary_data) > 0:
if summary_data[0].get("MAC"):
mac = summary_data[0]["MAC"]
data.fan_1 = summary_data[0]["Fan Speed In"]
data.fan_2 = summary_data[0]["Fan Speed Out"]
@@ -208,4 +230,14 @@ class BTMiner(BaseMiner):
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

View File

@@ -113,12 +113,17 @@ class CGMiner(BaseMiner):
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")

View File

@@ -6,5 +6,23 @@ class M21S(BaseMiner):
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

View File

@@ -2,5 +2,5 @@ from .M20S import M20S
from .M20S_Plus import M20SPlus
from .M21 import M21
from .M21S import M21S
from .M21S import M21S, M21SV20, M21SV60
from .M21S_Plus import M21SPlus

View File

@@ -8,3 +8,39 @@ class M30S(BaseMiner):
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

View File

@@ -8,3 +8,30 @@ class M30SPlus(BaseMiner):
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

View File

@@ -1,11 +1,20 @@
from 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.model = "M30S++ V30"
self.nominal_chips = 111
self.fan_count = 2
@@ -14,6 +23,6 @@ class M30SPlusPlusVG40(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M30S++V40"
self.model = "M30S++ V40"
self.nominal_chips = 117
self.fan_count = 2

View File

@@ -8,3 +8,12 @@ class M31SPlus(BaseMiner):
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

View File

@@ -1,8 +1,8 @@
from .M30S import M30S
from .M30S_Plus import M30SPlus
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40
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
from .M31S_Plus import M31SPlus, M31SPlusVE20
from .M32S import M32S

View File

@@ -4,6 +4,9 @@ from miners._types import S17 # noqa - Ignore access to _module
import httpx
# TODO add config
class BMMinerS17(BMMiner, S17):
def __init__(self, ip: str) -> None:
super().__init__(ip)
@@ -22,6 +25,19 @@ class BMMinerS17(BMMiner, S17):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerS17Plus(BMMiner, S17Plus):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerS17Pro(BMMiner, S17Pro):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerS17e(BMMiner, S17e):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerT17(BMMiner, T17):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerT17Plus(BMMiner, T17Plus):
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")

View File

@@ -22,6 +22,19 @@ class BMMinerT17e(BMMiner, T17e):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19 # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerS19(BMMiner, S19):
@@ -10,6 +13,36 @@ class BMMinerS19(BMMiner, S19):
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"
@@ -23,6 +56,19 @@ class BMMinerS19(BMMiner, S19):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19Pro # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerS19Pro(BMMiner, S19Pro):
@@ -10,6 +13,36 @@ class BMMinerS19Pro(BMMiner, S19Pro):
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"
@@ -23,6 +56,19 @@ class BMMinerS19Pro(BMMiner, S19Pro):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19a # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerS19a(BMMiner, S19a):
@@ -10,6 +13,36 @@ class BMMinerS19a(BMMiner, S19a):
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"
@@ -23,6 +56,19 @@ class BMMinerS19a(BMMiner, S19a):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19j # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerS19j(BMMiner, S19j):
@@ -10,6 +13,36 @@ class BMMinerS19j(BMMiner, S19j):
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"
@@ -23,6 +56,19 @@ class BMMinerS19j(BMMiner, S19j):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19jPro # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerS19jPro(BMMiner, S19jPro):
@@ -10,6 +13,36 @@ class BMMinerS19jPro(BMMiner, S19jPro):
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"
@@ -23,6 +56,19 @@ class BMMinerS19jPro(BMMiner, S19jPro):
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")

View File

@@ -1,8 +1,11 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import T19 # noqa - Ignore access to _module
from config.miner_config import MinerConfig
import httpx
import json
import asyncio
class BMMinerT19(BMMiner, T19):
@@ -10,6 +13,36 @@ class BMMinerT19(BMMiner, T19):
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"
@@ -23,6 +56,19 @@ class BMMinerT19(BMMiner, T19):
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")

View File

@@ -135,6 +135,8 @@ MINER_CLASSES = {
"M21S": {
"Default": BTMinerM21S,
"BTMiner": BTMinerM21S,
"60": BTMinerM21SV60,
"20": BTMinerM21SV20,
},
"M21S+": {
"Default": BTMinerM21SPlus,
@@ -143,16 +145,23 @@ MINER_CLASSES = {
"M30S": {
"Default": BTMinerM30S,
"BTMiner": BTMinerM30S,
"50": BTMinerM30SV50,
"G20": BTMinerM30SVG20,
"E20": BTMinerM30SVE20,
"E10": BTMinerM30SVE10,
},
"M30S+": {
"Default": BTMinerM30SPlus,
"BTMiner": BTMinerM30SPlus,
"F20": BTMinerM30SPlusVF20,
"E40": BTMinerM30SPlusVE40,
"G60": BTMinerM30SPlusVG60,
},
"M30S++": {
"Default": BTMinerM30SPlusPlusVG40,
"BTMiner": BTMinerM30SPlusPlusVG40,
"40": BTMinerM30SPlusPlusVG40,
"30": BTMinerM30SPlusPlusVG30,
"Default": BTMinerM30SPlusPlus,
"BTMiner": BTMinerM30SPlusPlus,
"G40": BTMinerM30SPlusPlusVG40,
"G30": BTMinerM30SPlusPlusVG30,
},
"M31S": {
"Default": BTMinerM31S,
@@ -161,6 +170,7 @@ MINER_CLASSES = {
"M31S+": {
"Default": BTMinerM31SPlus,
"BTMiner": BTMinerM31SPlus,
"E20": BTMinerM31SPlusVE20,
},
"M32S": {
"Default": BTMinerM32S,
@@ -430,12 +440,13 @@ class MinerFactory(metaclass=Singleton):
if model:
# whatsminer have a V in their version string (M20SV41), remove everything after it
if "V" in model:
ver = model.split("VG")[1]
model = model.split("V")[0]
_ver = model.split("V")
if len(_ver) > 1:
ver = model.split("V")[1]
model = model.split("V")[0]
# don't need "Bitmain", just "Antminer XX" as model
if "Bitmain " in model:
model = model.replace("Bitmain ", "")
return model, api, ver
@staticmethod

View File

@@ -1,8 +1,20 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M21S # noqa - Ignore access to _module
from miners._types import M21S, M21SV20, M21SV60 # noqa - Ignore access to _module
class BTMinerM21S(BTMiner, M21S):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM21SV20(BTMiner, M21SV20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM21SV60(BTMiner, M21SV60):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -2,5 +2,5 @@ from .M20S import BTMinerM20S
from .M20S_Plus import BTMinerM20SPlus
from .M21 import BTMinerM21
from .M21S import BTMinerM21S
from .M21S import BTMinerM21S, BTMinerM21SV20, BTMinerM21SV60
from .M21S_Plus import BTMinerM21SPlus

View File

@@ -1,8 +1,38 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M30S # noqa - Ignore access to _module
from miners._types import (
M30S,
M30SV50,
M30SVG20,
M30SVE20,
M30SVE10,
) # noqa - Ignore access to _module
class BTMinerM30S(BTMiner, M30S):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SV50(BTMiner, M30SV50):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SVG20(BTMiner, M30SVG20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SVE20(BTMiner, M30SVE20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SVE10(BTMiner, M30SVE10):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -1,8 +1,31 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M30SPlus # noqa - Ignore access to _module
from miners._types import (
M30SPlus,
M30SPlusVE40,
M30SPlusVF20,
M30SPlusVG60,
) # noqa - Ignore access to _module
class BTMinerM30SPlus(BTMiner, M30SPlus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusVE40(BTMiner, M30SPlusVE40):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusVF20(BTMiner, M30SPlusVF20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusVG60(BTMiner, M30SPlusVG60):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -1,10 +1,17 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import ( # noqa - Ignore access to _module
M30SPlusPlus,
M30SPlusPlusVG40,
M30SPlusPlusVG30,
)
class BTMinerM30SPlusPlus(BTMiner, M30SPlusPlus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM30SPlusPlusVG40(BTMiner, M30SPlusPlusVG40):
def __init__(self, ip: str) -> None:
super().__init__(ip)

View File

@@ -1,8 +1,14 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M31SPlus # noqa - Ignore access to _module
from miners._types import M31SPlus, M31SPlusVE20 # noqa - Ignore access to _module
class BTMinerM31SPlus(BTMiner, M31SPlus):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
class BTMinerM31SPlusVE20(BTMiner, M31SPlusVE20):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip

View File

@@ -1,8 +1,23 @@
from .M30S import BTMinerM30S
from .M30S_Plus import BTMinerM30SPlus
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30
from .M30S import (
BTMinerM30S,
BTMinerM30SVE10,
BTMinerM30SVE20,
BTMinerM30SVG20,
BTMinerM30SV50,
)
from .M30S_Plus import (
BTMinerM30SPlus,
BTMinerM30SPlusVF20,
BTMinerM30SPlusVE40,
BTMinerM30SPlusVG60,
)
from .M30S_Plus_Plus import (
BTMinerM30SPlusPlus,
BTMinerM30SPlusPlusVG40,
BTMinerM30SPlusPlusVG30,
)
from .M31S import BTMinerM31S
from .M31S_Plus import BTMinerM31SPlus
from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20
from .M32S import BTMinerM32S

View File

@@ -13,6 +13,10 @@ if (
def main():
from logger import init_logger
init_logger()
asyncio.run(ui())

View File

@@ -20,7 +20,8 @@ async def btn_import(table, selected):
miner = await MinerFactory().get_miner(ip)
await miner.get_config()
config = miner.config
window["cfg_config_txt"].update(config)
if config:
window["cfg_config_txt"].update(config.as_yaml())
@disable_buttons("Configuring")

View File

@@ -1,32 +1,26 @@
from datetime import datetime, timedelta
from io import BytesIO
from typing import List, Dict
from data import MinerData
import matplotlib.pyplot as plt
from matplotlib.ticker import MultipleLocator
from matplotlib.dates import DateFormatter
import numpy as np
from reportlab.lib import colors
from matplotlib.ticker import MultipleLocator
from reportlab.lib.pagesizes import letter, inch
from reportlab.lib.styles import (
ParagraphStyle,
TA_CENTER, # noqa - not declared in __all__
)
from reportlab.lib.utils import ImageReader
from reportlab.platypus import (
SimpleDocTemplate,
KeepInFrame,
Table,
Image,
Paragraph,
TableStyle,
PageBreak,
Spacer,
)
from io import BytesIO
from svglib.svglib import svg2rlg
from data import MinerData
async def generate_pdf(data: Dict[str, List[MinerData]], file_loc):
doc = SimpleDocTemplate(

View File

@@ -229,9 +229,9 @@ class TableManager(metaclass=Singleton):
"Temp",
"Total",
"Ideal",
"Left Chips",
"Center Chips",
"Right Chips",
"Left Board",
"Center Board",
"Right Board",
]:
if isinstance(self.data[data_key][self.sort_key], str):
return -300