def retry_wrapper(self, *args, **kwargs): """ Args: self (Hermit): """ init = None for _ in range(RETRY_TRIES): try: if callable(init): self.sleep(RETRY_DELAY) init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_reconnect() # When unable to send requests except requests.exceptions.ConnectionError as e: logger.error(e) text = str(e) if 'Connection aborted' in text: # Hermit not installed or not running # ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response')) def init(): self.adb_reconnect() self.hermit_init() else: # Lost connection, adb server was killed # HTTPConnectionPool(host='127.0.0.1', port=20269): # Max retries exceeded with url: /click?x=500&y=500 def init(): self.adb_reconnect() # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_reconnect() else: break # HermitError: {"code":-1,"msg":"error"} except HermitError as e: logger.error(e) def init(): self.adb_reconnect() self.hermit_init() # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover
def run(self, command): try: self.__getattribute__(command)() return True except TaskEnd: return True except GameNotRunningError as e: logger.warning(e) self.config.task_call('Restart') return True except (GameStuckError, GameTooManyClickError) as e: logger.warning(e) self.save_error_log() logger.warning(f'Game stuck, {self.config.Emulator_PackageName} will be restarted in 10 seconds') logger.warning('If you are playing by hand, please stop Alas') self.config.task_call('Restart') self.device.sleep(10) return False except LogisticsRefreshBugHandler as e: logger.warning(e) self.save_error_log() self.config.task_call('Restart') self.device.sleep(10) return False except ScriptError as e: logger.critical(e) logger.critical('This is likely to be a mistake of developers, but sometimes just random issues') exit(1) except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) self.save_error_log() exit(1)
def on_task_exception(self): logger.exception("An internal error occurred in the application") toast_msg = ("应用发生内部错误" if "zh" in session_info.user_language else "An internal error occurred in the application") e_type, e_value, e_tb = sys.exc_info() lines = traceback.format_exception(e_type, e_value, e_tb) traceback_msg = "".join(lines) traceback_console = Console(color_system="truecolor", tab_size=2, record=True, width=90) with traceback_console.capture(): # prevent logging to stdout again traceback_console.print_exception(word_wrap=True, extra_lines=1, show_locals=True) if State.theme == "dark": theme = DARK_TERMINAL_THEME else: theme = LIGHT_TERMINAL_THEME html = traceback_console.export_html(theme=theme, code_format=TRACEBACK_CODE_FORMAT, inline_styles=True) try: popup(title=toast_msg, content=put_html(html), size=PopupSize.LARGE) run_js( "console.error(traceback_msg)", traceback_msg="Internal Server Error\n" + traceback_msg, ) except Exception: pass
def handle_adb_error(e): """ Args: e (Exception): Returns: bool: If should retry """ text = str(e) if 'not found' in text: # When you call `adb disconnect <serial>` # Or when adb server was killed (low possibility) # AdbError(device '127.0.0.1:59865' not found) logger.error(e) return True elif 'timeout' in text: # AdbTimeout(adb read timeout) logger.error(e) return True elif 'closed' in text: # AdbError(closed) # Usually after AdbTimeout(adb read timeout) # Disconnect and re-connect should fix this. logger.error(e) return True else: # AdbError(device offline) # AdbError() logger.exception(e) possible_reasons( 'If you are using BlueStacks or LD player or WSA, please enable ADB in the settings of your emulator', 'Emulator died, please restart emulator', 'Serial incorrect, no such device exists or emulator is not running' ) return False
def run(self, command): logger.attr('Command', command) self.config.start_time = datetime.now() self.device = Device(config=self.config) while 1: try: self.__getattribute__(command.lower())() break except GameNotRunningError as e: logger.warning(e) az = LoginHandler(self.config, device=self.device) az.app_restart() az.ensure_no_unfinished_campaign() continue except GameTooManyClickError as e: logger.warning(e) az = LoginHandler(self.config, device=self.device) az.app_restart() az.ensure_no_unfinished_campaign() continue except GameStuckError as e: logger.warning(e) self.save_error_log() az = LoginHandler(self.config, device=self.device) az.handle_game_stuck() continue except Exception as e: logger.exception(e) self.save_error_log() break
def extract_drop(self, campaign): """ Parse images from a given folder. Args: campaign (str): """ print('') logger.hr(f'extract drops from {campaign}', level=1) _ = self.csv_overwrite_check with open(self.csv_file, 'a', newline='', encoding=DropStatistics.CSV_ENCODING) as csv_file: writer = csv.writer(csv_file) for ts, file in tqdm( load_folder(self.drop_folder(campaign)).items()): try: rows = list(self.parse_drop(file)) writer.writerows(rows) except ImageError as e: logger.warning(e) continue except Exception as e: logger.exception(e) logger.warning(f'Error on image {ts}') continue
def run(self, command): self.config.start_time = datetime.now() try: self.device = Device(config=self.config) self.__getattribute__(command.lower())() except Exception as e: logger.exception(e) if self.config.ENABLE_ERROR_LOG_AND_SCREENSHOT_SAVE: folder = f'./log/error/{int(time.time() * 1000)}' logger.info(f'Saving error: {folder}') os.mkdir(folder) for data in logger.screenshot_deque: image_time = datetime.strftime(data['time'], '%Y-%m-%d_%H-%M-%S-%f') image = handle_sensitive_image(data['image']) image.save(f'{folder}/{image_time}.png') with open(log_file, 'r') as f: start = 0 for index, line in enumerate(f.readlines()): if re.search('\+-{15,}\+', line): start = index with open(log_file, 'r') as f: text = f.readlines()[start - 2:] text = handle_sensitive_logs(text) with open(f'{folder}/log.txt', 'w') as f: f.writelines(text)
def loop(self) -> None: """ Start task loop. You **should** run this function in an individual thread. """ self._alive = True while self._alive: if self.tasks: with self._lock: self.tasks.sort(key=operator.attrgetter("next_run")) task = self.tasks[0] if task.next_run < time.time(): start_time = time.time() try: self._task = task # logger.debug(f'Start task {task.g.__name__}') task.send(self) # logger.debug(f'End task {task.g.__name__}') except Exception as e: logger.exception(e) self.remove_task(task, nowait=True) finally: self._task = None end_time = time.time() task.next_run += task.delay with self._lock: for task in self.tasks: task.next_run += end_time - start_time else: time.sleep(0.05) else: time.sleep(0.5) logger.info("End of task handler loop")
def device(self): try: from module.device.device import Device device = Device(config=self.config) return device except Exception as e: logger.exception(e) exit(1)
def config(self): try: config = AzurLaneConfig(config_name=self.config_name) return config except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def device(self): try: from module.device.device import Device device = Device(config=self.config) return device except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def config(self): try: config = AzurLaneConfig(config_name=self.config_name) # Set server before loading any buttons. server.server = deep_get(config.data, keys='Alas.Emulator.Server', default='cn') return config except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) exit(1)
def start_remote_access_service_(**kwargs): logger.info("Start remote access service") try: remote_access_service(**kwargs) except KeyboardInterrupt: # ignore KeyboardInterrupt pass except Exception as e: logger.exception(e) finally: if _ssh_process: logger.info("Exception occurred, killing ssh process") _ssh_process.kill() logger.info("Exit remote access service thread")
def run_process(config_name, func: str, q: queue.Queue, e: threading.Event = None) -> None: # Setup logger set_file_logger(name=config_name) set_func_logger(func=q.put) # Set server before loading any buttons. import module.config.server as server from module.config.config import AzurLaneConfig AzurLaneConfig.stop_event = e config = AzurLaneConfig(config_name=config_name) server.server = deep_get(config.data, keys="Alas.Emulator.Server", default="cn") try: # Run alas if func == "Alas": from alas import AzurLaneAutoScript if e is not None: AzurLaneAutoScript.stop_event = e AzurLaneAutoScript(config_name=config_name).loop() elif func == "Daemon": from module.daemon.daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task="Daemon").run() elif func == "OpsiDaemon": from module.daemon.os_daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task="OpsiDaemon").run() elif func == "AzurLaneUncensored": from module.daemon.uncensored import AzurLaneUncensored AzurLaneUncensored(config=config_name, task="AzurLaneUncensored").run() elif func == "Benchmark": from module.daemon.benchmark import Benchmark Benchmark(config=config_name, task="Benchmark").run() elif func == "GameManager": from module.daemon.game_manager import GameManager GameManager(config=config_name, task="GameManager").run() else: logger.critical("No function matched") logger.info(f"[{config_name}] exited. Reason: Finish\n") except Exception as e: logger.exception(e)
def _alas_thread_wait_config_change(self) -> None: paths = [] for path, d in deep_iter(self.ALAS_ARGS, depth=3): if d["type"] in ["lock", "disable", "hide"]: continue paths.append(self.path_to_idx[".".join(path)]) while self.alive: try: val = pin_wait_change(*paths) self.modified_config_queue.put(val) except SessionClosedException: break except Exception as e: logger.exception(e)
def keep_ssh_alive(): task_handler: TaskHandler task_handler = yield while True: if _ssh_thread is not None and _ssh_thread.is_alive(): yield continue logger.info("Remote access service is not running, starting now") try: start_remote_access_service() except ParseError as e: logger.exception(e) task_handler.remove_current_task() yield
def handle_adb_error(e): """ Args: e (Exception): Returns: bool: If should retry """ text = str(e) if 'not found' in text: # When you call `adb disconnect <serial>` # Or when adb server was killed (low possibility) # AdbError(device '127.0.0.1:59865' not found) logger.error(e) return True elif 'timeout' in text: # AdbTimeout(adb read timeout) logger.error(e) return True elif 'closed' in text: # AdbError(closed) # Usually after AdbTimeout(adb read timeout) # Disconnect and re-connect should fix this. logger.error(e) return True elif 'device offline' in text: # AdbError(device offline) # When a device that has been connected wirelessly is disconnected passively, # it does not disappear from the adb device list, # but will be displayed as offline. # In many cases, such as disconnection and recovery caused by network fluctuations, # or after VMOS reboot when running Alas on a phone, # the device is still available, but it needs to be disconnected and re-connected. logger.error(e) return True elif 'unknown host service' in text: # AdbError(unknown host service) # Another version of ADB service started, current ADB service has been killed. # Usually because user opened a Chinese emulator, which uses ADB from the Stone Age. logger.error(e) return True else: # AdbError() logger.exception(e) possible_reasons( 'If you are using BlueStacks or LD player or WSA, please enable ADB in the settings of your emulator', 'Emulator died, please restart emulator', 'Serial incorrect, no such device exists or emulator is not running' ) return False
def run_process(config_name, func: str, q: queue.Queue, e: threading.Event = None) -> None: # Setup logger qh = QueueHandler(q) formatter = logging.Formatter( fmt='%(asctime)s.%(msecs)03d | %(levelname)s | %(message)s', datefmt='%H:%M:%S') webconsole = logging.StreamHandler(stream=qh) webconsole.setFormatter(formatter) logger.addHandler(webconsole) # Set server before loading any buttons. import module.config.server as server from module.config.config import AzurLaneConfig AzurLaneConfig.stop_event = e config = AzurLaneConfig(config_name=config_name) server.server = deep_get(config.data, keys='Alas.Emulator.Server', default='cn') try: # Run alas if func == 'Alas': from alas import AzurLaneAutoScript if e is not None: AzurLaneAutoScript.stop_event = e AzurLaneAutoScript(config_name=config_name).loop() elif func == 'Daemon': from module.daemon.daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task='Daemon').run() elif func == 'OpsiDaemon': from module.daemon.os_daemon import AzurLaneDaemon AzurLaneDaemon(config=config_name, task='OpsiDaemon').run() elif func == 'AzurLaneUncensored': from module.daemon.uncensored import AzurLaneUncensored AzurLaneUncensored(config=config_name, task='AzurLaneUncensored').run() elif func == 'Benchmark': from module.daemon.benchmark import Benchmark Benchmark(config=config_name, task='Benchmark').run() elif func == 'GameManager': from module.daemon.game_manager import GameManager GameManager(config=config_name, task='GameManager').run() else: logger.critical("No function matched") logger.info(f"[{config_name}] exited. Reason: Finish") except Exception as e: logger.exception(e)
def run(self, command): try: self.device.screenshot() self.__getattribute__(command)() return True except TaskEnd: return True except GameNotRunningError as e: logger.warning(e) self.config.task_call('Restart') return True except (GameStuckError, GameTooManyClickError) as e: logger.warning(e) self.save_error_log() logger.warning( f'Game stuck, {self.config.Emulator_PackageName} will be restarted in 10 seconds' ) logger.warning('If you are playing by hand, please stop Alas') self.config.task_call('Restart') self.device.sleep(10) return False except GameBugError as e: logger.warning(e) self.save_error_log() logger.warning( 'An error has occurred in Azur Lane game client, Alas is unable to handle' ) logger.warning( f'Restarting {self.config.Emulator_PackageName} to fix it') self.config.task_call('Restart') self.device.sleep(10) return False except GamePageUnknownError: logger.critical('Game page unknown') self.save_error_log() exit(1) except ScriptError as e: logger.critical(e) logger.critical( 'This is likely to be a mistake of developers, but sometimes just random issues' ) exit(1) except RequestHumanTakeover: logger.critical('Request human takeover') exit(1) except Exception as e: logger.exception(e) self.save_error_log() exit(1)
def _save_config(self, modified: Dict[str, str], config_name: str) -> None: try: valid = [] invalid = [] config = State.config_updater.read_file(config_name) for k, v in modified.copy().items(): valuetype = deep_get(self.ALAS_ARGS, k + ".valuetype") v = parse_pin_value(v, valuetype) validate = deep_get(self.ALAS_ARGS, k + ".validate") if not len(str(v)): default = deep_get(self.ALAS_ARGS, k + ".value") deep_set(config, k, default) valid.append(self.path_to_idx[k]) modified[k] = default elif not validate or re_fullmatch(validate, v): deep_set(config, k, v) valid.append(self.path_to_idx[k]) # update Emotion Record if Emotion Value is changed if "Emotion" in k and "Value" in k: k = k.split(".") k[-1] = k[-1].replace("Value", "Record") k = ".".join(k) v = datetime.now().strftime("%Y-%m-%d %H:%M:%S") modified[k] = v deep_set(config, k, v) valid.append(self.path_to_idx[k]) pin[self.path_to_idx[k]] = v else: modified.pop(k) invalid.append(self.path_to_idx[k]) logger.warning(f"Invalid value {v} for key {k}, skip saving.") self.pin_remove_invalid_mark(valid) self.pin_set_invalid_mark(invalid) if modified: toast( t("Gui.Toast.ConfigSaved"), duration=1, position="right", color="success", ) logger.info( f"Save config {filepath_config(config_name)}, {dict_to_kv(modified)}" ) State.config_updater.write_file(config_name, config) except Exception as e: logger.exception(e)
def retry_wrapper(self, *args, **kwargs): """ Args: self (AScreenCap): """ init = None for _ in range(RETRY_TRIES): try: if callable(init): self.sleep(RETRY_DELAY) init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) # When ascreencap is not installed except AscreencapError as e: logger.error(e) def init(): self.ascreencap_init() # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) else: break # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover
def extract_template(self, campaign): """ Extract images from a given folder. Args: campaign (str): """ print('') logger.hr(f'Extract templates from {campaign}', level=1) for ts, file in tqdm(load_folder(self.drop_folder(campaign)).items()): try: self.parse_template(file) except ImageError as e: logger.warning(e) continue except Exception as e: logger.exception(e) logger.warning(f'Error on image {ts}') continue
def query_search(self): logger.info("开始执行Google查询,查询时间可能较长,建议耐心等待") try: # 从查询列表里循环查询语句,查询并拿出结果 for i in self.search_list: logger.info("正在查询语句:%s" % i) final_result = GoogleSearch().search(i, proxy_list=proxy_List) # 两次查询的时间间隔 time.sleep(time_sleep) if final_result.results: self.search_result.append(final_result.results) # 将搜索出的结果记录到日志中 for j in range(len(final_result.results)): logger.info("查询结果已经记录:{} {}".format( final_result.results[j].title, final_result.results[j].url)) except KeyboardInterrupt as reason: logger.exception(reason) logger.info("查询过程已结束")
def benchmark_test(self, func, *args, **kwargs): """ Args: func: Function to test. *args: Passes to func. **kwargs: Passes to func. Returns: float: Time cost on average. """ logger.hr(f'Benchmark test', level=2) logger.info(f'Testing function: {func.__name__}') record = [] for n in range(1, self.TEST_TOTAL + 1): start = time.time() try: func(*args, **kwargs) except RequestHumanTakeover: logger.critical('RequestHumanTakeover') logger.warning( f'Benchmark tests failed on func: {func.__name__}') return 'Failed' except Exception as e: logger.exception(e) logger.warning( f'Benchmark tests failed on func: {func.__name__}') return 'Failed' cost = time.time() - start logger.attr(f'{str(n).rjust(2, "0")}/{self.TEST_TOTAL}', f'{float2str(cost)}') record.append(cost) logger.info('Benchmark tests done') average = float(np.mean(np.sort(record)[:self.TEST_BEST])) logger.info( f'Time cost {float2str(average)} ({self.TEST_BEST} best results out of {self.TEST_TOTAL} tests)' ) return average
def _save(self, image, genre, timestamp): """ Args: image: Image to save. genre (str): Name of sub folder. timestamp (int): Millisecond timestamp. Returns: bool: If success """ try: folder = os.path.join(str(self.config.DropRecord_SaveFolder), genre) os.makedirs(folder, exist_ok=True) file = os.path.join(folder, f'{timestamp}.png') image.save(file) logger.info(f'Image save success, file: {file}') return True except Exception as e: logger.exception(e) return False
def _save(self, image, genre, filename): """ Args: image: Image to save. genre (str): Name of sub folder. filename (str): 'xxx.png' Returns: bool: If success """ try: folder = os.path.join(str(self.config.DropRecord_SaveFolder), genre) os.makedirs(folder, exist_ok=True) file = os.path.join(folder, filename) save_image(image, file) logger.info(f'Image save success, file: {file}') return True except Exception as e: logger.exception(e) return False
def _check_update_(self) -> bool: """ Deprecated """ self.state = 'checking' r = self.repo.split('/') owner = r[3] repo = r[4] if 'gitee' in r[2]: base = "https://gitee.com/api/v5/repos/" headers = {} token = self.config['ApiToken'] if token: para = {"access_token": token} else: base = "https://api.github.com/repos/" headers = { 'Accept': 'application/vnd.github.v3.sha' } para = {} token = self.config['ApiToken'] if token: headers['Authorization'] = 'token ' + token try: list_commit = requests.get( base + f"{owner}/{repo}/branches/{self.branch}", headers=headers, params=para) except Exception as e: logger.exception(e) logger.warning("Check update failed") return 0 if list_commit.status_code != 200: logger.warning( f"Check update failed, code {list_commit.status_code}") return 0 try: sha = list_commit.json()['commit']['sha'] except Exception as e: logger.exception(e) logger.warning("Check update failed when parsing return json") return 0 local_sha, _, _, _ = self._get_local_commit() if sha == local_sha: logger.info("No update") return 0 try: get_commit = requests.get( base + f"{owner}/{repo}/commits/" + local_sha, headers=headers, params=para) except Exception as e: logger.exception(e) logger.warning("Check update failed") return 0 if get_commit.status_code != 200: # for develops logger.info( f"Cannot find local commit {local_sha[:8]} in upstream, skip update") return 0 logger.info(f"Update {sha[:8]} avaliable") return 1
def retry_wrapper(self, *args, **kwargs): """ Args: self (Minitouch): """ init = None sleep = True for _ in range(RETRY_TRIES): try: if callable(init): if sleep: self.sleep(RETRY_DELAY) sleep = True init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) # Emulator closed except ConnectionAbortedError as e: logger.error(e) def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) # MinitouchNotInstalledError: Received empty data from minitouch except MinitouchNotInstalledError as e: logger.error(e) sleep = False def init(): self.install_uiautomator2() if self._minitouch_port: self.adb_forward_remove(f'tcp:{self._minitouch_port}') del_cached_property(self, 'minitouch_builder') # MinitouchOccupiedError: Timeout when connecting to minitouch except MinitouchOccupiedError as e: logger.error(e) sleep = False def init(): self.restart_atx() if self._minitouch_port: self.adb_forward_remove(f'tcp:{self._minitouch_port}') del_cached_property(self, 'minitouch_builder') # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) else: break except BrokenPipeError as e: logger.error(e) def init(): del_cached_property(self, 'minitouch_builder') # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover
def _check_update_(self) -> bool: """ Deprecated """ self.state = "checking" r = self.Repository.split("/") owner = r[3] repo = r[4] if "gitee" in r[2]: base = "https://gitee.com/api/v5/repos/" headers = {} token = self.config["ApiToken"] if token: para = {"access_token": token} else: base = "https://api.github.com/repos/" headers = {"Accept": "application/vnd.github.v3.sha"} para = {} token = self.config["ApiToken"] if token: headers["Authorization"] = "token " + token try: list_commit = requests.get( base + f"{owner}/{repo}/branches/{self.Branch}", headers=headers, params=para, ) except Exception as e: logger.exception(e) logger.warning("Check update failed") return 0 if list_commit.status_code != 200: logger.warning( f"Check update failed, code {list_commit.status_code}") return 0 try: sha = list_commit.json()["commit"]["sha"] except Exception as e: logger.exception(e) logger.warning("Check update failed when parsing return json") return 0 local_sha, _, _, _ = self._get_local_commit() if sha == local_sha: logger.info("No update") return 0 try: get_commit = requests.get( base + f"{owner}/{repo}/commits/" + local_sha, headers=headers, params=para, ) except Exception as e: logger.exception(e) logger.warning("Check update failed") return 0 if get_commit.status_code != 200: # for develops logger.info( f"Cannot find local commit {local_sha[:8]} in upstream, skip update" ) return 0 logger.info(f"Update {sha[:8]} available") return 1
def retry_wrapper(self, *args, **kwargs): """ Args: self (Uiautomator2): """ init = None sleep = True for _ in range(RETRY_TRIES): try: if callable(init): if sleep: self.sleep(RETRY_DELAY) sleep = True init() return func(self, *args, **kwargs) # Can't handle except RequestHumanTakeover: break # When adb server was killed except ConnectionResetError as e: logger.error(e) def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) # In `device.set_new_command_timeout(604800)` # json.decoder.JSONDecodeError: Expecting value: line 1 column 2 (char 1) except JSONDecodeError as e: logger.error(e) sleep = False def init(): self.install_uiautomator2() # AdbError except AdbError as e: if handle_adb_error(e): def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) else: break # RuntimeError: USB device 127.0.0.1:5555 is offline except RuntimeError as e: if handle_adb_error(e): def init(): self.adb_disconnect(self.serial) self.adb_connect(self.serial) else: break # In `assert c.read string(4) == _OKAY` # ADB on emulator not enabled except AssertionError as e: logger.exception(e) possible_reasons( 'If you are using BlueStacks or LD player, ' 'please enable ADB in the settings of your emulator') break # Unknown, probably a trucked image except Exception as e: logger.exception(e) def init(): pass logger.critical(f'Retry {func.__name__}() failed') raise RequestHumanTakeover