Compare commits

..

13 Commits

Author SHA1 Message Date
Upstream Data
d39d278296 version: bump version number 2024-12-10 10:51:58 -07:00
Upstream Data
ea8b922367 bug: fix hive default password 2024-12-10 10:51:46 -07:00
Upstream Data
0d1c8d80e0 version: bump version number 2024-12-10 10:46:44 -07:00
Upstream Data
494d25da97 feature: add support for hiveon S19 2024-12-10 10:46:23 -07:00
Upstream Data
0327d93a35 version: bump version number 2024-12-10 10:29:18 -07:00
Upstream Data
680584c468 bug: fix pydantic validation error with hashrate tuning 2024-12-10 10:28:55 -07:00
Upstream Data
c0dbafb198 version: bump version number 2024-12-10 09:21:34 -07:00
Upstream Data
97d2c4ac34 feature: add more hiveon functionality 2024-12-10 09:21:11 -07:00
Upstream Data
055d633c91 bug: fix hiveon identification in some cases 2024-12-10 08:43:39 -07:00
Upstream Data
68c57f265f feature: add supports presets flag 2024-12-10 07:44:34 -07:00
Upstream Data
1ba0f8ed83 feature: parse vnish presets 2024-12-10 07:44:34 -07:00
Upstream Data
7f74b083d3 feature: add mining mode preset option to config 2024-12-10 07:44:34 -07:00
Wilfred Allyn
97c20dae0a bug: fix boser rpc 2024-12-08 08:54:31 -07:00
14 changed files with 300 additions and 39 deletions

View File

@@ -245,13 +245,13 @@ class MinerConfig(BaseModel):
) )
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "MinerConfig": def from_vnish(cls, web_settings: dict, web_presets: list[dict]) -> "MinerConfig":
"""Constructs a MinerConfig object from web settings for VNish miners.""" """Constructs a MinerConfig object from web settings for VNish miners."""
return cls( return cls(
pools=PoolConfig.from_vnish(web_settings), pools=PoolConfig.from_vnish(web_settings),
fan_mode=FanModeConfig.from_vnish(web_settings), fan_mode=FanModeConfig.from_vnish(web_settings),
temperature=TemperatureConfig.from_vnish(web_settings), temperature=TemperatureConfig.from_vnish(web_settings),
mining_mode=MiningModeConfig.from_vnish(web_settings), mining_mode=MiningModeConfig.from_vnish(web_settings, web_presets),
) )
@classmethod @classmethod
@@ -299,3 +299,7 @@ class MinerConfig(BaseModel):
@classmethod @classmethod
def from_hammer(cls, *args, **kwargs) -> "MinerConfig": def from_hammer(cls, *args, **kwargs) -> "MinerConfig":
return cls.from_am_modern(*args, **kwargs) return cls.from_am_modern(*args, **kwargs)
@classmethod
def from_hiveon_modern(cls, web_conf: dict) -> "MinerConfig":
return cls.from_am_modern(web_conf)

View File

@@ -36,6 +36,7 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
) )
from .algo import TunerAlgo, TunerAlgoType from .algo import TunerAlgo, TunerAlgoType
from .presets import MiningPreset
from .scaling import ScalingConfig from .scaling import ScalingConfig
@@ -251,9 +252,9 @@ class MiningModeHashrateTune(MinerConfigValue):
arbitrary_types_allowed = True arbitrary_types_allowed = True
mode: str = field(init=False, default="hashrate_tuning") mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None hashrate: int | None = None
algo: TunerAlgoType = field(default_factory=TunerAlgo.default) algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
scaling: ScalingConfig = None scaling: ScalingConfig | None = None
@classmethod @classmethod
def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune": def from_dict(cls, dict_conf: dict | None) -> "MiningModeHashrateTune":
@@ -344,6 +345,29 @@ class MiningModeHashrateTune(MinerConfigValue):
return {"autotunerset": {"enabled": True}} return {"autotunerset": {"enabled": True}}
class MiningModePreset(MinerConfigValue):
mode: str = field(init=False, default="preset")
active_preset: MiningPreset
available_presets: list[MiningPreset] = field(default_factory=list)
def as_vnish(self) -> dict:
return {"overclock": {**self.active_preset.as_vnish()}}
@classmethod
def from_vnish(
cls, web_overclock_settings: dict, web_presets: list[dict]
) -> "MiningModePreset":
active_preset = None
for preset in web_presets:
if preset["name"] == web_overclock_settings["preset"]:
active_preset = preset
return cls(
active_preset=MiningPreset.from_vnish(active_preset),
available_presets=[MiningPreset.from_vnish(p) for p in web_presets],
)
class ManualBoardSettings(MinerConfigValue): class ManualBoardSettings(MinerConfigValue):
freq: float freq: float
volt: float volt: float
@@ -444,6 +468,7 @@ class MiningModeConfig(MinerConfigOption):
sleep = MiningModeSleep sleep = MiningModeSleep
power_tuning = MiningModePowerTune power_tuning = MiningModePowerTune
hashrate_tuning = MiningModeHashrateTune hashrate_tuning = MiningModeHashrateTune
preset = MiningModePreset
manual = MiningModeManual manual = MiningModeManual
@classmethod @classmethod
@@ -561,7 +586,7 @@ class MiningModeConfig(MinerConfigOption):
) )
@classmethod @classmethod
def from_vnish(cls, web_settings: dict): def from_vnish(cls, web_settings: dict, web_presets: list[dict]):
try: try:
mode_settings = web_settings["miner"]["overclock"] mode_settings = web_settings["miner"]["overclock"]
except KeyError: except KeyError:
@@ -570,7 +595,7 @@ class MiningModeConfig(MinerConfigOption):
if mode_settings["preset"] == "disabled": if mode_settings["preset"] == "disabled":
return MiningModeManual.from_vnish(mode_settings) return MiningModeManual.from_vnish(mode_settings)
else: else:
return cls.power_tuning(power=int(mode_settings["preset"])) return MiningModePreset.from_vnish(mode_settings, web_presets)
@classmethod @classmethod
def from_boser(cls, grpc_miner_conf: dict): def from_boser(cls, grpc_miner_conf: dict):
@@ -673,5 +698,6 @@ MiningMode = TypeVar(
MiningModeManual, MiningModeManual,
MiningModePowerTune, MiningModePowerTune,
MiningModeHashrateTune, MiningModeHashrateTune,
MiningModePreset,
], ],
) )

View File

@@ -0,0 +1,34 @@
from pyasic.config.base import MinerConfigValue
class MiningPreset(MinerConfigValue):
name: str | None = None
power: int | None = None
hashrate: int | None = None
tuned: bool | None = None
modded_psu: bool = False
def as_vnish(self) -> dict:
if self.name is not None:
return {"preset": self.name}
return {}
@classmethod
def from_vnish(cls, web_preset: dict):
name = web_preset["name"]
hr_power_split = web_preset["pretty"].split("~")
if len(hr_power_split) == 1:
power = None
hashrate = None
else:
power = hr_power_split[0].replace("watt", "").strip()
hashrate = hr_power_split[1].replace("TH", "").strip()
tuned = web_preset["status"] == "tuned"
modded_psu = web_preset["modded_psu_required"]
return cls(
name=name,
power=power,
hashrate=hashrate,
tuned=tuned,
modded_psu=modded_psu,
)

View File

@@ -14,7 +14,7 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import HiveonModern
from pyasic.miners.device.models import ( from pyasic.miners.device.models import (
S19, S19,
S19L, S19L,
@@ -35,65 +35,65 @@ from pyasic.miners.device.models import (
) )
class HiveonS19(Hiveon, S19): class HiveonS19(HiveonModern, S19):
pass pass
class HiveonS19Plus(Hiveon, S19Plus): class HiveonS19Plus(HiveonModern, S19Plus):
pass pass
class HiveonS19i(Hiveon, S19i): class HiveonS19i(HiveonModern, S19i):
pass pass
class HiveonS19Pro(Hiveon, S19Pro): class HiveonS19Pro(HiveonModern, S19Pro):
pass pass
class HiveonS19ProPlus(Hiveon, S19ProPlus): class HiveonS19ProPlus(HiveonModern, S19ProPlus):
pass pass
class HiveonS19XP(Hiveon, S19XP): class HiveonS19XP(HiveonModern, S19XP):
pass pass
class HiveonS19a(Hiveon, S19a): class HiveonS19a(HiveonModern, S19a):
pass pass
class HiveonS19aPro(Hiveon, S19aPro): class HiveonS19aPro(HiveonModern, S19aPro):
pass pass
class HiveonS19j(Hiveon, S19j): class HiveonS19j(HiveonModern, S19j):
pass pass
class HiveonS19jNoPIC(Hiveon, S19jNoPIC): class HiveonS19jNoPIC(HiveonModern, S19jNoPIC):
pass pass
class HiveonS19jPro(Hiveon, S19jPro): class HiveonS19jPro(HiveonModern, S19jPro):
pass pass
class HiveonS19L(Hiveon, S19L): class HiveonS19L(HiveonModern, S19L):
pass pass
class HiveonS19ProHydro(Hiveon, S19ProHydro): class HiveonS19ProHydro(HiveonModern, S19ProHydro):
pass pass
class HiveonS19Hydro(Hiveon, S19Hydro): class HiveonS19Hydro(HiveonModern, S19Hydro):
pass pass
class HiveonS19ProPlusHydro(Hiveon, S19ProPlusHydro): class HiveonS19ProPlusHydro(HiveonModern, S19ProPlusHydro):
pass pass
class HiveonS19KPro(Hiveon, S19KPro): class HiveonS19KPro(HiveonModern, S19KPro):
pass pass

View File

@@ -14,9 +14,9 @@
# limitations under the License. - # limitations under the License. -
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import HiveonModern
from pyasic.miners.device.models import T19 from pyasic.miners.device.models import T19
class HiveonT19(Hiveon, T19): class HiveonT19(HiveonModern, T19):
pass pass

View File

@@ -21,7 +21,7 @@ import asyncssh
from pyasic.data import HashBoard from pyasic.data import HashBoard
from pyasic.device.algorithm import AlgoHashRate, HashUnit from pyasic.device.algorithm import AlgoHashRate, HashUnit
from pyasic.errors import APIError from pyasic.errors import APIError
from pyasic.miners.backends import Hiveon from pyasic.miners.backends import HiveonOld
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand
from pyasic.miners.device.models import T9 from pyasic.miners.device.models import T9
@@ -71,7 +71,7 @@ HIVEON_T9_DATA_LOC = DataLocations(
) )
class HiveonT9(Hiveon, T9): class HiveonT9(HiveonOld, T9):
data_locations = HIVEON_T9_DATA_LOC data_locations = HIVEON_T9_DATA_LOC
################################################## ##################################################

View File

@@ -25,7 +25,7 @@ from .cgminer import CGMiner
from .epic import ePIC from .epic import ePIC
from .goldshell import GoldshellMiner from .goldshell import GoldshellMiner
from .hammer import BlackMiner from .hammer import BlackMiner
from .hiveon import Hiveon from .hiveon import HiveonModern, HiveonOld
from .iceriver import IceRiver from .iceriver import IceRiver
from .innosilicon import Innosilicon from .innosilicon import Innosilicon
from .luxminer import LUXMiner from .luxminer import LUXMiner

View File

@@ -746,7 +746,7 @@ class BOSer(BraiinsOSFirmware):
"""Handler for new versions of BraiinsOS+ (post-gRPC)""" """Handler for new versions of BraiinsOS+ (post-gRPC)"""
_rpc_cls = BOSMinerRPCAPI _rpc_cls = BOSMinerRPCAPI
web: BOSMinerRPCAPI rpc: BOSMinerRPCAPI
_web_cls = BOSerWebAPI _web_cls = BOSerWebAPI
web: BOSerWebAPI web: BOSerWebAPI

View File

@@ -16,13 +16,28 @@
from typing import Optional from typing import Optional
from pyasic import APIError from pyasic import APIError
from pyasic.config import MinerConfig, MiningModeConfig
from pyasic.miners.backends import BMMiner from pyasic.miners.backends import BMMiner
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, RPCAPICommand from pyasic.miners.data import (
DataFunction,
DataLocations,
DataOptions,
RPCAPICommand,
WebAPICommand,
)
from pyasic.miners.device.firmware import HiveonFirmware from pyasic.miners.device.firmware import HiveonFirmware
from pyasic.web.hiveon import HiveonWebAPI from pyasic.web.hiveon import HiveonWebAPI
HIVEON_DATA_LOC = DataLocations( HIVEON_MODERN_DATA_LOC = DataLocations(
**{ **{
str(DataOptions.MAC): DataFunction(
"_get_mac",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.HOSTNAME): DataFunction(
"_get_hostname",
[WebAPICommand("web_get_system_info", "get_system_info")],
),
str(DataOptions.API_VERSION): DataFunction( str(DataOptions.API_VERSION): DataFunction(
"_get_api_ver", "_get_api_ver",
[RPCAPICommand("rpc_version", "version")], [RPCAPICommand("rpc_version", "version")],
@@ -59,16 +74,194 @@ HIVEON_DATA_LOC = DataLocations(
"_get_pools", "_get_pools",
[RPCAPICommand("rpc_pools", "pools")], [RPCAPICommand("rpc_pools", "pools")],
), ),
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")],
),
} }
) )
class Hiveon(HiveonFirmware, BMMiner): class HiveonModern(HiveonFirmware, BMMiner):
data_locations = HIVEON_DATA_LOC data_locations = HIVEON_MODERN_DATA_LOC
web: HiveonWebAPI web: HiveonWebAPI
_web_cls = HiveonWebAPI _web_cls = HiveonWebAPI
async def get_config(self) -> MinerConfig:
data = await self.web.get_miner_conf()
if data:
self.config = MinerConfig.from_hiveon_modern(data)
return self.config
async def fault_light_on(self) -> bool:
data = await self.web.blink(blink=True)
if data:
if data.get("code") == "B000":
self.light = True
return self.light
async def fault_light_off(self) -> bool:
data = await self.web.blink(blink=False)
if data:
if data.get("code") == "B100":
self.light = False
return self.light
async def reboot(self) -> bool:
data = await self.web.reboot()
if data:
return True
return False
async def stop_mining(self) -> bool:
cfg = await self.get_config()
cfg.miner_mode = MiningModeConfig.sleep()
await self.send_config(cfg)
return True
async def resume_mining(self) -> bool:
cfg = await self.get_config()
cfg.miner_mode = MiningModeConfig.normal()
await self.send_config(cfg)
return True
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if not rpc_stats:
try:
rpc_stats = await self.rpc.stats()
except APIError:
pass
if rpc_stats:
boards = rpc_stats.get("STATS")
try:
wattage_raw = boards[1]["chain_power"]
except (KeyError, IndexError):
pass
else:
# parse wattage position out of raw data
return round(float(wattage_raw.split(" ")[0]))
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()
except APIError:
pass
if web_get_system_info is not None:
try:
return web_get_system_info["hostname"]
except KeyError:
pass
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()
except APIError:
pass
if web_get_system_info is not None:
try:
return web_get_system_info["macaddr"]
except KeyError:
pass
try:
data = await self.web.get_network_info()
if data:
return data["macaddr"]
except KeyError:
pass
async def _get_fault_light(
self, web_get_blink_status: dict = None
) -> Optional[bool]:
if self.light:
return self.light
if web_get_blink_status is None:
try:
web_get_blink_status = await self.web.get_blink_status()
except APIError:
pass
if web_get_blink_status is not None:
try:
self.light = web_get_blink_status["blink"]
except KeyError:
pass
return self.light
async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]:
if web_get_conf is None:
try:
web_get_conf = await self.web.get_miner_conf()
except APIError:
pass
if web_get_conf is not None:
try:
if str(web_get_conf["bitmain-work-mode"]).isdigit():
return (
False if int(web_get_conf["bitmain-work-mode"]) == 1 else True
)
return False
except LookupError:
pass
HIVEON_OLD_DATA_LOC = DataLocations(
**{
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.HASHRATE): DataFunction(
"_get_hashrate",
[RPCAPICommand("rpc_summary", "summary")],
),
str(DataOptions.EXPECTED_HASHRATE): DataFunction(
"_get_expected_hashrate",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.HASHBOARDS): DataFunction(
"_get_hashboards",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.FANS): DataFunction(
"_get_fans",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.UPTIME): DataFunction(
"_get_uptime",
[RPCAPICommand("rpc_stats", "stats")],
),
str(DataOptions.POOLS): DataFunction(
"_get_pools",
[RPCAPICommand("rpc_pools", "pools")],
),
str(DataOptions.WATTAGE): DataFunction(
"_get_wattage",
[RPCAPICommand("rpc_stats", "stats")],
),
}
)
class HiveonOld(HiveonFirmware, BMMiner):
data_locations = HIVEON_OLD_DATA_LOC
async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]: async def _get_wattage(self, rpc_stats: dict = None) -> Optional[int]:
if not rpc_stats: if not rpc_stats:
try: try:

View File

@@ -95,6 +95,7 @@ class VNish(VNishFirmware, BMMiner):
web: VNishWebAPI web: VNishWebAPI
supports_shutdown = True supports_shutdown = True
supports_presets = True
data_locations = VNISH_DATA_LOC data_locations = VNISH_DATA_LOC
@@ -266,7 +267,8 @@ class VNish(VNishFirmware, BMMiner):
async def get_config(self) -> MinerConfig: async def get_config(self) -> MinerConfig:
try: try:
web_settings = await self.web.settings() web_settings = await self.web.settings()
web_presets = await self.web.autotune_presets()
except APIError: except APIError:
return self.config return self.config
self.config = MinerConfig.from_vnish(web_settings) self.config = MinerConfig.from_vnish(web_settings, web_presets)
return self.config return self.config

View File

@@ -56,6 +56,7 @@ class MinerProtocol(Protocol):
supports_shutdown: bool = False supports_shutdown: bool = False
supports_power_modes: bool = False supports_power_modes: bool = False
supports_presets: bool = False
supports_autotuning: bool = False supports_autotuning: bool = False
api_ver: str = None api_ver: str = None

View File

@@ -424,9 +424,10 @@ MINER_CLASSES = {
"BLOCKMINER 720I": ePICBlockMiner720i, "BLOCKMINER 720I": ePICBlockMiner720i,
}, },
MinerTypes.HIVEON: { MinerTypes.HIVEON: {
None: Hiveon, None: HiveonModern,
"ANTMINER T9": HiveonT9, "ANTMINER T9": HiveonT9,
"ANTMINER S19JPRO": HiveonS19jPro, "ANTMINER S19JPRO": HiveonS19jPro,
"ANTMINER S19": HiveonS19,
}, },
MinerTypes.LUX_OS: { MinerTypes.LUX_OS: {
None: LUXMiner, None: LUXMiner,
@@ -864,7 +865,7 @@ class MinerFactory:
miner_type: MinerTypes | None, miner_type: MinerTypes | None,
) -> AnyMiner | None: ) -> AnyMiner | None:
# special case since hiveon miners return web results copying the antminer stock FW # special case since hiveon miners return web results copying the antminer stock FW
if "HIVEON" in str(miner_model).upper() and miner_type == MinerTypes.ANTMINER: if "HIVEON" in str(miner_model).upper():
miner_model = str(miner_model).upper().replace(" HIVEON", "") miner_model = str(miner_model).upper().replace(" HIVEON", "")
miner_type = MinerTypes.HIVEON miner_type = MinerTypes.HIVEON
try: try:
@@ -873,7 +874,7 @@ class MinerFactory:
if miner_type in MINER_CLASSES: if miner_type in MINER_CLASSES:
if miner_model is not None: if miner_model is not None:
warnings.warn( warnings.warn(
f"Partially supported miner found: {miner_model}, please open an issue with miner data " f"Partially supported miner found: {miner_model}, type: {miner_type}, please open an issue with miner data "
f"and this model on GitHub (https://github.com/UpstreamData/pyasic/issues)." f"and this model on GitHub (https://github.com/UpstreamData/pyasic/issues)."
) )
return MINER_CLASSES[miner_type][None](ip) return MINER_CLASSES[miner_type][None](ip)

View File

@@ -37,7 +37,7 @@ _settings = { # defaults
"default_goldshell_web_password": "123456789", "default_goldshell_web_password": "123456789",
"default_auradine_web_password": "admin", "default_auradine_web_password": "admin",
"default_epic_web_password": "letmein", "default_epic_web_password": "letmein",
"default_hive_web_password": "admin", "default_hive_web_password": "root",
"default_iceriver_web_password": "12345678", "default_iceriver_web_password": "12345678",
"default_antminer_ssh_password": "miner", "default_antminer_ssh_password": "miner",
"default_bosminer_ssh_password": "root", "default_bosminer_ssh_password": "root",

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "pyasic" name = "pyasic"
version = "0.64.14" version = "0.65.3"
description = "A simplified and standardized interface for Bitcoin ASICs." description = "A simplified and standardized interface for Bitcoin ASICs."
authors = ["UpstreamData <brett@upstreamdata.ca>"] authors = ["UpstreamData <brett@upstreamdata.ca>"]
repository = "https://github.com/UpstreamData/pyasic" repository = "https://github.com/UpstreamData/pyasic"