From bea44a72ea9836922848c0e65c6b5373cb4e9f7b Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 10 Jan 2024 22:12:27 -0700 Subject: [PATCH 01/36] feature: start refactoring BOSer and BOSMiner into separate classes. --- pyasic/config/power_scaling.py | 2 +- pyasic/miners/antminer/bosminer/X17/S17.py | 10 +- pyasic/miners/antminer/bosminer/X17/T17.py | 8 +- pyasic/miners/antminer/bosminer/X19/S19.py | 24 +- pyasic/miners/antminer/bosminer/X19/T19.py | 4 +- pyasic/miners/backends/__init__.py | 2 +- pyasic/miners/backends/bosminer_old.py | 155 ----- .../backends/{bosminer.py => braiins_os.py} | 626 +++++++++++++++++- pyasic/miners/backends/luxminer.py | 2 +- pyasic/web/braiins_os/__init__.py | 138 ++++ pyasic/web/braiins_os/graphql.py | 104 +++ .../__init__.py => braiins_os/grpc.py} | 243 +------ pyasic/web/braiins_os/luci.py | 83 +++ .../proto/__init__.py | 0 .../proto/braiins/__init__.py | 0 .../proto/braiins/bos/__init__.py | 0 .../proto/braiins/bos/v1/__init__.py | 0 pyasic/web/vnish.py | 3 + 18 files changed, 968 insertions(+), 436 deletions(-) delete mode 100644 pyasic/miners/backends/bosminer_old.py rename pyasic/miners/backends/{bosminer.py => braiins_os.py} (62%) create mode 100644 pyasic/web/braiins_os/__init__.py create mode 100644 pyasic/web/braiins_os/graphql.py rename pyasic/web/{bosminer/__init__.py => braiins_os/grpc.py} (60%) create mode 100644 pyasic/web/braiins_os/luci.py rename pyasic/web/{bosminer => braiins_os}/proto/__init__.py (100%) rename pyasic/web/{bosminer => braiins_os}/proto/braiins/__init__.py (100%) rename pyasic/web/{bosminer => braiins_os}/proto/braiins/bos/__init__.py (100%) rename pyasic/web/{bosminer => braiins_os}/proto/braiins/bos/v1/__init__.py (100%) diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index 7df2fffe..b373e51d 100644 --- a/pyasic/config/power_scaling.py +++ b/pyasic/config/power_scaling.py @@ -17,7 +17,7 @@ from dataclasses import dataclass, field from typing import Union from pyasic.config.base import MinerConfigOption, MinerConfigValue -from pyasic.web.bosminer.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours +from pyasic.web.braiins_os.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours @dataclass diff --git a/pyasic/miners/antminer/bosminer/X17/S17.py b/pyasic/miners/antminer/bosminer/X17/S17.py index 42a515f7..ebd3b83b 100644 --- a/pyasic/miners/antminer/bosminer/X17/S17.py +++ b/pyasic/miners/antminer/bosminer/X17/S17.py @@ -14,21 +14,21 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from pyasic.miners.backends import BOSMiner +from pyasic.miners.backends import BOSer from pyasic.miners.types import S17, S17e, S17Plus, S17Pro -class BOSMinerS17(BOSMiner, S17): +class BOSMinerS17(BOSer, S17): pass -class BOSMinerS17Plus(BOSMiner, S17Plus): +class BOSMinerS17Plus(BOSer, S17Plus): pass -class BOSMinerS17Pro(BOSMiner, S17Pro): +class BOSMinerS17Pro(BOSer, S17Pro): pass -class BOSMinerS17e(BOSMiner, S17e): +class BOSMinerS17e(BOSer, S17e): pass diff --git a/pyasic/miners/antminer/bosminer/X17/T17.py b/pyasic/miners/antminer/bosminer/X17/T17.py index a939ad80..1350ce58 100644 --- a/pyasic/miners/antminer/bosminer/X17/T17.py +++ b/pyasic/miners/antminer/bosminer/X17/T17.py @@ -14,17 +14,17 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from pyasic.miners.backends import BOSMiner +from pyasic.miners.backends import BOSer from pyasic.miners.types import T17, T17e, T17Plus -class BOSMinerT17(BOSMiner, T17): +class BOSMinerT17(BOSer, T17): pass -class BOSMinerT17Plus(BOSMiner, T17Plus): +class BOSMinerT17Plus(BOSer, T17Plus): pass -class BOSMinerT17e(BOSMiner, T17e): +class BOSMinerT17e(BOSer, T17e): pass diff --git a/pyasic/miners/antminer/bosminer/X19/S19.py b/pyasic/miners/antminer/bosminer/X19/S19.py index 98d413bb..10ad1de8 100644 --- a/pyasic/miners/antminer/bosminer/X19/S19.py +++ b/pyasic/miners/antminer/bosminer/X19/S19.py @@ -14,7 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from pyasic.miners.backends import BOSMiner +from pyasic.miners.backends import BOSer from pyasic.miners.types import ( S19, S19XP, @@ -30,45 +30,45 @@ from pyasic.miners.types import ( ) -class BOSMinerS19(BOSMiner, S19): +class BOSMinerS19(BOSer, S19): pass -class BOSMinerS19Plus(BOSMiner, S19Plus): +class BOSMinerS19Plus(BOSer, S19Plus): pass -class BOSMinerS19Pro(BOSMiner, S19Pro): +class BOSMinerS19Pro(BOSer, S19Pro): pass -class BOSMinerS19a(BOSMiner, S19a): +class BOSMinerS19a(BOSer, S19a): pass -class BOSMinerS19j(BOSMiner, S19j): +class BOSMinerS19j(BOSer, S19j): pass -class BOSMinerS19jNoPIC(BOSMiner, S19jNoPIC): +class BOSMinerS19jNoPIC(BOSer, S19jNoPIC): pass -class BOSMinerS19jPro(BOSMiner, S19jPro): +class BOSMinerS19jPro(BOSer, S19jPro): pass -class BOSMinerS19kProNoPIC(BOSMiner, S19kProNoPIC): +class BOSMinerS19kProNoPIC(BOSer, S19kProNoPIC): pass -class BOSMinerS19aPro(BOSMiner, S19aPro): +class BOSMinerS19aPro(BOSer, S19aPro): pass -class BOSMinerS19jProPlus(BOSMiner, S19jProPlus): +class BOSMinerS19jProPlus(BOSer, S19jProPlus): pass -class BOSMinerS19XP(BOSMiner, S19XP): +class BOSMinerS19XP(BOSer, S19XP): pass diff --git a/pyasic/miners/antminer/bosminer/X19/T19.py b/pyasic/miners/antminer/bosminer/X19/T19.py index e405ac8a..b57d0401 100644 --- a/pyasic/miners/antminer/bosminer/X19/T19.py +++ b/pyasic/miners/antminer/bosminer/X19/T19.py @@ -14,9 +14,9 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from pyasic.miners.backends import BOSMiner +from pyasic.miners.backends import BOSer from pyasic.miners.types import T19 -class BOSMinerT19(BOSMiner, T19): +class BOSMinerT19(BOSer, T19): pass diff --git a/pyasic/miners/backends/__init__.py b/pyasic/miners/backends/__init__.py index 29caa3c6..0cba2d19 100644 --- a/pyasic/miners/backends/__init__.py +++ b/pyasic/miners/backends/__init__.py @@ -17,7 +17,7 @@ from .antminer import AntminerModern, AntminerOld from .bfgminer import BFGMiner from .bfgminer_goldshell import BFGMinerGoldshell from .bmminer import BMMiner -from .bosminer import BOSMiner +from .braiins_os import BOSer, BOSMiner from .btminer import BTMiner from .cgminer import CGMiner from .cgminer_avalon import CGMinerAvalon diff --git a/pyasic/miners/backends/bosminer_old.py b/pyasic/miners/backends/bosminer_old.py deleted file mode 100644 index 5e6def99..00000000 --- a/pyasic/miners/backends/bosminer_old.py +++ /dev/null @@ -1,155 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright 2022 Upstream Data Inc - -# - -# Licensed under the Apache License, Version 2.0 (the "License"); - -# you may not use this file except in compliance with the License. - -# You may obtain a copy of the License at - -# - -# http://www.apache.org/licenses/LICENSE-2.0 - -# - -# Unless required by applicable law or agreed to in writing, software - -# distributed under the License is distributed on an "AS IS" BASIS, - -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# See the License for the specific language governing permissions and - -# limitations under the License. - -# ------------------------------------------------------------------------------ - -import logging -from typing import List, Optional, Tuple - -from pyasic.config import MinerConfig -from pyasic.data import Fan, HashBoard, MinerData -from pyasic.data.error_codes import MinerErrorData -from pyasic.miners.backends import BOSMiner - - -class BOSMinerOld(BOSMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip, api_ver) - - async def send_ssh_command(self, cmd: str) -> Optional[str]: - result = None - - try: - conn = await self._get_ssh_connection() - except ConnectionError: - return None - - # open an ssh connection - async with conn: - # 3 retries - for i in range(3): - try: - # run the command and get the result - result = await conn.run(cmd) - result = result.stdout - - except Exception as e: - # if the command fails, log it - logging.warning(f"{self} command {cmd} error: {e}") - - # on the 3rd retry, return None - if i == 3: - return - continue - # return the result, either command output or None - return result - - async def update_to_plus(self): - result = await self.send_ssh_command("opkg update && opkg install bos_plus") - return result - - async def check_light(self) -> bool: - return False - - async def fault_light_on(self) -> bool: - return False - - async def fault_light_off(self) -> bool: - return False - - async def get_config(self) -> None: - return None - - async def reboot(self) -> bool: - return False - - async def restart_backend(self) -> bool: - return False - - async def stop_mining(self) -> bool: - return False - - async def resume_mining(self) -> bool: - return False - - async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: - return None - - async def set_power_limit(self, wattage: int) -> bool: - return False - - ################################################## - ### DATA GATHERING FUNCTIONS (get_{some_data}) ### - ################################################## - - async def get_mac(self, *args, **kwargs) -> Optional[str]: - return None - - async def get_model(self, *args, **kwargs) -> str: - return "S9" - - async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: - return None, None - - async def get_hostname(self, *args, **kwargs) -> Optional[str]: - return None - - async def get_hashrate(self, *args, **kwargs) -> Optional[float]: - return None - - async def get_hashboards(self, *args, **kwargs) -> List[HashBoard]: - return [] - - async def get_env_temp(self, *args, **kwargs) -> Optional[float]: - return None - - async def get_wattage(self, *args, **kwargs) -> Optional[int]: - return None - - async def get_wattage_limit(self, *args, **kwargs) -> Optional[int]: - return None - - async def get_fans( - self, - *args, - **kwargs, - ) -> List[Fan]: - return [Fan(), Fan(), Fan(), Fan()] - - async def get_fan_psu(self, *args, **kwargs) -> Optional[int]: - return None - - async def get_api_ver(self, *args, **kwargs) -> Optional[str]: - return None - - async def get_fw_ver(self, *args, **kwargs) -> Optional[str]: - return None - - async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: - return [] - - async def get_fault_light(self, *args, **kwargs) -> bool: - return False - - async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]: - return None - - async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData: - return MinerData(ip=str(self.ip)) - - async def is_mining(self, *args, **kwargs) -> Optional[bool]: - return None - - async def get_uptime(self, *args, **kwargs) -> Optional[int]: - return None diff --git a/pyasic/miners/backends/bosminer.py b/pyasic/miners/backends/braiins_os.py similarity index 62% rename from pyasic/miners/backends/bosminer.py rename to pyasic/miners/backends/braiins_os.py index 51720562..d2ca51d8 100644 --- a/pyasic/miners/backends/bosminer.py +++ b/pyasic/miners/backends/braiins_os.py @@ -36,18 +36,620 @@ from pyasic.miners.base import ( RPCAPICommand, WebAPICommand, ) -from pyasic.web.bosminer import BOSMinerWebAPI +from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI BOSMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( "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")] + ), + str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", + [RPCAPICommand("api_summary", "summary")], + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [ - WebAPICommand( - "web_net_conf", "/cgi-bin/luci/admin/network/iface_status/lan" - ) + RPCAPICommand("api_temps", "temps"), + RPCAPICommand("api_devdetails", "devdetails"), + RPCAPICommand("api_devs", "devs"), ], ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.FANS): DataFunction( + "get_fans", + [RPCAPICommand("api_fans", "fans")], + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "get_errors", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) + + +class BOSMiner(BaseMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip) + # interfaces + self.api = BOSMinerAPI(ip, api_ver) + self.web = BOSMinerWebAPI(ip) + + # static data + self.api_type = "BOSMiner" + # data gathering locations + self.data_locations = BOSMINER_DATA_LOC + # autotuning/shutdown support + self.supports_autotuning = True + self.supports_shutdown = True + + # data storage + self.api_ver = api_ver + + async def send_ssh_command(self, cmd: str) -> Optional[str]: + result = None + + try: + conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10) + except (ConnectionError, asyncio.TimeoutError): + return None + + # open an ssh connection + async with conn: + # 3 retries + for i in range(3): + try: + # run the command and get the result + result = await conn.run(cmd) + stderr = result.stderr + result = result.stdout + + if len(stderr) > len(result): + result = stderr + + except Exception as e: + # if the command fails, log it + logging.warning(f"{self} command {cmd} error: {e}") + + # on the 3rd retry, return None + if i == 3: + return + continue + # return the result, either command output or None + return result + + async def fault_light_on(self) -> bool: + logging.debug(f"{self}: Sending fault_light on command.") + ret = await self.send_ssh_command("miner fault_light on") + logging.debug(f"{self}: fault_light on command completed.") + if isinstance(ret, str): + self.light = True + return self.light + return False + + async def fault_light_off(self) -> bool: + logging.debug(f"{self}: Sending fault_light off command.") + self.light = False + ret = await self.send_ssh_command("miner fault_light off") + logging.debug(f"{self}: fault_light off command completed.") + if isinstance(ret, str): + self.light = False + return True + return False + + async def restart_backend(self) -> bool: + return await self.restart_bosminer() + + async def restart_bosminer(self) -> bool: + logging.debug(f"{self}: Sending bosminer restart command.") + ret = await self.send_ssh_command("/etc/init.d/bosminer restart") + logging.debug(f"{self}: bosminer restart command completed.") + if isinstance(ret, str): + return True + return False + + async def stop_mining(self) -> bool: + try: + data = await self.api.pause() + except APIError: + return False + if data.get("PAUSE"): + if data["PAUSE"][0]: + return True + return False + + async def resume_mining(self) -> bool: + try: + data = await self.api.resume() + except APIError: + return False + if data.get("RESUME"): + if data["RESUME"][0]: + return True + return False + + async def reboot(self) -> bool: + logging.debug(f"{self}: Sending reboot command.") + ret = await self.send_ssh_command("/sbin/reboot") + logging.debug(f"{self}: Reboot command completed.") + if isinstance(ret, str): + return True + return False + + async def get_config(self) -> MinerConfig: + logging.debug(f"{self}: Getting config.") + + 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 + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + logging.debug(f"{self}: Sending config.") + self.config = config + + toml_conf = toml.dumps( + { + "format": { + "version": "1.2+", + "generator": "pyasic", + "model": f"{self.make.replace('Miner', 'miner')} {self.model.replace(' (BOS)', '').replace('j', 'J')}", + "timestamp": int(time.time()), + }, + **config.as_bosminer(user_suffix=user_suffix), + } + ) + try: + conn = await self._get_ssh_connection() + except ConnectionError as e: + raise APIError("SSH connection failed when sending config.") from e + async with conn: + # BBB check because bitmain suxx + bbb_check = await conn.run( + "if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;" + ) + + bbb = bbb_check.stdout.strip() == "1" + + if not bbb: + await conn.run("/etc/init.d/bosminer stop") + logging.debug(f"{self}: Opening SFTP connection.") + async with conn.start_sftp_client() as sftp: + logging.debug(f"{self}: Opening config file.") + async with sftp.open("/etc/bosminer.toml", "w+") as file: + await file.write(toml_conf) + logging.debug(f"{self}: Restarting BOSMiner") + await conn.run("/etc/init.d/bosminer start") + + # I really hate BBB, please get rid of it if you have it + else: + await conn.run("/etc/init.d/S99bosminer stop") + logging.debug(f"{self}: BBB sending config") + await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml") + logging.debug(f"{self}: BBB restarting bosminer.") + await conn.run("/etc/init.d/S99bosminer start") + + 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}") + return False + else: + return True + + async def set_static_ip( + self, + ip: str, + dns: str, + 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 + + "'" + ) + data = await self.send_ssh_command("cat /etc/config/network") + + split_data = data.split("\n\n") + for idx in range(len(split_data)): + if "config interface 'lan'" in split_data[idx]: + split_data[idx] = cfg_data_lan + config = "\n\n".join(split_data) + + conn = await self._get_ssh_connection() + + async with conn: + 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'" + data = await self.send_ssh_command("cat /etc/config/network") + + split_data = data.split("\n\n") + for idx in range(len(split_data)): + if "config interface 'lan'" in split_data[idx]: + split_data[idx] = cfg_data_lan + config = "\n\n".join(split_data) + + conn = await self._get_ssh_connection() + + async with conn: + await conn.run("echo '" + config + "' > /etc/config/network") + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## + + 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" + ) + except APIError: + pass + + if isinstance(web_net_conf, dict): + if "admin/network/iface_status/lan" in web_net_conf.keys(): + web_net_conf = web_net_conf["admin/network/iface_status/lan"] + + if web_net_conf: + try: + return web_net_conf[0]["macaddr"] + except LookupError: + pass + # could use ssh, but its slow and buggy + # result = await self.send_ssh_command("cat /sys/class/net/eth0/address") + # 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]: + if not api_version: + try: + api_version = await self.api.version() + except APIError: + pass + + # Now get the API version + if api_version: + try: + api_ver = api_version["VERSION"][0]["API"] + except (KeyError, IndexError): + api_ver = None + self.api_ver = api_ver + self.api.api_ver = self.api_ver + + return self.api_ver + + async def get_fw_ver(self) -> Optional[str]: + fw_ver = await self.send_ssh_command("cat /etc/bos_version") + + # if we get the version data, parse it + if fw_ver is not None: + ver = fw_ver.split("-")[5] + if "." in ver: + self.fw_ver = ver + logging.debug(f"Found version for {self.ip}: {self.fw_ver}") + + return self.fw_ver + + async def get_hostname(self) -> Union[str, None]: + try: + hostname = ( + await self.send_ssh_command("cat /proc/sys/kernel/hostname") + ).strip() + except Exception as e: + logging.error(f"BOSMiner get_hostname failed with error: {e}") + return None + return hostname + + async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + # get hr from API + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) + except (KeyError, IndexError, ValueError, TypeError): + pass + + async def get_hashboards( + self, + api_temps: dict = None, + api_devdetails: dict = None, + api_devs: dict = None, + ): + hashboards = [ + HashBoard(slot=i, expected_chips=self.expected_chips) + for i in range(self.expected_hashboards) + ] + + cmds = [] + if not api_temps: + cmds.append("temps") + if not api_devdetails: + cmds.append("devdetails") + if not api_devs: + cmds.append("devs") + if len(cmds) > 0: + try: + d = await self.api.multicommand(*cmds) + except APIError: + d = {} + try: + api_temps = d["temps"][0] + except (KeyError, IndexError): + api_temps = None + try: + api_devdetails = d["devdetails"][0] + except (KeyError, IndexError): + api_devdetails = None + try: + api_devs = d["devs"][0] + except (KeyError, IndexError): + api_devs = None + if api_temps: + try: + offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_temps["TEMPS"]: + _id = board["ID"] - offset + chip_temp = round(board["Chip"]) + board_temp = round(board["Board"]) + hashboards[_id].chip_temp = chip_temp + hashboards[_id].temp = board_temp + except (IndexError, KeyError, ValueError, TypeError): + pass + + if api_devdetails: + try: + offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_devdetails["DEVDETAILS"]: + _id = board["ID"] - offset + chips = board["Chips"] + hashboards[_id].chips = chips + hashboards[_id].missing = False + except (IndexError, KeyError): + pass + + if api_devs: + try: + offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_devs["DEVS"]: + _id = board["ID"] - offset + hashrate = round(float(board["MHS 1m"] / 1000000), 2) + hashboards[_id].hashrate = hashrate + except (IndexError, KeyError): + pass + + return hashboards + + async def get_env_temp(self) -> Optional[float]: + return None + + async def get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + try: + return api_tunerstatus["TUNERSTATUS"][0][ + "ApproximateMinerPowerConsumption" + ] + except (KeyError, IndexError): + pass + + async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + try: + return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] + except (KeyError, IndexError): + pass + + async def get_fans(self, api_fans: dict = None) -> List[Fan]: + if not api_fans: + try: + api_fans = await self.api.fans() + except APIError: + pass + + if api_fans: + fans = [] + for n in range(self.fan_count): + try: + fans.append(Fan(api_fans["FANS"][n]["RPM"])) + except (IndexError, KeyError): + pass + return fans + return [Fan() for _ in range(self.fan_count)] + + async def get_fan_psu(self) -> Optional[int]: + return None + + async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + errors = [] + try: + chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] + if chain_status and len(chain_status) > 0: + offset = ( + 6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0 + ) + + for board in chain_status: + _id = board["HashchainIndex"] - offset + if board["Status"] not in [ + "Stable", + "Testing performance profile", + "Tuning individual chips", + ]: + _error = board["Status"].split(" {")[0] + _error = _error[0].lower() + _error[1:] + errors.append(BraiinsOSError(f"Slot {_id} {_error}")) + return errors + except (KeyError, IndexError): + pass + + async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: + if self.light: + return self.light + try: + data = ( + await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") + ).strip() + self.light = False + if data == "50": + self.light = True + return self.light + except (TypeError, AttributeError): + return self.light + + async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: + if not api_devs: + try: + api_devs = await self.api.devs() + except APIError: + pass + + if api_devs: + try: + offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0 + hr_list = [] + + for board in api_devs["DEVS"]: + _id = board["ID"] - offset + expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) + if expected_hashrate: + hr_list.append(expected_hashrate) + if len(hr_list) == 0: + return 0 + else: + return round( + (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 + ) + except (IndexError, KeyError): + pass + + async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: + if not api_devdetails: + try: + api_devdetails = await self.api.send_command( + "devdetails", ignore_errors=True, allow_warning=False + ) + except APIError: + pass + + if api_devdetails: + try: + return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" + except LookupError: + pass + + async def get_uptime(self, api_summary: dict = None) -> Optional[int]: + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + return int(api_summary["SUMMARY"][0]["Elapsed"]) + except LookupError: + pass + + +BOSER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "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")] @@ -180,17 +782,17 @@ BOSMINER_DATA_LOC = DataLocations( ) -class BOSMiner(BaseMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0", boser: bool = None) -> None: +class BOSer(BaseMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip) # interfaces self.api = BOSMinerAPI(ip, api_ver) - self.web = BOSMinerWebAPI(ip, boser=boser) + self.web = BOSerWebAPI(ip) # static data self.api_type = "BOSMiner" # data gathering locations - self.data_locations = BOSMINER_DATA_LOC + self.data_locations = BOSER_DATA_LOC # autotuning/shutdown support self.supports_autotuning = True self.supports_shutdown = True @@ -432,16 +1034,14 @@ class BOSMiner(BaseMiner): if not web_net_conf: try: web_net_conf = await self.web.send_command( - "/cgi-bin/luci/admin/network/iface_status/lan" + "admin/network/iface_status/lan" ) except APIError: pass if isinstance(web_net_conf, dict): - if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys(): - web_net_conf = web_net_conf[ - "/cgi-bin/luci/admin/network/iface_status/lan" - ] + if "admin/network/iface_status/lan" in web_net_conf.keys(): + web_net_conf = web_net_conf["admin/network/iface_status/lan"] if web_net_conf: try: diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index 3467581c..d54061da 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -34,7 +34,7 @@ from pyasic.miners.base import ( RPCAPICommand, WebAPICommand, ) -from pyasic.web.bosminer import BOSMinerWebAPI +from pyasic.web.braiins_os import BOSMinerWebAPI LUXMINER_DATA_LOC = DataLocations( **{ diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py new file mode 100644 index 00000000..a884e0fa --- /dev/null +++ b/pyasic/web/braiins_os/__init__.py @@ -0,0 +1,138 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ +import asyncio +from typing import Union + +from pyasic import settings +from pyasic.errors import APIError +from pyasic.web import BaseWebAPI + +from .graphql import BOSerGraphQLAPI +from .grpc import BOSerGRPCAPI +from .luci import BOSMinerLuCIAPI + + +class BOSMinerWebAPI(BaseWebAPI): + def __init__(self, ip: str) -> None: + self.luci = BOSMinerLuCIAPI( + ip, settings.get("default_bosminer_password", "root") + ) + self._pwd = settings.get("default_bosminer_password", "root") + super().__init__(ip) + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + + async def send_command( + self, + command: Union[str, dict], + ignore_errors: bool = False, + allow_warning: bool = True, + **parameters: Union[str, int, bool], + ) -> dict: + return await self.luci.send_command(command) + + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + return await self.luci.multicommand(*commands) + + +class BOSerWebAPI(BOSMinerWebAPI): + def __init__(self, ip: str) -> None: + self.gql = BOSerGraphQLAPI( + ip, settings.get("default_bosminer_password", "root") + ) + self.grpc = BOSerGRPCAPI(ip, settings.get("default_bosminer_password", "root")) + super().__init__(ip) + + @property + def pwd(self): + return self._pwd + + @pwd.setter + def pwd(self, other: str): + self._pwd = other + self.luci.pwd = other + self.gql.pwd = other + self.grpc.pwd = other + + async def send_command( + self, + command: Union[str, dict], + ignore_errors: bool = False, + allow_warning: bool = True, + **parameters: Union[str, int, bool], + ) -> dict: + command_type = self.select_command_type(command) + if command_type is "gql": + return await self.gql.send_command(command) + elif command_type is "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": + return await self.luci.send_command(command) + + @staticmethod + def select_command_type(command: Union[str, dict]) -> str: + if isinstance(command, dict): + return "gql" + elif command.startswith("grpc_"): + return "grpc" + else: + return "luci" + + async def multicommand( + self, *commands: Union[dict, str], allow_warning: bool = True + ) -> dict: + cmd_types = {"grpc": [], "gql": [], "luci": []} + for cmd in commands: + cmd_types[self.select_command_type(cmd)] = cmd + + async def no_op(): + return {} + + if len(cmd_types["grpc"]) > 0: + grpc_data_t = asyncio.create_task( + self.grpc.multicommand(*cmd_types["grpc"]) + ) + else: + grpc_data_t = no_op() + if len(cmd_types["gql"]) > 0: + gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) + else: + gql_data_t = no_op() + if len(cmd_types["luci"]) > 0: + luci_data_t = asyncio.create_task( + self.luci.multicommand(*cmd_types["luci"]) + ) + else: + luci_data_t = no_op() + + await asyncio.gather(grpc_data_t, gql_data_t, luci_data_t) + + data = dict( + **luci_data_t.result(), **gql_data_t.result(), **luci_data_t.result() + ) + return data diff --git a/pyasic/web/braiins_os/graphql.py b/pyasic/web/braiins_os/graphql.py new file mode 100644 index 00000000..7c00ffd5 --- /dev/null +++ b/pyasic/web/braiins_os/graphql.py @@ -0,0 +1,104 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ +import json +from typing import Union + +import httpx + +from pyasic import settings + + +class BOSerGraphQLAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: dict) -> dict: + def merge(*d: dict): + ret = {} + for i in d: + if i: + for k in i: + if not k in ret: + ret[k] = i[k] + else: + ret[k] = merge(ret[k], i[k]) + return None if ret == {} else ret + + command = merge(*commands) + data = await self.send_command(command) + if data is not None: + if data.get("data") is None: + try: + commands = list(commands) + # noinspection PyTypeChecker + commands.remove({"bos": {"faultLight": None}}) + command = merge(*commands) + data = await self.send_command(command) + except (LookupError, ValueError): + pass + if not data: + data = {} + data["multicommand"] = False + return data + + async def send_command( + self, + command: dict, + ) -> dict: + url = f"http://{self.ip}/graphql" + query = command + if command is None: + return {} + if command.get("query") is None: + query = {"query": self.parse_command(command)} + try: + async with httpx.AsyncClient(transport=settings.transport()) as client: + await self.auth(client) + data = await client.post(url, json=query) + except httpx.HTTPError: + pass + else: + if data.status_code == 200: + try: + return data.json() + except json.decoder.JSONDecodeError: + pass + + def parse_command(self, graphql_command: Union[dict, set]) -> str: + if isinstance(graphql_command, dict): + data = [] + for key in graphql_command: + if graphql_command[key] is not None: + parsed = self.parse_command(graphql_command[key]) + data.append(key + parsed) + else: + data.append(key) + else: + data = graphql_command + return "{" + ",".join(data) + "}" + + async def auth(self, client: httpx.AsyncClient) -> None: + url = f"http://{self.ip}/graphql" + await client.post( + url, + json={ + "query": ( + f'mutation{{auth{{login(username:"{self.username}", password:"{self.pwd}"){{__typename}}}}}}' + ) + }, + ) diff --git a/pyasic/web/bosminer/__init__.py b/pyasic/web/braiins_os/grpc.py similarity index 60% rename from pyasic/web/bosminer/__init__.py rename to pyasic/web/braiins_os/grpc.py index ab3909d3..821572e7 100644 --- a/pyasic/web/bosminer/__init__.py +++ b/pyasic/web/braiins_os/grpc.py @@ -13,258 +13,17 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import json from datetime import timedelta -from typing import Union -import httpx from betterproto import Message from grpclib.client import Channel -from pyasic import settings from pyasic.errors import APIError -from pyasic.web import BaseWebAPI from .proto.braiins.bos import * from .proto.braiins.bos.v1 import * -class BOSMinerWebAPI(BaseWebAPI): - def __init__(self, ip: str, boser: bool = None) -> None: - if boser is None: - boser = True - - if boser: - self.gql = BOSMinerGQLAPI( - ip, settings.get("default_bosminer_password", "root") - ) - self.grpc = BOSMinerGRPCAPI( - ip, settings.get("default_bosminer_password", "root") - ) - else: - self.gql = None - self.grpc = None - self.luci = BOSMinerLuCIAPI( - ip, settings.get("default_bosminer_password", "root") - ) - - self._pwd = settings.get("default_bosminer_password", "root") - super().__init__(ip) - - @property - def pwd(self): - return self._pwd - - @pwd.setter - def pwd(self, other: str): - self._pwd = other - self.luci.pwd = other - if self.gql is not None: - self.gql.pwd = other - if self.grpc is not None: - self.grpc.pwd = other - - async def send_command( - self, - command: Union[str, dict], - ignore_errors: bool = False, - allow_warning: bool = True, - **parameters: Union[str, int, bool], - ) -> dict: - if isinstance(command, dict): - if self.gql is not None: - return await self.gql.send_command(command) - elif command.startswith("/cgi-bin/luci"): - return await self.gql.send_command(command) - else: - if self.grpc is not None: - return await self.grpc.send_command(command) - - async def multicommand( - self, *commands: Union[dict, str], allow_warning: bool = True - ) -> dict: - luci_commands = [] - gql_commands = [] - grpc_commands = [] - for cmd in commands: - if isinstance(cmd, dict): - gql_commands.append(cmd) - elif cmd.startswith("/cgi-bin/luci"): - luci_commands.append(cmd) - else: - grpc_commands.append(cmd) - - luci_data = await self.luci.multicommand(*luci_commands) - if self.gql is not None: - gql_data = await self.gql.multicommand(*gql_commands) - else: - gql_data = None - if self.grpc is not None: - grpc_data = await self.grpc.multicommand(*grpc_commands) - else: - grpc_data = None - - if gql_data is None: - gql_data = {} - if luci_data is None: - luci_data = {} - if grpc_data is None: - grpc_data = {} - - data = dict(**luci_data, **gql_data, **grpc_data) - return data - - -class BOSMinerGQLAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: dict) -> dict: - def merge(*d: dict): - ret = {} - for i in d: - if i: - for k in i: - if not k in ret: - ret[k] = i[k] - else: - ret[k] = merge(ret[k], i[k]) - return None if ret == {} else ret - - command = merge(*commands) - data = await self.send_command(command) - if data is not None: - if data.get("data") is None: - try: - commands = list(commands) - # noinspection PyTypeChecker - commands.remove({"bos": {"faultLight": None}}) - command = merge(*commands) - data = await self.send_command(command) - except (LookupError, ValueError): - pass - if not data: - data = {} - data["multicommand"] = False - return data - - async def send_command( - self, - command: dict, - ) -> dict: - url = f"http://{self.ip}/graphql" - query = command - if command is None: - return {} - if command.get("query") is None: - query = {"query": self.parse_command(command)} - try: - async with httpx.AsyncClient(transport=settings.transport()) as client: - await self.auth(client) - data = await client.post(url, json=query) - except httpx.HTTPError: - pass - else: - if data.status_code == 200: - try: - return data.json() - except json.decoder.JSONDecodeError: - pass - - def parse_command(self, graphql_command: Union[dict, set]) -> str: - if isinstance(graphql_command, dict): - data = [] - for key in graphql_command: - if graphql_command[key] is not None: - parsed = self.parse_command(graphql_command[key]) - data.append(key + parsed) - else: - data.append(key) - else: - data = graphql_command - return "{" + ",".join(data) + "}" - - async def auth(self, client: httpx.AsyncClient) -> None: - url = f"http://{self.ip}/graphql" - await client.post( - url, - json={ - "query": ( - 'mutation{auth{login(username:"' - + "root" - + '", password:"' - + self.pwd - + '"){__typename}}}' - ) - }, - ) - - -class BOSMinerLuCIAPI: - def __init__(self, ip: str, pwd: str): - self.ip = ip - self.username = "root" - self.pwd = pwd - - async def multicommand(self, *commands: str) -> dict: - data = {} - for command in commands: - data[command] = await self.send_command(command, ignore_errors=True) - return data - - async def send_command(self, path: str, ignore_errors: bool = False) -> dict: - try: - async with httpx.AsyncClient(transport=settings.transport()) as client: - await self.auth(client) - data = await client.get( - f"http://{self.ip}{path}", headers={"User-Agent": "BTC Tools v0.1"} - ) - if data.status_code == 200: - return data.json() - if ignore_errors: - return {} - raise APIError( - f"Web command failed: path={path}, code={data.status_code}" - ) - except (httpx.HTTPError, json.JSONDecodeError): - if ignore_errors: - return {} - raise APIError(f"Web command failed: path={path}") - - async def auth(self, session: httpx.AsyncClient): - login = {"luci_username": self.username, "luci_password": self.pwd} - url = f"http://{self.ip}/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", - } - await session.post(url, headers=headers, data=login) - - async def get_net_conf(self): - return await self.send_command("/cgi-bin/luci/admin/network/iface_status/lan") - - async def get_cfg_metadata(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_metadata") - - async def get_cfg_data(self): - return await self.send_command("/cgi-bin/luci/admin/miner/cfg_data") - - async def get_bos_info(self): - return await self.send_command("/cgi-bin/luci/bos/info") - - async def get_overview(self): - return await self.send_command( - "/cgi-bin/luci/admin/status/overview?status=1" - ) # needs status=1 or it fails - - async def get_api_status(self): - return await self.send_command("/cgi-bin/luci/admin/miner/api_status") - - class BOSMinerGRPCStub( ApiVersionServiceStub, AuthenticationServiceStub, @@ -279,7 +38,7 @@ class BOSMinerGRPCStub( pass -class BOSMinerGRPCAPI: +class BOSerGRPCAPI: def __init__(self, ip: str, pwd: str): self.ip = ip self.username = "root" diff --git a/pyasic/web/braiins_os/luci.py b/pyasic/web/braiins_os/luci.py new file mode 100644 index 00000000..f8a745c2 --- /dev/null +++ b/pyasic/web/braiins_os/luci.py @@ -0,0 +1,83 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ +import json + +import httpx + +from pyasic import settings +from pyasic.errors import APIError + + +class BOSMinerLuCIAPI: + def __init__(self, ip: str, pwd: str): + self.ip = ip + self.username = "root" + self.pwd = pwd + + async def multicommand(self, *commands: str) -> dict: + data = {} + for command in commands: + data[command] = await self.send_command(command, ignore_errors=True) + return data + + async def send_command(self, path: str, ignore_errors: bool = False) -> dict: + try: + 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}", + headers={"User-Agent": "BTC Tools v0.1"}, + ) + if data.status_code == 200: + return data.json() + if ignore_errors: + return {} + raise APIError( + f"LUCI web command failed: path={path}, code={data.status_code}" + ) + except (httpx.HTTPError, json.JSONDecodeError): + if ignore_errors: + return {} + raise APIError(f"LUCI web command failed: path={path}") + + async def auth(self, session: httpx.AsyncClient): + login = {"luci_username": self.username, "luci_password": self.pwd} + url = f"http://{self.ip}/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", + } + await session.post(url, headers=headers, data=login) + + async def get_net_conf(self): + return await self.send_command("admin/network/iface_status/lan") + + async def get_cfg_metadata(self): + return await self.send_command("admin/miner/cfg_metadata") + + async def get_cfg_data(self): + return await self.send_command("admin/miner/cfg_data") + + async def get_bos_info(self): + return await self.send_command("bos/info") + + async def get_overview(self): + return await self.send_command( + "admin/status/overview?status=1" + ) # needs status=1 or it fails + + async def get_api_status(self): + return await self.send_command("admin/miner/api_status") diff --git a/pyasic/web/bosminer/proto/__init__.py b/pyasic/web/braiins_os/proto/__init__.py similarity index 100% rename from pyasic/web/bosminer/proto/__init__.py rename to pyasic/web/braiins_os/proto/__init__.py diff --git a/pyasic/web/bosminer/proto/braiins/__init__.py b/pyasic/web/braiins_os/proto/braiins/__init__.py similarity index 100% rename from pyasic/web/bosminer/proto/braiins/__init__.py rename to pyasic/web/braiins_os/proto/braiins/__init__.py diff --git a/pyasic/web/bosminer/proto/braiins/bos/__init__.py b/pyasic/web/braiins_os/proto/braiins/bos/__init__.py similarity index 100% rename from pyasic/web/bosminer/proto/braiins/bos/__init__.py rename to pyasic/web/braiins_os/proto/braiins/bos/__init__.py diff --git a/pyasic/web/bosminer/proto/braiins/bos/v1/__init__.py b/pyasic/web/braiins_os/proto/braiins/bos/v1/__init__.py similarity index 100% rename from pyasic/web/bosminer/proto/braiins/bos/v1/__init__.py rename to pyasic/web/braiins_os/proto/braiins/bos/v1/__init__.py diff --git a/pyasic/web/vnish.py b/pyasic/web/vnish.py index 881f0814..f9566cf0 100644 --- a/pyasic/web/vnish.py +++ b/pyasic/web/vnish.py @@ -145,3 +145,6 @@ class VNishWebAPI(BaseWebAPI): async def settings(self): return await self.send_command("settings") + + async def autotune_presets(self): + return await self.send_command("autotune/presets") From 36494f2acac2d3a163710ea50d78ba6de900feaa Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 10 Jan 2024 22:15:31 -0700 Subject: [PATCH 02/36] bug: remove boser check in miner_factory, and fix bad syntax on comparison. --- pyasic/miners/miner_factory.py | 20 +------------------- pyasic/web/braiins_os/__init__.py | 6 +++--- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index c35e05c6..eae3b83e 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -484,15 +484,10 @@ class MinerFactory: except asyncio.TimeoutError: pass - boser_enabled = None - if miner_type == MinerTypes.BRAIINS_OS: - boser_enabled = await self.get_boser_braiins_os(ip) - miner = self._select_miner_from_classes( ip, miner_type=miner_type, miner_model=miner_model, - boser_enabled=boser_enabled, ) if miner is not None and not isinstance(miner, UnknownMiner): @@ -775,13 +770,9 @@ class MinerFactory: ip: ipaddress.ip_address, miner_model: Union[str, None], miner_type: Union[MinerTypes, None], - boser_enabled: bool = None, ) -> AnyMiner: - kwargs = {} - if boser_enabled is not None: - kwargs["boser"] = boser_enabled try: - return MINER_CLASSES[miner_type][str(miner_model).upper()](ip, **kwargs) + return MINER_CLASSES[miner_type][str(miner_model).upper()](ip) except LookupError: if miner_type in MINER_CLASSES: return MINER_CLASSES[miner_type][None](ip) @@ -909,15 +900,6 @@ class MinerFactory: except (httpx.HTTPError, LookupError): pass - async def get_boser_braiins_os(self, ip: str): - # TODO: refine this check - try: - sock_json_data = await self.send_api_command(ip, "version") - return sock_json_data["STATUS"][0]["Msg"].split(" ")[0].upper() == "BOSER" - except LookupError: - # let the bosminer class decide - return None - async def get_miner_model_vnish(self, ip: str) -> Optional[str]: sock_json_data = await self.send_api_command(ip, "stats") try: diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index a884e0fa..f40666a1 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -84,14 +84,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 From 7ab3d8b54e16494c197df61627f0aadb17f7cdc6 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 10 Jan 2024 22:26:28 -0700 Subject: [PATCH 03/36] feature: improve data gathering slightly on BOSMiner. --- pyasic/miners/backends/braiins_os.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index d2ca51d8..f00ef7f5 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -48,7 +48,9 @@ BOSMINER_DATA_LOC = DataLocations( str(DataOptions.API_VERSION): DataFunction( "get_api_ver", [RPCAPICommand("api_version", "version")] ), - str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), + 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", @@ -385,15 +387,20 @@ 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.send_command("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] + 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 From bb481553fabf1faf3fcc4a3aadb3efb1a7aaed95 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 10 Jan 2024 22:46:58 -0700 Subject: [PATCH 04/36] bug: fix missing message in grpc command. --- pyasic/web/braiins_os/grpc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 821572e7..3a70196d 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -138,7 +138,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( From 014896ae1b8c8e40a318807c8540b0d2b12d40e4 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 09:53:06 -0700 Subject: [PATCH 05/36] bug: fix data passed by get_version to BOSminer. --- pyasic/miners/backends/braiins_os.py | 38 ++++++++++++++++------------ 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index f00ef7f5..0bc04300 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -289,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") @@ -314,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") @@ -335,9 +343,7 @@ class BOSMiner(BaseMiner): 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 @@ -361,11 +367,11 @@ class BOSMiner(BaseMiner): return "? (BOS)" async def get_version( - self, api_version: dict = None, graphql_version: dict = None + self, api_version: dict = None, web_bos_info: 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()) + fw_ver_t = asyncio.create_task(self.get_fw_ver(web_bos_info)) await asyncio.gather(api_ver_t, fw_ver_t) return miner_version(api_ver=api_ver_t.result(), fw_ver=fw_ver_t.result()) From 895fb1b43e442007e444c10baccb609e3d159733 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 10:20:18 -0700 Subject: [PATCH 06/36] refactor: swap `except (KeyError, ValueError)` to `except LookupError`. --- pyasic/miners/antminer/hiveon/X9/T9.py | 8 ++++---- pyasic/miners/backends/antminer.py | 6 +++--- pyasic/miners/backends/bfgminer.py | 6 +++--- pyasic/miners/backends/bmminer.py | 6 +++--- pyasic/miners/backends/braiins_os.py | 18 +++++++++--------- pyasic/miners/backends/btminer.py | 12 ++++++------ pyasic/miners/backends/cgminer.py | 8 ++++---- pyasic/miners/backends/luxminer.py | 2 +- pyasic/miners/innosilicon/cgminer/A10X/A10X.py | 4 ++-- pyasic/miners/innosilicon/cgminer/T3X/T3H.py | 2 +- 10 files changed, 36 insertions(+), 36 deletions(-) diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index 33e4e77d..691d150c 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -62,14 +62,14 @@ class HiveonT9(Hiveon, T9): try: hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"] hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"] - except (KeyError, IndexError): + except LookupError: pass else: hashboard.missing = False try: hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"] chips += api_stats["STATS"][1][f"chain_acn{chipset}"] - except (KeyError, IndexError): + except LookupError: pass hashboard.hashrate = round(hashrate / 1000, 2) hashboard.chips = chips @@ -88,7 +88,7 @@ class HiveonT9(Hiveon, T9): boards = api_stats.get("STATS") try: wattage_raw = boards[1]["chain_power"] - except (KeyError, IndexError): + except LookupError: pass else: # parse wattage position out of raw data @@ -113,7 +113,7 @@ class HiveonT9(Hiveon, T9): env_temp = api_stats["STATS"][1][f"temp3_{chipset}"] if not env_temp == 0: env_temp_list.append(int(env_temp)) - except (KeyError, IndexError): + except LookupError: pass if not env_temp_list == []: diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index e314f761..c9678f14 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -190,7 +190,7 @@ class AntminerModern(BMMiner): errors.append(X19Error(item["msg"])) except KeyError: continue - except (KeyError, IndexError): + except LookupError: pass return errors @@ -267,7 +267,7 @@ class AntminerModern(BMMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def set_static_ip( @@ -499,7 +499,7 @@ class AntminerOld(CGMiner): fans_data[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) - except (KeyError, IndexError): + except LookupError: pass return fans_data diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py index 06f83423..047cf2db 100644 --- a/pyasic/miners/backends/bfgminer.py +++ b/pyasic/miners/backends/bfgminer.py @@ -134,7 +134,7 @@ class BFGMiner(BaseMiner): if api_version: try: self.api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: pass return self.api_ver @@ -153,7 +153,7 @@ class BFGMiner(BaseMiner): if api_version: try: self.fw_ver = api_version["VERSION"][0]["CompileTime"] - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver @@ -311,7 +311,7 @@ class BFGMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def is_mining(self, *args, **kwargs) -> Optional[bool]: diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py index 5999c692..828bfd0b 100644 --- a/pyasic/miners/backends/bmminer.py +++ b/pyasic/miners/backends/bmminer.py @@ -173,7 +173,7 @@ class BMMiner(BaseMiner): if api_version: try: self.api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: pass return self.api_ver @@ -192,7 +192,7 @@ class BMMiner(BaseMiner): if api_version: try: self.fw_ver = api_version["VERSION"][0]["CompileTime"] - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver @@ -360,7 +360,7 @@ class BMMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def is_mining(self, *args, **kwargs) -> Optional[bool]: diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 0bc04300..6952614c 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -386,7 +386,7 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -459,7 +459,7 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except (KeyError, IndexError): + except LookupError: api_temps = None try: api_devdetails = d["devdetails"][0] @@ -467,7 +467,7 @@ class BOSMiner(BaseMiner): api_devdetails = None try: api_devs = d["devs"][0] - except (KeyError, IndexError): + except LookupError: api_devs = None if api_temps: try: @@ -522,7 +522,7 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except (KeyError, IndexError): + except LookupError: pass async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: @@ -535,7 +535,7 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except (KeyError, IndexError): + except LookupError: pass async def get_fans(self, api_fans: dict = None) -> List[Fan]: @@ -1091,7 +1091,7 @@ class BOSer(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -1283,7 +1283,7 @@ class BOSer(BaseMiner): api_temps = None try: api_devdetails = d["devdetails"][0] - except (KeyError, IndexError): + except LookupError: api_devdetails = None try: api_devs = d["devs"][0] @@ -1363,7 +1363,7 @@ class BOSer(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except (KeyError, IndexError): + except LookupError: pass async def get_wattage_limit( @@ -1517,7 +1517,7 @@ class BOSer(BaseMiner): _error = _error[0].lower() + _error[1:] errors.append(BraiinsOSError(f"Slot {_id} {_error}")) return errors - except (KeyError, IndexError): + except LookupError: pass async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index 9af81880..476f9cf1 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -309,7 +309,7 @@ class BTMiner(BaseMiner): try: mac = api_summary["SUMMARY"][0]["MAC"] return str(mac).upper() - except (KeyError, IndexError): + except LookupError: pass async def get_version( @@ -383,7 +383,7 @@ class BTMiner(BaseMiner): self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace( "'", "" ) - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver @@ -448,7 +448,7 @@ class BTMiner(BaseMiner): hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].serial_number = board["PCB SN"] hashboards[board["ASC"]].missing = False - except (KeyError, IndexError): + except LookupError: pass return hashboards @@ -477,7 +477,7 @@ class BTMiner(BaseMiner): try: wattage = api_summary["SUMMARY"][0]["Power"] return wattage if not wattage == -1 else None - except (KeyError, IndexError): + except LookupError: pass async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: @@ -510,7 +510,7 @@ class BTMiner(BaseMiner): Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)), ] - except (KeyError, IndexError): + except LookupError: pass return fans @@ -589,7 +589,7 @@ class BTMiner(BaseMiner): expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] if expected_hashrate: return round(expected_hashrate / 1000, 2) - except (KeyError, IndexError): + except LookupError: pass async def get_fault_light(self, api_get_miner_info: dict = None) -> bool: diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py index 424a979c..6b8a5be8 100644 --- a/pyasic/miners/backends/cgminer.py +++ b/pyasic/miners/backends/cgminer.py @@ -206,7 +206,7 @@ class CGMiner(BaseMiner): if api_version: try: self.api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: pass return self.api_ver @@ -224,7 +224,7 @@ class CGMiner(BaseMiner): if api_version: try: self.fw_ver = api_version["VERSION"][0]["CGMiner"] - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver @@ -336,7 +336,7 @@ class CGMiner(BaseMiner): fans[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) - except (KeyError, IndexError): + except LookupError: pass return fans @@ -370,7 +370,7 @@ class CGMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def is_mining(self, *args, **kwargs) -> Optional[bool]: diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index d54061da..dade9121 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -346,7 +346,7 @@ class LUXMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def is_mining(self) -> Optional[bool]: diff --git a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py index d1e8c77d..b208c913 100644 --- a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py +++ b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py @@ -145,7 +145,7 @@ class CGMinerA10X(CGMiner, A10X): return round( float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000000000), 5 ) - except (KeyError, IndexError): + except LookupError: pass async def get_hashboards( @@ -303,7 +303,7 @@ class CGMinerA10X(CGMiner, A10X): if api_version: try: self.fw_ver = api_version["VERSION"][0]["CGMiner"].split("-")[-1:][0] - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver diff --git a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py index 321c151a..4c512fdd 100644 --- a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py +++ b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py @@ -123,7 +123,7 @@ class CGMinerT3HPlus(CGMiner, T3HPlus): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError): + except LookupError: pass async def get_hashboards( From 39299f2cfa56ef5075fe559c24ed5886741046eb Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 11 Jan 2024 16:00:03 +0000 Subject: [PATCH 07/36] fix: docs/requirements.txt to reduce vulnerabilities The following vulnerabilities are fixed by pinning transitive dependencies: - https://snyk.io/vuln/SNYK-PYTHON-JINJA2-6150717 --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 464409ee..f27379a8 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,3 +1,3 @@ -jinja2<3.1.0 +jinja2<3.1.3 mkdocs mkdocstrings[python] From ed6eb11653e3a72fafd188f1595292130d9b3ced Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 13:57:48 -0700 Subject: [PATCH 08/36] bug: fix being unable to get fw version as part of multicommand. --- pyasic/miners/backends/braiins_os.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 6952614c..3641c2ae 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -396,10 +396,14 @@ class BOSMiner(BaseMiner): 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.send_command("bos/info") + web_bos_info = await self.web.luci.get_bos_info() except APIError: return None + 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: From 6c9a378eee6db3186cf36671ea330c4c8e6b939b Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 11:54:17 -0700 Subject: [PATCH 09/36] feature: add boser config parsing. --- pyasic/config/__init__.py | 22 +++++++++++----- pyasic/config/base.py | 6 ++--- pyasic/config/fans.py | 20 ++++++++++++++ pyasic/config/mining.py | 30 +++++++++++++++++++++ pyasic/config/pools.py | 35 ++++++++++++++++++++++++- pyasic/config/power_scaling.py | 39 +++++++++++++++++++++++++--- pyasic/config/temperature.py | 31 ++++++++++++++++++++++ pyasic/miners/backends/braiins_os.py | 18 ++----------- pyasic/web/braiins_os/grpc.py | 5 ++-- 9 files changed, 174 insertions(+), 32 deletions(-) diff --git a/pyasic/config/__init__.py b/pyasic/config/__init__.py index c3ba16d4..ae84f00b 100644 --- a/pyasic/config/__init__.py +++ b/pyasic/config/__init__.py @@ -99,13 +99,13 @@ class MinerConfig: **self.power_scaling.as_bosminer(), } - def as_bos_grpc(self, user_suffix: str = None) -> dict: + def as_boser(self, user_suffix: str = None) -> dict: return { - **self.fan_mode.as_bos_grpc(), - **self.temperature.as_bos_grpc(), - **self.mining_mode.as_bos_grpc(), - **self.pools.as_bos_grpc(user_suffix=user_suffix), - **self.power_scaling.as_bos_grpc(), + **self.fan_mode.as_boser(), + **self.temperature.as_boser(), + **self.mining_mode.as_boser(), + **self.pools.as_boser(user_suffix=user_suffix), + **self.power_scaling.as_boser(), } def as_epic(self, user_suffix: str = None) -> dict: @@ -161,6 +161,16 @@ class MinerConfig: power_scaling=PowerScalingConfig.from_bosminer(toml_conf), ) + @classmethod + def from_boser(cls, grpc_miner_conf: dict) -> "MinerConfig": + return cls( + pools=PoolConfig.from_boser(grpc_miner_conf), + mining_mode=MiningModeConfig.from_boser(grpc_miner_conf), + fan_mode=FanModeConfig.from_boser(grpc_miner_conf), + temperature=TemperatureConfig.from_boser(grpc_miner_conf), + power_scaling=PowerScalingConfig.from_boser(grpc_miner_conf), + ) + @classmethod def from_epic(cls, web_conf: dict) -> "MinerConfig": return cls( diff --git a/pyasic/config/base.py b/pyasic/config/base.py index 3f38adeb..df5946ac 100644 --- a/pyasic/config/base.py +++ b/pyasic/config/base.py @@ -44,8 +44,8 @@ class MinerConfigOption(Enum): def as_bosminer(self) -> dict: return self.value.as_bosminer() - def as_bos_grpc(self) -> dict: - return self.value.as_bos_grpc() + def as_boser(self) -> dict: + return self.value.as_boser() def as_epic(self) -> dict: return self.value.as_epic() @@ -91,7 +91,7 @@ class MinerConfigValue: def as_bosminer(self) -> dict: return {} - def as_bos_grpc(self) -> dict: + def as_boser(self) -> dict: return {} def as_epic(self) -> dict: diff --git a/pyasic/config/fans.py b/pyasic/config/fans.py index 5d93bcd0..04d95434 100644 --- a/pyasic/config/fans.py +++ b/pyasic/config/fans.py @@ -182,3 +182,23 @@ class FanModeConfig(MinerConfigOption): return cls.manual().from_vnish(web_settings["miner"]["cooling"]) elif mode == "immers": return cls.immersion() + + @classmethod + def from_boser(cls, grpc_miner_conf: dict): + try: + temperature_conf = grpc_miner_conf["temperature"] + except LookupError: + return cls.default() + + keys = temperature_conf.keys() + if "auto" in keys: + if "minimumRequiredFans" in keys: + return cls.normal(temperature_conf["minimumRequiredFans"]) + return cls.normal() + if "manual" in keys: + conf = {} + if "fanSpeedRatio" in temperature_conf["manual"].keys(): + conf["speed"] = int(temperature_conf["manual"]["fanSpeedRatio"]) + if "minimumRequiredFans" in keys: + conf["minimum_fans"] = int(temperature_conf["minimumRequiredFans"]) + return cls.manual(**conf) diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index 1b9a7eb3..f4ae4781 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -260,3 +260,33 @@ class MiningModeConfig(MinerConfigOption): return MiningModeManual.from_vnish(mode_settings) else: return cls.power_tuning(int(mode_settings["preset"])) + + @classmethod + def from_boser(cls, grpc_miner_conf: dict): + try: + tuner_conf = grpc_miner_conf["tuner"] + if not tuner_conf.get("enabled", False): + return cls.default() + except LookupError: + return cls.default() + + if tuner_conf.get("tunerMode") is not None: + if tuner_conf["tunerMode"] == 1: + if tuner_conf.get("powerTarget") is not None: + return cls.power_tuning(tuner_conf["powerTarget"]["watt"]) + return cls.power_tuning() + + if tuner_conf["tunerMode"] == 2: + if tuner_conf.get("hashrateTarget") is not None: + return cls.hashrate_tuning( + int(tuner_conf["hashrateTarget"]["terahashPerSecond"]) + ) + return cls.hashrate_tuning() + + if tuner_conf.get("powerTarget") is not None: + return cls.power_tuning(tuner_conf["powerTarget"]["watt"]) + + if tuner_conf.get("hashrateTarget") is not None: + return cls.hashrate_tuning( + int(tuner_conf["hashrateTarget"]["terahashPerSecond"]) + ) diff --git a/pyasic/config/pools.py b/pyasic/config/pools.py index b2d10fb5..82fb7535 100644 --- a/pyasic/config/pools.py +++ b/pyasic/config/pools.py @@ -149,6 +149,14 @@ class Pool(MinerConfigValue): password=web_pool["pass"], ) + @classmethod + def from_boser(cls, grpc_pool: dict) -> "Pool": + return cls( + url=grpc_pool["url"], + user=grpc_pool["user"], + password=grpc_pool["password"], + ) + @dataclass class PoolGroup(MinerConfigValue): @@ -287,6 +295,19 @@ class PoolGroup(MinerConfigValue): def from_vnish(cls, web_settings_pools: dict) -> "PoolGroup": return cls([Pool.from_vnish(p) for p in web_settings_pools]) + @classmethod + def from_boser(cls, grpc_pool_group: dict): + try: + return cls( + pools=[Pool.from_boser(p) for p in grpc_pool_group["pools"]], + name=grpc_pool_group["name"], + quota=grpc_pool_group["quota"]["value"] + if grpc_pool_group.get("quota") is not None + else 1, + ) + except LookupError: + return cls() + @dataclass class PoolConfig(MinerConfigValue): @@ -349,7 +370,7 @@ class PoolConfig(MinerConfigValue): } return {"group": [PoolGroup().as_bosminer()]} - def as_bos_grpc(self, user_suffix: str = None) -> dict: + def as_boser(self, user_suffix: str = None) -> dict: return {} @classmethod @@ -394,3 +415,15 @@ class PoolConfig(MinerConfigValue): return cls([PoolGroup.from_vnish(web_settings["miner"]["pools"])]) except LookupError: return cls() + + @classmethod + def from_boser(cls, grpc_miner_conf: dict): + try: + return cls( + groups=[ + PoolGroup.from_boser(group) + for group in grpc_miner_conf["poolGroups"] + ] + ) + except LookupError: + return cls() diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index b373e51d..0e4bf336 100644 --- a/pyasic/config/power_scaling.py +++ b/pyasic/config/power_scaling.py @@ -37,7 +37,7 @@ class PowerScalingShutdownEnabled(MinerConfigValue): return cfg - def as_bos_grpc(self) -> dict: + def as_boser(self) -> dict: cfg = {"enable_shutdown ": True} if self.duration is not None: @@ -57,7 +57,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 +88,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,7 +146,7 @@ class PowerScalingEnabled(MinerConfigValue): return {"power_scaling": cfg} - def as_bos_grpc(self) -> dict: + def as_boser(self) -> dict: cfg = {"enable": True} target_conf = {} if self.power_step is not None: @@ -144,7 +157,7 @@ class PowerScalingEnabled(MinerConfigValue): cfg["target"] = DpsTarget(power_target=DpsPowerTarget(**target_conf)) if self.shutdown_enabled is not None: - cfg = {**cfg, **self.shutdown_enabled.as_bos_grpc()} + cfg = {**cfg, **self.shutdown_enabled.as_boser()} return {"dps": cfg} @@ -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/config/temperature.py b/pyasic/config/temperature.py index 07ec4f60..60b48762 100644 --- a/pyasic/config/temperature.py +++ b/pyasic/config/temperature.py @@ -80,3 +80,34 @@ class TemperatureConfig(MinerConfigValue): except KeyError: pass return cls() + + @classmethod + def from_boser(cls, grpc_miner_conf: dict): + try: + temperature_conf = grpc_miner_conf["temperature"] + except KeyError: + return cls.default() + + root_key = None + for key in ["auto", "manual", "disabled"]: + if key in temperature_conf.keys(): + root_key = key + break + if root_key is None: + return cls.default() + + conf = {} + keys = temperature_conf[root_key].keys() + if "targetTemperature" in keys: + conf["target"] = int( + temperature_conf[root_key]["targetTemperature"]["degreeC"] + ) + if "hotTemperature" in keys: + conf["hot"] = int(temperature_conf[root_key]["hotTemperature"]["degreeC"]) + if "dangerousTemperature" in keys: + conf["danger"] = int( + temperature_conf[root_key]["dangerousTemperature"]["degreeC"] + ) + + return cls(**conf) + return cls.default() diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 3641c2ae..48de0a3e 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -908,23 +908,9 @@ 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: logging.debug(f"{self}: Sending config.") diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 3a70196d..0dcf4a68 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -43,6 +43,7 @@ class BOSerGRPCAPI: self.ip = ip self.username = "root" self.pwd = pwd + self.port = 50051 self._auth = None self._auth_time = datetime.now() @@ -76,7 +77,7 @@ class BOSerGRPCAPI: metadata = [] if auth: metadata.append(("authorization", await self.auth())) - async with Channel(self.ip, 50051) as c: + async with Channel(self.ip, self.port) as c: endpoint = getattr(BOSMinerGRPCStub(c), command) if endpoint is None: if not ignore_errors: @@ -93,7 +94,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", From 53a018f526f510e7c15fb8cc20d08a45d5f06c2c Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 11:58:26 -0700 Subject: [PATCH 10/36] feature: add boser fault light functions. --- pyasic/miners/backends/braiins_os.py | 148 +-------------------------- 1 file changed, 5 insertions(+), 143 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 48de0a3e..fc1cac3b 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -817,54 +817,15 @@ class BOSer(BaseMiner): # data storage self.api_ver = api_ver - async def send_ssh_command(self, cmd: str) -> Optional[str]: - result = None - - try: - conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10) - except (ConnectionError, asyncio.TimeoutError): - return None - - # open an ssh connection - async with conn: - # 3 retries - for i in range(3): - try: - # run the command and get the result - result = await conn.run(cmd) - stderr = result.stderr - result = result.stdout - - if len(stderr) > len(result): - result = stderr - - except Exception as e: - # if the command fails, log it - logging.warning(f"{self} command {cmd} error: {e}") - - # on the 3rd retry, return None - if i == 3: - return - continue - # return the result, either command output or None - return result - async def fault_light_on(self) -> bool: - logging.debug(f"{self}: Sending fault_light on command.") - ret = await self.send_ssh_command("miner fault_light on") - logging.debug(f"{self}: fault_light on command completed.") - if isinstance(ret, str): - self.light = True - return self.light + resp = await self.web.grpc.set_locate_device_status(True) + if resp.get("enabled", False): + return True return False async def fault_light_off(self) -> bool: - logging.debug(f"{self}: Sending fault_light off command.") - self.light = False - ret = await self.send_ssh_command("miner fault_light off") - logging.debug(f"{self}: fault_light off command completed.") - if isinstance(ret, str): - self.light = False + resp = await self.web.grpc.set_locate_device_status(False) + if resp == {}: return True return False @@ -915,60 +876,7 @@ class BOSer(BaseMiner): async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: logging.debug(f"{self}: Sending config.") self.config = config - - if self.web.grpc is not None: - try: - await self._send_config_grpc(config, user_suffix) - return - except: - pass - await self._send_config_bosminer(config, user_suffix) - - async def _send_config_grpc(self, config: MinerConfig, user_suffix: str = None): raise NotImplementedError - mining_mode = config.mining_mode - - async def _send_config_bosminer(self, config: MinerConfig, user_suffix: str = None): - toml_conf = toml.dumps( - { - "format": { - "version": "1.2+", - "generator": "pyasic", - "model": f"{self.make.replace('Miner', 'miner')} {self.model.replace(' (BOS)', '').replace('j', 'J')}", - "timestamp": int(time.time()), - }, - **config.as_bosminer(user_suffix=user_suffix), - } - ) - try: - conn = await self._get_ssh_connection() - except ConnectionError as e: - raise APIError("SSH connection failed when sending config.") from e - async with conn: - # BBB check because bitmain suxx - bbb_check = await conn.run( - "if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;" - ) - - bbb = bbb_check.stdout.strip() == "1" - - if not bbb: - await conn.run("/etc/init.d/bosminer stop") - logging.debug(f"{self}: Opening SFTP connection.") - async with conn.start_sftp_client() as sftp: - logging.debug(f"{self}: Opening config file.") - async with sftp.open("/etc/bosminer.toml", "w+") as file: - await file.write(toml_conf) - logging.debug(f"{self}: Restarting BOSMiner") - await conn.run("/etc/init.d/bosminer start") - - # I really hate BBB, please get rid of it if you have it - else: - await conn.run("/etc/init.d/S99bosminer stop") - logging.debug(f"{self}: BBB sending config") - await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml") - logging.debug(f"{self}: BBB restarting bosminer.") - await conn.run("/etc/init.d/S99bosminer start") async def set_power_limit(self, wattage: int) -> bool: try: @@ -983,52 +891,6 @@ class BOSer(BaseMiner): else: return True - async def set_static_ip( - self, - ip: str, - dns: str, - 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 - + "'" - ) - data = await self.send_ssh_command("cat /etc/config/network") - - split_data = data.split("\n\n") - for idx in range(len(split_data)): - if "config interface 'lan'" in split_data[idx]: - split_data[idx] = cfg_data_lan - config = "\n\n".join(split_data) - - conn = await self._get_ssh_connection() - - async with conn: - 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'" - data = await self.send_ssh_command("cat /etc/config/network") - - split_data = data.split("\n\n") - for idx in range(len(split_data)): - if "config interface 'lan'" in split_data[idx]: - split_data[idx] = cfg_data_lan - config = "\n\n".join(split_data) - - conn = await self._get_ssh_connection() - - async with conn: - await conn.run("echo '" + config + "' > /etc/config/network") - ################################################## ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## From 361d6e07cc762e20b71f4e646bc67c6d97c6f6e8 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 13:29:46 -0700 Subject: [PATCH 11/36] feature: finish get_data functions for bosminer --- pyasic/miners/backends/braiins_os.py | 621 +++++---------------------- pyasic/web/braiins_os/__init__.py | 24 +- pyasic/web/braiins_os/grpc.py | 22 +- 3 files changed, 141 insertions(+), 526 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index fc1cac3b..e8850922 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -33,6 +33,7 @@ from pyasic.miners.base import ( DataLocations, DataOptions, GraphQLCommand, + GRPCCommand, RPCAPICommand, WebAPICommand, ) @@ -665,128 +666,57 @@ BOSER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( "get_mac", - [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_version", "version")] + "get_api_ver", [GRPCCommand("api_version", "get_api_version")] ), str(DataOptions.FW_VERSION): DataFunction( "get_fw_ver", - [ - GraphQLCommand( - "graphql_version", {"bos": {"info": {"version": {"full": None}}}} - ) - ], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HOSTNAME): DataFunction( "get_hostname", - [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHRATE): DataFunction( "get_hashrate", - [ - RPCAPICommand("api_summary", "summary"), - GraphQLCommand( - "graphql_hashrate", - { - "bosminer": { - "info": {"workSolver": {"realHashrate": {"mhs1M": None}}} - } - }, - ), - ], + [RPCAPICommand("api_summary", "summary")], ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + "get_expected_hashrate", + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHBOARDS): DataFunction( "get_hashboards", - [ - RPCAPICommand("api_temps", "temps"), - RPCAPICommand("api_devdetails", "devdetails"), - RPCAPICommand("api_devs", "devs"), - GraphQLCommand( - "graphql_boards", - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "realHashrate": {"mhs1M": None}, - "hwDetails": {"chips": None}, - "temperatures": {"degreesC": None}, - } - } - } - } - }, - ), - ], + [GRPCCommand("grpc_hashboards", "get_hashboards")], ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), str(DataOptions.WATTAGE): DataFunction( "get_wattage", - [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_wattage", - { - "bosminer": { - "info": { - "workSolver": {"power": {"approxConsumptionW": None}} - } - } - }, - ), - ], + [GRPCCommand("grpc_miner_stats", "get_miner_stats")], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( "get_wattage_limit", [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_wattage_limit", - {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}}, - ), + GRPCCommand( + "grpc_active_performance_mode", "get_active_performance_mode" + ) ], ), str(DataOptions.FANS): DataFunction( "get_fans", - [ - RPCAPICommand("api_fans", "fans"), - GraphQLCommand( - "graphql_fans", - {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}}, - ), - ], + [GRPCCommand("grpc_cooling_state", "get_cooling_state")], ), str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.ERRORS): DataFunction( "get_errors", - [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_errors", - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "tuner": {"statusMessages": None}, - } - } - } - } - }, - ), - ], + [RPCAPICommand("api_tunerstatus", "tunerstatus")], ), str(DataOptions.FAULT_LIGHT): DataFunction( "get_fault_light", - [GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})], + [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], ), str(DataOptions.IS_MINING): DataFunction( "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] @@ -830,41 +760,29 @@ class BOSer(BaseMiner): return False async def restart_backend(self) -> bool: - return await self.restart_bosminer() + return await self.restart_boser() - async def restart_bosminer(self) -> bool: - logging.debug(f"{self}: Sending bosminer restart command.") - ret = await self.send_ssh_command("/etc/init.d/bosminer restart") - logging.debug(f"{self}: bosminer restart command completed.") - if isinstance(ret, str): - return True - return False + async def restart_boser(self) -> bool: + ret = await self.web.grpc.restart() + return True async def stop_mining(self) -> bool: try: - data = await self.api.pause() + await self.web.grpc.pause_mining() except APIError: return False - if data.get("PAUSE"): - if data["PAUSE"][0]: - return True - return False + return True async def resume_mining(self) -> bool: try: - data = await self.api.resume() + await self.web.grpc.resume_mining() except APIError: return False - if data.get("RESUME"): - if data["RESUME"][0]: - return True - return False + return True async def reboot(self) -> bool: - logging.debug(f"{self}: Sending reboot command.") - ret = await self.send_ssh_command("/sbin/reboot") - logging.debug(f"{self}: Reboot command completed.") - if isinstance(ret, str): + ret = await self.web.grpc.reboot() + if ret == {}: return True return False @@ -895,28 +813,18 @@ class BOSer(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: - if not web_net_conf: + async def get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: + if not grpc_miner_details: try: - web_net_conf = await self.web.send_command( - "admin/network/iface_status/lan" - ) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass - if isinstance(web_net_conf, dict): - if "admin/network/iface_status/lan" in web_net_conf.keys(): - web_net_conf = web_net_conf["admin/network/iface_status/lan"] - - if web_net_conf: + if grpc_miner_details: try: - return web_net_conf[0]["macaddr"] - except LookupError: + return grpc_miner_details["macAddress"].upper() + except (LookupError, TypeError): pass - # could use ssh, but its slow and buggy - # result = await self.send_ssh_command("cat /sys/class/net/eth0/address") - # if result: - # return result.upper().strip() async def get_model(self) -> Optional[str]: if self.model is not None: @@ -950,27 +858,21 @@ class BOSer(BaseMiner): return self.api_ver - async def get_fw_ver(self, graphql_version: dict = None) -> Optional[str]: - if not graphql_version: + async def get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: + if not grpc_miner_details: try: - graphql_version = await self.web.send_command( - {"bos": {"info": {"version": {"full"}}}} - ) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass fw_ver = None - if graphql_version: + if grpc_miner_details: try: - fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"] + fw_ver = grpc_miner_details["bosVersion"]["current"] except (KeyError, TypeError): pass - if not fw_ver: - # try version data file - fw_ver = await self.send_ssh_command("cat /etc/bos_version") - # if we get the version data, parse it if fw_ver is not None: ver = fw_ver.split("-")[5] @@ -980,62 +882,20 @@ class BOSer(BaseMiner): return self.fw_ver - async def get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]: - hostname = None - - if not graphql_hostname: + async def get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]: + if not grpc_miner_details: try: - graphql_hostname = await self.web.send_command({"bos": {"hostname"}}) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass - if graphql_hostname: + if grpc_miner_details: try: - hostname = graphql_hostname["data"]["bos"]["hostname"] - return hostname - except (TypeError, KeyError): + return grpc_miner_details["hostname"] + except LookupError: pass - try: - async with await self._get_ssh_connection() as conn: - if conn is not None: - data = await conn.run("cat /proc/sys/kernel/hostname") - host = data.stdout.strip() - logging.debug(f"Found hostname for {self.ip}: {host}") - hostname = host - else: - logging.warning(f"Failed to get hostname for miner: {self}") - except Exception as e: - logging.warning(f"Failed to get hostname for miner: {self}, {e}") - return hostname - - async def get_hashrate( - self, api_summary: dict = None, graphql_hashrate: dict = None - ) -> Optional[float]: - # get hr from graphql - if not graphql_hashrate: - try: - graphql_hashrate = await self.web.send_command( - {"bosminer": {"info": {"workSolver": {"realHashrate": {"mhs1M"}}}}} - ) - except APIError: - pass - - if graphql_hashrate: - try: - return round( - float( - graphql_hashrate["data"]["bosminer"]["info"]["workSolver"][ - "realHashrate" - ]["mhs1M"] - / 1000000 - ), - 2, - ) - except (LookupError, ValueError, TypeError): - pass - - # get hr from API + async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -1048,243 +908,104 @@ class BOSer(BaseMiner): except (KeyError, IndexError, ValueError, TypeError): pass - async def get_hashboards( - self, - api_temps: dict = None, - api_devdetails: dict = None, - api_devs: dict = None, - graphql_boards: dict = None, - ): + async def get_expected_hashrate( + self, grpc_miner_details: dict = None + ) -> Optional[float]: + if not grpc_miner_details: + try: + grpc_miner_details = await self.web.grpc.get_miner_details() + except APIError: + pass + + if grpc_miner_details: + try: + return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000 + except LookupError: + pass + + 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) ] - if not graphql_boards and not (api_devs or api_temps or api_devdetails): + if grpc_hashboards is None: try: - graphql_boards = await self.web.send_command( - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "realHashrate": {"mhs1M"}, - "hwDetails": {"chips"}, - "temperatures": {"degreesC"}, - } - } - } - } - }, - ) + grpc_hashboards = await self.web.grpc.get_hashboards() except APIError: pass - if graphql_boards: - try: - boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][ - "childSolvers" - ] - except (TypeError, LookupError): - boards = None - - if boards: - b_names = [int(b["name"]) for b in boards] - offset = 0 - if 3 in b_names: - offset = 1 - elif 6 in b_names or 7 in b_names or 8 in b_names: - offset = 6 - for hb in boards: - _id = int(hb["name"]) - offset - board = hashboards[_id] - - board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2) - temps = hb["temperatures"] - try: - if len(temps) > 0: - board.temp = round(hb["temperatures"][0]["degreesC"]) - if len(temps) > 1: - board.chip_temp = round(hb["temperatures"][1]["degreesC"]) - except (TypeError, KeyError, ValueError, IndexError): - pass - details = hb.get("hwDetails") - if details: - if chips := details["chips"]: - board.chips = chips - board.missing = False - - return hashboards - - cmds = [] - if not api_temps: - cmds.append("temps") - if not api_devdetails: - cmds.append("devdetails") - if not api_devs: - cmds.append("devs") - if len(cmds) > 0: - try: - d = await self.api.multicommand(*cmds) - except APIError: - d = {} - try: - api_temps = d["temps"][0] - except (KeyError, IndexError): - api_temps = None - try: - api_devdetails = d["devdetails"][0] - except LookupError: - api_devdetails = None - try: - api_devs = d["devs"][0] - except (KeyError, IndexError): - api_devs = None - if api_temps: - try: - offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_temps["TEMPS"]: - _id = board["ID"] - offset - chip_temp = round(board["Chip"]) - board_temp = round(board["Board"]) - hashboards[_id].chip_temp = chip_temp - hashboards[_id].temp = board_temp - except (IndexError, KeyError, ValueError, TypeError): - pass - - if api_devdetails: - try: - offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_devdetails["DEVDETAILS"]: - _id = board["ID"] - offset - chips = board["Chips"] - hashboards[_id].chips = chips - hashboards[_id].missing = False - except (IndexError, KeyError): - pass - - if api_devs: - try: - offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_devs["DEVS"]: - _id = board["ID"] - offset - hashrate = round(float(board["MHS 1m"] / 1000000), 2) - hashboards[_id].hashrate = hashrate - except (IndexError, KeyError): - pass + if grpc_hashboards is not None: + for board in grpc_hashboards["hashboards"]: + idx = int(board["id"]) - 1 + if board.get("chipsCount") is not None: + hashboards[idx].chips = board["chipsCount"] + if board.get("boardTemp") is not None: + hashboards[idx].temp = board["boardTemp"]["degreeC"] + if board.get("highestChipTemp") is not None: + hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][ + "degreeC" + ] + if board.get("stats") is not None: + if not board["stats"]["realHashrate"]["last5S"] == {}: + hashboards[idx].hashrate = round( + board["stats"]["realHashrate"]["last5S"][ + "gigahashPerSecond" + ] + / 1000, + 2, + ) + hashboards[idx].missing = False return hashboards async def get_env_temp(self) -> Optional[float]: return None - async def get_wattage( - self, api_tunerstatus: dict = None, graphql_wattage: dict = None - ) -> Optional[int]: - if not graphql_wattage and not api_tunerstatus: + async def get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: + if grpc_miner_stats is None: try: - graphql_wattage = await self.web.send_command( - { - "bosminer": { - "info": {"workSolver": {"power": {"approxConsumptionW"}}} - } - } - ) - except APIError: - pass - if graphql_wattage is not None: - try: - return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][ - "power" - ]["approxConsumptionW"] - except (LookupError, TypeError): - pass - - if not api_tunerstatus: - try: - api_tunerstatus = await self.api.tunerstatus() + grpc_miner_stats = self.web.grpc.get_miner_stats() except APIError: pass - if api_tunerstatus: + if grpc_miner_stats: try: - return api_tunerstatus["TUNERSTATUS"][0][ - "ApproximateMinerPowerConsumption" - ] - except LookupError: + return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"] + except KeyError: pass async def get_wattage_limit( - self, api_tunerstatus: dict = None, graphql_wattage_limit: dict = None + self, grpc_active_performance_mode: dict = None ) -> Optional[int]: - if not graphql_wattage_limit and not api_tunerstatus: + if grpc_active_performance_mode is None: try: - graphql_wattage_limit = await self.web.send_command( - {"bosminer": {"info": {"workSolver": {"power": {"limitW"}}}}} + grpc_active_performance_mode = ( + self.web.grpc.get_active_performance_mode() ) except APIError: pass - if graphql_wattage_limit: + if grpc_active_performance_mode: try: - return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][ - "power" - ]["limitW"] - except (LookupError, TypeError): + return grpc_active_performance_mode["tunerMode"]["powerTarget"][ + "powerTarget" + ]["watt"] + except KeyError: pass - if not api_tunerstatus: + async def get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: + if grpc_cooling_state is None: try: - api_tunerstatus = await self.api.tunerstatus() + grpc_cooling_state = self.web.grpc.get_cooling_state() except APIError: pass - if api_tunerstatus: - try: - return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except (KeyError, IndexError): - pass - - async def get_fans( - self, api_fans: dict = None, graphql_fans: dict = None - ) -> List[Fan]: - if not graphql_fans and not api_fans: - try: - graphql_fans = await self.web.send_command( - {"bosminer": {"info": {"fans": {"name", "rpm"}}}} - ) - except APIError: - pass - if graphql_fans.get("data"): + if grpc_cooling_state: fans = [] for n in range(self.fan_count): try: - fans.append( - Fan( - speed=graphql_fans["data"]["bosminer"]["info"]["fans"][n][ - "rpm" - ] - ) - ) - except (LookupError, TypeError): - pass - return fans - - if not api_fans: - try: - api_fans = await self.api.fans() - except APIError: - pass - - if api_fans: - fans = [] - for n in range(self.fan_count): - try: - fans.append(Fan(api_fans["FANS"][n]["RPM"])) + fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) except (IndexError, KeyError): pass return fans @@ -1293,56 +1014,7 @@ class BOSer(BaseMiner): async def get_fan_psu(self) -> Optional[int]: return None - async def get_errors( - self, api_tunerstatus: dict = None, graphql_errors: dict = None - ) -> List[MinerErrorData]: - if not graphql_errors and not api_tunerstatus: - try: - graphql_errors = await self.web.send_command( - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "tuner": {"statusMessages"}, - } - } - } - } - } - ) - except APIError: - pass - - if graphql_errors: - errors = [] - try: - boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][ - "childSolvers" - ] - except (LookupError, TypeError): - boards = None - - if boards: - offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else 0 - for hb in boards: - _id = int(hb["name"]) - offset - tuner = hb["tuner"] - if tuner: - if msg := tuner.get("statusMessages"): - if len(msg) > 0: - if hb["tuner"]["statusMessages"][0] not in [ - "Stable", - "Testing performance profile", - "Tuning individual chips", - ]: - errors.append( - BraiinsOSError( - f"Slot {_id} {hb['tuner']['statusMessages'][0]}" - ) - ) - + async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -1372,87 +1044,24 @@ class BOSer(BaseMiner): except LookupError: pass - async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: - if self.light: + async def get_fault_light(self, grpc_locate_device_status: dict = None) -> bool: + if self.light is not None: return self.light - if not graphql_fault_light: - if self.fw_ver: - # fw version has to be greater than 21.09 and not 21.09 - if ( - int(self.fw_ver.split(".")[0]) == 21 - and int(self.fw_ver.split(".")[1]) > 9 - ) or int(self.fw_ver.split(".")[0]) > 21: - try: - graphql_fault_light = await self.web.send_command( - {"bos": {"faultLight"}} - ) - except APIError: - pass - else: - logging.info( - f"FW version {self.fw_ver} is too low for fault light info in graphql." - ) - else: - # worth trying - try: - graphql_fault_light = await self.web.send_command( - {"bos": {"faultLight"}} - ) - except APIError: - logging.debug( - "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" - ) - if not graphql_fault_light: - # also a failure - logging.debug( - "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" - ) - - # get light through GraphQL - if graphql_fault_light: + if not grpc_locate_device_status: try: - self.light = graphql_fault_light["data"]["bos"]["faultLight"] - return self.light - except (TypeError, ValueError, LookupError): - pass - - # get light via ssh if that fails (10x slower) - try: - data = ( - await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") - ).strip() - self.light = False - if data == "50": - self.light = True - return self.light - except (TypeError, AttributeError): - return self.light - - async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: - if not api_devs: - try: - api_devs = await self.api.devs() + grpc_locate_device_status = ( + await self.web.grpc.get_locate_device_status() + ) except APIError: pass - if api_devs: + if grpc_locate_device_status: + if grpc_locate_device_status == {}: + return False try: - offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0 - hr_list = [] - - for board in api_devs["DEVS"]: - _id = board["ID"] - offset - expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) - if expected_hashrate: - hr_list.append(expected_hashrate) - if len(hr_list) == 0: - return 0 - else: - return round( - (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 - ) - except (IndexError, KeyError): + return grpc_locate_device_status["enabled"] + except LookupError: pass async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index f40666a1..04e0ded4 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -98,17 +98,15 @@ class BOSerWebAPI(BOSMinerWebAPI): def select_command_type(command: Union[str, dict]) -> str: if isinstance(command, dict): return "gql" - elif command.startswith("grpc_"): - return "grpc" else: - return "luci" + return "grpc" async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True ) -> dict: - cmd_types = {"grpc": [], "gql": [], "luci": []} + cmd_types = {"grpc": [], "gql": []} for cmd in commands: - cmd_types[self.select_command_type(cmd)] = cmd + cmd_types[self.select_command_type(cmd)].append(cmd) async def no_op(): return {} @@ -118,21 +116,13 @@ class BOSerWebAPI(BOSMinerWebAPI): self.grpc.multicommand(*cmd_types["grpc"]) ) else: - grpc_data_t = no_op() + grpc_data_t = asyncio.create_task(no_op()) if len(cmd_types["gql"]) > 0: gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) else: - gql_data_t = no_op() - if len(cmd_types["luci"]) > 0: - luci_data_t = asyncio.create_task( - self.luci.multicommand(*cmd_types["luci"]) - ) - else: - luci_data_t = no_op() + gql_data_t = asyncio.create_task(no_op()) - await asyncio.gather(grpc_data_t, gql_data_t, luci_data_t) + await asyncio.gather(grpc_data_t, gql_data_t) - data = dict( - **luci_data_t.result(), **gql_data_t.result(), **luci_data_t.result() - ) + data = dict(**grpc_data_t.result(), **gql_data_t.result()) return data diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 0dcf4a68..0f63a887 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +import asyncio from datetime import timedelta from betterproto import Message @@ -65,7 +66,20 @@ class BOSerGRPCAPI: ] async def multicommand(self, *commands: str) -> dict: - pass + result = {"multicommand": True} + tasks = {} + for command in commands: + try: + tasks[command] = asyncio.create_task(getattr(self, command)()) + except AttributeError: + result["command"] = {} + + await asyncio.gather(*list(tasks.values())) + + for cmd in tasks: + result[cmd] = tasks[cmd].result() + + return result async def send_command( self, @@ -164,10 +178,12 @@ class BOSerGRPCAPI: ) async def get_tuner_state(self): - return await self.send_command("get_tuner_state") + return await self.send_command("get_tuner_state", GetTunerStateRequest()) async def list_target_profiles(self): - return await self.send_command("list_target_profiles") + return await self.send_command( + "list_target_profiles", ListTargetProfilesRequest() + ) async def set_default_power_target( self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY From aa2dc5a53d6c1e9d0ef122492d31048e692304a9 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 15:06:44 -0700 Subject: [PATCH 12/36] feature: update some gRPC functions, and add `as_boser` for some of the `MinerConfig` values. --- pyasic/config/mining.py | 40 ++++++++++++++++ pyasic/config/power_scaling.py | 40 ++++++++-------- pyasic/miners/backends/braiins_os.py | 20 ++++---- pyasic/web/braiins_os/grpc.py | 69 +++++++++++++++++++++++++--- 4 files changed, 133 insertions(+), 36 deletions(-) diff --git a/pyasic/config/mining.py b/pyasic/config/mining.py index f4ae4781..b4101a51 100644 --- a/pyasic/config/mining.py +++ b/pyasic/config/mining.py @@ -17,6 +17,16 @@ from dataclasses import dataclass, field from typing import Dict, Union from pyasic.config.base import MinerConfigOption, MinerConfigValue +from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( + HashrateTargetMode, + PerformanceMode, + Power, + PowerTargetMode, + SaveAction, + SetPerformanceModeRequest, + TeraHashrate, + TunerPerformanceMode, +) @dataclass @@ -99,6 +109,20 @@ class MiningModePowerTune(MinerConfigValue): def as_bosminer(self) -> dict: return {"autotuning": {"enabled": True, "psu_power_limit": self.power}} + def as_boser(self) -> dict: + return { + "set_performance_mode": SetPerformanceModeRequest( + save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY, + mode=PerformanceMode( + tuner_mode=TunerPerformanceMode( + power_target=PowerTargetMode( + power_target=Power(watt=self.power) + ) + ) + ), + ), + } + @dataclass class MiningModeHashrateTune(MinerConfigValue): @@ -112,6 +136,22 @@ class MiningModeHashrateTune(MinerConfigValue): def as_am_modern(self) -> dict: return {"miner-mode": "0"} + def as_boser(self) -> dict: + return { + "set_performance_mode": SetPerformanceModeRequest( + save_action=SaveAction.SAVE_ACTION_SAVE_AND_APPLY, + mode=PerformanceMode( + tuner_mode=TunerPerformanceMode( + hashrate_target=HashrateTargetMode( + hashrate_target=TeraHashrate( + terahash_per_second=self.hashrate + ) + ) + ) + ), + ) + } + @dataclass class ManualBoardSettings(MinerConfigValue): diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index 0e4bf336..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 @@ -38,12 +44,7 @@ class PowerScalingShutdownEnabled(MinerConfigValue): return cfg def as_boser(self) -> dict: - cfg = {"enable_shutdown ": True} - - if self.duration is not None: - cfg["shutdown_duration"] = Hours(self.duration) - - return cfg + return {"enable_shutdown": True, "shutdown_duration": self.duration} @dataclass @@ -147,19 +148,18 @@ class PowerScalingEnabled(MinerConfigValue): return {"power_scaling": cfg} def as_boser(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_boser()} - - return {"dps": cfg} + 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 diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index e8850922..49a9bf98 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -792,22 +792,22 @@ class BOSer(BaseMiner): 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}) ### diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 0f63a887..8fa7e240 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -14,6 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ import asyncio +import logging from datetime import timedelta from betterproto import Message @@ -284,16 +285,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() From cfa51623c47110c1e88a9547198a778fd3be0f18 Mon Sep 17 00:00:00 2001 From: fdeh Date: Sun, 14 Jan 2024 19:53:47 +0300 Subject: [PATCH 13/36] Fix VNish get_hashrate and get_fans errors Update vnish.py. Fix data locations according to the method arguments --- pyasic/miners/backends/vnish.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py index 0df888a6..06ffa300 100644 --- a/pyasic/miners/backends/vnish.py +++ b/pyasic/miners/backends/vnish.py @@ -45,7 +45,7 @@ VNISH_DATA_LOC = DataLocations( "get_hostname", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", [WebAPICommand("web_summary", "summary")] + "get_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] @@ -61,7 +61,7 @@ VNISH_DATA_LOC = DataLocations( "get_wattage_limit", [WebAPICommand("web_settings", "settings")] ), str(DataOptions.FANS): DataFunction( - "get_fans", [WebAPICommand("web_summary", "summary")] + "get_fans", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), str(DataOptions.ERRORS): DataFunction("get_errors"), From d0eb5119aa50e4f4aab9fb943be808258583cca3 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Sun, 14 Jan 2024 09:59:06 -0700 Subject: [PATCH 14/36] version: bump version number. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ea795a2e..b94204ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "pyasic" -version = "0.46.0" +version = "0.46.1" description = "A simplified and standardized interface for Bitcoin ASICs." authors = ["UpstreamData "] repository = "https://github.com/UpstreamData/pyasic" From 71c85e060387dff57c5fb51255932fa6ff24ec6a Mon Sep 17 00:00:00 2001 From: b-rowan Date: Sun, 14 Jan 2024 12:09:29 -0700 Subject: [PATCH 15/36] bug: fix a possible failed authentication when using gRPC. --- pyasic/web/braiins_os/grpc.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 8fa7e240..c5f405e9 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -18,6 +18,7 @@ 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 @@ -92,13 +93,23 @@ class BOSerGRPCAPI: metadata = [] if auth: metadata.append(("authorization", await self.auth())) - 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 {} - 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( From 5a61a8776664eee3bf9e795354025ff725cf0a15 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Sun, 14 Jan 2024 12:58:11 -0700 Subject: [PATCH 16/36] docs: update docs. --- docs/data/miner_data.md | 9 ++++++++- pyasic/data/__init__.py | 9 +++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/docs/data/miner_data.md b/docs/data/miner_data.md index 55d35062..fb8843fa 100644 --- a/docs/data/miner_data.md +++ b/docs/data/miner_data.md @@ -1,6 +1,6 @@ # pyasic -## Miner Data +## Miner Data ::: pyasic.data.MinerData handler: python options: @@ -13,3 +13,10 @@ options: show_root_heading: false heading_level: 4 + +## Fan Data +::: pyasic.data.Fan + handler: python + options: + show_root_heading: false + heading_level: 4 diff --git a/pyasic/data/__init__.py b/pyasic/data/__init__.py index e894d697..3acf2942 100644 --- a/pyasic/data/__init__.py +++ b/pyasic/data/__init__.py @@ -39,6 +39,7 @@ class HashBoard: chip_temp: The temperature of the chips as an int. chips: The chip count of the board as an int. expected_chips: The expected chip count of the board as an int. + serial_number: The serial number of the board. missing: Whether the board is returned from the miners data as a bool. """ @@ -110,7 +111,7 @@ class MinerData: hashrate: The hashrate of the miner in TH/s as a float. Calculated automatically. _hashrate: Backup for hashrate found via API instead of hashboards. expected_hashrate: The factory nominal hashrate of the miner in TH/s as a float. - hashboards: A list of hashboards on the miner with their statistics. + hashboards: A list of [`HashBoard`][pyasic.data.HashBoard]s on the miner with their statistics. 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. @@ -123,11 +124,7 @@ class MinerData: percent_expected_hashrate: The percent of total hashrate out of the expected hashrate. Calculated automatically. percent_expected_wattage: The percent of total wattage out of the expected wattage. Calculated automatically. nominal: Whether the number of chips in the miner is nominal. Calculated automatically. - pool_split: The pool split as a str. - pool_1_url: The first pool url on the miner as a str. - pool_1_user: The first pool user on the miner as a str. - pool_2_url: The second pool url on the miner as a str. - pool_2_user: The second pool user on the miner as a str. + config: The parsed config of the miner, using [`MinerConfig`][pyasic.config.MinerConfig]. errors: A list of errors on the miner. fault_light: Whether the fault light is on as a boolean. efficiency: Efficiency of the miner in J/TH (Watts per TH/s). Calculated automatically. From 6f64cc5e0dedbb90ccec35c148d8f6033f7800b4 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 11:33:44 -0700 Subject: [PATCH 17/36] refactor: remove parameters from `get_{x}` functions and move them to `_get_{x}(**params)`. Add `miner.fw_str`, and `miner.raw_model`. Remove `model` from `get_data` exclude. Swap `fan_count` to `expected_fans`. --- pyasic/load/__init__.py | 8 +- pyasic/miners/backends/antminer.py | 98 +- pyasic/miners/backends/bfgminer.py | 87 +- pyasic/miners/backends/bfgminer_goldshell.py | 43 +- pyasic/miners/backends/bmminer.py | 91 +- pyasic/miners/backends/bosminer_old.py | 155 +++ pyasic/miners/backends/braiins_os.py | 978 ++++++++---------- pyasic/miners/backends/btminer.py | 89 +- pyasic/miners/backends/cgminer.py | 84 +- pyasic/miners/backends/cgminer_avalon.py | 61 +- pyasic/miners/backends/epic.py | 76 +- pyasic/miners/backends/hiveon.py | 6 +- pyasic/miners/backends/luxminer.py | 73 +- pyasic/miners/backends/vnish.py | 55 +- pyasic/miners/base.py | 163 ++- .../miners/innosilicon/cgminer/A10X/A10X.py | 4 +- pyasic/miners/types/antminer/X15/Z15.py | 2 +- pyasic/miners/types/antminer/X17/S17.py | 8 +- pyasic/miners/types/antminer/X17/T17.py | 6 +- pyasic/miners/types/antminer/X19/S19.py | 34 +- pyasic/miners/types/antminer/X19/T19.py | 2 +- pyasic/miners/types/antminer/X3/D3.py | 2 +- pyasic/miners/types/antminer/X3/HS3.py | 2 +- pyasic/miners/types/antminer/X3/L3.py | 2 +- pyasic/miners/types/antminer/X5/DR5.py | 2 +- pyasic/miners/types/antminer/X7/L7.py | 2 +- pyasic/miners/types/antminer/X9/E9.py | 2 +- pyasic/miners/types/antminer/X9/S9.py | 6 +- pyasic/miners/types/antminer/X9/T9.py | 2 +- pyasic/miners/types/avalonminer/A10X/A1026.py | 2 +- pyasic/miners/types/avalonminer/A10X/A1047.py | 2 +- pyasic/miners/types/avalonminer/A10X/A1066.py | 2 +- pyasic/miners/types/avalonminer/A11X/A1166.py | 2 +- pyasic/miners/types/avalonminer/A12X/A1246.py | 2 +- pyasic/miners/types/avalonminer/A7X/A721.py | 2 +- pyasic/miners/types/avalonminer/A7X/A741.py | 2 +- pyasic/miners/types/avalonminer/A7X/A761.py | 2 +- pyasic/miners/types/avalonminer/A8X/A821.py | 2 +- pyasic/miners/types/avalonminer/A8X/A841.py | 2 +- pyasic/miners/types/avalonminer/A8X/A851.py | 2 +- pyasic/miners/types/avalonminer/A9X/A921.py | 2 +- pyasic/miners/types/goldshell/X5/CK5.py | 2 +- pyasic/miners/types/goldshell/X5/HS5.py | 2 +- pyasic/miners/types/goldshell/X5/KD5.py | 2 +- pyasic/miners/types/goldshell/XMax/KDMax.py | 2 +- pyasic/miners/types/innosilicon/A10X/A10X.py | 2 +- pyasic/miners/types/innosilicon/T3X/T3H.py | 2 +- pyasic/miners/types/whatsminer/M2X/M20.py | 2 +- pyasic/miners/types/whatsminer/M2X/M20P.py | 4 +- pyasic/miners/types/whatsminer/M2X/M20S.py | 6 +- .../miners/types/whatsminer/M2X/M20S_Plus.py | 2 +- pyasic/miners/types/whatsminer/M2X/M21.py | 2 +- pyasic/miners/types/whatsminer/M2X/M21S.py | 6 +- .../miners/types/whatsminer/M2X/M21S_Plus.py | 2 +- pyasic/miners/types/whatsminer/M2X/M29.py | 2 +- pyasic/miners/types/whatsminer/M3X/M30.py | 4 +- pyasic/miners/types/whatsminer/M3X/M30K.py | 2 +- pyasic/miners/types/whatsminer/M3X/M30L.py | 2 +- pyasic/miners/types/whatsminer/M3X/M30S.py | 58 +- .../miners/types/whatsminer/M3X/M30S_Plus.py | 62 +- .../types/whatsminer/M3X/M30S_Plus_Plus.py | 42 +- pyasic/miners/types/whatsminer/M3X/M31.py | 4 +- pyasic/miners/types/whatsminer/M3X/M31H.py | 4 +- pyasic/miners/types/whatsminer/M3X/M31L.py | 2 +- pyasic/miners/types/whatsminer/M3X/M31S.py | 24 +- pyasic/miners/types/whatsminer/M3X/M31SE.py | 6 +- .../miners/types/whatsminer/M3X/M31S_Plus.py | 44 +- pyasic/miners/types/whatsminer/M3X/M32.py | 4 +- pyasic/miners/types/whatsminer/M3X/M32S.py | 2 +- pyasic/miners/types/whatsminer/M3X/M33.py | 6 +- pyasic/miners/types/whatsminer/M3X/M33S.py | 2 +- .../miners/types/whatsminer/M3X/M33S_Plus.py | 6 +- .../types/whatsminer/M3X/M33S_Plus_Plus.py | 6 +- .../miners/types/whatsminer/M3X/M34S_Plus.py | 2 +- pyasic/miners/types/whatsminer/M3X/M36S.py | 2 +- .../miners/types/whatsminer/M3X/M36S_Plus.py | 2 +- .../types/whatsminer/M3X/M36S_Plus_Plus.py | 2 +- pyasic/miners/types/whatsminer/M3X/M39.py | 6 +- pyasic/miners/types/whatsminer/M5X/M50.py | 26 +- pyasic/miners/types/whatsminer/M5X/M50S.py | 16 +- .../miners/types/whatsminer/M5X/M50S_Plus.py | 8 +- .../types/whatsminer/M5X/M50S_Plus_Plus.py | 6 +- pyasic/miners/types/whatsminer/M5X/M53.py | 2 +- pyasic/miners/types/whatsminer/M5X/M53S.py | 2 +- .../miners/types/whatsminer/M5X/M53S_Plus.py | 2 +- pyasic/miners/types/whatsminer/M5X/M56.py | 2 +- pyasic/miners/types/whatsminer/M5X/M56S.py | 2 +- .../miners/types/whatsminer/M5X/M56S_Plus.py | 2 +- pyasic/miners/types/whatsminer/M5X/M59.py | 2 +- pyasic/miners/types/whatsminer/M6X/M60.py | 8 +- pyasic/miners/types/whatsminer/M6X/M60S.py | 8 +- pyasic/miners/types/whatsminer/M6X/M63.py | 6 +- pyasic/miners/types/whatsminer/M6X/M63S.py | 6 +- pyasic/miners/types/whatsminer/M6X/M66.py | 4 +- pyasic/miners/types/whatsminer/M6X/M66S.py | 6 +- tests/miners_tests/__init__.py | 1 - 96 files changed, 1323 insertions(+), 1285 deletions(-) create mode 100644 pyasic/miners/backends/bosminer_old.py diff --git a/pyasic/load/__init__.py b/pyasic/load/__init__.py index f706c256..012c15fe 100644 --- a/pyasic/load/__init__.py +++ b/pyasic/load/__init__.py @@ -66,14 +66,14 @@ class _MinerPhaseBalancer: str(miner.ip): { "miner": miner, "set": 0, - "min": miner.fan_count * FAN_USAGE, + "min": miner.expected_fans * FAN_USAGE, } for miner in miners } for miner in miners: if ( isinstance(miner, BTMiner) - and not (miner.model.startswith("M2") if miner.model else True) + and not (miner.raw_model.startswith("M2") if miner.raw_model else True) ) or isinstance(miner, BOSMiner): if isinstance(miner, S9): self.miners[str(miner.ip)]["tune"] = True @@ -98,8 +98,8 @@ class _MinerPhaseBalancer: self.miners[str(miner.ip)]["tune"] = False self.miners[str(miner.ip)]["shutdown"] = True self.miners[str(miner.ip)]["max"] = 3600 - if miner.model: - if miner.model.startswith("M2"): + if miner.raw_model: + if miner.raw_model.startswith("M2"): self.miners[str(miner.ip)]["tune"] = False self.miners[str(miner.ip)]["shutdown"] = True self.miners[str(miner.ip)]["max"] = 2400 diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index c9678f14..5fde75a6 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -34,44 +34,43 @@ from pyasic.web.antminer import AntminerModernWebAPI, AntminerOldWebAPI ANTMINER_MODERN_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [WebAPICommand("web_get_system_info", "get_system_info")] + "_get_mac", [WebAPICommand("web_get_system_info", "get_system_info")] ), - 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", [WebAPICommand("web_get_system_info", "get_system_info")] + "_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] ), 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", []), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.HASHBOARDS): DataFunction("_get_hashboards"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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", [WebAPICommand("web_summary", "summary")] + "_get_errors", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", + "_get_fault_light", [WebAPICommand("web_get_blink_status", "get_blink_status")], ), str(DataOptions.IS_MINING): DataFunction( - "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] + "_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] ), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_stats", "stats")] + "_get_uptime", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -141,7 +140,7 @@ class AntminerModern(BMMiner): await self.send_config(cfg) return True - async def get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]: + async def _get_hostname(self, web_get_system_info: dict = None) -> Union[str, None]: if not web_get_system_info: try: web_get_system_info = await self.web.get_system_info() @@ -154,7 +153,7 @@ class AntminerModern(BMMiner): except KeyError: pass - async def get_mac(self, web_get_system_info: dict = None) -> Union[str, None]: + async def _get_mac(self, web_get_system_info: dict = None) -> Union[str, None]: if not web_get_system_info: try: web_get_system_info = await self.web.get_system_info() @@ -174,7 +173,7 @@ class AntminerModern(BMMiner): except KeyError: pass - async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: + async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: if not web_summary: try: web_summary = await self.web.summary() @@ -194,7 +193,7 @@ class AntminerModern(BMMiner): pass return errors - async def get_hashboards(self) -> List[HashBoard]: + async def _get_hashboards(self) -> List[HashBoard]: hashboards = [ HashBoard(idx, expected_chips=self.expected_chips) for idx in range(self.expected_hashboards) @@ -230,7 +229,7 @@ class AntminerModern(BMMiner): pass return hashboards - async def get_fault_light(self, web_get_blink_status: dict = None) -> bool: + async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool: if self.light: return self.light @@ -247,7 +246,7 @@ class AntminerModern(BMMiner): pass return self.light - 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() @@ -312,7 +311,7 @@ class AntminerModern(BMMiner): protocol=protocol, ) - async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]: + async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]: if not web_get_conf: try: web_get_conf = await self.web.get_miner_conf() @@ -329,7 +328,7 @@ class AntminerModern(BMMiner): except LookupError: pass - async def get_uptime(self, api_stats: dict = None) -> Optional[int]: + async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() @@ -345,43 +344,42 @@ class AntminerModern(BMMiner): ANTMINER_OLD_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("get_mac"), - str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.MAC): DataFunction("_get_mac"), 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", [WebAPICommand("web_get_system_info", "get_system_info")] + "_get_hostname", [WebAPICommand("web_get_system_info", "get_system_info")] ), 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"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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.FAN_PSU): DataFunction("_get_fan_psu"), + str(DataOptions.ERRORS): DataFunction("_get_errors"), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", + "_get_fault_light", [WebAPICommand("web_get_blink_status", "get_blink_status")], ), str(DataOptions.IS_MINING): DataFunction( - "is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] + "_is_mining", [WebAPICommand("web_get_conf", "get_miner_conf")] ), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_stats", "stats")] + "_get_uptime", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -408,7 +406,7 @@ class AntminerOld(CGMiner): self.config = config await self.web.set_miner_conf(config.as_am_old(user_suffix=user_suffix)) - async def get_mac(self) -> Union[str, None]: + async def _get_mac(self) -> Union[str, None]: try: data = await self.web.get_system_info() if data: @@ -445,7 +443,7 @@ class AntminerOld(CGMiner): return True return False - async def get_fault_light(self, web_get_blink_status: dict = None) -> bool: + async def _get_fault_light(self, web_get_blink_status: dict = None) -> bool: if self.light: return self.light @@ -462,7 +460,7 @@ class AntminerOld(CGMiner): pass return self.light - async def get_hostname(self, web_get_system_info: dict = None) -> Optional[str]: + async def _get_hostname(self, web_get_system_info: dict = None) -> Optional[str]: if not web_get_system_info: try: web_get_system_info = await self.web.get_system_info() @@ -475,14 +473,14 @@ class AntminerOld(CGMiner): except KeyError: 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() except APIError: pass - fans_data = [Fan() for _ in range(self.fan_count)] + fans_data = [Fan() for _ in range(self.expected_fans)] if api_stats: try: fan_offset = -1 @@ -495,7 +493,7 @@ class AntminerOld(CGMiner): if fan_offset == -1: fan_offset = 3 - for fan in range(self.fan_count): + for fan in range(self.expected_fans): fans_data[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) @@ -503,7 +501,7 @@ class AntminerOld(CGMiner): pass return fans_data - async def get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: hashboards = [] if not api_stats: @@ -557,7 +555,7 @@ class AntminerOld(CGMiner): return hashboards - async def is_mining(self, web_get_conf: dict = None) -> Optional[bool]: + async def _is_mining(self, web_get_conf: dict = None) -> Optional[bool]: if not web_get_conf: try: web_get_conf = await self.web.get_miner_conf() @@ -582,7 +580,7 @@ class AntminerOld(CGMiner): else: return False - async def get_uptime(self, api_stats: dict = None) -> Optional[int]: + async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py index 047cf2db..03b9b0de 100644 --- a/pyasic/miners/backends/bfgminer.py +++ b/pyasic/miners/backends/bfgminer.py @@ -32,35 +32,34 @@ from pyasic.miners.base import ( BFGMINER_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("get_mac"), - str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.MAC): DataFunction("_get_mac"), 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"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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.UPTIME): DataFunction("get_uptime"), + 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"), str(DataOptions.CONFIG): DataFunction("get_config"), } ) @@ -117,14 +116,10 @@ class BFGMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self) -> str: - return "00:00:00:00:00:00" - - async def get_api_ver(self, api_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.api_ver: - return self.api_ver + async def _get_mac(self) -> Optional[str]: + return None + async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -139,11 +134,7 @@ class BFGMiner(BaseMiner): return self.api_ver - async def get_fw_ver(self, api_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.fw_ver: - return self.fw_ver - + async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -158,26 +149,16 @@ class BFGMiner(BaseMiner): return self.fw_ver - async def get_version( - self, api_version: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - # check if version is cached - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - return miner_version( - api_ver=await self.get_api_ver(api_version), - fw_ver=await self.get_fw_ver(api_version=api_version), - ) - async def reboot(self) -> bool: return False - async def get_fan_psu(self): + async def _get_fan_psu(self): return None - async def get_hostname(self) -> Optional[str]: + async def _get_hostname(self) -> Optional[str]: return None - 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: @@ -191,7 +172,7 @@ class BFGMiner(BaseMiner): except (IndexError, KeyError, 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 = [] if not api_stats: @@ -245,16 +226,16 @@ class BFGMiner(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) -> Optional[int]: + async def _get_wattage(self) -> Optional[int]: return None - async def get_wattage_limit(self) -> Optional[int]: + async def _get_wattage_limit(self) -> Optional[int]: return None - 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() @@ -274,7 +255,7 @@ class BFGMiner(BaseMiner): if fan_offset == -1: fan_offset = 1 - for fan in range(self.fan_count): + for fan in range(self.expected_fans): fans_data[fan] = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) @@ -284,13 +265,13 @@ class BFGMiner(BaseMiner): return fans - async def get_errors(self) -> List[MinerErrorData]: + async def _get_errors(self) -> List[MinerErrorData]: return [] - async def get_fault_light(self) -> bool: + async def _get_fault_light(self) -> bool: return False - async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: # X19 method, not sure compatibility if not api_stats: try: @@ -314,8 +295,8 @@ class BFGMiner(BaseMiner): except LookupError: pass - 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, *args, **kwargs) -> Optional[int]: + async def _get_uptime(self, *args, **kwargs) -> Optional[int]: return None diff --git a/pyasic/miners/backends/bfgminer_goldshell.py b/pyasic/miners/backends/bfgminer_goldshell.py index 07c2d0a9..ee8a3fb5 100644 --- a/pyasic/miners/backends/bfgminer_goldshell.py +++ b/pyasic/miners/backends/bfgminer_goldshell.py @@ -32,40 +32,39 @@ from pyasic.web.goldshell import GoldshellWebAPI GOLDSHELL_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [WebAPICommand("web_setting", "setting")] + "_get_mac", [WebAPICommand("web_setting", "setting")] ), - 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", [WebAPICommand("web_status", "status")] + "_get_fw_ver", [WebAPICommand("web_status", "status")] ), - 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", + "_get_hashboards", [ RPCAPICommand("api_devs", "devs"), RPCAPICommand("api_devdetails", "devdetails"), ], ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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.UPTIME): DataFunction("get_uptime"), + 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"), str(DataOptions.CONFIG): DataFunction("get_config"), } ) @@ -110,7 +109,7 @@ class BFGMinerGoldshell(BFGMiner): url=pool["url"], user=pool["user"], password=pool["pass"] ) - async def get_mac(self, web_setting: dict = None) -> str: + async def _get_mac(self, web_setting: dict = None) -> str: if not web_setting: try: web_setting = await self.web.setting() @@ -123,7 +122,7 @@ class BFGMinerGoldshell(BFGMiner): except KeyError: pass - async def get_fw_ver(self, web_status: dict = None) -> str: + async def _get_fw_ver(self, web_status: dict = None) -> str: if not web_status: try: web_status = await self.web.setting() @@ -136,7 +135,7 @@ class BFGMinerGoldshell(BFGMiner): except KeyError: pass - async def get_hashboards( + async def _get_hashboards( self, api_devs: dict = None, api_devdetails: dict = None ) -> List[HashBoard]: if not api_devs: @@ -186,8 +185,8 @@ class BFGMinerGoldshell(BFGMiner): return hashboards - 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, *args, **kwargs) -> Optional[int]: + async def _get_uptime(self, *args, **kwargs) -> Optional[int]: return None diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py index 828bfd0b..8d49c810 100644 --- a/pyasic/miners/backends/bmminer.py +++ b/pyasic/miners/backends/bmminer.py @@ -33,36 +33,35 @@ from pyasic.miners.base import ( BMMINER_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("get_mac"), - str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.MAC): DataFunction("_get_mac"), 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"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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"), } @@ -156,14 +155,10 @@ class BMMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self) -> str: - return "00:00:00:00:00:00" - - async def get_api_ver(self, api_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.api_ver: - return self.api_ver + async def _get_mac(self) -> Optional[str]: + return None + async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -178,11 +173,7 @@ class BMMiner(BaseMiner): return self.api_ver - async def get_fw_ver(self, api_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.fw_ver: - return self.fw_ver - + async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -197,24 +188,14 @@ class BMMiner(BaseMiner): return self.fw_ver - async def get_version( - self, api_version: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - # check if version is cached - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - return miner_version( - api_ver=await self.get_api_ver(api_version), - fw_ver=await self.get_fw_ver(api_version=api_version), - ) - - async def get_fan_psu(self): + async def _get_fan_psu(self): return None - async def get_hostname(self) -> Optional[str]: + async def _get_hostname(self) -> Optional[str]: hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname") return hn - 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: @@ -228,7 +209,7 @@ class BMMiner(BaseMiner): except (IndexError, KeyError, 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 = [] if not api_stats: @@ -295,23 +276,23 @@ class BMMiner(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) -> Optional[int]: + async def _get_wattage(self) -> Optional[int]: return None - async def get_wattage_limit(self) -> Optional[int]: + async def _get_wattage_limit(self) -> Optional[int]: return None - 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() except APIError: pass - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if api_stats: try: fan_offset = -1 @@ -324,7 +305,7 @@ class BMMiner(BaseMiner): if fan_offset == -1: fan_offset = 1 - for fan in range(self.fan_count): + for fan in range(self.expected_fans): fans[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) @@ -333,13 +314,13 @@ class BMMiner(BaseMiner): return fans - async def get_errors(self) -> List[MinerErrorData]: + async def _get_errors(self) -> List[MinerErrorData]: return [] - async def get_fault_light(self) -> bool: + async def _get_fault_light(self) -> bool: return False - async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: # X19 method, not sure compatibility if not api_stats: try: @@ -363,13 +344,13 @@ class BMMiner(BaseMiner): except LookupError: pass - 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, api_stats: dict = None) -> Optional[int]: + async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: - api_stats = await self.web.get_miner_conf() + api_stats = await self.api.stats() except APIError: pass diff --git a/pyasic/miners/backends/bosminer_old.py b/pyasic/miners/backends/bosminer_old.py new file mode 100644 index 00000000..86b99309 --- /dev/null +++ b/pyasic/miners/backends/bosminer_old.py @@ -0,0 +1,155 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ + +import logging +from typing import List, Optional, Tuple + +from pyasic.config import MinerConfig +from pyasic.data import Fan, HashBoard, MinerData +from pyasic.data.error_codes import MinerErrorData +from pyasic.miners.backends import BOSMiner + + +class BOSMinerOld(BOSMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip, api_ver) + + async def send_ssh_command(self, cmd: str) -> Optional[str]: + result = None + + try: + conn = await self._get_ssh_connection() + except ConnectionError: + return None + + # open an ssh connection + async with conn: + # 3 retries + for i in range(3): + try: + # run the command and get the result + result = await conn.run(cmd) + result = result.stdout + + except Exception as e: + # if the command fails, log it + logging.warning(f"{self} command {cmd} error: {e}") + + # on the 3rd retry, return None + if i == 3: + return + continue + # return the result, either command output or None + return result + + async def update_to_plus(self): + result = await self.send_ssh_command("opkg update && opkg install bos_plus") + return result + + async def check_light(self) -> bool: + return False + + async def fault_light_on(self) -> bool: + return False + + async def fault_light_off(self) -> bool: + return False + + async def get_config(self) -> None: + return None + + async def reboot(self) -> bool: + return False + + async def restart_backend(self) -> bool: + return False + + async def stop_mining(self) -> bool: + return False + + async def resume_mining(self) -> bool: + return False + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + return None + + async def set_power_limit(self, wattage: int) -> bool: + return False + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## + + async def _get_mac(self, *args, **kwargs) -> Optional[str]: + return None + + async def get_model(self, *args, **kwargs) -> str: + return "S9" + + async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: + return None, None + + async def _get_hostname(self, *args, **kwargs) -> Optional[str]: + return None + + async def _get_hashrate(self, *args, **kwargs) -> Optional[float]: + return None + + async def _get_hashboards(self, *args, **kwargs) -> List[HashBoard]: + return [] + + async def _get_env_temp(self, *args, **kwargs) -> Optional[float]: + return None + + async def _get_wattage(self, *args, **kwargs) -> Optional[int]: + return None + + async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]: + return None + + async def _get_fans( + self, + *args, + **kwargs, + ) -> List[Fan]: + return [Fan(), Fan(), Fan(), Fan()] + + async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]: + return None + + async def _get_api_ver(self, *args, **kwargs) -> Optional[str]: + return None + + async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]: + return None + + async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]: + return [] + + async def _get_fault_light(self, *args, **kwargs) -> bool: + return False + + async def _get_expected_hashrate(self, *args, **kwargs) -> Optional[float]: + return None + + async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData: + return MinerData(ip=str(self.ip)) + + async def _is_mining(self, *args, **kwargs) -> Optional[bool]: + return None + + async def _get_uptime(self, *args, **kwargs) -> Optional[int]: + return None diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 49a9bf98..7307233d 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -33,65 +33,146 @@ from pyasic.miners.base import ( DataLocations, DataOptions, GraphQLCommand, - GRPCCommand, RPCAPICommand, WebAPICommand, ) -from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI +from pyasic.web.bosminer import BOSMinerWebAPI BOSMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", - [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], + "_get_mac", + [ + WebAPICommand( + "web_net_conf", "/cgi-bin/luci/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", [WebAPICommand("web_bos_info", "bos/info")] + "_get_fw_ver", + [ + GraphQLCommand( + "graphql_version", {"bos": {"info": {"version": {"full": None}}}} + ) + ], + ), + str(DataOptions.HOSTNAME): DataFunction( + "_get_hostname", + [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})], ), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", - [RPCAPICommand("api_summary", "summary")], + "_get_hashrate", + [ + RPCAPICommand("api_summary", "summary"), + GraphQLCommand( + "graphql_hashrate", + { + "bosminer": { + "info": {"workSolver": {"realHashrate": {"mhs1M": None}}} + } + }, + ), + ], ), 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"), + GraphQLCommand( + "graphql_boards", + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "realHashrate": {"mhs1M": None}, + "hwDetails": {"chips": None}, + "temperatures": {"degreesC": None}, + } + } + } + } + }, + ), ], ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", - [RPCAPICommand("api_tunerstatus", "tunerstatus")], + "_get_wattage", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_wattage", + { + "bosminer": { + "info": { + "workSolver": {"power": {"approxConsumptionW": None}} + } + } + }, + ), + ], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", - [RPCAPICommand("api_tunerstatus", "tunerstatus")], + "_get_wattage_limit", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_wattage_limit", + {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}}, + ), + ], ), str(DataOptions.FANS): DataFunction( - "get_fans", - [RPCAPICommand("api_fans", "fans")], + "_get_fans", + [ + RPCAPICommand("api_fans", "fans"), + GraphQLCommand( + "graphql_fans", + {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}}, + ), + ], ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( - "get_errors", - [RPCAPICommand("api_tunerstatus", "tunerstatus")], + "_get_errors", + [ + RPCAPICommand("api_tunerstatus", "tunerstatus"), + GraphQLCommand( + "graphql_errors", + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "tuner": {"statusMessages": None}, + } + } + } + } + }, + ), + ], + ), + str(DataOptions.FAULT_LIGHT): DataFunction( + "_get_fault_light", + [GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})], ), - 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"), } @@ -99,14 +180,15 @@ BOSMINER_DATA_LOC = DataLocations( class BOSMiner(BaseMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + def __init__(self, ip: str, api_ver: str = "0.0.0", boser: bool = None) -> None: super().__init__(ip) # interfaces self.api = BOSMinerAPI(ip, api_ver) - self.web = BOSMinerWebAPI(ip) + self.web = BOSMinerWebAPI(ip, boser=boser) # static data self.api_type = "BOSMiner" + self.fw_str = "BOS" # data gathering locations self.data_locations = BOSMINER_DATA_LOC # autotuning/shutdown support @@ -229,12 +311,25 @@ class BOSMiner(BaseMiner): logging.debug(f"{self}: Sending config.") self.config = config + if self.web.grpc is not None: + try: + await self._send_config_grpc(config, user_suffix) + return + except: + pass + await self._send_config_bosminer(config, user_suffix) + + async def _send_config_grpc(self, config: MinerConfig, user_suffix: str = None): + raise NotImplementedError + mining_mode = config.mining_mode + + async def _send_config_bosminer(self, config: MinerConfig, user_suffix: str = None): toml_conf = toml.dumps( { "format": { "version": "1.2+", "generator": "pyasic", - "model": f"{self.make.replace('Miner', 'miner')} {self.model.replace(' (BOS)', '').replace('j', 'J')}", + "raw_model": f"{self.make.replace('Miner', 'miner')} {self.raw_model}", "timestamp": int(time.time()), }, **config.as_bosminer(user_suffix=user_suffix), @@ -290,17 +385,16 @@ class BOSMiner(BaseMiner): gateway: str, subnet_mask: str = "255.255.255.0", ): - 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}'", - ] + 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 + + "'" ) data = await self.send_ssh_command("cat /etc/config/network") @@ -316,14 +410,7 @@ class BOSMiner(BaseMiner): await conn.run("echo '" + config + "' > /etc/config/network") async def set_dhcp(self): - cfg_data_lan = "\n\t".join( - [ - "config interface 'lan'", - "option type 'bridge'", - "option ifname 'eth0'", - "option proto 'dhcp'", - ] - ) + cfg_data_lan = "config interface 'lan'\n\toption type 'bridge'\n\toption ifname 'eth0'\n\toption proto 'dhcp'" data = await self.send_ssh_command("cat /etc/config/network") split_data = data.split("\n\n") @@ -341,16 +428,20 @@ 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.get_net_conf() + web_net_conf = await self.web.send_command( + "/cgi-bin/luci/admin/network/iface_status/lan" + ) except APIError: pass if isinstance(web_net_conf, dict): - if "admin/network/iface_status/lan" in web_net_conf.keys(): - web_net_conf = web_net_conf["admin/network/iface_status/lan"] + if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys(): + web_net_conf = web_net_conf[ + "/cgi-bin/luci/admin/network/iface_status/lan" + ] if web_net_conf: try: @@ -363,20 +454,20 @@ class BOSMiner(BaseMiner): # return result.upper().strip() async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (BOS)" + if self.raw_model is not None: + return self.raw_model + " (BOS)" return "? (BOS)" async def get_version( - self, api_version: dict = None, web_bos_info: dict = None + 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_t = asyncio.create_task(self.get_api_ver(api_version)) - fw_ver_t = asyncio.create_task(self.get_fw_ver(web_bos_info)) - await asyncio.gather(api_ver_t, fw_ver_t) - return miner_version(api_ver=api_ver_t.result(), fw_ver=fw_ver_t.result()) + 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]: + async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -387,45 +478,98 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except LookupError: + except (KeyError, IndexError): api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver return self.api_ver - async def get_fw_ver(self, web_bos_info: dict) -> Optional[str]: - if web_bos_info is None: + async def _get_fw_ver(self, graphql_version: dict = None) -> Optional[str]: + if not graphql_version: try: - web_bos_info = await self.web.luci.get_bos_info() + graphql_version = await self.web.send_command( + {"bos": {"info": {"version": {"full"}}}} + ) except APIError: - return None + pass - if isinstance(web_bos_info, dict): - if "bos/info" in web_bos_info.keys(): - web_bos_info = web_bos_info["bos/info"] + fw_ver = None - try: - ver = web_bos_info["version"].split("-")[5] + if graphql_version: + try: + fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"] + except (KeyError, TypeError): + pass + + if not fw_ver: + # try version data file + fw_ver = await self.send_ssh_command("cat /etc/bos_version") + + # if we get the version data, parse it + if fw_ver is not None: + ver = fw_ver.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, graphql_hostname: dict = None) -> Union[str, None]: + hostname = None + + if not graphql_hostname: + try: + graphql_hostname = await self.web.send_command({"bos": {"hostname"}}) + except APIError: + pass + + if graphql_hostname: + try: + hostname = graphql_hostname["data"]["bos"]["hostname"] + return hostname + except (TypeError, KeyError): + pass + try: - hostname = ( - await self.send_ssh_command("cat /proc/sys/kernel/hostname") - ).strip() + async with await self._get_ssh_connection() as conn: + if conn is not None: + data = await conn.run("cat /proc/sys/kernel/hostname") + host = data.stdout.strip() + logging.debug(f"Found hostname for {self.ip}: {host}") + hostname = host + else: + logging.warning(f"Failed to get hostname for miner: {self}") except Exception as e: - logging.error(f"BOSMiner get_hostname failed with error: {e}") - return None + logging.warning(f"Failed to get hostname for miner: {self}, {e}") return hostname - async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + async def _get_hashrate( + self, api_summary: dict = None, graphql_hashrate: dict = None + ) -> Optional[float]: + # get hr from graphql + if not graphql_hashrate: + try: + graphql_hashrate = await self.web.send_command( + {"bosminer": {"info": {"workSolver": {"realHashrate": {"mhs1M"}}}}} + ) + except APIError: + pass + + if graphql_hashrate: + try: + return round( + float( + graphql_hashrate["data"]["bosminer"]["info"]["workSolver"][ + "realHashrate" + ]["mhs1M"] + / 1000000 + ), + 2, + ) + except (LookupError, ValueError, TypeError): + pass + # get hr from API if not api_summary: try: @@ -439,17 +583,75 @@ 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, api_devs: dict = None, + graphql_boards: dict = None, ): hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) ] + if not graphql_boards and not (api_devs or api_temps or api_devdetails): + try: + graphql_boards = await self.web.send_command( + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "realHashrate": {"mhs1M"}, + "hwDetails": {"chips"}, + "temperatures": {"degreesC"}, + } + } + } + } + }, + ) + except APIError: + pass + + if graphql_boards: + try: + boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][ + "childSolvers" + ] + except (TypeError, LookupError): + boards = None + + if boards: + b_names = [int(b["name"]) for b in boards] + offset = 0 + if 3 in b_names: + offset = 1 + elif 6 in b_names or 7 in b_names or 8 in b_names: + offset = 6 + for hb in boards: + _id = int(hb["name"]) - offset + board = hashboards[_id] + + board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2) + temps = hb["temperatures"] + try: + if len(temps) > 0: + board.temp = round(hb["temperatures"][0]["degreesC"]) + if len(temps) > 1: + board.chip_temp = round(hb["temperatures"][1]["degreesC"]) + except (TypeError, KeyError, ValueError, IndexError): + pass + details = hb.get("hwDetails") + if details: + if chips := details["chips"]: + board.chips = chips + board.missing = False + + return hashboards + cmds = [] if not api_temps: cmds.append("temps") @@ -464,7 +666,7 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except LookupError: + except (KeyError, IndexError): api_temps = None try: api_devdetails = d["devdetails"][0] @@ -472,7 +674,7 @@ class BOSMiner(BaseMiner): api_devdetails = None try: api_devs = d["devs"][0] - except LookupError: + except (KeyError, IndexError): api_devs = None if api_temps: try: @@ -512,10 +714,31 @@ 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, graphql_wattage: dict = None + ) -> Optional[int]: + if not graphql_wattage and not api_tunerstatus: + try: + graphql_wattage = await self.web.send_command( + { + "bosminer": { + "info": {"workSolver": {"power": {"approxConsumptionW"}}} + } + } + ) + except APIError: + pass + if graphql_wattage is not None: + try: + return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][ + "power" + ]["approxConsumptionW"] + except (LookupError, TypeError): + pass + if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -527,10 +750,28 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except LookupError: + except (KeyError, IndexError): + pass + + async def _get_wattage_limit( + self, api_tunerstatus: dict = None, graphql_wattage_limit: dict = None + ) -> Optional[int]: + if not graphql_wattage_limit and not api_tunerstatus: + try: + graphql_wattage_limit = await self.web.send_command( + {"bosminer": {"info": {"workSolver": {"power": {"limitW"}}}}} + ) + except APIError: + pass + + if graphql_wattage_limit: + try: + return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][ + "power" + ]["limitW"] + except (LookupError, TypeError): pass - async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -540,10 +781,34 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except LookupError: + except (KeyError, IndexError): pass - async def get_fans(self, api_fans: dict = None) -> List[Fan]: + async def _get_fans( + self, api_fans: dict = None, graphql_fans: dict = None + ) -> List[Fan]: + if not graphql_fans and not api_fans: + try: + graphql_fans = await self.web.send_command( + {"bosminer": {"info": {"fans": {"name", "rpm"}}}} + ) + except APIError: + pass + if graphql_fans.get("data"): + fans = [] + for n in range(self.expected_fans): + try: + fans.append( + Fan( + speed=graphql_fans["data"]["bosminer"]["info"]["fans"][n][ + "rpm" + ] + ) + ) + except (LookupError, TypeError): + pass + return fans + if not api_fans: try: api_fans = await self.api.fans() @@ -552,18 +817,67 @@ class BOSMiner(BaseMiner): if api_fans: fans = [] - for n in range(self.fan_count): + for n in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][n]["RPM"])) except (IndexError, KeyError): pass return fans - return [Fan() for _ in range(self.fan_count)] + return [Fan() for _ in range(self.expected_fans)] - 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, graphql_errors: dict = None + ) -> List[MinerErrorData]: + if not graphql_errors and not api_tunerstatus: + try: + graphql_errors = await self.web.send_command( + { + "bosminer": { + "info": { + "workSolver": { + "childSolvers": { + "name": None, + "tuner": {"statusMessages"}, + } + } + } + } + } + ) + except APIError: + pass + + if graphql_errors: + errors = [] + try: + boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][ + "childSolvers" + ] + except (LookupError, TypeError): + boards = None + + if boards: + offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else 0 + for hb in boards: + _id = int(hb["name"]) - offset + tuner = hb["tuner"] + if tuner: + if msg := tuner.get("statusMessages"): + if len(msg) > 0: + if hb["tuner"]["statusMessages"][0] not in [ + "Stable", + "Testing performance profile", + "Tuning individual chips", + ]: + errors.append( + BraiinsOSError( + f"Slot {_id} {hb['tuner']['statusMessages'][0]}" + ) + ) + if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -593,9 +907,52 @@ 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, graphql_fault_light: dict = None) -> bool: if self.light: return self.light + + if not graphql_fault_light: + if self.fw_ver: + # fw version has to be greater than 21.09 and not 21.09 + if ( + int(self.fw_ver.split(".")[0]) == 21 + and int(self.fw_ver.split(".")[1]) > 9 + ) or int(self.fw_ver.split(".")[0]) > 21: + try: + graphql_fault_light = await self.web.send_command( + {"bos": {"faultLight"}} + ) + except APIError: + pass + else: + logging.info( + f"FW version {self.fw_ver} is too low for fault light info in graphql." + ) + else: + # worth trying + try: + graphql_fault_light = await self.web.send_command( + {"bos": {"faultLight"}} + ) + except APIError: + logging.debug( + "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" + ) + if not graphql_fault_light: + # also a failure + logging.debug( + "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" + ) + + # get light through GraphQL + if graphql_fault_light: + try: + self.light = graphql_fault_light["data"]["bos"]["faultLight"] + return self.light + except (TypeError, ValueError, LookupError): + pass + + # get light via ssh if that fails (10x slower) try: data = ( await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") @@ -607,7 +964,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() @@ -633,7 +990,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( @@ -648,438 +1005,7 @@ class BOSMiner(BaseMiner): except LookupError: pass - async def get_uptime(self, api_summary: dict = None) -> Optional[int]: - if not api_summary: - try: - api_summary = await self.api.summary() - except APIError: - pass - - if api_summary: - try: - return int(api_summary["SUMMARY"][0]["Elapsed"]) - except LookupError: - pass - - -BOSER_DATA_LOC = DataLocations( - **{ - str(DataOptions.MAC): DataFunction( - "get_mac", - [GRPCCommand("grpc_miner_details", "get_miner_details")], - ), - str(DataOptions.MODEL): DataFunction("get_model"), - str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [GRPCCommand("api_version", "get_api_version")] - ), - str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", - [GRPCCommand("grpc_miner_details", "get_miner_details")], - ), - str(DataOptions.HOSTNAME): DataFunction( - "get_hostname", - [GRPCCommand("grpc_miner_details", "get_miner_details")], - ), - str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", - [RPCAPICommand("api_summary", "summary")], - ), - str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", - [GRPCCommand("grpc_miner_details", "get_miner_details")], - ), - str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", - [GRPCCommand("grpc_hashboards", "get_hashboards")], - ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), - str(DataOptions.WATTAGE): DataFunction( - "get_wattage", - [GRPCCommand("grpc_miner_stats", "get_miner_stats")], - ), - str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", - [ - GRPCCommand( - "grpc_active_performance_mode", "get_active_performance_mode" - ) - ], - ), - str(DataOptions.FANS): DataFunction( - "get_fans", - [GRPCCommand("grpc_cooling_state", "get_cooling_state")], - ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), - str(DataOptions.ERRORS): DataFunction( - "get_errors", - [RPCAPICommand("api_tunerstatus", "tunerstatus")], - ), - str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", - [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], - ), - str(DataOptions.IS_MINING): DataFunction( - "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] - ), - str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_summary", "summary")] - ), - str(DataOptions.CONFIG): DataFunction("get_config"), - } -) - - -class BOSer(BaseMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip) - # interfaces - self.api = BOSMinerAPI(ip, api_ver) - self.web = BOSerWebAPI(ip) - - # static data - self.api_type = "BOSMiner" - # data gathering locations - self.data_locations = BOSER_DATA_LOC - # autotuning/shutdown support - self.supports_autotuning = True - self.supports_shutdown = True - - # data storage - self.api_ver = api_ver - - async def fault_light_on(self) -> bool: - resp = await self.web.grpc.set_locate_device_status(True) - if resp.get("enabled", False): - return True - return False - - async def fault_light_off(self) -> bool: - resp = await self.web.grpc.set_locate_device_status(False) - if resp == {}: - return True - return False - - async def restart_backend(self) -> bool: - return await self.restart_boser() - - async def restart_boser(self) -> bool: - ret = await self.web.grpc.restart() - return True - - async def stop_mining(self) -> bool: - try: - await self.web.grpc.pause_mining() - except APIError: - return False - return True - - async def resume_mining(self) -> bool: - try: - await self.web.grpc.resume_mining() - except APIError: - return False - return True - - async def reboot(self) -> bool: - ret = await self.web.grpc.reboot() - if ret == {}: - return True - return False - - async def get_config(self) -> MinerConfig: - grpc_conf = await self.web.grpc.get_miner_configuration() - - 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 - - async def set_power_limit(self, wattage: int) -> bool: - try: - result = await self.web.grpc.set_power_target(wattage) - except APIError: - return False - - try: - if result["powerTarget"]["watt"] == wattage: - return True - except KeyError: - pass - return False - - ################################################## - ### DATA GATHERING FUNCTIONS (get_{some_data}) ### - ################################################## - - async def get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: - if not grpc_miner_details: - try: - grpc_miner_details = await self.web.grpc.get_miner_details() - except APIError: - pass - - if grpc_miner_details: - try: - return grpc_miner_details["macAddress"].upper() - except (LookupError, TypeError): - pass - - 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: - api_version = await self.api.version() - except APIError: - pass - - # Now get the API version - if api_version: - try: - api_ver = api_version["VERSION"][0]["API"] - except LookupError: - api_ver = None - self.api_ver = api_ver - self.api.api_ver = self.api_ver - - return self.api_ver - - async def get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: - if not grpc_miner_details: - try: - grpc_miner_details = await self.web.grpc.get_miner_details() - except APIError: - pass - - fw_ver = None - - if grpc_miner_details: - try: - fw_ver = grpc_miner_details["bosVersion"]["current"] - except (KeyError, TypeError): - pass - - # if we get the version data, parse it - if fw_ver is not None: - ver = fw_ver.split("-")[5] - if "." in ver: - self.fw_ver = ver - logging.debug(f"Found version for {self.ip}: {self.fw_ver}") - - return self.fw_ver - - async def get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]: - if not grpc_miner_details: - try: - grpc_miner_details = await self.web.grpc.get_miner_details() - except APIError: - pass - - if grpc_miner_details: - try: - return grpc_miner_details["hostname"] - except LookupError: - pass - - async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: - if not api_summary: - try: - api_summary = await self.api.summary() - except APIError: - pass - - if api_summary: - try: - return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError, ValueError, TypeError): - pass - - async def get_expected_hashrate( - self, grpc_miner_details: dict = None - ) -> Optional[float]: - if not grpc_miner_details: - try: - grpc_miner_details = await self.web.grpc.get_miner_details() - except APIError: - pass - - if grpc_miner_details: - try: - return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000 - except LookupError: - pass - - 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) - ] - - if grpc_hashboards is None: - try: - grpc_hashboards = await self.web.grpc.get_hashboards() - except APIError: - pass - - if grpc_hashboards is not None: - for board in grpc_hashboards["hashboards"]: - idx = int(board["id"]) - 1 - if board.get("chipsCount") is not None: - hashboards[idx].chips = board["chipsCount"] - if board.get("boardTemp") is not None: - hashboards[idx].temp = board["boardTemp"]["degreeC"] - if board.get("highestChipTemp") is not None: - hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][ - "degreeC" - ] - if board.get("stats") is not None: - if not board["stats"]["realHashrate"]["last5S"] == {}: - hashboards[idx].hashrate = round( - board["stats"]["realHashrate"]["last5S"][ - "gigahashPerSecond" - ] - / 1000, - 2, - ) - hashboards[idx].missing = False - - return hashboards - - async def get_env_temp(self) -> Optional[float]: - return None - - async def get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: - if grpc_miner_stats is None: - try: - grpc_miner_stats = self.web.grpc.get_miner_stats() - except APIError: - pass - - if grpc_miner_stats: - try: - return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"] - except KeyError: - pass - - async def get_wattage_limit( - self, grpc_active_performance_mode: dict = None - ) -> Optional[int]: - if grpc_active_performance_mode is None: - try: - grpc_active_performance_mode = ( - self.web.grpc.get_active_performance_mode() - ) - except APIError: - pass - - if grpc_active_performance_mode: - try: - return grpc_active_performance_mode["tunerMode"]["powerTarget"][ - "powerTarget" - ]["watt"] - except KeyError: - pass - - 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() - except APIError: - pass - - if grpc_cooling_state: - fans = [] - for n in range(self.fan_count): - try: - fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) - except (IndexError, KeyError): - pass - return fans - return [Fan() for _ in range(self.fan_count)] - - async def get_fan_psu(self) -> Optional[int]: - return None - - async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: - if not api_tunerstatus: - try: - api_tunerstatus = await self.api.tunerstatus() - except APIError: - pass - - if api_tunerstatus: - errors = [] - try: - chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] - if chain_status and len(chain_status) > 0: - offset = ( - 6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0 - ) - - for board in chain_status: - _id = board["HashchainIndex"] - offset - if board["Status"] not in [ - "Stable", - "Testing performance profile", - "Tuning individual chips", - ]: - _error = board["Status"].split(" {")[0] - _error = _error[0].lower() + _error[1:] - errors.append(BraiinsOSError(f"Slot {_id} {_error}")) - return errors - except LookupError: - pass - - async def get_fault_light(self, grpc_locate_device_status: dict = None) -> bool: - if self.light is not None: - return self.light - - if not grpc_locate_device_status: - try: - grpc_locate_device_status = ( - await self.web.grpc.get_locate_device_status() - ) - except APIError: - pass - - if grpc_locate_device_status: - if grpc_locate_device_status == {}: - return False - try: - return grpc_locate_device_status["enabled"] - except LookupError: - pass - - async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: - if not api_devdetails: - try: - api_devdetails = await self.api.send_command( - "devdetails", ignore_errors=True, allow_warning=False - ) - except APIError: - pass - - if api_devdetails: - try: - return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" - 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() diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index 476f9cf1..e5c7e991 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -35,70 +35,69 @@ from pyasic.miners.base import ( BTMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", + "_get_mac", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_miner_info", "get_miner_info"), ], ), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_get_version", "get_version")] + "_get_api_ver", [RPCAPICommand("api_get_version", "get_version")] ), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", + "_get_fw_ver", [ RPCAPICommand("api_get_version", "get_version"), RPCAPICommand("api_summary", "summary"), ], ), str(DataOptions.HOSTNAME): DataFunction( - "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] + "_get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] ), 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_summary", "summary")] + "_get_expected_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", [RPCAPICommand("api_devs", "devs")] + "_get_hashboards", [RPCAPICommand("api_devs", "devs")] ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction( - "get_env_temp", [RPCAPICommand("api_summary", "summary")] + "_get_env_temp", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [RPCAPICommand("api_summary", "summary")] + "_get_wattage", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", [RPCAPICommand("api_summary", "summary")] + "_get_wattage_limit", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.FANS): DataFunction( - "get_fans", + "_get_fans", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.FAN_PSU): DataFunction( - "get_fan_psu", + "_get_fan_psu", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.ERRORS): DataFunction( - "get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")] + "_get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")] ), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", + "_get_fault_light", [RPCAPICommand("api_get_miner_info", "get_miner_info")], ), str(DataOptions.IS_MINING): DataFunction( - "is_mining", [RPCAPICommand("api_status", "status")] + "_is_mining", [RPCAPICommand("api_status", "status")] ), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_summary", "summary")] + "_get_uptime", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -239,7 +238,7 @@ class BTMiner(BaseMiner): else: cfg = MinerConfig() - is_mining = await self.is_mining(status) + is_mining = await self._is_mining(status) if not is_mining: cfg.mining_mode = MiningModeConfig.sleep() return cfg @@ -283,7 +282,7 @@ class BTMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac( + async def _get_mac( self, api_summary: dict = None, api_get_miner_info: dict = None ) -> Optional[str]: if not api_get_miner_info: @@ -312,21 +311,7 @@ class BTMiner(BaseMiner): except LookupError: pass - async def get_version( - self, api_get_version: dict = None, api_summary: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - api_ver = await self.get_api_ver(api_get_version=api_get_version) - fw_ver = await self.get_fw_ver( - api_get_version=api_get_version, api_summary=api_summary - ) - return miner_version(api_ver, fw_ver) - - async def get_api_ver(self, api_get_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.api_ver: - return self.api_ver - + async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: if not api_get_version: try: api_get_version = await self.api.get_version() @@ -349,13 +334,9 @@ class BTMiner(BaseMiner): return self.api_ver - async def get_fw_ver( + async def _get_fw_ver( self, api_get_version: dict = None, api_summary: dict = None ) -> Optional[str]: - # Check to see if the version info is already cached - if self.fw_ver: - return self.fw_ver - if not api_get_version: try: api_get_version = await self.api.get_version() @@ -388,7 +369,7 @@ class BTMiner(BaseMiner): return self.fw_ver - async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: + async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: hostname = None if not api_get_miner_info: try: @@ -404,7 +385,7 @@ class BTMiner(BaseMiner): 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: @@ -418,7 +399,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) @@ -453,7 +434,7 @@ class BTMiner(BaseMiner): return hashboards - async def get_env_temp(self, api_summary: dict = None) -> Optional[float]: + async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -466,7 +447,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_wattage(self, api_summary: dict = None) -> Optional[int]: + async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -480,7 +461,7 @@ class BTMiner(BaseMiner): except LookupError: pass - async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -493,7 +474,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_fans( + async def _get_fans( self, api_summary: dict = None, api_get_psu: dict = None ) -> List[Fan]: if not api_summary: @@ -502,10 +483,10 @@ class BTMiner(BaseMiner): except APIError: pass - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if api_summary: try: - if self.fan_count > 0: + if self.expected_fans > 0: fans = [ Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)), @@ -515,7 +496,7 @@ class BTMiner(BaseMiner): return fans - async def get_fan_psu( + async def _get_fan_psu( self, api_summary: dict = None, api_get_psu: dict = None ) -> Optional[int]: if not api_summary: @@ -542,7 +523,7 @@ class BTMiner(BaseMiner): except (KeyError, TypeError): pass - async def get_errors( + async def _get_errors( self, api_summary: dict = None, api_get_error_code: dict = None ) -> List[MinerErrorData]: errors = [] @@ -577,7 +558,7 @@ class BTMiner(BaseMiner): return errors - async def get_expected_hashrate(self, api_summary: dict = None): + async def _get_expected_hashrate(self, api_summary: dict = None): if not api_summary: try: api_summary = await self.api.summary() @@ -592,7 +573,7 @@ class BTMiner(BaseMiner): except LookupError: pass - async def get_fault_light(self, api_get_miner_info: dict = None) -> bool: + async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool: if not api_get_miner_info: try: api_get_miner_info = await self.api.get_miner_info() @@ -630,7 +611,7 @@ class BTMiner(BaseMiner): async def set_hostname(self, hostname: str): await self.api.set_hostname(hostname) - async def is_mining(self, api_status: dict = None) -> Optional[bool]: + async def _is_mining(self, api_status: dict = None) -> Optional[bool]: if not api_status: try: api_status = await self.api.status() @@ -649,7 +630,7 @@ class BTMiner(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() diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py index 6b8a5be8..5d533e4d 100644 --- a/pyasic/miners/backends/cgminer.py +++ b/pyasic/miners/backends/cgminer.py @@ -33,36 +33,35 @@ from pyasic.miners.base import ( CGMINER_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("get_mac"), - str(DataOptions.MODEL): DataFunction("get_model"), + str(DataOptions.MAC): DataFunction("_get_mac"), 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"), - str(DataOptions.WATTAGE): DataFunction("get_wattage"), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), + str(DataOptions.WATTAGE): DataFunction("_get_wattage"), + 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"), } @@ -181,22 +180,10 @@ class CGMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self) -> Optional[str]: + async def _get_mac(self) -> Optional[str]: return None - async def get_version( - self, api_version: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - return miner_version( - api_ver=await self.get_api_ver(api_version=api_version), - fw_ver=await self.get_fw_ver(api_version=api_version), - ) - - async def get_api_ver(self, api_version: dict = None) -> Optional[str]: - if self.api_ver: - return self.api_ver - + async def _get_api_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -211,10 +198,7 @@ class CGMiner(BaseMiner): return self.api_ver - async def get_fw_ver(self, api_version: dict = None) -> Optional[str]: - if self.fw_ver: - return self.fw_ver - + async def _get_fw_ver(self, api_version: dict = None) -> Optional[str]: if not api_version: try: api_version = await self.api.version() @@ -229,11 +213,11 @@ class CGMiner(BaseMiner): return self.fw_ver - async def get_hostname(self) -> Optional[str]: + async def _get_hostname(self) -> Optional[str]: hn = await self.send_ssh_command("cat /proc/sys/kernel/hostname") return hn - 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: @@ -249,7 +233,7 @@ class CGMiner(BaseMiner): except (IndexError, KeyError, 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 = [] if not api_stats: @@ -303,23 +287,23 @@ class CGMiner(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) -> Optional[int]: + async def _get_wattage(self) -> Optional[int]: return None - async def get_wattage_limit(self) -> Optional[int]: + async def _get_wattage_limit(self) -> Optional[int]: return None - 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() except APIError: pass - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if api_stats: try: fan_offset = -1 @@ -332,7 +316,7 @@ class CGMiner(BaseMiner): if fan_offset == -1: fan_offset = 1 - for fan in range(self.fan_count): + for fan in range(self.expected_fans): fans[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) @@ -340,16 +324,16 @@ class CGMiner(BaseMiner): pass return fans - async def get_fan_psu(self) -> Optional[int]: + async def _get_fan_psu(self) -> Optional[int]: return None - async def get_errors(self) -> List[MinerErrorData]: + async def _get_errors(self) -> List[MinerErrorData]: return [] - async def get_fault_light(self) -> bool: + async def _get_fault_light(self) -> bool: return False - async def get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, api_stats: dict = None) -> Optional[float]: # X19 method, not sure compatibility if not api_stats: try: @@ -373,10 +357,10 @@ class CGMiner(BaseMiner): except LookupError: pass - 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, api_stats: dict = None) -> Optional[int]: + async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 1db2dd9e..ebc7b716 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -28,44 +28,43 @@ 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", [RPCAPICommand("api_version", "version")] + "_get_hostname", [RPCAPICommand("api_version", "version")] ), 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"), } ) @@ -176,7 +175,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() @@ -194,7 +193,7 @@ class CGMinerAvalon(CGMiner): except (KeyError, ValueError): pass - async def get_hostname(self, mac: str = None) -> Optional[str]: + async def _get_hostname(self, mac: str = None) -> Optional[str]: return None # if not mac: # mac = await self.get_mac() @@ -202,7 +201,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() @@ -215,7 +214,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) @@ -263,7 +262,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() @@ -278,7 +277,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() @@ -293,10 +292,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() @@ -311,14 +310,14 @@ 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() except APIError: pass - fans_data = [Fan() for _ in range(self.fan_count)] + fans_data = [Fan() for _ in range(self.expected_fans)] if api_stats: try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] @@ -326,17 +325,17 @@ class CGMinerAvalon(CGMiner): except LookupError: return fans_data - for fan in range(self.fan_count): + for fan in range(self.expected_fans): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) except (IndexError, KeyError, ValueError, TypeError): 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: @@ -365,5 +364,5 @@ 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 diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 88358403..0914d0f8 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -17,9 +17,9 @@ from typing import List, Optional, Tuple from pyasic import MinerConfig +from pyasic.config import MinerConfig, MiningModeConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error -from pyasic.config import MinerConfig, MiningModeConfig from pyasic.errors import APIError from pyasic.logger import logger from pyasic.miners.base import ( @@ -34,47 +34,46 @@ from pyasic.web.epic import ePICWebAPI EPIC_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [WebAPICommand("web_network", "network")] + "_get_mac", [WebAPICommand("web_network", "network")] ), - str(DataOptions.MODEL): DataFunction("get_model"), - str(DataOptions.API_VERSION): DataFunction("get_api_ver"), + str(DataOptions.API_VERSION): DataFunction("_get_api_ver"), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", [WebAPICommand("web_summary", "summary")] + "_get_fw_ver", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HOSTNAME): DataFunction( - "get_hostname", [WebAPICommand("web_summary", "summary")] + "_get_hostname", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", [WebAPICommand("web_summary", "summary")] + "_get_hashrate", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "get_expected_hashrate", [WebAPICommand("web_summary", "summary")] + "_get_expected_hashrate", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", + "_get_hashboards", [ WebAPICommand("web_summary", "summary"), WebAPICommand("web_hashrate", "hashrate"), ], ), - str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [WebAPICommand("web_summary", "summary")] + "_get_wattage", [WebAPICommand("web_summary", "summary")] ), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"), str(DataOptions.FANS): DataFunction( - "get_fans", [WebAPICommand("web_summary", "summary")] + "_get_fans", [WebAPICommand("web_summary", "summary")] ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( - "get_errors", [WebAPICommand("web_summary", "summary")] + "_get_errors", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", [WebAPICommand("web_summary", "summary")] + "_get_fault_light", [WebAPICommand("web_summary", "summary")] ), - str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.IS_MINING): DataFunction("_is_mining"), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [WebAPICommand("web_summary", "summary")] + "_get_uptime", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -89,14 +88,10 @@ class ePIC(BaseMiner): # static data self.api_type = "ePIC" + self.fw_str = "ePIC" # data gathering locations self.data_locations = EPIC_DATA_LOC - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (ePIC)" - return "? (ePIC)" - async def get_config(self) -> MinerConfig: summary = None try: @@ -150,7 +145,7 @@ class ePIC(BaseMiner): pass return False - async def get_mac(self, web_network: dict = None) -> str: + async def _get_mac(self, web_network: dict = None) -> str: if not web_network: web_network = await self.web.network() if web_network: @@ -161,7 +156,7 @@ class ePIC(BaseMiner): except KeyError: pass - async def get_hostname(self, web_summary: dict = None) -> str: + async def _get_hostname(self, web_summary: dict = None) -> str: if not web_summary: web_summary = await self.web.summary() if web_summary: @@ -171,7 +166,7 @@ class ePIC(BaseMiner): except KeyError: pass - async def get_wattage(self, web_summary: dict = None) -> Optional[int]: + async def _get_wattage(self, web_summary: dict = None) -> Optional[int]: if not web_summary: web_summary = await self.web.summary() @@ -183,7 +178,7 @@ class ePIC(BaseMiner): 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]: # get hr from API if not web_summary: try: @@ -202,7 +197,7 @@ class ePIC(BaseMiner): logger.error(e) pass - async def get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]: + async def _get_expected_hashrate(self, web_summary: dict = None) -> Optional[float]: # get hr from API if not web_summary: try: @@ -226,7 +221,7 @@ class ePIC(BaseMiner): logger.error(e) pass - async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]: + async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]: if not web_summary: web_summary = await self.web.summary() @@ -238,7 +233,7 @@ class ePIC(BaseMiner): except KeyError: pass - async def get_fans(self, web_summary: dict = None) -> List[Fan]: + async def _get_fans(self, web_summary: dict = None) -> List[Fan]: if not web_summary: try: web_summary = await self.web.summary() @@ -255,7 +250,7 @@ class ePIC(BaseMiner): fans.append(Fan()) return fans - async def get_hashboards( + async def _get_hashboards( self, web_summary: dict = None, web_hashrate: dict = None ) -> List[HashBoard]: if not web_summary: @@ -286,10 +281,10 @@ class ePIC(BaseMiner): hb_list[hr["Index"]].temp = hb["Temperature"] return hb_list - 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, web_summary: dict = None) -> Optional[int]: + async def _get_uptime(self, web_summary: dict = None) -> Optional[int]: if not web_summary: web_summary = await self.web.summary() if web_summary: @@ -300,7 +295,7 @@ class ePIC(BaseMiner): pass return None - async def get_fault_light(self, web_summary: dict = None) -> bool: + async def _get_fault_light(self, web_summary: dict = None) -> bool: if not web_summary: web_summary = await self.web.summary() if web_summary: @@ -311,7 +306,7 @@ class ePIC(BaseMiner): pass return False - async def get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: + async def _get_errors(self, web_summary: dict = None) -> List[MinerErrorData]: if not web_summary: web_summary = await self.web.summary() errors = [] @@ -331,22 +326,19 @@ class ePIC(BaseMiner): def fault_light_on(self) -> bool: return False - def get_api_ver(self, *args, **kwargs) -> Optional[str]: + def _get_api_ver(self, *args, **kwargs) -> Optional[str]: pass def get_config(self) -> MinerConfig: return self.config - def get_env_temp(self, *args, **kwargs) -> Optional[float]: + def _get_env_temp(self, *args, **kwargs) -> Optional[float]: pass - def get_fan_psu(self, *args, **kwargs) -> Optional[int]: + def _get_fan_psu(self, *args, **kwargs) -> Optional[int]: pass - def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: - pass - - def get_wattage_limit(self, *args, **kwargs) -> Optional[int]: + def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]: pass def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 11b458e0..3b99bcf6 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -24,8 +24,4 @@ class Hiveon(BMMiner): super().__init__(ip, api_ver) # static data self.api_type = "Hiveon" - - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (Hiveon)" - return "? (Hiveon)" + self.fw_str = "Hive" diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index dade9121..49f6ccea 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -39,35 +39,34 @@ from pyasic.web.braiins_os import BOSMinerWebAPI LUXMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [RPCAPICommand("api_config", "config")] + "_get_mac", [RPCAPICommand("api_config", "config")] ), - str(DataOptions.MODEL): DataFunction("get_model"), - str(DataOptions.API_VERSION): DataFunction("get_api_ver"), - str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), - str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.API_VERSION): DataFunction("_get_api_ver"), + str(DataOptions.FW_VERSION): DataFunction("_get_fw_ver"), + 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"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [RPCAPICommand("api_power", "power")] + "_get_wattage", [RPCAPICommand("api_power", "power")] ), - str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.WATTAGE_LIMIT): DataFunction("_get_wattage_limit"), str(DataOptions.FANS): DataFunction( - "get_fans", [RPCAPICommand("api_fans", "fans")] + "_get_fans", [RPCAPICommand("api_fans", "fans")] ), - 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"), } @@ -83,6 +82,7 @@ class LUXMiner(BaseMiner): # static data self.api_type = "LUXMiner" + self.fw_str = "LuxOS" # data gathering locations self.data_locations = LUXMINER_DATA_LOC # autotuning/shutdown support @@ -181,7 +181,7 @@ class LUXMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self, api_config: dict = None) -> Optional[str]: + async def _get_mac(self, api_config: dict = None) -> Optional[str]: mac = None if not api_config: try: @@ -197,24 +197,19 @@ class LUXMiner(BaseMiner): return mac - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (LuxOS)" - return "? (LuxOS)" - async def get_version(self) -> Tuple[Optional[str], Optional[str]]: pass - async def get_api_ver(self) -> Optional[str]: + async def _get_api_ver(self) -> Optional[str]: pass - async def get_fw_ver(self) -> Optional[str]: + async def _get_fw_ver(self) -> Optional[str]: pass - async def get_hostname(self) -> Union[str, None]: + async def _get_hostname(self) -> Union[str, None]: pass - async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -227,7 +222,7 @@ class LUXMiner(BaseMiner): except (IndexError, KeyError, 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 = [] if not api_stats: @@ -281,10 +276,10 @@ class LUXMiner(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_power: dict) -> Optional[int]: + async def _get_wattage(self, api_power: dict) -> Optional[int]: if not api_power: try: api_power = await self.api.power() @@ -297,10 +292,10 @@ class LUXMiner(BaseMiner): except (IndexError, KeyError, ValueError, TypeError): pass - async def get_wattage_limit(self) -> Optional[int]: + async def _get_wattage_limit(self) -> Optional[int]: return None - 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() @@ -310,23 +305,23 @@ class LUXMiner(BaseMiner): fans = [] if api_fans: - for fan in range(self.fan_count): + for fan in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][0]["RPM"])) except (IndexError, KeyError, ValueError, TypeError): fans.append(Fan()) return fans - async def get_fan_psu(self) -> Optional[int]: + async def _get_fan_psu(self) -> Optional[int]: return None - async def get_errors(self) -> List[MinerErrorData]: + async def _get_errors(self) -> List[MinerErrorData]: pass - async def get_fault_light(self) -> bool: + async def _get_fault_light(self) -> bool: pass - 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() @@ -349,10 +344,10 @@ class LUXMiner(BaseMiner): except LookupError: pass - async def is_mining(self) -> Optional[bool]: + async def _is_mining(self) -> Optional[bool]: pass - async def get_uptime(self, api_stats: dict = None) -> Optional[int]: + async def _get_uptime(self, api_stats: dict = None) -> Optional[int]: if not api_stats: try: api_stats = await self.api.stats() diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py index 06ffa300..f2af26a2 100644 --- a/pyasic/miners/backends/vnish.py +++ b/pyasic/miners/backends/vnish.py @@ -32,42 +32,41 @@ from pyasic.web.vnish import VNishWebAPI VNISH_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", [WebAPICommand("web_summary", "summary")] + "_get_mac", [WebAPICommand("web_summary", "summary")] ), - 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", [WebAPICommand("web_summary", "summary")] + "_get_fw_ver", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HOSTNAME): DataFunction( - "get_hostname", [WebAPICommand("web_summary", "summary")] + "_get_hostname", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HASHRATE): DataFunction( - "get_hashrate", [RPCAPICommand("api_summary", "summary")] + "_get_hashrate", [WebAPICommand("web_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"), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [WebAPICommand("web_summary", "summary")] + "_get_wattage", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", [WebAPICommand("web_settings", "settings")] + "_get_wattage_limit", [WebAPICommand("web_settings", "settings")] ), str(DataOptions.FANS): DataFunction( - "get_fans", [RPCAPICommand("api_stats", "stats")] + "_get_fans", [WebAPICommand("web_summary", "summary")] ), - 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"), + 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"), str(DataOptions.CONFIG): DataFunction("get_config"), } ) @@ -81,14 +80,10 @@ class VNish(BMMiner): # static data self.api_type = "VNish" + self.fw_str = "VNish" # data gathering locations self.data_locations = VNISH_DATA_LOC - async def get_model(self) -> Optional[str]: - if self.model is not None: - return self.model + " (VNish)" - return "? (VNish)" - async def restart_backend(self) -> bool: data = await self.web.restart_vnish() if data: @@ -125,7 +120,7 @@ class VNish(BMMiner): pass return False - async def get_mac(self, web_summary: dict = None) -> str: + async def _get_mac(self, web_summary: dict = None) -> str: if not web_summary: web_info = await self.web.info() @@ -143,7 +138,7 @@ class VNish(BMMiner): except KeyError: pass - async def get_hostname(self, web_summary: dict = None) -> str: + async def _get_hostname(self, web_summary: dict = None) -> str: if not web_summary: web_info = await self.web.info() @@ -161,7 +156,7 @@ class VNish(BMMiner): except KeyError: pass - async def get_wattage(self, web_summary: dict = None) -> Optional[int]: + async def _get_wattage(self, web_summary: dict = None) -> Optional[int]: if not web_summary: web_summary = await self.web.summary() @@ -173,7 +168,7 @@ class VNish(BMMiner): except KeyError: pass - 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: @@ -190,7 +185,7 @@ class VNish(BMMiner): logger.error(e) pass - async def get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, web_settings: dict = None) -> Optional[int]: if not web_settings: web_settings = await self.web.summary() @@ -203,7 +198,7 @@ class VNish(BMMiner): except (KeyError, TypeError): pass - async def get_fw_ver(self, web_summary: dict = None) -> Optional[str]: + async def _get_fw_ver(self, web_summary: dict = None) -> Optional[str]: if not web_summary: web_summary = await self.web.summary() @@ -215,10 +210,10 @@ class VNish(BMMiner): except KeyError: pass - 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, *args, **kwargs) -> Optional[int]: + async def _get_uptime(self, *args, **kwargs) -> Optional[int]: return None async def get_config(self) -> MinerConfig: diff --git a/pyasic/miners/base.py b/pyasic/miners/base.py index 52aa88d1..aaf86998 100644 --- a/pyasic/miners/base.py +++ b/pyasic/miners/base.py @@ -31,7 +31,6 @@ from pyasic.logger import logger class DataOptions(Enum): MAC = "mac" - MODEL = "model" API_VERSION = "api_ver" FW_VERSION = "fw_ver" HOSTNAME = "hostname" @@ -106,11 +105,12 @@ class BaseMiner(ABC): self.api_type = None # type self.make = None - self.model = None + self.raw_model = None + self.fw_str = None # physical attributes self.expected_hashboards = 3 self.expected_chips = 0 - self.fan_count = 2 + self.expected_fans = 2 # data gathering locations self.data_locations: DataLocations = None # autotuning/shutdown support @@ -140,6 +140,13 @@ class BaseMiner(ABC): def __eq__(self, other): return ipaddress.ip_address(self.ip) == ipaddress.ip_address(other.ip) + @property + def model(self): + model_data = [self.raw_model] + if self.fw_str is not None: + model_data.append(f"({self.fw_str})") + return " ".join(model_data) + @property def pwd(self): # noqa - Skip PyCharm inspection data = [] @@ -309,14 +316,13 @@ class BaseMiner(ABC): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - @abstractmethod - async def get_mac(self, *args, **kwargs) -> Optional[str]: + async def get_mac(self) -> Optional[str]: """Get the MAC address of the miner and return it as a string. Returns: A string representing the MAC address of the miner. """ - pass + return await self._get_mac() async def get_model(self) -> Optional[str]: """Get the model of the miner and return it as a string. @@ -326,148 +332,198 @@ class BaseMiner(ABC): """ return self.model - @abstractmethod - async def get_api_ver(self, *args, **kwargs) -> Optional[str]: + async def get_api_ver(self) -> Optional[str]: """Get the API version of the miner and is as a string. Returns: API version as a string. """ - pass + return await self._get_api_ver() - @abstractmethod - async def get_fw_ver(self, *args, **kwargs) -> Optional[str]: + async def get_fw_ver(self) -> Optional[str]: """Get the firmware version of the miner and is as a string. Returns: Firmware version as a string. """ - pass + return await self._get_fw_ver() - @abstractmethod - async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: + async def get_version(self) -> Tuple[Optional[str], Optional[str]]: """Get the API version and firmware version of the miner and return them as strings. Returns: A tuple of (API version, firmware version) as strings. """ - pass + api_ver = await self.get_api_ver() + fw_ver = await self.get_fw_ver() + return api_ver, fw_ver - @abstractmethod - async def get_hostname(self, *args, **kwargs) -> Optional[str]: + async def get_hostname(self) -> Optional[str]: """Get the hostname of the miner and return it as a string. Returns: A string representing the hostname of the miner. """ - pass + return await self._get_hostname() - @abstractmethod - async def get_hashrate(self, *args, **kwargs) -> Optional[float]: + async def get_hashrate(self) -> Optional[float]: """Get the hashrate of the miner and return it as a float in TH/s. Returns: Hashrate of the miner in TH/s as a float. """ - pass + return await self._get_hashrate() - @abstractmethod - async def get_hashboards(self, *args, **kwargs) -> List[HashBoard]: + async def get_hashboards(self) -> List[HashBoard]: """Get hashboard data from the miner in the form of [`HashBoard`][pyasic.data.HashBoard]. Returns: A [`HashBoard`][pyasic.data.HashBoard] instance containing hashboard data from the miner. """ - pass + return await self._get_hashboards() - @abstractmethod - async def get_env_temp(self, *args, **kwargs) -> Optional[float]: + async def get_env_temp(self) -> Optional[float]: """Get environment temp from the miner as a float. Returns: Environment temp of the miner as a float. """ - pass + return await self._get_env_temp() - @abstractmethod - async def get_wattage(self, *args, **kwargs) -> Optional[int]: + async def get_wattage(self) -> Optional[int]: """Get wattage from the miner as an int. Returns: Wattage of the miner as an int. """ - pass + return await self._get_wattage() - @abstractmethod - async def get_wattage_limit(self, *args, **kwargs) -> Optional[int]: + async def get_wattage_limit(self) -> Optional[int]: """Get wattage limit from the miner as an int. Returns: Wattage limit of the miner as an int. """ - pass + return await self._get_wattage_limit() - @abstractmethod - async def get_fans(self, *args, **kwargs) -> List[Fan]: + async def get_fans(self) -> List[Fan]: """Get fan data from the miner in the form [fan_1, fan_2, fan_3, fan_4]. Returns: A list of fan data. """ - pass + return await self._get_fans() - @abstractmethod - async def get_fan_psu(self, *args, **kwargs) -> Optional[int]: + async def get_fan_psu(self) -> Optional[int]: """Get PSU fan speed from the miner. Returns: PSU fan speed. """ - pass + return await self._get_fan_psu() - @abstractmethod - async def get_errors(self, *args, **kwargs) -> List[MinerErrorData]: + async def get_errors(self) -> List[MinerErrorData]: """Get a list of the errors the miner is experiencing. Returns: A list of error classes representing different errors. """ - pass + return await self._get_errors() - @abstractmethod - async def get_fault_light(self, *args, **kwargs) -> bool: + async def get_fault_light(self) -> bool: """Check the status of the fault light and return on or off as a boolean. Returns: A boolean value where `True` represents on and `False` represents off. """ - pass + return await self._get_fault_light() - @abstractmethod - async def get_expected_hashrate(self, *args, **kwargs) -> Optional[float]: + async def get_expected_hashrate(self) -> Optional[float]: """Get the nominal hashrate from factory if available. Returns: A float value of nominal hashrate in TH/s. """ - pass + return await self._get_expected_hashrate() - @abstractmethod - async def is_mining(self, *args, **kwargs) -> Optional[bool]: + async def is_mining(self) -> Optional[bool]: """Check whether the miner is mining. Returns: A boolean value representing if the miner is mining. """ - pass + return await self._is_mining() - @abstractmethod - async def get_uptime(self, *args, **kwargs) -> Optional[int]: + async def get_uptime(self) -> Optional[int]: """Get the uptime of the miner in seconds. Returns: The uptime of the miner in seconds. """ + return await self._get_uptime() + + @abstractmethod + async def _get_mac(self, *args, **kwargs) -> Optional[str]: + pass + + @abstractmethod + async def _get_api_ver(self, *args, **kwargs) -> Optional[str]: + pass + + @abstractmethod + async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]: + pass + + @abstractmethod + async def _get_hostname(self, *args, **kwargs) -> Optional[str]: + pass + + @abstractmethod + async def _get_hashrate(self, *args, **kwargs) -> Optional[float]: + pass + + @abstractmethod + async def _get_hashboards(self, *args, **kwargs) -> List[HashBoard]: + pass + + @abstractmethod + async def _get_env_temp(self, *args, **kwargs) -> Optional[float]: + pass + + @abstractmethod + async def _get_wattage(self, *args, **kwargs) -> Optional[int]: + pass + + @abstractmethod + async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]: + pass + + @abstractmethod + async def _get_fans(self, *args, **kwargs) -> List[Fan]: + pass + + @abstractmethod + async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]: + pass + + @abstractmethod + async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]: + pass + + @abstractmethod + async def _get_fault_light(self, *args, **kwargs) -> bool: + pass + + @abstractmethod + async def _get_expected_hashrate(self, *args, **kwargs) -> Optional[float]: + pass + + @abstractmethod + async def _is_mining(self, *args, **kwargs) -> Optional[bool]: + pass + + @abstractmethod + async def _get_uptime(self, *args, **kwargs) -> Optional[int]: pass async def _get_data( @@ -571,6 +627,7 @@ class BaseMiner(ABC): data = MinerData( ip=str(self.ip), make=self.make, + model=self.model, expected_chips=self.expected_chips * self.expected_hashboards, expected_hashboards=self.expected_hashboards, hashboards=[ diff --git a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py index b208c913..7f626bf0 100644 --- a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py +++ b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py @@ -254,7 +254,7 @@ class CGMinerA10X(CGMiner, A10X): else: web_get_all = web_get_all["all"] - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if web_get_all: try: spd = web_get_all["fansSpeed"] @@ -262,7 +262,7 @@ class CGMinerA10X(CGMiner, A10X): pass else: round((int(spd) * 6000) / 100) - for i in range(self.fan_count): + for i in range(self.expected_fans): fans[i].speed = spd return fans diff --git a/pyasic/miners/types/antminer/X15/Z15.py b/pyasic/miners/types/antminer/X15/Z15.py index 7a0e5ac7..f91b25b6 100644 --- a/pyasic/miners/types/antminer/X15/Z15.py +++ b/pyasic/miners/types/antminer/X15/Z15.py @@ -21,6 +21,6 @@ class Z15(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Z15" + self.raw_model = "Z15" self.expected_chips = 3 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X17/S17.py b/pyasic/miners/types/antminer/X17/S17.py index 277e3c92..8d4ec087 100644 --- a/pyasic/miners/types/antminer/X17/S17.py +++ b/pyasic/miners/types/antminer/X17/S17.py @@ -21,7 +21,7 @@ class S17(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S17" + self.raw_model = "S17" self.expected_chips = 48 self.fan_count = 4 @@ -29,7 +29,7 @@ class S17(AntMiner): # noqa - ignore ABC method implementation class S17Plus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) - self.model = "S17+" + self.raw_model = "S17+" self.expected_chips = 65 self.fan_count = 4 @@ -38,7 +38,7 @@ class S17Pro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S17 Pro" + self.raw_model = "S17 Pro" self.expected_chips = 48 self.fan_count = 4 @@ -47,6 +47,6 @@ class S17e(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S17e" + self.raw_model = "S17e" self.expected_chips = 135 self.fan_count = 4 diff --git a/pyasic/miners/types/antminer/X17/T17.py b/pyasic/miners/types/antminer/X17/T17.py index d41a27d9..6d4b71fc 100644 --- a/pyasic/miners/types/antminer/X17/T17.py +++ b/pyasic/miners/types/antminer/X17/T17.py @@ -21,7 +21,7 @@ class T17(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "T17" + self.raw_model = "T17" self.expected_chips = 30 self.fan_count = 4 @@ -30,7 +30,7 @@ class T17Plus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "T17+" + self.raw_model = "T17+" self.expected_chips = 44 self.fan_count = 4 @@ -39,6 +39,6 @@ class T17e(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "T17e" + self.raw_model = "T17e" self.expected_chips = 78 self.fan_count = 4 diff --git a/pyasic/miners/types/antminer/X19/S19.py b/pyasic/miners/types/antminer/X19/S19.py index 42bfd891..86e6d57c 100644 --- a/pyasic/miners/types/antminer/X19/S19.py +++ b/pyasic/miners/types/antminer/X19/S19.py @@ -21,7 +21,7 @@ class S19(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19" + self.raw_model = "S19" self.expected_chips = 76 self.fan_count = 4 @@ -30,7 +30,7 @@ class S19NoPIC(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19 No PIC" + self.raw_model = "S19 No PIC" self.expected_chips = 88 self.fan_count = 4 @@ -39,7 +39,7 @@ class S19Pro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19 Pro" + self.raw_model = "S19 Pro" self.expected_chips = 114 self.fan_count = 4 @@ -48,7 +48,7 @@ class S19i(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19i" + self.raw_model = "S19i" self.expected_chips = 80 self.fan_count = 4 @@ -57,7 +57,7 @@ class S19Plus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19+" + self.raw_model = "S19+" self.expected_chips = 80 self.fan_count = 4 @@ -66,7 +66,7 @@ class S19ProPlus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19 Pro+" + self.raw_model = "S19 Pro+" self.expected_chips = 120 self.fan_count = 4 @@ -75,7 +75,7 @@ class S19XP(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19 XP" + self.raw_model = "S19 XP" self.expected_chips = 110 self.fan_count = 4 @@ -84,7 +84,7 @@ class S19a(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19a" + self.raw_model = "S19a" self.expected_chips = 72 self.fan_count = 4 @@ -93,7 +93,7 @@ class S19aPro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19a Pro" + self.raw_model = "S19a Pro" self.expected_chips = 100 self.fan_count = 4 @@ -102,7 +102,7 @@ class S19j(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19j" + self.raw_model = "S19j" self.expected_chips = 114 self.fan_count = 4 @@ -111,7 +111,7 @@ class S19jNoPIC(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19j No PIC" + self.raw_model = "S19j No PIC" self.expected_chips = 88 self.fan_count = 4 @@ -120,7 +120,7 @@ class S19jPro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19j Pro" + self.raw_model = "S19j Pro" self.expected_chips = 126 self.fan_count = 4 @@ -129,7 +129,7 @@ class S19jProPlus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19j Pro+" + self.raw_model = "S19j Pro+" self.expected_chips = 120 self.fan_count = 4 @@ -138,7 +138,7 @@ class S19kPro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19k Pro" + self.raw_model = "S19k Pro" self.expected_chips = 77 self.fan_count = 4 @@ -147,7 +147,7 @@ class S19L(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19L" + self.raw_model = "S19L" self.expected_chips = 76 self.fan_count = 4 @@ -156,7 +156,7 @@ class S19kProNoPIC(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19k Pro No PIC" + self.raw_model = "S19k Pro No PIC" self.expected_chips = 77 self.fan_count = 4 @@ -165,7 +165,7 @@ class S19ProHydro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S19 Pro Hydro" + self.raw_model = "S19 Pro Hydro" self.expected_chips = 180 self.expected_hashboards = 4 self.fan_count = 0 diff --git a/pyasic/miners/types/antminer/X19/T19.py b/pyasic/miners/types/antminer/X19/T19.py index 249d1e06..df575cf8 100644 --- a/pyasic/miners/types/antminer/X19/T19.py +++ b/pyasic/miners/types/antminer/X19/T19.py @@ -21,6 +21,6 @@ class T19(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "T19" + self.raw_model = "T19" self.expected_chips = 76 self.fan_count = 4 diff --git a/pyasic/miners/types/antminer/X3/D3.py b/pyasic/miners/types/antminer/X3/D3.py index 1d24676f..6471db46 100644 --- a/pyasic/miners/types/antminer/X3/D3.py +++ b/pyasic/miners/types/antminer/X3/D3.py @@ -21,7 +21,7 @@ class D3(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "D3" + self.raw_model = "D3" self.expected_chips = 60 self.expected_hashboards = 3 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X3/HS3.py b/pyasic/miners/types/antminer/X3/HS3.py index 540f4852..3b90d21b 100644 --- a/pyasic/miners/types/antminer/X3/HS3.py +++ b/pyasic/miners/types/antminer/X3/HS3.py @@ -21,7 +21,7 @@ class HS3(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "HS3" + self.raw_model = "HS3" self.expected_chips = 92 self.expected_hashboards = 3 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X3/L3.py b/pyasic/miners/types/antminer/X3/L3.py index 95335304..0567d38a 100644 --- a/pyasic/miners/types/antminer/X3/L3.py +++ b/pyasic/miners/types/antminer/X3/L3.py @@ -20,6 +20,6 @@ class L3Plus(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "L3+" + self.raw_model = "L3+" self.expected_chips = 72 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X5/DR5.py b/pyasic/miners/types/antminer/X5/DR5.py index c688c0b3..f6bf4c10 100644 --- a/pyasic/miners/types/antminer/X5/DR5.py +++ b/pyasic/miners/types/antminer/X5/DR5.py @@ -21,7 +21,7 @@ class DR5(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "DR5" + self.raw_model = "DR5" self.expected_chips = 72 self.expected_hashboards = 3 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X7/L7.py b/pyasic/miners/types/antminer/X7/L7.py index abcfd920..1401aa27 100644 --- a/pyasic/miners/types/antminer/X7/L7.py +++ b/pyasic/miners/types/antminer/X7/L7.py @@ -20,6 +20,6 @@ class L7(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "L7" + self.raw_model = "L7" self.expected_chips = 120 self.fan_count = 4 diff --git a/pyasic/miners/types/antminer/X9/E9.py b/pyasic/miners/types/antminer/X9/E9.py index 315dc0f5..771ad5cd 100644 --- a/pyasic/miners/types/antminer/X9/E9.py +++ b/pyasic/miners/types/antminer/X9/E9.py @@ -21,7 +21,7 @@ class E9Pro(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "E9Pro" + self.raw_model = "E9Pro" self.expected_chips = 8 self.expected_hashboards = 2 self.fan_count = 4 diff --git a/pyasic/miners/types/antminer/X9/S9.py b/pyasic/miners/types/antminer/X9/S9.py index 48f8b009..32940bf9 100644 --- a/pyasic/miners/types/antminer/X9/S9.py +++ b/pyasic/miners/types/antminer/X9/S9.py @@ -21,7 +21,7 @@ class S9(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S9" + self.raw_model = "S9" self.expected_chips = 63 self.fan_count = 2 @@ -30,7 +30,7 @@ class S9i(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S9i" + self.raw_model = "S9i" self.expected_chips = 63 self.fan_count = 2 @@ -39,6 +39,6 @@ class S9j(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "S9j" + self.raw_model = "S9j" self.expected_chips = 63 self.fan_count = 2 diff --git a/pyasic/miners/types/antminer/X9/T9.py b/pyasic/miners/types/antminer/X9/T9.py index 44273c24..83a27de4 100644 --- a/pyasic/miners/types/antminer/X9/T9.py +++ b/pyasic/miners/types/antminer/X9/T9.py @@ -21,6 +21,6 @@ class T9(AntMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "T9" + self.raw_model = "T9" self.expected_chips = 54 self.fan_count = 2 diff --git a/pyasic/miners/types/avalonminer/A10X/A1026.py b/pyasic/miners/types/avalonminer/A10X/A1026.py index b7655e14..909f32ac 100644 --- a/pyasic/miners/types/avalonminer/A10X/A1026.py +++ b/pyasic/miners/types/avalonminer/A10X/A1026.py @@ -21,6 +21,6 @@ class Avalon1026(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 1026" + self.raw_model = "Avalon 1026" self.expected_chips = 80 self.fan_count = 2 diff --git a/pyasic/miners/types/avalonminer/A10X/A1047.py b/pyasic/miners/types/avalonminer/A10X/A1047.py index 6bf344ef..9880dadf 100644 --- a/pyasic/miners/types/avalonminer/A10X/A1047.py +++ b/pyasic/miners/types/avalonminer/A10X/A1047.py @@ -21,6 +21,6 @@ class Avalon1047(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 1047" + self.raw_model = "Avalon 1047" self.expected_chips = 80 self.fan_count = 2 diff --git a/pyasic/miners/types/avalonminer/A10X/A1066.py b/pyasic/miners/types/avalonminer/A10X/A1066.py index eb6dcd67..c9c7ad69 100644 --- a/pyasic/miners/types/avalonminer/A10X/A1066.py +++ b/pyasic/miners/types/avalonminer/A10X/A1066.py @@ -21,6 +21,6 @@ class Avalon1066(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 1066" + self.raw_model = "Avalon 1066" self.expected_chips = 114 self.fan_count = 4 diff --git a/pyasic/miners/types/avalonminer/A11X/A1166.py b/pyasic/miners/types/avalonminer/A11X/A1166.py index 0a809c1e..e26136f7 100644 --- a/pyasic/miners/types/avalonminer/A11X/A1166.py +++ b/pyasic/miners/types/avalonminer/A11X/A1166.py @@ -22,6 +22,6 @@ class Avalon1166Pro(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 1166 Pro" + self.raw_model = "Avalon 1166 Pro" self.expected_chips = 120 self.fan_count = 4 diff --git a/pyasic/miners/types/avalonminer/A12X/A1246.py b/pyasic/miners/types/avalonminer/A12X/A1246.py index 7443ed0f..98b3dd1a 100644 --- a/pyasic/miners/types/avalonminer/A12X/A1246.py +++ b/pyasic/miners/types/avalonminer/A12X/A1246.py @@ -22,6 +22,6 @@ class Avalon1246(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 1246" + self.raw_model = "Avalon 1246" self.expected_chips = 120 self.fan_count = 4 diff --git a/pyasic/miners/types/avalonminer/A7X/A721.py b/pyasic/miners/types/avalonminer/A7X/A721.py index 9c4de083..4074ddf2 100644 --- a/pyasic/miners/types/avalonminer/A7X/A721.py +++ b/pyasic/miners/types/avalonminer/A7X/A721.py @@ -21,7 +21,7 @@ class Avalon721(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 721" + self.raw_model = "Avalon 721" self.expected_hashboards = 4 self.expected_chips = 18 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A7X/A741.py b/pyasic/miners/types/avalonminer/A7X/A741.py index 6ee44d13..4c2c704c 100644 --- a/pyasic/miners/types/avalonminer/A7X/A741.py +++ b/pyasic/miners/types/avalonminer/A7X/A741.py @@ -21,7 +21,7 @@ class Avalon741(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 741" + self.raw_model = "Avalon 741" self.expected_hashboards = 4 self.expected_chips = 22 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A7X/A761.py b/pyasic/miners/types/avalonminer/A7X/A761.py index 118d8c58..a4ee460c 100644 --- a/pyasic/miners/types/avalonminer/A7X/A761.py +++ b/pyasic/miners/types/avalonminer/A7X/A761.py @@ -21,7 +21,7 @@ class Avalon761(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 761" + self.raw_model = "Avalon 761" self.expected_hashboards = 4 self.expected_chips = 18 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A8X/A821.py b/pyasic/miners/types/avalonminer/A8X/A821.py index 129e4abc..137a9feb 100644 --- a/pyasic/miners/types/avalonminer/A8X/A821.py +++ b/pyasic/miners/types/avalonminer/A8X/A821.py @@ -21,7 +21,7 @@ class Avalon821(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 821" + self.raw_model = "Avalon 821" self.expected_hashboards = 4 self.expected_chips = 26 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A8X/A841.py b/pyasic/miners/types/avalonminer/A8X/A841.py index aee85c2c..eaa2ceef 100644 --- a/pyasic/miners/types/avalonminer/A8X/A841.py +++ b/pyasic/miners/types/avalonminer/A8X/A841.py @@ -21,7 +21,7 @@ class Avalon841(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 841" + self.raw_model = "Avalon 841" self.expected_hashboards = 4 self.expected_chips = 26 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A8X/A851.py b/pyasic/miners/types/avalonminer/A8X/A851.py index 97691680..f6451d5f 100644 --- a/pyasic/miners/types/avalonminer/A8X/A851.py +++ b/pyasic/miners/types/avalonminer/A8X/A851.py @@ -21,7 +21,7 @@ class Avalon851(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 851" + self.raw_model = "Avalon 851" self.expected_hashboards = 4 self.expected_chips = 26 self.fan_count = 1 diff --git a/pyasic/miners/types/avalonminer/A9X/A921.py b/pyasic/miners/types/avalonminer/A9X/A921.py index 8873ee93..3882bba3 100644 --- a/pyasic/miners/types/avalonminer/A9X/A921.py +++ b/pyasic/miners/types/avalonminer/A9X/A921.py @@ -21,7 +21,7 @@ class Avalon921(AvalonMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "Avalon 921" + self.raw_model = "Avalon 921" self.expected_hashboards = 4 self.expected_chips = 26 self.fan_count = 1 diff --git a/pyasic/miners/types/goldshell/X5/CK5.py b/pyasic/miners/types/goldshell/X5/CK5.py index 51771600..3f7fd4eb 100644 --- a/pyasic/miners/types/goldshell/X5/CK5.py +++ b/pyasic/miners/types/goldshell/X5/CK5.py @@ -20,7 +20,7 @@ class CK5(GoldshellMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "CK5" + self.raw_model = "CK5" self.expected_hashboards = 4 self.expected_chips = 46 self.fan_count = 4 diff --git a/pyasic/miners/types/goldshell/X5/HS5.py b/pyasic/miners/types/goldshell/X5/HS5.py index b789af55..2ee19a79 100644 --- a/pyasic/miners/types/goldshell/X5/HS5.py +++ b/pyasic/miners/types/goldshell/X5/HS5.py @@ -20,7 +20,7 @@ class HS5(GoldshellMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "HS5" + self.raw_model = "HS5" self.expected_hashboards = 4 self.expected_chips = 46 self.fan_count = 4 diff --git a/pyasic/miners/types/goldshell/X5/KD5.py b/pyasic/miners/types/goldshell/X5/KD5.py index 21f137d4..9ac33ebc 100644 --- a/pyasic/miners/types/goldshell/X5/KD5.py +++ b/pyasic/miners/types/goldshell/X5/KD5.py @@ -20,7 +20,7 @@ class KD5(GoldshellMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "KD5" + self.raw_model = "KD5" self.expected_hashboards = 4 self.expected_chips = 46 self.fan_count = 4 diff --git a/pyasic/miners/types/goldshell/XMax/KDMax.py b/pyasic/miners/types/goldshell/XMax/KDMax.py index 8c42dd58..6924a01c 100644 --- a/pyasic/miners/types/goldshell/XMax/KDMax.py +++ b/pyasic/miners/types/goldshell/XMax/KDMax.py @@ -20,7 +20,7 @@ class KDMax(GoldshellMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "KD Max" + self.raw_model = "KD Max" self.expected_hashboards = 3 self.expected_chips = 84 self.fan_count = 4 diff --git a/pyasic/miners/types/innosilicon/A10X/A10X.py b/pyasic/miners/types/innosilicon/A10X/A10X.py index aeeb0dca..b4d489f3 100644 --- a/pyasic/miners/types/innosilicon/A10X/A10X.py +++ b/pyasic/miners/types/innosilicon/A10X/A10X.py @@ -20,4 +20,4 @@ class A10X(InnosiliconMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip, api_ver) self.ip = ip - self.model = "A10X" + self.raw_model = "A10X" diff --git a/pyasic/miners/types/innosilicon/T3X/T3H.py b/pyasic/miners/types/innosilicon/T3X/T3H.py index cc56274d..bea0a4aa 100644 --- a/pyasic/miners/types/innosilicon/T3X/T3H.py +++ b/pyasic/miners/types/innosilicon/T3X/T3H.py @@ -21,6 +21,6 @@ class T3HPlus(InnosiliconMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip, api_ver) self.ip = ip - self.model = "T3H+" + self.raw_model = "T3H+" self.expected_chips = 114 self.fan_count = 4 diff --git a/pyasic/miners/types/whatsminer/M2X/M20.py b/pyasic/miners/types/whatsminer/M2X/M20.py index cb805605..01075d86 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20.py +++ b/pyasic/miners/types/whatsminer/M2X/M20.py @@ -21,6 +21,6 @@ class M20V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20 V10" + self.raw_model = "M20 V10" self.expected_chips = 70 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M20P.py b/pyasic/miners/types/whatsminer/M2X/M20P.py index 60c9024b..91c648c1 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20P.py +++ b/pyasic/miners/types/whatsminer/M2X/M20P.py @@ -21,7 +21,7 @@ class M20PV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20P V10" + self.raw_model = "M20P V10" self.expected_chips = 156 self.fan_count = 2 @@ -30,6 +30,6 @@ class M20PV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20P V30" + self.raw_model = "M20P V30" self.expected_chips = 148 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M20S.py b/pyasic/miners/types/whatsminer/M2X/M20S.py index 72caa9d3..ccfa4d80 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20S.py +++ b/pyasic/miners/types/whatsminer/M2X/M20S.py @@ -23,7 +23,7 @@ class M20SV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20S V10" + self.raw_model = "M20S V10" self.expected_chips = 105 self.fan_count = 2 @@ -32,7 +32,7 @@ class M20SV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20S V20" + self.raw_model = "M20S V20" self.expected_chips = 111 self.fan_count = 2 @@ -41,6 +41,6 @@ class M20SV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20S V30" + self.raw_model = "M20S V30" self.expected_chips = 140 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M20S_Plus.py b/pyasic/miners/types/whatsminer/M2X/M20S_Plus.py index 40d3cfe5..036fffb0 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20S_Plus.py +++ b/pyasic/miners/types/whatsminer/M2X/M20S_Plus.py @@ -23,7 +23,7 @@ class M20SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M20S+ V30" + self.raw_model = "M20S+ V30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M20S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M2X/M21.py b/pyasic/miners/types/whatsminer/M2X/M21.py index b523d291..1a2f811c 100644 --- a/pyasic/miners/types/whatsminer/M2X/M21.py +++ b/pyasic/miners/types/whatsminer/M2X/M21.py @@ -23,6 +23,6 @@ class M21V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M21 V10" + self.raw_model = "M21 V10" self.expected_chips = 33 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M21S.py b/pyasic/miners/types/whatsminer/M2X/M21S.py index 3fd378f2..dd224341 100644 --- a/pyasic/miners/types/whatsminer/M2X/M21S.py +++ b/pyasic/miners/types/whatsminer/M2X/M21S.py @@ -23,7 +23,7 @@ class M21SV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M21S V20" + self.raw_model = "M21S V20" self.expected_chips = 66 self.fan_count = 2 @@ -32,7 +32,7 @@ class M21SV60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M21S V60" + self.raw_model = "M21S V60" self.expected_chips = 105 self.fan_count = 2 @@ -41,6 +41,6 @@ class M21SV70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M21S V70" + self.raw_model = "M21S V70" self.expected_chips = 111 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M2X/M21S_Plus.py b/pyasic/miners/types/whatsminer/M2X/M21S_Plus.py index 6c231025..178e7a3c 100644 --- a/pyasic/miners/types/whatsminer/M2X/M21S_Plus.py +++ b/pyasic/miners/types/whatsminer/M2X/M21S_Plus.py @@ -23,7 +23,7 @@ class M21SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M21S+ V20" + self.raw_model = "M21S+ V20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M21S+ V20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M2X/M29.py b/pyasic/miners/types/whatsminer/M2X/M29.py index 0c2eeab4..e3cd9d34 100644 --- a/pyasic/miners/types/whatsminer/M2X/M29.py +++ b/pyasic/miners/types/whatsminer/M2X/M29.py @@ -23,6 +23,6 @@ class M29V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M29 V10" + self.raw_model = "M29 V10" self.expected_chips = 50 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30.py b/pyasic/miners/types/whatsminer/M3X/M30.py index 3d9d5560..4e1aaf0d 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30.py +++ b/pyasic/miners/types/whatsminer/M3X/M30.py @@ -23,7 +23,7 @@ class M30V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30 V10" + self.raw_model = "M30 V10" self.expected_chips = 105 self.fan_count = 2 @@ -32,6 +32,6 @@ class M30V20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30 V20" + self.raw_model = "M30 V20" self.expected_chips = 111 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30K.py b/pyasic/miners/types/whatsminer/M3X/M30K.py index e8fafa51..b951c10b 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30K.py +++ b/pyasic/miners/types/whatsminer/M3X/M30K.py @@ -23,7 +23,7 @@ class M30KV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30K V10" + self.raw_model = "M30K V10" self.expected_hashboards = 4 self.expected_chips = 240 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30L.py b/pyasic/miners/types/whatsminer/M3X/M30L.py index 07ea7870..191b5e60 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30L.py +++ b/pyasic/miners/types/whatsminer/M3X/M30L.py @@ -23,7 +23,7 @@ class M30LV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30L V10" + self.raw_model = "M30L V10" self.board_num = 4 self.expected_chips = 144 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30S.py b/pyasic/miners/types/whatsminer/M3X/M30S.py index 0d225a8e..c9f4f12d 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30S.py +++ b/pyasic/miners/types/whatsminer/M3X/M30S.py @@ -23,7 +23,7 @@ class M30SV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V10" + self.raw_model = "M30S V10" self.expected_chips = 148 self.fan_count = 2 @@ -32,7 +32,7 @@ class M30SV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V20" + self.raw_model = "M30S V20" self.expected_chips = 156 self.fan_count = 2 @@ -41,7 +41,7 @@ class M30SV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V30" + self.raw_model = "M30S V30" self.expected_chips = 164 self.fan_count = 2 @@ -50,7 +50,7 @@ class M30SV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V40" + self.raw_model = "M30S V40" self.expected_chips = 172 self.fan_count = 2 @@ -59,7 +59,7 @@ class M30SV50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V50" + self.raw_model = "M30S V50" self.expected_chips = 156 self.fan_count = 2 @@ -68,7 +68,7 @@ class M30SV60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V60" + self.raw_model = "M30S V60" self.expected_chips = 164 self.fan_count = 2 @@ -77,7 +77,7 @@ class M30SV70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V70" + self.raw_model = "M30S V70" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30SV70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -89,7 +89,7 @@ class M30SV80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S V80" + self.raw_model = "M30S V80" self.expected_chips = 129 self.fan_count = 2 @@ -98,7 +98,7 @@ class M30SVE10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE10" + self.raw_model = "M30S VE10" self.expected_chips = 105 self.fan_count = 2 @@ -107,7 +107,7 @@ class M30SVE20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE20" + self.raw_model = "M30S VE20" self.expected_chips = 111 self.fan_count = 2 @@ -116,7 +116,7 @@ class M30SVE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE30" + self.raw_model = "M30S VE30" self.expected_chips = 117 self.fan_count = 2 @@ -125,7 +125,7 @@ class M30SVE40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE40" + self.raw_model = "M30S VE40" self.expected_chips = 123 self.fan_count = 2 @@ -134,7 +134,7 @@ class M30SVE50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE50" + self.raw_model = "M30S VE50" self.expected_chips = 129 self.fan_count = 2 @@ -143,7 +143,7 @@ class M30SVE60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE60" + self.raw_model = "M30S VE60" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30SVE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -155,7 +155,7 @@ class M30SVE70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VE70" + self.raw_model = "M30S VE70" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30SVE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -167,7 +167,7 @@ class M30SVF10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VF10" + self.raw_model = "M30S VF10" self.expected_chips = 70 self.fan_count = 2 @@ -176,7 +176,7 @@ class M30SVF20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VF20" + self.raw_model = "M30S VF20" self.expected_chips = 74 self.fan_count = 2 @@ -185,7 +185,7 @@ class M30SVF30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VF30" + self.raw_model = "M30S VF30" self.expected_chips = 78 self.fan_count = 2 @@ -194,7 +194,7 @@ class M30SVG10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VG10" + self.raw_model = "M30S VG10" self.expected_chips = 66 self.fan_count = 2 @@ -203,7 +203,7 @@ class M30SVG20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VG20" + self.raw_model = "M30S VG20" self.expected_chips = 70 self.fan_count = 2 @@ -212,7 +212,7 @@ class M30SVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VG30" + self.raw_model = "M30S VG30" self.expected_chips = 74 self.fan_count = 2 @@ -221,7 +221,7 @@ class M30SVG40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VG40" + self.raw_model = "M30S VG40" self.expected_chips = 78 self.fan_count = 2 @@ -230,7 +230,7 @@ class M30SVH10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH10" + self.raw_model = "M30S VH10" self.expected_chips = 64 self.fan_count = 2 @@ -239,7 +239,7 @@ class M30SVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH20" + self.raw_model = "M30S VH20" self.expected_chips = 66 self.fan_count = 2 @@ -248,7 +248,7 @@ class M30SVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH30" + self.raw_model = "M30S VH30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30SVH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -260,7 +260,7 @@ class M30SVH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH40" + self.raw_model = "M30S VH40" self.expected_chips = 64 self.fan_count = 2 @@ -269,7 +269,7 @@ class M30SVH50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH50" + self.raw_model = "M30S VH50" self.expected_chips = 66 self.fan_count = 2 @@ -278,7 +278,7 @@ class M30SVH60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VH60" + self.raw_model = "M30S VH60" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30SVH60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -290,6 +290,6 @@ class M30SVI20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S VI20" + self.raw_model = "M30S VI20" self.expected_chips = 70 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M30S_Plus.py index 16df8c9b..2f0d2f8e 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M30S_Plus.py @@ -23,7 +23,7 @@ class M30SPlusV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V10" + self.raw_model = "M30S+ V10" self.expected_chips = 215 self.fan_count = 2 @@ -32,7 +32,7 @@ class M30SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V20" + self.raw_model = "M30S+ V20" self.expected_chips = 255 self.fan_count = 2 @@ -41,7 +41,7 @@ class M30SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V30" + self.raw_model = "M30S+ V30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ V30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -53,7 +53,7 @@ class M30SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V40" + self.raw_model = "M30S+ V40" self.expected_chips = 235 self.fan_count = 2 @@ -62,7 +62,7 @@ class M30SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V50" + self.raw_model = "M30S+ V50" self.expected_chips = 225 self.fan_count = 2 @@ -71,7 +71,7 @@ class M30SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V60" + self.raw_model = "M30S+ V60" self.expected_chips = 245 self.fan_count = 2 @@ -80,7 +80,7 @@ class M30SPlusV70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V70" + self.raw_model = "M30S+ V70" self.expected_chips = 235 self.fan_count = 2 @@ -89,7 +89,7 @@ class M30SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V80" + self.raw_model = "M30S+ V80" self.expected_chips = 245 self.fan_count = 2 @@ -98,7 +98,7 @@ class M30SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V90" + self.raw_model = "M30S+ V90" self.expected_chips = 225 self.fan_count = 2 @@ -107,7 +107,7 @@ class M30SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ V100" + self.raw_model = "M30S+ V100" self.expected_chips = 215 self.fan_count = 2 @@ -116,7 +116,7 @@ class M30SPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE30" + self.raw_model = "M30S+ VE30" self.expected_chips = 148 self.fan_count = 2 @@ -125,7 +125,7 @@ class M30SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE40" + self.raw_model = "M30S+ VE40" self.expected_chips = 156 self.fan_count = 2 @@ -134,7 +134,7 @@ class M30SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE50" + self.raw_model = "M30S+ VE50" self.expected_chips = 164 self.fan_count = 2 @@ -143,7 +143,7 @@ class M30SPlusVE60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE60" + self.raw_model = "M30S+ VE60" self.expected_chips = 172 self.fan_count = 2 @@ -152,7 +152,7 @@ class M30SPlusVE70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE70" + self.raw_model = "M30S+ VE70" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -164,7 +164,7 @@ class M30SPlusVE80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE80" + self.raw_model = "M30S+ VE80" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -176,7 +176,7 @@ class M30SPlusVE90(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE90" + self.raw_model = "M30S+ VE90" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE90, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -188,7 +188,7 @@ class M30SPlusVE100(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VE100" + self.raw_model = "M30S+ VE100" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE100, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -200,7 +200,7 @@ class M30SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VF20" + self.raw_model = "M30S+ VF20" self.expected_chips = 111 self.fan_count = 2 @@ -209,7 +209,7 @@ class M30SPlusVF30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VF30" + self.raw_model = "M30S+ VF30" self.expected_chips = 117 self.fan_count = 2 @@ -218,7 +218,7 @@ class M30SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VG20" + self.raw_model = "M30S+ VG20" self.expected_chips = 82 self.fan_count = 2 @@ -227,7 +227,7 @@ class M30SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VG30" + self.raw_model = "M30S+ VG30" self.expected_chips = 78 self.fan_count = 2 @@ -236,7 +236,7 @@ class M30SPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VG40" + self.raw_model = "M30S+ VG40" self.expected_chips = 105 self.fan_count = 2 @@ -245,7 +245,7 @@ class M30SPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VG50" + self.raw_model = "M30S+ VG50" self.expected_chips = 111 self.fan_count = 2 @@ -254,7 +254,7 @@ class M30SPlusVG60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VG60" + self.raw_model = "M30S+ VG60" self.expected_chips = 86 self.fan_count = 2 @@ -263,7 +263,7 @@ class M30SPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH10" + self.raw_model = "M30S+ VH10" self.expected_chips = 64 self.fan_count = 2 @@ -272,7 +272,7 @@ class M30SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH20" + self.raw_model = "M30S+ VH20" self.expected_chips = 66 self.fan_count = 2 @@ -281,7 +281,7 @@ class M30SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH30" + self.raw_model = "M30S+ VH30" self.expected_chips = 70 self.fan_count = 2 @@ -290,7 +290,7 @@ class M30SPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH40" + self.raw_model = "M30S+ VH40" self.expected_chips = 74 self.fan_count = 2 @@ -299,7 +299,7 @@ class M30SPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH50" + self.raw_model = "M30S+ VH50" self.expected_chips = 64 self.fan_count = 2 @@ -308,6 +308,6 @@ class M30SPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S+ VH60" + self.raw_model = "M30S+ VH60" self.expected_chips = 66 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M30S_Plus_Plus.py b/pyasic/miners/types/whatsminer/M3X/M30S_Plus_Plus.py index 4ccac9e6..9a257031 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30S_Plus_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M30S_Plus_Plus.py @@ -23,7 +23,7 @@ class M30SPlusPlusV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ V10" + self.raw_model = "M30S++ V10" self.expected_hashboards = 4 self.expected_chips = 255 self.fan_count = 2 @@ -33,7 +33,7 @@ class M30SPlusPlusV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ V20" + self.raw_model = "M30S++ V20" self.expected_hashboards = 4 self.expected_chips = 255 self.fan_count = 2 @@ -43,7 +43,7 @@ class M30SPlusPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VE30" + self.raw_model = "M30S++ VE30" self.expected_chips = 215 self.fan_count = 2 @@ -52,7 +52,7 @@ class M30SPlusPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VE40" + self.raw_model = "M30S++ VE40" self.expected_chips = 225 self.fan_count = 2 @@ -61,7 +61,7 @@ class M30SPlusPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VE50" + self.raw_model = "M30S++ VE50" self.expected_chips = 235 self.fan_count = 2 @@ -70,7 +70,7 @@ class M30SPlusPlusVF40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VF40" + self.raw_model = "M30S++ VF40" self.expected_chips = 156 self.fan_count = 2 @@ -79,7 +79,7 @@ class M30SPlusPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VG30" + self.raw_model = "M30S++ VG30" self.expected_chips = 111 self.fan_count = 2 @@ -88,7 +88,7 @@ class M30SPlusPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VG40" + self.raw_model = "M30S++ VG40" self.expected_chips = 117 self.fan_count = 2 @@ -97,7 +97,7 @@ class M30SPlusPlusVG50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VG50" + self.raw_model = "M30S++ VG50" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S++ VG50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -109,7 +109,7 @@ class M30SPlusPlusVH10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH10" + self.raw_model = "M30S++ VH10" self.expected_chips = 82 self.fan_count = 2 @@ -118,7 +118,7 @@ class M30SPlusPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH20" + self.raw_model = "M30S++ VH20" self.expected_chips = 86 self.fan_count = 2 @@ -127,7 +127,7 @@ class M30SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH30" + self.raw_model = "M30S++ VH30" self.expected_chips = 111 self.fan_count = 2 @@ -136,7 +136,7 @@ class M30SPlusPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH40" + self.raw_model = "M30S++ VH40" self.expected_chips = 70 self.fan_count = 2 @@ -145,7 +145,7 @@ class M30SPlusPlusVH50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH50" + self.raw_model = "M30S++ VH50" self.expected_chips = 74 self.fan_count = 2 @@ -154,7 +154,7 @@ class M30SPlusPlusVH60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH60" + self.raw_model = "M30S++ VH60" self.expected_chips = 78 self.fan_count = 2 @@ -163,7 +163,7 @@ class M30SPlusPlusVH70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH70" + self.raw_model = "M30S++ VH70" self.expected_chips = 70 self.fan_count = 2 @@ -172,7 +172,7 @@ class M30SPlusPlusVH80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH80" + self.raw_model = "M30S++ VH80" self.expected_chips = 74 self.fan_count = 2 @@ -181,7 +181,7 @@ class M30SPlusPlusVH90(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH90" + self.raw_model = "M30S++ VH90" self.expected_chips = 78 self.fan_count = 2 @@ -190,7 +190,7 @@ class M30SPlusPlusVH100(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VH100" + self.raw_model = "M30S++ VH100" self.expected_chips = 82 self.fan_count = 2 @@ -199,7 +199,7 @@ class M30SPlusPlusVJ20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VJ20" + self.raw_model = "M30S++ VJ20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S++ VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -211,7 +211,7 @@ class M30SPlusPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M30S++ VJ30" + self.raw_model = "M30S++ VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S++ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M3X/M31.py b/pyasic/miners/types/whatsminer/M3X/M31.py index 07785517..0e5b7003 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31.py +++ b/pyasic/miners/types/whatsminer/M3X/M31.py @@ -23,7 +23,7 @@ class M31V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31 V10" + self.raw_model = "M31 V10" self.expected_chips = 70 self.fan_count = 2 @@ -32,6 +32,6 @@ class M31V20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31 V20" + self.raw_model = "M31 V20" self.expected_chips = 74 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M31H.py b/pyasic/miners/types/whatsminer/M3X/M31H.py index 8a1a9540..64612a1b 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31H.py +++ b/pyasic/miners/types/whatsminer/M3X/M31H.py @@ -23,7 +23,7 @@ class M31HV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31H V10" + self.raw_model = "M31H V10" self.expected_chips = 114 self.fan_count = 0 @@ -32,7 +32,7 @@ class M31HV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31H V40" + self.raw_model = "M31H V40" self.expected_hashboards = 4 self.expected_chips = 136 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M31L.py b/pyasic/miners/types/whatsminer/M3X/M31L.py index d5eca1b5..7b30e980 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31L.py +++ b/pyasic/miners/types/whatsminer/M3X/M31L.py @@ -23,6 +23,6 @@ class M31LV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31L V10" + self.raw_model = "M31L V10" self.expected_chips = 114 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M31S.py b/pyasic/miners/types/whatsminer/M3X/M31S.py index 70e4a9d1..cbe6df82 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31S.py +++ b/pyasic/miners/types/whatsminer/M3X/M31S.py @@ -23,7 +23,7 @@ class M31SV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V10" + self.raw_model = "M31S V10" self.expected_chips = 105 self.fan_count = 2 @@ -32,7 +32,7 @@ class M31SV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V20" + self.raw_model = "M31S V20" self.expected_chips = 111 self.fan_count = 2 @@ -41,7 +41,7 @@ class M31SV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V30" + self.raw_model = "M31S V30" self.expected_chips = 117 self.fan_count = 2 @@ -50,7 +50,7 @@ class M31SV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V40" + self.raw_model = "M31S V40" self.expected_chips = 123 self.fan_count = 2 @@ -59,7 +59,7 @@ class M31SV50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V50" + self.raw_model = "M31S V50" self.expected_chips = 78 self.fan_count = 2 @@ -68,7 +68,7 @@ class M31SV60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V60" + self.raw_model = "M31S V60" self.expected_chips = 105 self.fan_count = 2 @@ -77,7 +77,7 @@ class M31SV70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V70" + self.raw_model = "M31S V70" self.expected_chips = 111 self.fan_count = 2 @@ -86,7 +86,7 @@ class M31SV80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V80" + self.raw_model = "M31S V80" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M31SV80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -98,7 +98,7 @@ class M31SV90(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S V90" + self.raw_model = "M31S V90" self.expected_chips = 117 self.fan_count = 2 @@ -107,7 +107,7 @@ class M31SVE10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S VE10" + self.raw_model = "M31S VE10" self.expected_chips = 70 self.fan_count = 2 @@ -116,7 +116,7 @@ class M31SVE20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S VE20" + self.raw_model = "M31S VE20" self.expected_chips = 74 self.fan_count = 2 @@ -125,7 +125,7 @@ class M31SVE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S VE30" + self.raw_model = "M31S VE30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M31SVE30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M3X/M31SE.py b/pyasic/miners/types/whatsminer/M3X/M31SE.py index c16ead2c..5f6aff90 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31SE.py +++ b/pyasic/miners/types/whatsminer/M3X/M31SE.py @@ -23,7 +23,7 @@ class M31SEV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31SE V10" + self.raw_model = "M31SE V10" self.expected_chips = 82 self.fan_count = 2 @@ -32,7 +32,7 @@ class M31SEV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31SE V20" + self.raw_model = "M31SE V20" self.expected_chips = 78 self.fan_count = 2 @@ -41,6 +41,6 @@ class M31SEV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31SE V30" + self.raw_model = "M31SE V30" self.expected_chips = 78 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M31S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M31S_Plus.py index 3a609ff8..e4eccfee 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M31S_Plus.py @@ -23,7 +23,7 @@ class M31SPlusV10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V10" + self.raw_model = "M31S+ V10" self.expected_chips = 105 self.fan_count = 2 @@ -32,7 +32,7 @@ class M31SPlusV20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V20" + self.raw_model = "M31S+ V20" self.expected_chips = 111 self.fan_count = 2 @@ -41,7 +41,7 @@ class M31SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V30" + self.raw_model = "M31S+ V30" self.expected_chips = 117 self.fan_count = 2 @@ -50,7 +50,7 @@ class M31SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V40" + self.raw_model = "M31S+ V40" self.expected_chips = 123 self.fan_count = 2 @@ -59,7 +59,7 @@ class M31SPlusV50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V50" + self.raw_model = "M31S+ V50" self.expected_chips = 148 self.fan_count = 2 @@ -68,7 +68,7 @@ class M31SPlusV60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V60" + self.raw_model = "M31S+ V60" self.expected_chips = 156 self.fan_count = 2 @@ -77,7 +77,7 @@ class M31SPlusV80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V80" + self.raw_model = "M31S+ V80" self.expected_chips = 129 self.fan_count = 2 @@ -86,7 +86,7 @@ class M31SPlusV90(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V90" + self.raw_model = "M31S+ V90" self.expected_chips = 117 self.fan_count = 2 @@ -95,7 +95,7 @@ class M31SPlusV100(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V100" + self.raw_model = "M31S+ V100" self.expected_chips = 111 self.fan_count = 2 @@ -104,7 +104,7 @@ class M31SPlusVE10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE10" + self.raw_model = "M31S+ VE10" self.expected_chips = 82 self.fan_count = 2 @@ -113,7 +113,7 @@ class M31SPlusVE20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE20" + self.raw_model = "M31S+ VE20" self.expected_chips = 78 self.fan_count = 2 @@ -122,7 +122,7 @@ class M31SPlusVE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE30" + self.raw_model = "M31S+ VE30" self.expected_chips = 105 self.fan_count = 2 @@ -131,7 +131,7 @@ class M31SPlusVE40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE40" + self.raw_model = "M31S+ VE40" self.expected_chips = 111 self.fan_count = 2 @@ -140,7 +140,7 @@ class M31SPlusVE50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE50" + self.raw_model = "M31S+ VE50" self.expected_chips = 117 self.fan_count = 2 @@ -149,7 +149,7 @@ class M31SPlusVE60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE60" + self.raw_model = "M31S+ VE60" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE60, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -161,7 +161,7 @@ class M31SPlusVE80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VE80" + self.raw_model = "M31S+ VE80" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VE80, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -173,7 +173,7 @@ class M31SPlusVF20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VF20" + self.raw_model = "M31S+ VF20" self.expected_chips = 66 self.fan_count = 2 @@ -182,7 +182,7 @@ class M31SPlusVF30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VF30" + self.raw_model = "M31S+ VF30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M30S+ VF30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -194,7 +194,7 @@ class M31SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VG20" + self.raw_model = "M31S+ VG20" self.expected_chips = 66 self.fan_count = 2 @@ -203,7 +203,7 @@ class M31SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ VG30" + self.raw_model = "M31S+ VG30" self.expected_chips = 70 self.fan_count = 2 @@ -212,7 +212,7 @@ class M31SPlusV30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V30" + self.raw_model = "M31S+ V30" self.expected_chips = 117 self.fan_count = 2 @@ -221,6 +221,6 @@ class M31SPlusV40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M31S+ V40" + self.raw_model = "M31S+ V40" self.expected_chips = 123 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M32.py b/pyasic/miners/types/whatsminer/M3X/M32.py index 67cb86fd..15d066d3 100644 --- a/pyasic/miners/types/whatsminer/M3X/M32.py +++ b/pyasic/miners/types/whatsminer/M3X/M32.py @@ -23,7 +23,7 @@ class M32V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M32 V10" + self.raw_model = "M32 V10" self.expected_chips = 78 self.fan_count = 2 @@ -32,6 +32,6 @@ class M32V20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M32 V20" + self.raw_model = "M32 V20" self.expected_chips = 74 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M32S.py b/pyasic/miners/types/whatsminer/M3X/M32S.py index 2421cda3..9d0705ab 100644 --- a/pyasic/miners/types/whatsminer/M3X/M32S.py +++ b/pyasic/miners/types/whatsminer/M3X/M32S.py @@ -21,6 +21,6 @@ class M32S(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M32S" + self.raw_model = "M32S" self.expected_chips = 78 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M3X/M33.py b/pyasic/miners/types/whatsminer/M3X/M33.py index 92d7f270..373a79a8 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33.py +++ b/pyasic/miners/types/whatsminer/M3X/M33.py @@ -23,7 +23,7 @@ class M33V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33 V10" + self.raw_model = "M33 V10" self.expected_chips = 33 self.fan_count = 0 @@ -32,7 +32,7 @@ class M33V20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33 V20" + self.raw_model = "M33 V20" self.expected_chips = 62 self.fan_count = 0 @@ -41,6 +41,6 @@ class M33V30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33 V30" + self.raw_model = "M33 V30" self.expected_chips = 66 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M33S.py b/pyasic/miners/types/whatsminer/M3X/M33S.py index 5337ff53..98390fc3 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33S.py +++ b/pyasic/miners/types/whatsminer/M3X/M33S.py @@ -23,7 +23,7 @@ class M33SVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S VG30" + self.raw_model = "M33S VG30" self.expected_hashboards = 4 self.expected_chips = 116 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M33S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M33S_Plus.py index e5883eb8..99f7a85f 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M33S_Plus.py @@ -23,7 +23,7 @@ class M33SPlusVG20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S+ VG20" + self.raw_model = "M33S+ VG20" self.expected_hashboards = 4 self.expected_chips = 112 self.fan_count = 0 @@ -33,7 +33,7 @@ class M33SPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S+ VH20" + self.raw_model = "M33S+ VH20" self.expected_hashboards = 4 self.expected_chips = 100 self.fan_count = 0 @@ -43,7 +43,7 @@ class M33SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S+ VH30" + self.raw_model = "M33S+ VH30" self.expected_hashboards = 4 self.expected_chips = 0 # slot1 116, slot2 106, slot3 116, slot4 106 warnings.warn( diff --git a/pyasic/miners/types/whatsminer/M3X/M33S_Plus_Plus.py b/pyasic/miners/types/whatsminer/M3X/M33S_Plus_Plus.py index 84dcfc32..2af0f58b 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33S_Plus_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M33S_Plus_Plus.py @@ -23,7 +23,7 @@ class M33SPlusPlusVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S++ VH20" + self.raw_model = "M33S++ VH20" self.expected_hashboards = 4 self.expected_chips = 112 self.fan_count = 0 @@ -33,7 +33,7 @@ class M33SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S++ VH30" + self.raw_model = "M33S++ VH30" self.expected_hashboards = 4 self.expected_chips = 0 warnings.warn( @@ -46,7 +46,7 @@ class M33SPlusPlusVG40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M33S++ VG40" + self.raw_model = "M33S++ VG40" self.expected_hashboards = 4 self.expected_chips = 174 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M34S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M34S_Plus.py index 2b29ffa1..97bdf1d6 100644 --- a/pyasic/miners/types/whatsminer/M3X/M34S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M34S_Plus.py @@ -21,7 +21,7 @@ class M34SPlusVE10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M34S+ VE10" + self.raw_model = "M34S+ VE10" self.expected_hashboards = 4 self.expected_chips = 116 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M36S.py b/pyasic/miners/types/whatsminer/M3X/M36S.py index 2e15d0fd..b100851b 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S.py @@ -23,7 +23,7 @@ class M36SVE10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M36S VE10" + self.raw_model = "M36S VE10" self.expected_hashboards = 4 self.expected_chips = 114 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py index a6fa8ed0..a1dc8826 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py @@ -23,7 +23,7 @@ class M36SPlusVG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M36S+ VG30" + self.raw_model = "M36S+ VG30" self.expected_hashboards = 4 self.expected_chips = 108 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py b/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py index dd04ca83..6ce8dae9 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py @@ -23,7 +23,7 @@ class M36SPlusPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M36S++ VH30" + self.raw_model = "M36S++ VH30" self.expected_hashboards = 4 self.expected_chips = 80 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M3X/M39.py b/pyasic/miners/types/whatsminer/M3X/M39.py index a6cfae42..6f8b24e1 100644 --- a/pyasic/miners/types/whatsminer/M3X/M39.py +++ b/pyasic/miners/types/whatsminer/M3X/M39.py @@ -23,7 +23,7 @@ class M39V10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M39 V10" + self.raw_model = "M39 V10" self.expected_chips = 50 self.fan_count = 0 @@ -32,7 +32,7 @@ class M39V20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M39 V20" + self.raw_model = "M39 V20" self.expected_chips = 54 self.fan_count = 0 @@ -41,6 +41,6 @@ class M39V30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M39 V30" + self.raw_model = "M39 V30" self.expected_chips = 68 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M5X/M50.py b/pyasic/miners/types/whatsminer/M5X/M50.py index cac9bb33..76166549 100644 --- a/pyasic/miners/types/whatsminer/M5X/M50.py +++ b/pyasic/miners/types/whatsminer/M5X/M50.py @@ -23,7 +23,7 @@ class M50VE30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VE30" + self.raw_model = "M50 VE30" self.expected_hashboards = 4 self.expected_chips = 255 self.fan_count = 2 @@ -33,7 +33,7 @@ class M50VG30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VG30" + self.raw_model = "M50 VG30" self.expected_chips = 156 self.fan_count = 2 @@ -42,7 +42,7 @@ class M50VH10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH10" + self.raw_model = "M50 VH10" self.expected_chips = 86 self.fan_count = 2 @@ -51,7 +51,7 @@ class M50VH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH20" + self.raw_model = "M50 VH20" self.expected_chips = 111 self.fan_count = 2 @@ -60,7 +60,7 @@ class M50VH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH30" + self.raw_model = "M50 VH30" self.expected_chips = 117 self.fan_count = 2 @@ -69,7 +69,7 @@ class M50VH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH40" + self.raw_model = "M50 VH40" self.expected_chips = 84 self.fan_count = 2 @@ -78,7 +78,7 @@ class M50VH50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH50" + self.raw_model = "M50 VH50" self.expected_chips = 105 self.fan_count = 2 @@ -87,7 +87,7 @@ class M50VH60(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH60" + self.raw_model = "M50 VH60" self.expected_chips = 84 self.fan_count = 2 @@ -96,7 +96,7 @@ class M50VH70(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH70" + self.raw_model = "M50 VH70" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50 VH70, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -108,7 +108,7 @@ class M50VH80(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VH80" + self.raw_model = "M50 VH80" self.expected_chips = 111 self.fan_count = 2 @@ -117,7 +117,7 @@ class M50VJ10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VJ10" + self.raw_model = "M50 VJ10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50 VJ10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -129,7 +129,7 @@ class M50VJ20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VJ20" + self.raw_model = "M50 VJ20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50 VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -141,7 +141,7 @@ class M50VJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50 VJ30" + self.raw_model = "M50 VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50 VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M50S.py b/pyasic/miners/types/whatsminer/M5X/M50S.py index bbf5f732..370fb9dd 100644 --- a/pyasic/miners/types/whatsminer/M5X/M50S.py +++ b/pyasic/miners/types/whatsminer/M5X/M50S.py @@ -23,7 +23,7 @@ class M50SVJ10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VJ10" + self.raw_model = "M50S VJ10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VJ10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M50SVJ20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VJ20" + self.raw_model = "M50S VJ20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VJ20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M50SVJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VJ30" + self.raw_model = "M50S VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -59,7 +59,7 @@ class M50SVH10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VH10" + self.raw_model = "M50S VH10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VH10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -71,7 +71,7 @@ class M50SVH20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VH20" + self.raw_model = "M50S VH20" self.expected_chips = 135 self.fan_count = 2 @@ -80,7 +80,7 @@ class M50SVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VH30" + self.raw_model = "M50S VH30" self.expected_chips = 156 self.fan_count = 2 @@ -89,7 +89,7 @@ class M50SVH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VH40" + self.raw_model = "M50S VH40" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -101,7 +101,7 @@ class M50SVH50(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S VH50" + self.raw_model = "M50S VH50" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S VH50, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M50S_Plus.py b/pyasic/miners/types/whatsminer/M5X/M50S_Plus.py index 03ab6316..54479080 100644 --- a/pyasic/miners/types/whatsminer/M5X/M50S_Plus.py +++ b/pyasic/miners/types/whatsminer/M5X/M50S_Plus.py @@ -23,7 +23,7 @@ class M50SPlusVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S+ VH30" + self.raw_model = "M50S+ VH30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S+ VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M50SPlusVH40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S+ VH40" + self.raw_model = "M50S+ VH40" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S+ VH40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M50SPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S+ VJ30" + self.raw_model = "M50S+ VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S+ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -59,6 +59,6 @@ class M50SPlusVK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S+ VK20" + self.raw_model = "M50S+ VK20" self.expected_chips = 117 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M5X/M50S_Plus_Plus.py b/pyasic/miners/types/whatsminer/M5X/M50S_Plus_Plus.py index 10b93b64..ba5ae6c5 100644 --- a/pyasic/miners/types/whatsminer/M5X/M50S_Plus_Plus.py +++ b/pyasic/miners/types/whatsminer/M5X/M50S_Plus_Plus.py @@ -23,7 +23,7 @@ class M50SPlusPlusVK10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S++ VK10" + self.raw_model = "M50S++ VK10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S+ VK10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M50SPlusPlusVK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S++ VK20" + self.raw_model = "M50S++ VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M50S+ VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,6 +47,6 @@ class M50SPlusPlusVK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M50S++ VK30" + self.raw_model = "M50S++ VK30" self.expected_chips = 76 self.fan_count = 2 diff --git a/pyasic/miners/types/whatsminer/M5X/M53.py b/pyasic/miners/types/whatsminer/M5X/M53.py index e385141a..9e85eccc 100644 --- a/pyasic/miners/types/whatsminer/M5X/M53.py +++ b/pyasic/miners/types/whatsminer/M5X/M53.py @@ -23,7 +23,7 @@ class M53VH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M53 VH30" + self.raw_model = "M53 VH30" self.expected_hashboards = 4 self.expected_chips = 128 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M5X/M53S.py b/pyasic/miners/types/whatsminer/M5X/M53S.py index 34156587..61eca632 100644 --- a/pyasic/miners/types/whatsminer/M5X/M53S.py +++ b/pyasic/miners/types/whatsminer/M5X/M53S.py @@ -23,7 +23,7 @@ class M53SVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M53S VH30" + self.raw_model = "M53S VH30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M53S VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M53S_Plus.py b/pyasic/miners/types/whatsminer/M5X/M53S_Plus.py index 2cead1f2..a48b697d 100644 --- a/pyasic/miners/types/whatsminer/M5X/M53S_Plus.py +++ b/pyasic/miners/types/whatsminer/M5X/M53S_Plus.py @@ -23,7 +23,7 @@ class M53SPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M53S+ VJ30" + self.raw_model = "M53S+ VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M53S+ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M56.py b/pyasic/miners/types/whatsminer/M5X/M56.py index 1c0980cb..29326152 100644 --- a/pyasic/miners/types/whatsminer/M5X/M56.py +++ b/pyasic/miners/types/whatsminer/M5X/M56.py @@ -23,7 +23,7 @@ class M56VH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M56 VH30" + self.raw_model = "M56 VH30" self.expected_hashboards = 4 self.expected_chips = 108 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M5X/M56S.py b/pyasic/miners/types/whatsminer/M5X/M56S.py index b6c5ef78..87ea2f46 100644 --- a/pyasic/miners/types/whatsminer/M5X/M56S.py +++ b/pyasic/miners/types/whatsminer/M5X/M56S.py @@ -23,7 +23,7 @@ class M56SVH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M56S VH30" + self.raw_model = "M56S VH30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M56S VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M56S_Plus.py b/pyasic/miners/types/whatsminer/M5X/M56S_Plus.py index 0bbeacd9..3addcf4d 100644 --- a/pyasic/miners/types/whatsminer/M5X/M56S_Plus.py +++ b/pyasic/miners/types/whatsminer/M5X/M56S_Plus.py @@ -23,7 +23,7 @@ class M56SPlusVJ30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M56S+ VJ30" + self.raw_model = "M56S+ VJ30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M56S+ VJ30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M5X/M59.py b/pyasic/miners/types/whatsminer/M5X/M59.py index b33e03cf..5333dd79 100644 --- a/pyasic/miners/types/whatsminer/M5X/M59.py +++ b/pyasic/miners/types/whatsminer/M5X/M59.py @@ -23,7 +23,7 @@ class M59VH30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M59 VH30" + self.raw_model = "M59 VH30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M59 VH30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M6X/M60.py b/pyasic/miners/types/whatsminer/M6X/M60.py index 652c1c15..635d42ae 100644 --- a/pyasic/miners/types/whatsminer/M6X/M60.py +++ b/pyasic/miners/types/whatsminer/M6X/M60.py @@ -23,7 +23,7 @@ class M60VK10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60 VK10" + self.raw_model = "M60 VK10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60 VK10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M60VK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60 VK20" + self.raw_model = "M60 VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60 VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M60VK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60 VK30" + self.raw_model = "M60 VK30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60 VK30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -59,7 +59,7 @@ class M60VK40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60 VK40" + self.raw_model = "M60 VK40" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60 VK40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M6X/M60S.py b/pyasic/miners/types/whatsminer/M6X/M60S.py index 8f735838..a349cd7f 100644 --- a/pyasic/miners/types/whatsminer/M6X/M60S.py +++ b/pyasic/miners/types/whatsminer/M6X/M60S.py @@ -23,7 +23,7 @@ class M60SVK10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60S VK10" + self.raw_model = "M60S VK10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60S VK10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M60SVK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60S VK20" + self.raw_model = "M60S VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60S VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M60SVK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60S VK30" + self.raw_model = "M60S VK30" self.expected_hashboards = 3 self.expected_chips = 78 self.fan_count = 2 @@ -57,7 +57,7 @@ class M60SVK40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M60S VK40" + self.raw_model = "M60S VK40" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M60S VK40, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M6X/M63.py b/pyasic/miners/types/whatsminer/M6X/M63.py index b1075dd1..939b229d 100644 --- a/pyasic/miners/types/whatsminer/M6X/M63.py +++ b/pyasic/miners/types/whatsminer/M6X/M63.py @@ -23,7 +23,7 @@ class M63VK10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63 VK10" + self.raw_model = "M63 VK10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M63 VK10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M63VK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63 VK20" + self.raw_model = "M63 VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M63 VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M63VK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63 VK30" + self.raw_model = "M63 VK30" self.expected_chips = 68 self.expected_hashboards = 4 self.fan_count = 0 diff --git a/pyasic/miners/types/whatsminer/M6X/M63S.py b/pyasic/miners/types/whatsminer/M6X/M63S.py index 90c20291..06abf024 100644 --- a/pyasic/miners/types/whatsminer/M6X/M63S.py +++ b/pyasic/miners/types/whatsminer/M6X/M63S.py @@ -23,7 +23,7 @@ class M63SVK10(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63S VK10" + self.raw_model = "M63S VK10" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M63S VK10, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M63SVK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63S VK20" + self.raw_model = "M63S VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M63S VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -47,7 +47,7 @@ class M63SVK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M63S VK30" + self.raw_model = "M63S VK30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M63S VK30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M6X/M66.py b/pyasic/miners/types/whatsminer/M6X/M66.py index c006aa06..f13dd83d 100644 --- a/pyasic/miners/types/whatsminer/M6X/M66.py +++ b/pyasic/miners/types/whatsminer/M6X/M66.py @@ -23,7 +23,7 @@ class M66VK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M66 VK20" + self.raw_model = "M66 VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M66 VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M66VK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M66 VK30" + self.raw_model = "M66 VK30" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M66 VK30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/pyasic/miners/types/whatsminer/M6X/M66S.py b/pyasic/miners/types/whatsminer/M6X/M66S.py index 70137a95..05b05ba2 100644 --- a/pyasic/miners/types/whatsminer/M6X/M66S.py +++ b/pyasic/miners/types/whatsminer/M6X/M66S.py @@ -23,7 +23,7 @@ class M66SVK20(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M66S VK20" + self.raw_model = "M66S VK20" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M66S VK20, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." @@ -35,7 +35,7 @@ class M66SVK30(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M66S VK30" + self.raw_model = "M66S VK30" self.expected_chips = 96 self.expected_hashboards = 4 self.fan_count = 0 @@ -45,7 +45,7 @@ class M66SVK40(WhatsMiner): # noqa - ignore ABC method implementation def __init__(self, ip: str, api_ver: str = "0.0.0"): super().__init__(ip, api_ver) self.ip = ip - self.model = "M66S VK40" + self.raw_model = "M66S VK40" self.expected_chips = 0 warnings.warn( "Unknown chip count for miner type M66 VK30, please open an issue on GitHub (https://github.com/UpstreamData/pyasic)." diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index 1247fc7d..1f77c638 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -56,7 +56,6 @@ class MinersTest(unittest.TestCase): "hostname", "is_mining", "mac", - "model", "expected_hashrate", "uptime", "wattage", From 2ef85d38686b053cb683d3841c033d5cd591dd2e Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 15:00:48 -0700 Subject: [PATCH 18/36] refactor: optimize imports. --- pyasic/miners/backends/bfgminer.py | 3 +-- pyasic/miners/backends/bmminer.py | 3 +-- pyasic/miners/backends/btminer.py | 4 +--- pyasic/miners/backends/cgminer.py | 3 +-- pyasic/miners/backends/cgminer_avalon.py | 1 - pyasic/miners/backends/epic.py | 5 ++--- pyasic/miners/backends/hiveon.py | 2 -- pyasic/miners/backends/luxminer.py | 12 ++---------- pyasic/miners/innosilicon/cgminer/T3X/T3H.py | 1 - pyasic/miners/types/avalonminer/A11X/A1166.py | 1 - pyasic/miners/types/avalonminer/A12X/A1246.py | 1 - pyasic/miners/types/whatsminer/M2X/M20S.py | 2 -- pyasic/miners/types/whatsminer/M2X/M21.py | 2 -- pyasic/miners/types/whatsminer/M2X/M21S.py | 2 -- pyasic/miners/types/whatsminer/M2X/M29.py | 2 -- pyasic/miners/types/whatsminer/M3X/M30.py | 2 -- pyasic/miners/types/whatsminer/M3X/M30K.py | 2 -- pyasic/miners/types/whatsminer/M3X/M30L.py | 2 -- pyasic/miners/types/whatsminer/M3X/M31.py | 2 -- pyasic/miners/types/whatsminer/M3X/M31H.py | 2 -- pyasic/miners/types/whatsminer/M3X/M31L.py | 2 -- pyasic/miners/types/whatsminer/M3X/M31SE.py | 2 -- pyasic/miners/types/whatsminer/M3X/M32.py | 2 -- pyasic/miners/types/whatsminer/M3X/M33.py | 2 -- pyasic/miners/types/whatsminer/M3X/M33S.py | 2 -- pyasic/miners/types/whatsminer/M3X/M36S.py | 2 -- pyasic/miners/types/whatsminer/M3X/M36S_Plus.py | 2 -- pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py | 2 -- pyasic/miners/types/whatsminer/M3X/M39.py | 2 -- pyasic/miners/types/whatsminer/M5X/M53.py | 2 -- pyasic/miners/types/whatsminer/M5X/M56.py | 2 -- pyasic/miners/unknown.py | 1 - pyasic/web/__init__.py | 1 - pyasic/web/epic.py | 3 +-- tests/miners_tests/__init__.py | 6 +----- 35 files changed, 10 insertions(+), 77 deletions(-) diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py index 03b9b0de..0ce02197 100644 --- a/pyasic/miners/backends/bfgminer.py +++ b/pyasic/miners/backends/bfgminer.py @@ -14,8 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from collections import namedtuple -from typing import List, Optional, Tuple +from typing import List, Optional from pyasic.API.bfgminer import BFGMinerAPI from pyasic.config import MinerConfig diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py index 8d49c810..1fd9225b 100644 --- a/pyasic/miners/backends/bmminer.py +++ b/pyasic/miners/backends/bmminer.py @@ -15,8 +15,7 @@ # ------------------------------------------------------------------------------ import logging -from collections import namedtuple -from typing import List, Optional, Tuple +from typing import List, Optional from pyasic.API.bmminer import BMMinerAPI from pyasic.config import MinerConfig diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index e5c7e991..60fb3e8b 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -15,8 +15,7 @@ # ------------------------------------------------------------------------------ import logging -from collections import namedtuple -from typing import List, Optional, Tuple +from typing import List, Optional from pyasic.API.btminer import BTMinerAPI from pyasic.config import MinerConfig, MiningModeConfig @@ -29,7 +28,6 @@ from pyasic.miners.base import ( DataLocations, DataOptions, RPCAPICommand, - WebAPICommand, ) BTMINER_DATA_LOC = DataLocations( diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py index 5d533e4d..4789421f 100644 --- a/pyasic/miners/backends/cgminer.py +++ b/pyasic/miners/backends/cgminer.py @@ -15,8 +15,7 @@ # ------------------------------------------------------------------------------ import logging -from collections import namedtuple -from typing import List, Optional, Tuple +from typing import List, Optional from pyasic.API.cgminer import CGMinerAPI from pyasic.config import MinerConfig diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index ebc7b716..23bae3f0 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -14,7 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import logging import re from typing import List, Optional diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 0914d0f8..83452263 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -14,10 +14,9 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import List, Optional, Tuple +from typing import List, Optional -from pyasic import MinerConfig -from pyasic.config import MinerConfig, MiningModeConfig +from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.errors import APIError diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 3b99bcf6..1ce6c12b 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import Optional - from pyasic.miners.backends import BMMiner diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index 49f6ccea..b22260b9 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -13,18 +13,12 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import asyncio -import logging -from collections import namedtuple from typing import List, Optional, Tuple, Union -import toml - -from pyasic.API.bosminer import BOSMinerAPI from pyasic.API.luxminer import LUXMinerAPI from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard -from pyasic.data.error_codes import BraiinsOSError, MinerErrorData +from pyasic.data.error_codes import MinerErrorData from pyasic.errors import APIError from pyasic.miners.base import ( BaseMiner, @@ -32,9 +26,7 @@ from pyasic.miners.base import ( DataLocations, DataOptions, RPCAPICommand, - WebAPICommand, ) -from pyasic.web.braiins_os import BOSMinerWebAPI LUXMINER_DATA_LOC = DataLocations( **{ @@ -341,7 +333,7 @@ class LUXMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except LookupError: + except (KeyError, IndexError): pass async def _is_mining(self) -> Optional[bool]: diff --git a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py index 4c512fdd..593ef039 100644 --- a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py +++ b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import logging from typing import List, Optional from pyasic.config import MinerConfig diff --git a/pyasic/miners/types/avalonminer/A11X/A1166.py b/pyasic/miners/types/avalonminer/A11X/A1166.py index e26136f7..685a0f53 100644 --- a/pyasic/miners/types/avalonminer/A11X/A1166.py +++ b/pyasic/miners/types/avalonminer/A11X/A1166.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings from pyasic.miners.makes import AvalonMiner diff --git a/pyasic/miners/types/avalonminer/A12X/A1246.py b/pyasic/miners/types/avalonminer/A12X/A1246.py index 98b3dd1a..b066a02b 100644 --- a/pyasic/miners/types/avalonminer/A12X/A1246.py +++ b/pyasic/miners/types/avalonminer/A12X/A1246.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings from pyasic.miners.makes import AvalonMiner diff --git a/pyasic/miners/types/whatsminer/M2X/M20S.py b/pyasic/miners/types/whatsminer/M2X/M20S.py index ccfa4d80..cbefb29d 100644 --- a/pyasic/miners/types/whatsminer/M2X/M20S.py +++ b/pyasic/miners/types/whatsminer/M2X/M20S.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M2X/M21.py b/pyasic/miners/types/whatsminer/M2X/M21.py index 1a2f811c..5b274895 100644 --- a/pyasic/miners/types/whatsminer/M2X/M21.py +++ b/pyasic/miners/types/whatsminer/M2X/M21.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M2X/M21S.py b/pyasic/miners/types/whatsminer/M2X/M21S.py index dd224341..7c63a150 100644 --- a/pyasic/miners/types/whatsminer/M2X/M21S.py +++ b/pyasic/miners/types/whatsminer/M2X/M21S.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M2X/M29.py b/pyasic/miners/types/whatsminer/M2X/M29.py index e3cd9d34..1d31fcfe 100644 --- a/pyasic/miners/types/whatsminer/M2X/M29.py +++ b/pyasic/miners/types/whatsminer/M2X/M29.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M30.py b/pyasic/miners/types/whatsminer/M3X/M30.py index 4e1aaf0d..a2ea42a6 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30.py +++ b/pyasic/miners/types/whatsminer/M3X/M30.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M30K.py b/pyasic/miners/types/whatsminer/M3X/M30K.py index b951c10b..3177ef08 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30K.py +++ b/pyasic/miners/types/whatsminer/M3X/M30K.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M30L.py b/pyasic/miners/types/whatsminer/M3X/M30L.py index 191b5e60..743ccc69 100644 --- a/pyasic/miners/types/whatsminer/M3X/M30L.py +++ b/pyasic/miners/types/whatsminer/M3X/M30L.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M31.py b/pyasic/miners/types/whatsminer/M3X/M31.py index 0e5b7003..405d64aa 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31.py +++ b/pyasic/miners/types/whatsminer/M3X/M31.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M31H.py b/pyasic/miners/types/whatsminer/M3X/M31H.py index 64612a1b..f86e3467 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31H.py +++ b/pyasic/miners/types/whatsminer/M3X/M31H.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M31L.py b/pyasic/miners/types/whatsminer/M3X/M31L.py index 7b30e980..8981b94e 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31L.py +++ b/pyasic/miners/types/whatsminer/M3X/M31L.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M31SE.py b/pyasic/miners/types/whatsminer/M3X/M31SE.py index 5f6aff90..d8207ab6 100644 --- a/pyasic/miners/types/whatsminer/M3X/M31SE.py +++ b/pyasic/miners/types/whatsminer/M3X/M31SE.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M32.py b/pyasic/miners/types/whatsminer/M3X/M32.py index 15d066d3..01bc0078 100644 --- a/pyasic/miners/types/whatsminer/M3X/M32.py +++ b/pyasic/miners/types/whatsminer/M3X/M32.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M33.py b/pyasic/miners/types/whatsminer/M3X/M33.py index 373a79a8..c8955ea6 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33.py +++ b/pyasic/miners/types/whatsminer/M3X/M33.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M33S.py b/pyasic/miners/types/whatsminer/M3X/M33S.py index 98390fc3..65d846ee 100644 --- a/pyasic/miners/types/whatsminer/M3X/M33S.py +++ b/pyasic/miners/types/whatsminer/M3X/M33S.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M36S.py b/pyasic/miners/types/whatsminer/M3X/M36S.py index b100851b..2bc177dc 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py b/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py index a1dc8826..27d169d2 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S_Plus.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py b/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py index 6ce8dae9..e7e54d0c 100644 --- a/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py +++ b/pyasic/miners/types/whatsminer/M3X/M36S_Plus_Plus.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M3X/M39.py b/pyasic/miners/types/whatsminer/M3X/M39.py index 6f8b24e1..ebefbc56 100644 --- a/pyasic/miners/types/whatsminer/M3X/M39.py +++ b/pyasic/miners/types/whatsminer/M3X/M39.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M5X/M53.py b/pyasic/miners/types/whatsminer/M5X/M53.py index 9e85eccc..d65c6500 100644 --- a/pyasic/miners/types/whatsminer/M5X/M53.py +++ b/pyasic/miners/types/whatsminer/M5X/M53.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/types/whatsminer/M5X/M56.py b/pyasic/miners/types/whatsminer/M5X/M56.py index 29326152..3d4e07b4 100644 --- a/pyasic/miners/types/whatsminer/M5X/M56.py +++ b/pyasic/miners/types/whatsminer/M5X/M56.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import warnings - from pyasic.miners.makes import WhatsMiner diff --git a/pyasic/miners/unknown.py b/pyasic/miners/unknown.py index c1b1cf97..ddffdc2c 100644 --- a/pyasic/miners/unknown.py +++ b/pyasic/miners/unknown.py @@ -20,7 +20,6 @@ from pyasic.API.unknown import UnknownAPI from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard, MinerData from pyasic.data.error_codes import MinerErrorData -from pyasic.errors import APIError from pyasic.miners.base import BaseMiner diff --git a/pyasic/web/__init__.py b/pyasic/web/__init__.py index c31c2e9b..72774619 100644 --- a/pyasic/web/__init__.py +++ b/pyasic/web/__init__.py @@ -13,7 +13,6 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import ipaddress import warnings from abc import ABC, abstractmethod from typing import Union diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py index 2784edee..6482bdc5 100644 --- a/pyasic/web/epic.py +++ b/pyasic/web/epic.py @@ -14,13 +14,12 @@ # limitations under the License. - # ------------------------------------------------------------------------------ import json -import warnings from typing import Union import httpx from pyasic import settings -from pyasic.errors import APIError, APIWarning +from pyasic.errors import APIError from pyasic.web import BaseWebAPI diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index 1f77c638..82d8a246 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -13,16 +13,12 @@ # 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): From cd1768aae9ffee31c297147f83457eaeb6936d68 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 15:20:33 -0700 Subject: [PATCH 19/36] refactor: swap (KeyError, IndexError) for LookupError. --- pyasic/miners/backends/antminer.py | 2 +- pyasic/miners/backends/bfgminer.py | 6 ++--- pyasic/miners/backends/bmminer.py | 4 ++-- pyasic/miners/backends/braiins_os.py | 28 ++++++++++++------------ pyasic/miners/backends/btminer.py | 10 ++++----- pyasic/miners/backends/cgminer.py | 4 ++-- pyasic/miners/backends/cgminer_avalon.py | 14 ++++++------ pyasic/miners/backends/epic.py | 2 +- pyasic/miners/backends/luxminer.py | 12 +++++----- pyasic/miners/backends/vnish.py | 2 +- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index 5fde75a6..7e7d5c33 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -550,7 +550,7 @@ class AntminerOld(CGMiner): if (not chips) or (not chips > 0): hashboard.missing = True hashboards.append(hashboard) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return hashboards diff --git a/pyasic/miners/backends/bfgminer.py b/pyasic/miners/backends/bfgminer.py index 0ce02197..8dfb1499 100644 --- a/pyasic/miners/backends/bfgminer.py +++ b/pyasic/miners/backends/bfgminer.py @@ -168,7 +168,7 @@ class BFGMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 20s"] / 1000000), 2) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -220,7 +220,7 @@ class BFGMiner(BaseMiner): if (not chips) or (not chips > 0): hashboard.missing = True hashboards.append(hashboard) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return hashboards @@ -258,7 +258,7 @@ class BFGMiner(BaseMiner): fans_data[fan] = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) - except (KeyError, IndexError): + except LookupError: pass fans = [Fan(speed=d) if d else Fan() for d in fans_data] diff --git a/pyasic/miners/backends/bmminer.py b/pyasic/miners/backends/bmminer.py index 1fd9225b..9d2b71f7 100644 --- a/pyasic/miners/backends/bmminer.py +++ b/pyasic/miners/backends/bmminer.py @@ -205,7 +205,7 @@ class BMMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -308,7 +308,7 @@ class BMMiner(BaseMiner): fans[fan].speed = api_stats["STATS"][1].get( f"fan{fan_offset+fan}", 0 ) - except (KeyError, IndexError): + except LookupError: pass return fans diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 7307233d..84df4a05 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -478,7 +478,7 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -580,7 +580,7 @@ class BOSMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards( @@ -642,7 +642,7 @@ class BOSMiner(BaseMiner): board.temp = round(hb["temperatures"][0]["degreesC"]) if len(temps) > 1: board.chip_temp = round(hb["temperatures"][1]["degreesC"]) - except (TypeError, KeyError, ValueError, IndexError): + except (LookupError, TypeError, ValueError): pass details = hb.get("hwDetails") if details: @@ -666,15 +666,15 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except (KeyError, IndexError): + except LookupError: api_temps = None try: api_devdetails = d["devdetails"][0] - except (KeyError, IndexError): + except LookupError: api_devdetails = None try: api_devs = d["devs"][0] - except (KeyError, IndexError): + except LookupError: api_devs = None if api_temps: try: @@ -686,7 +686,7 @@ class BOSMiner(BaseMiner): board_temp = round(board["Board"]) hashboards[_id].chip_temp = chip_temp hashboards[_id].temp = board_temp - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass if api_devdetails: @@ -698,7 +698,7 @@ class BOSMiner(BaseMiner): chips = board["Chips"] hashboards[_id].chips = chips hashboards[_id].missing = False - except (IndexError, KeyError): + except LookupError: pass if api_devs: @@ -709,7 +709,7 @@ class BOSMiner(BaseMiner): _id = board["ID"] - offset hashrate = round(float(board["MHS 1m"] / 1000000), 2) hashboards[_id].hashrate = hashrate - except (IndexError, KeyError): + except LookupError: pass return hashboards @@ -750,7 +750,7 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except (KeyError, IndexError): + except LookupError: pass async def _get_wattage_limit( @@ -781,7 +781,7 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except (KeyError, IndexError): + except LookupError: pass async def _get_fans( @@ -820,7 +820,7 @@ class BOSMiner(BaseMiner): for n in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][n]["RPM"])) - except (IndexError, KeyError): + except LookupError: pass return fans return [Fan() for _ in range(self.expected_fans)] @@ -904,7 +904,7 @@ class BOSMiner(BaseMiner): _error = _error[0].lower() + _error[1:] errors.append(BraiinsOSError(f"Slot {_id} {_error}")) return errors - except (KeyError, IndexError): + except LookupError: pass async def _get_fault_light(self, graphql_fault_light: dict = None) -> bool: @@ -987,7 +987,7 @@ class BOSMiner(BaseMiner): return round( (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 ) - except (IndexError, KeyError): + except LookupError: pass async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index 60fb3e8b..fa9085df 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -394,7 +394,7 @@ class BTMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError): + except LookupError: pass async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: @@ -442,7 +442,7 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Env Temp"] - except (KeyError, IndexError): + except LookupError: pass async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: @@ -469,7 +469,7 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Power Limit"] - except (KeyError, IndexError): + except LookupError: pass async def _get_fans( @@ -506,7 +506,7 @@ class BTMiner(BaseMiner): if api_summary: try: return int(api_summary["SUMMARY"][0]["Power Fanspeed"]) - except (KeyError, IndexError): + except LookupError: pass if not api_get_psu: @@ -537,7 +537,7 @@ class BTMiner(BaseMiner): err = api_summary["SUMMARY"][0].get(f"Error Code {i}") if err: errors.append(WhatsminerError(error_code=err)) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass if not api_get_error_code: diff --git a/pyasic/miners/backends/cgminer.py b/pyasic/miners/backends/cgminer.py index 4789421f..9e56d119 100644 --- a/pyasic/miners/backends/cgminer.py +++ b/pyasic/miners/backends/cgminer.py @@ -229,7 +229,7 @@ class CGMiner(BaseMiner): return round( float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 ) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -281,7 +281,7 @@ class CGMiner(BaseMiner): if (not chips) or (not chips > 0): hashboard.missing = True hashboards.append(hashboard) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return hashboards diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 23bae3f0..9b4ce6b9 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -210,7 +210,7 @@ class CGMinerAvalon(CGMiner): if api_devs: try: return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -229,7 +229,7 @@ class CGMinerAvalon(CGMiner): try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): return hashboards for board in range(self.expected_hashboards): @@ -273,7 +273,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return round(float(parsed_stats["GHSmm"]) / 1000, 2) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: @@ -288,7 +288,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return float(parsed_stats["Temp"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_wattage(self) -> Optional[int]: @@ -306,7 +306,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return int(parsed_stats["MPO"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_fans(self, api_stats: dict = None) -> List[Fan]: @@ -327,7 +327,7 @@ class CGMinerAvalon(CGMiner): for fan in range(self.expected_fans): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return fans_data @@ -349,7 +349,7 @@ class CGMinerAvalon(CGMiner): parsed_stats = self.parse_stats(unparsed_stats) led = int(parsed_stats["Led"]) return True if led == 1 else False - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass try: diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 83452263..a4519133 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -216,7 +216,7 @@ class ePIC(BaseMiner): hashrate += hb["Hashrate"][0] / ideal return round(float(float(hashrate / 1000000)), 2) - except (IndexError, KeyError, ValueError, TypeError) as e: + except (LookupError, ValueError, TypeError) as e: logger.error(e) pass diff --git a/pyasic/miners/backends/luxminer.py b/pyasic/miners/backends/luxminer.py index b22260b9..7e1d6499 100644 --- a/pyasic/miners/backends/luxminer.py +++ b/pyasic/miners/backends/luxminer.py @@ -211,7 +211,7 @@ class LUXMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["GHS 5s"] / 1000), 2) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -263,7 +263,7 @@ class LUXMiner(BaseMiner): if (not chips) or (not chips > 0): hashboard.missing = True hashboards.append(hashboard) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return hashboards @@ -281,7 +281,7 @@ class LUXMiner(BaseMiner): if api_power: try: return api_power["POWER"][0]["Watts"] - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_wattage_limit(self) -> Optional[int]: @@ -299,8 +299,8 @@ class LUXMiner(BaseMiner): if api_fans: for fan in range(self.expected_fans): try: - fans.append(Fan(api_fans["FANS"][0]["RPM"])) - except (IndexError, KeyError, ValueError, TypeError): + fans.append(Fan(api_fans["FANS"][fan]["RPM"])) + except (LookupError, ValueError, TypeError): fans.append(Fan()) return fans @@ -333,7 +333,7 @@ class LUXMiner(BaseMiner): return round(expected_rate / 1000000, 2) else: return round(expected_rate, 2) - except (KeyError, IndexError): + except LookupError: pass async def _is_mining(self) -> Optional[bool]: diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py index f2af26a2..7fafba06 100644 --- a/pyasic/miners/backends/vnish.py +++ b/pyasic/miners/backends/vnish.py @@ -181,7 +181,7 @@ class VNish(BMMiner): return round( float(float(api_summary["SUMMARY"][0]["GHS 5s"]) / 1000), 2 ) - except (IndexError, KeyError, ValueError, TypeError) as e: + except (LookupError, ValueError, TypeError) as e: logger.error(e) pass From b4b84c773fff2c046f6f2564a04a7c183acc073a Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 15:29:29 -0700 Subject: [PATCH 20/36] refactor: remove bad function. --- pyasic/miners/backends/braiins_os.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 84df4a05..f2e98828 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -458,15 +458,6 @@ class BOSMiner(BaseMiner): return self.raw_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: From 067d5c98f5faa7ce3d00e188a9bf3f3544522231 Mon Sep 17 00:00:00 2001 From: fdeh Date: Sun, 14 Jan 2024 19:53:47 +0300 Subject: [PATCH 21/36] Fix VNish get_hashrate and get_fans errors Update vnish.py. Fix data locations according to the method arguments --- pyasic/miners/backends/vnish.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyasic/miners/backends/vnish.py b/pyasic/miners/backends/vnish.py index 7fafba06..13d8cef5 100644 --- a/pyasic/miners/backends/vnish.py +++ b/pyasic/miners/backends/vnish.py @@ -44,7 +44,7 @@ VNISH_DATA_LOC = DataLocations( "_get_hostname", [WebAPICommand("web_summary", "summary")] ), str(DataOptions.HASHRATE): DataFunction( - "_get_hashrate", [WebAPICommand("web_summary", "summary")] + "_get_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( "_get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] @@ -60,7 +60,7 @@ VNISH_DATA_LOC = DataLocations( "_get_wattage_limit", [WebAPICommand("web_settings", "settings")] ), str(DataOptions.FANS): DataFunction( - "_get_fans", [WebAPICommand("web_summary", "summary")] + "_get_fans", [RPCAPICommand("api_stats", "stats")] ), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction("_get_errors"), From 06cc84f16d42945c71a5634b59cfbf236074cc26 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 11:33:44 -0700 Subject: [PATCH 22/36] refactor: remove parameters from `get_{x}` functions and move them to `_get_{x}(**params)`. Add `miner.fw_str`, and `miner.raw_model`. Remove `model` from `get_data` exclude. Swap `fan_count` to `expected_fans`. --- pyasic/miners/backends/braiins_os.py | 37 +++++++++++++++++----------- pyasic/miners/backends/epic.py | 7 +++--- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index f2e98828..7307233d 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -458,6 +458,15 @@ class BOSMiner(BaseMiner): return self.raw_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: @@ -469,7 +478,7 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except LookupError: + except (KeyError, IndexError): api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -571,7 +580,7 @@ class BOSMiner(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_hashboards( @@ -633,7 +642,7 @@ class BOSMiner(BaseMiner): board.temp = round(hb["temperatures"][0]["degreesC"]) if len(temps) > 1: board.chip_temp = round(hb["temperatures"][1]["degreesC"]) - except (LookupError, TypeError, ValueError): + except (TypeError, KeyError, ValueError, IndexError): pass details = hb.get("hwDetails") if details: @@ -657,15 +666,15 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except LookupError: + except (KeyError, IndexError): api_temps = None try: api_devdetails = d["devdetails"][0] - except LookupError: + except (KeyError, IndexError): api_devdetails = None try: api_devs = d["devs"][0] - except LookupError: + except (KeyError, IndexError): api_devs = None if api_temps: try: @@ -677,7 +686,7 @@ class BOSMiner(BaseMiner): board_temp = round(board["Board"]) hashboards[_id].chip_temp = chip_temp hashboards[_id].temp = board_temp - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): pass if api_devdetails: @@ -689,7 +698,7 @@ class BOSMiner(BaseMiner): chips = board["Chips"] hashboards[_id].chips = chips hashboards[_id].missing = False - except LookupError: + except (IndexError, KeyError): pass if api_devs: @@ -700,7 +709,7 @@ class BOSMiner(BaseMiner): _id = board["ID"] - offset hashrate = round(float(board["MHS 1m"] / 1000000), 2) hashboards[_id].hashrate = hashrate - except LookupError: + except (IndexError, KeyError): pass return hashboards @@ -741,7 +750,7 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except LookupError: + except (KeyError, IndexError): pass async def _get_wattage_limit( @@ -772,7 +781,7 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except LookupError: + except (KeyError, IndexError): pass async def _get_fans( @@ -811,7 +820,7 @@ class BOSMiner(BaseMiner): for n in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][n]["RPM"])) - except LookupError: + except (IndexError, KeyError): pass return fans return [Fan() for _ in range(self.expected_fans)] @@ -895,7 +904,7 @@ class BOSMiner(BaseMiner): _error = _error[0].lower() + _error[1:] errors.append(BraiinsOSError(f"Slot {_id} {_error}")) return errors - except LookupError: + except (KeyError, IndexError): pass async def _get_fault_light(self, graphql_fault_light: dict = None) -> bool: @@ -978,7 +987,7 @@ class BOSMiner(BaseMiner): return round( (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 ) - except LookupError: + except (IndexError, KeyError): pass async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index a4519133..0914d0f8 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -14,9 +14,10 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import List, Optional +from typing import List, Optional, Tuple -from pyasic.config import MinerConfig +from pyasic import MinerConfig +from pyasic.config import MinerConfig, MiningModeConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.errors import APIError @@ -216,7 +217,7 @@ class ePIC(BaseMiner): hashrate += hb["Hashrate"][0] / ideal return round(float(float(hashrate / 1000000)), 2) - except (LookupError, ValueError, TypeError) as e: + except (IndexError, KeyError, ValueError, TypeError) as e: logger.error(e) pass From aa9ba66f8ecb10335c484fa00a18716378dff77c Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 10:16:47 -0700 Subject: [PATCH 23/36] bug: add test to cross check function arguments, and fix some method implementations and naming. --- docs/miners/innosilicon/A10X.md | 3 +- docs/miners/innosilicon/T3X.md | 3 +- pyasic/miners/antminer/hiveon/X9/T9.py | 40 +- pyasic/miners/backends/btminer.py | 149 ++++--- pyasic/miners/backends/cgminer_avalon.py | 81 ++-- pyasic/miners/backends/hiveon.py | 63 ++- pyasic/miners/backends/innosilicon.py | 404 ++++++++++++++++++ .../miners/innosilicon/cgminer/A10X/A10X.py | 316 +------------- .../innosilicon/cgminer/A10X/__init__.py | 2 +- pyasic/miners/innosilicon/cgminer/T3X/T3H.py | 276 +----------- .../innosilicon/cgminer/T3X/__init__.py | 2 +- pyasic/miners/miner_factory.py | 4 +- pyasic/web/{inno.py => innosilicon.py} | 0 tests/miners_tests/__init__.py | 25 ++ 14 files changed, 655 insertions(+), 713 deletions(-) create mode 100644 pyasic/miners/backends/innosilicon.py rename pyasic/web/{inno.py => innosilicon.py} (100%) diff --git a/docs/miners/innosilicon/A10X.md b/docs/miners/innosilicon/A10X.md index b3e5bf03..62dc9450 100644 --- a/docs/miners/innosilicon/A10X.md +++ b/docs/miners/innosilicon/A10X.md @@ -2,9 +2,8 @@ ## A10X Models ## A10X -::: pyasic.miners.innosilicon.cgminer.A10X.A10X.CGMinerA10X +::: pyasic.miners.innosilicon.cgminer.A10X.A10X.InnosiliconA10X handler: python options: show_root_heading: false heading_level: 4 - diff --git a/docs/miners/innosilicon/T3X.md b/docs/miners/innosilicon/T3X.md index 47fd870e..bd877b5a 100644 --- a/docs/miners/innosilicon/T3X.md +++ b/docs/miners/innosilicon/T3X.md @@ -2,9 +2,8 @@ ## T3X Models ## T3H+ -::: pyasic.miners.innosilicon.cgminer.T3X.T3H.CGMinerT3HPlus +::: pyasic.miners.innosilicon.cgminer.T3X.T3H.InnosiliconT3HPlus handler: python options: show_root_heading: false heading_level: 4 - diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index 691d150c..f7194624 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -25,10 +25,6 @@ from pyasic.miners.types import T9 class HiveonT9(Hiveon, T9): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip, api_ver=api_ver) - self.ip = ip - self.pwd = "admin" ################################################## ### DATA GATHERING FUNCTIONS (get_{some_data}) ### @@ -46,34 +42,44 @@ class HiveonT9(Hiveon, T9): pass 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) + ] + + if api_stats is None: + try: + api_stats = self.api.stats() + except APIError: + return [] + board_map = { 0: [2, 9, 10], 1: [3, 11, 12], 2: [4, 13, 14], } - hashboards = [] for board in board_map: - hashboard = HashBoard(slot=board, expected_chips=self.expected_chips) hashrate = 0 chips = 0 for chipset in board_map[board]: - if hashboard.chip_temp == None: + if hashboards[board].chip_temp is None: try: - hashboard.board_temp = api_stats["STATS"][1][f"temp{chipset}"] - hashboard.chip_temp = api_stats["STATS"][1][f"temp2_{chipset}"] - except LookupError: + hashboards[board].temp = api_stats["STATS"][1][f"temp{chipset}"] + hashboards[board].chip_temp = api_stats["STATS"][1][ + f"temp2_{chipset}" + ] + except (KeyError, IndexError): pass else: - hashboard.missing = False + hashboards[board].missing = False try: hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"] chips += api_stats["STATS"][1][f"chain_acn{chipset}"] - except LookupError: + except (KeyError, IndexError): pass - hashboard.hashrate = round(hashrate / 1000, 2) - hashboard.chips = chips - hashboards.append(hashboard) + hashboards[board].hashrate = round(hashrate / 1000, 2) + hashboards[board].chips = chips return hashboards @@ -88,7 +94,7 @@ class HiveonT9(Hiveon, T9): boards = api_stats.get("STATS") try: wattage_raw = boards[1]["chain_power"] - except LookupError: + except (KeyError, IndexError): pass else: # parse wattage position out of raw data @@ -113,7 +119,7 @@ class HiveonT9(Hiveon, T9): env_temp = api_stats["STATS"][1][f"temp3_{chipset}"] if not env_temp == 0: env_temp_list.append(int(env_temp)) - except LookupError: + except (KeyError, IndexError): pass if not env_temp_list == []: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index fa9085df..de3ef732 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -15,7 +15,8 @@ # ------------------------------------------------------------------------------ import logging -from typing import List, Optional +from collections import namedtuple +from typing import List, Optional, Tuple from pyasic.API.btminer import BTMinerAPI from pyasic.config import MinerConfig, MiningModeConfig @@ -28,74 +29,80 @@ from pyasic.miners.base import ( DataLocations, DataOptions, RPCAPICommand, + WebAPICommand, ) BTMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "_get_mac", + "get_mac", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_miner_info", "get_miner_info"), ], ), + str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "_get_api_ver", [RPCAPICommand("api_get_version", "get_version")] + "get_api_ver", [RPCAPICommand("api_get_version", "get_version")] ), str(DataOptions.FW_VERSION): DataFunction( - "_get_fw_ver", + "get_fw_ver", [ RPCAPICommand("api_get_version", "get_version"), RPCAPICommand("api_summary", "summary"), ], ), str(DataOptions.HOSTNAME): DataFunction( - "_get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] + "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] ), 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_summary", "summary")] + "get_expected_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.HASHBOARDS): DataFunction( - "_get_hashboards", [RPCAPICommand("api_devs", "devs")] + "get_hashboards", [RPCAPICommand("api_devs", "devs")] ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction( - "_get_env_temp", [RPCAPICommand("api_summary", "summary")] + "get_env_temp", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE): DataFunction( - "_get_wattage", [RPCAPICommand("api_summary", "summary")] + "get_wattage", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "_get_wattage_limit", [RPCAPICommand("api_summary", "summary")] + "get_wattage_limit", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.FANS): DataFunction( - "_get_fans", + "get_fans", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.FAN_PSU): DataFunction( - "_get_fan_psu", + "get_fan_psu", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.ERRORS): DataFunction( - "_get_errors", [RPCAPICommand("api_get_error_code", "get_error_code")] + "get_errors", + [ + RPCAPICommand("api_get_error_code", "get_error_code"), + RPCAPICommand("api_summary", "summary"), + ], ), str(DataOptions.FAULT_LIGHT): DataFunction( - "_get_fault_light", + "get_fault_light", [RPCAPICommand("api_get_miner_info", "get_miner_info")], ), str(DataOptions.IS_MINING): DataFunction( - "_is_mining", [RPCAPICommand("api_status", "status")] + "is_mining", [RPCAPICommand("api_status", "status")] ), str(DataOptions.UPTIME): DataFunction( - "_get_uptime", [RPCAPICommand("api_summary", "summary")] + "get_uptime", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -236,7 +243,7 @@ class BTMiner(BaseMiner): else: cfg = MinerConfig() - is_mining = await self._is_mining(status) + is_mining = await self.is_mining(status) if not is_mining: cfg.mining_mode = MiningModeConfig.sleep() return cfg @@ -280,7 +287,7 @@ class BTMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def _get_mac( + async def get_mac( self, api_summary: dict = None, api_get_miner_info: dict = None ) -> Optional[str]: if not api_get_miner_info: @@ -306,10 +313,24 @@ class BTMiner(BaseMiner): try: mac = api_summary["SUMMARY"][0]["MAC"] return str(mac).upper() - except LookupError: + except (KeyError, IndexError): pass - async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: + async def get_version( + self, api_get_version: dict = None, api_summary: dict = None + ) -> Tuple[Optional[str], Optional[str]]: + miner_version = namedtuple("MinerVersion", "api_ver fw_ver") + api_ver = await self.get_api_ver(api_get_version=api_get_version) + fw_ver = await self.get_fw_ver( + api_get_version=api_get_version, api_summary=api_summary + ) + return miner_version(api_ver, fw_ver) + + async def get_api_ver(self, api_get_version: dict = None) -> Optional[str]: + # Check to see if the version info is already cached + if self.api_ver: + return self.api_ver + if not api_get_version: try: api_get_version = await self.api.get_version() @@ -332,9 +353,13 @@ class BTMiner(BaseMiner): return self.api_ver - async def _get_fw_ver( + async def get_fw_ver( self, api_get_version: dict = None, api_summary: dict = None ) -> Optional[str]: + # Check to see if the version info is already cached + if self.fw_ver: + return self.fw_ver + if not api_get_version: try: api_get_version = await self.api.get_version() @@ -362,12 +387,12 @@ class BTMiner(BaseMiner): self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace( "'", "" ) - except LookupError: + except (KeyError, IndexError): pass return self.fw_ver - async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: + async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: hostname = None if not api_get_miner_info: try: @@ -383,7 +408,7 @@ class BTMiner(BaseMiner): 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: @@ -394,10 +419,10 @@ class BTMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except LookupError: + except (KeyError, IndexError): pass - async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: + async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) @@ -427,12 +452,12 @@ class BTMiner(BaseMiner): hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].serial_number = board["PCB SN"] hashboards[board["ASC"]].missing = False - except LookupError: + except (KeyError, IndexError): pass return hashboards - async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]: + async def get_env_temp(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -442,10 +467,10 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Env Temp"] - except LookupError: + except (KeyError, IndexError): pass - async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: + async def get_wattage(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -456,10 +481,10 @@ class BTMiner(BaseMiner): try: wattage = api_summary["SUMMARY"][0]["Power"] return wattage if not wattage == -1 else None - except LookupError: + except (KeyError, IndexError): pass - async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: + async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -469,10 +494,10 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Power Limit"] - except LookupError: + except (KeyError, IndexError): pass - async def _get_fans( + async def get_fans( self, api_summary: dict = None, api_get_psu: dict = None ) -> List[Fan]: if not api_summary: @@ -481,20 +506,20 @@ class BTMiner(BaseMiner): except APIError: pass - fans = [Fan() for _ in range(self.expected_fans)] + fans = [Fan() for _ in range(self.fan_count)] if api_summary: try: - if self.expected_fans > 0: + if self.fan_count > 0: fans = [ Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)), ] - except LookupError: + except (KeyError, IndexError): pass return fans - async def _get_fan_psu( + async def get_fan_psu( self, api_summary: dict = None, api_get_psu: dict = None ) -> Optional[int]: if not api_summary: @@ -506,7 +531,7 @@ class BTMiner(BaseMiner): if api_summary: try: return int(api_summary["SUMMARY"][0]["Power Fanspeed"]) - except LookupError: + except (KeyError, IndexError): pass if not api_get_psu: @@ -521,26 +546,11 @@ class BTMiner(BaseMiner): except (KeyError, TypeError): pass - async def _get_errors( + async def get_errors( self, api_summary: dict = None, api_get_error_code: dict = None ) -> List[MinerErrorData]: errors = [] - if not api_summary and not api_get_error_code: - try: - api_summary = await self.api.summary() - except APIError: - pass - - if api_summary: - try: - for i in range(api_summary["SUMMARY"][0]["Error Code Count"]): - err = api_summary["SUMMARY"][0].get(f"Error Code {i}") - if err: - errors.append(WhatsminerError(error_code=err)) - except (LookupError, ValueError, TypeError): - pass - - if not api_get_error_code: + if not api_get_error_code and not api_summary: try: api_get_error_code = await self.api.get_error_code() except APIError: @@ -554,9 +564,24 @@ class BTMiner(BaseMiner): else: errors.append(WhatsminerError(error_code=int(err))) + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + for i in range(api_summary["SUMMARY"][0]["Error Code Count"]): + err = api_summary["SUMMARY"][0].get(f"Error Code {i}") + if err: + errors.append(WhatsminerError(error_code=err)) + except (KeyError, IndexError, ValueError, TypeError): + pass + return errors - async def _get_expected_hashrate(self, api_summary: dict = None): + async def get_expected_hashrate(self, api_summary: dict = None): if not api_summary: try: api_summary = await self.api.summary() @@ -568,10 +593,10 @@ class BTMiner(BaseMiner): expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] if expected_hashrate: return round(expected_hashrate / 1000, 2) - except LookupError: + except (KeyError, IndexError): pass - async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool: + async def get_fault_light(self, api_get_miner_info: dict = None) -> bool: if not api_get_miner_info: try: api_get_miner_info = await self.api.get_miner_info() @@ -609,7 +634,7 @@ class BTMiner(BaseMiner): async def set_hostname(self, hostname: str): await self.api.set_hostname(hostname) - async def _is_mining(self, api_status: dict = None) -> Optional[bool]: + async def is_mining(self, api_status: dict = None) -> Optional[bool]: if not api_status: try: api_status = await self.api.status() @@ -628,7 +653,7 @@ class BTMiner(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() diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 9b4ce6b9..216087b5 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -14,6 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ +import logging import re from typing import List, Optional @@ -27,43 +28,42 @@ 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")] - ), - str(DataOptions.HOSTNAME): DataFunction( - "_get_hostname", [RPCAPICommand("api_version", "version")] + "get_fw_ver", [RPCAPICommand("api_version", "version")] ), + 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 +174,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 +192,7 @@ class CGMinerAvalon(CGMiner): except (KeyError, ValueError): pass - async def _get_hostname(self, mac: str = None) -> Optional[str]: + async def get_hostname(self) -> Optional[str]: return None # if not mac: # mac = await self.get_mac() @@ -200,7 +200,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() @@ -210,10 +210,10 @@ class CGMinerAvalon(CGMiner): if api_devs: try: return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2) - except (LookupError, ValueError, TypeError): + 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) @@ -229,7 +229,7 @@ class CGMinerAvalon(CGMiner): try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): return hashboards for board in range(self.expected_hashboards): @@ -261,7 +261,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() @@ -273,10 +273,10 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return round(float(parsed_stats["GHSmm"]) / 1000, 2) - except (LookupError, ValueError, TypeError): + 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() @@ -288,13 +288,13 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return float(parsed_stats["Temp"]) - except (LookupError, ValueError, TypeError): + 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() @@ -306,17 +306,17 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return int(parsed_stats["MPO"]) - except (LookupError, ValueError, TypeError): + 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() except APIError: pass - fans_data = [Fan() for _ in range(self.expected_fans)] + fans_data = [Fan() for _ in range(self.fan_count)] if api_stats: try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] @@ -324,17 +324,17 @@ class CGMinerAvalon(CGMiner): except LookupError: return fans_data - for fan in range(self.expected_fans): + for fan in range(self.fan_count): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): 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: @@ -349,7 +349,7 @@ class CGMinerAvalon(CGMiner): parsed_stats = self.parse_stats(unparsed_stats) led = int(parsed_stats["Led"]) return True if led == 1 else False - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): pass try: @@ -363,5 +363,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]: return None diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 1ce6c12b..600eb41c 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -14,12 +14,73 @@ # limitations under the License. - # ------------------------------------------------------------------------------ +from typing import List, Optional + +from pyasic.data import HashBoard from pyasic.miners.backends import BMMiner +from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPICommand + +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")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction( + "get_env_temp", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction("get_wattage_limit"), + str(DataOptions.FANS): DataFunction( + "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.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) class Hiveon(BMMiner): def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip, api_ver) + self.pwd = "admin" # static data self.api_type = "Hiveon" - self.fw_str = "Hive" + # 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]: + pass + + async def get_wattage(self, api_stats: dict = None) -> Optional[int]: + pass + + 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 new file mode 100644 index 00000000..192bfeb6 --- /dev/null +++ b/pyasic/miners/backends/innosilicon.py @@ -0,0 +1,404 @@ +# ------------------------------------------------------------------------------ +# Copyright 2022 Upstream Data Inc - +# - +# Licensed under the Apache License, Version 2.0 (the "License"); - +# you may not use this file except in compliance with the License. - +# You may obtain a copy of the License at - +# - +# http://www.apache.org/licenses/LICENSE-2.0 - +# - +# Unless required by applicable law or agreed to in writing, software - +# distributed under the License is distributed on an "AS IS" BASIS, - +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - +# See the License for the specific language governing permissions and - +# limitations under the License. - +# ------------------------------------------------------------------------------ +from typing import List, Optional + +from pyasic.config import MinerConfig +from pyasic.data import Fan, HashBoard +from pyasic.data.error_codes import MinerErrorData +from pyasic.data.error_codes.innosilicon import InnosiliconError +from pyasic.errors import APIError +from pyasic.miners.backends import CGMiner +from pyasic.miners.base import ( + DataFunction, + DataLocations, + DataOptions, + RPCAPICommand, + WebAPICommand, +) +from pyasic.web.innosilicon import InnosiliconWebAPI + +INNOSILICON_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "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")] + ), + str(DataOptions.FW_VERSION): DataFunction( + "get_fw_ver", [RPCAPICommand("api_version", "version")] + ), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", + [ + RPCAPICommand("api_summary", "summary"), + WebAPICommand("web_get_all", "getAll"), + ], + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", + [ + RPCAPICommand("api_stats", "stats"), + WebAPICommand("web_get_all", "getAll"), + ], + ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", + [ + WebAPICommand("web_get_all", "getAll"), + RPCAPICommand("api_stats", "stats"), + ], + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", + [ + WebAPICommand("web_get_all", "getAll"), + ], + ), + str(DataOptions.FANS): DataFunction( + "get_fans", + [ + WebAPICommand("web_get_all", "getAll"), + ], + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "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.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_stats", "stats")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) + + +class Innosilicon(CGMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip, api_ver=api_ver) + # interfaces + self.web = InnosiliconWebAPI(ip) + + # static data + # data gathering locations + self.data_locations = INNOSILICON_DATA_LOC + # autotuning/shutdown support + self.supports_shutdown = True + + # data storage + self.api_ver = api_ver + + async def fault_light_on(self) -> bool: + return False + + async def fault_light_off(self) -> bool: + return False + + async def get_config(self) -> MinerConfig: + # get pool data + try: + pools = await self.web.pools() + except APIError: + return self.config + + self.config = MinerConfig.from_inno(pools) + return self.config + + async def reboot(self) -> bool: + try: + data = await self.web.reboot() + except APIError: + pass + else: + return data["success"] + + async def restart_cgminer(self) -> bool: + try: + data = await self.web.restart_cgminer() + except APIError: + pass + else: + return data["success"] + + async def restart_backend(self) -> bool: + return await self.restart_cgminer() + + async def stop_mining(self) -> bool: + return False + # data = await self.web.poweroff() + # try: + # return data["success"] + # except KeyError: + # return False + + async def resume_mining(self) -> bool: + return False + # data = await self.web.restart_cgminer() + # print(data) + # try: + # return data["success"] + # except KeyError: + # return False + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + self.config = config + await self.web.update_pools(config.as_inno(user_suffix=user_suffix)) + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## + + async def get_mac( + self, web_get_all: dict = None, web_overview: dict = None + ) -> Optional[str]: + if web_get_all: + web_get_all = web_get_all["all"] + + if not web_get_all and not web_overview: + try: + web_overview = await self.web.overview() + except APIError: + pass + + if web_get_all: + try: + mac = web_get_all["mac"] + return mac.upper() + except KeyError: + pass + + if web_overview: + try: + mac = web_overview["version"]["ethaddr"] + return mac.upper() + except KeyError: + pass + + async def get_hashrate( + self, api_summary: dict = None, web_get_all: dict = None + ) -> Optional[float]: + if web_get_all: + web_get_all = web_get_all["all"] + + if not api_summary and not web_get_all: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if web_get_all: + try: + if "Hash Rate H" in web_get_all["total_hash"].keys(): + return round( + float(web_get_all["total_hash"]["Hash Rate H"] / 1000000000000), + 2, + ) + elif "Hash Rate" in web_get_all["total_hash"].keys(): + return round( + float(web_get_all["total_hash"]["Hash Rate"] / 1000000), 5 + ) + except KeyError: + pass + + if api_summary: + try: + return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) + except (KeyError, IndexError): + pass + + async def get_hashboards( + self, api_stats: dict = None, web_get_all: dict = None + ) -> List[HashBoard]: + if web_get_all: + web_get_all = web_get_all["all"] + + hashboards = [ + HashBoard(slot=i, expected_chips=self.expected_chips) + for i in range(self.expected_hashboards) + ] + + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + if not web_get_all: + try: + web_get_all = await self.web.get_all() + except APIError: + pass + else: + web_get_all = web_get_all["all"] + + if api_stats: + if api_stats.get("STATS"): + for board in api_stats["STATS"]: + try: + idx = board["Chain ID"] + chips = board["Num active chips"] + except KeyError: + pass + else: + hashboards[idx].chips = chips + hashboards[idx].missing = False + + if web_get_all: + if web_get_all.get("chain"): + for board in web_get_all["chain"]: + idx = board.get("ASC") + if idx is not None: + temp = board.get("Temp min") + if temp: + hashboards[idx].temp = round(temp) + + hashrate = board.get("Hash Rate H") + if hashrate: + hashboards[idx].hashrate = round( + hashrate / 1000000000000, 2 + ) + + chip_temp = board.get("Temp max") + if chip_temp: + hashboards[idx].chip_temp = round(chip_temp) + + return hashboards + + async def get_wattage( + self, web_get_all: dict = None, api_stats: dict = None + ) -> Optional[int]: + if web_get_all: + web_get_all = web_get_all["all"] + + if not web_get_all: + try: + web_get_all = await self.web.get_all() + except APIError: + pass + else: + web_get_all = web_get_all["all"] + + if web_get_all: + try: + return web_get_all["power"] + except KeyError: + pass + + if not api_stats: + try: + api_stats = await self.api.stats() + except APIError: + pass + + if api_stats: + if api_stats.get("STATS"): + for board in api_stats["STATS"]: + try: + wattage = board["power"] + except KeyError: + pass + else: + wattage = int(wattage) + return wattage + + async def get_fans(self, web_get_all: dict = None) -> List[Fan]: + if web_get_all: + web_get_all = web_get_all["all"] + + if not web_get_all: + try: + web_get_all = await self.web.get_all() + except APIError: + pass + else: + web_get_all = web_get_all["all"] + + fans = [Fan() for _ in range(self.fan_count)] + if web_get_all: + try: + spd = web_get_all["fansSpeed"] + except KeyError: + pass + else: + round((int(spd) * 6000) / 100) + for i in range(self.fan_count): + fans[i].speed = spd + + return fans + + async def get_errors( + self, web_get_error_detail: dict = None + ) -> List[MinerErrorData]: + errors = [] + if not web_get_error_detail: + try: + web_get_error_detail = await self.web.get_error_detail() + except APIError: + pass + + if web_get_error_detail: + try: + # only 1 error? + # TODO: check if this should be a loop, can't remember. + err = web_get_error_detail["code"] + except KeyError: + pass + else: + err = int(err) + if not err == 0: + errors.append(InnosiliconError(error_code=err)) + return errors + + async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: + if web_get_all: + web_get_all = web_get_all["all"] + + if not web_get_all: + try: + web_get_all = await self.web.get_all() + except APIError: + pass + else: + web_get_all = web_get_all["all"] + + if web_get_all: + try: + level = web_get_all["running_mode"]["level"] + except KeyError: + pass + else: + # this is very possibly not correct. + level = int(level) + limit = 1250 + (250 * level) + return limit + + async def get_expected_hashrate(self) -> Optional[float]: + pass diff --git a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py index 7f626bf0..54394037 100644 --- a/pyasic/miners/innosilicon/cgminer/A10X/A10X.py +++ b/pyasic/miners/innosilicon/cgminer/A10X/A10X.py @@ -13,320 +13,10 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -import logging -from typing import List, Optional -from pyasic.config import MinerConfig -from pyasic.data import Fan, HashBoard -from pyasic.data.error_codes import InnosiliconError, MinerErrorData -from pyasic.errors import APIError -from pyasic.miners.backends import CGMiner +from pyasic.miners.backends.innosilicon import Innosilicon from pyasic.miners.types import A10X -from pyasic.web.inno import InnosiliconWebAPI -class CGMinerA10X(CGMiner, A10X): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip, api_ver=api_ver) - self.ip = ip - self.web = InnosiliconWebAPI(ip) - - async def fault_light_on(self) -> bool: - return False - - async def fault_light_off(self) -> bool: - return False - - async def get_config(self, web_pools: dict = None) -> MinerConfig: - if not web_pools: - try: - web_pools = await self.web.pools() - except APIError as e: - logging.warning(e) - - if web_pools: - if "pools" in web_pools.keys(): - cfg = MinerConfig().from_raw(web_pools) - self.config = cfg - return self.config - - async def reboot(self) -> bool: - try: - data = await self.web.reboot() - except APIError: - pass - else: - return data["success"] - - async def restart_cgminer(self) -> bool: - try: - data = await self.web.restart_cgminer() - except APIError: - pass - else: - return data["success"] - - async def restart_backend(self) -> bool: - return await self.restart_cgminer() - - async def stop_mining(self) -> bool: - return False - # data = await self.web.poweroff() - # try: - # return data["success"] - # except KeyError: - # return False - - async def resume_mining(self) -> bool: - return False - # data = await self.web.restart_cgminer() - # print(data) - # try: - # return data["success"] - # except KeyError: - # return False - - async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: - pass - # doesnt work for some reason - # self.config = config - # await self.web.update_pools(config.as_inno(user_suffix=user_suffix)) - - ################################################## - ### DATA GATHERING FUNCTIONS (get_{some_data}) ### - ################################################## - - async def get_mac( - self, web_get_all: dict = None, web_overview: dict = None - ) -> Optional[str]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all and not web_overview: - try: - web_overview = await self.web.overview() - except APIError: - pass - - if web_get_all: - try: - mac = web_get_all["mac"] - return mac.upper() - except KeyError: - pass - - if web_overview: - try: - mac = web_overview["version"]["ethaddr"] - return mac.upper() - except KeyError: - pass - - async def get_hashrate( - self, api_summary: dict = None, web_get_all: dict = None - ) -> Optional[float]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not api_summary and not web_get_all: - try: - api_summary = await self.api.summary() - except APIError: - pass - - if web_get_all: - try: - return round(float(web_get_all["total_hash"]["Hash Rate"] / 1000000), 5) - except KeyError: - pass - - if api_summary: - try: - return round( - float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000000000), 5 - ) - except LookupError: - pass - - async def get_hashboards( - self, api_stats: dict = None, web_get_all: dict = None - ) -> List[HashBoard]: - hashboards = [ - HashBoard(slot=i, expected_chips=self.expected_chips) - for i in range(self.expected_hashboards) - ] - if web_get_all: - web_get_all = web_get_all["all"] - - if not api_stats: - try: - api_stats = await self.api.stats() - except APIError: - pass - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if api_stats: - if api_stats.get("STATS"): - for board in api_stats["STATS"]: - try: - idx = board["Chain ID"] - chips = board["Num active chips"] - except KeyError: - pass - else: - hashboards[idx].chips = chips - hashboards[idx].missing = False - - if web_get_all: - if web_get_all.get("chain"): - for board in web_get_all["chain"]: - idx = board.get("ASC") - if idx is not None: - temp = board.get("Temp min") - if temp: - hashboards[idx].temp = round(temp) - - hashrate = board.get("Hash Rate H") - if hashrate: - hashboards[idx].hashrate = round( - hashrate / 1000000000000, 5 - ) - - chip_temp = board.get("Temp max") - if chip_temp: - hashboards[idx].chip_temp = round(chip_temp) - - return hashboards - - async def get_wattage( - self, web_get_all: dict = None, api_stats: dict = None - ) -> Optional[int]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if web_get_all: - try: - return web_get_all["power"] - except KeyError: - pass - - if not api_stats: - try: - api_stats = await self.api.stats() - except APIError: - pass - - if api_stats: - if api_stats.get("STATS"): - for board in api_stats["STATS"]: - try: - wattage = board["power"] - except KeyError: - pass - else: - wattage = int(wattage) - return wattage - - async def get_fans(self, web_get_all: dict = None) -> List[Fan]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - fans = [Fan() for _ in range(self.expected_fans)] - if web_get_all: - try: - spd = web_get_all["fansSpeed"] - except KeyError: - pass - else: - round((int(spd) * 6000) / 100) - for i in range(self.expected_fans): - fans[i].speed = spd - - return fans - - async def get_errors( - self, web_get_error_detail: dict = None - ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality - errors = [] - if not web_get_error_detail: - try: - web_get_error_detail = await self.web.get_error_detail() - except APIError: - pass - - if web_get_error_detail: - try: - # only 1 error? - # TODO: check if this should be a loop, can't remember. - err = web_get_error_detail["code"] - except KeyError: - pass - else: - err = int(err) - if not err == 0: - errors.append(InnosiliconError(error_code=err)) - return errors - - async def get_fw_ver(self, api_version: dict = None) -> Optional[str]: - if self.fw_ver: - return self.fw_ver - - if not api_version: - try: - api_version = await self.api.version() - except APIError: - pass - - if api_version: - try: - self.fw_ver = api_version["VERSION"][0]["CGMiner"].split("-")[-1:][0] - except LookupError: - pass - - return self.fw_ver - - async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if web_get_all: - try: - level = web_get_all["running_mode"]["level"] - except KeyError: - pass - else: - # this is very possibly not correct. - level = int(level) - limit = 1250 + (250 * level) - return limit +class InnosiliconA10X(Innosilicon, A10X): + pass diff --git a/pyasic/miners/innosilicon/cgminer/A10X/__init__.py b/pyasic/miners/innosilicon/cgminer/A10X/__init__.py index 9d7075b9..e18c86d7 100644 --- a/pyasic/miners/innosilicon/cgminer/A10X/__init__.py +++ b/pyasic/miners/innosilicon/cgminer/A10X/__init__.py @@ -14,4 +14,4 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from .A10X import CGMinerA10X +from .A10X import InnosiliconA10X diff --git a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py index 593ef039..5e34366a 100644 --- a/pyasic/miners/innosilicon/cgminer/T3X/T3H.py +++ b/pyasic/miners/innosilicon/cgminer/T3X/T3H.py @@ -13,280 +13,10 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import List, Optional -from pyasic.config import MinerConfig -from pyasic.data import Fan, HashBoard -from pyasic.data.error_codes import InnosiliconError, MinerErrorData -from pyasic.errors import APIError -from pyasic.miners.backends import CGMiner +from pyasic.miners.backends.innosilicon import Innosilicon from pyasic.miners.types import T3HPlus -from pyasic.web.inno import InnosiliconWebAPI -class CGMinerT3HPlus(CGMiner, T3HPlus): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip, api_ver=api_ver) - self.ip = ip - self.web = InnosiliconWebAPI(ip) - - async def fault_light_on(self) -> bool: - return False - - async def fault_light_off(self) -> bool: - return False - - async def get_config(self, api_pools: dict = None) -> MinerConfig: - # get pool data - try: - pools = await self.api.pools() - except APIError: - return self.config - - self.config = MinerConfig.from_api(pools) - return self.config - - async def reboot(self) -> bool: - try: - data = await self.web.reboot() - except APIError: - pass - else: - return data["success"] - - async def restart_cgminer(self) -> bool: - try: - data = await self.web.restart_cgminer() - except APIError: - pass - else: - return data["success"] - - async def restart_backend(self) -> bool: - return await self.restart_cgminer() - - async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: - self.config = config - await self.web.update_pools(config.as_inno(user_suffix=user_suffix)) - - ################################################## - ### DATA GATHERING FUNCTIONS (get_{some_data}) ### - ################################################## - - async def get_mac( - self, web_get_all: dict = None, web_overview: dict = None - ) -> Optional[str]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all and not web_overview: - try: - web_overview = await self.web.overview() - except APIError: - pass - - if web_get_all: - try: - mac = web_get_all["mac"] - return mac.upper() - except KeyError: - pass - - if web_overview: - try: - mac = web_overview["version"]["ethaddr"] - return mac.upper() - except KeyError: - pass - - async def get_hashrate( - self, api_summary: dict = None, web_get_all: dict = None - ) -> Optional[float]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not api_summary and not web_get_all: - try: - api_summary = await self.api.summary() - except APIError: - pass - - if web_get_all: - try: - return round( - float(web_get_all["total_hash"]["Hash Rate H"] / 1000000000000), 2 - ) - except KeyError: - pass - - if api_summary: - try: - return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except LookupError: - pass - - async def get_hashboards( - self, api_stats: dict = None, web_get_all: dict = None - ) -> List[HashBoard]: - if web_get_all: - web_get_all = web_get_all["all"] - - hashboards = [ - HashBoard(slot=i, expected_chips=self.expected_chips) - for i in range(self.expected_hashboards) - ] - - if not api_stats: - try: - api_stats = await self.api.stats() - except APIError: - pass - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if api_stats: - if api_stats.get("STATS"): - for board in api_stats["STATS"]: - try: - idx = board["Chain ID"] - chips = board["Num active chips"] - except KeyError: - pass - else: - hashboards[idx].chips = chips - hashboards[idx].missing = False - - if web_get_all: - if web_get_all.get("chain"): - for board in web_get_all["chain"]: - idx = board.get("ASC") - if idx is not None: - temp = board.get("Temp min") - if temp: - hashboards[idx].temp = round(temp) - - hashrate = board.get("Hash Rate H") - if hashrate: - hashboards[idx].hashrate = round( - hashrate / 1000000000000, 2 - ) - - chip_temp = board.get("Temp max") - if chip_temp: - hashboards[idx].chip_temp = round(chip_temp) - - return hashboards - - async def get_wattage( - self, web_get_all: dict = None, api_stats: dict = None - ) -> Optional[int]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if web_get_all: - try: - return web_get_all["power"] - except KeyError: - pass - - if not api_stats: - try: - api_stats = await self.api.stats() - except APIError: - pass - - if api_stats: - if api_stats.get("STATS"): - for board in api_stats["STATS"]: - try: - wattage = board["power"] - except KeyError: - pass - else: - wattage = int(wattage) - return wattage - - async def get_fans(self, web_get_all: dict = None) -> List[Fan]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - fans = [Fan() for _ in range(self.fan_count)] - if web_get_all: - try: - spd = web_get_all["fansSpeed"] - except KeyError: - pass - else: - round((int(spd) * 6000) / 100) - for i in range(self.fan_count): - fans[i].speed = spd - - return fans - - async def get_errors( - self, web_get_error_detail: dict = None - ) -> List[MinerErrorData]: # noqa: named this way for automatic functionality - errors = [] - if not web_get_error_detail: - try: - web_get_error_detail = await self.web.get_error_detail() - except APIError: - pass - - if web_get_error_detail: - try: - # only 1 error? - # TODO: check if this should be a loop, can't remember. - err = web_get_error_detail["code"] - except KeyError: - pass - else: - err = int(err) - if not err == 0: - errors.append(InnosiliconError(error_code=err)) - return errors - - async def get_wattage_limit(self, web_get_all: dict = None) -> Optional[int]: - if web_get_all: - web_get_all = web_get_all["all"] - - if not web_get_all: - try: - web_get_all = await self.web.get_all() - except APIError: - pass - else: - web_get_all = web_get_all["all"] - - if web_get_all: - try: - level = web_get_all["running_mode"]["level"] - except KeyError: - pass - else: - # this is very possibly not correct. - level = int(level) - limit = 1250 + (250 * level) - return limit +class InnosiliconT3HPlus(Innosilicon, T3HPlus): + pass diff --git a/pyasic/miners/innosilicon/cgminer/T3X/__init__.py b/pyasic/miners/innosilicon/cgminer/T3X/__init__.py index f2a2eb8b..ebfb0337 100644 --- a/pyasic/miners/innosilicon/cgminer/T3X/__init__.py +++ b/pyasic/miners/innosilicon/cgminer/T3X/__init__.py @@ -14,4 +14,4 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from .T3H import CGMinerT3HPlus +from .T3H import InnosiliconT3HPlus diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index eae3b83e..725d091f 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -326,8 +326,8 @@ MINER_CLASSES = { }, MinerTypes.INNOSILICON: { None: CGMiner, - "T3H+": CGMinerT3HPlus, - "A10X": CGMinerA10X, + "T3H+": InnosiliconT3HPlus, + "A10X": InnosiliconA10X, }, MinerTypes.GOLDSHELL: { None: BFGMiner, diff --git a/pyasic/web/inno.py b/pyasic/web/innosilicon.py similarity index 100% rename from pyasic/web/inno.py rename to pyasic/web/innosilicon.py diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index 82d8a246..e0e700a6 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -72,6 +72,31 @@ class MinersTest(unittest.TestCase): ) self.assertEqual(miner_keys, keys) + 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 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}", + miner_model=miner_model, + miner_api=miner_api, + ): + func = getattr(miner, data_point["cmd"]) + signature = inspect.signature(func) + parameters = signature.parameters + param_names = list(parameters.keys()) + for arg in ["kwargs", "args"]: + try: + param_names.remove(arg) + except ValueError: + pass + self.assertEqual( + set(param_names), + set([k["name"] for k in data_point["kwargs"]]), + ) + if __name__ == "__main__": unittest.main() From a8c45cb95d1776963e62226fc36fb7fed6cd0e23 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 11:33:44 -0700 Subject: [PATCH 24/36] refactor: remove parameters from `get_{x}` functions and move them to `_get_{x}(**params)`. Add `miner.fw_str`, and `miner.raw_model`. Remove `model` from `get_data` exclude. Swap `fan_count` to `expected_fans`. --- pyasic/miners/backends/btminer.py | 89 ++++++++++-------------- pyasic/miners/backends/cgminer_avalon.py | 61 ++++++++-------- pyasic/miners/backends/hiveon.py | 6 +- 3 files changed, 66 insertions(+), 90 deletions(-) diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index de3ef732..f647077e 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -35,74 +35,73 @@ from pyasic.miners.base import ( BTMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( - "get_mac", + "_get_mac", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_miner_info", "get_miner_info"), ], ), - str(DataOptions.MODEL): DataFunction("get_model"), str(DataOptions.API_VERSION): DataFunction( - "get_api_ver", [RPCAPICommand("api_get_version", "get_version")] + "_get_api_ver", [RPCAPICommand("api_get_version", "get_version")] ), str(DataOptions.FW_VERSION): DataFunction( - "get_fw_ver", + "_get_fw_ver", [ RPCAPICommand("api_get_version", "get_version"), RPCAPICommand("api_summary", "summary"), ], ), str(DataOptions.HOSTNAME): DataFunction( - "get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] + "_get_hostname", [RPCAPICommand("api_get_miner_info", "get_miner_info")] ), 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_summary", "summary")] + "_get_expected_hashrate", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.HASHBOARDS): DataFunction( - "get_hashboards", [RPCAPICommand("api_devs", "devs")] + "_get_hashboards", [RPCAPICommand("api_devs", "devs")] ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction( - "get_env_temp", [RPCAPICommand("api_summary", "summary")] + "_get_env_temp", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE): DataFunction( - "get_wattage", [RPCAPICommand("api_summary", "summary")] + "_get_wattage", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.WATTAGE_LIMIT): DataFunction( - "get_wattage_limit", [RPCAPICommand("api_summary", "summary")] + "_get_wattage_limit", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.FANS): DataFunction( - "get_fans", + "_get_fans", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.FAN_PSU): DataFunction( - "get_fan_psu", + "_get_fan_psu", [ RPCAPICommand("api_summary", "summary"), RPCAPICommand("api_get_psu", "get_psu"), ], ), str(DataOptions.ERRORS): DataFunction( - "get_errors", + "_get_errors", [ RPCAPICommand("api_get_error_code", "get_error_code"), RPCAPICommand("api_summary", "summary"), ], ), str(DataOptions.FAULT_LIGHT): DataFunction( - "get_fault_light", + "_get_fault_light", [RPCAPICommand("api_get_miner_info", "get_miner_info")], ), str(DataOptions.IS_MINING): DataFunction( - "is_mining", [RPCAPICommand("api_status", "status")] + "_is_mining", [RPCAPICommand("api_status", "status")] ), str(DataOptions.UPTIME): DataFunction( - "get_uptime", [RPCAPICommand("api_summary", "summary")] + "_get_uptime", [RPCAPICommand("api_summary", "summary")] ), str(DataOptions.CONFIG): DataFunction("get_config"), } @@ -243,7 +242,7 @@ class BTMiner(BaseMiner): else: cfg = MinerConfig() - is_mining = await self.is_mining(status) + is_mining = await self._is_mining(status) if not is_mining: cfg.mining_mode = MiningModeConfig.sleep() return cfg @@ -287,7 +286,7 @@ class BTMiner(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac( + async def _get_mac( self, api_summary: dict = None, api_get_miner_info: dict = None ) -> Optional[str]: if not api_get_miner_info: @@ -316,21 +315,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_version( - self, api_get_version: dict = None, api_summary: dict = None - ) -> Tuple[Optional[str], Optional[str]]: - miner_version = namedtuple("MinerVersion", "api_ver fw_ver") - api_ver = await self.get_api_ver(api_get_version=api_get_version) - fw_ver = await self.get_fw_ver( - api_get_version=api_get_version, api_summary=api_summary - ) - return miner_version(api_ver, fw_ver) - - async def get_api_ver(self, api_get_version: dict = None) -> Optional[str]: - # Check to see if the version info is already cached - if self.api_ver: - return self.api_ver - + async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: if not api_get_version: try: api_get_version = await self.api.get_version() @@ -353,13 +338,9 @@ class BTMiner(BaseMiner): return self.api_ver - async def get_fw_ver( + async def _get_fw_ver( self, api_get_version: dict = None, api_summary: dict = None ) -> Optional[str]: - # Check to see if the version info is already cached - if self.fw_ver: - return self.fw_ver - if not api_get_version: try: api_get_version = await self.api.get_version() @@ -392,7 +373,7 @@ class BTMiner(BaseMiner): return self.fw_ver - async def get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: + async def _get_hostname(self, api_get_miner_info: dict = None) -> Optional[str]: hostname = None if not api_get_miner_info: try: @@ -408,7 +389,7 @@ class BTMiner(BaseMiner): 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 +403,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: + async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: hashboards = [ HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) @@ -457,7 +438,7 @@ class BTMiner(BaseMiner): return hashboards - async def get_env_temp(self, api_summary: dict = None) -> Optional[float]: + async def _get_env_temp(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -470,7 +451,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_wattage(self, api_summary: dict = None) -> Optional[int]: + async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -484,7 +465,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: + async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: if not api_summary: try: api_summary = await self.api.summary() @@ -497,7 +478,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_fans( + async def _get_fans( self, api_summary: dict = None, api_get_psu: dict = None ) -> List[Fan]: if not api_summary: @@ -506,10 +487,10 @@ class BTMiner(BaseMiner): except APIError: pass - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if api_summary: try: - if self.fan_count > 0: + if self.expected_fans > 0: fans = [ Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)), @@ -519,7 +500,7 @@ class BTMiner(BaseMiner): return fans - async def get_fan_psu( + async def _get_fan_psu( self, api_summary: dict = None, api_get_psu: dict = None ) -> Optional[int]: if not api_summary: @@ -546,7 +527,7 @@ class BTMiner(BaseMiner): except (KeyError, TypeError): pass - async def get_errors( + async def _get_errors( self, api_summary: dict = None, api_get_error_code: dict = None ) -> List[MinerErrorData]: errors = [] @@ -581,7 +562,7 @@ class BTMiner(BaseMiner): return errors - async def get_expected_hashrate(self, api_summary: dict = None): + async def _get_expected_hashrate(self, api_summary: dict = None): if not api_summary: try: api_summary = await self.api.summary() @@ -596,7 +577,7 @@ class BTMiner(BaseMiner): except (KeyError, IndexError): pass - async def get_fault_light(self, api_get_miner_info: dict = None) -> bool: + async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool: if not api_get_miner_info: try: api_get_miner_info = await self.api.get_miner_info() @@ -634,7 +615,7 @@ class BTMiner(BaseMiner): async def set_hostname(self, hostname: str): await self.api.set_hostname(hostname) - async def is_mining(self, api_status: dict = None) -> Optional[bool]: + async def _is_mining(self, api_status: dict = None) -> Optional[bool]: if not api_status: try: api_status = await self.api.status() @@ -653,7 +634,7 @@ class BTMiner(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() diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 216087b5..51e34ff7 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,14 +308,14 @@ 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() except APIError: pass - fans_data = [Fan() for _ in range(self.fan_count)] + fans_data = [Fan() for _ in range(self.expected_fans)] if api_stats: try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] @@ -324,17 +323,17 @@ class CGMinerAvalon(CGMiner): except LookupError: return fans_data - for fan in range(self.fan_count): + for fan in range(self.expected_fans): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) except (IndexError, KeyError, ValueError, TypeError): 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,7 +362,7 @@ 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]: diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 600eb41c..7cc6cfd7 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -68,14 +68,10 @@ class Hiveon(BMMiner): self.pwd = "admin" # static data self.api_type = "Hiveon" + self.fw_str = "Hive" # 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]: pass From 60f3687d0236ca286b518e74b6d29f8355524f17 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 15:00:48 -0700 Subject: [PATCH 25/36] refactor: optimize imports. --- pyasic/miners/backends/btminer.py | 4 +--- pyasic/miners/backends/cgminer_avalon.py | 1 - pyasic/miners/backends/epic.py | 5 ++--- pyasic/miners/backends/hiveon.py | 1 - 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index f647077e..39fd3786 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -15,8 +15,7 @@ # ------------------------------------------------------------------------------ import logging -from collections import namedtuple -from typing import List, Optional, Tuple +from typing import List, Optional from pyasic.API.btminer import BTMinerAPI from pyasic.config import MinerConfig, MiningModeConfig @@ -29,7 +28,6 @@ from pyasic.miners.base import ( DataLocations, DataOptions, RPCAPICommand, - WebAPICommand, ) BTMINER_DATA_LOC = DataLocations( diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 51e34ff7..cd78e6b1 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -14,7 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import logging import re from typing import List, Optional diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 0914d0f8..83452263 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -14,10 +14,9 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -from typing import List, Optional, Tuple +from typing import List, Optional -from pyasic import MinerConfig -from pyasic.config import MinerConfig, MiningModeConfig +from pyasic.config import MinerConfig from pyasic.data import Fan, HashBoard from pyasic.data.error_codes import MinerErrorData, X19Error from pyasic.errors import APIError diff --git a/pyasic/miners/backends/hiveon.py b/pyasic/miners/backends/hiveon.py index 7cc6cfd7..40a32406 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -23,7 +23,6 @@ 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")] ), From b0337e8417df177adbbf0f5b72040bcc7d499f15 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 15:20:33 -0700 Subject: [PATCH 26/36] refactor: swap (KeyError, IndexError) for LookupError. --- pyasic/miners/antminer/hiveon/X9/T9.py | 8 +++---- pyasic/miners/backends/braiins_os.py | 28 ++++++++++++------------ pyasic/miners/backends/btminer.py | 22 +++++++++---------- pyasic/miners/backends/cgminer_avalon.py | 14 ++++++------ pyasic/miners/backends/epic.py | 2 +- 5 files changed, 37 insertions(+), 37 deletions(-) diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index f7194624..d9dd4832 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -69,14 +69,14 @@ class HiveonT9(Hiveon, T9): hashboards[board].chip_temp = api_stats["STATS"][1][ f"temp2_{chipset}" ] - except (KeyError, IndexError): + except LookupError: pass else: hashboards[board].missing = False try: hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"] chips += api_stats["STATS"][1][f"chain_acn{chipset}"] - except (KeyError, IndexError): + except LookupError: pass hashboards[board].hashrate = round(hashrate / 1000, 2) hashboards[board].chips = chips @@ -94,7 +94,7 @@ class HiveonT9(Hiveon, T9): boards = api_stats.get("STATS") try: wattage_raw = boards[1]["chain_power"] - except (KeyError, IndexError): + except LookupError: pass else: # parse wattage position out of raw data @@ -119,7 +119,7 @@ class HiveonT9(Hiveon, T9): env_temp = api_stats["STATS"][1][f"temp3_{chipset}"] if not env_temp == 0: env_temp_list.append(int(env_temp)) - except (KeyError, IndexError): + except LookupError: pass if not env_temp_list == []: diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 7307233d..84df4a05 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -478,7 +478,7 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -580,7 +580,7 @@ class BOSMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards( @@ -642,7 +642,7 @@ class BOSMiner(BaseMiner): board.temp = round(hb["temperatures"][0]["degreesC"]) if len(temps) > 1: board.chip_temp = round(hb["temperatures"][1]["degreesC"]) - except (TypeError, KeyError, ValueError, IndexError): + except (LookupError, TypeError, ValueError): pass details = hb.get("hwDetails") if details: @@ -666,15 +666,15 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except (KeyError, IndexError): + except LookupError: api_temps = None try: api_devdetails = d["devdetails"][0] - except (KeyError, IndexError): + except LookupError: api_devdetails = None try: api_devs = d["devs"][0] - except (KeyError, IndexError): + except LookupError: api_devs = None if api_temps: try: @@ -686,7 +686,7 @@ class BOSMiner(BaseMiner): board_temp = round(board["Board"]) hashboards[_id].chip_temp = chip_temp hashboards[_id].temp = board_temp - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass if api_devdetails: @@ -698,7 +698,7 @@ class BOSMiner(BaseMiner): chips = board["Chips"] hashboards[_id].chips = chips hashboards[_id].missing = False - except (IndexError, KeyError): + except LookupError: pass if api_devs: @@ -709,7 +709,7 @@ class BOSMiner(BaseMiner): _id = board["ID"] - offset hashrate = round(float(board["MHS 1m"] / 1000000), 2) hashboards[_id].hashrate = hashrate - except (IndexError, KeyError): + except LookupError: pass return hashboards @@ -750,7 +750,7 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except (KeyError, IndexError): + except LookupError: pass async def _get_wattage_limit( @@ -781,7 +781,7 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except (KeyError, IndexError): + except LookupError: pass async def _get_fans( @@ -820,7 +820,7 @@ class BOSMiner(BaseMiner): for n in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][n]["RPM"])) - except (IndexError, KeyError): + except LookupError: pass return fans return [Fan() for _ in range(self.expected_fans)] @@ -904,7 +904,7 @@ class BOSMiner(BaseMiner): _error = _error[0].lower() + _error[1:] errors.append(BraiinsOSError(f"Slot {_id} {_error}")) return errors - except (KeyError, IndexError): + except LookupError: pass async def _get_fault_light(self, graphql_fault_light: dict = None) -> bool: @@ -987,7 +987,7 @@ class BOSMiner(BaseMiner): return round( (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 ) - except (IndexError, KeyError): + except LookupError: pass async def _is_mining(self, api_devdetails: dict = None) -> Optional[bool]: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index 39fd3786..543c8419 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -310,7 +310,7 @@ class BTMiner(BaseMiner): try: mac = api_summary["SUMMARY"][0]["MAC"] return str(mac).upper() - except (KeyError, IndexError): + except LookupError: pass async def _get_api_ver(self, api_get_version: dict = None) -> Optional[str]: @@ -366,7 +366,7 @@ class BTMiner(BaseMiner): self.fw_ver = api_summary["SUMMARY"][0]["Firmware Version"].replace( "'", "" ) - except (KeyError, IndexError): + except LookupError: pass return self.fw_ver @@ -398,7 +398,7 @@ class BTMiner(BaseMiner): if api_summary: try: return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError): + except LookupError: pass async def _get_hashboards(self, api_devs: dict = None) -> List[HashBoard]: @@ -431,7 +431,7 @@ class BTMiner(BaseMiner): hashboards[board["ASC"]].chips = board["Effective Chips"] hashboards[board["ASC"]].serial_number = board["PCB SN"] hashboards[board["ASC"]].missing = False - except (KeyError, IndexError): + except LookupError: pass return hashboards @@ -446,7 +446,7 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Env Temp"] - except (KeyError, IndexError): + except LookupError: pass async def _get_wattage(self, api_summary: dict = None) -> Optional[int]: @@ -460,7 +460,7 @@ class BTMiner(BaseMiner): try: wattage = api_summary["SUMMARY"][0]["Power"] return wattage if not wattage == -1 else None - except (KeyError, IndexError): + except LookupError: pass async def _get_wattage_limit(self, api_summary: dict = None) -> Optional[int]: @@ -473,7 +473,7 @@ class BTMiner(BaseMiner): if api_summary: try: return api_summary["SUMMARY"][0]["Power Limit"] - except (KeyError, IndexError): + except LookupError: pass async def _get_fans( @@ -493,7 +493,7 @@ class BTMiner(BaseMiner): Fan(api_summary["SUMMARY"][0].get("Fan Speed In", 0)), Fan(api_summary["SUMMARY"][0].get("Fan Speed Out", 0)), ] - except (KeyError, IndexError): + except LookupError: pass return fans @@ -510,7 +510,7 @@ class BTMiner(BaseMiner): if api_summary: try: return int(api_summary["SUMMARY"][0]["Power Fanspeed"]) - except (KeyError, IndexError): + except LookupError: pass if not api_get_psu: @@ -555,7 +555,7 @@ class BTMiner(BaseMiner): err = api_summary["SUMMARY"][0].get(f"Error Code {i}") if err: errors.append(WhatsminerError(error_code=err)) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return errors @@ -572,7 +572,7 @@ class BTMiner(BaseMiner): expected_hashrate = api_summary["SUMMARY"][0]["Factory GHS"] if expected_hashrate: return round(expected_hashrate / 1000, 2) - except (KeyError, IndexError): + except LookupError: pass async def _get_fault_light(self, api_get_miner_info: dict = None) -> bool: diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index cd78e6b1..893ceaa1 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -208,7 +208,7 @@ class CGMinerAvalon(CGMiner): if api_devs: try: return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2) - except (KeyError, IndexError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: @@ -227,7 +227,7 @@ class CGMinerAvalon(CGMiner): try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): return hashboards for board in range(self.expected_hashboards): @@ -271,7 +271,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return round(float(parsed_stats["GHSmm"]) / 1000, 2) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_env_temp(self, api_stats: dict = None) -> Optional[float]: @@ -286,7 +286,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return float(parsed_stats["Temp"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_wattage(self) -> Optional[int]: @@ -304,7 +304,7 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return int(parsed_stats["MPO"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass async def _get_fans(self, api_stats: dict = None) -> List[Fan]: @@ -325,7 +325,7 @@ class CGMinerAvalon(CGMiner): for fan in range(self.expected_fans): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass return fans_data @@ -347,7 +347,7 @@ class CGMinerAvalon(CGMiner): parsed_stats = self.parse_stats(unparsed_stats) led = int(parsed_stats["Led"]) return True if led == 1 else False - except (IndexError, KeyError, ValueError, TypeError): + except (LookupError, ValueError, TypeError): pass try: diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index 83452263..a4519133 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -216,7 +216,7 @@ class ePIC(BaseMiner): hashrate += hb["Hashrate"][0] / ideal return round(float(float(hashrate / 1000000)), 2) - except (IndexError, KeyError, ValueError, TypeError) as e: + except (LookupError, ValueError, TypeError) as e: logger.error(e) pass From 1a4f3f7dc7bc18de7e4f58d9ee0dac39c3bbdff4 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 10:29:39 -0700 Subject: [PATCH 27/36] bug: make sure all data locations are accurate. --- pyasic/miners/antminer/hiveon/X9/T9.py | 8 ++-- pyasic/miners/backends/cgminer_avalon.py | 2 +- pyasic/miners/backends/hiveon.py | 38 +++++++++---------- pyasic/miners/backends/innosilicon.py | 47 ++++++++++++------------ tests/__init__.py | 2 + tests/miners_tests/__init__.py | 1 + 6 files changed, 50 insertions(+), 48 deletions(-) diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index d9dd4832..3f806626 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -30,7 +30,7 @@ class HiveonT9(Hiveon, T9): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def get_mac(self): + async def _get_mac(self): try: mac = ( (await self.send_ssh_command("cat /sys/class/net/eth0/address")) @@ -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/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 893ceaa1..125472f8 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -364,5 +364,5 @@ class CGMinerAvalon(CGMiner): 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 40a32406..22a6a48a 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -22,39 +22,39 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC HIVEON_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("get_mac"), + str(DataOptions.MAC): DataFunction("_get_mac"), 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.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,11 +71,11 @@ class Hiveon(BMMiner): # data gathering locations self.data_locations = HIVEON_DATA_LOC - 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..135c908e 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.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/tests/__init__.py b/tests/__init__.py index 23a60f58..0c5048c4 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -17,6 +17,8 @@ import unittest from tests.api_tests import * +from tests.config_tests import TestConfig +from tests.miners_tests import MinersTest from tests.network_tests import NetworkTest if __name__ == "__main__": diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index e0e700a6..104235ae 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +import inspect import unittest import warnings from dataclasses import asdict From 269e6aac14d3c205b71aaaf2692126e2edfbeebd Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 10:35:15 -0700 Subject: [PATCH 28/36] bug: add more tests and finish renaming methods. --- pyasic/miners/backends/antminer.py | 2 +- pyasic/miners/backends/innosilicon.py | 2 +- tests/miners_tests/__init__.py | 22 +++++++++++++++++++--- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/pyasic/miners/backends/antminer.py b/pyasic/miners/backends/antminer.py index 7e7d5c33..6dcbeedc 100644 --- a/pyasic/miners/backends/antminer.py +++ b/pyasic/miners/backends/antminer.py @@ -58,7 +58,7 @@ ANTMINER_MODERN_DATA_LOC = DataLocations( str(DataOptions.FANS): DataFunction( "_get_fans", [RPCAPICommand("api_stats", "stats")] ), - str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( "_get_errors", [WebAPICommand("web_summary", "summary")] ), diff --git a/pyasic/miners/backends/innosilicon.py b/pyasic/miners/backends/innosilicon.py index 135c908e..9e85525a 100644 --- a/pyasic/miners/backends/innosilicon.py +++ b/pyasic/miners/backends/innosilicon.py @@ -91,7 +91,7 @@ INNOSILICON_DATA_LOC = DataLocations( ], ), str(DataOptions.FAULT_LIGHT): DataFunction("_get_fault_light"), - str(DataOptions.IS_MINING): DataFunction("is_mining"), + str(DataOptions.IS_MINING): DataFunction("_is_mining"), str(DataOptions.UPTIME): DataFunction( "_get_uptime", [RPCAPICommand("api_stats", "stats")] ), diff --git a/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index 104235ae..c1c4cfaa 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -28,7 +28,7 @@ class MinersTest(unittest.TestCase): for miner_model in MINER_CLASSES.keys(): for miner_api in MINER_CLASSES[miner_model].keys(): with self.subTest( - msg=f"Creation of miner using model={miner_model}, api={miner_api}", + msg=f"Test creation of miner", miner_model=miner_model, miner_api=miner_api, ): @@ -63,7 +63,7 @@ class MinersTest(unittest.TestCase): for miner_model in MINER_CLASSES.keys(): for miner_api in MINER_CLASSES[miner_model].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_model=miner_model, miner_api=miner_api, ): @@ -80,7 +80,7 @@ class MinersTest(unittest.TestCase): miner = MINER_CLASSES[miner_model][miner_api]("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_model=miner_model, miner_api=miner_api, ): @@ -98,6 +98,22 @@ class MinersTest(unittest.TestCase): set([k["name"] for k in data_point["kwargs"]]), ) + def test_data_locations_use_private_funcs(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 data_point in asdict(miner.data_locations).values(): + with self.subTest( + msg=f"Test {data_point['cmd']} is private", + miner_model=miner_model, + miner_api=miner_api, + ): + self.assertTrue( + data_point["cmd"].startswith("_") + or data_point["cmd"] == "get_config" + ) + if __name__ == "__main__": unittest.main() From 672e753afb924de0f987803cd176d902a666bc76 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 10:16:47 -0700 Subject: [PATCH 29/36] bug: add test to cross check function arguments, and fix some method implementations and naming. --- pyasic/miners/antminer/hiveon/X9/T9.py | 16 ++--- pyasic/miners/backends/btminer.py | 1 - pyasic/miners/backends/cgminer_avalon.py | 78 ++++++++++++------------ pyasic/miners/backends/hiveon.py | 45 ++++++++------ pyasic/miners/backends/innosilicon.py | 49 +++++++-------- tests/miners_tests/__init__.py | 28 +++------ 6 files changed, 106 insertions(+), 111 deletions(-) diff --git a/pyasic/miners/antminer/hiveon/X9/T9.py b/pyasic/miners/antminer/hiveon/X9/T9.py index 3f806626..f7194624 100644 --- a/pyasic/miners/antminer/hiveon/X9/T9.py +++ b/pyasic/miners/antminer/hiveon/X9/T9.py @@ -30,7 +30,7 @@ class HiveonT9(Hiveon, T9): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def _get_mac(self): + async def get_mac(self): try: mac = ( (await self.send_ssh_command("cat /sys/class/net/eth0/address")) @@ -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) @@ -69,21 +69,21 @@ class HiveonT9(Hiveon, T9): hashboards[board].chip_temp = api_stats["STATS"][1][ f"temp2_{chipset}" ] - except LookupError: + except (KeyError, IndexError): pass else: hashboards[board].missing = False try: hashrate += api_stats["STATS"][1][f"chain_rate{chipset}"] chips += api_stats["STATS"][1][f"chain_acn{chipset}"] - except LookupError: + except (KeyError, IndexError): pass hashboards[board].hashrate = round(hashrate / 1000, 2) hashboards[board].chips = chips 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() @@ -94,13 +94,13 @@ class HiveonT9(Hiveon, T9): boards = api_stats.get("STATS") try: wattage_raw = boards[1]["chain_power"] - except LookupError: + except (KeyError, IndexError): pass else: # 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], @@ -119,7 +119,7 @@ class HiveonT9(Hiveon, T9): env_temp = api_stats["STATS"][1][f"temp3_{chipset}"] if not env_temp == 0: env_temp_list.append(int(env_temp)) - except LookupError: + except (KeyError, IndexError): pass if not env_temp_list == []: diff --git a/pyasic/miners/backends/btminer.py b/pyasic/miners/backends/btminer.py index 543c8419..b49aecab 100644 --- a/pyasic/miners/backends/btminer.py +++ b/pyasic/miners/backends/btminer.py @@ -557,7 +557,6 @@ class BTMiner(BaseMiner): errors.append(WhatsminerError(error_code=err)) except (LookupError, ValueError, TypeError): pass - return errors async def _get_expected_hashrate(self, api_summary: dict = None): diff --git a/pyasic/miners/backends/cgminer_avalon.py b/pyasic/miners/backends/cgminer_avalon.py index 125472f8..216087b5 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -14,6 +14,7 @@ # limitations under the License. - # ------------------------------------------------------------------------------ +import logging import re from typing import List, Optional @@ -27,41 +28,42 @@ 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"), } ) @@ -172,7 +174,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() @@ -190,7 +192,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() @@ -198,7 +200,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() @@ -208,10 +210,10 @@ class CGMinerAvalon(CGMiner): if api_devs: try: return round(float(api_devs["DEVS"][0]["MHS 1m"] / 1000000), 2) - except (LookupError, ValueError, TypeError): + 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) @@ -227,7 +229,7 @@ class CGMinerAvalon(CGMiner): try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): return hashboards for board in range(self.expected_hashboards): @@ -259,7 +261,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() @@ -271,10 +273,10 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return round(float(parsed_stats["GHSmm"]) / 1000, 2) - except (LookupError, ValueError, TypeError): + 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() @@ -286,13 +288,13 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return float(parsed_stats["Temp"]) - except (LookupError, ValueError, TypeError): + 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() @@ -304,17 +306,17 @@ class CGMinerAvalon(CGMiner): unparsed_stats = api_stats["STATS"][0]["MM ID0"] parsed_stats = self.parse_stats(unparsed_stats) return int(parsed_stats["MPO"]) - except (LookupError, ValueError, TypeError): + 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() except APIError: pass - fans_data = [Fan() for _ in range(self.expected_fans)] + fans_data = [Fan() for _ in range(self.fan_count)] if api_stats: try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] @@ -322,17 +324,17 @@ class CGMinerAvalon(CGMiner): except LookupError: return fans_data - for fan in range(self.expected_fans): + for fan in range(self.fan_count): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): 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: @@ -347,7 +349,7 @@ class CGMinerAvalon(CGMiner): parsed_stats = self.parse_stats(unparsed_stats) led = int(parsed_stats["Led"]) return True if led == 1 else False - except (LookupError, ValueError, TypeError): + except (IndexError, KeyError, ValueError, TypeError): pass try: @@ -361,8 +363,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 22a6a48a..600eb41c 100644 --- a/pyasic/miners/backends/hiveon.py +++ b/pyasic/miners/backends/hiveon.py @@ -22,39 +22,40 @@ from pyasic.miners.base import DataFunction, DataLocations, DataOptions, RPCAPIC HIVEON_DATA_LOC = DataLocations( **{ - str(DataOptions.MAC): DataFunction("_get_mac"), + 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.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"), } @@ -67,15 +68,19 @@ class Hiveon(BMMiner): self.pwd = "admin" # static data self.api_type = "Hiveon" - self.fw_str = "Hive" # data gathering locations self.data_locations = HIVEON_DATA_LOC - async def _get_hashboards(self, api_stats: dict = None) -> List[HashBoard]: + 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]: 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 9e85525a..192bfeb6 100644 --- a/pyasic/miners/backends/innosilicon.py +++ b/pyasic/miners/backends/innosilicon.py @@ -33,67 +33,68 @@ 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"), } @@ -175,7 +176,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: @@ -201,7 +202,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: @@ -233,7 +234,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: @@ -291,7 +292,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: @@ -328,7 +329,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"] @@ -353,7 +354,7 @@ class Innosilicon(CGMiner): return fans - async def _get_errors( + async def get_errors( self, web_get_error_detail: dict = None ) -> List[MinerErrorData]: errors = [] @@ -376,7 +377,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"] @@ -399,5 +400,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/tests/miners_tests/__init__.py b/tests/miners_tests/__init__.py index c1c4cfaa..ef30002d 100644 --- a/tests/miners_tests/__init__.py +++ b/tests/miners_tests/__init__.py @@ -13,13 +13,16 @@ # 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.miner_factory import MINER_CLASSES +from pyasic.miners.base import BaseMiner +from pyasic.miners.miner_factory import MINER_CLASSES, MinerFactory class MinersTest(unittest.TestCase): @@ -28,7 +31,7 @@ class MinersTest(unittest.TestCase): for miner_model in MINER_CLASSES.keys(): for miner_api in MINER_CLASSES[miner_model].keys(): with self.subTest( - msg=f"Test creation of miner", + msg=f"Creation of miner using model={miner_model}, api={miner_api}", miner_model=miner_model, miner_api=miner_api, ): @@ -53,6 +56,7 @@ class MinersTest(unittest.TestCase): "hostname", "is_mining", "mac", + "model", "expected_hashrate", "uptime", "wattage", @@ -63,7 +67,7 @@ class MinersTest(unittest.TestCase): for miner_model in MINER_CLASSES.keys(): for miner_api in MINER_CLASSES[miner_model].keys(): with self.subTest( - msg=f"Data map key check", + msg=f"Data map key check of miner using model={miner_model}, api={miner_api}", miner_model=miner_model, miner_api=miner_api, ): @@ -80,7 +84,7 @@ class MinersTest(unittest.TestCase): miner = MINER_CLASSES[miner_model][miner_api]("127.0.0.1") for data_point in asdict(miner.data_locations).values(): with self.subTest( - msg=f"Test {data_point['cmd']} signature matches", + msg=f"Test {data_point['cmd']} signature matches with model={miner_model}, api={miner_api}", miner_model=miner_model, miner_api=miner_api, ): @@ -98,22 +102,6 @@ class MinersTest(unittest.TestCase): set([k["name"] for k in data_point["kwargs"]]), ) - def test_data_locations_use_private_funcs(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 data_point in asdict(miner.data_locations).values(): - with self.subTest( - msg=f"Test {data_point['cmd']} is private", - miner_model=miner_model, - miner_api=miner_api, - ): - self.assertTrue( - data_point["cmd"].startswith("_") - or data_point["cmd"] == "get_config" - ) - if __name__ == "__main__": unittest.main() From 928e0dd028d507879fd7f9d02bcd45fd65a55db4 Mon Sep 17 00:00:00 2001 From: b-rowan Date: Wed, 10 Jan 2024 22:12:27 -0700 Subject: [PATCH 30/36] feature: start refactoring BOSer and BOSMiner into separate classes. --- pyasic/config/power_scaling.py | 77 +-- pyasic/miners/backends/bosminer_old.py | 155 ------ pyasic/miners/backends/braiins_os.py | 626 ++++++++++++++++++++++++- pyasic/web/braiins_os/__init__.py | 30 +- pyasic/web/braiins_os/grpc.py | 125 +---- 5 files changed, 675 insertions(+), 338 deletions(-) diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index 3c64ec26..b373e51d 100644 --- a/pyasic/config/power_scaling.py +++ b/pyasic/config/power_scaling.py @@ -17,13 +17,7 @@ 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, - Power, - SetDpsRequest, -) +from pyasic.web.braiins_os.proto.braiins.bos.v1 import DpsPowerTarget, DpsTarget, Hours @dataclass @@ -43,8 +37,13 @@ class PowerScalingShutdownEnabled(MinerConfigValue): return cfg - def as_boser(self) -> dict: - return {"enable_shutdown": True, "shutdown_duration": self.duration} + def as_bos_grpc(self) -> dict: + cfg = {"enable_shutdown ": True} + + if self.duration is not None: + cfg["shutdown_duration"] = Hours(self.duration) + + return cfg @dataclass @@ -58,7 +57,7 @@ class PowerScalingShutdownDisabled(MinerConfigValue): def as_bosminer(self) -> dict: return {"shutdown_enabled": False} - def as_boser(self) -> dict: + def as_bos_grpc(self) -> dict: return {"enable_shutdown ": False} @@ -89,19 +88,6 @@ 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): @@ -147,19 +133,20 @@ class PowerScalingEnabled(MinerConfigValue): return {"power_scaling": 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), - ) - ), - ), - } + 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} @dataclass @@ -200,21 +187,3 @@ 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/backends/bosminer_old.py b/pyasic/miners/backends/bosminer_old.py index 86b99309..e69de29b 100644 --- a/pyasic/miners/backends/bosminer_old.py +++ b/pyasic/miners/backends/bosminer_old.py @@ -1,155 +0,0 @@ -# ------------------------------------------------------------------------------ -# Copyright 2022 Upstream Data Inc - -# - -# Licensed under the Apache License, Version 2.0 (the "License"); - -# you may not use this file except in compliance with the License. - -# You may obtain a copy of the License at - -# - -# http://www.apache.org/licenses/LICENSE-2.0 - -# - -# Unless required by applicable law or agreed to in writing, software - -# distributed under the License is distributed on an "AS IS" BASIS, - -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -# See the License for the specific language governing permissions and - -# limitations under the License. - -# ------------------------------------------------------------------------------ - -import logging -from typing import List, Optional, Tuple - -from pyasic.config import MinerConfig -from pyasic.data import Fan, HashBoard, MinerData -from pyasic.data.error_codes import MinerErrorData -from pyasic.miners.backends import BOSMiner - - -class BOSMinerOld(BOSMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: - super().__init__(ip, api_ver) - - async def send_ssh_command(self, cmd: str) -> Optional[str]: - result = None - - try: - conn = await self._get_ssh_connection() - except ConnectionError: - return None - - # open an ssh connection - async with conn: - # 3 retries - for i in range(3): - try: - # run the command and get the result - result = await conn.run(cmd) - result = result.stdout - - except Exception as e: - # if the command fails, log it - logging.warning(f"{self} command {cmd} error: {e}") - - # on the 3rd retry, return None - if i == 3: - return - continue - # return the result, either command output or None - return result - - async def update_to_plus(self): - result = await self.send_ssh_command("opkg update && opkg install bos_plus") - return result - - async def check_light(self) -> bool: - return False - - async def fault_light_on(self) -> bool: - return False - - async def fault_light_off(self) -> bool: - return False - - async def get_config(self) -> None: - return None - - async def reboot(self) -> bool: - return False - - async def restart_backend(self) -> bool: - return False - - async def stop_mining(self) -> bool: - return False - - async def resume_mining(self) -> bool: - return False - - async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: - return None - - async def set_power_limit(self, wattage: int) -> bool: - return False - - ################################################## - ### DATA GATHERING FUNCTIONS (get_{some_data}) ### - ################################################## - - async def _get_mac(self, *args, **kwargs) -> Optional[str]: - return None - - async def get_model(self, *args, **kwargs) -> str: - return "S9" - - async def get_version(self, *args, **kwargs) -> Tuple[Optional[str], Optional[str]]: - return None, None - - async def _get_hostname(self, *args, **kwargs) -> Optional[str]: - return None - - async def _get_hashrate(self, *args, **kwargs) -> Optional[float]: - return None - - async def _get_hashboards(self, *args, **kwargs) -> List[HashBoard]: - return [] - - async def _get_env_temp(self, *args, **kwargs) -> Optional[float]: - return None - - async def _get_wattage(self, *args, **kwargs) -> Optional[int]: - return None - - async def _get_wattage_limit(self, *args, **kwargs) -> Optional[int]: - return None - - async def _get_fans( - self, - *args, - **kwargs, - ) -> List[Fan]: - return [Fan(), Fan(), Fan(), Fan()] - - async def _get_fan_psu(self, *args, **kwargs) -> Optional[int]: - return None - - async def _get_api_ver(self, *args, **kwargs) -> Optional[str]: - return None - - async def _get_fw_ver(self, *args, **kwargs) -> Optional[str]: - return None - - async def _get_errors(self, *args, **kwargs) -> List[MinerErrorData]: - return [] - - async def _get_fault_light(self, *args, **kwargs) -> bool: - return False - - async def _get_expected_hashrate(self, *args, **kwargs) -> Optional[float]: - return None - - async def get_data(self, allow_warning: bool = False, **kwargs) -> MinerData: - return MinerData(ip=str(self.ip)) - - async def _is_mining(self, *args, **kwargs) -> Optional[bool]: - return None - - async def _get_uptime(self, *args, **kwargs) -> Optional[int]: - return None diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 84df4a05..dd0abe65 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -36,18 +36,620 @@ from pyasic.miners.base import ( RPCAPICommand, WebAPICommand, ) -from pyasic.web.bosminer import BOSMinerWebAPI +from pyasic.web.braiins_os import BOSerWebAPI, BOSMinerWebAPI BOSMINER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( "_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")] + ), + str(DataOptions.FW_VERSION): DataFunction("get_fw_ver"), + str(DataOptions.HOSTNAME): DataFunction("get_hostname"), + str(DataOptions.HASHRATE): DataFunction( + "get_hashrate", + [RPCAPICommand("api_summary", "summary")], + ), + str(DataOptions.EXPECTED_HASHRATE): DataFunction( + "get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + ), + str(DataOptions.HASHBOARDS): DataFunction( + "get_hashboards", [ - WebAPICommand( - "web_net_conf", "/cgi-bin/luci/admin/network/iface_status/lan" - ) + RPCAPICommand("api_temps", "temps"), + RPCAPICommand("api_devdetails", "devdetails"), + RPCAPICommand("api_devs", "devs"), ], ), + str(DataOptions.ENVIRONMENT_TEMP): DataFunction("get_env_temp"), + str(DataOptions.WATTAGE): DataFunction( + "get_wattage", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.WATTAGE_LIMIT): DataFunction( + "get_wattage_limit", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.FANS): DataFunction( + "get_fans", + [RPCAPICommand("api_fans", "fans")], + ), + str(DataOptions.FAN_PSU): DataFunction("get_fan_psu"), + str(DataOptions.ERRORS): DataFunction( + "get_errors", + [RPCAPICommand("api_tunerstatus", "tunerstatus")], + ), + str(DataOptions.FAULT_LIGHT): DataFunction("get_fault_light"), + str(DataOptions.IS_MINING): DataFunction( + "is_mining", [RPCAPICommand("api_devdetails", "devdetails")] + ), + str(DataOptions.UPTIME): DataFunction( + "get_uptime", [RPCAPICommand("api_summary", "summary")] + ), + str(DataOptions.CONFIG): DataFunction("get_config"), + } +) + + +class BOSMiner(BaseMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: + super().__init__(ip) + # interfaces + self.api = BOSMinerAPI(ip, api_ver) + self.web = BOSMinerWebAPI(ip) + + # static data + self.api_type = "BOSMiner" + # data gathering locations + self.data_locations = BOSMINER_DATA_LOC + # autotuning/shutdown support + self.supports_autotuning = True + self.supports_shutdown = True + + # data storage + self.api_ver = api_ver + + async def send_ssh_command(self, cmd: str) -> Optional[str]: + result = None + + try: + conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10) + except (ConnectionError, asyncio.TimeoutError): + return None + + # open an ssh connection + async with conn: + # 3 retries + for i in range(3): + try: + # run the command and get the result + result = await conn.run(cmd) + stderr = result.stderr + result = result.stdout + + if len(stderr) > len(result): + result = stderr + + except Exception as e: + # if the command fails, log it + logging.warning(f"{self} command {cmd} error: {e}") + + # on the 3rd retry, return None + if i == 3: + return + continue + # return the result, either command output or None + return result + + async def fault_light_on(self) -> bool: + logging.debug(f"{self}: Sending fault_light on command.") + ret = await self.send_ssh_command("miner fault_light on") + logging.debug(f"{self}: fault_light on command completed.") + if isinstance(ret, str): + self.light = True + return self.light + return False + + async def fault_light_off(self) -> bool: + logging.debug(f"{self}: Sending fault_light off command.") + self.light = False + ret = await self.send_ssh_command("miner fault_light off") + logging.debug(f"{self}: fault_light off command completed.") + if isinstance(ret, str): + self.light = False + return True + return False + + async def restart_backend(self) -> bool: + return await self.restart_bosminer() + + async def restart_bosminer(self) -> bool: + logging.debug(f"{self}: Sending bosminer restart command.") + ret = await self.send_ssh_command("/etc/init.d/bosminer restart") + logging.debug(f"{self}: bosminer restart command completed.") + if isinstance(ret, str): + return True + return False + + async def stop_mining(self) -> bool: + try: + data = await self.api.pause() + except APIError: + return False + if data.get("PAUSE"): + if data["PAUSE"][0]: + return True + return False + + async def resume_mining(self) -> bool: + try: + data = await self.api.resume() + except APIError: + return False + if data.get("RESUME"): + if data["RESUME"][0]: + return True + return False + + async def reboot(self) -> bool: + logging.debug(f"{self}: Sending reboot command.") + ret = await self.send_ssh_command("/sbin/reboot") + logging.debug(f"{self}: Reboot command completed.") + if isinstance(ret, str): + return True + return False + + async def get_config(self) -> MinerConfig: + logging.debug(f"{self}: Getting config.") + + 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 + + async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: + logging.debug(f"{self}: Sending config.") + self.config = config + + toml_conf = toml.dumps( + { + "format": { + "version": "1.2+", + "generator": "pyasic", + "model": f"{self.make.replace('Miner', 'miner')} {self.model.replace(' (BOS)', '').replace('j', 'J')}", + "timestamp": int(time.time()), + }, + **config.as_bosminer(user_suffix=user_suffix), + } + ) + try: + conn = await self._get_ssh_connection() + except ConnectionError as e: + raise APIError("SSH connection failed when sending config.") from e + async with conn: + # BBB check because bitmain suxx + bbb_check = await conn.run( + "if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;" + ) + + bbb = bbb_check.stdout.strip() == "1" + + if not bbb: + await conn.run("/etc/init.d/bosminer stop") + logging.debug(f"{self}: Opening SFTP connection.") + async with conn.start_sftp_client() as sftp: + logging.debug(f"{self}: Opening config file.") + async with sftp.open("/etc/bosminer.toml", "w+") as file: + await file.write(toml_conf) + logging.debug(f"{self}: Restarting BOSMiner") + await conn.run("/etc/init.d/bosminer start") + + # I really hate BBB, please get rid of it if you have it + else: + await conn.run("/etc/init.d/S99bosminer stop") + logging.debug(f"{self}: BBB sending config") + await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml") + logging.debug(f"{self}: BBB restarting bosminer.") + await conn.run("/etc/init.d/S99bosminer start") + + 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}") + return False + else: + return True + + async def set_static_ip( + self, + ip: str, + dns: str, + 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 + + "'" + ) + data = await self.send_ssh_command("cat /etc/config/network") + + split_data = data.split("\n\n") + for idx in range(len(split_data)): + if "config interface 'lan'" in split_data[idx]: + split_data[idx] = cfg_data_lan + config = "\n\n".join(split_data) + + conn = await self._get_ssh_connection() + + async with conn: + 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'" + data = await self.send_ssh_command("cat /etc/config/network") + + split_data = data.split("\n\n") + for idx in range(len(split_data)): + if "config interface 'lan'" in split_data[idx]: + split_data[idx] = cfg_data_lan + config = "\n\n".join(split_data) + + conn = await self._get_ssh_connection() + + async with conn: + await conn.run("echo '" + config + "' > /etc/config/network") + + ################################################## + ### DATA GATHERING FUNCTIONS (get_{some_data}) ### + ################################################## + + 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" + ) + except APIError: + pass + + if isinstance(web_net_conf, dict): + if "admin/network/iface_status/lan" in web_net_conf.keys(): + web_net_conf = web_net_conf["admin/network/iface_status/lan"] + + if web_net_conf: + try: + return web_net_conf[0]["macaddr"] + except LookupError: + pass + # could use ssh, but its slow and buggy + # result = await self.send_ssh_command("cat /sys/class/net/eth0/address") + # 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]: + if not api_version: + try: + api_version = await self.api.version() + except APIError: + pass + + # Now get the API version + if api_version: + try: + api_ver = api_version["VERSION"][0]["API"] + except (KeyError, IndexError): + api_ver = None + self.api_ver = api_ver + self.api.api_ver = self.api_ver + + return self.api_ver + + async def get_fw_ver(self) -> Optional[str]: + fw_ver = await self.send_ssh_command("cat /etc/bos_version") + + # if we get the version data, parse it + if fw_ver is not None: + ver = fw_ver.split("-")[5] + if "." in ver: + self.fw_ver = ver + logging.debug(f"Found version for {self.ip}: {self.fw_ver}") + + return self.fw_ver + + async def get_hostname(self) -> Union[str, None]: + try: + hostname = ( + await self.send_ssh_command("cat /proc/sys/kernel/hostname") + ).strip() + except Exception as e: + logging.error(f"BOSMiner get_hostname failed with error: {e}") + return None + return hostname + + async def get_hashrate(self, api_summary: dict = None) -> Optional[float]: + # get hr from API + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + return round(float(api_summary["SUMMARY"][0]["MHS 1m"] / 1000000), 2) + except (KeyError, IndexError, ValueError, TypeError): + pass + + async def get_hashboards( + self, + api_temps: dict = None, + api_devdetails: dict = None, + api_devs: dict = None, + ): + hashboards = [ + HashBoard(slot=i, expected_chips=self.expected_chips) + for i in range(self.expected_hashboards) + ] + + cmds = [] + if not api_temps: + cmds.append("temps") + if not api_devdetails: + cmds.append("devdetails") + if not api_devs: + cmds.append("devs") + if len(cmds) > 0: + try: + d = await self.api.multicommand(*cmds) + except APIError: + d = {} + try: + api_temps = d["temps"][0] + except (KeyError, IndexError): + api_temps = None + try: + api_devdetails = d["devdetails"][0] + except (KeyError, IndexError): + api_devdetails = None + try: + api_devs = d["devs"][0] + except (KeyError, IndexError): + api_devs = None + if api_temps: + try: + offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_temps["TEMPS"]: + _id = board["ID"] - offset + chip_temp = round(board["Chip"]) + board_temp = round(board["Board"]) + hashboards[_id].chip_temp = chip_temp + hashboards[_id].temp = board_temp + except (IndexError, KeyError, ValueError, TypeError): + pass + + if api_devdetails: + try: + offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_devdetails["DEVDETAILS"]: + _id = board["ID"] - offset + chips = board["Chips"] + hashboards[_id].chips = chips + hashboards[_id].missing = False + except (IndexError, KeyError): + pass + + if api_devs: + try: + offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1 + + for board in api_devs["DEVS"]: + _id = board["ID"] - offset + hashrate = round(float(board["MHS 1m"] / 1000000), 2) + hashboards[_id].hashrate = hashrate + except (IndexError, KeyError): + pass + + return hashboards + + async def get_env_temp(self) -> Optional[float]: + return None + + async def get_wattage(self, api_tunerstatus: dict = None) -> Optional[int]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + try: + return api_tunerstatus["TUNERSTATUS"][0][ + "ApproximateMinerPowerConsumption" + ] + except (KeyError, IndexError): + pass + + async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + try: + return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] + except (KeyError, IndexError): + pass + + async def get_fans(self, api_fans: dict = None) -> List[Fan]: + if not api_fans: + try: + api_fans = await self.api.fans() + except APIError: + pass + + if api_fans: + fans = [] + for n in range(self.fan_count): + try: + fans.append(Fan(api_fans["FANS"][n]["RPM"])) + except (IndexError, KeyError): + pass + return fans + return [Fan() for _ in range(self.fan_count)] + + async def get_fan_psu(self) -> Optional[int]: + return None + + async def get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: + if not api_tunerstatus: + try: + api_tunerstatus = await self.api.tunerstatus() + except APIError: + pass + + if api_tunerstatus: + errors = [] + try: + chain_status = api_tunerstatus["TUNERSTATUS"][0]["TunerChainStatus"] + if chain_status and len(chain_status) > 0: + offset = ( + 6 if int(chain_status[0]["HashchainIndex"]) in [6, 7, 8] else 0 + ) + + for board in chain_status: + _id = board["HashchainIndex"] - offset + if board["Status"] not in [ + "Stable", + "Testing performance profile", + "Tuning individual chips", + ]: + _error = board["Status"].split(" {")[0] + _error = _error[0].lower() + _error[1:] + errors.append(BraiinsOSError(f"Slot {_id} {_error}")) + return errors + except (KeyError, IndexError): + pass + + async def get_fault_light(self, graphql_fault_light: dict = None) -> bool: + if self.light: + return self.light + try: + data = ( + await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") + ).strip() + self.light = False + if data == "50": + self.light = True + return self.light + except (TypeError, AttributeError): + return self.light + + async def get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: + if not api_devs: + try: + api_devs = await self.api.devs() + except APIError: + pass + + if api_devs: + try: + offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0 + hr_list = [] + + for board in api_devs["DEVS"]: + _id = board["ID"] - offset + expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) + if expected_hashrate: + hr_list.append(expected_hashrate) + if len(hr_list) == 0: + return 0 + else: + return round( + (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 + ) + except (IndexError, KeyError): + pass + + async def is_mining(self, api_devdetails: dict = None) -> Optional[bool]: + if not api_devdetails: + try: + api_devdetails = await self.api.send_command( + "devdetails", ignore_errors=True, allow_warning=False + ) + except APIError: + pass + + if api_devdetails: + try: + return not api_devdetails["STATUS"][0]["Msg"] == "Unavailable" + except LookupError: + pass + + async def get_uptime(self, api_summary: dict = None) -> Optional[int]: + if not api_summary: + try: + api_summary = await self.api.summary() + except APIError: + pass + + if api_summary: + try: + return int(api_summary["SUMMARY"][0]["Elapsed"]) + except LookupError: + pass + + +BOSER_DATA_LOC = DataLocations( + **{ + str(DataOptions.MAC): DataFunction( + "get_mac", + [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], + ), str(DataOptions.API_VERSION): DataFunction( "_get_api_ver", [RPCAPICommand("api_version", "version")] ), @@ -179,18 +781,18 @@ BOSMINER_DATA_LOC = DataLocations( ) -class BOSMiner(BaseMiner): - def __init__(self, ip: str, api_ver: str = "0.0.0", boser: bool = None) -> None: +class BOSer(BaseMiner): + def __init__(self, ip: str, api_ver: str = "0.0.0") -> None: super().__init__(ip) # interfaces self.api = BOSMinerAPI(ip, api_ver) - self.web = BOSMinerWebAPI(ip, boser=boser) + self.web = BOSerWebAPI(ip) # static data self.api_type = "BOSMiner" self.fw_str = "BOS" # data gathering locations - self.data_locations = BOSMINER_DATA_LOC + self.data_locations = BOSER_DATA_LOC # autotuning/shutdown support self.supports_autotuning = True self.supports_shutdown = True @@ -432,16 +1034,14 @@ class BOSMiner(BaseMiner): if not web_net_conf: try: web_net_conf = await self.web.send_command( - "/cgi-bin/luci/admin/network/iface_status/lan" + "admin/network/iface_status/lan" ) except APIError: pass if isinstance(web_net_conf, dict): - if "/cgi-bin/luci/admin/network/iface_status/lan" in web_net_conf.keys(): - web_net_conf = web_net_conf[ - "/cgi-bin/luci/admin/network/iface_status/lan" - ] + if "admin/network/iface_status/lan" in web_net_conf.keys(): + web_net_conf = web_net_conf["admin/network/iface_status/lan"] if web_net_conf: try: diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index 04e0ded4..a884e0fa 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -84,29 +84,31 @@ class BOSerWebAPI(BOSMinerWebAPI): **parameters: Union[str, int, bool], ) -> dict: command_type = self.select_command_type(command) - if command_type == "gql": + if command_type is "gql": return await self.gql.send_command(command) - elif command_type == "grpc": + elif command_type is "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 == "luci": + elif command_type is "luci": return await self.luci.send_command(command) @staticmethod def select_command_type(command: Union[str, dict]) -> str: if isinstance(command, dict): return "gql" - else: + elif command.startswith("grpc_"): return "grpc" + else: + return "luci" async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True ) -> dict: - cmd_types = {"grpc": [], "gql": []} + cmd_types = {"grpc": [], "gql": [], "luci": []} for cmd in commands: - cmd_types[self.select_command_type(cmd)].append(cmd) + cmd_types[self.select_command_type(cmd)] = cmd async def no_op(): return {} @@ -116,13 +118,21 @@ class BOSerWebAPI(BOSMinerWebAPI): self.grpc.multicommand(*cmd_types["grpc"]) ) else: - grpc_data_t = asyncio.create_task(no_op()) + grpc_data_t = no_op() if len(cmd_types["gql"]) > 0: gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) else: - gql_data_t = asyncio.create_task(no_op()) + gql_data_t = no_op() + if len(cmd_types["luci"]) > 0: + luci_data_t = asyncio.create_task( + self.luci.multicommand(*cmd_types["luci"]) + ) + else: + luci_data_t = no_op() - await asyncio.gather(grpc_data_t, gql_data_t) + await asyncio.gather(grpc_data_t, gql_data_t, luci_data_t) - data = dict(**grpc_data_t.result(), **gql_data_t.result()) + data = dict( + **luci_data_t.result(), **gql_data_t.result(), **luci_data_t.result() + ) return data diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index c5f405e9..821572e7 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -13,12 +13,9 @@ # See the License for the specific language governing permissions and - # 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 @@ -46,7 +43,6 @@ class BOSerGRPCAPI: self.ip = ip self.username = "root" self.pwd = pwd - self.port = 50051 self._auth = None self._auth_time = datetime.now() @@ -68,20 +64,7 @@ class BOSerGRPCAPI: ] async def multicommand(self, *commands: str) -> dict: - result = {"multicommand": True} - tasks = {} - for command in commands: - try: - tasks[command] = asyncio.create_task(getattr(self, command)()) - except AttributeError: - result["command"] = {} - - await asyncio.gather(*list(tasks.values())) - - for cmd in tasks: - result[cmd] = tasks[cmd].result() - - return result + pass async def send_command( self, @@ -93,23 +76,13 @@ class BOSerGRPCAPI: metadata = [] if auth: metadata.append(("authorization", await self.auth())) - 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 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() async def auth(self): if self._auth is not None and self._auth_time - datetime.now() < timedelta( @@ -120,7 +93,7 @@ class BOSerGRPCAPI: return self._auth async def _get_auth(self): - async with Channel(self.ip, self.port) as c: + async with Channel(self.ip, 50051) as c: req = LoginRequest(username=self.username, password=self.pwd) async with c.request( "/braiins.bos.v1.AuthenticationService/Login", @@ -165,9 +138,7 @@ class BOSerGRPCAPI: ) async def get_locate_device_status(self): - return await self.send_command( - "get_locate_device_status", GetLocateDeviceStatusRequest() - ) + return await self.send_command("get_locate_device_status") async def set_password(self, password: str = None): return await self.send_command( @@ -190,12 +161,10 @@ class BOSerGRPCAPI: ) async def get_tuner_state(self): - return await self.send_command("get_tuner_state", GetTunerStateRequest()) + return await self.send_command("get_tuner_state") async def list_target_profiles(self): - return await self.send_command( - "list_target_profiles", ListTargetProfilesRequest() - ) + return await self.send_command("list_target_profiles") async def set_default_power_target( self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY @@ -296,71 +265,15 @@ class BOSerGRPCAPI: async def set_dps( self, - enable: bool, - power_step: int, - min_power_target: int, - enable_shutdown: bool = None, - shutdown_duration: int = None, ): - return await self.send_command( - "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), - ) - ), - ), - ) + raise NotImplementedError + return await self.send_command("braiins.bos.v1.PerformanceService/SetDPS") - 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 set_performance_mode(self): + raise NotImplementedError + return await self.send_command( + "braiins.bos.v1.PerformanceService/SetPerformanceMode" + ) async def get_active_performance_mode(self): return await self.send_command( From 7be6596fddcecdef7a3396d03581cf86ecb037df Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Thu, 11 Jan 2024 10:20:18 -0700 Subject: [PATCH 31/36] refactor: swap `except (KeyError, ValueError)` to `except LookupError`. --- pyasic/miners/backends/braiins_os.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index dd0abe65..011e2dab 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -378,7 +378,7 @@ class BOSMiner(BaseMiner): if api_version: try: api_ver = api_version["VERSION"][0]["API"] - except (KeyError, IndexError): + except LookupError: api_ver = None self.api_ver = api_ver self.api.api_ver = self.api_ver @@ -446,7 +446,7 @@ class BOSMiner(BaseMiner): d = {} try: api_temps = d["temps"][0] - except (KeyError, IndexError): + except LookupError: api_temps = None try: api_devdetails = d["devdetails"][0] @@ -454,7 +454,7 @@ class BOSMiner(BaseMiner): api_devdetails = None try: api_devs = d["devs"][0] - except (KeyError, IndexError): + except LookupError: api_devs = None if api_temps: try: @@ -509,7 +509,7 @@ class BOSMiner(BaseMiner): return api_tunerstatus["TUNERSTATUS"][0][ "ApproximateMinerPowerConsumption" ] - except (KeyError, IndexError): + except LookupError: pass async def get_wattage_limit(self, api_tunerstatus: dict = None) -> Optional[int]: @@ -522,7 +522,7 @@ class BOSMiner(BaseMiner): if api_tunerstatus: try: return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except (KeyError, IndexError): + except LookupError: pass async def get_fans(self, api_fans: dict = None) -> List[Fan]: From 831d6ee9552cd3f356be4647e3008785e6632213 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 11:58:26 -0700 Subject: [PATCH 32/36] feature: add boser fault light functions. --- pyasic/miners/backends/braiins_os.py | 148 +-------------------------- 1 file changed, 5 insertions(+), 143 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 011e2dab..2afd07b9 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -800,54 +800,15 @@ class BOSer(BaseMiner): # data storage self.api_ver = api_ver - async def send_ssh_command(self, cmd: str) -> Optional[str]: - result = None - - try: - conn = await asyncio.wait_for(self._get_ssh_connection(), timeout=10) - except (ConnectionError, asyncio.TimeoutError): - return None - - # open an ssh connection - async with conn: - # 3 retries - for i in range(3): - try: - # run the command and get the result - result = await conn.run(cmd) - stderr = result.stderr - result = result.stdout - - if len(stderr) > len(result): - result = stderr - - except Exception as e: - # if the command fails, log it - logging.warning(f"{self} command {cmd} error: {e}") - - # on the 3rd retry, return None - if i == 3: - return - continue - # return the result, either command output or None - return result - async def fault_light_on(self) -> bool: - logging.debug(f"{self}: Sending fault_light on command.") - ret = await self.send_ssh_command("miner fault_light on") - logging.debug(f"{self}: fault_light on command completed.") - if isinstance(ret, str): - self.light = True - return self.light + resp = await self.web.grpc.set_locate_device_status(True) + if resp.get("enabled", False): + return True return False async def fault_light_off(self) -> bool: - logging.debug(f"{self}: Sending fault_light off command.") - self.light = False - ret = await self.send_ssh_command("miner fault_light off") - logging.debug(f"{self}: fault_light off command completed.") - if isinstance(ret, str): - self.light = False + resp = await self.web.grpc.set_locate_device_status(False) + if resp == {}: return True return False @@ -912,60 +873,7 @@ class BOSer(BaseMiner): async def send_config(self, config: MinerConfig, user_suffix: str = None) -> None: logging.debug(f"{self}: Sending config.") self.config = config - - if self.web.grpc is not None: - try: - await self._send_config_grpc(config, user_suffix) - return - except: - pass - await self._send_config_bosminer(config, user_suffix) - - async def _send_config_grpc(self, config: MinerConfig, user_suffix: str = None): raise NotImplementedError - mining_mode = config.mining_mode - - async def _send_config_bosminer(self, config: MinerConfig, user_suffix: str = None): - toml_conf = toml.dumps( - { - "format": { - "version": "1.2+", - "generator": "pyasic", - "raw_model": f"{self.make.replace('Miner', 'miner')} {self.raw_model}", - "timestamp": int(time.time()), - }, - **config.as_bosminer(user_suffix=user_suffix), - } - ) - try: - conn = await self._get_ssh_connection() - except ConnectionError as e: - raise APIError("SSH connection failed when sending config.") from e - async with conn: - # BBB check because bitmain suxx - bbb_check = await conn.run( - "if [ ! -f /etc/init.d/bosminer ]; then echo '1'; else echo '0'; fi;" - ) - - bbb = bbb_check.stdout.strip() == "1" - - if not bbb: - await conn.run("/etc/init.d/bosminer stop") - logging.debug(f"{self}: Opening SFTP connection.") - async with conn.start_sftp_client() as sftp: - logging.debug(f"{self}: Opening config file.") - async with sftp.open("/etc/bosminer.toml", "w+") as file: - await file.write(toml_conf) - logging.debug(f"{self}: Restarting BOSMiner") - await conn.run("/etc/init.d/bosminer start") - - # I really hate BBB, please get rid of it if you have it - else: - await conn.run("/etc/init.d/S99bosminer stop") - logging.debug(f"{self}: BBB sending config") - await conn.run("echo '" + toml_conf + "' > /etc/bosminer.toml") - logging.debug(f"{self}: BBB restarting bosminer.") - await conn.run("/etc/init.d/S99bosminer start") async def set_power_limit(self, wattage: int) -> bool: try: @@ -980,52 +888,6 @@ class BOSer(BaseMiner): else: return True - async def set_static_ip( - self, - ip: str, - dns: str, - 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 - + "'" - ) - data = await self.send_ssh_command("cat /etc/config/network") - - split_data = data.split("\n\n") - for idx in range(len(split_data)): - if "config interface 'lan'" in split_data[idx]: - split_data[idx] = cfg_data_lan - config = "\n\n".join(split_data) - - conn = await self._get_ssh_connection() - - async with conn: - 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'" - data = await self.send_ssh_command("cat /etc/config/network") - - split_data = data.split("\n\n") - for idx in range(len(split_data)): - if "config interface 'lan'" in split_data[idx]: - split_data[idx] = cfg_data_lan - config = "\n\n".join(split_data) - - conn = await self._get_ssh_connection() - - async with conn: - await conn.run("echo '" + config + "' > /etc/config/network") - ################################################## ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## From f1501718a374b808c97d1779382465f628ad45ab Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Fri, 12 Jan 2024 13:29:46 -0700 Subject: [PATCH 33/36] feature: finish get_data functions for bosminer --- pyasic/miners/backends/braiins_os.py | 619 +++++---------------------- pyasic/web/braiins_os/__init__.py | 24 +- pyasic/web/braiins_os/grpc.py | 22 +- 3 files changed, 140 insertions(+), 525 deletions(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index 2afd07b9..d0358329 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -33,6 +33,7 @@ from pyasic.miners.base import ( DataLocations, DataOptions, GraphQLCommand, + GRPCCommand, RPCAPICommand, WebAPICommand, ) @@ -648,127 +649,56 @@ BOSER_DATA_LOC = DataLocations( **{ str(DataOptions.MAC): DataFunction( "get_mac", - [WebAPICommand("web_net_conf", "admin/network/iface_status/lan")], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.API_VERSION): DataFunction( - "_get_api_ver", [RPCAPICommand("api_version", "version")] + "_get_api_ver", [GRPCCommand("api_version", "get_api_version")] ), str(DataOptions.FW_VERSION): DataFunction( "_get_fw_ver", - [ - GraphQLCommand( - "graphql_version", {"bos": {"info": {"version": {"full": None}}}} - ) - ], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HOSTNAME): DataFunction( "_get_hostname", - [GraphQLCommand("graphql_hostname", {"bos": {"hostname": None}})], + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHRATE): DataFunction( "_get_hashrate", - [ - RPCAPICommand("api_summary", "summary"), - GraphQLCommand( - "graphql_hashrate", - { - "bosminer": { - "info": {"workSolver": {"realHashrate": {"mhs1M": None}}} - } - }, - ), - ], + [RPCAPICommand("api_summary", "summary")], ), str(DataOptions.EXPECTED_HASHRATE): DataFunction( - "_get_expected_hashrate", [RPCAPICommand("api_devs", "devs")] + "_get_expected_hashrate", + [GRPCCommand("grpc_miner_details", "get_miner_details")], ), str(DataOptions.HASHBOARDS): DataFunction( "_get_hashboards", - [ - RPCAPICommand("api_temps", "temps"), - RPCAPICommand("api_devdetails", "devdetails"), - RPCAPICommand("api_devs", "devs"), - GraphQLCommand( - "graphql_boards", - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "realHashrate": {"mhs1M": None}, - "hwDetails": {"chips": None}, - "temperatures": {"degreesC": None}, - } - } - } - } - }, - ), - ], + [GRPCCommand("grpc_hashboards", "get_hashboards")], ), str(DataOptions.ENVIRONMENT_TEMP): DataFunction("_get_env_temp"), str(DataOptions.WATTAGE): DataFunction( "_get_wattage", - [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_wattage", - { - "bosminer": { - "info": { - "workSolver": {"power": {"approxConsumptionW": None}} - } - } - }, - ), - ], + [GRPCCommand("grpc_miner_stats", "get_miner_stats")], ), str(DataOptions.WATTAGE_LIMIT): DataFunction( "_get_wattage_limit", [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_wattage_limit", - {"bosminer": {"info": {"workSolver": {"power": {"limitW": None}}}}}, - ), + GRPCCommand( + "grpc_active_performance_mode", "get_active_performance_mode" + ) ], ), str(DataOptions.FANS): DataFunction( "_get_fans", - [ - RPCAPICommand("api_fans", "fans"), - GraphQLCommand( - "graphql_fans", - {"bosminer": {"info": {"fans": {"name": None, "rpm": None}}}}, - ), - ], + [GRPCCommand("grpc_cooling_state", "get_cooling_state")], ), str(DataOptions.FAN_PSU): DataFunction("_get_fan_psu"), str(DataOptions.ERRORS): DataFunction( "_get_errors", - [ - RPCAPICommand("api_tunerstatus", "tunerstatus"), - GraphQLCommand( - "graphql_errors", - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "tuner": {"statusMessages": None}, - } - } - } - } - }, - ), - ], + [RPCAPICommand("api_tunerstatus", "tunerstatus")], ), str(DataOptions.FAULT_LIGHT): DataFunction( "_get_fault_light", - [GraphQLCommand("graphql_fault_light", {"bos": {"faultLight": None}})], + [GRPCCommand("grpc_locate_device_status", "get_locate_device_status")], ), str(DataOptions.IS_MINING): DataFunction( "_is_mining", [RPCAPICommand("api_devdetails", "devdetails")] @@ -813,41 +743,29 @@ class BOSer(BaseMiner): return False async def restart_backend(self) -> bool: - return await self.restart_bosminer() + return await self.restart_boser() - async def restart_bosminer(self) -> bool: - logging.debug(f"{self}: Sending bosminer restart command.") - ret = await self.send_ssh_command("/etc/init.d/bosminer restart") - logging.debug(f"{self}: bosminer restart command completed.") - if isinstance(ret, str): - return True - return False + async def restart_boser(self) -> bool: + ret = await self.web.grpc.restart() + return True async def stop_mining(self) -> bool: try: - data = await self.api.pause() + await self.web.grpc.pause_mining() except APIError: return False - if data.get("PAUSE"): - if data["PAUSE"][0]: - return True - return False + return True async def resume_mining(self) -> bool: try: - data = await self.api.resume() + await self.web.grpc.resume_mining() except APIError: return False - if data.get("RESUME"): - if data["RESUME"][0]: - return True - return False + return True async def reboot(self) -> bool: - logging.debug(f"{self}: Sending reboot command.") - ret = await self.send_ssh_command("/sbin/reboot") - logging.debug(f"{self}: Reboot command completed.") - if isinstance(ret, str): + ret = await self.web.grpc.reboot() + if ret == {}: return True return False @@ -892,28 +810,18 @@ class BOSer(BaseMiner): ### DATA GATHERING FUNCTIONS (get_{some_data}) ### ################################################## - async def _get_mac(self, web_net_conf: Union[dict, list] = None) -> Optional[str]: - if not web_net_conf: + async def _get_mac(self, grpc_miner_details: dict = None) -> Optional[str]: + if not grpc_miner_details: try: - web_net_conf = await self.web.send_command( - "admin/network/iface_status/lan" - ) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass - if isinstance(web_net_conf, dict): - if "admin/network/iface_status/lan" in web_net_conf.keys(): - web_net_conf = web_net_conf["admin/network/iface_status/lan"] - - if web_net_conf: + if grpc_miner_details: try: - return web_net_conf[0]["macaddr"] - except LookupError: + return grpc_miner_details["macAddress"].upper() + except (LookupError, TypeError): pass - # could use ssh, but its slow and buggy - # result = await self.send_ssh_command("cat /sys/class/net/eth0/address") - # if result: - # return result.upper().strip() async def get_model(self) -> Optional[str]: if self.raw_model is not None: @@ -947,27 +855,21 @@ class BOSer(BaseMiner): return self.api_ver - async def _get_fw_ver(self, graphql_version: dict = None) -> Optional[str]: - if not graphql_version: + async def _get_fw_ver(self, grpc_miner_details: dict = None) -> Optional[str]: + if not grpc_miner_details: try: - graphql_version = await self.web.send_command( - {"bos": {"info": {"version": {"full"}}}} - ) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass fw_ver = None - if graphql_version: + if grpc_miner_details: try: - fw_ver = graphql_version["data"]["bos"]["info"]["version"]["full"] + fw_ver = grpc_miner_details["bosVersion"]["current"] except (KeyError, TypeError): pass - if not fw_ver: - # try version data file - fw_ver = await self.send_ssh_command("cat /etc/bos_version") - # if we get the version data, parse it if fw_ver is not None: ver = fw_ver.split("-")[5] @@ -977,62 +879,20 @@ class BOSer(BaseMiner): return self.fw_ver - async def _get_hostname(self, graphql_hostname: dict = None) -> Union[str, None]: - hostname = None - - if not graphql_hostname: + async def _get_hostname(self, grpc_miner_details: dict = None) -> Union[str, None]: + if not grpc_miner_details: try: - graphql_hostname = await self.web.send_command({"bos": {"hostname"}}) + grpc_miner_details = await self.web.grpc.get_miner_details() except APIError: pass - if graphql_hostname: + if grpc_miner_details: try: - hostname = graphql_hostname["data"]["bos"]["hostname"] - return hostname - except (TypeError, KeyError): + return grpc_miner_details["hostname"] + except LookupError: pass - try: - async with await self._get_ssh_connection() as conn: - if conn is not None: - data = await conn.run("cat /proc/sys/kernel/hostname") - host = data.stdout.strip() - logging.debug(f"Found hostname for {self.ip}: {host}") - hostname = host - else: - logging.warning(f"Failed to get hostname for miner: {self}") - except Exception as e: - logging.warning(f"Failed to get hostname for miner: {self}, {e}") - return hostname - - async def _get_hashrate( - self, api_summary: dict = None, graphql_hashrate: dict = None - ) -> Optional[float]: - # get hr from graphql - if not graphql_hashrate: - try: - graphql_hashrate = await self.web.send_command( - {"bosminer": {"info": {"workSolver": {"realHashrate": {"mhs1M"}}}}} - ) - except APIError: - pass - - if graphql_hashrate: - try: - return round( - float( - graphql_hashrate["data"]["bosminer"]["info"]["workSolver"][ - "realHashrate" - ]["mhs1M"] - / 1000000 - ), - 2, - ) - except (LookupError, ValueError, TypeError): - pass - - # get hr from API + async def _get_hashrate(self, api_summary: dict = None) -> Optional[float]: if not api_summary: try: api_summary = await self.api.summary() @@ -1045,243 +905,104 @@ class BOSer(BaseMiner): except (LookupError, ValueError, TypeError): pass - async def _get_hashboards( - self, - api_temps: dict = None, - api_devdetails: dict = None, - api_devs: dict = None, - graphql_boards: dict = None, - ): + async def get_expected_hashrate( + self, grpc_miner_details: dict = None + ) -> Optional[float]: + if not grpc_miner_details: + try: + grpc_miner_details = await self.web.grpc.get_miner_details() + except APIError: + pass + + if grpc_miner_details: + try: + return grpc_miner_details["stickerHashrate"]["gigahashPerSecond"] / 1000 + except LookupError: + pass + + 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) ] - if not graphql_boards and not (api_devs or api_temps or api_devdetails): + if grpc_hashboards is None: try: - graphql_boards = await self.web.send_command( - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "realHashrate": {"mhs1M"}, - "hwDetails": {"chips"}, - "temperatures": {"degreesC"}, - } - } - } - } - }, - ) + grpc_hashboards = await self.web.grpc.get_hashboards() except APIError: pass - if graphql_boards: - try: - boards = graphql_boards["data"]["bosminer"]["info"]["workSolver"][ - "childSolvers" - ] - except (TypeError, LookupError): - boards = None - - if boards: - b_names = [int(b["name"]) for b in boards] - offset = 0 - if 3 in b_names: - offset = 1 - elif 6 in b_names or 7 in b_names or 8 in b_names: - offset = 6 - for hb in boards: - _id = int(hb["name"]) - offset - board = hashboards[_id] - - board.hashrate = round(hb["realHashrate"]["mhs1M"] / 1000000, 2) - temps = hb["temperatures"] - try: - if len(temps) > 0: - board.temp = round(hb["temperatures"][0]["degreesC"]) - if len(temps) > 1: - board.chip_temp = round(hb["temperatures"][1]["degreesC"]) - except (LookupError, TypeError, ValueError): - pass - details = hb.get("hwDetails") - if details: - if chips := details["chips"]: - board.chips = chips - board.missing = False - - return hashboards - - cmds = [] - if not api_temps: - cmds.append("temps") - if not api_devdetails: - cmds.append("devdetails") - if not api_devs: - cmds.append("devs") - if len(cmds) > 0: - try: - d = await self.api.multicommand(*cmds) - except APIError: - d = {} - try: - api_temps = d["temps"][0] - except LookupError: - api_temps = None - try: - api_devdetails = d["devdetails"][0] - except LookupError: - api_devdetails = None - try: - api_devs = d["devs"][0] - except LookupError: - api_devs = None - if api_temps: - try: - offset = 6 if api_temps["TEMPS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_temps["TEMPS"]: - _id = board["ID"] - offset - chip_temp = round(board["Chip"]) - board_temp = round(board["Board"]) - hashboards[_id].chip_temp = chip_temp - hashboards[_id].temp = board_temp - except (LookupError, ValueError, TypeError): - pass - - if api_devdetails: - try: - offset = 6 if api_devdetails["DEVDETAILS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_devdetails["DEVDETAILS"]: - _id = board["ID"] - offset - chips = board["Chips"] - hashboards[_id].chips = chips - hashboards[_id].missing = False - except LookupError: - pass - - if api_devs: - try: - offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 1 - - for board in api_devs["DEVS"]: - _id = board["ID"] - offset - hashrate = round(float(board["MHS 1m"] / 1000000), 2) - hashboards[_id].hashrate = hashrate - except LookupError: - pass + if grpc_hashboards is not None: + for board in grpc_hashboards["hashboards"]: + idx = int(board["id"]) - 1 + if board.get("chipsCount") is not None: + hashboards[idx].chips = board["chipsCount"] + if board.get("boardTemp") is not None: + hashboards[idx].temp = board["boardTemp"]["degreeC"] + if board.get("highestChipTemp") is not None: + hashboards[idx].chip_temp = board["highestChipTemp"]["temperature"][ + "degreeC" + ] + if board.get("stats") is not None: + if not board["stats"]["realHashrate"]["last5S"] == {}: + hashboards[idx].hashrate = round( + board["stats"]["realHashrate"]["last5S"][ + "gigahashPerSecond" + ] + / 1000, + 2, + ) + hashboards[idx].missing = False return hashboards async def _get_env_temp(self) -> Optional[float]: return None - async def _get_wattage( - self, api_tunerstatus: dict = None, graphql_wattage: dict = None - ) -> Optional[int]: - if not graphql_wattage and not api_tunerstatus: + async def _get_wattage(self, grpc_miner_stats: dict = None) -> Optional[int]: + if grpc_miner_stats is None: try: - graphql_wattage = await self.web.send_command( - { - "bosminer": { - "info": {"workSolver": {"power": {"approxConsumptionW"}}} - } - } - ) - except APIError: - pass - if graphql_wattage is not None: - try: - return graphql_wattage["data"]["bosminer"]["info"]["workSolver"][ - "power" - ]["approxConsumptionW"] - except (LookupError, TypeError): - pass - - if not api_tunerstatus: - try: - api_tunerstatus = await self.api.tunerstatus() + grpc_miner_stats = self.web.grpc.get_miner_stats() except APIError: pass - if api_tunerstatus: + if grpc_miner_stats: try: - return api_tunerstatus["TUNERSTATUS"][0][ - "ApproximateMinerPowerConsumption" - ] - except LookupError: + return grpc_miner_stats["powerStats"]["approximatedConsumption"]["watt"] + except KeyError: pass async def _get_wattage_limit( - self, api_tunerstatus: dict = None, graphql_wattage_limit: dict = None + self, grpc_active_performance_mode: dict = None ) -> Optional[int]: - if not graphql_wattage_limit and not api_tunerstatus: + if grpc_active_performance_mode is None: try: - graphql_wattage_limit = await self.web.send_command( - {"bosminer": {"info": {"workSolver": {"power": {"limitW"}}}}} + grpc_active_performance_mode = ( + self.web.grpc.get_active_performance_mode() ) except APIError: pass - if graphql_wattage_limit: + if grpc_active_performance_mode: try: - return graphql_wattage_limit["data"]["bosminer"]["info"]["workSolver"][ - "power" - ]["limitW"] - except (LookupError, TypeError): + return grpc_active_performance_mode["tunerMode"]["powerTarget"][ + "powerTarget" + ]["watt"] + except KeyError: pass - if not api_tunerstatus: + async def get_fans(self, grpc_cooling_state: dict = None) -> List[Fan]: + if grpc_cooling_state is None: try: - api_tunerstatus = await self.api.tunerstatus() + grpc_cooling_state = self.web.grpc.get_cooling_state() except APIError: pass - if api_tunerstatus: - try: - return api_tunerstatus["TUNERSTATUS"][0]["PowerLimit"] - except LookupError: - pass - - async def _get_fans( - self, api_fans: dict = None, graphql_fans: dict = None - ) -> List[Fan]: - if not graphql_fans and not api_fans: - try: - graphql_fans = await self.web.send_command( - {"bosminer": {"info": {"fans": {"name", "rpm"}}}} - ) - except APIError: - pass - if graphql_fans.get("data"): + if grpc_cooling_state: fans = [] for n in range(self.expected_fans): try: - fans.append( - Fan( - speed=graphql_fans["data"]["bosminer"]["info"]["fans"][n][ - "rpm" - ] - ) - ) - except (LookupError, TypeError): - pass - return fans - - if not api_fans: - try: - api_fans = await self.api.fans() - except APIError: - pass - - if api_fans: - fans = [] - for n in range(self.expected_fans): - try: - fans.append(Fan(api_fans["FANS"][n]["RPM"])) + fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) except LookupError: pass return fans @@ -1290,56 +1011,7 @@ class BOSer(BaseMiner): async def _get_fan_psu(self) -> Optional[int]: return None - async def _get_errors( - self, api_tunerstatus: dict = None, graphql_errors: dict = None - ) -> List[MinerErrorData]: - if not graphql_errors and not api_tunerstatus: - try: - graphql_errors = await self.web.send_command( - { - "bosminer": { - "info": { - "workSolver": { - "childSolvers": { - "name": None, - "tuner": {"statusMessages"}, - } - } - } - } - } - ) - except APIError: - pass - - if graphql_errors: - errors = [] - try: - boards = graphql_errors["data"]["bosminer"]["info"]["workSolver"][ - "childSolvers" - ] - except (LookupError, TypeError): - boards = None - - if boards: - offset = 6 if int(boards[0]["name"]) in [6, 7, 8] else 0 - for hb in boards: - _id = int(hb["name"]) - offset - tuner = hb["tuner"] - if tuner: - if msg := tuner.get("statusMessages"): - if len(msg) > 0: - if hb["tuner"]["statusMessages"][0] not in [ - "Stable", - "Testing performance profile", - "Tuning individual chips", - ]: - errors.append( - BraiinsOSError( - f"Slot {_id} {hb['tuner']['statusMessages'][0]}" - ) - ) - + async def _get_errors(self, api_tunerstatus: dict = None) -> List[MinerErrorData]: if not api_tunerstatus: try: api_tunerstatus = await self.api.tunerstatus() @@ -1369,86 +1041,23 @@ class BOSer(BaseMiner): except LookupError: pass - async def _get_fault_light(self, graphql_fault_light: dict = None) -> bool: - if self.light: + async def _get_fault_light(self, grpc_locate_device_status: dict = None) -> bool: + if self.light is not None: return self.light - if not graphql_fault_light: - if self.fw_ver: - # fw version has to be greater than 21.09 and not 21.09 - if ( - int(self.fw_ver.split(".")[0]) == 21 - and int(self.fw_ver.split(".")[1]) > 9 - ) or int(self.fw_ver.split(".")[0]) > 21: - try: - graphql_fault_light = await self.web.send_command( - {"bos": {"faultLight"}} - ) - except APIError: - pass - else: - logging.info( - f"FW version {self.fw_ver} is too low for fault light info in graphql." - ) - else: - # worth trying - try: - graphql_fault_light = await self.web.send_command( - {"bos": {"faultLight"}} - ) - except APIError: - logging.debug( - "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" - ) - if not graphql_fault_light: - # also a failure - logging.debug( - "GraphQL fault light failed, likely due to version being too low (<=21.0.9)" - ) - - # get light through GraphQL - if graphql_fault_light: + if not grpc_locate_device_status: try: - self.light = graphql_fault_light["data"]["bos"]["faultLight"] - return self.light - except (TypeError, ValueError, LookupError): - pass - - # get light via ssh if that fails (10x slower) - try: - data = ( - await self.send_ssh_command("cat /sys/class/leds/'Red LED'/delay_off") - ).strip() - self.light = False - if data == "50": - self.light = True - return self.light - except (TypeError, AttributeError): - return self.light - - async def _get_expected_hashrate(self, api_devs: dict = None) -> Optional[float]: - if not api_devs: - try: - api_devs = await self.api.devs() + grpc_locate_device_status = ( + await self.web.grpc.get_locate_device_status() + ) except APIError: pass - if api_devs: + if grpc_locate_device_status: + if grpc_locate_device_status == {}: + return False try: - offset = 6 if api_devs["DEVS"][0]["ID"] in [6, 7, 8] else 0 - hr_list = [] - - for board in api_devs["DEVS"]: - _id = board["ID"] - offset - expected_hashrate = round(float(board["Nominal MHS"] / 1000000), 2) - if expected_hashrate: - hr_list.append(expected_hashrate) - if len(hr_list) == 0: - return 0 - else: - return round( - (sum(hr_list) / len(hr_list)) * self.expected_hashboards, 2 - ) + return grpc_locate_device_status["enabled"] except LookupError: pass diff --git a/pyasic/web/braiins_os/__init__.py b/pyasic/web/braiins_os/__init__.py index a884e0fa..95cc88df 100644 --- a/pyasic/web/braiins_os/__init__.py +++ b/pyasic/web/braiins_os/__init__.py @@ -98,17 +98,15 @@ class BOSerWebAPI(BOSMinerWebAPI): def select_command_type(command: Union[str, dict]) -> str: if isinstance(command, dict): return "gql" - elif command.startswith("grpc_"): - return "grpc" else: - return "luci" + return "grpc" async def multicommand( self, *commands: Union[dict, str], allow_warning: bool = True ) -> dict: - cmd_types = {"grpc": [], "gql": [], "luci": []} + cmd_types = {"grpc": [], "gql": []} for cmd in commands: - cmd_types[self.select_command_type(cmd)] = cmd + cmd_types[self.select_command_type(cmd)].append(cmd) async def no_op(): return {} @@ -118,21 +116,13 @@ class BOSerWebAPI(BOSMinerWebAPI): self.grpc.multicommand(*cmd_types["grpc"]) ) else: - grpc_data_t = no_op() + grpc_data_t = asyncio.create_task(no_op()) if len(cmd_types["gql"]) > 0: gql_data_t = asyncio.create_task(self.gql.multicommand(*cmd_types["gql"])) else: - gql_data_t = no_op() - if len(cmd_types["luci"]) > 0: - luci_data_t = asyncio.create_task( - self.luci.multicommand(*cmd_types["luci"]) - ) - else: - luci_data_t = no_op() + gql_data_t = asyncio.create_task(no_op()) - await asyncio.gather(grpc_data_t, gql_data_t, luci_data_t) + await asyncio.gather(grpc_data_t, gql_data_t) - data = dict( - **luci_data_t.result(), **gql_data_t.result(), **luci_data_t.result() - ) + data = dict(**grpc_data_t.result(), **gql_data_t.result()) return data diff --git a/pyasic/web/braiins_os/grpc.py b/pyasic/web/braiins_os/grpc.py index 821572e7..ff1afa26 100644 --- a/pyasic/web/braiins_os/grpc.py +++ b/pyasic/web/braiins_os/grpc.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and - # limitations under the License. - # ------------------------------------------------------------------------------ +import asyncio from datetime import timedelta from betterproto import Message @@ -64,7 +65,20 @@ class BOSerGRPCAPI: ] async def multicommand(self, *commands: str) -> dict: - pass + result = {"multicommand": True} + tasks = {} + for command in commands: + try: + tasks[command] = asyncio.create_task(getattr(self, command)()) + except AttributeError: + result["command"] = {} + + await asyncio.gather(*list(tasks.values())) + + for cmd in tasks: + result[cmd] = tasks[cmd].result() + + return result async def send_command( self, @@ -161,10 +175,12 @@ class BOSerGRPCAPI: ) async def get_tuner_state(self): - return await self.send_command("get_tuner_state") + return await self.send_command("get_tuner_state", GetTunerStateRequest()) async def list_target_profiles(self): - return await self.send_command("list_target_profiles") + return await self.send_command( + "list_target_profiles", ListTargetProfilesRequest() + ) async def set_default_power_target( self, save_action: SaveAction = SaveAction.SAVE_ACTION_SAVE_AND_APPLY From e45e51ce658eec1343049de78fabbd8a538ff108 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 13:09:23 -0700 Subject: [PATCH 34/36] 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) From ce34dfdde838707399714a8907cb48f5c5e11347 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 14:00:51 -0700 Subject: [PATCH 35/36] bug: fix fault_light check for boser. --- pyasic/miners/backends/braiins_os.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index ac280fc7..c1e05b81 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -1031,7 +1031,7 @@ class BOSer(BaseMiner): except APIError: pass - if grpc_locate_device_status: + if grpc_locate_device_status is not None: if grpc_locate_device_status == {}: return False try: From edaf89c73a2dfdd09e8558dd459da64c0b431a80 Mon Sep 17 00:00:00 2001 From: UpstreamData Date: Mon, 15 Jan 2024 14:18:41 -0700 Subject: [PATCH 36/36] refactor: fix some formatting issues and bugs. --- pyasic/config/power_scaling.py | 4 +--- pyasic/miners/backends/braiins_os.py | 12 +++++------- pyasic/miners/backends/cgminer_avalon.py | 5 ++--- pyasic/miners/backends/epic.py | 13 +++++-------- pyasic/miners/backends/innosilicon.py | 4 ++-- pyasic/miners/miner_factory.py | 1 + pyasic/miners/unknown.py | 1 - pyasic/web/epic.py | 4 ++-- tests/__init__.py | 2 -- 9 files changed, 18 insertions(+), 28 deletions(-) diff --git a/pyasic/config/power_scaling.py b/pyasic/config/power_scaling.py index 3c64ec26..08c48c44 100644 --- a/pyasic/config/power_scaling.py +++ b/pyasic/config/power_scaling.py @@ -20,7 +20,6 @@ from pyasic.config.base import MinerConfigOption, MinerConfigValue from pyasic.web.braiins_os.proto.braiins.bos.v1 import ( DpsPowerTarget, DpsTarget, - Hours, Power, SetDpsRequest, ) @@ -210,9 +209,8 @@ class PowerScalingConfig(MinerConfigOption): except LookupError: return cls.default() - conf = {} + conf = {"shutdown_enabled": PowerScalingShutdown.from_boser(dps_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: diff --git a/pyasic/miners/backends/braiins_os.py b/pyasic/miners/backends/braiins_os.py index c1e05b81..28c0a375 100644 --- a/pyasic/miners/backends/braiins_os.py +++ b/pyasic/miners/backends/braiins_os.py @@ -16,8 +16,7 @@ import asyncio import logging import time -from collections import namedtuple -from typing import List, Optional, Tuple, Union +from typing import List, Optional, Union import toml @@ -32,7 +31,6 @@ from pyasic.miners.base import ( DataFunction, DataLocations, DataOptions, - GraphQLCommand, GRPCCommand, RPCAPICommand, WebAPICommand, @@ -537,13 +535,13 @@ class BOSMiner(BaseMiner): if api_fans: fans = [] - for n in range(self.fan_count): + for n in range(self.expected_fans): try: fans.append(Fan(api_fans["FANS"][n]["RPM"])) except (IndexError, KeyError): pass return fans - return [Fan() for _ in range(self.fan_count)] + return [Fan() for _ in range(self.expected_fans)] async def _get_fan_psu(self) -> Optional[int]: return None @@ -978,13 +976,13 @@ class BOSer(BaseMiner): if grpc_cooling_state: fans = [] - for n in range(self.fan_count): + for n in range(self.expected_fans): try: fans.append(Fan(grpc_cooling_state["fans"][n]["rpm"])) except (IndexError, KeyError): pass return fans - return [Fan() for _ in range(self.fan_count)] + return [Fan() for _ in range(self.expected_fans)] 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 c681cd12..84d4f54b 100644 --- a/pyasic/miners/backends/cgminer_avalon.py +++ b/pyasic/miners/backends/cgminer_avalon.py @@ -14,7 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import logging import re from typing import List, Optional @@ -315,7 +314,7 @@ class CGMinerAvalon(CGMiner): except APIError: pass - fans_data = [Fan() for _ in range(self.fan_count)] + fans_data = [Fan() for _ in range(self.expected_fans)] if api_stats: try: unparsed_stats = api_stats["STATS"][0]["MM ID0"] @@ -323,7 +322,7 @@ class CGMinerAvalon(CGMiner): except LookupError: return fans_data - for fan in range(self.fan_count): + for fan in range(self.expected_fans): try: fans_data[fan].speed = int(parsed_stats[f"Fan{fan + 1}"]) except (IndexError, KeyError, ValueError, TypeError): diff --git a/pyasic/miners/backends/epic.py b/pyasic/miners/backends/epic.py index a4519133..979a65ab 100644 --- a/pyasic/miners/backends/epic.py +++ b/pyasic/miners/backends/epic.py @@ -151,7 +151,7 @@ class ePIC(BaseMiner): try: for network in web_network: mac = web_network[network]["mac_address"] - return mac + return mac except KeyError: pass @@ -188,7 +188,7 @@ class ePIC(BaseMiner): if web_summary: try: hashrate = 0 - if web_summary["HBs"] != None: + if web_summary["HBs"] is not None: for hb in web_summary["HBs"]: hashrate += hb["Hashrate"][0] return round(float(float(hashrate / 1000000)), 2) @@ -207,7 +207,7 @@ class ePIC(BaseMiner): if web_summary: try: hashrate = 0 - if web_summary["HBs"] != None: + if web_summary["HBs"] is not None: for hb in web_summary["HBs"]: if hb["Hashrate"][1] == 0: ideal = 1.0 @@ -266,7 +266,7 @@ class ePIC(BaseMiner): HashBoard(slot=i, expected_chips=self.expected_chips) for i in range(self.expected_hashboards) ] - if web_summary["HBs"] != None: + if web_summary["HBs"] is not None: for hb in web_summary["HBs"]: for hr in web_hashrate: if hr["Index"] == hb["Index"]: @@ -312,7 +312,7 @@ class ePIC(BaseMiner): if web_summary: try: error = web_summary["Status"]["Last Error"] - if error != None: + if error is not None: errors.append(X19Error(str(error))) return errors except KeyError: @@ -328,9 +328,6 @@ class ePIC(BaseMiner): def _get_api_ver(self, *args, **kwargs) -> Optional[str]: pass - def get_config(self) -> MinerConfig: - return self.config - def _get_env_temp(self, *args, **kwargs) -> Optional[float]: pass diff --git a/pyasic/miners/backends/innosilicon.py b/pyasic/miners/backends/innosilicon.py index 9e85525a..67a8e77d 100644 --- a/pyasic/miners/backends/innosilicon.py +++ b/pyasic/miners/backends/innosilicon.py @@ -340,7 +340,7 @@ class Innosilicon(CGMiner): else: web_get_all = web_get_all["all"] - fans = [Fan() for _ in range(self.fan_count)] + fans = [Fan() for _ in range(self.expected_fans)] if web_get_all: try: spd = web_get_all["fansSpeed"] @@ -348,7 +348,7 @@ class Innosilicon(CGMiner): pass else: round((int(spd) * 6000) / 100) - for i in range(self.fan_count): + for i in range(self.expected_fans): fans[i].speed = spd return fans diff --git a/pyasic/miners/miner_factory.py b/pyasic/miners/miner_factory.py index 725d091f..4987db02 100644 --- a/pyasic/miners/miner_factory.py +++ b/pyasic/miners/miner_factory.py @@ -476,6 +476,7 @@ class MinerFactory: fn = miner_model_fns.get(miner_type) if fn is not None: + # noinspection PyArgumentList task = asyncio.create_task(fn(ip)) try: miner_model = await asyncio.wait_for( diff --git a/pyasic/miners/unknown.py b/pyasic/miners/unknown.py index ddffdc2c..6e443226 100644 --- a/pyasic/miners/unknown.py +++ b/pyasic/miners/unknown.py @@ -33,7 +33,6 @@ class UnknownMiner(BaseMiner): super().__init__(ip) self.ip = ip self.api = UnknownAPI(ip) - self.model = "Unknown" def __repr__(self) -> str: return f"Unknown: {str(self.ip)}" diff --git a/pyasic/web/epic.py b/pyasic/web/epic.py index 62f0c82e..684648b9 100644 --- a/pyasic/web/epic.py +++ b/pyasic/web/epic.py @@ -89,10 +89,10 @@ class ePICWebAPI(BaseWebAPI): return data async def restart_epic(self) -> dict: - return await self.send_command("softreboot", post=True, parameters=None) + return await self.send_command("softreboot", post=True) async def reboot(self) -> dict: - return await self.send_command("reboot", post=True, parameters=None) + return await self.send_command("reboot", post=True) async def pause_mining(self) -> dict: return await self.send_command("miner", post=True, parameters="Stop") diff --git a/tests/__init__.py b/tests/__init__.py index 0c5048c4..17b6c644 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -14,8 +14,6 @@ # limitations under the License. - # ------------------------------------------------------------------------------ -import unittest - from tests.api_tests import * from tests.config_tests import TestConfig from tests.miners_tests import MinersTest