diff --git a/pyasic/config/__init__.py b/pyasic/config/__init__.py index 5dfc5c8d..fded4944 100644 --- a/pyasic/config/__init__.py +++ b/pyasic/config/__init__.py @@ -113,10 +113,17 @@ class MinerConfig: **self.fan_mode.as_epic(), **self.temperature.as_epic(), **self.mining_mode.as_epic(), - **self.pools.as_epic(user_suffix=user_suffix), + **self.pools.as_epic(), **self.power_scaling.as_epic(), } + def as_auradine(self, user_suffix: str = None) -> dict: + return { + **self.fan_mode.as_auradine(), + **self.mining_mode.as_auradine(), + **self.pools.as_auradine(user_suffix=user_suffix), + } + @classmethod def from_dict(cls, dict_conf: dict) -> "MinerConfig": return cls( @@ -189,6 +196,14 @@ class MinerConfig: mining_mode=MiningModeConfig.from_vnish(web_settings), ) + @classmethod + def from_auradine(cls, web_conf: dict) -> "MinerConfig": + return cls( + pools=PoolConfig.from_api(web_conf["pools"][0]), + fan_mode=FanModeConfig.from_auradine(web_conf["fans"][0]), + mining_mode=MiningModeConfig.from_auradine(web_conf["mode"][0]), + ) + def merge(a: dict, b: dict) -> dict: result = deepcopy(a) diff --git a/pyasic/config/base.py b/pyasic/config/base.py index df5946ac..0d416acd 100644 --- a/pyasic/config/base.py +++ b/pyasic/config/base.py @@ -53,6 +53,9 @@ class MinerConfigOption(Enum): def as_vnish(self) -> dict: return self.value.as_vnish() + def as_auradine(self) -> dict: + return self.value.as_auradine() + def __call__(self, *args, **kwargs): return self.value(*args, **kwargs) @@ -99,3 +102,6 @@ class MinerConfigValue: def as_vnish(self) -> dict: return {} + + def as_auradine(self) -> dict: + return {} diff --git a/pyasic/config/fans.py b/pyasic/config/fans.py index 04d95434..82565596 100644 --- a/pyasic/config/fans.py +++ b/pyasic/config/fans.py @@ -92,6 +92,9 @@ class FanModeManual(MinerConfigValue): "fan_control": {"min_fans": self.minimum_fans, "speed": self.speed}, } + def as_auradine(self) -> dict: + return {"fan": {"percentage": self.speed}} + @dataclass class FanModeImmersion(MinerConfigValue): @@ -107,6 +110,9 @@ class FanModeImmersion(MinerConfigValue): def as_bosminer(self) -> dict: return {"temp_control": {"mode": "disabled"}} + def as_auradine(self) -> dict: + return {"fan": {"percentage": 0}} + class FanModeConfig(MinerConfigOption): normal = FanModeNormal @@ -202,3 +208,13 @@ class FanModeConfig(MinerConfigOption): if "minimumRequiredFans" in keys: conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"]) return cls.manual(**conf) + + @classmethod + def from_auradine(cls, web_fan: dict): + try: + fan_data = web_fan["Fan"][0] + fan_1_max = fan_data["Max"] + fan_1_target = fan_data["Target"] + return cls.manual(speed=round((fan_1_target / fan_1_max) * 100)) + except LookupError: + return cls.default() diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index b4101a51..4e29c049 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -43,6 +43,9 @@ class MiningModeNormal(MinerConfigValue): def as_wm(self) -> dict: return {"mode": self.mode} + def as_auradine(self) -> dict: + return {"mode": {"mode": self.mode}} + @dataclass class MiningModeSleep(MinerConfigValue): @@ -58,6 +61,9 @@ class MiningModeSleep(MinerConfigValue): def as_wm(self) -> dict: return {"mode": self.mode} + def as_auradine(self) -> dict: + return {"mode": {"sleep": "on"}} + @dataclass class MiningModeLPM(MinerConfigValue): @@ -73,6 +79,9 @@ class MiningModeLPM(MinerConfigValue): def as_wm(self) -> dict: return {"mode": self.mode} + def as_auradine(self) -> dict: + return {"mode": {"mode": "eco"}} + @dataclass class MiningModeHPM(MinerConfigValue): @@ -88,6 +97,9 @@ class MiningModeHPM(MinerConfigValue): def as_wm(self) -> dict: return {"mode": self.mode} + def as_auradine(self) -> dict: + return {"mode": {"mode": "turbo"}} + @dataclass class MiningModePowerTune(MinerConfigValue): @@ -123,6 +135,9 @@ class MiningModePowerTune(MinerConfigValue): ), } + def as_auradine(self) -> dict: + return {"mode": {"mode": "custom", "tune": "power", "power": self.power}} + @dataclass class MiningModeHashrateTune(MinerConfigValue): @@ -152,6 +167,9 @@ class MiningModeHashrateTune(MinerConfigValue): ) } + def as_auradine(self) -> dict: + return {"mode": {"mode": "custom", "tune": "ths", "ths": self.hashrate}} + @dataclass class ManualBoardSettings(MinerConfigValue): @@ -330,3 +348,22 @@ class MiningModeConfig(MinerConfigOption): return cls.hashrate_tuning( int(tuner_conf["hashrateTarget"]["terahashPerSecond"]) ) + + @classmethod + def from_auradine(cls, web_mode: dict): + try: + mode_data = web_mode["Mode"][0] + if mode_data.get("Sleep") == "on": + return cls.sleep() + if mode_data.get("Mode") == "normal": + return cls.normal() + if mode_data.get("Mode") == "eco": + return cls.low() + if mode_data.get("Mode") == "turbo": + return cls.high() + if mode_data.get("Ths") is not None: + return cls.hashrate_tuning(mode_data["Ths"]) + if mode_data.get("Power") is not None: + return cls.power_tuning(mode_data["Power"]) + except LookupError: + return cls.default() diff --git a/pyasic/config/pools.py b/pyasic/config/pools.py index e067421b..4d8eb443 100644 --- a/pyasic/config/pools.py +++ b/pyasic/config/pools.py @@ -27,7 +27,7 @@ class Pool(MinerConfigValue): user: str password: str - def as_am_modern(self, user_suffix: str = None): + def as_am_modern(self, user_suffix: str = None) -> dict: if user_suffix is not None: return { "url": self.url, @@ -36,7 +36,7 @@ class Pool(MinerConfigValue): } return {"url": self.url, "user": self.user, "pass": self.password} - def as_wm(self, idx: int = 1, user_suffix: str = None): + def as_wm(self, idx: int = 1, user_suffix: str = None) -> dict: if user_suffix is not None: return { f"pool_{idx}": self.url, @@ -49,7 +49,7 @@ class Pool(MinerConfigValue): f"passwd_{idx}": self.password, } - def as_am_old(self, idx: int = 1, user_suffix: str = None): + def as_am_old(self, idx: int = 1, user_suffix: str = None) -> dict: if user_suffix is not None: return { f"_ant_pool{idx}url": self.url, @@ -62,7 +62,7 @@ class Pool(MinerConfigValue): f"_ant_pool{idx}pw": self.password, } - def as_goldshell(self, user_suffix: str = None): + def as_goldshell(self, user_suffix: str = None) -> dict: if user_suffix is not None: return { "url": self.url, @@ -71,12 +71,12 @@ class Pool(MinerConfigValue): } return {"url": self.url, "user": self.user, "pass": self.password} - def as_avalon(self, user_suffix: str = None): + def as_avalon(self, user_suffix: str = None) -> str: if user_suffix is not None: return ",".join([self.url, f"{self.user}{user_suffix}", self.password]) return ",".join([self.url, self.user, self.password]) - def as_inno(self, idx: int = 1, user_suffix: str = None): + def as_inno(self, idx: int = 1, user_suffix: str = None) -> dict: if user_suffix is not None: return { f"Pool{idx}": self.url, @@ -89,7 +89,7 @@ class Pool(MinerConfigValue): f"Password{idx}": self.password, } - def as_bosminer(self, user_suffix: str = None): + def as_bosminer(self, user_suffix: str = None) -> dict: if user_suffix is not None: return { "url": self.url, @@ -98,6 +98,15 @@ class Pool(MinerConfigValue): } return {"url": self.url, "user": self.user, "password": self.password} + def as_auradine(self, user_suffix: str = None) -> dict: + if user_suffix is not None: + return { + "url": self.url, + "user": f"{self.user}{user_suffix}", + "pass": self.password, + } + return {"url": self.url, "user": self.user, "pass": self.password} + @classmethod def from_dict(cls, dict_conf: Union[dict, None]) -> "Pool": return cls( @@ -241,6 +250,9 @@ class PoolGroup(MinerConfigValue): return conf return {"name": "Group", "pool": []} + def as_auradine(self, user_suffix: str = None) -> list: + return [p.as_auradine(user_suffix=user_suffix) for p in self.pools] + @classmethod def from_dict(cls, dict_conf: Union[dict, None]) -> "PoolGroup": cls_conf = {} @@ -296,7 +308,7 @@ class PoolGroup(MinerConfigValue): return cls([Pool.from_vnish(p) for p in web_settings_pools]) @classmethod - def from_boser(cls, grpc_pool_group: dict): + def from_boser(cls, grpc_pool_group: dict) -> "PoolGroup": try: return cls( pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]], @@ -373,6 +385,15 @@ class PoolConfig(MinerConfigValue): def as_boser(self, user_suffix: str = None) -> dict: return {} + def as_auradine(self, user_suffix: str = None) -> dict: + if len(self.groups) > 0: + return { + "updatepools": { + "pools": self.groups[0].as_auradine(user_suffix=user_suffix) + } + } + return {"updatepools": {"pools": PoolGroup().as_auradine()}} + @classmethod def from_api(cls, api_pools: dict) -> "PoolConfig": try: @@ -417,7 +438,7 @@ class PoolConfig(MinerConfigValue): return cls() @classmethod - def from_boser(cls, grpc_miner_conf: dict): + def from_boser(cls, grpc_miner_conf: dict) -> "PoolConfig": try: return cls( groups=[ diff --git a/pyasic/miners/backends/auradine.py b/pyasic/miners/backends/auradine.py index 54d8ced9..b23efa8d 100644 --- a/pyasic/miners/backends/auradine.py +++ b/pyasic/miners/backends/auradine.py @@ -13,7 +13,10 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +import logging +from enum import Enum +from pyasic import APIError, MinerConfig from pyasic.miners.base import BaseMiner, DataLocations from pyasic.rpc.gcminer import GCMinerRPCAPI from pyasic.web.auradine import FluxWebAPI @@ -21,6 +24,35 @@ from pyasic.web.auradine import FluxWebAPI AURADINE_DATA_LOC = DataLocations(**{}) +class AuradineLEDColors(Enum): + OFF = 0 + GREEN = 1 + RED = 2 + YELLOW = 3 + GREEN_FLASHING = 4 + RED_FLASHING = 5 + YELLOW_FLASHING = 6 + + def __int__(self): + return self.value + + +class AuradineLEDCodes(Enum): + NO_POWER = 1 + NORMAL = 2 + LOCATE_MINER = 3 + TEMPERATURE = 4 + POOL_CONFIG = 5 + NETWORK = 6 + CONTROL_BOARD = 7 + HASH_RATE_LOW = 8 + CUSTOM1 = 101 + CUSTOM2 = 102 + + def __int__(self): + return self.value + + class Auradine(BaseMiner): """Base handler for Auradine miners""" @@ -30,3 +62,68 @@ class Auradine(BaseMiner): web: FluxWebAPI data_locations = AURADINE_DATA_LOC + + supports_shutdown = True + supports_autotuning = True + + async def fault_light_on(self) -> bool: + return await self.web.set_led(code=int(AuradineLEDCodes.LOCATE_MINER)) + + async def fault_light_off(self) -> bool: + return await self.web.set_led(code=int(AuradineLEDCodes.NORMAL)) + + async def reboot(self) -> bool: + try: + await self.web.reboot() + except APIError: + return False + return True + + async def restart_backend(self) -> bool: + try: + await self.web.restart_gcminer() + except APIError: + return False + return True + + async def stop_mining(self) -> bool: + try: + await self.web.set_mode(sleep="on") + except APIError: + return False + return True + + async def resume_mining(self) -> bool: + try: + await self.web.set_mode(sleep="off") + except APIError: + return False + return True + + async def set_power_limit(self, wattage: int) -> bool: + try: + await self.web.set_mode(mode="custom", tune="power", power=wattage) + except APIError: + return False + return True + + async def get_config(self) -> MinerConfig: + try: + web_conf = await self.web.multicommand("pools", "mode", "fan") + return MinerConfig.from_auradine(web_conf=web_conf) + except APIError as e: + logging.warning(e) + except LookupError: + pass + return MinerConfig() + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + self.config = config + + conf = config.as_auradine(user_suffix=user_suffix) + for key in conf.keys(): + await self.web.send_command(command=key, **conf[key]) + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## diff --git a/pyasic/web/auradine.py b/pyasic/web/auradine.py index 31da7883..979ad805 100644 --- a/pyasic/web/auradine.py +++ b/pyasic/web/auradine.py @@ -192,7 +192,10 @@ class FluxWebAPI(BaseWebAPI): async def get_led(self): return await self.send_command("led") - async def set_led(self, code: int, led_1: int, led_2: int, msg: str = ""): + async def set_led(self, code: int): + return await self.send_command("led", code=code) + + async def set_led_custom(self, code: int, led_1: int, led_2: int, msg: str): return await self.send_command( "led", code=code, led1=led_1, led2=led_2, msg=msg )