feature: swap from @dataclass to pydantic BaseModel (#238)
* feature: switch almost everything to pydantic BaseModel * feature: swap more dataclasses over to pydantic models * feature: use more model serializers to make data handle better * bug: fix some serialization issues with `None` values * bug: fix some initialization problems with miner mode config * bug: fix new BOS+ pool parsing * bug: fix new BOS+ board temperature parsing serialization error * bug: more misc fixes with serialization, extra methods, and hashrate types * bug: add explicit type conversions to hashrate types * bug: fix epic pool URL parsing * bug: fix positional args in hashboards * bug: fix epic missing url scheme * convert board temp to int --------- Co-authored-by: Upstream Data <brett@upstreamdata.ca> Co-authored-by: John-Paul Compagnone <jpcompagnone@epicblockchain.io>
This commit is contained in:
@@ -13,25 +13,25 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from dataclasses import asdict, dataclass, field
|
||||
|
||||
from pyasic.config.fans import FanModeConfig
|
||||
from pyasic.config.mining import MiningModeConfig
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from pyasic.config.fans import FanMode, FanModeConfig
|
||||
from pyasic.config.mining import MiningMode, MiningModeConfig
|
||||
from pyasic.config.mining.scaling import ScalingConfig
|
||||
from pyasic.config.pools import PoolConfig
|
||||
from pyasic.config.temperature import TemperatureConfig
|
||||
from pyasic.misc import merge_dicts
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfig:
|
||||
class MinerConfig(BaseModel):
|
||||
"""Represents the configuration for a miner including pool configuration,
|
||||
fan mode, temperature settings, mining mode, and power scaling."""
|
||||
|
||||
pools: PoolConfig = field(default_factory=PoolConfig.default)
|
||||
fan_mode: FanModeConfig = field(default_factory=FanModeConfig.default)
|
||||
temperature: TemperatureConfig = field(default_factory=TemperatureConfig.default)
|
||||
mining_mode: MiningModeConfig = field(default_factory=MiningModeConfig.default)
|
||||
pools: PoolConfig = Field(default_factory=PoolConfig.default)
|
||||
fan_mode: FanMode = Field(default_factory=FanModeConfig.default)
|
||||
temperature: TemperatureConfig = Field(default_factory=TemperatureConfig.default)
|
||||
mining_mode: MiningMode = Field(default_factory=MiningModeConfig.default)
|
||||
|
||||
def __getitem__(self, item):
|
||||
try:
|
||||
@@ -41,7 +41,7 @@ class MinerConfig:
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Converts the MinerConfig object to a dictionary."""
|
||||
return asdict(self)
|
||||
return self.model_dump()
|
||||
|
||||
def as_am_modern(self, user_suffix: str = None) -> dict:
|
||||
"""Generates the configuration in the format suitable for modern Antminers."""
|
||||
@@ -107,7 +107,7 @@ class MinerConfig:
|
||||
}
|
||||
|
||||
def as_boser(self, user_suffix: str = None) -> dict:
|
||||
""" "Generates the configuration in the format suitable for BOSer."""
|
||||
"""Generates the configuration in the format suitable for BOSer."""
|
||||
return {
|
||||
**self.fan_mode.as_boser(),
|
||||
**self.temperature.as_boser(),
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import asdict, dataclass
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class MinerConfigOption(Enum):
|
||||
@classmethod
|
||||
@@ -80,14 +81,13 @@ class MinerConfigOption(Enum):
|
||||
raise KeyError
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerConfigValue:
|
||||
class MinerConfigValue(BaseModel):
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
return cls()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
return asdict(self)
|
||||
return self.model_dump()
|
||||
|
||||
def as_am_modern(self) -> dict:
|
||||
return {}
|
||||
|
||||
@@ -15,14 +15,15 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
mode: str = Field(init=False, default="normal")
|
||||
minimum_fans: int = 1
|
||||
minimum_speed: int = 0
|
||||
|
||||
@@ -87,9 +88,8 @@ class FanModeNormal(MinerConfigValue):
|
||||
return {"fanset": {"speed": -1, "min_fans": self.minimum_fans}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeManual(MinerConfigValue):
|
||||
mode: str = field(init=False, default="manual")
|
||||
mode: str = Field(init=False, default="manual")
|
||||
speed: int = 100
|
||||
minimum_fans: int = 1
|
||||
|
||||
@@ -151,9 +151,8 @@ class FanModeManual(MinerConfigValue):
|
||||
return {"fanset": {"speed": self.speed, "min_fans": self.minimum_fans}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class FanModeImmersion(MinerConfigValue):
|
||||
mode: str = field(init=False, default="immersion")
|
||||
mode: str = Field(init=False, default="immersion")
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "FanModeImmersion":
|
||||
@@ -273,7 +272,7 @@ class FanModeConfig(MinerConfigOption):
|
||||
keys = temperature_conf.keys()
|
||||
if "auto" in keys:
|
||||
if "minimumRequiredFans" in keys:
|
||||
return cls.normal(temperature_conf["minimumRequiredFans"])
|
||||
return cls.normal(minimum_fans=temperature_conf["minimumRequiredFans"])
|
||||
return cls.normal()
|
||||
if "manual" in keys:
|
||||
conf = {}
|
||||
@@ -300,7 +299,9 @@ class FanModeConfig(MinerConfigOption):
|
||||
mode = web_config["general-config"]["environment-profile"]
|
||||
if mode == "AirCooling":
|
||||
if web_config["advance-config"]["override-fan-control"]:
|
||||
return cls.manual(web_config["advance-config"]["fan-fixed-percent"])
|
||||
return cls.manual(
|
||||
speed=web_config["advance-config"]["fan-fixed-percent"]
|
||||
)
|
||||
return cls.normal()
|
||||
return cls.immersion()
|
||||
except LookupError:
|
||||
@@ -333,3 +334,6 @@ class FanModeConfig(MinerConfigOption):
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
|
||||
FanMode = TypeVar("FanMode", bound=Union[*[v.value for v in FanModeConfig]])
|
||||
|
||||
@@ -15,7 +15,8 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pyasic import settings
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
@@ -34,11 +35,10 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
TunerPerformanceMode,
|
||||
)
|
||||
|
||||
from .algo import TunerAlgo
|
||||
from .algo import TunerAlgo, TunerAlgoType
|
||||
from .scaling import ScalingConfig
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeNormal(MinerConfigValue):
|
||||
mode: str = field(init=False, default="normal")
|
||||
|
||||
@@ -74,7 +74,6 @@ class MiningModeNormal(MinerConfigValue):
|
||||
return {"autotunerset": {"enabled": False}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeSleep(MinerConfigValue):
|
||||
mode: str = field(init=False, default="sleep")
|
||||
|
||||
@@ -107,7 +106,6 @@ class MiningModeSleep(MinerConfigValue):
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeLPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="low")
|
||||
|
||||
@@ -130,7 +128,6 @@ class MiningModeLPM(MinerConfigValue):
|
||||
return {"settings": {"level": 1}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHPM(MinerConfigValue):
|
||||
mode: str = field(init=False, default="high")
|
||||
|
||||
@@ -150,12 +147,11 @@ class MiningModeHPM(MinerConfigValue):
|
||||
return {"mode": {"mode": "turbo"}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModePowerTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="power_tuning")
|
||||
power: int = None
|
||||
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||
scaling: ScalingConfig = None
|
||||
power: int | None = None
|
||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
||||
scaling: ScalingConfig | None = None
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None) -> "MiningModePowerTune":
|
||||
@@ -247,11 +243,10 @@ class MiningModePowerTune(MinerConfigValue):
|
||||
return {"autotunerset": {"enabled": True}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeHashrateTune(MinerConfigValue):
|
||||
mode: str = field(init=False, default="hashrate_tuning")
|
||||
hashrate: int = None
|
||||
algo: TunerAlgo = field(default_factory=TunerAlgo.default)
|
||||
algo: TunerAlgoType = field(default_factory=TunerAlgo.default)
|
||||
scaling: ScalingConfig = None
|
||||
|
||||
@classmethod
|
||||
@@ -343,7 +338,6 @@ class MiningModeHashrateTune(MinerConfigValue):
|
||||
return {"autotunerset": {"enabled": True}}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ManualBoardSettings(MinerConfigValue):
|
||||
freq: float
|
||||
volt: float
|
||||
@@ -358,7 +352,6 @@ class ManualBoardSettings(MinerConfigValue):
|
||||
return {"miner-mode": 0}
|
||||
|
||||
|
||||
@dataclass
|
||||
class MiningModeManual(MinerConfigValue):
|
||||
mode: str = field(init=False, default="manual")
|
||||
|
||||
@@ -521,7 +514,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if autotuning_conf.get("psu_power_limit") is not None:
|
||||
# old autotuning conf
|
||||
return cls.power_tuning(
|
||||
autotuning_conf["psu_power_limit"],
|
||||
power=autotuning_conf["psu_power_limit"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
|
||||
)
|
||||
if autotuning_conf.get("mode") is not None:
|
||||
@@ -530,7 +523,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if mode == "power_target":
|
||||
if autotuning_conf.get("power_target") is not None:
|
||||
return cls.power_tuning(
|
||||
autotuning_conf["power_target"],
|
||||
power=autotuning_conf["power_target"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="power"),
|
||||
)
|
||||
return cls.power_tuning(
|
||||
@@ -539,7 +532,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if mode == "hashrate_target":
|
||||
if autotuning_conf.get("hashrate_target") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
autotuning_conf["hashrate_target"],
|
||||
hashrate=autotuning_conf["hashrate_target"],
|
||||
scaling=ScalingConfig.from_bosminer(toml_conf, mode="hashrate"),
|
||||
)
|
||||
return cls.hashrate_tuning(
|
||||
@@ -556,7 +549,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if mode_settings["preset"] == "disabled":
|
||||
return MiningModeManual.from_vnish(mode_settings)
|
||||
else:
|
||||
return cls.power_tuning(int(mode_settings["preset"]))
|
||||
return cls.power_tuning(power=int(mode_settings["preset"]))
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_miner_conf: dict):
|
||||
@@ -571,7 +564,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if tuner_conf["tunerMode"] == 1:
|
||||
if tuner_conf.get("powerTarget") is not None:
|
||||
return cls.power_tuning(
|
||||
tuner_conf["powerTarget"]["watt"],
|
||||
power=tuner_conf["powerTarget"]["watt"],
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
|
||||
)
|
||||
return cls.power_tuning(
|
||||
@@ -581,7 +574,7 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if tuner_conf["tunerMode"] == 2:
|
||||
if tuner_conf.get("hashrateTarget") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
scaling=ScalingConfig.from_boser(
|
||||
grpc_miner_conf, mode="hashrate"
|
||||
),
|
||||
@@ -592,13 +585,13 @@ class MiningModeConfig(MinerConfigOption):
|
||||
|
||||
if tuner_conf.get("powerTarget") is not None:
|
||||
return cls.power_tuning(
|
||||
tuner_conf["powerTarget"]["watt"],
|
||||
power=tuner_conf["powerTarget"]["watt"],
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="power"),
|
||||
)
|
||||
|
||||
if tuner_conf.get("hashrateTarget") is not None:
|
||||
return cls.hashrate_tuning(
|
||||
int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
hashrate=int(tuner_conf["hashrateTarget"]["terahashPerSecond"]),
|
||||
scaling=ScalingConfig.from_boser(grpc_miner_conf, mode="hashrate"),
|
||||
)
|
||||
|
||||
@@ -617,9 +610,9 @@ class MiningModeConfig(MinerConfigOption):
|
||||
if mode_data.get("Mode") == "turbo":
|
||||
return cls.high()
|
||||
if mode_data.get("Ths") is not None:
|
||||
return cls.hashrate_tuning(mode_data["Ths"])
|
||||
return cls.hashrate_tuning(hashrate=mode_data["Ths"])
|
||||
if mode_data.get("Power") is not None:
|
||||
return cls.power_tuning(mode_data["Power"])
|
||||
return cls.power_tuning(power=mode_data["Power"])
|
||||
except LookupError:
|
||||
return cls.default()
|
||||
|
||||
@@ -647,3 +640,6 @@ class MiningModeConfig(MinerConfigOption):
|
||||
except LookupError:
|
||||
pass
|
||||
return cls.default()
|
||||
|
||||
|
||||
MiningMode = TypeVar("MiningMode", bound=Union[*[v.value for v in MiningModeConfig]])
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TypeVar, Union
|
||||
|
||||
from pyasic.config.base import MinerConfigOption, MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class StandardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="standard")
|
||||
|
||||
@@ -13,7 +13,6 @@ class StandardTuneAlgo(MinerConfigValue):
|
||||
return VOptAlgo().as_epic()
|
||||
|
||||
|
||||
@dataclass
|
||||
class VOptAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="voltage_optimizer")
|
||||
|
||||
@@ -21,7 +20,6 @@ class VOptAlgo(MinerConfigValue):
|
||||
return "VoltageOptimizer"
|
||||
|
||||
|
||||
@dataclass
|
||||
class BoardTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="board_tune")
|
||||
|
||||
@@ -29,7 +27,6 @@ class BoardTuneAlgo(MinerConfigValue):
|
||||
return "BoardTune"
|
||||
|
||||
|
||||
@dataclass
|
||||
class ChipTuneAlgo(MinerConfigValue):
|
||||
mode: str = field(init=False, default="chip_tune")
|
||||
|
||||
@@ -37,7 +34,6 @@ class ChipTuneAlgo(MinerConfigValue):
|
||||
return "ChipTune"
|
||||
|
||||
|
||||
@dataclass
|
||||
class TunerAlgo(MinerConfigOption):
|
||||
standard = StandardTuneAlgo
|
||||
voltage_optimizer = VOptAlgo
|
||||
@@ -45,11 +41,11 @@ class TunerAlgo(MinerConfigOption):
|
||||
chip_tune = ChipTuneAlgo
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
def default(cls) -> TunerAlgoType:
|
||||
return cls.standard()
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict_conf: dict | None):
|
||||
def from_dict(cls, dict_conf: dict | None) -> TunerAlgoType:
|
||||
mode = dict_conf.get("mode")
|
||||
if mode is None:
|
||||
return cls.default()
|
||||
@@ -57,3 +53,6 @@ class TunerAlgo(MinerConfigOption):
|
||||
cls_attr = getattr(cls, mode)
|
||||
if cls_attr is not None:
|
||||
return cls_attr().from_dict(dict_conf)
|
||||
|
||||
|
||||
TunerAlgoType = TypeVar("TunerAlgoType", bound=Union[*[v.value for v in TunerAlgo]])
|
||||
|
||||
@@ -20,7 +20,6 @@ from dataclasses import dataclass
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScalingShutdown(MinerConfigValue):
|
||||
enabled: bool = False
|
||||
duration: int = None
|
||||
@@ -35,7 +34,9 @@ class ScalingShutdown(MinerConfigValue):
|
||||
def from_bosminer(cls, power_scaling_conf: dict):
|
||||
sd_enabled = power_scaling_conf.get("shutdown_enabled")
|
||||
if sd_enabled is not None:
|
||||
return cls(sd_enabled, power_scaling_conf.get("shutdown_duration"))
|
||||
return cls(
|
||||
enabled=sd_enabled, duration=power_scaling_conf.get("shutdown_duration")
|
||||
)
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
@@ -43,9 +44,12 @@ class ScalingShutdown(MinerConfigValue):
|
||||
sd_enabled = power_scaling_conf.get("shutdownEnabled")
|
||||
if sd_enabled is not None:
|
||||
try:
|
||||
return cls(sd_enabled, power_scaling_conf["shutdownDuration"]["hours"])
|
||||
return cls(
|
||||
enabled=sd_enabled,
|
||||
duration=power_scaling_conf["shutdownDuration"]["hours"],
|
||||
)
|
||||
except KeyError:
|
||||
return cls(sd_enabled)
|
||||
return cls(enabled=sd_enabled)
|
||||
return None
|
||||
|
||||
def as_bosminer(self) -> dict:
|
||||
@@ -60,7 +64,6 @@ class ScalingShutdown(MinerConfigValue):
|
||||
return {"enable_shutdown": self.enabled, "shutdown_duration": self.duration}
|
||||
|
||||
|
||||
@dataclass
|
||||
class ScalingConfig(MinerConfigValue):
|
||||
step: int = None
|
||||
minimum: int = None
|
||||
|
||||
@@ -17,9 +17,10 @@ from __future__ import annotations
|
||||
|
||||
import random
|
||||
import string
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
PoolConfiguration,
|
||||
@@ -30,7 +31,6 @@ from pyasic.web.braiins_os.proto.braiins.bos.v1 import (
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class Pool(MinerConfigValue):
|
||||
url: str
|
||||
user: str
|
||||
@@ -235,11 +235,10 @@ class Pool(MinerConfigValue):
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolGroup(MinerConfigValue):
|
||||
pools: list[Pool] = field(default_factory=list)
|
||||
pools: list[Pool] = Field(default_factory=list)
|
||||
quota: int = 1
|
||||
name: str = None
|
||||
name: str | None = None
|
||||
|
||||
def __post_init__(self):
|
||||
if self.name is None:
|
||||
@@ -254,7 +253,7 @@ class PoolGroup(MinerConfigValue):
|
||||
if len(self.pools) > idx:
|
||||
pools.append(self.pools[idx].as_am_modern(user_suffix=user_suffix))
|
||||
else:
|
||||
pools.append(Pool("", "", "").as_am_modern())
|
||||
pools.append(Pool(url="", user="", password="").as_am_modern())
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
@@ -267,7 +266,7 @@ class PoolGroup(MinerConfigValue):
|
||||
**self.pools[idx].as_wm(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_wm(idx=idx + 1))
|
||||
pools.update(**Pool(url="", user="", password="").as_wm(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
@@ -280,7 +279,9 @@ class PoolGroup(MinerConfigValue):
|
||||
**self.pools[idx].as_am_old(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_am_old(idx=idx + 1))
|
||||
pools.update(
|
||||
**Pool(url="", user="", password="").as_am_old(idx=idx + 1)
|
||||
)
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
@@ -290,7 +291,7 @@ class PoolGroup(MinerConfigValue):
|
||||
def as_avalon(self, user_suffix: str = None) -> str:
|
||||
if len(self.pools) > 0:
|
||||
return self.pools[0].as_avalon(user_suffix=user_suffix)
|
||||
return Pool("", "", "").as_avalon()
|
||||
return Pool(url="", user="", password="").as_avalon()
|
||||
|
||||
def as_inno(self, user_suffix: str = None) -> dict:
|
||||
pools = {}
|
||||
@@ -301,7 +302,7 @@ class PoolGroup(MinerConfigValue):
|
||||
**self.pools[idx].as_inno(idx=idx + 1, user_suffix=user_suffix)
|
||||
)
|
||||
else:
|
||||
pools.update(**Pool("", "", "").as_inno(idx=idx + 1))
|
||||
pools.update(**Pool(url="", user="", password="").as_inno(idx=idx + 1))
|
||||
idx += 1
|
||||
return pools
|
||||
|
||||
@@ -371,11 +372,11 @@ class PoolGroup(MinerConfigValue):
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls([Pool.from_goldshell(p) for p in web_pools])
|
||||
return cls(pools=[Pool.from_goldshell(p) for p in web_pools])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolGroup":
|
||||
return cls([Pool.from_inno(p) for p in web_pools])
|
||||
return cls(pools=[Pool.from_inno(p) for p in web_pools])
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_group_conf: dict) -> "PoolGroup":
|
||||
@@ -389,7 +390,7 @@ class PoolGroup(MinerConfigValue):
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup":
|
||||
return cls([Pool.from_vnish(p) for p in web_settings_pools])
|
||||
return cls(pools=[Pool.from_vnish(p) for p in web_settings_pools])
|
||||
|
||||
@classmethod
|
||||
def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup":
|
||||
@@ -424,9 +425,8 @@ class PoolGroup(MinerConfigValue):
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolConfig(MinerConfigValue):
|
||||
groups: List[PoolGroup] = field(default_factory=list)
|
||||
groups: List[PoolGroup] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def default(cls) -> "PoolConfig":
|
||||
@@ -538,38 +538,38 @@ class PoolConfig(MinerConfigValue):
|
||||
return PoolConfig.default()
|
||||
pool_data = sorted(pool_data, key=lambda x: int(x["POOL"]))
|
||||
|
||||
return cls([PoolGroup.from_api(pool_data)])
|
||||
return cls(groups=[PoolGroup.from_api(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_epic(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["StratumConfigs"]
|
||||
return cls([PoolGroup.from_epic(pool_data)])
|
||||
return cls(groups=[PoolGroup.from_epic(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_am_modern(cls, web_conf: dict) -> "PoolConfig":
|
||||
pool_data = web_conf["pools"]
|
||||
|
||||
return cls([PoolGroup.from_am_modern(pool_data)])
|
||||
return cls(groups=[PoolGroup.from_am_modern(pool_data)])
|
||||
|
||||
@classmethod
|
||||
def from_goldshell(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls([PoolGroup.from_goldshell(web_pools)])
|
||||
return cls(groups=[PoolGroup.from_goldshell(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_inno(cls, web_pools: list) -> "PoolConfig":
|
||||
return cls([PoolGroup.from_inno(web_pools)])
|
||||
return cls(groups=[PoolGroup.from_inno(web_pools)])
|
||||
|
||||
@classmethod
|
||||
def from_bosminer(cls, toml_conf: dict) -> "PoolConfig":
|
||||
if toml_conf.get("group") is None:
|
||||
return cls()
|
||||
|
||||
return cls([PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||
return cls(groups=[PoolGroup.from_bosminer(g) for g in toml_conf["group"]])
|
||||
|
||||
@classmethod
|
||||
def from_vnish(cls, web_settings: dict) -> "PoolConfig":
|
||||
try:
|
||||
return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])])
|
||||
return cls(groups=[PoolGroup.from_vnish(web_settings["miner"]["pools"])])
|
||||
except LookupError:
|
||||
return cls()
|
||||
|
||||
|
||||
@@ -20,11 +20,10 @@ from dataclasses import dataclass
|
||||
from pyasic.config.base import MinerConfigValue
|
||||
|
||||
|
||||
@dataclass
|
||||
class TemperatureConfig(MinerConfigValue):
|
||||
target: int = None
|
||||
hot: int = None
|
||||
danger: int = None
|
||||
target: int | None = None
|
||||
hot: int | None = None
|
||||
danger: int | None = None
|
||||
|
||||
@classmethod
|
||||
def default(cls):
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
import copy
|
||||
import json
|
||||
import time
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, List, Union
|
||||
|
||||
from pydantic import BaseModel, Field, computed_field, field_serializer
|
||||
|
||||
from pyasic.config import MinerConfig
|
||||
from pyasic.config.mining import MiningModePowerTune
|
||||
from pyasic.data.pools import PoolMetrics, Scheme
|
||||
@@ -29,11 +29,10 @@ from .boards import HashBoard
|
||||
from .device import DeviceInfo
|
||||
from .error_codes import BraiinsOSError, InnosiliconError, WhatsminerError, X19Error
|
||||
from .fans import Fan
|
||||
from .hashrate import AlgoHashRate, HashUnit
|
||||
from .hashrate import AlgoHashRate, AlgoHashRateType, HashUnit
|
||||
|
||||
|
||||
@dataclass
|
||||
class MinerData:
|
||||
class MinerData(BaseModel):
|
||||
"""A Dataclass to standardize data returned from miners (specifically `AnyMiner().get_data()`)
|
||||
|
||||
Attributes:
|
||||
@@ -77,58 +76,44 @@ class MinerData:
|
||||
|
||||
# general
|
||||
ip: str
|
||||
_datetime: datetime = field(repr=False, default=None)
|
||||
datetime: str = field(init=False)
|
||||
timestamp: int = field(init=False)
|
||||
raw_datetime: datetime = Field(
|
||||
exclude=True, default_factory=datetime.now(timezone.utc).astimezone, repr=False
|
||||
)
|
||||
|
||||
# about
|
||||
device_info: DeviceInfo = None
|
||||
make: str = field(init=False)
|
||||
model: str = field(init=False)
|
||||
firmware: str = field(init=False)
|
||||
algo: str = field(init=False)
|
||||
mac: str = None
|
||||
api_ver: str = None
|
||||
fw_ver: str = None
|
||||
hostname: str = None
|
||||
device_info: DeviceInfo | None = None
|
||||
mac: str | None = None
|
||||
api_ver: str | None = None
|
||||
fw_ver: str | None = None
|
||||
hostname: str | None = None
|
||||
|
||||
# hashrate
|
||||
hashrate: AlgoHashRate = field(init=False)
|
||||
_hashrate: AlgoHashRate = field(repr=False, default=None)
|
||||
raw_hashrate: AlgoHashRateType = Field(exclude=True, default=None, repr=False)
|
||||
|
||||
# expected
|
||||
expected_hashrate: float = None
|
||||
expected_hashboards: int = None
|
||||
expected_chips: int = None
|
||||
expected_fans: int = None
|
||||
|
||||
# % expected
|
||||
percent_expected_chips: float = field(init=False)
|
||||
percent_expected_hashrate: float = field(init=False)
|
||||
percent_expected_wattage: float = field(init=False)
|
||||
expected_hashrate: AlgoHashRateType | None = None
|
||||
expected_hashboards: int | None = None
|
||||
expected_chips: int | None = None
|
||||
expected_fans: int | None = None
|
||||
|
||||
# temperature
|
||||
temperature_avg: int = field(init=False)
|
||||
env_temp: float = None
|
||||
env_temp: int | None = None
|
||||
|
||||
# power
|
||||
wattage: int = None
|
||||
wattage_limit: int = field(init=False)
|
||||
voltage: float = None
|
||||
_wattage_limit: int = field(repr=False, default=None)
|
||||
wattage: int | None = None
|
||||
voltage: float | None = None
|
||||
raw_wattage_limit: int | None = Field(exclude=True, default=None, repr=False)
|
||||
|
||||
# fans
|
||||
fans: List[Fan] = field(default_factory=list)
|
||||
fan_psu: int = None
|
||||
fans: List[Fan] = Field(default_factory=list)
|
||||
fan_psu: int | None = None
|
||||
|
||||
# boards
|
||||
hashboards: List[HashBoard] = field(default_factory=list)
|
||||
total_chips: int = field(init=False)
|
||||
nominal: bool = field(init=False)
|
||||
hashboards: List[HashBoard] = Field(default_factory=list)
|
||||
|
||||
# config
|
||||
config: MinerConfig = None
|
||||
fault_light: Union[bool, None] = None
|
||||
config: MinerConfig | None = None
|
||||
fault_light: bool | None = None
|
||||
|
||||
# errors
|
||||
errors: List[
|
||||
@@ -138,30 +123,21 @@ class MinerData:
|
||||
X19Error,
|
||||
InnosiliconError,
|
||||
]
|
||||
] = field(default_factory=list)
|
||||
] = Field(default_factory=list)
|
||||
|
||||
# mining state
|
||||
is_mining: bool = True
|
||||
uptime: int = None
|
||||
efficiency: int = field(init=False)
|
||||
uptime: int | None = None
|
||||
|
||||
# pools
|
||||
pools: list[PoolMetrics] = field(default_factory=list)
|
||||
pools: list[PoolMetrics] = Field(default_factory=list)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return [f.name for f in fields(cls) if not f.name.startswith("_")]
|
||||
|
||||
@staticmethod
|
||||
def dict_factory(x):
|
||||
return {
|
||||
k: v.value if isinstance(v, Scheme) else v
|
||||
for (k, v) in x
|
||||
if not k.startswith("_")
|
||||
}
|
||||
return list(cls.model_fields.keys())
|
||||
|
||||
def __post_init__(self):
|
||||
self._datetime = datetime.now(timezone.utc).astimezone()
|
||||
self.raw_datetime = datetime.now(timezone.utc).astimezone()
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
@@ -189,19 +165,19 @@ class MinerData:
|
||||
|
||||
def __floordiv__(self, other):
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
for key in self.fields():
|
||||
item = getattr(self, key)
|
||||
if isinstance(item, int):
|
||||
setattr(cp, key, item // other)
|
||||
if isinstance(item, float):
|
||||
setattr(cp, key, round(item / other, 2))
|
||||
setattr(cp, key, item / other)
|
||||
return cp
|
||||
|
||||
def __add__(self, other):
|
||||
if not isinstance(other, MinerData):
|
||||
raise TypeError("Cannot add MinerData to non MinerData type.")
|
||||
cp = copy.deepcopy(self)
|
||||
for key in self:
|
||||
for key in self.fields():
|
||||
item = getattr(self, key)
|
||||
other_item = getattr(other, key)
|
||||
if item is None:
|
||||
@@ -221,34 +197,49 @@ class MinerData:
|
||||
setattr(cp, key, item & other_item)
|
||||
return cp
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def hashrate(self): # noqa - Skip PyCharm inspection
|
||||
def hashrate(self) -> AlgoHashRateType:
|
||||
if len(self.hashboards) > 0:
|
||||
hr_data = []
|
||||
for item in self.hashboards:
|
||||
if item.hashrate is not None:
|
||||
hr_data.append(item.hashrate)
|
||||
if len(hr_data) > 0:
|
||||
return sum(hr_data, start=type(hr_data[0])(0))
|
||||
return self._hashrate
|
||||
return sum(hr_data, start=type(hr_data[0])(rate=0))
|
||||
return self.raw_hashrate
|
||||
|
||||
@field_serializer("hashrate")
|
||||
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
|
||||
if hashrate is not None:
|
||||
return float(hashrate)
|
||||
|
||||
@field_serializer("expected_hashrate")
|
||||
def serialize_expected_hashrate(
|
||||
self, expected_hashrate: AlgoHashRateType | None, _info
|
||||
) -> float:
|
||||
if expected_hashrate is not None:
|
||||
return float(expected_hashrate)
|
||||
|
||||
@hashrate.setter
|
||||
def hashrate(self, val):
|
||||
self._hashrate = val
|
||||
self.raw_hashrate = val
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def wattage_limit(self): # noqa - Skip PyCharm inspection
|
||||
def wattage_limit(self) -> int:
|
||||
if self.config is not None:
|
||||
if isinstance(self.config.mining_mode, MiningModePowerTune):
|
||||
return self.config.mining_mode.power
|
||||
return self._wattage_limit
|
||||
return self.raw_wattage_limit
|
||||
|
||||
@wattage_limit.setter
|
||||
def wattage_limit(self, val: int):
|
||||
self._wattage_limit = val
|
||||
self.raw_wattage_limit = val
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def total_chips(self): # noqa - Skip PyCharm inspection
|
||||
def total_chips(self) -> int | None:
|
||||
if len(self.hashboards) > 0:
|
||||
chip_data = []
|
||||
for item in self.hashboards:
|
||||
@@ -258,34 +249,25 @@ class MinerData:
|
||||
return sum(chip_data)
|
||||
return None
|
||||
|
||||
@total_chips.setter
|
||||
def total_chips(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def nominal(self): # noqa - Skip PyCharm inspection
|
||||
def nominal(self) -> bool | None:
|
||||
if self.total_chips is None or self.expected_chips is None:
|
||||
return None
|
||||
return self.expected_chips == self.total_chips
|
||||
|
||||
@nominal.setter
|
||||
def nominal(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def percent_expected_chips(self): # noqa - Skip PyCharm inspection
|
||||
def percent_expected_chips(self) -> int | None:
|
||||
if self.total_chips is None or self.expected_chips is None:
|
||||
return None
|
||||
if self.total_chips == 0 or self.expected_chips == 0:
|
||||
return 0
|
||||
return round((self.total_chips / self.expected_chips) * 100)
|
||||
|
||||
@percent_expected_chips.setter
|
||||
def percent_expected_chips(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def percent_expected_hashrate(self): # noqa - Skip PyCharm inspection
|
||||
def percent_expected_hashrate(self) -> int | None:
|
||||
if self.hashrate is None or self.expected_hashrate is None:
|
||||
return None
|
||||
try:
|
||||
@@ -293,12 +275,9 @@ class MinerData:
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@percent_expected_hashrate.setter
|
||||
def percent_expected_hashrate(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def percent_expected_wattage(self): # noqa - Skip PyCharm inspection
|
||||
def percent_expected_wattage(self) -> int | None:
|
||||
if self.wattage_limit is None or self.wattage is None:
|
||||
return None
|
||||
try:
|
||||
@@ -306,12 +285,9 @@ class MinerData:
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@percent_expected_wattage.setter
|
||||
def percent_expected_wattage(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def temperature_avg(self): # noqa - Skip PyCharm inspection
|
||||
def temperature_avg(self) -> int | None:
|
||||
total_temp = 0
|
||||
temp_count = 0
|
||||
for hb in self.hashboards:
|
||||
@@ -322,12 +298,9 @@ class MinerData:
|
||||
return None
|
||||
return round(total_temp / temp_count)
|
||||
|
||||
@temperature_avg.setter
|
||||
def temperature_avg(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def efficiency(self): # noqa - Skip PyCharm inspection
|
||||
def efficiency(self) -> int | None:
|
||||
if self.hashrate is None or self.wattage is None:
|
||||
return None
|
||||
try:
|
||||
@@ -335,67 +308,45 @@ class MinerData:
|
||||
except ZeroDivisionError:
|
||||
return 0
|
||||
|
||||
@efficiency.setter
|
||||
def efficiency(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def datetime(self): # noqa - Skip PyCharm inspection
|
||||
return self._datetime.isoformat()
|
||||
|
||||
@datetime.setter
|
||||
def datetime(self, val):
|
||||
pass
|
||||
def datetime(self) -> str:
|
||||
return self.raw_datetime.isoformat()
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def timestamp(self): # noqa - Skip PyCharm inspection
|
||||
return int(time.mktime(self._datetime.timetuple()))
|
||||
|
||||
@timestamp.setter
|
||||
def timestamp(self, val):
|
||||
pass
|
||||
def timestamp(self) -> int:
|
||||
return int(time.mktime(self.raw_datetime.timetuple()))
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def make(self): # noqa - Skip PyCharm inspection
|
||||
def make(self) -> str:
|
||||
if self.device_info.make is not None:
|
||||
return str(self.device_info.make)
|
||||
|
||||
@make.setter
|
||||
def make(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def model(self): # noqa - Skip PyCharm inspection
|
||||
def model(self) -> str:
|
||||
if self.device_info.model is not None:
|
||||
return str(self.device_info.model)
|
||||
|
||||
@model.setter
|
||||
def model(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def firmware(self): # noqa - Skip PyCharm inspection
|
||||
def firmware(self) -> str:
|
||||
if self.device_info.firmware is not None:
|
||||
return str(self.device_info.firmware)
|
||||
|
||||
@firmware.setter
|
||||
def firmware(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def algo(self): # noqa - Skip PyCharm inspection
|
||||
def algo(self) -> str:
|
||||
if self.device_info.algo is not None:
|
||||
return str(self.device_info.algo)
|
||||
|
||||
@algo.setter
|
||||
def algo(self, val):
|
||||
pass
|
||||
|
||||
def keys(self) -> list:
|
||||
return [f.name for f in fields(self)]
|
||||
return list(self.model_fields.keys())
|
||||
|
||||
def asdict(self) -> dict:
|
||||
return asdict(self, dict_factory=self.dict_factory)
|
||||
return self.model_dump()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
@@ -411,7 +362,7 @@ class MinerData:
|
||||
Returns:
|
||||
A JSON version of this class.
|
||||
"""
|
||||
return json.dumps(self.as_dict())
|
||||
return self.model_dump_json()
|
||||
|
||||
def as_csv(self) -> str:
|
||||
"""Get this dataclass as CSV.
|
||||
@@ -440,7 +391,7 @@ class MinerData:
|
||||
field_data = []
|
||||
|
||||
tags = ["ip", "mac", "model", "hostname"]
|
||||
for attribute in self:
|
||||
for attribute in self.fields():
|
||||
if attribute in tags:
|
||||
escaped_data = self.get(attribute, "Unknown").replace(" ", "\\ ")
|
||||
tag_data.append(f"{attribute}={escaped_data}")
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from .hashrate import AlgoHashRate
|
||||
from pydantic import BaseModel, field_serializer
|
||||
|
||||
from .hashrate import AlgoHashRateType
|
||||
|
||||
|
||||
@dataclass
|
||||
class HashBoard:
|
||||
class HashBoard(BaseModel):
|
||||
"""A Dataclass to standardize hashboard data.
|
||||
|
||||
Attributes:
|
||||
@@ -39,16 +39,21 @@ class HashBoard:
|
||||
"""
|
||||
|
||||
slot: int = 0
|
||||
hashrate: AlgoHashRate = None
|
||||
temp: int = None
|
||||
chip_temp: int = None
|
||||
chips: int = None
|
||||
expected_chips: int = None
|
||||
serial_number: str = None
|
||||
hashrate: AlgoHashRateType | None = None
|
||||
temp: int | None = None
|
||||
chip_temp: int | None = None
|
||||
chips: int | None = None
|
||||
expected_chips: int | None = None
|
||||
serial_number: str | None = None
|
||||
missing: bool = True
|
||||
tuned: bool = None
|
||||
active: bool = None
|
||||
voltage: float = None
|
||||
tuned: bool | None = None
|
||||
active: bool | None = None
|
||||
voltage: float | None = None
|
||||
|
||||
@field_serializer("hashrate")
|
||||
def serialize_hashrate(self, hashrate: AlgoHashRateType | None) -> float:
|
||||
if hashrate is not None:
|
||||
return float(hashrate)
|
||||
|
||||
def get(self, __key: str, default: Any = None):
|
||||
try:
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
from dataclasses import dataclass
|
||||
from pydantic import BaseModel, ConfigDict, field_serializer
|
||||
|
||||
from pyasic.device.algorithm import MinerAlgo
|
||||
from pyasic.device.algorithm import MinerAlgoType
|
||||
from pyasic.device.firmware import MinerFirmware
|
||||
from pyasic.device.makes import MinerMake
|
||||
from pyasic.device.models import MinerModel
|
||||
from pyasic.device.models import MinerModelType
|
||||
|
||||
|
||||
@dataclass
|
||||
class DeviceInfo:
|
||||
make: MinerMake = None
|
||||
model: MinerModel = None
|
||||
firmware: MinerFirmware = None
|
||||
algo: MinerAlgo = None
|
||||
class DeviceInfo(BaseModel):
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
make: MinerMake | None = None
|
||||
model: MinerModelType | None = None
|
||||
firmware: MinerFirmware | None = None
|
||||
algo: MinerAlgoType | None = None
|
||||
|
||||
@field_serializer("make")
|
||||
def serialize_make(self, make: MinerMake, _info):
|
||||
return str(make)
|
||||
|
||||
@field_serializer("model")
|
||||
def serialize_model(self, model: MinerModelType, _info):
|
||||
return str(model)
|
||||
|
||||
@field_serializer("firmware")
|
||||
def serialize_firmware(self, firmware: MinerFirmware, _info):
|
||||
return str(firmware)
|
||||
|
||||
@field_serializer("algo")
|
||||
def serialize_algo(self, algo: MinerAlgoType, _info):
|
||||
return str(algo)
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class X19Error:
|
||||
class X19Error(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of X19 miners.
|
||||
|
||||
Attributes:
|
||||
@@ -28,10 +26,3 @@ class X19Error:
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
18
pyasic/data/error_codes/base.py
Normal file
18
pyasic/data/error_codes/base.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class BaseMinerError(BaseModel):
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return list(cls.model_fields.keys())
|
||||
|
||||
def asdict(self) -> dict:
|
||||
return self.model_dump()
|
||||
|
||||
def as_dict(self) -> dict:
|
||||
"""Get this dataclass as a dictionary.
|
||||
|
||||
Returns:
|
||||
A dictionary version of this class.
|
||||
"""
|
||||
return self.asdict()
|
||||
@@ -14,11 +14,10 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, fields
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class BraiinsOSError:
|
||||
class BraiinsOSError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of BraiinsOS+ miners.
|
||||
|
||||
Attributes:
|
||||
@@ -28,10 +27,3 @@ class BraiinsOSError:
|
||||
|
||||
error_message: str
|
||||
error_code: int = 0
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
@@ -14,11 +14,13 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
|
||||
from pydantic import computed_field
|
||||
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class InnosiliconError:
|
||||
class InnosiliconError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of Innosilicon miners.
|
||||
|
||||
Attributes:
|
||||
@@ -27,25 +29,14 @@ class InnosiliconError:
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||
if self.error_code in ERROR_CODES:
|
||||
return ERROR_CODES[self.error_code]
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
21: "The PLUG signal of the hash board is not detected.",
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
# See the License for the specific language governing permissions and -
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
from pydantic import computed_field
|
||||
|
||||
from dataclasses import asdict, dataclass, field, fields
|
||||
from pyasic.data.error_codes.base import BaseMinerError
|
||||
|
||||
|
||||
@dataclass
|
||||
class WhatsminerError:
|
||||
class WhatsminerError(BaseMinerError):
|
||||
"""A Dataclass to handle error codes of Whatsminers.
|
||||
|
||||
Attributes:
|
||||
@@ -27,14 +27,10 @@ class WhatsminerError:
|
||||
"""
|
||||
|
||||
error_code: int
|
||||
error_message: str = field(init=False)
|
||||
|
||||
@classmethod
|
||||
def fields(cls):
|
||||
return fields(cls)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def error_message(self): # noqa - Skip PyCharm inspection
|
||||
def error_message(self) -> str: # noqa - Skip PyCharm inspection
|
||||
if len(str(self.error_code)) == 6 and not str(self.error_code)[:1] == "1":
|
||||
err_type = int(str(self.error_code)[:2])
|
||||
err_subtype = int(str(self.error_code)[2:3])
|
||||
@@ -74,13 +70,6 @@ class WhatsminerError:
|
||||
except KeyError:
|
||||
return "Unknown error type."
|
||||
|
||||
@error_message.setter
|
||||
def error_message(self, val):
|
||||
pass
|
||||
|
||||
def asdict(self):
|
||||
return asdict(self)
|
||||
|
||||
|
||||
ERROR_CODES = {
|
||||
1: { # Fan error
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
# limitations under the License. -
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@dataclass
|
||||
class Fan:
|
||||
|
||||
class Fan(BaseModel):
|
||||
"""A Dataclass to standardize fan data.
|
||||
|
||||
Attributes:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from enum import Enum
|
||||
|
||||
from pyasic.data.hashrate.base import AlgoHashRateType
|
||||
from pyasic.data.hashrate.sha256 import SHA256HashRate
|
||||
from pyasic.device.algorithm.sha256 import SHA256Unit
|
||||
|
||||
|
||||
5
pyasic/data/hashrate/base.py
Normal file
5
pyasic/data/hashrate/base.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AlgoHashRateType(BaseModel):
|
||||
pass
|
||||
@@ -1,13 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from pyasic.data.hashrate.base import AlgoHashRateType
|
||||
from pyasic.device.algorithm import MinerAlgo
|
||||
from pyasic.device.algorithm.sha256 import SHA256Unit
|
||||
|
||||
|
||||
@dataclass
|
||||
class SHA256HashRate:
|
||||
class SHA256HashRate(AlgoHashRateType):
|
||||
rate: float
|
||||
unit: SHA256Unit = MinerAlgo.SHA256.unit.default
|
||||
|
||||
@@ -25,28 +23,38 @@ class SHA256HashRate:
|
||||
|
||||
def __add__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
|
||||
if isinstance(other, SHA256HashRate):
|
||||
return SHA256HashRate(self.rate + other.into(self.unit).rate, self.unit)
|
||||
return SHA256HashRate(self.rate + other, self.unit)
|
||||
return SHA256HashRate(
|
||||
rate=self.rate + other.into(self.unit).rate, unit=self.unit
|
||||
)
|
||||
return SHA256HashRate(rate=self.rate + other, unit=self.unit)
|
||||
|
||||
def __sub__(self, other: SHA256HashRate | int | float) -> SHA256HashRate:
|
||||
if isinstance(other, SHA256HashRate):
|
||||
return SHA256HashRate(self.rate - other.into(self.unit).rate, self.unit)
|
||||
return SHA256HashRate(self.rate - other, self.unit)
|
||||
return SHA256HashRate(
|
||||
rate=self.rate - other.into(self.unit).rate, unit=self.unit
|
||||
)
|
||||
return SHA256HashRate(rate=self.rate - other, unit=self.unit)
|
||||
|
||||
def __truediv__(self, other: SHA256HashRate | int | float):
|
||||
if isinstance(other, SHA256HashRate):
|
||||
return SHA256HashRate(self.rate / other.into(self.unit).rate, self.unit)
|
||||
return SHA256HashRate(self.rate / other, self.unit)
|
||||
return SHA256HashRate(
|
||||
rate=self.rate / other.into(self.unit).rate, unit=self.unit
|
||||
)
|
||||
return SHA256HashRate(rate=self.rate / other, unit=self.unit)
|
||||
|
||||
def __floordiv__(self, other: SHA256HashRate | int | float):
|
||||
if isinstance(other, SHA256HashRate):
|
||||
return SHA256HashRate(self.rate // other.into(self.unit).rate, self.unit)
|
||||
return SHA256HashRate(self.rate // other, self.unit)
|
||||
return SHA256HashRate(
|
||||
rate=self.rate // other.into(self.unit).rate, unit=self.unit
|
||||
)
|
||||
return SHA256HashRate(rate=self.rate // other, unit=self.unit)
|
||||
|
||||
def __mul__(self, other: SHA256HashRate | int | float):
|
||||
if isinstance(other, SHA256HashRate):
|
||||
return SHA256HashRate(self.rate * other.into(self.unit).rate, self.unit)
|
||||
return SHA256HashRate(self.rate * other, self.unit)
|
||||
return SHA256HashRate(
|
||||
rate=self.rate * other.into(self.unit).rate, unit=self.unit
|
||||
)
|
||||
return SHA256HashRate(rate=self.rate * other, unit=self.unit)
|
||||
|
||||
def into(self, other: SHA256Unit) -> SHA256HashRate:
|
||||
return SHA256HashRate(
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from pydantic import BaseModel, computed_field, model_serializer
|
||||
|
||||
|
||||
class Scheme(Enum):
|
||||
STRATUM_V1 = "stratum+tcp"
|
||||
@@ -10,13 +11,16 @@ class Scheme(Enum):
|
||||
STRATUM_V1_SSL = "stratum+ssl"
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolUrl:
|
||||
class PoolUrl(BaseModel):
|
||||
scheme: Scheme
|
||||
host: str
|
||||
port: int
|
||||
pubkey: Optional[str] = None
|
||||
|
||||
@model_serializer
|
||||
def serialize(self):
|
||||
return str(self)
|
||||
|
||||
def __str__(self) -> str:
|
||||
if self.scheme == Scheme.STRATUM_V2 and self.pubkey:
|
||||
return f"{self.scheme.value}://{self.host}:{self.port}/{self.pubkey}"
|
||||
@@ -36,8 +40,7 @@ class PoolUrl:
|
||||
return cls(scheme=scheme, host=host, port=port, pubkey=pubkey)
|
||||
|
||||
|
||||
@dataclass
|
||||
class PoolMetrics:
|
||||
class PoolMetrics(BaseModel):
|
||||
"""A dataclass to standardize pool metrics returned from miners.
|
||||
Attributes:
|
||||
|
||||
@@ -63,18 +66,14 @@ class PoolMetrics:
|
||||
alive: bool = None
|
||||
index: int = None
|
||||
user: str = None
|
||||
pool_rejected_percent: float = field(init=False)
|
||||
pool_stale_percent: float = field(init=False)
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def pool_rejected_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||
"""Calculate and return the percentage of rejected shares"""
|
||||
return self._calculate_percentage(self.rejected, self.accepted + self.rejected)
|
||||
|
||||
@pool_rejected_percent.setter
|
||||
def pool_rejected_percent(self, val):
|
||||
pass
|
||||
|
||||
@computed_field # type: ignore[misc]
|
||||
@property
|
||||
def pool_stale_percent(self) -> float: # noqa - Skip PyCharm inspection
|
||||
"""Calculate and return the percentage of stale shares."""
|
||||
@@ -82,10 +81,6 @@ class PoolMetrics:
|
||||
self.get_failures, self.accepted + self.rejected
|
||||
)
|
||||
|
||||
@pool_stale_percent.setter
|
||||
def pool_stale_percent(self, val):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _calculate_percentage(value: int, total: int) -> float:
|
||||
"""Calculate the percentage."""
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from pyasic.device.algorithm.base import MinerAlgoType
|
||||
from pyasic.device.algorithm.sha256 import SHA256Algo
|
||||
|
||||
|
||||
|
||||
7
pyasic/device/algorithm/base.py
Normal file
7
pyasic/device/algorithm/base.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
|
||||
class MinerAlgoType(str):
|
||||
pass
|
||||
@@ -2,6 +2,8 @@ from __future__ import annotations
|
||||
|
||||
from enum import IntEnum
|
||||
|
||||
from .base import MinerAlgoType
|
||||
|
||||
|
||||
class SHA256Unit(IntEnum):
|
||||
H = 1
|
||||
@@ -58,7 +60,7 @@ class SHA256Unit(IntEnum):
|
||||
|
||||
|
||||
# make this json serializable
|
||||
class _SHA256Algo(str):
|
||||
class _SHA256Algo(MinerAlgoType):
|
||||
unit = SHA256Unit
|
||||
|
||||
def __repr__(self):
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class AntminerModels(str, Enum):
|
||||
class MinerModelType(str, Enum):
|
||||
pass
|
||||
|
||||
|
||||
class AntminerModels(MinerModelType):
|
||||
D3 = "D3"
|
||||
HS3 = "HS3"
|
||||
L3Plus = "L3+"
|
||||
@@ -58,7 +62,7 @@ class AntminerModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class WhatsminerModels(str, Enum):
|
||||
class WhatsminerModels(MinerModelType):
|
||||
M20V10 = "M20 V10"
|
||||
M20SV10 = "M20S V10"
|
||||
M20SV20 = "M20S V20"
|
||||
@@ -279,7 +283,7 @@ class WhatsminerModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class AvalonminerModels(str, Enum):
|
||||
class AvalonminerModels(MinerModelType):
|
||||
Avalon721 = "Avalon 721"
|
||||
Avalon741 = "Avalon 741"
|
||||
Avalon761 = "Avalon 761"
|
||||
@@ -298,7 +302,7 @@ class AvalonminerModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class InnosiliconModels(str, Enum):
|
||||
class InnosiliconModels(MinerModelType):
|
||||
T3HPlus = "T3H+"
|
||||
A10X = "A10X"
|
||||
A11 = "A11"
|
||||
@@ -308,7 +312,7 @@ class InnosiliconModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class GoldshellModels(str, Enum):
|
||||
class GoldshellModels(MinerModelType):
|
||||
CK5 = "CK5"
|
||||
HS5 = "HS5"
|
||||
KD5 = "KD5"
|
||||
@@ -320,7 +324,7 @@ class GoldshellModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class ePICModels(str, Enum):
|
||||
class ePICModels(MinerModelType):
|
||||
BM520i = "BlockMiner 520i"
|
||||
BM720i = "BlockMiner 720i"
|
||||
|
||||
@@ -328,7 +332,7 @@ class ePICModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class AuradineModels(str, Enum):
|
||||
class AuradineModels(MinerModelType):
|
||||
AT1500 = "AT1500"
|
||||
AT2860 = "AT2860"
|
||||
AT2880 = "AT2880"
|
||||
@@ -341,7 +345,7 @@ class AuradineModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class BitAxeModels(str, Enum):
|
||||
class BitAxeModels(MinerModelType):
|
||||
BM1366 = "Ultra"
|
||||
BM1368 = "Supra"
|
||||
BM1397 = "Max"
|
||||
@@ -351,7 +355,7 @@ class BitAxeModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class IceRiverModels(str, Enum):
|
||||
class IceRiverModels(MinerModelType):
|
||||
KS0 = "KS0"
|
||||
KS1 = "KS1"
|
||||
KS2 = "KS2"
|
||||
@@ -366,7 +370,7 @@ class IceRiverModels(str, Enum):
|
||||
return self.value
|
||||
|
||||
|
||||
class HammerModels(str, Enum):
|
||||
class HammerModels(MinerModelType):
|
||||
D10 = "D10"
|
||||
|
||||
def __str__(self):
|
||||
|
||||
@@ -126,7 +126,7 @@ class HiveonT9(Hiveon, T9):
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||
hashrate, HashUnit.SHA256.GH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[board].chips = chips
|
||||
|
||||
@@ -172,4 +172,4 @@ class HiveonT9(Hiveon, T9):
|
||||
pass
|
||||
|
||||
if not env_temp_list == []:
|
||||
return round(float(sum(env_temp_list) / len(env_temp_list)), 2)
|
||||
return round(sum(env_temp_list) / len(env_temp_list))
|
||||
|
||||
@@ -183,13 +183,13 @@ class AntminerModern(BMMiner):
|
||||
|
||||
async def stop_mining(self) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.miner_mode = MiningModeConfig.sleep
|
||||
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
|
||||
cfg.miner_mode = MiningModeConfig.normal()
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
@@ -239,7 +239,7 @@ class AntminerModern(BMMiner):
|
||||
for item in web_summary["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(item["msg"]))
|
||||
errors.append(X19Error(error_message=item["msg"]))
|
||||
except KeyError:
|
||||
continue
|
||||
except LookupError:
|
||||
@@ -248,7 +248,7 @@ class AntminerModern(BMMiner):
|
||||
|
||||
async def _get_hashboards(self) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(idx, expected_chips=self.expected_chips)
|
||||
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||
for idx in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
@@ -261,7 +261,7 @@ class AntminerModern(BMMiner):
|
||||
try:
|
||||
for board in rpc_stats["STATS"][0]["chain"]:
|
||||
hashboards[board["index"]].hashrate = AlgoHashRate.SHA256(
|
||||
board["rate_real"], HashUnit.SHA256.GH
|
||||
rate=board["rate_real"], unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[board["index"]].chips = board["asic_num"]
|
||||
board_temp_data = list(
|
||||
@@ -318,7 +318,7 @@ class AntminerModern(BMMiner):
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -625,7 +625,7 @@ class AntminerOld(CGMiner):
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = AlgoHashRate.SHA256(
|
||||
float(hashrate), HashUnit.SHA256.GH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
|
||||
@@ -293,7 +293,8 @@ class Auradine(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 5s"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 5s"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -322,9 +323,9 @@ class Auradine(StockFirmware):
|
||||
for board in rpc_devs["DEVS"]:
|
||||
b_id = board["ID"] - 1
|
||||
hashboards[b_id].hashrate = AlgoHashRate.SHA256(
|
||||
board["MHS 5s"], HashUnit.SHA256.MH
|
||||
rate=float(board["MHS 5s"]), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[b_id].temp = round(float(float(board["Temperature"])), 2)
|
||||
hashboards[b_id].temp = round(float(board["Temperature"]))
|
||||
hashboards[b_id].missing = False
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -390,7 +391,7 @@ class Auradine(StockFirmware):
|
||||
if web_fan is not None:
|
||||
try:
|
||||
for fan in web_fan["Fan"]:
|
||||
fans.append(Fan(round(fan["Speed"])))
|
||||
fans.append(Fan(speed=round(fan["Speed"])))
|
||||
except LookupError:
|
||||
pass
|
||||
return fans
|
||||
|
||||
@@ -184,7 +184,7 @@ class AvalonMiner(CGMiner):
|
||||
if rpc_devs is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_devs["DEVS"][0]["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_devs["DEVS"][0]["MHS 1m"]), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -217,7 +217,7 @@ class AvalonMiner(CGMiner):
|
||||
try:
|
||||
board_hr = parsed_stats["MGHS"][board]
|
||||
hashboards[board].hashrate = AlgoHashRate.SHA256(
|
||||
float(board_hr), HashUnit.SHA256.GH
|
||||
rate=float(board_hr), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -253,7 +253,7 @@ class AvalonMiner(CGMiner):
|
||||
unparsed_stats = rpc_stats["STATS"][0]["MM ID0"]
|
||||
parsed_stats = self.parse_stats(unparsed_stats)
|
||||
return AlgoHashRate.SHA256(
|
||||
float(parsed_stats["GHSmm"][0]), HashUnit.SHA256.GH
|
||||
rate=float(parsed_stats["GHSmm"][0]), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except (IndexError, KeyError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -121,7 +121,8 @@ class BFGMiner(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 20s"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 20s"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -167,7 +168,7 @@ class BFGMiner(StockFirmware):
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = AlgoHashRate.SHA256(
|
||||
hashrate, HashUnit.SHA256.GH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
@@ -260,7 +261,7 @@ class BFGMiner(StockFirmware):
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -94,7 +94,7 @@ class BitAxe(BaseMiner):
|
||||
if web_system_info is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
web_system_info["hashRate"], HashUnit.SHA256.GH
|
||||
rate=float(web_system_info["hashRate"]), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -124,7 +124,8 @@ class BitAxe(BaseMiner):
|
||||
return [
|
||||
HashBoard(
|
||||
hashrate=AlgoHashRate.SHA256(
|
||||
web_system_info["hashRate"], HashUnit.SHA256.GH
|
||||
rate=float(web_system_info["hashRate"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default),
|
||||
chip_temp=web_system_info.get("temp"),
|
||||
temp=web_system_info.get("vrTemp"),
|
||||
|
||||
@@ -125,7 +125,8 @@ class BMMiner(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -184,7 +185,7 @@ class BMMiner(StockFirmware):
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = AlgoHashRate.SHA256(
|
||||
hashrate, HashUnit.SHA256.GH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
@@ -246,7 +247,7 @@ class BMMiner(StockFirmware):
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -363,7 +363,8 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -435,7 +436,7 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
for board in rpc_devs["DEVS"]:
|
||||
_id = board["ID"] - offset
|
||||
hashboards[_id].hashrate = AlgoHashRate.SHA256(
|
||||
board["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(board["MHS 1m"]), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
@@ -481,7 +482,7 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(rpc_fans["FANS"][n]["RPM"]))
|
||||
fans.append(Fan(speed=rpc_fans["FANS"][n]["RPM"]))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return fans
|
||||
@@ -512,7 +513,9 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
]:
|
||||
_error = board["Status"].split(" {")[0]
|
||||
_error = _error[0].lower() + _error[1:]
|
||||
errors.append(BraiinsOSError(f"Slot {_id} {_error}"))
|
||||
errors.append(
|
||||
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
||||
)
|
||||
return errors
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
@@ -543,16 +546,19 @@ class BOSMiner(BraiinsOSFirmware):
|
||||
hr_list = []
|
||||
|
||||
for board in rpc_devs["DEVS"]:
|
||||
expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2)
|
||||
expected_hashrate = float(board["Nominal MHS"] / 1000000)
|
||||
if expected_hashrate:
|
||||
hr_list.append(expected_hashrate)
|
||||
|
||||
if len(hr_list) == 0:
|
||||
return AlgoHashRate.SHA256(0)
|
||||
return AlgoHashRate.SHA256(rate=float(0))
|
||||
else:
|
||||
return AlgoHashRate.SHA256(
|
||||
(sum(hr_list) / len(hr_list)) * self.expected_hashboards
|
||||
)
|
||||
rate=float(
|
||||
(sum(hr_list) / len(hr_list)) * self.expected_hashboards
|
||||
),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
|
||||
@@ -890,7 +896,8 @@ class BOSer(BraiinsOSFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (KeyError, IndexError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -907,8 +914,10 @@ class BOSer(BraiinsOSFirmware):
|
||||
if grpc_miner_details is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"],
|
||||
HashUnit.SHA256.GH,
|
||||
rate=float(
|
||||
grpc_miner_details["stickerHashrate"]["gigahashPerSecond"]
|
||||
),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -933,18 +942,20 @@ class BOSer(BraiinsOSFirmware):
|
||||
if board.get("chipsCount") is not None:
|
||||
hashboards[idx].chips = board["chipsCount"]
|
||||
if board.get("boardTemp") is not None:
|
||||
hashboards[idx].temp = board["boardTemp"]["degreeC"]
|
||||
hashboards[idx].temp = int(board["boardTemp"]["degreeC"])
|
||||
if board.get("highestChipTemp") is not None:
|
||||
hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][
|
||||
"degreeC"
|
||||
]
|
||||
hashboards[idx].chip_temp = int(
|
||||
board["highestChipTemp"]["temperature"]["degreeC"]
|
||||
)
|
||||
if board.get("stats") is not None:
|
||||
if not board["stats"]["realHashrate"]["last5S"] == {}:
|
||||
hashboards[idx].hashrate = AlgoHashRate.SHA256(
|
||||
board["stats"]["realHashrate"]["last5S"][
|
||||
"gigahashPerSecond"
|
||||
],
|
||||
HashUnit.SHA256.GH,
|
||||
rate=float(
|
||||
board["stats"]["realHashrate"]["last5S"][
|
||||
"gigahashPerSecond"
|
||||
]
|
||||
),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[idx].missing = False
|
||||
|
||||
@@ -993,7 +1004,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"]))
|
||||
fans.append(Fan(speed=grpc_cooling_state["fans"][n]["rpm"]))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return fans
|
||||
@@ -1024,7 +1035,9 @@ class BOSer(BraiinsOSFirmware):
|
||||
]:
|
||||
_error = board["Status"].split(" {")[0]
|
||||
_error = _error[0].lower() + _error[1:]
|
||||
errors.append(BraiinsOSError(f"Slot {_id} {_error}"))
|
||||
errors.append(
|
||||
BraiinsOSError(error_message=f"Slot {_id} {_error}")
|
||||
)
|
||||
return errors
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -1085,7 +1098,7 @@ class BOSer(BraiinsOSFirmware):
|
||||
for group in grpc_pool_groups["poolGroups"]:
|
||||
for idx, pool_info in enumerate(group["pools"]):
|
||||
pool_data = PoolMetrics(
|
||||
url=pool_info["url"],
|
||||
url=PoolUrl.from_str(pool_info["url"]),
|
||||
user=pool_info["user"],
|
||||
index=idx,
|
||||
accepted=pool_info["stats"].get("acceptedShares", 0),
|
||||
|
||||
@@ -274,7 +274,7 @@ class BTMiner(StockFirmware):
|
||||
cfg.mining_mode = MiningModeConfig.normal()
|
||||
return cfg
|
||||
|
||||
cfg.mining_mode = MiningModeConfig.power_tuning(power_lim)
|
||||
cfg.mining_mode = MiningModeConfig.power_tuning(power=power_lim)
|
||||
self.config = cfg
|
||||
return self.config
|
||||
|
||||
@@ -404,7 +404,8 @@ class BTMiner(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -434,7 +435,7 @@ class BTMiner(StockFirmware):
|
||||
hashboards[board["ASC"]].chip_temp = round(board["Chip Temp Avg"])
|
||||
hashboards[board["ASC"]].temp = round(board["Temperature"])
|
||||
hashboards[board["ASC"]].hashrate = AlgoHashRate.SHA256(
|
||||
board["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(board["MHS 1m"]), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[board["ASC"]].chips = board["Effective Chips"]
|
||||
hashboards[board["ASC"]].serial_number = board["PCB SN"]
|
||||
@@ -498,8 +499,8 @@ class BTMiner(StockFirmware):
|
||||
try:
|
||||
if self.expected_fans > 0:
|
||||
fans = [
|
||||
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
||||
Fan(rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
||||
Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed In", 0)),
|
||||
Fan(speed=rpc_summary["SUMMARY"][0].get("Fan Speed Out", 0)),
|
||||
]
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -584,7 +585,7 @@ class BTMiner(StockFirmware):
|
||||
expected_hashrate = rpc_summary["SUMMARY"][0]["Factory GHS"]
|
||||
if expected_hashrate:
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_hashrate, HashUnit.SHA256.GH
|
||||
rate=float(expected_hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
except LookupError:
|
||||
|
||||
@@ -124,7 +124,8 @@ class CGMiner(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -20,7 +20,7 @@ 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.data.pools import PoolMetrics, PoolUrl
|
||||
from pyasic.errors import APIError
|
||||
from pyasic.logger import logger
|
||||
from pyasic.miners.data import DataFunction, DataLocations, DataOptions, WebAPICommand
|
||||
@@ -234,9 +234,9 @@ class ePIC(ePICFirmware):
|
||||
if web_summary["HBs"] is not None:
|
||||
for hb in web_summary["HBs"]:
|
||||
hashrate += hb["Hashrate"][0]
|
||||
return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.MH).into(
|
||||
HashUnit.SHA256.TH
|
||||
)
|
||||
return AlgoHashRate.SHA256(
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.MH
|
||||
).into(HashUnit.SHA256.TH)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -260,9 +260,9 @@ class ePIC(ePICFirmware):
|
||||
ideal = hb["Hashrate"][1] / 100
|
||||
|
||||
hashrate += hb["Hashrate"][0] / ideal
|
||||
return AlgoHashRate.SHA256(hashrate, HashUnit.SHA256.MH).into(
|
||||
self.algo.unit.default
|
||||
)
|
||||
return AlgoHashRate.SHA256(
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -293,7 +293,7 @@ class ePIC(ePICFirmware):
|
||||
if web_summary is not None:
|
||||
for fan in web_summary["Fans Rpm"]:
|
||||
try:
|
||||
fans.append(Fan(web_summary["Fans Rpm"][fan]))
|
||||
fans.append(Fan(speed=web_summary["Fans Rpm"][fan]))
|
||||
except (LookupError, ValueError, TypeError):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
@@ -353,10 +353,10 @@ class ePIC(ePICFirmware):
|
||||
# Update the Hashboard object
|
||||
hb_list[hb["Index"]].missing = False
|
||||
hb_list[hb["Index"]].hashrate = AlgoHashRate.SHA256(
|
||||
hashrate, HashUnit.SHA256.MH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
hb_list[hb["Index"]].chips = num_of_chips
|
||||
hb_list[hb["Index"]].temp = hb["Temperature"]
|
||||
hb_list[hb["Index"]].temp = int(hb["Temperature"])
|
||||
hb_list[hb["Index"]].tuned = tuned
|
||||
hb_list[hb["Index"]].active = active
|
||||
hb_list[hb["Index"]].voltage = hb["Input Voltage"]
|
||||
@@ -417,7 +417,7 @@ class ePIC(ePICFirmware):
|
||||
try:
|
||||
error = web_summary["Status"]["Last Error"]
|
||||
if error is not None:
|
||||
errors.append(X19Error(str(error)))
|
||||
errors.append(X19Error(error_message=str(error)))
|
||||
return errors
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -437,6 +437,10 @@ class ePIC(ePICFirmware):
|
||||
web_summary.get("Session") is not None
|
||||
and web_summary.get("Stratum") is not None
|
||||
):
|
||||
url = web_summary["Stratum"].get("Current Pool")
|
||||
# TODO: when scheme gets put in, update this
|
||||
if url is not None:
|
||||
url = PoolUrl.from_str(f"stratum+tcp://{url}")
|
||||
pool_data.append(
|
||||
PoolMetrics(
|
||||
accepted=web_summary["Session"].get("Accepted"),
|
||||
@@ -445,7 +449,7 @@ class ePIC(ePICFirmware):
|
||||
remote_failures=0,
|
||||
active=web_summary["Stratum"].get("IsPoolConnected"),
|
||||
alive=web_summary["Stratum"].get("IsPoolConnected"),
|
||||
url=web_summary["Stratum"].get("Current Pool"),
|
||||
url=url,
|
||||
user=web_summary["Stratum"].get("Current User"),
|
||||
index=web_summary["Stratum"].get("Config Id"),
|
||||
)
|
||||
|
||||
@@ -163,7 +163,7 @@ class GoldshellMiner(BFGMiner):
|
||||
try:
|
||||
b_id = board["ID"]
|
||||
hashboards[b_id].hashrate = AlgoHashRate.SHA256(
|
||||
board["MHS 20s"], HashUnit.SHA256.MH
|
||||
rate=float(board["MHS 20s"]), unit=HashUnit.SHA256.MH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[b_id].temp = board["tstemp-2"]
|
||||
hashboards[b_id].missing = False
|
||||
|
||||
@@ -172,7 +172,8 @@ class BlackMiner(StockFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
@@ -231,7 +232,7 @@ class BlackMiner(StockFirmware):
|
||||
hashrate = boards[1].get(f"chain_rate{i}")
|
||||
if hashrate:
|
||||
hashboard.hashrate = AlgoHashRate.SHA256(
|
||||
float(hashrate), HashUnit.SHA256.GH
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
chips = boards[1].get(f"chain_acn{i}")
|
||||
@@ -275,42 +276,6 @@ class BlackMiner(StockFirmware):
|
||||
|
||||
return fans
|
||||
|
||||
async def _get_expected_hashrate(
|
||||
self, rpc_stats: dict = None
|
||||
) -> Optional[AlgoHashRate]:
|
||||
# X19 method, not sure compatibility
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
expected_rate = rpc_stats["STATS"][1]["total_rateideal"]
|
||||
try:
|
||||
rate_unit = rpc_stats["STATS"][1]["rate_unit"]
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_uptime(self, rpc_stats: dict = None) -> Optional[int]:
|
||||
if rpc_stats is None:
|
||||
try:
|
||||
rpc_stats = await self.rpc.stats()
|
||||
except APIError:
|
||||
pass
|
||||
|
||||
if rpc_stats is not None:
|
||||
try:
|
||||
return int(rpc_stats["STATS"][1]["Elapsed"])
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]:
|
||||
if web_get_system_info is None:
|
||||
try:
|
||||
@@ -357,7 +322,7 @@ class BlackMiner(StockFirmware):
|
||||
for item in web_summary["SUMMARY"][0]["status"]:
|
||||
try:
|
||||
if not item["status"] == "s":
|
||||
errors.append(X19Error(item["msg"]))
|
||||
errors.append(X19Error(error_message=item["msg"]))
|
||||
except KeyError:
|
||||
continue
|
||||
except LookupError:
|
||||
@@ -400,7 +365,7 @@ class BlackMiner(StockFirmware):
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -87,7 +87,9 @@ class IceRiver(StockFirmware):
|
||||
|
||||
if web_userpanel is not None:
|
||||
try:
|
||||
return [Fan(spd) for spd in web_userpanel["userpanel"]["data"]["fans"]]
|
||||
return [
|
||||
Fan(speed=spd) for spd in web_userpanel["userpanel"]["data"]["fans"]
|
||||
]
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -130,7 +132,7 @@ class IceRiver(StockFirmware):
|
||||
try:
|
||||
base_unit = web_userpanel["userpanel"]["data"]["unit"]
|
||||
return AlgoHashRate.SHA256(
|
||||
float(
|
||||
rate=float(
|
||||
web_userpanel["userpanel"]["data"]["rtpow"].replace(
|
||||
base_unit, ""
|
||||
)
|
||||
@@ -186,7 +188,8 @@ class IceRiver(StockFirmware):
|
||||
hb_list[idx].chip_temp = round(board["outtmp"])
|
||||
hb_list[idx].temp = round(board["intmp"])
|
||||
hb_list[idx].hashrate = AlgoHashRate.SHA256(
|
||||
float(board["rtpow"].replace("G", "")), HashUnit.SHA256.GH
|
||||
rate=float(board["rtpow"].replace("G", "")),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
hb_list[idx].chips = int(board["chipnum"])
|
||||
hb_list[idx].missing = False
|
||||
|
||||
@@ -187,11 +187,13 @@ class Innosilicon(CGMiner):
|
||||
try:
|
||||
if "Hash Rate H" in web_get_all["total_hash"].keys():
|
||||
return AlgoHashRate.SHA256(
|
||||
web_get_all["total_hash"]["Hash Rate H"], HashUnit.SHA256.H
|
||||
rate=float(web_get_all["total_hash"]["Hash Rate H"]),
|
||||
unit=HashUnit.SHA256.H,
|
||||
).into(self.algo.unit.default)
|
||||
elif "Hash Rate" in web_get_all["total_hash"].keys():
|
||||
return AlgoHashRate.SHA256(
|
||||
web_get_all["total_hash"]["Hash Rate"], HashUnit.SHA256.MH
|
||||
rate=float(web_get_all["total_hash"]["Hash Rate"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except KeyError:
|
||||
pass
|
||||
@@ -199,7 +201,8 @@ class Innosilicon(CGMiner):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["MHS 1m"], HashUnit.SHA256.MH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["MHS 1m"]),
|
||||
unit=HashUnit.SHA256.MH,
|
||||
).into(self.algo.unit.default)
|
||||
except (KeyError, IndexError):
|
||||
pass
|
||||
@@ -253,7 +256,7 @@ class Innosilicon(CGMiner):
|
||||
hashrate = board.get("Hash Rate H")
|
||||
if hashrate:
|
||||
hashboards[idx].hashrate = AlgoHashRate.SHA256(
|
||||
hashrate, HashUnit.SHA256.H
|
||||
rate=float(hashrate), unit=HashUnit.SHA256.H
|
||||
).into(self.algo.unit.default)
|
||||
|
||||
chip_temp = board.get("Temp max")
|
||||
|
||||
@@ -179,14 +179,15 @@ class LUXMiner(LuxOSFirmware):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
async def _get_hashboards(self, rpc_stats: dict = None) -> List[HashBoard]:
|
||||
hashboards = [
|
||||
HashBoard(idx, expected_chips=self.expected_chips)
|
||||
HashBoard(slot=idx, expected_chips=self.expected_chips)
|
||||
for idx in range(self.expected_hashboards)
|
||||
]
|
||||
|
||||
@@ -202,7 +203,8 @@ class LUXMiner(LuxOSFirmware):
|
||||
for idx in range(3):
|
||||
board_n = idx + 1
|
||||
hashboards[idx].hashrate = AlgoHashRate.SHA256(
|
||||
float(board_stats[f"chain_rate{board_n}"]), HashUnit.SHA256.GH
|
||||
rate=float(board_stats[f"chain_rate{board_n}"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[idx].chips = int(board_stats[f"chain_acn{board_n}"])
|
||||
chip_temp_data = list(
|
||||
@@ -253,7 +255,7 @@ class LUXMiner(LuxOSFirmware):
|
||||
if rpc_fans is not None:
|
||||
for fan in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(rpc_fans["FANS"][fan]["RPM"]))
|
||||
fans.append(Fan(speed=rpc_fans["FANS"][fan]["RPM"]))
|
||||
except (LookupError, ValueError, TypeError):
|
||||
fans.append(Fan())
|
||||
return fans
|
||||
@@ -275,7 +277,7 @@ class LUXMiner(LuxOSFirmware):
|
||||
except KeyError:
|
||||
rate_unit = "GH"
|
||||
return AlgoHashRate.SHA256(
|
||||
expected_rate, HashUnit.SHA256.from_str(rate_unit)
|
||||
rate=float(expected_rate), unit=HashUnit.SHA256.from_str(rate_unit)
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -99,7 +99,7 @@ class MaraMiner(MaraFirmware):
|
||||
|
||||
async def set_power_limit(self, wattage: int) -> bool:
|
||||
cfg = await self.get_config()
|
||||
cfg.mining_mode = MiningModeConfig.power_tuning(wattage)
|
||||
cfg.mining_mode = MiningModeConfig.power_tuning(power=wattage)
|
||||
await self.send_config(cfg)
|
||||
return True
|
||||
|
||||
@@ -179,13 +179,13 @@ class MaraMiner(MaraFirmware):
|
||||
for hb in web_hashboards["hashboards"]:
|
||||
idx = hb["index"]
|
||||
hashboards[idx].hashrate = AlgoHashRate.SHA256(
|
||||
hb["hashrate_average"], HashUnit.SHA256.GH
|
||||
rate=float(hb["hashrate_average"]), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
hashboards[idx].temp = round(
|
||||
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"]), 2
|
||||
sum(hb["temperature_pcb"]) / len(hb["temperature_pcb"])
|
||||
)
|
||||
hashboards[idx].chip_temp = round(
|
||||
sum(hb["temperature_chip"]) / len(hb["temperature_chip"]), 2
|
||||
sum(hb["temperature_chip"]) / len(hb["temperature_chip"])
|
||||
)
|
||||
hashboards[idx].chips = hb["asic_num"]
|
||||
hashboards[idx].serial_number = hb["serial_number"]
|
||||
@@ -243,7 +243,7 @@ class MaraMiner(MaraFirmware):
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
web_brief["hashrate_realtime"], HashUnit.SHA256.TH
|
||||
rate=float(web_brief["hashrate_realtime"]), unit=HashUnit.SHA256.TH
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
@@ -259,7 +259,7 @@ class MaraMiner(MaraFirmware):
|
||||
fans = []
|
||||
for n in range(self.expected_fans):
|
||||
try:
|
||||
fans.append(Fan(web_fans["fans"][n]["current_speed"]))
|
||||
fans.append(Fan(speed=web_fans["fans"][n]["current_speed"]))
|
||||
except (IndexError, KeyError):
|
||||
pass
|
||||
return fans
|
||||
@@ -291,7 +291,7 @@ class MaraMiner(MaraFirmware):
|
||||
if web_brief is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
web_brief["hashrate_ideal"], HashUnit.SHA256.GH
|
||||
rate=float(web_brief["hashrate_ideal"]), unit=HashUnit.SHA256.GH
|
||||
).into(self.algo.unit.default)
|
||||
except LookupError:
|
||||
pass
|
||||
|
||||
@@ -208,7 +208,8 @@ class VNish(VNishFirmware, BMMiner):
|
||||
if rpc_summary is not None:
|
||||
try:
|
||||
return AlgoHashRate.SHA256(
|
||||
rpc_summary["SUMMARY"][0]["GHS 5s"], HashUnit.SHA256.GH
|
||||
rate=float(rpc_summary["SUMMARY"][0]["GHS 5s"]),
|
||||
unit=HashUnit.SHA256.GH,
|
||||
).into(self.algo.unit.default)
|
||||
except (LookupError, ValueError, TypeError):
|
||||
pass
|
||||
|
||||
@@ -495,6 +495,7 @@ class MinerProtocol(Protocol):
|
||||
function = getattr(self, getattr(self.data_locations, data_name).cmd)
|
||||
miner_data[data_name] = await function(**args_to_send)
|
||||
except Exception as e:
|
||||
raise e
|
||||
raise APIError(
|
||||
f"Failed to call {data_name} on {self} while getting data."
|
||||
) from e
|
||||
|
||||
Reference in New Issue
Block a user