Merge branch 'master' into update_firmware_2
This commit is contained in:
@@ -140,6 +140,14 @@ class MinerConfig:
|
||||
**self.pools.as_mara(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
**self.fan_mode.as_bitaxe(),
|
||||
**self.temperature.as_bitaxe(),
|
||||
**self.mining_mode.as_bitaxe(),
|
||||
**self.pools.as_bitaxe(user_suffix=user_suffix),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from a dictionary."""
|
||||
@@ -235,3 +243,10 @@ class MinerConfig:
|
||||
fan_mode=FanModeConfig.from_mara(web_miner_config),
|
||||
mining_mode=MiningModeConfig.from_mara(web_miner_config),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "MinerConfig":
|
||||
return cls(
|
||||
pools=PoolConfig.from_bitaxe(web_system_info),
|
||||
fan_mode=FanModeConfig.from_bitaxe(web_system_info),
|
||||
)
|
||||
|
||||
@@ -60,6 +60,9 @@ class MinerConfigOption(Enum):
|
||||
def as_mara(self) -> dict:
|
||||
return self.value.as_mara()
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return self.value.as_bitaxe()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@@ -119,6 +122,9 @@ class MinerConfigValue:
|
||||
def as_mara(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {}
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
|
||||
@@ -80,6 +80,9 @@ class FanModeNormal(MinerConfigValue):
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {"autoFanspeed": 1}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeManual(MinerConfigValue):
|
||||
@@ -138,6 +141,9 @@ class FanModeManual(MinerConfigValue):
|
||||
},
|
||||
}
|
||||
|
||||
def as_bitaxe(self) -> dict:
|
||||
return {"autoFanspeed": 0, "fanspeed": self.speed}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
@@ -291,3 +297,10 @@ class FanModeConfig(MinerConfigOption):
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict):
|
||||
if web_system_info["autofanspeed"] == 1:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.manual(speed=web_system_info["fanspeed"])
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
@@ -127,6 +127,13 @@ class Pool(MinerConfigValue):
|
||||
}
|
||||
return {"url": self.url, "user": self.user, "pass": self.password}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return {
|
||||
"stratumURL": self.url,
|
||||
"stratumUser": f"{self.user}{user_suffix}",
|
||||
"stratumPassword": self.password,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "Pool":
|
||||
return cls(
|
||||
@@ -194,6 +201,15 @@ class Pool(MinerConfigValue):
|
||||
password=web_pool["pass"],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "Pool":
|
||||
url = f"stratum+tcp://{web_system_info['stratumURL']}:{web_system_info['stratumPort']}"
|
||||
return cls(
|
||||
url=url,
|
||||
user=web_system_info["stratumUser"],
|
||||
password=web_system_info.get("stratumPassword", ""),
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolGroup(MinerConfigValue):
|
||||
@@ -287,6 +303,9 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_mara(self, user_suffix: str = None) -> list:
|
||||
return [p.as_mara(user_suffix=user_suffix) for p in self.pools]
|
||||
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return self.pools[0].as_bitaxe(user_suffix=user_suffix)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "PoolGroup":
|
||||
cls_conf = {}
|
||||
@@ -360,6 +379,10 @@ class PoolGroup(MinerConfigValue):
|
||||
def from_mara(cls, web_config_pools: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_mara(pool_conf) for pool_conf in web_config_pools])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_bitaxe(web_system_info)])
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolConfig(MinerConfigValue):
|
||||
@@ -456,6 +479,9 @@ class PoolConfig(MinerConfigValue):
|
||||
return {"pools": self.groups[0].as_mara(user_suffix=user_suffix)}
|
||||
return {"pools": []}
|
||||
|
||||
def as_bitaxe(self, user_suffix: str = None) -> dict:
|
||||
return self.groups[0].as_bitaxe(user_suffix=user_suffix)
|
||||
|
||||
@classmethod
|
||||
def from_api(cls, api_pools: dict) -> "PoolConfig":
|
||||
try:
|
||||
@@ -514,3 +540,7 @@ class PoolConfig(MinerConfigValue):
|
||||
@classmethod
|
||||
def from_mara(cls, web_config: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_mara(web_config["pools"])])
|
||||
|
||||
@classmethod
|
||||
def from_bitaxe(cls, web_system_info: dict) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_bitaxe(web_system_info)])
|
||||
|
||||
@@ -1,4 +1,35 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
class Scheme(Enum):
|
||||
STRATUM_V1 = "stratum+tcp"
|
||||
STRATUM_V2 = "stratum2+tcp"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolUrl:
|
||||
scheme: Scheme
|
||||
host: str
|
||||
port: int
|
||||
pubkey: Optional[str] = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
|
||||
else:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}"
|
||||
|
||||
@classmethod
|
||||
def from_str(cls, url: str) -> "PoolUrl":
|
||||
parsed_url = urlparse(url)
|
||||
scheme = Scheme(parsed_url.scheme)
|
||||
host = parsed_url.hostname
|
||||
port = parsed_url.port
|
||||
pubkey = parsed_url.path.lstrip("/") if scheme == Scheme.STRATUM_V2 else None
|
||||
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -19,13 +50,13 @@ class PoolMetrics:
|
||||
pool_stale_percent: Percentage of stale shares by the pool.
|
||||
"""
|
||||
|
||||
url: PoolUrl
|
||||
accepted: int = None
|
||||
rejected: int = None
|
||||
get_failures: int = None
|
||||
remote_failures: int = None
|
||||
active: bool = None
|
||||
alive: bool = None
|
||||
url: str = None
|
||||
index: int = None
|
||||
user: str = None
|
||||
pool_rejected_percent: float = field(init=False)
|
||||
|
||||
@@ -25,6 +25,7 @@ class MinerMake(str, Enum):
|
||||
GOLDSHELL = "Goldshell"
|
||||
AURADINE = "Auradine"
|
||||
EPIC = "ePIC"
|
||||
BITAXE = "BitAxe"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
@@ -329,6 +329,15 @@ class AuradineModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class BitAxeModels(str, Enum):
|
||||
BM1366 = "Ultra"
|
||||
BM1368 = "Supra"
|
||||
BM1397 = "Max"
|
||||
|
||||
def __str__(self):
|
||||
return self.value
|
||||
|
||||
|
||||
class MinerModel:
|
||||
ANTMINER = AntminerModels
|
||||
WHATSMINER = WhatsminerModels
|
||||
@@ -337,3 +346,4 @@ class MinerModel:
|
||||
GOLDSHELL = GoldshellModels
|
||||
AURADINE = AuradineModels
|
||||
EPIC = ePICModels
|
||||
BITAXE = BitAxeModels
|
||||
|
||||
@@ -24,6 +24,7 @@ from pyasic.miners.device.models import (
|
||||
S19jPro,
|
||||
S19NoPIC,
|
||||
S19Pro,
|
||||
S19ProHydro,
|
||||
)
|
||||
|
||||
|
||||
@@ -57,3 +58,7 @@ class VNishS19j(VNish, S19j):
|
||||
|
||||
class VNishS19jPro(VNish, S19jPro):
|
||||
pass
|
||||
|
||||
|
||||
class VNishS19ProHydro(VNish, S19ProHydro):
|
||||
pass
|
||||
|
||||
@@ -22,6 +22,7 @@ from .S19 import (
|
||||
VNishS19jPro,
|
||||
VNishS19NoPIC,
|
||||
VNishS19Pro,
|
||||
VNishS19ProHydro,
|
||||
VNishS19XP,
|
||||
)
|
||||
from .T19 import VNishT19
|
||||
|
||||
@@ -19,6 +19,8 @@ from typing import List, Optional, Union
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.backends.bmminer import BMMiner
|
||||
from pyasic.miners.backends.cgminer import CGMiner
|
||||
from pyasic.miners.data import (
|
||||
@@ -31,8 +33,6 @@ from pyasic.miners.data import (
|
||||
from pyasic.rpc.antminer import AntminerRPCAPI
|
||||
from pyasic.ssh.antminer import AntminerModernSSH
|
||||
from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
from pyasic.errors import APIError
|
||||
|
||||
ANTMINER_MODERN_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -95,7 +95,7 @@ class AntminerModern(BMMiner):
|
||||
web: AntminerModernWebAPI
|
||||
|
||||
_rpc_cls = AntminerRPCAPI
|
||||
web: AntminerRPCAPI
|
||||
rpc: AntminerRPCAPI
|
||||
|
||||
_ssh_cls = AntminerModernSSH
|
||||
ssh: AntminerModernSSH
|
||||
@@ -156,7 +156,7 @@ class AntminerModern(BMMiner):
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
@@ -169,7 +169,7 @@ class AntminerModern(BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]:
|
||||
async def _get_mac(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
web_get_system_info = await self.web.get_system_info()
|
||||
@@ -264,7 +264,9 @@ class AntminerModern(BMMiner):
|
||||
pass
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
@@ -368,6 +370,8 @@ class AntminerModern(BMMiner):
|
||||
try:
|
||||
pools = rpc_pools.get("POOLS", [])
|
||||
for pool_info in pools:
|
||||
url = pool_info.get("URL")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("Accepted"),
|
||||
rejected=pool_info.get("Rejected"),
|
||||
@@ -375,10 +379,9 @@ class AntminerModern(BMMiner):
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_info.get("URL"),
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL")
|
||||
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
@@ -446,7 +449,7 @@ class AntminerOld(CGMiner):
|
||||
self.config = config
|
||||
await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix))
|
||||
|
||||
async def _get_mac(self) -> Union[str, None]:
|
||||
async def _get_mac(self) -> Optional[str]:
|
||||
try:
|
||||
data = await self.web.get_system_info()
|
||||
if data:
|
||||
|
||||
@@ -281,7 +281,7 @@ class Auradine(StockFirmware):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
|
||||
@@ -173,7 +173,7 @@ class AvalonMiner(CGMiner):
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_devs: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
rpc_devs = await self.rpc.devs()
|
||||
@@ -238,7 +238,9 @@ class AvalonMiner(CGMiner):
|
||||
|
||||
return hashboards
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
|
||||
@@ -105,7 +105,7 @@ class BFGMiner(StockFirmware):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
# get hr from API
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
@@ -207,7 +207,9 @@ class BFGMiner(StockFirmware):
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
# X19 method, not sure compatibility
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
|
||||
189
pyasic/miners/backends/bitaxe.py
Normal file
189
pyasic/miners/backends/bitaxe.py
Normal file
@@ -0,0 +1,189 @@
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.device import MinerFirmware
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.web.bitaxe import BitAxeWebAPI
|
||||
|
||||
BITAXE_DATA_LOC = DataLocations(
|
||||
**{
|
||||
str(DataOptions.HASHRATE): DataFunction(
|
||||
"_get_hashrate",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.WATTAGE): DataFunction(
|
||||
"_get_wattage",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HASHBOARDS): DataFunction(
|
||||
"_get_hashboards",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.HOSTNAME): DataFunction(
|
||||
"_get_hostname",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FANS): DataFunction(
|
||||
"_get_fans",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.FW_VERSION): DataFunction(
|
||||
"_get_fw_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
str(DataOptions.API_VERSION): DataFunction(
|
||||
"_get_api_ver",
|
||||
[WebAPICommand("web_system_info", "system/info")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class BitAxe(BaseMiner):
|
||||
"""Handler for BitAxe"""
|
||||
|
||||
web: BitAxeWebAPI
|
||||
_web_cls = BitAxeWebAPI
|
||||
|
||||
firmware = MinerFirmware.STOCK
|
||||
|
||||
data_locations = BITAXE_DATA_LOC
|
||||
|
||||
async def reboot(self) -> bool:
|
||||
await self.web.restart()
|
||||
return True
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
web_system_info = await self.web.system_info()
|
||||
return MinerConfig.from_bitaxe(web_system_info)
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
await self.web.update_settings(**config.as_bitaxe())
|
||||
|
||||
async def _get_wattage(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return round(web_system_info["power"])
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(
|
||||
self, web_system_info: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
web_system_info["hashRate"], HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, web_system_info: dict = None) -> Optional[int]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["uptimeSeconds"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, web_system_info: dict = None) -> List[HashBoard]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [
|
||||
HashBoard(
|
||||
hashrate=AlgoHashRate.SHA256(
|
||||
web_system_info["hashRate"], HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default),
|
||||
chip_temp=web_system_info.get("temp"),
|
||||
temp=web_system_info.get("vrTemp"),
|
||||
chips=web_system_info.get("asicCount", 1),
|
||||
expected_chips=self.expected_chips,
|
||||
missing=False,
|
||||
active=True,
|
||||
voltage=web_system_info.get("voltage"),
|
||||
)
|
||||
]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_fans(self, web_system_info: dict = None) -> List[Fan]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return [Fan(speed=web_system_info["fanrpm"])]
|
||||
except KeyError:
|
||||
pass
|
||||
return []
|
||||
|
||||
async def _get_hostname(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["hostname"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_api_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_fw_ver(self, web_system_info: dict = None) -> Optional[str]:
|
||||
if web_system_info is None:
|
||||
try:
|
||||
web_system_info = await self.web.system_info()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return web_system_info["version"]
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -109,7 +109,7 @@ class BMMiner(StockFirmware):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
# get hr from API
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
@@ -223,7 +223,9 @@ class BMMiner(StockFirmware):
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
# X19 method, not sure compatibility
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
|
||||
@@ -26,6 +26,7 @@ from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.error_codes import BraiinsOSError, MinerErrorData
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
@@ -39,7 +40,6 @@ from pyasic.rpc.bosminer import BOSMinerRPCAPI
|
||||
from pyasic.ssh.braiins_os import BOSMinerSSH
|
||||
from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import SaveAction
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
|
||||
BOSMINER_DATA_LOC = DataLocations(
|
||||
**{
|
||||
@@ -349,7 +349,7 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
return None
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
@@ -525,7 +525,9 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
except (TypeError, AttributeError):
|
||||
return self.light
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_devs: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_devs: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if rpc_devs is None:
|
||||
try:
|
||||
rpc_devs = await self.rpc.devs()
|
||||
@@ -590,6 +592,8 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
try:
|
||||
pools = rpc_pools.get("POOLS", [])
|
||||
for pool_info in pools:
|
||||
url = pool_info.get("URL")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("Accepted"),
|
||||
rejected=pool_info.get("Rejected"),
|
||||
@@ -597,17 +601,15 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_info.get("URL"),
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
|
||||
|
||||
async def upgrade_firmware(self, file: Path):
|
||||
"""
|
||||
Upgrade the firmware of the BOSMiner device.
|
||||
@@ -866,7 +868,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
@@ -883,7 +885,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
|
||||
async def _get_expected_hashrate(
|
||||
self, grpc_miner_details: dict = None
|
||||
) -> Optional[float]:
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if grpc_miner_details is None:
|
||||
try:
|
||||
grpc_miner_details = await self.web.get_miner_details()
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import logging
|
||||
from typing import List, Optional
|
||||
import aiofiles
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
import aiofiles
|
||||
|
||||
from pyasic.config import MinerConfig, MiningModeConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
@@ -388,7 +389,7 @@ class BTMiner(StockFirmware):
|
||||
|
||||
return hostname
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
@@ -564,7 +565,9 @@ class BTMiner(StockFirmware):
|
||||
pass
|
||||
return errors
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_summary: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
@@ -675,17 +678,24 @@ class BTMiner(StockFirmware):
|
||||
|
||||
result = await self.rpc.update_firmware(upgrade_contents)
|
||||
|
||||
logging.info("Firmware upgrade process completed successfully for Whatsminer.")
|
||||
logging.info(
|
||||
"Firmware upgrade process completed successfully for Whatsminer."
|
||||
)
|
||||
return result
|
||||
except FileNotFoundError as e:
|
||||
logging.error(f"File not found during the firmware upgrade process: {e}")
|
||||
raise
|
||||
except ValueError as e:
|
||||
logging.error(f"Validation error occurred during the firmware upgrade process: {e}")
|
||||
logging.error(
|
||||
f"Validation error occurred during the firmware upgrade process: {e}"
|
||||
)
|
||||
raise
|
||||
except OSError as e:
|
||||
logging.error(f"OS error occurred during the firmware upgrade process: {e}")
|
||||
raise
|
||||
except Exception as e:
|
||||
logging.error(f"An unexpected error occurred during the firmware upgrade process: {e}", exc_info=True)
|
||||
logging.error(
|
||||
f"An unexpected error occurred during the firmware upgrade process: {e}",
|
||||
exc_info=True,
|
||||
)
|
||||
raise
|
||||
|
||||
@@ -109,7 +109,7 @@ class CGMiner(StockFirmware):
|
||||
|
||||
return self.fw_ver
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
|
||||
@@ -14,14 +14,15 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.error_codes import MinerErrorData, X19Error
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
from pyasic.miners.device.firmware import ePICFirmware
|
||||
from pyasic.web.epic import ePICWebAPI
|
||||
@@ -220,7 +221,7 @@ class ePIC(ePICFirmware):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, web_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
@@ -239,7 +240,9 @@ class ePIC(ePICFirmware):
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, web_summary: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_summary is None:
|
||||
try:
|
||||
web_summary = await self.web.summary()
|
||||
@@ -449,4 +452,18 @@ class ePIC(ePICFirmware):
|
||||
)
|
||||
return pool_data
|
||||
except LookupError:
|
||||
pass
|
||||
pass
|
||||
|
||||
async def upgrade_firmware(self, file: Path | str, keep_settings: bool = True) -> bool:
|
||||
|
||||
"""
|
||||
Upgrade the firmware of the ePIC miner device.
|
||||
|
||||
Args:
|
||||
file (Path | str): The local file path of the firmware to be uploaded.
|
||||
keep_settings (bool): Whether to keep the current settings after the update.
|
||||
|
||||
Returns:
|
||||
bool: Whether the firmware update succeeded.
|
||||
"""
|
||||
return await self.web.system_update(file=file, keep_settings=keep_settings)
|
||||
@@ -169,7 +169,7 @@ class Innosilicon(CGMiner):
|
||||
|
||||
async def _get_hashrate(
|
||||
self, rpc_summary: dict = None, web_get_all: dict = None
|
||||
) -> Optional[float]:
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_get_all:
|
||||
web_get_all = web_get_all["all"]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from typing import List, Optional
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, HashUnit
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
|
||||
from pyasic.miners.device.firmware import LuxOSFirmware
|
||||
@@ -51,6 +52,9 @@ LUXMINER_DATA_LOC = DataLocations(
|
||||
str(DataOptions.UPTIME): DataFunction(
|
||||
"_get_uptime", [RPCAPICommand("rpc_stats", "stats")]
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools", [RPCAPICommand("rpc_pools", "pools")]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -162,7 +166,7 @@ class LUXMiner(LuxOSFirmware):
|
||||
|
||||
return mac
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
rpc_summary = await self.rpc.summary()
|
||||
@@ -263,7 +267,9 @@ class LUXMiner(LuxOSFirmware):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(self, rpc_stats: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
@@ -295,3 +301,33 @@ class LUXMiner(LuxOSFirmware):
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_pools(self, rpc_pools: dict = None) -> List[PoolMetrics]:
|
||||
if rpc_pools is None:
|
||||
try:
|
||||
rpc_pools = await self.rpc.pools()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
pools_data = []
|
||||
if rpc_pools is not None:
|
||||
try:
|
||||
pools = rpc_pools.get("POOLS", [])
|
||||
for pool_info in pools:
|
||||
url = pool_info.get("URL")
|
||||
pool_url = PoolUrl.from_str(url) if url else None
|
||||
pool_data = PoolMetrics(
|
||||
accepted=pool_info.get("Accepted"),
|
||||
rejected=pool_info.get("Rejected"),
|
||||
get_failures=pool_info.get("Get Failures"),
|
||||
remote_failures=pool_info.get("Remote Failures"),
|
||||
active=pool_info.get("Stratum Active"),
|
||||
alive=pool_info.get("Status") == "Alive",
|
||||
url=pool_url,
|
||||
user=pool_info.get("User"),
|
||||
index=pool_info.get("POOL"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
|
||||
@@ -225,7 +225,7 @@ class MaraMiner(MaraFirmware):
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, web_brief: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, web_brief: dict = None) -> Optional[AlgoHashRate]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
@@ -271,7 +271,9 @@ class MaraMiner(MaraFirmware):
|
||||
pass
|
||||
return False
|
||||
|
||||
async def _get_expected_hashrate(self, web_brief: dict = None) -> Optional[float]:
|
||||
async def _get_expected_hashrate(
|
||||
self, web_brief: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_brief is None:
|
||||
try:
|
||||
web_brief = await self.web.brief()
|
||||
@@ -288,7 +290,7 @@ class MaraMiner(MaraFirmware):
|
||||
|
||||
async def _get_wattage_limit(
|
||||
self, web_miner_config: dict = None
|
||||
) -> Optional[float]:
|
||||
) -> Optional[AlgoHashRate]:
|
||||
if web_miner_config is None:
|
||||
try:
|
||||
web_miner_config = await self.web.get_miner_config()
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.miners.base import BaseMiner
|
||||
from pyasic.rpc.unknown import UnknownRPCAPI
|
||||
@@ -80,7 +80,7 @@ class UnknownMiner(BaseMiner):
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
return None
|
||||
|
||||
async def _get_hashrate(self) -> Optional[float]:
|
||||
async def _get_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
return None
|
||||
|
||||
async def _get_hashboards(self) -> List[HashBoard]:
|
||||
@@ -113,7 +113,7 @@ class UnknownMiner(BaseMiner):
|
||||
async def _get_fault_light(self) -> bool:
|
||||
return False
|
||||
|
||||
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||
async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
return None
|
||||
|
||||
async def _is_mining(self, *args, **kwargs) -> Optional[bool]:
|
||||
|
||||
@@ -193,7 +193,7 @@ class VNish(VNishFirmware, BMMiner):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[float]:
|
||||
async def _get_hashrate(self, rpc_summary: dict = None) -> Optional[AlgoHashRate]:
|
||||
# get hr from API
|
||||
if rpc_summary is None:
|
||||
try:
|
||||
|
||||
@@ -29,4 +29,4 @@ class M3X(BTMiner):
|
||||
|
||||
|
||||
class M2X(BTMiner):
|
||||
pass
|
||||
pass
|
||||
|
||||
@@ -19,7 +19,7 @@ import warnings
|
||||
from typing import List, Optional, Protocol, Tuple, Type, TypeVar, Union
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, MinerData
|
||||
from pyasic.data import AlgoHashRate, Fan, HashBoard, MinerData
|
||||
from pyasic.data.device import DeviceInfo
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.data.pools import PoolMetrics
|
||||
@@ -238,7 +238,7 @@ class MinerProtocol(Protocol):
|
||||
"""
|
||||
return await self._get_hostname()
|
||||
|
||||
async def get_hashrate(self) -> Optional[float]:
|
||||
async def get_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
"""Get the hashrate of the miner and return it as a float in TH/s.
|
||||
|
||||
Returns:
|
||||
@@ -318,7 +318,7 @@ class MinerProtocol(Protocol):
|
||||
"""
|
||||
return await self._get_fault_light()
|
||||
|
||||
async def get_expected_hashrate(self) -> Optional[float]:
|
||||
async def get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
"""Get the nominal hashrate from factory if available.
|
||||
|
||||
Returns:
|
||||
@@ -362,7 +362,7 @@ class MinerProtocol(Protocol):
|
||||
async def _get_hostname(self) -> Optional[str]:
|
||||
pass
|
||||
|
||||
async def _get_hashrate(self) -> Optional[float]:
|
||||
async def _get_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self) -> List[HashBoard]:
|
||||
@@ -392,7 +392,7 @@ class MinerProtocol(Protocol):
|
||||
async def _get_fault_light(self) -> Optional[bool]:
|
||||
pass
|
||||
|
||||
async def _get_expected_hashrate(self) -> Optional[float]:
|
||||
async def _get_expected_hashrate(self) -> Optional[AlgoHashRate]:
|
||||
pass
|
||||
|
||||
async def _is_mining(self) -> Optional[bool]:
|
||||
@@ -574,4 +574,4 @@ class BaseMiner(MinerProtocol):
|
||||
"""
|
||||
return False
|
||||
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
AnyMiner = TypeVar("AnyMiner", bound=BaseMiner)
|
||||
|
||||
1
pyasic/miners/bitaxe/__init__.py
Normal file
1
pyasic/miners/bitaxe/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .espminer import *
|
||||
6
pyasic/miners/bitaxe/espminer/BM/BM1366.py
Normal file
6
pyasic/miners/bitaxe/espminer/BM/BM1366.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.bitaxe import BitAxe
|
||||
from pyasic.miners.device.models.bitaxe import Ultra
|
||||
|
||||
|
||||
class BitAxeUltra(BitAxe, Ultra):
|
||||
pass
|
||||
6
pyasic/miners/bitaxe/espminer/BM/BM1368.py
Normal file
6
pyasic/miners/bitaxe/espminer/BM/BM1368.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.bitaxe import BitAxe
|
||||
from pyasic.miners.device.models.bitaxe import Supra
|
||||
|
||||
|
||||
class BitAxeSupra(BitAxe, Supra):
|
||||
pass
|
||||
6
pyasic/miners/bitaxe/espminer/BM/BM1397.py
Normal file
6
pyasic/miners/bitaxe/espminer/BM/BM1397.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from pyasic.miners.backends.bitaxe import BitAxe
|
||||
from pyasic.miners.device.models.bitaxe import Max
|
||||
|
||||
|
||||
class BitAxeMax(BitAxe, Max):
|
||||
pass
|
||||
3
pyasic/miners/bitaxe/espminer/BM/__init__.py
Normal file
3
pyasic/miners/bitaxe/espminer/BM/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .BM1366 import BitAxeUltra
|
||||
from .BM1368 import BitAxeSupra
|
||||
from .BM1397 import BitAxeMax
|
||||
1
pyasic/miners/bitaxe/espminer/__init__.py
Normal file
1
pyasic/miners/bitaxe/espminer/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .BM import *
|
||||
@@ -44,3 +44,7 @@ class AuradineMake(BaseMiner):
|
||||
|
||||
class ePICMake(BaseMiner):
|
||||
make = MinerMake.EPIC
|
||||
|
||||
|
||||
class BitAxeMake(BaseMiner):
|
||||
make = MinerMake.BITAXE
|
||||
|
||||
@@ -18,6 +18,6 @@ from pyasic.miners.device.makes import AntMinerMake
|
||||
|
||||
|
||||
class L3Plus(AntMinerMake):
|
||||
raw_model = MinerModel.ANTMINER
|
||||
raw_model = MinerModel.ANTMINER.L3Plus
|
||||
|
||||
expected_chips = 72
|
||||
|
||||
10
pyasic/miners/device/models/bitaxe/BM/BM1366.py
Normal file
10
pyasic/miners/device/models/bitaxe/BM/BM1366.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import BitAxeMake
|
||||
|
||||
|
||||
class Ultra(BitAxeMake):
|
||||
raw_model = MinerModel.BITAXE.BM1366
|
||||
|
||||
expected_hashboards = 1
|
||||
expected_chips = 1
|
||||
expected_fans = 1
|
||||
10
pyasic/miners/device/models/bitaxe/BM/BM1368.py
Normal file
10
pyasic/miners/device/models/bitaxe/BM/BM1368.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import BitAxeMake
|
||||
|
||||
|
||||
class Supra(BitAxeMake):
|
||||
raw_model = MinerModel.BITAXE.BM1368
|
||||
|
||||
expected_hashboards = 1
|
||||
expected_chips = 1
|
||||
expected_fans = 1
|
||||
10
pyasic/miners/device/models/bitaxe/BM/BM1397.py
Normal file
10
pyasic/miners/device/models/bitaxe/BM/BM1397.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.miners.device.makes import BitAxeMake
|
||||
|
||||
|
||||
class Max(BitAxeMake):
|
||||
raw_model = MinerModel.BITAXE.BM1397
|
||||
|
||||
expected_hashboards = 1
|
||||
expected_chips = 1
|
||||
expected_fans = 1
|
||||
3
pyasic/miners/device/models/bitaxe/BM/__init__.py
Normal file
3
pyasic/miners/device/models/bitaxe/BM/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .BM1366 import Ultra
|
||||
from .BM1368 import Supra
|
||||
from .BM1397 import Max
|
||||
1
pyasic/miners/device/models/bitaxe/__init__.py
Normal file
1
pyasic/miners/device/models/bitaxe/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .BM import *
|
||||
@@ -31,8 +31,10 @@ from pyasic.miners.antminer import *
|
||||
from pyasic.miners.auradine import *
|
||||
from pyasic.miners.avalonminer import *
|
||||
from pyasic.miners.backends import *
|
||||
from pyasic.miners.backends.bitaxe import BitAxe
|
||||
from pyasic.miners.backends.unknown import UnknownMiner
|
||||
from pyasic.miners.base import AnyMiner
|
||||
from pyasic.miners.bitaxe import *
|
||||
from pyasic.miners.blockminer import *
|
||||
from pyasic.miners.device.makes import *
|
||||
from pyasic.miners.goldshell import *
|
||||
@@ -53,6 +55,7 @@ class MinerTypes(enum.Enum):
|
||||
EPIC = 9
|
||||
AURADINE = 10
|
||||
MARATHON = 11
|
||||
BITAXE = 12
|
||||
|
||||
|
||||
MINER_CLASSES = {
|
||||
@@ -383,6 +386,7 @@ MINER_CLASSES = {
|
||||
"ANTMINER S19J PRO": VNishS19jPro,
|
||||
"ANTMINER S19A": VNishS19a,
|
||||
"ANTMINER S19A PRO": VNishS19aPro,
|
||||
"ANTMINER S19 PRO HYD.": VNishS19ProHydro,
|
||||
"ANTMINER T19": VNishT19,
|
||||
"ANTMINER S21": VNishS21,
|
||||
},
|
||||
@@ -438,6 +442,12 @@ MINER_CLASSES = {
|
||||
"ANTMINER S21": MaraS21,
|
||||
"ANTMINER T21": MaraT21,
|
||||
},
|
||||
MinerTypes.BITAXE: {
|
||||
None: BitAxe,
|
||||
"BM1368": BitAxeSupra,
|
||||
"BM1366": BitAxeUltra,
|
||||
"BM1397": BitAxeMax,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -514,6 +524,7 @@ class MinerFactory:
|
||||
MinerTypes.LUX_OS: self.get_miner_model_luxos,
|
||||
MinerTypes.AURADINE: self.get_miner_model_auradine,
|
||||
MinerTypes.MARATHON: self.get_miner_model_marathon,
|
||||
MinerTypes.BITAXE: self.get_miner_model_bitaxe,
|
||||
}
|
||||
fn = miner_model_fns.get(miner_type)
|
||||
|
||||
@@ -595,6 +606,8 @@ class MinerFactory:
|
||||
return MinerTypes.WHATSMINER
|
||||
if "Braiins OS" in web_text:
|
||||
return MinerTypes.BRAIINS_OS
|
||||
if "AxeOS" in web_text:
|
||||
return MinerTypes.BITAXE
|
||||
if "cloud-box" in web_text:
|
||||
return MinerTypes.GOLDSHELL
|
||||
if "AnthillOS" in web_text:
|
||||
@@ -1008,6 +1021,18 @@ class MinerFactory:
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
async def get_miner_model_bitaxe(self, ip: str) -> str | None:
|
||||
web_json_data = await self.send_web_command(ip, "/api/system/info")
|
||||
|
||||
try:
|
||||
miner_model = web_json_data["ASICModel"]
|
||||
if miner_model == "":
|
||||
return None
|
||||
|
||||
return miner_model
|
||||
except (TypeError, LookupError):
|
||||
pass
|
||||
|
||||
|
||||
miner_factory = MinerFactory()
|
||||
|
||||
|
||||
@@ -92,4 +92,4 @@ class BOSMinerSSH(BaseSSH):
|
||||
Returns:
|
||||
str: Status of the LED.
|
||||
"""
|
||||
return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
return await self.send_command("cat /sys/class/leds/'Red LED'/delay_off")
|
||||
|
||||
96
pyasic/web/bitaxe.py
Normal file
96
pyasic/web/bitaxe.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
from typing import Any
|
||||
|
||||
import httpx
|
||||
|
||||
from pyasic import APIError, settings
|
||||
from pyasic.web.base import BaseWebAPI
|
||||
|
||||
|
||||
class BitAxeWebAPI(BaseWebAPI):
|
||||
async def send_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}/api/{command}"
|
||||
try:
|
||||
async with httpx.AsyncClient(
|
||||
transport=settings.transport(),
|
||||
) as client:
|
||||
if parameters.get("post", False):
|
||||
parameters.pop("post")
|
||||
data = await client.post(
|
||||
url,
|
||||
timeout=settings.get("api_function_timeout", 3),
|
||||
json=parameters,
|
||||
)
|
||||
elif parameters.get("patch", False):
|
||||
parameters.pop("patch")
|
||||
data = await client.patch(
|
||||
url,
|
||||
timeout=settings.get("api_function_timeout", 3),
|
||||
json=parameters,
|
||||
)
|
||||
else:
|
||||
data = await client.get(url)
|
||||
except httpx.HTTPError:
|
||||
pass
|
||||
else:
|
||||
if data.status_code == 200:
|
||||
try:
|
||||
return data.json()
|
||||
except json.decoder.JSONDecodeError:
|
||||
pass
|
||||
|
||||
async def multicommand(
|
||||
self, *commands: str, ignore_errors: bool = False, allow_warning: bool = True
|
||||
) -> dict:
|
||||
"""Execute multiple commands simultaneously on the BitAxe miner.
|
||||
|
||||
Args:
|
||||
*commands (str): Commands to execute.
|
||||
ignore_errors (bool): Whether to ignore errors during command execution.
|
||||
allow_warning (bool): Whether to proceed despite warnings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing responses for all commands executed.
|
||||
"""
|
||||
tasks = {}
|
||||
# send all commands individually
|
||||
for cmd in commands:
|
||||
tasks[cmd] = asyncio.create_task(
|
||||
self.send_command(cmd, allow_warning=allow_warning)
|
||||
)
|
||||
|
||||
await asyncio.gather(*[tasks[cmd] for cmd in tasks], return_exceptions=True)
|
||||
|
||||
data = {"multicommand": True}
|
||||
for cmd in tasks:
|
||||
try:
|
||||
result = tasks[cmd].result()
|
||||
if result is None or result == {}:
|
||||
result = {}
|
||||
data[cmd] = result
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
return data
|
||||
|
||||
async def system_info(self):
|
||||
return await self.send_command("system/info")
|
||||
|
||||
async def swarm_info(self):
|
||||
return await self.send_command("swarm/info")
|
||||
|
||||
async def restart(self):
|
||||
return await self.send_command("system/restart", post=True)
|
||||
|
||||
async def update_settings(self, **config):
|
||||
return await self.send_command("system", patch=True, **config)
|
||||
@@ -15,9 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import aiofiles
|
||||
import httpx
|
||||
|
||||
from pyasic import settings
|
||||
@@ -46,6 +49,14 @@ class ePICWebAPI(BaseWebAPI):
|
||||
async with httpx.AsyncClient(transport=settings.transport()) as client:
|
||||
for retry_cnt in range(settings.get("get_data_retries", 1)):
|
||||
try:
|
||||
if parameters.get("form") is not None:
|
||||
form_data = parameters["form"]
|
||||
form_data.add_field("password", self.pwd)
|
||||
response = await client.post(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
timeout=5,
|
||||
data=form_data,
|
||||
)
|
||||
if post:
|
||||
response = await client.post(
|
||||
f"http://{self.ip}:{self.port}/{command}",
|
||||
@@ -135,3 +146,22 @@ class ePICWebAPI(BaseWebAPI):
|
||||
|
||||
async def capabilities(self) -> dict:
|
||||
return await self.send_command("capabilities")
|
||||
|
||||
async def system_update(self, file: Path | str, keep_settings: bool = True):
|
||||
"""Perform a system update by uploading a firmware file and sending a
|
||||
command to initiate the update."""
|
||||
|
||||
# calculate the SHA256 checksum of the firmware file
|
||||
sha256_hash = hashlib.sha256()
|
||||
async with aiofiles.open(str(file), "rb") as f:
|
||||
while chunk := await f.read(8192):
|
||||
sha256_hash.update(chunk)
|
||||
checksum = sha256_hash.hexdigest()
|
||||
|
||||
# prepare the multipart/form-data request
|
||||
form_data = aiohttp.FormData()
|
||||
form_data.add_field("checksum", checksum)
|
||||
form_data.add_field("keepsettings", str(keep_settings).lower())
|
||||
form_data.add_field("update.zip", open(file, "rb"), filename="update.zip")
|
||||
|
||||
await self.send_command("systemupdate", form=form_data)
|
||||
|
||||
Reference in New Issue
Block a user