Merge pull request #143 from jpcomps/master
feat: add additional ePIC UMC features, add Output Voltage to MinerData
This commit is contained in:
@@ -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()
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,16 +325,27 @@ 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.get("HBs") is not None:
|
if web_summary is not None and web_capabilities is not None:
|
||||||
for hb in web_summary["HBs"]:
|
if web_summary.get("HBs") is not None:
|
||||||
num_of_chips = web_capabilities["Performance Estimator"]["Chip Count"]
|
for hb in web_summary["HBs"]:
|
||||||
hashrate = hb["Hashrate"][0]
|
if web_capabilities.get("Performance Estimator") is not None:
|
||||||
# Update the Hashboard object
|
num_of_chips = web_capabilities["Performance Estimator"][
|
||||||
hb_list[hb["Index"]].missing = False
|
"Chip Count"
|
||||||
hb_list[hb["Index"]].hashrate = round(hashrate / 1000000, 2)
|
]
|
||||||
hb_list[hb["Index"]].chips = num_of_chips
|
else:
|
||||||
hb_list[hb["Index"]].temp = hb["Temperature"]
|
num_of_chips = self.expected_chips
|
||||||
return hb_list
|
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]
|
||||||
|
# Update the Hashboard object
|
||||||
|
hb_list[hb["Index"]].missing = False
|
||||||
|
hb_list[hb["Index"]].hashrate = round(hashrate / 1000000, 2)
|
||||||
|
hb_list[hb["Index"]].chips = num_of_chips
|
||||||
|
hb_list[hb["Index"]].temp = hb["Temperature"]
|
||||||
|
return hb_list
|
||||||
|
|
||||||
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
|
async def _is_mining(self, web_summary, *args, **kwargs) -> Optional[bool]:
|
||||||
if web_summary is None:
|
if web_summary is None:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user