From e45e51ce658eec1343049de78fabbd8a538ff108 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 13:09:23 -0700 Subject: [PATCH] refactor: fix merge. --- pyasic/config/power_scaling.py | 77 ++++++--- pyasic/miners/antminer/hiveon/X9/T9.py | 6 +- pyasic/miners/backends/bosminer_old.py | 0 pyasic/miners/backends/braiins_os.py | 200 ++++++++++------------- pyasic/miners/backends/cgminer_avalon.py | 59 ++++--- pyasic/miners/backends/hiveon.py | 40 ++--- pyasic/miners/backends/innosilicon.py | 49 +++--- pyasic/miners/base.py | 2 +- pyasic/web/__init__.py | 1 + pyasic/web/antminer.py | 8 +- pyasic/web/braiins_os/__init__.py | 27 ++- pyasic/web/braiins_os/graphql.py | 5 +- pyasic/web/braiins_os/grpc.py | 101 ++++++++++-- pyasic/web/braiins_os/luci.py | 5 +- pyasic/web/epic.py | 5 +- pyasic/web/goldshell.py | 12 +- pyasic/web/innosilicon.py | 6 +- pyasic/web/vnish.py | 8 +- tests/miners_tests/__init__.py | 41 ++--- 19 files changed, 373 insertions(+), 279 deletions(-) delete mode 100644 pyasic/miners/backends/bosminer_old.py diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index b373e51d..3c64ec26 100644 --- a/pyasic/config/power_scaling.py +++ b/pyasic/config/power_scaling.py @@ -17,7 +17,13 @@ from dataclasses import dataclass, field from typing import Union from pyasic.config.base import MinerConfigOption, MinerConfigValue -from pyasic.web.braiins_os.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours +from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( + DpsPowerTarget, + DpsTarget, + Hours, + Power, + SetDpsRequest, +) @dataclass @@ -37,13 +43,8 @@ class PowerScalingShutdownEnabled(MinerConfigValue): return cfg - def as_bos_grpc(self) -> dict: - cfg = {"enable_shutdown ": True} - - if self.duration is not None: - cfg["shutdown_duration"] = Hours(self.duration) - - return cfg + def as_boser(self) -> dict: + return {"enable_shutdown": True, "shutdown_duration": self.duration} @dataclass @@ -57,7 +58,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue): def as_bosminer(self) -> dict: return {"shutdown_enabled": False} - def as_bos_grpc(self) -> dict: + def as_boser(self) -> dict: return {"enable_shutdown ": False} @@ -88,6 +89,19 @@ class PowerScalingShutdown(MinerConfigOption): return cls.disabled() return None + @classmethod + def from_boser(cls, power_scaling_conf: dict): + sd_enabled = power_scaling_conf.get("shutdownEnabled") + if sd_enabled is not None: + if sd_enabled: + try: + return cls.enabled(power_scaling_conf["shutdownDuration"]["hours"]) + except KeyError: + return cls.enabled() + else: + return cls.disabled() + return None + @dataclass class PowerScalingEnabled(MinerConfigValue): @@ -133,20 +147,19 @@ class PowerScalingEnabled(MinerConfigValue): return {"power_scaling": cfg} - def as_bos_grpc(self) -> dict: - cfg = {"enable": True} - target_conf = {} - if self.power_step is not None: - target_conf["power_step"] = self.power_step - if self.minimum_power is not None: - target_conf["min_power_target"] = self.minimum_power - - cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf)) - - if self.shutdown_enabled is not None: - cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()} - - return {"dps": cfg} + def as_boser(self) -> dict: + return { + "set_dps": SetDpsRequest( + enable=True, + **self.shutdown_enabled.as_boser(), + target=DpsTarget( + power_target=DpsPowerTarget( + power_step=Power(self.power_step), + min_power_target=Power(self.minimum_power), + ) + ), + ), + } @dataclass @@ -187,3 +200,21 @@ class PowerScalingConfig(MinerConfigOption): return cls.disabled() return cls.default() + + @classmethod + def from_boser(cls, grpc_miner_conf: dict): + try: + dps_conf = grpc_miner_conf["dps"] + if not dps_conf.get("enabled", False): + return cls.disabled() + except LookupError: + return cls.default() + + conf = {} + + conf["shutdown_enabled"] = PowerScalingShutdown.from_boser(dps_conf) + if dps_conf.get("minPowerTarget") is not None: + conf["minimum_power"] = dps_conf["minPowerTarget"]["watt"] + if dps_conf.get("powerStep") is not None: + conf["power_step"] = dps_conf["powerStep"]["watt"] + return cls.enabled(**conf) diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index f7194624..58e3957d 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -41,7 +41,7 @@ class HiveonT9(Hiveon, T9): except (TypeError, ValueError, asyncssh.Error, OSError, AttributeError): pass - async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: hashboards = [ HashBoard(slot=board, expected_chips=self.expected_chips) for board in range(self.expected_hashboards) @@ -83,7 +83,7 @@ class HiveonT9(Hiveon, T9): return hashboards - async def get_wattage(self, api_stats: dict = None) -> Optional[int]: + async def _get_wattage(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() @@ -100,7 +100,7 @@ class HiveonT9(Hiveon, T9): # parse wattage position out of raw data return round(float(wattage_raw.split(" ")[0])) - async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: + async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: env_temp_list = [] board_map = { 0: [2, 9, 10], diff --git a/pyasic/miners/backends/bosminer_old.py b/pyasic/miners/backends/bosminer_old.py deleted file mode 100644 index e69de29b..00000000 diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index d0358329..ac280fc7 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -45,51 +45,52 @@ BOSMINER_DATA_LOC = DataLocations( "_get_mac", [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], ), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_version", "version")] + "_get_api_ver", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.FW_VERSION): DataFunction( + "_get_fw_ver", [WebAPICommand("web_bos_info", "bos/info")] + ), + str(DataOptions.HOSTNAME): DataFunction("_get_hostname"), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", + "_get_hashrate", [RPCAPICommand("api_summary", "summary")], ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + "_get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", + "_get_hashboards", [ RPCAPICommand("api_temps", "temps"), RPCAPICommand("api_devdetails", "devdetails"), RPCAPICommand("api_devs", "devs"), ], ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", + "_get_wattage", [RPCAPICommand("api_tunerstatus", "tunerstatus")], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", + "_get_wattage_limit", [RPCAPICommand("api_tunerstatus", "tunerstatus")], ), str(DataOptions.FANS): DataFunction( - "get_fans", + "_get_fans", [RPCAPICommand("api_fans", "fans")], ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( - "get_errors", + "_get_errors", [RPCAPICommand("api_tunerstatus", "tunerstatus")], ), - str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"), str(DataOptions.IS_MINING): DataFunction( - "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] + "_is_mining", [RPCAPICommand("api_devdetails", "devdetails")] ), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_summary", "summary")] + "_get_uptime", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -288,16 +289,17 @@ class BOSMiner(BaseMiner): gateway: str, subnet_mask: str = "255.255.255.0", ): - cfg_data_lan = ( - "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'static'\n\toption ipaddr '" - + ip - + "'\n\toption netmask '" - + subnet_mask - + "'\n\toption gateway '" - + gateway - + "'\n\toption dns '" - + dns - + "'" + cfg_data_lan = "\n\t".join( + [ + "config interface 'lan'", + "option type 'bridge'", + "option ifname 'eth0'", + "option proto 'static'", + f"option ipaddr '{ip}'", + f"option netmask '{subnet_mask}'", + f"option gateway '{gateway}'", + f"option dns '{dns}'", + ] ) data = await self.send_ssh_command("cat /etc/config/network") @@ -313,7 +315,14 @@ class BOSMiner(BaseMiner): await conn.run("echo '" + config + "' > /etc/config/network") async def set_dhcp(self): - cfg_data_lan = "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'dhcp'" + cfg_data_lan = "\n\t".join( + [ + "config interface 'lan'", + "option type 'bridge'", + "option ifname 'eth0'", + "option proto 'dhcp'", + ] + ) data = await self.send_ssh_command("cat /etc/config/network") split_data = data.split("\n\n") @@ -331,12 +340,10 @@ class BOSMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: + async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: if not web_net_conf: try: - web_net_conf = await self.web.luci.send_command( - "admin/network/iface_status/lan" - ) + web_net_conf = await self.web.luci.get_net_conf() except APIError: pass @@ -354,21 +361,7 @@ class BOSMiner(BaseMiner): # if result: # return result.upper().strip() - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (BOS)" - return "? (BOS)" - - async def get_version( - self, api_version: dict = None, graphql_version: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - api_ver_t = asyncio.create_task(self.get_api_ver(api_version)) - fw_ver_t = asyncio.create_task(self.get_fw_ver()) - await asyncio.gather(api_ver_t, fw_ver_t) - return miner_version(api_ver=api_ver_t.result(), fw_ver=fw_ver_t.result()) - - async def get_api_ver(self, api_version: dict = None) -> Optional[str]: + async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -386,19 +379,28 @@ class BOSMiner(BaseMiner): return self.api_ver - async def get_fw_ver(self) -> Optional[str]: - fw_ver = await self.send_ssh_command("cat /etc/bos_version") + async def _get_fw_ver(self, web_bos_info: dict) -> Optional[str]: + if web_bos_info is None: + try: + web_bos_info = await self.web.luci.get_bos_info() + except APIError: + return None - # if we get the version data, parse it - if fw_ver is not None: - ver = fw_ver.split("-")[5] + if isinstance(web_bos_info, dict): + if "bos/info" in web_bos_info.keys(): + web_bos_info = web_bos_info["bos/info"] + + try: + ver = web_bos_info["version"].split("-")[5] if "." in ver: self.fw_ver = ver logging.debug(f"Found version for {self.ip}: {self.fw_ver}") + except (LookupError, AttributeError): + return None return self.fw_ver - async def get_hostname(self) -> Union[str, None]: + async def _get_hostname(self) -> Union[str, None]: try: hostname = ( await self.send_ssh_command("cat /proc/sys/kernel/hostname") @@ -408,7 +410,7 @@ class BOSMiner(BaseMiner): return None return hostname - async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: # get hr from API if not api_summary: try: @@ -422,7 +424,7 @@ class BOSMiner(BaseMiner): except (KeyError, IndexError, ValueError, TypeError): pass - async def get_hashboards( + async def _get_hashboards( self, api_temps: dict = None, api_devdetails: dict = None, @@ -495,10 +497,10 @@ class BOSMiner(BaseMiner): return hashboards - async def get_env_temp(self) -> Optional[float]: + async def _get_env_temp(self) -> Optional[float]: return None - async def get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: + async def _get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -513,7 +515,7 @@ class BOSMiner(BaseMiner): except LookupError: pass - async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -526,7 +528,7 @@ class BOSMiner(BaseMiner): except LookupError: pass - async def get_fans(self, api_fans: dict = None) -> List[Fan]: + async def _get_fans(self, api_fans: dict = None) -> List[Fan]: if not api_fans: try: api_fans = await self.api.fans() @@ -543,10 +545,10 @@ class BOSMiner(BaseMiner): return fans return [Fan() for _ in range(self.fan_count)] - async def get_fan_psu(self) -> Optional[int]: + async def _get_fan_psu(self) -> Optional[int]: return None - async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: + async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -576,7 +578,7 @@ class BOSMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: + async def _get_fault_light(self) -> bool: if self.light: return self.light try: @@ -590,7 +592,7 @@ class BOSMiner(BaseMiner): except (TypeError, AttributeError): return self.light - async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: if not api_devs: try: api_devs = await self.api.devs() @@ -616,7 +618,7 @@ class BOSMiner(BaseMiner): except (IndexError, KeyError): pass - async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: + async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: if not api_devdetails: try: api_devdetails = await self.api.send_command( @@ -631,7 +633,7 @@ class BOSMiner(BaseMiner): except LookupError: pass - async def get_uptime(self, api_summary: dict = None) -> Optional[int]: + async def _get_uptime(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -648,7 +650,7 @@ class BOSMiner(BaseMiner): BOSER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", + "_get_mac", [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.API_VERSION): DataFunction( @@ -720,7 +722,6 @@ class BOSer(BaseMiner): # static data self.api_type = "BOSMiner" - self.fw_str = "BOS" # data gathering locations self.data_locations = BOSER_DATA_LOC # autotuning/shutdown support @@ -770,41 +771,27 @@ class BOSer(BaseMiner): return False async def get_config(self) -> MinerConfig: - logging.debug(f"{self}: Getting config.") + grpc_conf = await self.web.grpc.get_miner_configuration() - try: - conn = await self._get_ssh_connection() - except ConnectionError: - conn = None - - if conn: - async with conn: - # good ol' BBB compatibility :/ - toml_data = toml.loads( - (await conn.run("cat /etc/bosminer.toml")).stdout - ) - logging.debug(f"{self}: Converting config file.") - cfg = MinerConfig.from_bosminer(toml_data) - self.config = cfg - return self.config + return MinerConfig.from_boser(grpc_conf) async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + raise NotImplementedError logging.debug(f"{self}: Sending config.") self.config = config - raise NotImplementedError async def set_power_limit(self, wattage: int) -> bool: try: - cfg = await self.get_config() - if cfg is None: - return False - cfg.mining_mode = MiningModePowerTune(wattage) - await self.send_config(cfg) - except Exception as e: - logging.warning(f"{self} set_power_limit: {e}") + result = await self.web.grpc.set_power_target(wattage) + except APIError: return False - else: - return True + + try: + if result["powerTarget"]["watt"] == wattage: + return True + except KeyError: + pass + return False ################################################## ### DATA GATHERING FUNCTIONS (get_{some_data}) ### @@ -823,20 +810,11 @@ class BOSer(BaseMiner): except (LookupError, TypeError): pass - async def get_model(self) -> Optional[str]: - if self.raw_model is not None: - return self.raw_model + " (BOS)" + async def _get_model(self) -> Optional[str]: + if self.model is not None: + return self.model + " (BOS)" return "? (BOS)" - async def get_version( - self, api_version: dict = None, graphql_version: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - # check if version is cached - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - api_ver = await self.get_api_ver(api_version) - fw_ver = await self.get_fw_ver(graphql_version) - return miner_version(api_ver, fw_ver) - async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: @@ -902,10 +880,10 @@ class BOSer(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (LookupError, ValueError, TypeError): + except (KeyError, IndexError, ValueError, TypeError): pass - async def get_expected_hashrate( + async def _get_expected_hashrate( self, grpc_miner_details: dict = None ) -> Optional[float]: if not grpc_miner_details: @@ -920,7 +898,7 @@ class BOSer(BaseMiner): except LookupError: pass - async def get_hashboards(self, grpc_hashboards: dict = None): + async def _get_hashboards(self, grpc_hashboards: dict = None): hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) @@ -991,7 +969,7 @@ class BOSer(BaseMiner): except KeyError: pass - async def get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: + async def _get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: if grpc_cooling_state is None: try: grpc_cooling_state = self.web.grpc.get_cooling_state() @@ -1000,13 +978,13 @@ class BOSer(BaseMiner): if grpc_cooling_state: fans = [] - for n in range(self.expected_fans): + for n in range(self.fan_count): try: fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) - except LookupError: + except (IndexError, KeyError): pass return fans - return [Fan() for _ in range(self.expected_fans)] + return [Fan() for _ in range(self.fan_count)] async def _get_fan_psu(self) -> Optional[int]: return None diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 216087b5..c681cd12 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -28,42 +28,41 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC AVALON_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [RPCAPICommand("api_version", "version")] + "_get_mac", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_version", "version")] + "_get_api_ver", [RPCAPICommand("api_version", "version")] ), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", [RPCAPICommand("api_version", "version")] + "_get_fw_ver", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HOSTNAME): DataFunction("_get_hostname"), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", [RPCAPICommand("api_devs", "devs")] + "_get_hashrate", [RPCAPICommand("api_devs", "devs")] ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + "_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", [RPCAPICommand("api_stats", "stats")] + "_get_hashboards", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction( - "get_env_temp", [RPCAPICommand("api_stats", "stats")] + "_get_env_temp", [RPCAPICommand("api_stats", "stats")] ), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", [RPCAPICommand("api_stats", "stats")] + "_get_wattage_limit", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.FANS): DataFunction( - "get_fans", [RPCAPICommand("api_stats", "stats")] + "_get_fans", [RPCAPICommand("api_stats", "stats")] ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), - str(DataOptions.ERRORS): DataFunction("get_errors"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("_get_errors"), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", [RPCAPICommand("api_stats", "stats")] + "_get_fault_light", [RPCAPICommand("api_stats", "stats")] ), - str(DataOptions.IS_MINING): DataFunction("is_mining"), - str(DataOptions.UPTIME): DataFunction("get_uptime"), + str(DataOptions.IS_MINING): DataFunction("_is_mining"), + str(DataOptions.UPTIME): DataFunction("_get_uptime"), str(DataOptions.CONFIG): DataFunction("get_config"), } ) @@ -174,7 +173,7 @@ class CGMinerAvalon(CGMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self, api_version: dict = None) -> Optional[str]: + async def _get_mac(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -192,7 +191,7 @@ class CGMinerAvalon(CGMiner): except (KeyError, ValueError): pass - async def get_hostname(self) -> Optional[str]: + async def _get_hostname(self) -> Optional[str]: return None # if not mac: # mac = await self.get_mac() @@ -200,7 +199,7 @@ class CGMinerAvalon(CGMiner): # if mac: # return f"Avalon{mac.replace(':', '')[-6:]}" - async def get_hashrate(self, api_devs: dict = None) -> Optional[float]: + async def _get_hashrate(self, api_devs: dict = None) -> Optional[float]: if not api_devs: try: api_devs = await self.api.devs() @@ -213,7 +212,7 @@ class CGMinerAvalon(CGMiner): except (KeyError, IndexError, ValueError, TypeError): pass - async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) @@ -261,7 +260,7 @@ class CGMinerAvalon(CGMiner): return hashboards - async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: if not api_stats: try: api_stats = await self.api.stats() @@ -276,7 +275,7 @@ class CGMinerAvalon(CGMiner): except (IndexError, KeyError, ValueError, TypeError): pass - async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: + async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: if not api_stats: try: api_stats = await self.api.stats() @@ -291,10 +290,10 @@ class CGMinerAvalon(CGMiner): except (IndexError, KeyError, ValueError, TypeError): pass - async def get_wattage(self) -> Optional[int]: + async def _get_wattage(self) -> Optional[int]: return None - async def get_wattage_limit(self, api_stats: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() @@ -309,7 +308,7 @@ class CGMinerAvalon(CGMiner): except (IndexError, KeyError, ValueError, TypeError): pass - async def get_fans(self, api_stats: dict = None) -> List[Fan]: + async def _get_fans(self, api_stats: dict = None) -> List[Fan]: if not api_stats: try: api_stats = await self.api.stats() @@ -331,10 +330,10 @@ class CGMinerAvalon(CGMiner): pass return fans_data - async def get_errors(self) -> List[MinerErrorData]: + async def _get_errors(self) -> List[MinerErrorData]: return [] - async def get_fault_light(self, api_stats: dict = None) -> bool: # noqa + async def _get_fault_light(self, api_stats: dict = None) -> bool: # noqa if self.light: return self.light if not api_stats: @@ -363,8 +362,8 @@ class CGMinerAvalon(CGMiner): pass return False - async def is_mining(self, *args, **kwargs) -> Optional[bool]: + async def _is_mining(self, *args, **kwargs) -> Optional[bool]: return None - async def get_uptime(self) -> Optional[int]: + async def _get_uptime(self) -> Optional[int]: return None diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 600eb41c..7f0cf232 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -23,39 +23,38 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC HIVEON_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction("get_mac"), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_version", "version")] + "_get_api_ver", [RPCAPICommand("api_version", "version")] ), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", [RPCAPICommand("api_version", "version")] + "_get_fw_ver", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HOSTNAME): DataFunction("_get_hostname"), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", [RPCAPICommand("api_summary", "summary")] + "_get_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + "_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", [RPCAPICommand("api_stats", "stats")] + "_get_hashboards", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction( - "get_env_temp", [RPCAPICommand("api_stats", "stats")] + "_get_env_temp", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [RPCAPICommand("api_stats", "stats")] + "_get_wattage", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), str(DataOptions.FANS): DataFunction( - "get_fans", [RPCAPICommand("api_stats", "stats")] + "_get_fans", [RPCAPICommand("api_stats", "stats")] ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), - str(DataOptions.ERRORS): DataFunction("get_errors"), - str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), - str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("_get_errors"), + str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("_is_mining"), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_stats", "stats")] + "_get_uptime", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -71,16 +70,11 @@ class Hiveon(BMMiner): # data gathering locations self.data_locations = HIVEON_DATA_LOC - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (Hiveon)" - return "? (Hiveon)" - - async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: pass - async def get_wattage(self, api_stats: dict = None) -> Optional[int]: + async def _get_wattage(self, api_stats: dict = None) -> Optional[int]: pass - async def get_env_temp(self, api_stats: dict = None) -> Optional[float]: + async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: pass diff --git a/pyasic/miners/backends/innosilicon.py b/pyasic/miners/backends/innosilicon.py index 192bfeb6..9e85525a 100644 --- a/pyasic/miners/backends/innosilicon.py +++ b/pyasic/miners/backends/innosilicon.py @@ -33,68 +33,67 @@ from pyasic.web.innosilicon import InnosiliconWebAPI INNOSILICON_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", + "_get_mac", [ WebAPICommand("web_get_all", "getAll"), WebAPICommand("web_overview", "overview"), ], ), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_version", "version")] + "_get_api_ver", [RPCAPICommand("api_version", "version")] ), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", [RPCAPICommand("api_version", "version")] + "_get_fw_ver", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HOSTNAME): DataFunction("_get_hostname"), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", + "_get_hashrate", [ RPCAPICommand("api_summary", "summary"), WebAPICommand("web_get_all", "getAll"), ], ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", + "_get_expected_hashrate", ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", + "_get_hashboards", [ RPCAPICommand("api_stats", "stats"), WebAPICommand("web_get_all", "getAll"), ], ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", + "_get_wattage", [ WebAPICommand("web_get_all", "getAll"), RPCAPICommand("api_stats", "stats"), ], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", + "_get_wattage_limit", [ WebAPICommand("web_get_all", "getAll"), ], ), str(DataOptions.FANS): DataFunction( - "get_fans", + "_get_fans", [ WebAPICommand("web_get_all", "getAll"), ], ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( - "get_errors", + "_get_errors", [ WebAPICommand("web_get_error_detail", "getErrorDetail"), ], ), - str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), - str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"), + str(DataOptions.IS_MINING): DataFunction("_is_mining"), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_stats", "stats")] + "_get_uptime", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -176,7 +175,7 @@ class Innosilicon(CGMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac( + async def _get_mac( self, web_get_all: dict = None, web_overview: dict = None ) -> Optional[str]: if web_get_all: @@ -202,7 +201,7 @@ class Innosilicon(CGMiner): except KeyError: pass - async def get_hashrate( + async def _get_hashrate( self, api_summary: dict = None, web_get_all: dict = None ) -> Optional[float]: if web_get_all: @@ -234,7 +233,7 @@ class Innosilicon(CGMiner): except (KeyError, IndexError): pass - async def get_hashboards( + async def _get_hashboards( self, api_stats: dict = None, web_get_all: dict = None ) -> List[HashBoard]: if web_get_all: @@ -292,7 +291,7 @@ class Innosilicon(CGMiner): return hashboards - async def get_wattage( + async def _get_wattage( self, web_get_all: dict = None, api_stats: dict = None ) -> Optional[int]: if web_get_all: @@ -329,7 +328,7 @@ class Innosilicon(CGMiner): wattage = int(wattage) return wattage - async def get_fans(self, web_get_all: dict = None) -> List[Fan]: + async def _get_fans(self, web_get_all: dict = None) -> List[Fan]: if web_get_all: web_get_all = web_get_all["all"] @@ -354,7 +353,7 @@ class Innosilicon(CGMiner): return fans - async def get_errors( + async def _get_errors( self, web_get_error_detail: dict = None ) -> List[MinerErrorData]: errors = [] @@ -377,7 +376,7 @@ class Innosilicon(CGMiner): errors.append(InnosiliconError(error_code=err)) return errors - async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: if web_get_all: web_get_all = web_get_all["all"] @@ -400,5 +399,5 @@ class Innosilicon(CGMiner): limit = 1250 + (250 * level) return limit - async def get_expected_hashrate(self) -> Optional[float]: + async def _get_expected_hashrate(self) -> Optional[float]: pass diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index aaf86998..c0f61134 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -142,7 +142,7 @@ class BaseMiner(ABC): @property def model(self): - model_data = [self.raw_model] + model_data = [self.raw_model if self.raw_model is not None else "Unknown"] if self.fw_str is not None: model_data.append(f"({self.fw_str})") return " ".join(model_data) diff --git a/pyasic/web/__init__.py b/pyasic/web/__init__.py index 72774619..abcc9ab6 100644 --- a/pyasic/web/__init__.py +++ b/pyasic/web/__init__.py @@ -26,6 +26,7 @@ class BaseWebAPI(ABC): self.ip = ip # ipaddress.ip_address(ip) self.username = "root" self.pwd = "root" + self.port = 80 def __new__(cls, *args, **kwargs): if cls is BaseWebAPI: diff --git a/pyasic/web/antminer.py b/pyasic/web/antminer.py index 145d55ce..dc9051bb 100644 --- a/pyasic/web/antminer.py +++ b/pyasic/web/antminer.py @@ -35,10 +35,12 @@ class AntminerModernWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - url = f"http://{self.ip}/cgi-bin/{command}.cgi" + url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) try: - async with httpx.AsyncClient(transport=settings.transport()) as client: + async with httpx.AsyncClient( + transport=settings.transport(), + ) as client: if parameters: data = await client.post( url, @@ -149,7 +151,7 @@ class AntminerOldWebAPI(BaseWebAPI): allow_warning: bool = True, **parameters: Union[str, int, bool], ) -> dict: - url = f"http://{self.ip}/cgi-bin/{command}.cgi" + url = f"http://{self.ip}:{self.port}/cgi-bin/{command}.cgi" auth = httpx.DigestAuth(self.username, self.pwd) try: async with httpx.AsyncClient(transport=settings.transport()) as client: diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index 95cc88df..848e4e62 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -31,6 +31,7 @@ class BOSMinerWebAPI(BaseWebAPI): ip, settings.get("default_bosminer_password", "root") ) self._pwd = settings.get("default_bosminer_password", "root") + self._port = 80 super().__init__(ip) @property @@ -42,6 +43,15 @@ class BOSMinerWebAPI(BaseWebAPI): self._pwd = other self.luci.pwd = other + @property + def port(self): + return self._port + + @port.setter + def port(self, other: str): + self._port = other + self.luci.port = other + async def send_command( self, command: Union[str, dict], @@ -63,6 +73,7 @@ class BOSerWebAPI(BOSMinerWebAPI): ip, settings.get("default_bosminer_password", "root") ) self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root")) + self._port = 80 super().__init__(ip) @property @@ -76,6 +87,16 @@ class BOSerWebAPI(BOSMinerWebAPI): self.gql.pwd = other self.grpc.pwd = other + @property + def port(self): + return self._port + + @port.setter + def port(self, other: str): + self._port = other + self.luci.port = other + self.gql.port = other + async def send_command( self, command: Union[str, dict], @@ -84,14 +105,14 @@ class BOSerWebAPI(BOSMinerWebAPI): **parameters: Union[str, int, bool], ) -> dict: command_type = self.select_command_type(command) - if command_type is "gql": + if command_type == "gql": return await self.gql.send_command(command) - elif command_type is "grpc": + elif command_type == "grpc": try: return await (getattr(self.grpc, command.replace("grpc_", "")))() except AttributeError: raise APIError(f"No gRPC command found for command: {command}") - elif command_type is "luci": + elif command_type == "luci": return await self.luci.send_command(command) @staticmethod diff --git a/pyasic/web/braiins_os/graphql.py b/pyasic/web/braiins_os/graphql.py index 7c00ffd5..093f0933 100644 --- a/pyasic/web/braiins_os/graphql.py +++ b/pyasic/web/braiins_os/graphql.py @@ -26,6 +26,7 @@ class BOSerGraphQLAPI: self.ip = ip self.username = "root" self.pwd = pwd + self.port = 80 async def multicommand(self, *commands: dict) -> dict: def merge(*d: dict): @@ -60,7 +61,7 @@ class BOSerGraphQLAPI: self, command: dict, ) -> dict: - url = f"http://{self.ip}/graphql" + url = f"http://{self.ip}:{self.port}/graphql" query = command if command is None: return {} @@ -93,7 +94,7 @@ class BOSerGraphQLAPI: return "{" + ",".join(data) + "}" async def auth(self, client: httpx.AsyncClient) -> None: - url = f"http://{self.ip}/graphql" + url = f"http://{self.ip}:{self.port}/graphql" await client.post( url, json={ diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index ff1afa26..c5f405e9 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -14,9 +14,11 @@ # limitations under the License. - # ------------------------------------------------------------------------------ import asyncio +import logging from datetime import timedelta from betterproto import Message +from grpclib import GRPCError, Status from grpclib.client import Channel from pyasic.errors import APIError @@ -44,6 +46,7 @@ class BOSerGRPCAPI: self.ip = ip self.username = "root" self.pwd = pwd + self.port = 50051 self._auth = None self._auth_time = datetime.now() @@ -90,13 +93,23 @@ class BOSerGRPCAPI: metadata = [] if auth: metadata.append(("authorization", await self.auth())) - async with Channel(self.ip, 50051) as c: - endpoint = getattr(BOSMinerGRPCStub(c), command) - if endpoint is None: - if not ignore_errors: - raise APIError(f"Command not found - {endpoint}") - return {} - return (await endpoint(message, metadata=metadata)).to_pydict() + try: + async with Channel(self.ip, self.port) as c: + endpoint = getattr(BOSMinerGRPCStub(c), command) + if endpoint is None: + if not ignore_errors: + raise APIError(f"Command not found - {endpoint}") + return {} + try: + return (await endpoint(message, metadata=metadata)).to_pydict() + except GRPCError as e: + if e.status == Status.UNAUTHENTICATED: + await self._get_auth() + metadata = [("authorization", await self.auth())] + return (await endpoint(message, metadata=metadata)).to_pydict() + raise e + except GRPCError as e: + raise APIError(f"gRPC command failed - {endpoint}") from e async def auth(self): if self._auth is not None and self._auth_time - datetime.now() < timedelta( @@ -107,7 +120,7 @@ class BOSerGRPCAPI: return self._auth async def _get_auth(self): - async with Channel(self.ip, 50051) as c: + async with Channel(self.ip, self.port) as c: req = LoginRequest(username=self.username, password=self.pwd) async with c.request( "/braiins.bos.v1.AuthenticationService/Login", @@ -152,7 +165,9 @@ class BOSerGRPCAPI: ) async def get_locate_device_status(self): - return await self.send_command("get_locate_device_status") + return await self.send_command( + "get_locate_device_status", GetLocateDeviceStatusRequest() + ) async def set_password(self, password: str = None): return await self.send_command( @@ -281,16 +296,72 @@ class BOSerGRPCAPI: async def set_dps( self, + enable: bool, + power_step: int, + min_power_target: int, + enable_shutdown: bool = None, + shutdown_duration: int = None, ): - raise NotImplementedError - return await self.send_command("braiins.bos.v1.PerformanceService/SetDPS") - - async def set_performance_mode(self): - raise NotImplementedError return await self.send_command( - "braiins.bos.v1.PerformanceService/SetPerformanceMode" + "set_dps", + message=SetDpsRequest( + enable=enable, + enable_shutdown=enable_shutdown, + shutdown_duration=shutdown_duration, + target=DpsTarget( + power_target=DpsPowerTarget( + power_step=Power(power_step), + min_power_target=Power(min_power_target), + ) + ), + ), ) + async def set_performance_mode( + self, + wattage_target: int = None, + hashrate_target: int = None, + save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY, + ): + if wattage_target is not None and hashrate_target is not None: + logging.error( + "Cannot use both wattage_target and hashrate_target, using wattage_target." + ) + elif wattage_target is None and hashrate_target is None: + raise APIError( + "No target supplied, please supply either wattage_target or hashrate_target." + ) + if wattage_target is not None: + return await self.send_command( + "set_performance_mode", + message=SetPerformanceModeRequest( + save_action=save_action, + mode=PerformanceMode( + tuner_mode=TunerPerformanceMode( + power_target=PowerTargetMode( + power_target=Power(watt=wattage_target) + ) + ) + ), + ), + ) + if hashrate_target is not None: + return await self.send_command( + "set_performance_mode", + message=SetPerformanceModeRequest( + save_action=save_action, + mode=PerformanceMode( + tuner_mode=TunerPerformanceMode( + hashrate_target=HashrateTargetMode( + hashrate_target=TeraHashrate( + terahash_per_second=hashrate_target + ) + ) + ) + ), + ), + ) + async def get_active_performance_mode(self): return await self.send_command( "get_active_performance_mode", GetPerformanceModeRequest() diff --git a/pyasic/web/braiins_os/luci.py b/pyasic/web/braiins_os/luci.py index f8a745c2..24aa7a5e 100644 --- a/pyasic/web/braiins_os/luci.py +++ b/pyasic/web/braiins_os/luci.py @@ -26,6 +26,7 @@ class BOSMinerLuCIAPI: self.ip = ip self.username = "root" self.pwd = pwd + self.port = 80 async def multicommand(self, *commands: str) -> dict: data = {} @@ -38,7 +39,7 @@ class BOSMinerLuCIAPI: async with httpx.AsyncClient(transport=settings.transport()) as client: await self.auth(client) data = await client.get( - f"http://{self.ip}/cgi-bin/luci/{path}", + f"http://{self.ip}:{self.port}/cgi-bin/luci/{path}", headers={"User-Agent": "BTC Tools v0.1"}, ) if data.status_code == 200: @@ -55,7 +56,7 @@ class BOSMinerLuCIAPI: async def auth(self, session: httpx.AsyncClient): login = {"luci_username": self.username, "luci_password": self.pwd} - url = f"http://{self.ip}/cgi-bin/luci" + url = f"http://{self.ip}:{self.port}/cgi-bin/luci" headers = { "User-Agent": "BTC Tools v0.1", # only seems to respond if this user-agent is set "Content-Type": "application/x-www-form-urlencoded", diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py index 6482bdc5..62f0c82e 100644 --- a/pyasic/web/epic.py +++ b/pyasic/web/epic.py @@ -29,6 +29,7 @@ class ePICWebAPI(BaseWebAPI): self.username = "root" self.pwd = settings.get("default_epic_password", "letmein") self.token = None + self.port = 4028 async def send_command( self, @@ -50,13 +51,13 @@ class ePICWebAPI(BaseWebAPI): "password": self.pwd, } response = await client.post( - f"http://{self.ip}:4028/{command}", + f"http://{self.ip}:{self.port}/{command}", timeout=5, json=epic_param, ) else: response = await client.get( - f"http://{self.ip}:4028/{command}", + f"http://{self.ip}:{self.port}/{command}", timeout=5, ) if not response.status_code == 200: diff --git a/pyasic/web/goldshell.py b/pyasic/web/goldshell.py index f01abfad..b0626941 100644 --- a/pyasic/web/goldshell.py +++ b/pyasic/web/goldshell.py @@ -33,10 +33,10 @@ class GoldshellWebAPI(BaseWebAPI): async def auth(self): async with httpx.AsyncClient(transport=settings.transport()) as client: try: - await client.get(f"http://{self.ip}/user/logout") + await client.get(f"http://{self.ip}:{self.port}/user/logout") auth = ( await client.get( - f"http://{self.ip}/user/login?username={self.username}&password={self.pwd}&cipher=false" + f"http://{self.ip}:{self.port}/user/login?username={self.username}&password={self.pwd}&cipher=false" ) ).json() except httpx.HTTPError: @@ -46,7 +46,7 @@ class GoldshellWebAPI(BaseWebAPI): try: auth = ( await client.get( - f"http://{self.ip}/user/login?username=admin&password=bbad7537f4c8b6ea31eea0b3d760e257&cipher=true" + f"http://{self.ip}:{self.port}/user/login?username=admin&password=bbad7537f4c8b6ea31eea0b3d760e257&cipher=true" ) ).json() except (httpx.HTTPError, json.JSONDecodeError): @@ -76,14 +76,14 @@ class GoldshellWebAPI(BaseWebAPI): try: if parameters: response = await client.put( - f"http://{self.ip}/mcb/{command}", + f"http://{self.ip}:{self.port}/mcb/{command}", headers={"Authorization": "Bearer " + self.jwt}, timeout=settings.get("api_function_timeout", 5), json=parameters, ) else: response = await client.get( - f"http://{self.ip}/mcb/{command}", + f"http://{self.ip}:{self.port}/mcb/{command}", headers={"Authorization": "Bearer " + self.jwt}, timeout=settings.get("api_function_timeout", 5), ) @@ -106,7 +106,7 @@ class GoldshellWebAPI(BaseWebAPI): for command in commands: try: response = await client.get( - f"http://{self.ip}/mcb/{command}", + f"http://{self.ip}:{self.port}/mcb/{command}", headers={"Authorization": "Bearer " + self.jwt}, timeout=settings.get("api_function_timeout", 5), ) diff --git a/pyasic/web/innosilicon.py b/pyasic/web/innosilicon.py index ac174df6..ca095003 100644 --- a/pyasic/web/innosilicon.py +++ b/pyasic/web/innosilicon.py @@ -35,7 +35,7 @@ class InnosiliconWebAPI(BaseWebAPI): async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( - f"http://{self.ip}/api/auth", + f"http://{self.ip}:{self.port}/api/auth", data={"username": self.username, "password": self.pwd}, ) except httpx.HTTPError: @@ -58,7 +58,7 @@ class InnosiliconWebAPI(BaseWebAPI): for i in range(settings.get("get_data_retries", 1)): try: response = await client.post( - f"http://{self.ip}/api/{command}", + f"http://{self.ip}:{self.port}/api/{command}", headers={"Authorization": "Bearer " + self.jwt}, timeout=settings.get("api_function_timeout", 5), json=parameters, @@ -94,7 +94,7 @@ class InnosiliconWebAPI(BaseWebAPI): for command in commands: try: response = await client.post( - f"http://{self.ip}/api/{command}", + f"http://{self.ip}:{self.port}/api/{command}", headers={"Authorization": "Bearer " + self.jwt}, timeout=settings.get("api_function_timeout", 5), ) diff --git a/pyasic/web/vnish.py b/pyasic/web/vnish.py index f9566cf0..83ee092f 100644 --- a/pyasic/web/vnish.py +++ b/pyasic/web/vnish.py @@ -34,7 +34,7 @@ class VNishWebAPI(BaseWebAPI): async with httpx.AsyncClient(transport=settings.transport()) as client: try: auth = await client.post( - f"http://{self.ip}/api/v1/unlock", + f"http://{self.ip}:{self.port}/api/v1/unlock", json={"pw": self.pwd}, ) except httpx.HTTPError: @@ -68,21 +68,21 @@ class VNishWebAPI(BaseWebAPI): if parameters.get("post"): parameters.pop("post") response = await client.post( - f"http://{self.ip}/api/v1/{command}", + f"http://{self.ip}:{self.port}/api/v1/{command}", headers={"Authorization": auth}, timeout=settings.get("api_function_timeout", 5), json=parameters, ) elif not parameters == {}: response = await client.post( - f"http://{self.ip}/api/v1/{command}", + f"http://{self.ip}:{self.port}/api/v1/{command}", headers={"Authorization": auth}, timeout=settings.get("api_function_timeout", 5), json=parameters, ) else: response = await client.get( - f"http://{self.ip}/api/v1/{command}", + f"http://{self.ip}:{self.port}/api/v1/{command}", headers={"Authorization": auth}, timeout=settings.get("api_function_timeout", 5), ) diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index ef30002d..894661ea 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -13,31 +13,27 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import asyncio import inspect -import sys import unittest import warnings from dataclasses import asdict -from pyasic.miners.backends import CGMiner # noqa -from pyasic.miners.base import BaseMiner -from pyasic.miners.miner_factory import MINER_CLASSES, MinerFactory +from pyasic.miners.miner_factory import MINER_CLASSES class MinersTest(unittest.TestCase): - def test_miner_model_creation(self): + def test_miner_type_creation(self): warnings.filterwarnings("ignore") - for miner_model in MINER_CLASSES.keys(): - for miner_api in MINER_CLASSES[miner_model].keys(): + for miner_type in MINER_CLASSES.keys(): + for miner_model in MINER_CLASSES[miner_type].keys(): with self.subTest( - msg=f"Creation of miner using model={miner_model}, api={miner_api}", + msg=f"Test creation of miner", + miner_type=miner_type, miner_model=miner_model, - miner_api=miner_api, ): - miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") + miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1") self.assertTrue( - isinstance(miner, MINER_CLASSES[miner_model][miner_api]) + isinstance(miner, MINER_CLASSES[miner_type][miner_model]) ) def test_miner_data_map_keys(self): @@ -56,7 +52,6 @@ class MinersTest(unittest.TestCase): "hostname", "is_mining", "mac", - "model", "expected_hashrate", "uptime", "wattage", @@ -64,14 +59,14 @@ class MinersTest(unittest.TestCase): ] ) warnings.filterwarnings("ignore") - for miner_model in MINER_CLASSES.keys(): - for miner_api in MINER_CLASSES[miner_model].keys(): + for miner_type in MINER_CLASSES.keys(): + for miner_model in MINER_CLASSES[miner_type].keys(): with self.subTest( - msg=f"Data map key check of miner using model={miner_model}, api={miner_api}", + msg=f"Data map key check", + miner_type=miner_type, miner_model=miner_model, - miner_api=miner_api, ): - miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") + miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1") miner_keys = sorted( [str(k) for k in asdict(miner.data_locations).keys()] ) @@ -79,14 +74,14 @@ class MinersTest(unittest.TestCase): def test_data_locations_match_signatures_command(self): warnings.filterwarnings("ignore") - for miner_model in MINER_CLASSES.keys(): - for miner_api in MINER_CLASSES[miner_model].keys(): - miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") + for miner_type in MINER_CLASSES.keys(): + for miner_model in MINER_CLASSES[miner_type].keys(): + miner = MINER_CLASSES[miner_type][miner_model]("127.0.0.1") for data_point in asdict(miner.data_locations).values(): with self.subTest( - msg=f"Test {data_point['cmd']} signature matches with model={miner_model}, api={miner_api}", + msg=f"Test {data_point['cmd']} signature matches", + miner_type=miner_type, miner_model=miner_model, - miner_api=miner_api, ): func = getattr(miner, data_point["cmd"]) signature = inspect.signature(func)