Compare commits

..

27 Commits

Author SHA1 Message Date
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
UpstreamData
3ee49e6fd7 fixed a warning with ylim being set to 0 2022-06-02 14:52:17 -06:00
UpstreamData
dcd3e99d73 added interval to recording 2022-06-02 14:25:55 -06:00
UpstreamData
64018cdad8 completed basic recording functionality 2022-06-02 14:17:08 -06:00
UpstreamData
e7d269008c added the basics of the recording functionality, just need to write out to file. 2022-06-02 11:08:14 -06:00
UpstreamData
7dfe25e5d2 added base for recording miner data to pdf file. 2022-06-01 16:13:30 -06:00
UpstreamData
382f9cff76 added reboot command for X19 and X17 models on BMMiner 2022-06-01 14:02:34 -06:00
UpstreamData
a5195ff1db fix a bug with testbench where toolbox finds braiins but bench does not 2022-06-01 11:44:07 -06:00
UpstreamData
b1ec726d18 added some docstrings to data 2022-06-01 11:22:30 -06:00
UpstreamData
5ae2cb2b22 fixed a bug with not all table data getting reset on data update 2022-06-01 11:22:12 -06:00
UpstreamData
472a15f4ca added fault light function for X17 BMMiner models 2022-06-01 10:54:45 -06:00
UpstreamData
7cc7973587 fixed a bug with some BOS S17e not returning data frm devdetails and fans 2022-06-01 10:19:58 -06:00
UpstreamData
ab964e4c88 fixed a bug with sorting by chip % 2022-06-01 08:15:35 -06:00
UpstreamData
4087874b4a added get hostname to X19 miners 2022-05-31 17:05:05 -06:00
UpstreamData
844deec0d3 add fault light command to X19 miners 2022-05-31 16:54:56 -06:00
UpstreamData
d36eef4c33 switched to httpx 2022-05-31 16:08:17 -06:00
UpstreamData
69d4ee5570 Revert "add .readthedocs.yaml"
This reverts commit e7b01ccdab.
2022-05-31 13:23:41 -06:00
UpstreamData
e6d3ec01fe Merge remote-tracking branch 'origin/master' 2022-05-31 13:18:59 -06:00
UpstreamData
e7b01ccdab add .readthedocs.yaml 2022-05-31 13:18:52 -06:00
UpstreamData
38506903ea fixed an issue with BMMiner get data and bosminer get data not identifying correct board number. 2022-05-31 08:45:49 -06:00
48 changed files with 1700 additions and 123 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

View File

@@ -1,9 +1,41 @@
from dataclasses import dataclass, field, asdict
from datetime import datetime
@dataclass
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.
:param left_board_temp: The temp of the left PCB as an int.
:param left_board_chip_temp: The temp of the left board chips as an int.
:param center_board_temp: The temp of the center PCB as an int.
:param center_board_chip_temp: The temp of the center board chips as an int.
:param right_board_temp: The temp of the right PCB as an int.
:param right_board_chip_temp: The temp of the right board chips as an int.
:param wattage: Wattage of the miner as an int.
:param fan_1: The speed of the first fan as an int.
:param fan_2: The speed of the second fan as an int.
:param fan_3: The speed of the third fan as an int.
:param fan_4: The speed of the fourth fan as an int.
:param left_chips: The number of chips online in the left board as an int.
:param center_chips: The number of chips online in the left board as an int.
:param right_chips: The number of chips online in the left board as an int.
:param ideal_chips: The ideal number of chips in the miner as an int.
:param pool_split: The pool split as a str.
:param pool_1_url: The first pool url on the miner as a str.
:param pool_1_user: The first pool user on the miner as a str.
:param pool_2_url: The second pool url on the miner as a str.
:param pool_2_user: The second pool user on the miner as a str.
"""
ip: str
datetime: datetime = None
mac: str = "00:00:00:00:00:00"
model: str = "Unknown"
hostname: str = "Unknown"
hashrate: float = 0
@@ -26,12 +58,15 @@ class MinerData:
ideal_chips: int = 1
percent_ideal: float = field(init=False)
nominal: int = field(init=False)
pool_split: str = 0
pool_split: str = "0"
pool_1_url: str = "Unknown"
pool_1_user: str = "Unknown"
pool_2_url: str = ""
pool_2_user: str = ""
def __post_init__(self):
self.datetime = datetime.now()
@property
def total_chips(self): # noqa - Skip PyCharm inspection
return self.right_chips + self.center_chips + self.left_chips

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

@@ -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(
@@ -171,7 +175,7 @@ class BMMiner(BaseMiner):
if len(boards) > 0:
for board_num in range(1, 16, 5):
for _b_num in range(5):
b = boards[1].get(f"fan{board_num + _b_num}")
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

View File

@@ -1,12 +1,13 @@
import ipaddress
import logging
import json
import toml
from miners import BaseMiner
from API.bosminer import BOSMinerAPI
from API import APIError
from data import MinerData
@@ -137,7 +138,17 @@ class BOSMiner(BaseMiner):
return self.model + " (BOS)"
# get devdetails data
version_data = await self.api.devdetails()
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:
@@ -247,8 +258,13 @@ class BOSMiner(BaseMiner):
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
@@ -256,24 +272,33 @@ class BOSMiner(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(
"summary", "temps", "tunerstatus", "pools", "devdetails", "fans"
)
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")[0]
temps = miner_data.get("temps")[0]
tunerstatus = miner_data.get("tunerstatus")[0]
pools = miner_data.get("pools")[0]
devdetails = miner_data.get("devdetails")[0]
fans = miner_data.get("fans")[0]
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.get("SUMMARY")
hr = summary[0].get("SUMMARY")
if hr:
if len(hr) > 0:
hr = hr[0].get("MHS 1m")
@@ -281,11 +306,11 @@ class BOSMiner(BaseMiner):
data.hashrate = round(hr / 1000000, 2)
if temps:
temp = temps.get("TEMPS")
temp = temps[0].get("TEMPS")
if temp:
if len(temp) > 0:
board_map = {0: "left_board", 1: "center_board", 2: "right_board"}
offset = temp[0]["ID"]
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"])
@@ -294,7 +319,7 @@ class BOSMiner(BaseMiner):
setattr(data, f"{board_map[_id]}_temp", board_temp)
if fans:
fan_data = fans.get("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"])
@@ -307,7 +332,7 @@ class BOSMiner(BaseMiner):
pool_1_quota = 1
pool_2_quota = 1
quota = 0
for pool in pools.get("POOLS"):
for pool in pools[0].get("POOLS"):
if not pool_1_user:
pool_1_user = pool.get("User")
pool_1 = pool["URL"]
@@ -346,7 +371,7 @@ class BOSMiner(BaseMiner):
data.pool_split = str(quota)
if tunerstatus:
tuner = tunerstatus.get("TUNERSTATUS")
tuner = tunerstatus[0].get("TUNERSTATUS")
if tuner:
if len(tuner) > 0:
wattage = tuner[0].get("PowerLimit")
@@ -354,11 +379,11 @@ class BOSMiner(BaseMiner):
data.wattage = wattage
if devdetails:
boards = devdetails.get("DEVDETAILS")
boards = devdetails[0].get("DEVDETAILS")
if boards:
if len(boards) > 0:
board_map = {0: "left_chips", 1: "center_chips", 2: "right_chips"}
offset = boards[0]["ID"]
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"]

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

@@ -1,10 +1,19 @@
from miners import BaseMiner
class M21S(BaseMiner):
class M21SV60(BaseMiner):
def __init__(self, ip: str):
super().__init__()
self.ip = ip
self.model = "M21S"
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 M21SV20, M21SV60
from .M21S_Plus import M21SPlus

View File

@@ -8,3 +8,12 @@ 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

View File

@@ -8,3 +8,12 @@ class M30SPlus(BaseMiner):
self.model = "M30S+"
self.nominal_chips = 156
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

View File

@@ -5,7 +5,7 @@ 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 +14,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 import M30S, M30SV50
from .M30S_Plus import M30SPlus, M30SPlusVE40
from .M30S_Plus_Plus import M30SPlusPlusVG30, M30SPlusPlusVG40
from .M31S import M31S
from .M31S_Plus import M31SPlus
from .M31S_Plus import M31SPlus, M31SPlusVE20
from .M32S import M32S

View File

@@ -1,8 +1,87 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
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)
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S17Plus # noqa - Ignore access to _module
import httpx
class BMMinerS17Plus(BMMiner, S17Plus):
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S17Pro # noqa - Ignore access to _module
import httpx
class BMMinerS17Pro(BMMiner, S17Pro):
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S17e # noqa - Ignore access to _module
import httpx
class BMMinerS17e(BMMiner, S17e):
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import T17 # noqa - Ignore access to _module
import httpx
class BMMinerT17(BMMiner, T17):
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import T17Plus # noqa - Ignore access to _module
import httpx
class BMMinerT17Plus(BMMiner, T17Plus):
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

View File

@@ -1,8 +1,84 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import T17e # noqa - Ignore access to _module
import httpx
class BMMinerT17e(BMMiner, T17e):
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

View File

@@ -1,11 +1,73 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19 # noqa - Ignore access to _module
import httpx
import json
# TODO add config
class BMMinerS19(BMMiner, S19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -1,11 +1,70 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19Pro # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19Pro(BMMiner, S19Pro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -1,11 +1,70 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19a # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19a(BMMiner, S19a):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -1,11 +1,70 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19j # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19j(BMMiner, S19j):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -1,11 +1,70 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import S19jPro # noqa - Ignore access to _module
import httpx
import json
class BMMinerS19jPro(BMMiner, S19jPro):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -1,11 +1,70 @@
from miners._backends import BMMiner # noqa - Ignore access to _module
from miners._types import T19 # noqa - Ignore access to _module
import httpx
import json
class BMMinerT19(BMMiner, T19):
def __init__(self, ip: str) -> None:
super().__init__(ip)
self.ip = ip
async def get_hostname(self) -> str:
return "?"
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

View File

@@ -28,6 +28,8 @@ from settings import (
NETWORK_PING_TIMEOUT as PING_TIMEOUT,
)
import asyncssh
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
MINER_CLASSES = {
@@ -131,8 +133,10 @@ MINER_CLASSES = {
"BTMiner": BTMinerM21,
},
"M21S": {
"Default": BTMinerM21S,
"BTMiner": BTMinerM21S,
"Default": BTMinerM21SV60,
"BTMiner": BTMinerM21SV60,
"60": BTMinerM21SV60,
"20": BTMinerM21SV20,
},
"M21S+": {
"Default": BTMinerM21SPlus,
@@ -141,10 +145,12 @@ MINER_CLASSES = {
"M30S": {
"Default": BTMinerM30S,
"BTMiner": BTMinerM30S,
"50": BTMinerM30SV50,
},
"M30S+": {
"Default": BTMinerM30SPlus,
"BTMiner": BTMinerM30SPlus,
"40": BTMinerM30SPlusVE40,
},
"M30S++": {
"Default": BTMinerM30SPlusPlusVG40,
@@ -159,6 +165,7 @@ MINER_CLASSES = {
"M31S+": {
"Default": BTMinerM31SPlus,
"BTMiner": BTMinerM31SPlus,
"20": BTMinerM31SPlusVE20,
},
"M32S": {
"Default": BTMinerM32S,
@@ -411,15 +418,34 @@ class MinerFactory(metaclass=Singleton):
elif "am2-s17" in version["STATUS"][0]["Description"]:
model = "Antminer S17"
# final try on a braiins OS bug with devdetails not returning
else:
async with asyncssh.connect(
str(ip),
known_hosts=None,
username="root",
password="admin",
server_host_key_algs=["ssh-rsa"],
) as conn:
cfg = await conn.run("bosminer config --data")
if cfg:
cfg = json.loads(cfg.stdout)
model = cfg.get("data").get("format").get("model")
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]
if "VE" in model:
ver = model.split("VE")[1]
if "VG" in model:
ver = model.split("VG")[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,14 @@
from miners._backends import BTMiner # noqa - Ignore access to _module
from miners._types import M21S # noqa - Ignore access to _module
from miners._types import M21SV20, M21SV60 # noqa - Ignore access to _module
class BTMinerM21S(BTMiner, M21S):
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 BTMinerM21SV20, BTMinerM21SV60
from .M21S_Plus import BTMinerM21SPlus

View File

@@ -1,8 +1,14 @@
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 # 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

View File

@@ -1,8 +1,14 @@
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 # 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

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,8 @@
from .M30S import BTMinerM30S
from .M30S_Plus import BTMinerM30SPlus
from .M30S import BTMinerM30S, BTMinerM30SV50
from .M30S_Plus import BTMinerM30SPlus, BTMinerM30SPlusVE40
from .M30S_Plus_Plus import BTMinerM30SPlusPlusVG40, BTMinerM30SPlusPlusVG30
from .M31S import BTMinerM31S
from .M31S_Plus import BTMinerM31SPlus
from .M31S_Plus import BTMinerM31SPlus, BTMinerM31SPlusVE20
from .M32S import BTMinerM32S

Binary file not shown.

View File

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

View File

@@ -4,29 +4,17 @@ import webbrowser
from miners.miner_factory import MinerFactory
from tools.cfg_util.decorators import disable_buttons
from tools.cfg_util.layout import TABLE_KEYS
from tools.cfg_util.layout import window, update_prog_bar
from tools.cfg_util.layout import window, update_prog_bar, TABLE_HEADERS
from tools.cfg_util.tables import TableManager, DATA_HEADER_MAP
progress_bar_len = 0
DEFAULT_DATA = [
"Model",
"Hostname",
"Hashrate",
"Temperature",
"Total Chips",
"Nominal Chips",
"Left Board",
"Center Board",
"Right Board",
"Pool User",
"Pool 1",
"Pool 1 User",
"Pool 2",
"Pool 2 User",
"Wattage",
"Split",
]
headers = []
for key in TABLE_HEADERS.keys():
for item in TABLE_HEADERS[key]:
headers.append(item)
DEFAULT_DATA = set(headers)
def btn_all(table, selected):

View File

@@ -167,6 +167,7 @@ BUTTON_KEYS = [
"cfg_all",
"cfg_web",
"cmd_listen",
"record",
]
TABLE_HEIGHT = 27
@@ -217,7 +218,7 @@ def get_scan_layout():
scan_layout = [
[
sg.Text("Scan IP", background_color=MAIN_TABS_BG, pad=((0, 5), (1, 1))),
sg.InputText(key="scan_ip", size=(31, 1)),
sg.InputText(key="scan_ip", size=(31, 1), focus=True),
sg.Button(
"Scan",
key="btn_scan",
@@ -243,6 +244,7 @@ def get_scan_layout():
key="scan_web",
border_width=BTN_BORDER,
),
sg.Button("RECORD DATA", key="record", border_width=BTN_BORDER),
sg.Button(
"STOP LISTENING",
key="scan_cancel_listen",

View File

@@ -0,0 +1 @@
from tools.cfg_util.record.ui import record_ui

View File

@@ -0,0 +1,39 @@
from typing import List
from tools.cfg_util.record.manager import RecordingManager
import PySimpleGUI as sg
async def start_recording(
ips: List[str], file: str, record_window: sg.Window, interval: int = 10
):
record_window["start_recording"].update(visible=False)
record_window["stop_recording"].update(visible=True)
record_window["pause_recording"].update(visible=True)
record_window["resume_recording"].update(visible=False)
record_window["_placeholder"].update(visible=False)
await RecordingManager().record(ips, file, record_window, interval=interval)
async def pause_recording(record_window):
await RecordingManager().pause()
record_window["resume_recording"].update(visible=True)
record_window["start_recording"].update(visible=False)
record_window["stop_recording"].update(visible=True)
record_window["pause_recording"].update(visible=False)
async def stop_recording(record_window):
await RecordingManager().stop()
record_window["start_recording"].update(visible=True)
record_window["stop_recording"].update(visible=False)
record_window["pause_recording"].update(visible=False)
record_window["resume_recording"].update(visible=False)
record_window["_placeholder"].update(visible=True)
async def resume_recording(record_window):
await RecordingManager().resume()
record_window["start_recording"].update(visible=False)
record_window["stop_recording"].update(visible=True)
record_window["pause_recording"].update(visible=True)
record_window["resume_recording"].update(visible=False)

View File

@@ -0,0 +1,117 @@
import base64
from io import BytesIO
import PySimpleGUI as sg
from PIL import Image
PAUSE_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTUTB1ciCQAAArVJREFUaN7dmktrU0EUx38Zal20leITF2mrBbuyirFiBQt1E7+AFvWbqO13EAqFgqB1IT52LoxQddcHtctKU62P2oUL+wBdpqmLnAvjNcnNnbn35k7+cCBkhpn53XmdMzMZolMvMApcBAaAPuAI0Cnpf4At4CuwBiwB74ENUqBTwARQBPYNbRUYF/DENQjMACULAL/tAa+AoSQATghAOUKAavZc6opFt4CdmAF02wbGogRoB6YTBPDblLTBSh1AoYkQnr0FDplCdAELKYDwbF5bykMNp0KKIPSeORgGZDqFEPqcaUg3Uwzh2Z1G9okdB0B2gZP1QJ44AOHZo1oQFxLYsaO0MnCuGshLhyA8e+Y1PqN5sZ8BZbjn7AOLwDcgCwzXKasMzAGbUu8lrR1hVQZOA9+9PyYsvspPIO+rYETiDH/eDeCqL29eyjCt/55eWNFinOZrfK1hn5tfkv+qKW8xPz/qkZ3p11gI6Po3Wt5CQN5Fi3ZkFXDNwqn8FJC+Zpg3rEYVkLMooBQivWRZVj3llBwUuK4BJcuX6+pXQHcLgHQrk2AlhepStIiUnAC6rt9KfHvXtauALy0Asq7Ez3JdRQV8sCigLUR6m2VZ9bSs5GjfVGeCdlzDvGH1zvuxGoMbfyWEG3/dwo1f0QsaTzCwGqkCYRNY3dVD3T5g3TLUXZIVsAe4HBDqzgM/xM8bsgh194B+PdQFeOHg4cPTanTnHTwOGqzVVY8dAnlYb8wdp3JTlHaIbeBY0AS64QDI7UZXg6kUQ0yGWdYOAK9TCDGLwX1ip6z3aYGYs4lmO1LSM7NU7jSt1N7kOTMpQz0yjSW8NG/JChqLDgMPxMeJc8eekT0tdp0lvkc1OZqgXuC+HO2bAqxQuePosWlIJkKoLP8/PDvKvw/PflF5eFYEliWy24yi8r9Buqx661BEjQAAAABJRU5ErkJggg=="
RECORD_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhWlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9Ta0UqDlYQcchQnSwUFXGUKhbBQmkrtOpg8tIfoUlDkuLiKLgWHPxZrDq4OOvq4CoIgj8gjk5Oii5S4n1JoUWMFx7v47x7Du/dBwiNClPNrhigapaRTsTFXH5FDL6iGz4MIoaAxEw9mVnIwrO+7qmX6i7Ks7z7/qw+pWAywCcSzzLdsIjXiac3LZ3zPnGYlSWF+Jx43KALEj9yXXb5jXPJYYFnho1seo44TCyWOljuYFY2VOIp4oiiapQv5FxWOG9xVis11ronf2GooC1nuE5rBAksIokURMioYQMVWIjSrpFiIk3ncQ//sONPkUsm1wYYOeZRhQrJ8YP/we/ZmsXJCTcpFAcCL7b9MQoEd4Fm3ba/j227eQL4n4Erre2vNoCZT9LrbS1yBPRvAxfXbU3eAy53gKEnXTIkR/LTEopF4P2MvikPDNwCvavu3FrnOH0AsjSrpRvg4BAYK1H2mse7ezrn9m9Pa34/WbVynangsIwAAAAGYktHRAD/AP8A/6C9p5MAAAAJcEhZcwAADdcAAA3XAUIom3gAAAAHdElNRQfmBgEVMgjCc30iAAAC7klEQVRo3t2az2sTQRTHPztiFdqqNP5A0CZWUClYxejFg1CPnrVF/Qu8+A/4438QC4WCoBVBojcP9lDrQVBBe6wasdhIj01biUeb9bBv7DYmaXdmdrPrF96hKftmPjPz5r2dWQ93ygPDwFngOFAAckCP/P8XUAW+A1+BD8Br4Acp0BHgLlAGfEP7AtwR8MQ1BEwCvy0AGm0NeAGcSwLggADUHQI0s5K0FYuuAisxA4RtGRh1CdAFTCQI0Gjj0gcrdQNTHYTQ9grYZQrRC7xPAYS2d6GtPNJymkoRRHhmdkQBmUghRDhmtqSRFENou76VPLGSAZBV4GA7kMcZgND2sBXEmQQytkurA6d057eFQO4Dgy4z6QhwQYqnAWDObaXhAX3Ac/2HrmK/AcrW+w1JQIDvrfsPfghG0qtF2Xraqy5jVNEzchO4aDv6l4CdMjqNEDJqnieJ4DxwyH6WPGAJeKMbKwPHbCAKTWZgM/ngL4BXsoP5DAx68ma3kDSEY5h+Zbuk8i2WUYS14eXtY2VYAUWbwFYOIlaJLwsVlRwUGJfHrmTp64SS7ctUvkMWG18DCthtGuQ2sdEsVkbMH9+jTGc1F8OhgIXPXsV/IgXUTB6sxtAZC581Bfw0ebK0Xju5inTfIimuKmDestZxWc2aal7JgbLZfDqksPRVVsBH06fHpY52UYtblvWziuBo31gVy1jxwa/Yj8WMkr6UTT2UgtLZM4FxVPl+AhZ1Hnli40nD1CMuJwcQf/uud4qC7F5Ze9VdA44ClXBDz4DLrg8fcqFkV8K5nhJcd2zQ6QweBw21InyUIZAH7aZqP8FNUdohloF9m627KxkAuRYlaacVYizKbrAdeJlCiGkM7hN7CK670gLxFoOrN63ulMzMtItDm64Ox8yYLHVnGk14a67KDhqL+oB7UuPEmbEnJafFrpPE91FNkQ4oD9yWdwJTgDngFtDfqRf+Rh3m3w/P9rLxw7Mlgg/PysAsMAMsumj8D9eE1oYpe82nAAAAAElFTkSuQmCC"
RESUME_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTUqWFKqAQAAApRJREFUaN7dms9LVFEUxz8+tBZp9NMISq0Wuskia9Gumr+ghUkF9X9k+gfUKihGhKCMiH4sgiglSltVULYrfEFF0aocNQwqcKYWnRev8c0w8+bc++71C2clnOOHN/fc+z33NqGnTuAwsB/oBrqAjUCr/P07UAA+AG+BF8AU8AkHtAMYBkLgd8qYAYYE3Lp6gTFgqQGA8igC94ADNgC2CEBJESApbkktIzoOzBsGiMccMKAJsAoYtQhQHnn5HxrSGmAiQ4goHgNr00K0Ac8dgIjiWayV1/VzmnAIIv5lVtcDMuogRHzN1KRjDkNEcbKWfWLeA5AFYGs1kGseQERxpRLEPgs7tmaUgD1JIHc8gojiZtIptqiQ+CNwH/hhCaQo9uGfhhWSFoD1kq8HmLYEMxgHCRUSPknYVM8pfelq8SYq2KGUcKpCEzkCfDYM0xEAOcMeZlK6y12DNQ4FQJ8FQ1YAjgKnxbtrqy+QQYEtjclw4pVy3u4A2GnZ74fAQeC8bGoa2gUwa3ixV1MO+KJQ+yvArwxBAPoVav8MWCEKgMUM6+eASwp5FgPgWwYA0a7/ENiskG+hGXhvuXP1ANfFNmjpXSDt0JZOAS+VIQDCZjmlmtY6YER7chjT9Eo5NG6Lis14fIx/HS865LGxOhMH6VK0ug8sWt2lcqsLcNvD4cONpEW518NxUG+lNnbVI5DL1fpxu9wUuQ4xV8vRpt8DkBO17pR5hyEu1rPltwDjDkI8SnOf2CrXXa5APE1z9Ra/DB135Eu0aZigfMZrokXzqDxguTUXpIMa0QbgguETbUkGee02nN5uzD2qsTHCXaZO4KyM9hvxE4Ni8FKrSRFqO8sfnm3i/4dns/x9eBaKX5kU99iw/gAVDZKvjiX0kgAAAABJRU5ErkJggg=="
STOP_BTN = b"iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAABhmlDQ1BJQ0MgcHJvZmlsZQAAKJF9kT1Iw1AUhU9TtSIVByuIOGSogmBBtIijVLEIFkpboVUHk5f+CE0akhQXR8G14ODPYtXBxVlXB1dBEPwBcXRyUnSREu9LCi1ivPB4H+fdc3jvPkCol5lqdkwAqmYZqXhMzOZWxMAruuDDAKIYk5ipJ9ILGXjW1z31Ut1FeJZ335/Vq+RNBvhE4lmmGxbxOvH0pqVz3icOsZKkEJ8Tjxt0QeJHrssuv3EuOizwzJCRSc0Rh4jFYhvLbcxKhkocJQ4rqkb5QtZlhfMWZ7VcZc178hcG89pymuu0hhHHIhJIQoSMKjZQhoUI7RopJlJ0HvPwDzn+JLlkcm2AkWMeFaiQHD/4H/yerVmYmnSTgjGg88W2P0aAwC7QqNn297FtN04A/zNwpbX8lTow80l6raWFj4C+beDiuqXJe8DlDjD4pEuG5Eh+WkKhALyf0TflgP5boGfVnVvzHKcPQIZmtXQDHBwCo0XKXvN4d3f73P7tac7vB71FcsVdKt+2AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH5gYBFTYF2K7EmwAAAohJREFUaN7dmk1r00EQh58sWsG2NviKaG21oCer2HpUqN9Bi/pNrO13EIoNBcHWg/hy82APVQ+KCtKj0ohVFEUUm0ZabzZ6yPxhjUmb7Ow/2c0P5pKQ2X2yu7M7s5vBn/qAEWAYOAb0A7uALvl+DVgGPgBvgVfAE+ATAegwMAHkgT+OtgiMC3jTNQjMAr8VAJW2DjwATjcDYJ8AlDwCVLO70lYqugispAxgWwEY9QnQAUw3EaDSpqQPKnUCcy2ESOwRsMMVoht4GQBEYi+sUN7QdJoLCMIemW2NgEwHCGGvmbp0IWCIxC7Xs0+sRABSBPZvBHIrAojEbtaCONWEHdunlYAT1UDuRwSR2J2k8xnrFPsOMIrNcw14CnyVw+RG2iJz/Kxsuq4qAUeAj8kHE8p/5jrQ49CRHiCnbHvMdphXQmilgXljZ3auTlYdR6JSWZmarv3oNcA5RQeeAT89gBTFl6tGDDCkcPDdY7rwTfHbYSOFAk3U8CWNr6NGwlfsGjCy0GJX1rgkKwGq29AmMhK/Y9eqkRgeu4oGeN8GIEtGaq+aqelzmrsqb4AFhYO9nsuxrlpol0PjwcTRosJJzgOIpvz02nY0rkxuco4nhKyHGtoVO9XtB5aUC+6XHMW/1JnqHgDOANsVba4DA3aqC3AvwuLD7Wp0JyMsBw3WGqqZiEBubLYvFCKAKAB7NltA5yMAuVRvNJgKGGKykbC2FXgYIMQ8DveJXZSvu0KBeK7JZjsDGZl5yneaKnW0eM1MylT3ptEmh+ZliaCpaCdwTc44ae7Ys55znZo6TnqPaoZogfqAq1La1+QTY8AhTUcyHqF6+f/h2W7+fXj2g/LDs7ykp4+Bzz4a/wvuXq9nlKOgSQAAAABJRU5ErkJggg=="
def record_layout():
buffer = BytesIO(base64.b64decode(sg.EMOJI_BASE64_HAPPY_BIG_SMILE))
im1 = Image.open(buffer)
with BytesIO() as output:
im1.save(output, format="PNG")
blank = output.getvalue()
im2 = Image.new("RGBA", (50, 50), "#ffffff00")
with BytesIO() as output:
im2.save(output, format="PNG")
blank = output.getvalue()
record_layout = [
[sg.Text("", key="record_status")],
[
sg.Text("Data Output File:"),
sg.Input(key="record_file"),
sg.SaveAs(
"Select File",
key="pick_record_file",
file_types=(("PDF Files", "*.pdf"),),
target="record_file",
),
],
[
sg.Spin(
[i for i in range(5, 101, 5)],
initial_value=10,
size=(5, 1),
key="record_interval",
),
sg.Text("Data Interval (seconds)"),
],
[
sg.Push(),
sg.pin(
sg.Button(
image_data=RECORD_BTN,
button_color=(
sg.theme_background_color(),
sg.theme_background_color(),
),
tooltip="Start Recording",
border_width=0,
key="start_recording",
)
),
sg.pin(
sg.Button(
image_data=blank,
button_color=(
sg.theme_background_color(),
sg.theme_background_color(),
),
border_width=0,
key="_placeholder",
)
),
sg.pin(
sg.Button(
image_data=STOP_BTN,
tooltip="Stop Recording",
button_color=(
sg.theme_background_color(),
sg.theme_background_color(),
),
border_width=0,
visible=False,
key="stop_recording",
)
),
sg.pin(
sg.Button(
image_data=PAUSE_BTN,
tooltip="Pause Recording",
button_color=(
sg.theme_background_color(),
sg.theme_background_color(),
),
border_width=0,
visible=False,
key="pause_recording",
)
),
sg.pin(
sg.Button(
image_data=RESUME_BTN,
tooltip="Resume Recording",
button_color=(
sg.theme_background_color(),
sg.theme_background_color(),
),
border_width=0,
visible=False,
key="resume_recording",
)
),
sg.Push(),
],
]
return record_layout
def get_record_window():
return sg.Window("Record Miner Data", record_layout(), modal=True)

View File

@@ -0,0 +1,96 @@
import asyncio
from tools.cfg_util.record.pdf import generate_pdf
from miners.miner_factory import MinerFactory
from typing import List, Dict
(RECORDING, PAUSING, PAUSED, RESUMING, STOPPING, DONE) = range(6)
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class RecordingManager(metaclass=Singleton):
_instance = None
def __init__(self):
self.state = DONE
self.data: Dict[str:list] = {}
self.miners = []
self.output_file = None
self.interval: int = 10
self.record_window = None
async def _check_pause(self):
if self.state == PAUSING:
self.state = PAUSED
self.record_window["record_status"].update("Paused.")
while not self.state == RESUMING and not self.state == STOPPING:
await asyncio.sleep(0.1)
self.record_window["record_status"].update("Recording...")
async def _record_loop(self):
while True:
await self._check_pause()
if self.state == STOPPING:
break
tasks = []
for miner in self.miners:
tasks.append(miner.get_data())
for complete in asyncio.as_completed(tasks):
data = await complete
self.data[data.ip].append(data)
for i in range(self.interval * 10):
await self._check_pause()
if self.state == STOPPING:
break
await asyncio.sleep(0.1)
self.state = DONE
self.record_window["record_status"].update(
"Writing to file (this could take a minute)..."
)
await asyncio.sleep(0.5)
await asyncio.create_task(self.write_output())
self.record_window["record_status"].update("")
async def write_output(self):
await generate_pdf(self.data, self.output_file)
async def record(
self, ips: List[str], output_file: str, record_window, interval: int = 10
):
self.record_window = record_window
for ip in ips:
self.data[ip] = []
self.output_file = output_file
self.interval = interval
self.state = RECORDING
self.record_window["record_status"].update("Recording...")
async for miner in MinerFactory().get_miner_generator(ips):
self.miners.append(miner)
asyncio.create_task(self._record_loop())
async def pause(self):
self.state = PAUSING
self.record_window["record_status"].update("Pausing...")
async def resume(self):
self.state = RESUMING
self.record_window["record_status"].update("Resuming...")
async def stop(self):
self.state = STOPPING
self.record_window["record_status"].update("Stopping...")

View File

@@ -0,0 +1,174 @@
from io import BytesIO
from typing import List, Dict
import matplotlib.pyplot as plt
from matplotlib.dates import DateFormatter
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.platypus import (
SimpleDocTemplate,
KeepInFrame,
Image,
Paragraph,
PageBreak,
Spacer,
)
from svglib.svglib import svg2rlg
from data import MinerData
async def generate_pdf(data: Dict[str, List[MinerData]], file_loc):
doc = SimpleDocTemplate(
file_loc,
pagesize=letter,
topMargin=0.25 * inch,
leftMargin=1 * inch,
rightMargin=1 * inch,
bottomMargin=1 * inch,
title=f"Recorded Data",
)
elements = []
i = 0
for item in data.keys():
i += 1
if not i == 1:
elements.append(PageBreak())
page_elem = await generate_page(data[item])
for elem in page_elem:
elements.append(elem)
doc.build(
elements,
)
async def generate_page(data):
title_style = ParagraphStyle(
"Title",
alignment=TA_CENTER,
fontSize=25,
spaceAfter=40,
spaceBefore=150,
fontName="Helvetica-Bold",
)
hr_graph = create_hr_graph(data)
fan_graph = create_fans_graph(data)
temp_graph = create_temp_graph(data)
title = Paragraph(data[0].ip, style=title_style)
elements = [
title,
await hr_graph,
Spacer(0, 40),
await temp_graph,
Spacer(0, 40),
await fan_graph,
]
return elements
async def create_hr_graph(data):
fig, ax = plt.subplots(figsize=(6, 2))
xpoints = []
ypoints = []
for item in data:
xpoints.append(item.datetime)
ypoints.append(item.hashrate)
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_fontsize(6)
ax.plot(xpoints, ypoints)
ylim = max(ypoints) * 1.4
if ylim == 0:
ylim = 10
ax.set_ylim(0, ylim)
date_form = DateFormatter("%H:%M:%S")
ax.xaxis.set_major_formatter(date_form)
ax.yaxis.set_major_formatter("{x:1.1f} TH/s")
ax.set_title("Hashrate", fontsize=15)
ax.yaxis.set_major_locator(MultipleLocator(5))
ax.yaxis.set_minor_locator(MultipleLocator(1))
imgdata = BytesIO()
fig.savefig(imgdata, format="svg")
imgdata.seek(0) # rewind the data
drawing = svg2rlg(imgdata)
imgdata.close()
plt.close("all")
hr_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
return hr_graph
async def create_fans_graph(data):
fig, ax = plt.subplots(figsize=(6, 2))
xpoints = []
ypoints_f1 = []
ypoints_f2 = []
ypoints_f3 = []
ypoints_f4 = []
for item in data:
xpoints.append(item.datetime)
ypoints_f1.append(item.fan_1)
ypoints_f2.append(item.fan_2)
ypoints_f3.append(item.fan_3)
ypoints_f4.append(item.fan_4)
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_fontsize(6)
for ypoints in [ypoints_f1, ypoints_f2, ypoints_f3, ypoints_f4]:
if not ypoints == [-1 for x in range(len(ypoints))]:
ax.plot(xpoints, ypoints)
ax.set_ylim(0, 10000)
date_form = DateFormatter("%H:%M:%S")
ax.xaxis.set_major_formatter(date_form)
ax.yaxis.set_major_formatter("{x:1.0f} RPM")
ax.set_title("Fans", fontsize=15)
imgdata = BytesIO()
fig.savefig(imgdata, format="svg")
imgdata.seek(0) # rewind the data
drawing = svg2rlg(imgdata)
imgdata.close()
plt.close("all")
fans_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
return fans_graph
async def create_temp_graph(data):
fig, ax = plt.subplots(figsize=(6, 2))
# plt.figure()
xpoints = []
ypoints = []
for item in data:
xpoints.append(item.datetime)
ypoints.append(item.temperature_avg)
for label in ax.get_xticklabels() + ax.get_yticklabels():
label.set_fontsize(6)
ax.plot(xpoints, ypoints)
ax.set_ylim(0, 130)
ax.yaxis.set_major_locator(MultipleLocator(20))
ax.yaxis.set_minor_locator(MultipleLocator(5))
date_form = DateFormatter("%H:%M:%S")
ax.xaxis.set_major_formatter(date_form)
ax.yaxis.set_major_formatter("{x:1.1f} C")
ax.set_title("Temperature", fontsize=15)
imgdata = BytesIO()
fig.savefig(imgdata, format="svg")
imgdata.seek(0) # rewind the data
drawing = svg2rlg(imgdata)
imgdata.close()
plt.close("all")
temp_graph = KeepInFrame(375, 375, [Image(drawing)], hAlign="CENTER")
return temp_graph

View File

@@ -0,0 +1,40 @@
import asyncio
import PySimpleGUI as sg
from tools.cfg_util.record.layout import get_record_window
from tools.cfg_util.record.func import (
start_recording,
stop_recording,
pause_recording,
resume_recording,
)
async def record_ui(ips: list):
if not len(ips) > 0:
return
record_window = get_record_window()
while True:
event, values = record_window.read(0.001)
if event in (None, "Close", sg.WIN_CLOSED):
break
if event == "start_recording":
if values["record_file"]:
asyncio.create_task(
start_recording(
ips,
values["record_file"],
record_window,
interval=int(values["record_interval"]),
)
)
if event == "stop_recording":
asyncio.create_task(stop_recording(record_window))
if event == "resume_recording":
asyncio.create_task(resume_recording(record_window))
if event == "pause_recording":
asyncio.create_task(pause_recording(record_window))
if event == "__TIMEOUT__":
await asyncio.sleep(0.001)

View File

@@ -209,6 +209,10 @@ class TableManager(metaclass=Singleton):
return ipaddress.ip_address(self.data[data_key]["IP"])
if self.sort_key == "Chip %":
if self.data[data_key]["Chip %"] == "":
return 0
if isinstance(self.data[data_key]["Chip %"], int):
return self.data[data_key]["Chip %"]
return int((self.data[data_key]["Chip %"]).replace("%", ""))
if self.sort_key == "Hashrate":
@@ -225,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

View File

@@ -17,6 +17,7 @@ from tools.cfg_util.configure import (
btn_import,
btn_config,
)
from tools.cfg_util.record import record_ui
from tools.cfg_util.layout import window, TABLE_KEYS
from tools.cfg_util.general import btn_all, btn_web, btn_refresh
from tools.cfg_util.tables import TableManager
@@ -102,7 +103,7 @@ async def ui():
window["cmd_table"].Widget.column("#0", stretch=tk.NO, anchor=tk.CENTER)
while True:
event, value = window.read(0)
event, value = window.read(0.001)
if event in (None, "Close", sg.WIN_CLOSED):
sys.exit()
@@ -125,6 +126,16 @@ async def ui():
asyncio.create_task(btn_refresh(_table, value[_table]))
if event == "btn_scan":
asyncio.create_task(btn_scan(value["scan_ip"]))
if event == "record":
_table = "scan_table"
if value[_table]:
ips = [window[_table].Values[row][0] for row in value[_table]]
else:
ips = [
window[_table].Values[row][0]
for row in range(len(window[_table].Values))
]
asyncio.create_task(record_ui(ips))
# boards tab
if event == "boards_all":
@@ -202,7 +213,7 @@ async def ui():
asyncio.create_task(btn_cancel_listen())
if event == "__TIMEOUT__":
await asyncio.sleep(0)
await asyncio.sleep(0.001)
if __name__ == "__main__":

View File

@@ -229,6 +229,10 @@ class TestbenchMiner:
if "ERROR:Auth" in stdout_data:
error = "AUTH"
proc.kill()
if "INFO:Remote target is already running Braiins OS" in stdout_data:
proc.kill()
self.state = UPDATE
return
await self.add_to_output(stdout_data)
if stdout == b"":
break

View File

@@ -1,4 +1,5 @@
import aiohttp
import httpx
import shutil
import aiofiles
import asyncio
@@ -11,8 +12,8 @@ import logging
async def get_latest_version(session):
feeds_url = "http://feeds.braiins-os.com"
async with session.get(feeds_url) as resp:
data = await resp.read()
resp = await session.get(feeds_url)
data = resp.text
soup = BeautifulSoup(data, "html.parser")
@@ -32,8 +33,8 @@ async def get_latest_version(session):
async def get_feeds_file(session, version):
feeds_url = "http://feeds.braiins-os.com"
async with session.get(feeds_url + "/" + version) as resp:
data = await resp.read()
resp = await session.get(feeds_url + "/" + version, follow_redirects=True)
data = resp.text
soup = BeautifulSoup(data, "html.parser")
@@ -52,8 +53,8 @@ async def get_feeds_file(session, version):
async def get_update_file(session, version):
feeds_url = "http://feeds.braiins-os.com"
async with session.get(feeds_url + "/am1-s9") as resp:
data = await resp.read()
resp = await session.get(feeds_url + "/am1-s9")
data = resp.text
soup = BeautifulSoup(data, "html.parser")
@@ -77,14 +78,14 @@ async def get_latest_update_file(session, update_file):
if os.path.exists(update_file_dir):
os.remove(update_file_dir)
async with session.get(update_file_loc) as update_file_data:
if update_file_data.status == 200:
f = await aiofiles.open(
os.path.join(os.path.dirname(__file__), "files", "update.tar"),
mode="wb",
)
await f.write(await update_file_data.read())
await f.close()
update_file_data = await session.get(update_file_loc)
if update_file_data.status_code == 200:
f = await aiofiles.open(
os.path.join(os.path.dirname(__file__), "files", "update.tar"),
mode="wb",
)
await f.write(update_file_data.text)
await f.close()
async def get_latest_install_file(session, version, feeds_path, install_file):
@@ -99,13 +100,14 @@ async def get_latest_install_file(session, version, feeds_path, install_file):
shutil.rmtree(install_file_folder)
os.mkdir(install_file_folder)
async with session.get(install_file_loc) as install_file_data:
if install_file_data.status == 200:
f = await aiofiles.open(
os.path.join(install_file_folder, install_file), mode="wb"
)
await f.write(await install_file_data.read())
await f.close()
install_file_data = await session.get(install_file_loc)
if install_file_data.status_code == 200:
f = await aiofiles.open(
os.path.join(install_file_folder, install_file), mode="wb"
)
for chunk in install_file_data.iter_bytes():
await f.write(chunk)
await f.close()
async def update_installer_files():
@@ -113,7 +115,7 @@ async def update_installer_files():
os.path.dirname(__file__), "files", "bos-toolbox", "feeds"
)
feeds_versions = await get_local_versions()
async with aiohttp.ClientSession() as session:
async with httpx.AsyncClient() as session:
version = await get_latest_version(session)
if version not in feeds_versions:
@@ -148,4 +150,4 @@ async def get_local_versions():
if __name__ == "__main__":
asyncio.get_event_loop().run_until_complete(update_installer_files())
asyncio.run(update_installer_files())