feature: add really basic MaraFW support.

This commit is contained in:
Upstream Data
2024-04-12 09:55:45 -06:00
parent ba6a1606b6
commit f63d8f4b91
4 changed files with 243 additions and 14 deletions

View File

@@ -26,5 +26,6 @@ from .goldshell import GoldshellMiner
from .hiveon import Hiveon
from .innosilicon import Innosilicon
from .luxminer import LUXMiner
from .marathon import MaraMiner
from .vnish import VNish
from .whatsminer import M2X, M3X, M5X, M6X

View File

@@ -0,0 +1,87 @@
from typing import Optional
from pyasic.errors import APIError
from pyasic.miners.backends import AntminerModern
from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.web.marathon import MaraWebAPI
MARA_DATA_LOC = DataLocations(
**{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.FW_VERSION): DataFunction(
"_get_fw_ver",
[RPCAPICommand("rpc_version", "version")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.ERRORS): DataFunction(
"_get_errors",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FAULT_LIGHT): DataFunction(
"_get_fault_light",
[WebAPICommand("web_get_blink_status", "get_blink_status")],
),
str(DataOptions.IS_MINING): DataFunction(
"_is_mining",
[WebAPICommand("web_get_conf", "get_miner_conf")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[WebAPICommand("web_brief", "brief")],
),
}
)
class MaraMiner(AntminerModern):
_web_cls = MaraWebAPI
web: MaraWebAPI
data_locations = MARA_DATA_LOC
firmware = "MaraFW"
async def _get_wattage(self, web_brief: dict = None) -> Optional[int]:
if web_brief is None:
try:
web_brief = await self.web.brief()
except APIError:
pass
if web_brief is not None:
try:
return web_brief["power_consumption_estimated"]
except LookupError:
pass

View File

@@ -30,19 +30,7 @@ from pyasic.logger import logger
from pyasic.miners.antminer import *
from pyasic.miners.auradine import *
from pyasic.miners.avalonminer import *
from pyasic.miners.backends import (
Auradine,
AvalonMiner,
BMMiner,
BOSMiner,
BTMiner,
GoldshellMiner,
Hiveon,
Innosilicon,
LUXMiner,
VNish,
ePIC,
)
from pyasic.miners.backends import *
from pyasic.miners.backends.unknown import UnknownMiner
from pyasic.miners.base import AnyMiner
from pyasic.miners.blockminer import *
@@ -64,6 +52,7 @@ class MinerTypes(enum.Enum):
LUX_OS = 8
EPIC = 9
AURADINE = 10
MARATHON = 11
MINER_CLASSES = {
@@ -424,7 +413,7 @@ MINER_CLASSES = {
"ANTMINER S21": LUXMinerS21,
},
MinerTypes.AURADINE: {
None: type("GoldshellUnknown", (Auradine, AuradineMake), {}),
None: type("AuradineUnknown", (Auradine, AuradineMake), {}),
"AT1500": AuradineFluxAT1500,
"AT2860": AuradineFluxAT2860,
"AT2880": AuradineFluxAT2880,
@@ -433,6 +422,9 @@ MINER_CLASSES = {
"AD2500": AuradineFluxAD2500,
"AD3500": AuradineFluxAD3500,
},
MinerTypes.MARATHON: {
None: MaraMiner,
},
}
@@ -508,6 +500,7 @@ class MinerFactory:
MinerTypes.HIVEON: self.get_miner_model_hiveon,
MinerTypes.LUX_OS: self.get_miner_model_luxos,
MinerTypes.AURADINE: self.get_miner_model_auradine,
MinerTypes.MARATHON: self.get_miner_model_marathon,
}
fn = miner_model_fns.get(miner_type)
@@ -689,6 +682,8 @@ class MinerFactory:
return MinerTypes.HIVEON
if "LUXMINER" in upper_data:
return MinerTypes.LUX_OS
if "KAONSU" in upper_data:
return MinerTypes.MARATHON
if "ANTMINER" in upper_data and "DEVDETAILS" not in upper_data:
return MinerTypes.ANTMINER
if (
@@ -1001,6 +996,21 @@ class MinerFactory:
except LookupError:
pass
async def get_miner_model_marathon(self, ip: str) -> str | None:
auth = httpx.DigestAuth("root", "root")
web_json_data = await self.send_web_command(
ip, "/kaonsu/v1/overview", auth=auth
)
try:
miner_model = web_json_data["model"]
if miner_model == "":
return None
return miner_model
except (TypeError, LookupError):
pass
miner_factory = MinerFactory()

131
pyasic/web/marathon.py Normal file
View File

@@ -0,0 +1,131 @@
from __future__ import annotations
import json
from typing import Any
import httpx
from pyasic import settings
from pyasic.web.antminer import AntminerModernWebAPI
class MaraWebAPI(AntminerModernWebAPI):
def __init__(self, ip: str) -> None:
self.am_commands = [
"get_miner_conf",
"set_miner_conf",
"blink",
"reboot",
"get_system_info",
"get_network_info",
"summary",
"get_blink_status",
"set_network_conf",
]
super().__init__(ip)
async def _send_mara_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
url = f"http://{self.ip}:{self.port}/kaonsu/v1/{command}"
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(
transport=settings.transport(),
) as client:
if parameters:
data = await client.post(
url,
auth=auth,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
else:
data = await client.get(url, auth=auth)
except httpx.HTTPError:
pass
else:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
pass
async def _send_am_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
):
url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi"
auth = httpx.DigestAuth(self.username, self.pwd)
try:
async with httpx.AsyncClient(
transport=settings.transport(),
) as client:
if parameters:
data = await client.post(
url,
auth=auth,
timeout=settings.get("api_function_timeout", 3),
json=parameters,
)
else:
data = await client.get(url, auth=auth)
except httpx.HTTPError:
pass
else:
if data.status_code == 200:
try:
return data.json()
except json.decoder.JSONDecodeError:
pass
async def send_command(
self,
command: str | bytes,
ignore_errors: bool = False,
allow_warning: bool = True,
privileged: bool = False,
**parameters: Any,
) -> dict:
if command in self.am_commands:
return await self._send_am_command(
command,
ignore_errors=ignore_errors,
allow_warning=allow_warning,
privileged=privileged,
**parameters,
)
return await self._send_mara_command(
command,
ignore_errors=ignore_errors,
allow_warning=allow_warning,
privileged=privileged,
**parameters,
)
async def brief(self):
return await self.send_command("brief")
async def overview(self):
return await self.send_command("overview")
async def connections(self):
return await self.send_command("connections")
async def event_chart(self):
return await self.send_command("event_chart")
async def hashboards(self):
return await self.send_command("hashboards")
async def mara_pools(self):
return await self._send_mara_command("pools")