Merge pull request #143 from jpcomps/master

feat: add additional ePIC UMC features, add Output Voltage to MinerData
This commit is contained in:
Brett Rowan
2024-05-14 08:47:00 -06:00
committed by GitHub
7 changed files with 94 additions and 18 deletions

View File

@@ -156,6 +156,7 @@ class VOptAlgo(MinerConfigValue):
return "VoltageOptimizer" return "VoltageOptimizer"
@dataclass
class ChipTuneAlgo(MinerConfigValue): class ChipTuneAlgo(MinerConfigValue):
mode: str = field(init=False, default="chip_tune") mode: str = field(init=False, default="chip_tune")
@@ -249,6 +250,8 @@ class MiningModePowerTune(MinerConfigValue):
class MiningModeHashrateTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue):
mode: str = field(init=False, default="hashrate_tuning") mode: str = field(init=False, default="hashrate_tuning")
hashrate: int = None hashrate: int = None
throttle_limit: int = None
throttle_step: int = None
algo: TunerAlgo = field(default_factory=TunerAlgo.default) algo: TunerAlgo = field(default_factory=TunerAlgo.default)
@classmethod @classmethod
@@ -256,6 +259,10 @@ class MiningModeHashrateTune(MinerConfigValue):
cls_conf = {} cls_conf = {}
if dict_conf.get("hashrate"): if dict_conf.get("hashrate"):
cls_conf["hashrate"] = dict_conf["hashrate"] cls_conf["hashrate"] = dict_conf["hashrate"]
if dict_conf.get("throttle_limit"):
cls_conf["throttle_limit"] = dict_conf["throttle_limit"]
if dict_conf.get("throttle_step"):
cls_conf["throttle_step"] = dict_conf["throttle_step"]
if dict_conf.get("algo"): if dict_conf.get("algo"):
cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"]) cls_conf["algo"] = TunerAlgo.from_dict(dict_conf["algo"])
@@ -292,7 +299,17 @@ class MiningModeHashrateTune(MinerConfigValue):
return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}} return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}}
def as_epic(self) -> dict: def as_epic(self) -> dict:
return {"ptune": {"algo": self.algo.as_epic(), "target": self.hashrate}} mode = {
"ptune": {
"algo": self.algo.as_epic(),
"target": self.hashrate,
}
}
if self.throttle_limit is not None:
mode["ptune"]["min_throttle"] = self.throttle_limit
if self.throttle_step is not None:
mode["ptune"]["throttle_step"] = self.throttle_step
return mode
def as_mara(self) -> dict: def as_mara(self) -> dict:
return { return {
@@ -416,13 +433,19 @@ class MiningModeConfig(MinerConfigOption):
algo_info = web_conf["PerpetualTune"]["Algorithm"] algo_info = web_conf["PerpetualTune"]["Algorithm"]
if algo_info.get("VoltageOptimizer") is not None: if algo_info.get("VoltageOptimizer") is not None:
return cls.hashrate_tuning( return cls.hashrate_tuning(
hashrate=algo_info["VoltageOptimizer"]["Target"], hashrate=algo_info["VoltageOptimizer"].get("Target"),
algo=TunerAlgo.voltage_optimizer, throttle_limit=algo_info["VoltageOptimizer"].get(
"Min Throttle Target"
),
throttle_step=algo_info["VoltageOptimizer"].get(
"Throttle Step"
),
algo=TunerAlgo.voltage_optimizer(),
) )
else: else:
return cls.hashrate_tuning( return cls.hashrate_tuning(
hashrate=algo_info["ChipTune"]["Target"], hashrate=algo_info["ChipTune"]["Target"],
algo=TunerAlgo.chip_tune, algo=TunerAlgo.chip_tune(),
) )
else: else:
return cls.normal() return cls.normal()

View File

@@ -49,7 +49,9 @@ class TemperatureConfig(MinerConfigValue):
else: else:
temps_config["fans"]["Auto"]["Target Temperature"] = 60 temps_config["fans"]["Auto"]["Target Temperature"] = 60
if self.danger is not None: if self.danger is not None:
temps_config["temps"]["shutdown"] = self.danger temps_config["temps"]["critical"] = self.danger
if self.hot is not None:
temps_config["temps"]["shutdown"] = self.hot
return temps_config return temps_config
@classmethod @classmethod
@@ -74,16 +76,20 @@ class TemperatureConfig(MinerConfigValue):
@classmethod @classmethod
def from_epic(cls, web_conf: dict) -> "TemperatureConfig": def from_epic(cls, web_conf: dict) -> "TemperatureConfig":
try: try:
dangerous_temp = web_conf["Misc"]["Shutdown Temp"] dangerous_temp = web_conf["Misc"]["Critical Temp"]
except KeyError: except KeyError:
dangerous_temp = None dangerous_temp = None
try:
hot_temp = web_conf["Misc"]["Shutdown Temp"]
except KeyError:
hot_temp = None
# Need to do this in two blocks to avoid KeyError if one is missing # Need to do this in two blocks to avoid KeyError if one is missing
try: try:
target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"] target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"]
except KeyError: except KeyError:
target_temp = None target_temp = None
return cls(target=target_temp, danger=dangerous_temp) return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp)
@classmethod @classmethod
def from_vnish(cls, web_settings: dict) -> "TemperatureConfig": def from_vnish(cls, web_settings: dict) -> "TemperatureConfig":

View File

@@ -50,6 +50,7 @@ class MinerData:
temperature_avg: The average temperature across the boards. Calculated automatically. temperature_avg: The average temperature across the boards. Calculated automatically.
env_temp: The environment temps as a float. env_temp: The environment temps as a float.
wattage: Current power draw of the miner as an int. wattage: Current power draw of the miner as an int.
voltage: Current output voltage of the PSU as an float.
wattage_limit: Power limit of the miner as an int. wattage_limit: Power limit of the miner as an int.
fans: A list of fans on the miner with their speeds. fans: A list of fans on the miner with their speeds.
fan_psu: The speed of the PSU on the fan if the miner collects it. fan_psu: The speed of the PSU on the fan if the miner collects it.
@@ -84,6 +85,7 @@ class MinerData:
env_temp: float = None env_temp: float = None
wattage: int = None wattage: int = None
wattage_limit: int = field(init=False) wattage_limit: int = field(init=False)
voltage: float = None
_wattage_limit: int = field(repr=False, default=None) _wattage_limit: int = field(repr=False, default=None)
fans: List[Fan] = field(default_factory=list) fans: List[Fan] = field(default_factory=list)
fan_psu: int = None fan_psu: int = None

View File

@@ -58,6 +58,10 @@ EPIC_DATA_LOC = DataLocations(
"_get_wattage", "_get_wattage",
[WebAPICommand("web_summary", "summary")], [WebAPICommand("web_summary", "summary")],
), ),
str(DataOptions.VOLTAGE): DataFunction(
"_get_voltage",
[WebAPICommand("web_summary", "summary")],
),
str(DataOptions.FANS): DataFunction( str(DataOptions.FANS): DataFunction(
"_get_fans", "_get_fans",
[WebAPICommand("web_summary", "summary")], [WebAPICommand("web_summary", "summary")],
@@ -119,6 +123,7 @@ class ePIC(BaseMiner):
# Temps # Temps
if not conf.get("temps", {}) == {}: if not conf.get("temps", {}) == {}:
await self.web.set_shutdown_temp(conf["temps"]["shutdown"]) await self.web.set_shutdown_temp(conf["temps"]["shutdown"])
await self.web.set_critical_temp(conf["temps"]["critical"])
# Fans # Fans
# set with sub-keys instead of conf["fans"] because sometimes both can be set # set with sub-keys instead of conf["fans"] because sometimes both can be set
if not conf["fans"].get("Manual", {}) == {}: if not conf["fans"].get("Manual", {}) == {}:
@@ -129,7 +134,7 @@ class ePIC(BaseMiner):
# Mining Mode -- Need to handle that you may not be able to change while miner is tuning # Mining Mode -- Need to handle that you may not be able to change while miner is tuning
if conf["ptune"].get("enabled", True): if conf["ptune"].get("enabled", True):
await self.web.set_ptune_enable(True) await self.web.set_ptune_enable(True)
await self.web.set_ptune_algo(**conf["ptune"]) await self.web.set_ptune_algo(conf["ptune"])
## Pools ## Pools
await self.web.set_pools(conf["pools"]) await self.web.set_pools(conf["pools"])
@@ -216,6 +221,20 @@ class ePIC(BaseMiner):
except KeyError: except KeyError:
pass pass
async def _get_voltage(self, web_summary: dict = None) -> Optional[float]:
if web_summary is None:
try:
web_summary = await self.web.summary()
except APIError:
pass
if web_summary is not None:
try:
voltage = web_summary["Power Supply Stats"]["Output Voltage"]
return voltage
except KeyError:
pass
async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]: async def _get_hashrate(self, web_summary: dict = None) -> Optional[float]:
if web_summary is None: if web_summary is None:
try: try:
@@ -306,9 +325,20 @@ class ePIC(BaseMiner):
HashBoard(slot=i, expected_chips=self.expected_chips) HashBoard(slot=i, expected_chips=self.expected_chips)
for i in range(self.expected_hashboards) for i in range(self.expected_hashboards)
] ]
if web_summary is not None and web_capabilities is not None:
if web_summary.get("HBs") is not None: if web_summary.get("HBs") is not None:
for hb in web_summary["HBs"]: for hb in web_summary["HBs"]:
num_of_chips = web_capabilities["Performance Estimator"]["Chip Count"] if web_capabilities.get("Performance Estimator") is not None:
num_of_chips = web_capabilities["Performance Estimator"][
"Chip Count"
]
else:
num_of_chips = self.expected_chips
if web_capabilities.get("Board Serial Numbers") is not None:
if hb["Index"] < len(web_capabilities["Board Serial Numbers"]):
hb_list[hb["Index"]].serial_number = web_capabilities[
"Board Serial Numbers"
][hb["Index"]]
hashrate = hb["Hashrate"][0] hashrate = hb["Hashrate"][0]
# Update the Hashboard object # Update the Hashboard object
hb_list[hb["Index"]].missing = False hb_list[hb["Index"]].missing = False

View File

@@ -249,6 +249,14 @@ class MinerProtocol(Protocol):
""" """
return await self._get_wattage() return await self._get_wattage()
async def get_voltage(self) -> Optional[float]:
"""Get output voltage of the PSU as a float.
Returns:
Output voltage of the PSU as an float.
"""
return await self._get_voltage()
async def get_wattage_limit(self) -> Optional[int]: async def get_wattage_limit(self) -> Optional[int]:
"""Get wattage limit from the miner as an int. """Get wattage limit from the miner as an int.
@@ -337,6 +345,9 @@ class MinerProtocol(Protocol):
async def _get_wattage(self) -> Optional[int]: async def _get_wattage(self) -> Optional[int]:
pass pass
async def _get_voltage(self) -> Optional[float]:
pass
async def _get_wattage_limit(self) -> Optional[int]: async def _get_wattage_limit(self) -> Optional[int]:
pass pass

View File

@@ -37,6 +37,7 @@ class DataOptions(Enum):
IS_MINING = "is_mining" IS_MINING = "is_mining"
UPTIME = "uptime" UPTIME = "uptime"
CONFIG = "config" CONFIG = "config"
VOLTAGE = "voltage"
def __str__(self): def __str__(self):
return self.value return self.value

View File

@@ -97,6 +97,9 @@ class ePICWebAPI(BaseWebAPI):
async def set_shutdown_temp(self, params: int) -> dict: async def set_shutdown_temp(self, params: int) -> dict:
return await self.send_command("shutdowntemp", param=params) return await self.send_command("shutdowntemp", param=params)
async def set_critical_temp(self, params: int) -> dict:
return await self.send_command("criticaltemp", param=params)
async def set_fan(self, params: dict) -> dict: async def set_fan(self, params: dict) -> dict:
return await self.send_command("fanspeed", param=params) return await self.send_command("fanspeed", param=params)