diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index 2c8719db..77a6a1f6 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -156,6 +156,7 @@ class VOptAlgo(MinerConfigValue): return "VoltageOptimizer" +@dataclass class ChipTuneAlgo(MinerConfigValue): mode: str = field(init=False, default="chip_tune") @@ -249,6 +250,8 @@ class MiningModePowerTune(MinerConfigValue): class MiningModeHashrateTune(MinerConfigValue): mode: str = field(init=False, default="hashrate_tuning") hashrate: int = None + throttle_limit: int = None + throttle_step: int = None algo: TunerAlgo = field(default_factory=TunerAlgo.default) @classmethod @@ -256,6 +259,10 @@ class MiningModeHashrateTune(MinerConfigValue): cls_conf = {} if dict_conf.get("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"): 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}} 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: return { @@ -416,13 +433,19 @@ class MiningModeConfig(MinerConfigOption): algo_info = web_conf["PerpetualTune"]["Algorithm"] if algo_info.get("VoltageOptimizer") is not None: return cls.hashrate_tuning( - hashrate=algo_info["VoltageOptimizer"]["Target"], - algo=TunerAlgo.voltage_optimizer, + hashrate=algo_info["VoltageOptimizer"].get("Target"), + throttle_limit=algo_info["VoltageOptimizer"].get( + "Min Throttle Target" + ), + throttle_step=algo_info["VoltageOptimizer"].get( + "Throttle Step" + ), + algo=TunerAlgo.voltage_optimizer(), ) else: return cls.hashrate_tuning( hashrate=algo_info["ChipTune"]["Target"], - algo=TunerAlgo.chip_tune, + algo=TunerAlgo.chip_tune(), ) else: return cls.normal() diff --git a/pyasic/config/temperature.py b/pyasic/config/temperature.py index b5674f31..e8720add 100644 --- a/pyasic/config/temperature.py +++ b/pyasic/config/temperature.py @@ -49,7 +49,9 @@ class TemperatureConfig(MinerConfigValue): else: temps_config["fans"]["Auto"]["Target Temperature"] = 60 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 @classmethod @@ -74,16 +76,20 @@ class TemperatureConfig(MinerConfigValue): @classmethod def from_epic(cls, web_conf: dict) -> "TemperatureConfig": try: - dangerous_temp = web_conf["Misc"]["Shutdown Temp"] + dangerous_temp = web_conf["Misc"]["Critical Temp"] except KeyError: 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 try: target_temp = web_conf["Fans"]["Fan Mode"]["Auto"]["Target Temperature"] except KeyError: target_temp = None - return cls(target=target_temp, danger=dangerous_temp) + return cls(target=target_temp, hot=hot_temp, danger=dangerous_temp) @classmethod def from_vnish(cls, web_settings: dict) -> "TemperatureConfig": diff --git a/pyasic/data/__init__.py b/pyasic/data/__init__.py index 8051ea1d..6499ede1 100644 --- a/pyasic/data/__init__.py +++ b/pyasic/data/__init__.py @@ -50,6 +50,7 @@ class MinerData: temperature_avg: The average temperature across the boards. Calculated automatically. env_temp: The environment temps as a float. 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. 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. @@ -84,6 +85,7 @@ class MinerData: env_temp: float = None wattage: int = None wattage_limit: int = field(init=False) + voltage: float = None _wattage_limit: int = field(repr=False, default=None) fans: List[Fan] = field(default_factory=list) fan_psu: int = None diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index ff74e135..79972749 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -58,6 +58,10 @@ EPIC_DATA_LOC = DataLocations( "_get_wattage", [WebAPICommand("web_summary", "summary")], ), + str(DataOptions.VOLTAGE): DataFunction( + "_get_voltage", + [WebAPICommand("web_summary", "summary")], + ), str(DataOptions.FANS): DataFunction( "_get_fans", [WebAPICommand("web_summary", "summary")], @@ -119,6 +123,7 @@ class ePIC(BaseMiner): # Temps if not conf.get("temps", {}) == {}: await self.web.set_shutdown_temp(conf["temps"]["shutdown"]) + await self.web.set_critical_temp(conf["temps"]["critical"]) # Fans # set with sub-keys instead of conf["fans"] because sometimes both can be set 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 if conf["ptune"].get("enabled", 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 await self.web.set_pools(conf["pools"]) @@ -216,6 +221,20 @@ class ePIC(BaseMiner): except KeyError: 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]: if web_summary is None: try: @@ -306,16 +325,27 @@ class ePIC(BaseMiner): HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) ] - if web_summary.get("HBs") is not None: - for hb in web_summary["HBs"]: - num_of_chips = web_capabilities["Performance Estimator"]["Chip Count"] - 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 + if web_summary is not None and web_capabilities is not None: + if web_summary.get("HBs") is not None: + for hb in web_summary["HBs"]: + 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] + # 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]: if web_summary is None: diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index 0126b59c..82d63a33 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -249,6 +249,14 @@ class MinerProtocol(Protocol): """ 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]: """Get wattage limit from the miner as an int. @@ -337,6 +345,9 @@ class MinerProtocol(Protocol): async def _get_wattage(self) -> Optional[int]: pass + async def _get_voltage(self) -> Optional[float]: + pass + async def _get_wattage_limit(self) -> Optional[int]: pass diff --git a/pyasic/miners/data.py b/pyasic/miners/data.py index a21ccd16..9618e008 100644 --- a/pyasic/miners/data.py +++ b/pyasic/miners/data.py @@ -37,6 +37,7 @@ class DataOptions(Enum): IS_MINING = "is_mining" UPTIME = "uptime" CONFIG = "config" + VOLTAGE = "voltage" def __str__(self): return self.value diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py index dd4107ca..8f0e1b41 100644 --- a/pyasic/web/epic.py +++ b/pyasic/web/epic.py @@ -97,6 +97,9 @@ class ePICWebAPI(BaseWebAPI): async def set_shutdown_temp(self, params: int) -> dict: 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: return await self.send_command("fanspeed", param=params)