Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
837794bd57 | ||
|
|
36d16c7235 | ||
|
|
7797023689 |
@@ -56,6 +56,16 @@ class MinerConfig(BaseModel):
|
||||
**self.temperature.as_am_modern(),
|
||||
}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Elphapex."""
|
||||
return {
|
||||
**self.fan_mode.as_elphapex(),
|
||||
"fc-freq-level": "100",
|
||||
**self.mining_mode.as_elphapex(),
|
||||
**self.pools.as_elphapex(user_suffix=user_suffix),
|
||||
**self.temperature.as_elphapex(),
|
||||
}
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for Whatsminers."""
|
||||
return {
|
||||
@@ -199,6 +209,15 @@ class MinerConfig(BaseModel):
|
||||
fan_mode=FanModeConfig.from_am_modern(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for modern Antminers."""
|
||||
return cls(
|
||||
pools=PoolConfig.from_elphapex(web_conf),
|
||||
mining_mode=MiningModeConfig.from_elphapex(web_conf),
|
||||
fan_mode=FanModeConfig.from_elphapex(web_conf),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_am_old(cls, web_conf: dict) -> "MinerConfig":
|
||||
"""Constructs a MinerConfig object from web configuration for old versions of Antminers."""
|
||||
|
||||
@@ -67,6 +67,9 @@ class MinerConfigOption(Enum):
|
||||
def as_luxos(self) -> dict:
|
||||
return self.value.as_luxos()
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return self.value.as_elphapex()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.value(*args, **kwargs)
|
||||
|
||||
@@ -131,6 +134,9 @@ class MinerConfigValue(BaseModel):
|
||||
def as_luxos(self) -> dict:
|
||||
return {}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {}
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
return getattr(self, item)
|
||||
|
||||
@@ -55,6 +55,9 @@ class FanModeNormal(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": False, "bitmain-fan-pwn": "100"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": False, "fc-fan-pwn": "100"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "auto"},
|
||||
@@ -135,6 +138,9 @@ class FanModeManual(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-fan-pwm": str(self.speed)}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"temp_control": {"mode": "manual"},
|
||||
@@ -185,6 +191,9 @@ class FanModeImmersion(MinerConfigValue):
|
||||
def as_am_modern(self) -> dict:
|
||||
return {"bitmain-fan-ctrl": True, "bitmain-fan-pwm": "0"}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"fc-fan-ctrl": True, "fc-fan-pwm": "0"}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
return {
|
||||
"fan_control": {"min_fans": 0},
|
||||
@@ -239,6 +248,20 @@ class FanModeConfig(MinerConfigOption):
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-fan-ctrl") is not None:
|
||||
fan_manual = web_conf["fc-fan-ctrl"]
|
||||
if fan_manual:
|
||||
speed = int(web_conf["fc-fan-pwm"])
|
||||
if speed == 0:
|
||||
return cls.immersion()
|
||||
return cls.manual(speed=speed)
|
||||
else:
|
||||
return cls.normal()
|
||||
else:
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
|
||||
@@ -52,6 +52,9 @@ class MiningModeNormal(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -87,6 +90,9 @@ class MiningModeSleep(MinerConfigValue):
|
||||
return {"miner-mode": "1"}
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 1}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -119,6 +125,9 @@ class MiningModeLPM(MinerConfigValue):
|
||||
return {"miner-mode": "3"}
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 3}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -141,6 +150,9 @@ class MiningModeHPM(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
return {"mode": self.mode}
|
||||
|
||||
@@ -174,6 +186,9 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_wm(self) -> dict:
|
||||
if self.power is not None:
|
||||
return {"mode": self.mode, self.mode: {"wattage": self.power}}
|
||||
@@ -273,6 +288,9 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
conf = {"enabled": True, "mode": "hashrate_target"}
|
||||
if self.hashrate is not None:
|
||||
@@ -404,6 +422,9 @@ class ManualBoardSettings(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
return {"freq": self.freq}
|
||||
|
||||
@@ -428,6 +449,9 @@ class MiningModeManual(MinerConfigValue):
|
||||
return {"miner-mode": "0"}
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_elphapex(self) -> dict:
|
||||
return {"miner-mode": 0}
|
||||
|
||||
def as_vnish(self) -> dict:
|
||||
chains = [b.as_vnish() for b in self.boards.values() if b.freq != 0]
|
||||
return {
|
||||
@@ -525,6 +549,20 @@ class MiningModeConfig(MinerConfigOption):
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict):
|
||||
if web_conf.get("fc-work-mode") is not None:
|
||||
work_mode = web_conf["fc-work-mode"]
|
||||
if work_mode == "":
|
||||
return cls.default()
|
||||
if int(work_mode) == 0:
|
||||
return cls.normal()
|
||||
elif int(work_mode) == 1:
|
||||
return cls.sleep()
|
||||
elif int(work_mode) == 3:
|
||||
return cls.low()
|
||||
return cls.default()
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict):
|
||||
try:
|
||||
|
||||
@@ -43,6 +43,13 @@ class Pool(MinerConfigValue):
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
"url": self.url,
|
||||
"user": f"{self.user}{user_suffix or ''}",
|
||||
"pass": self.password,
|
||||
}
|
||||
|
||||
def as_wm(self, idx: int = 1, user_suffix: str | None = None) -> dict:
|
||||
return {
|
||||
f"pool_{idx}": self.url,
|
||||
@@ -146,6 +153,12 @@ class Pool(MinerConfigValue):
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_pool: dict) -> "Pool":
|
||||
return cls(
|
||||
url=web_pool["url"], user=web_pool["user"], password=web_pool["pass"]
|
||||
)
|
||||
|
||||
# TODO: check if this is accurate, user/username, pass/password
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pool: dict) -> "Pool":
|
||||
@@ -235,6 +248,17 @@ class PoolGroup(MinerConfigValue):
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> list:
|
||||
pools = []
|
||||
idx = 0
|
||||
while idx < 3:
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_elphapex(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool(url="", user="", password="").as_elphapex())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
pools = {}
|
||||
idx = 0
|
||||
@@ -351,6 +375,13 @@ class PoolGroup(MinerConfigValue):
|
||||
pools.append(Pool.from_am_modern(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_pool_list: list) -> "PoolGroup":
|
||||
pools = []
|
||||
for pool in web_pool_list:
|
||||
pools.append(Pool.from_elphapex(pool))
|
||||
return cls(pools=pools)
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
|
||||
@@ -436,6 +467,11 @@ class PoolConfig(MinerConfigValue):
|
||||
return {"pools": self.groups[0].as_am_modern(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_am_modern()}
|
||||
|
||||
def as_elphapex(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_elphapex(user_suffix=user_suffix)}
|
||||
return {"pools": PoolGroup().as_elphapex()}
|
||||
|
||||
def as_wm(self, user_suffix: str | None = None) -> dict:
|
||||
if len(self.groups) > 0:
|
||||
return {"pools": self.groups[0].as_wm(user_suffix=user_suffix)}
|
||||
@@ -537,6 +573,12 @@ class PoolConfig(MinerConfigValue):
|
||||
|
||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_elphapex(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls(groups=[PoolGroup.from_elphapex(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from typing import List, Optional
|
||||
|
||||
from pyasic import APIError
|
||||
from pyasic import APIError, MinerConfig
|
||||
from pyasic.data import Fan, HashBoard, X19Error
|
||||
from pyasic.data.error_codes import MinerErrorData
|
||||
from pyasic.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.device.algorithm import AlgoHashRate
|
||||
from pyasic.miners.data import (
|
||||
DataFunction,
|
||||
@@ -74,6 +75,10 @@ ELPHAPEX_DATA_LOC = DataLocations(
|
||||
"_get_uptime",
|
||||
[WebAPICommand("web_summary", "summary")],
|
||||
),
|
||||
str(DataOptions.POOLS): DataFunction(
|
||||
"_get_pools",
|
||||
[WebAPICommand("web_pools", "pools")],
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -86,6 +91,16 @@ class ElphapexMiner(StockFirmware):
|
||||
|
||||
data_locations = ELPHAPEX_DATA_LOC
|
||||
|
||||
async def get_config(self) -> MinerConfig:
|
||||
data = await self.web.get_miner_conf()
|
||||
if data:
|
||||
self.config = MinerConfig.from_elphapex(data)
|
||||
return self.config
|
||||
|
||||
async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None:
|
||||
self.config = config
|
||||
await self.web.set_miner_conf(config.as_elphapex(user_suffix=user_suffix))
|
||||
|
||||
async def fault_light_on(self) -> bool:
|
||||
data = await self.web.blink(blink=True)
|
||||
if data:
|
||||
@@ -317,3 +332,43 @@ class ElphapexMiner(StockFirmware):
|
||||
pass
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_pools(self, web_pools: list = None) -> List[PoolMetrics]:
|
||||
if web_pools is None:
|
||||
try:
|
||||
web_pools = await self.web.pools()
|
||||
except APIError:
|
||||
return []
|
||||
|
||||
active_pool_index = None
|
||||
highest_priority = float("inf")
|
||||
|
||||
for pool_info in web_pools["POOLS"]:
|
||||
if (
|
||||
pool_info.get("status") == "Alive"
|
||||
and pool_info.get("priority", float("inf")) < highest_priority
|
||||
):
|
||||
highest_priority = pool_info["priority"]
|
||||
active_pool_index = pool_info["index"]
|
||||
|
||||
pools_data = []
|
||||
if web_pools is not None:
|
||||
try:
|
||||
for pool_info in web_pools["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("stale"),
|
||||
remote_failures=pool_info.get("discarded"),
|
||||
active=pool_info.get("index") == active_pool_index,
|
||||
alive=pool_info.get("status") == "Alive",
|
||||
url=pool_url,
|
||||
user=pool_info.get("user"),
|
||||
index=pool_info.get("index"),
|
||||
)
|
||||
pools_data.append(pool_data)
|
||||
except LookupError:
|
||||
pass
|
||||
return pools_data
|
||||
|
||||
@@ -214,3 +214,11 @@ class ElphapexWebAPI(BaseWebAPI):
|
||||
dict: A dictionary indicating whether the LED is currently blinking.
|
||||
"""
|
||||
return await self.send_command("get_blink_status")
|
||||
|
||||
async def pools(self) -> dict:
|
||||
"""Check the status of the miner's pools.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the pool status as information.
|
||||
"""
|
||||
return await self.send_command("pools")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "pyasic"
|
||||
version = "0.71.5"
|
||||
version = "0.71.6"
|
||||
|
||||
description = "A simplified and standardized interface for Bitcoin ASICs."
|
||||
authors = [{name = "UpstreamData", email = "brett@upstreamdata.ca"}]
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
from .alphapex_tests import *
|
||||
from .elphapex_tests import *
|
||||
from .hammer_tests import *
|
||||
|
||||
@@ -9,6 +9,25 @@ from pyasic.data import Fan, HashBoard
|
||||
from pyasic.device.algorithm.hashrate.unit.scrypt import ScryptUnit
|
||||
from pyasic.miners.elphapex import ElphapexDG1Plus
|
||||
|
||||
POOLS = [
|
||||
{
|
||||
"url": "stratum+tcp://stratum.pool.io:3333",
|
||||
"user": "pool_username.real_worker",
|
||||
"pwd": "123",
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://stratum.pool.io:3334",
|
||||
"user": "pool_username.real_worker",
|
||||
"pwd": "123",
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://stratum.pool.io:3335",
|
||||
"user": "pool_username.real_worker",
|
||||
"pwd": "123",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
data = {
|
||||
ElphapexDG1Plus: {
|
||||
"web_get_system_info": {
|
||||
@@ -190,6 +209,81 @@ data = {
|
||||
"fc-work-mode": 0,
|
||||
"fc-freq": "1850",
|
||||
},
|
||||
"web_pools": {
|
||||
"STATUS": {
|
||||
"STATUS": "S",
|
||||
"when": 5411762,
|
||||
"timestamp": 1738768594,
|
||||
"api_version": "1.0.0",
|
||||
"Msg": "pools",
|
||||
},
|
||||
"Device Total Rejected": 8888,
|
||||
"POOLS": [
|
||||
{
|
||||
"diffs": 0,
|
||||
"diffr": 524288,
|
||||
"index": 0,
|
||||
"user": "pool_username.real_worker",
|
||||
"lsdiff": 524288,
|
||||
"lstime": "00:00:18",
|
||||
"diffa": 524288,
|
||||
"accepted": 798704,
|
||||
"diff1": 0,
|
||||
"stale": 0,
|
||||
"diff": "",
|
||||
"rejected": 3320,
|
||||
"status": "Unreachable",
|
||||
"getworks": 802024,
|
||||
"priority": 0,
|
||||
"url": "stratum+tcp://stratum.pool.io:3333",
|
||||
},
|
||||
{
|
||||
"diffs": 0,
|
||||
"diffr": 524288,
|
||||
"index": 1,
|
||||
"user": "pool_username.real_worker",
|
||||
"lsdiff": 524288,
|
||||
"lstime": "00:00:00",
|
||||
"diffa": 524288,
|
||||
"accepted": 604803,
|
||||
"diff1": 0,
|
||||
"stale": 0,
|
||||
"diff": "",
|
||||
"rejected": 2492,
|
||||
"status": "Alive",
|
||||
"getworks": 607295,
|
||||
"priority": 1,
|
||||
"url": "stratum+tcp://stratum.pool.io:3334",
|
||||
},
|
||||
{
|
||||
"diffs": 0,
|
||||
"diffr": 524288,
|
||||
"index": 2,
|
||||
"user": "pool_username.real_worker",
|
||||
"lsdiff": 524288,
|
||||
"lstime": "00:00:05",
|
||||
"diffa": 524288,
|
||||
"accepted": 691522,
|
||||
"diff1": 0,
|
||||
"stale": 0,
|
||||
"diff": "",
|
||||
"rejected": 3076,
|
||||
"status": "Unreachable",
|
||||
"getworks": 694598,
|
||||
"priority": 2,
|
||||
"url": "stratum+tcp://stratum.pool.io:3335",
|
||||
},
|
||||
],
|
||||
"Device Rejected%": 0.41999999999999998,
|
||||
"Device Total Work": 2103917,
|
||||
"INFO": {
|
||||
"miner_version": "DG1+_SW_V1.0.2",
|
||||
"CompileTime": "",
|
||||
"dev_sn": "28HY245192N000245C23B",
|
||||
"type": "DG1+",
|
||||
"hw_version": "DG1+_HW_V1.0",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -240,3 +334,6 @@ class TestElphapexMiners(unittest.IsolatedAsyncioTestCase):
|
||||
[Fan(speed=5340), Fan(speed=5400), Fan(speed=5400), Fan(speed=5400)],
|
||||
)
|
||||
self.assertEqual(result.total_chips, result.expected_chips)
|
||||
self.assertEqual(
|
||||
set([str(p.url) for p in result.pools]), set(p["url"] for p in POOLS)
|
||||
)
|
||||
@@ -16,12 +16,12 @@ POOLS = [
|
||||
"pwd": "123",
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://stratum.pool.io:3333",
|
||||
"url": "stratum+tcp://stratum.pool.io:3334",
|
||||
"user": "pool_username.real_worker",
|
||||
"pwd": "123",
|
||||
},
|
||||
{
|
||||
"url": "stratum+tcp://stratum.pool.io:3333",
|
||||
"url": "stratum+tcp://stratum.pool.io:3335",
|
||||
"user": "pool_username.real_worker",
|
||||
"pwd": "123",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user