简化重试函数

This commit is contained in:
Elaina 2024-12-01 14:50:39 +08:00
parent 7eb8655724
commit 23c1109cae
6 changed files with 100 additions and 109 deletions

View file

@ -18,45 +18,49 @@ from mower.utils.device.method.mumu_ipc import (
) )
from mower.utils.device.method.scrcpy import Scrcpy from mower.utils.device.method.scrcpy import Scrcpy
from mower.utils.device.simulator import restart_simulator from mower.utils.device.simulator import restart_simulator
from mower.utils.device.utils import RETRY_TRIES, retry_sleep
from mower.utils.image import bytes2img from mower.utils.image import bytes2img
from mower.utils.log import logger, save_screenshot from mower.utils.log import logger, save_screenshot
def retry_adb(func):
@wraps(func)
def retry_wrapper(self, *args, **kwargs):
for attempt in range(2):
try:
return func(self, *args, **kwargs)
except MowerExit:
raise
except ADBError as e:
logger.warning(f"ADB error, retrying... ({attempt + 1}/3)")
logger.exception(e)
self.adb.adb_reconnect()
except Exception as e:
raise Exception(e)
# After adb retries fail, escalate to simulator error
raise SimulatorError("ADB failed after 2 attempts.")
return retry_wrapper
def retry(func): def retry(func):
@wraps(func) @wraps(func)
def retry_wrapper(self, *args, **kwargs): def retry_wrapper(self, *args, **kwargs):
init = None for attempt in range(2):
for _ in range(RETRY_TRIES):
try: try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except MowerExit: except MowerExit:
break raise
except SimulatorError as e:
logger.exception(e)
def init():
restart_simulator()
except GameError as e: except GameError as e:
logger.warning(f"Game error, retrying... ({attempt + 1}/2)")
logger.exception(e) logger.exception(e)
self.check_current_focus()
def init(): except SimulatorError as e:
self.check_current_focus() logger.warning(f"Simulator error, retrying... ({attempt + 1}/2)")
except ADBError as e:
logger.exception(e) logger.exception(e)
restart_simulator()
def init():
self.adb.adb_reconnect()
except Exception as e: except Exception as e:
logger.exception(e) logger.exception(e)
restart_simulator()
def init():
pass
logger.error(f"Retry {func.__name__}() failed")
raise MowerExit raise MowerExit
return retry_wrapper return retry_wrapper
@ -85,7 +89,6 @@ class Device:
def run(self, cmd: str) -> Optional[bytes]: def run(self, cmd: str) -> Optional[bytes]:
return self.adb.adb_shell(cmd) return self.adb.adb_shell(cmd)
@retry
def launch(self) -> None: def launch(self) -> None:
"""launch the application""" """launch the application"""
logger.info("明日方舟,启动!") logger.info("明日方舟,启动!")
@ -101,13 +104,11 @@ class Device:
f"monkey --pct-syskeys 0 -p {config.conf.APPNAME} -c android.intent.category.LAUNCHER 1" f"monkey --pct-syskeys 0 -p {config.conf.APPNAME} -c android.intent.category.LAUNCHER 1"
) )
@retry
def exit(self) -> None: def exit(self) -> None:
"""exit the application""" """exit the application"""
logger.info("退出游戏") logger.info("退出游戏")
self.run(f"am force-stop {config.conf.APPNAME}") self.run(f"am force-stop {config.conf.APPNAME}")
@retry
def home(self) -> None: def home(self) -> None:
"""back to home""" """back to home"""
logger.info("返回模拟器主页") logger.info("返回模拟器主页")
@ -116,12 +117,12 @@ class Device:
) )
@retry @retry
@retry_adb
def send_keyevent(self, keycode: int) -> None: def send_keyevent(self, keycode: int) -> None:
"""send a key event""" """send a key event"""
logger.debug(keycode) logger.debug(keycode)
self.scrcpy.control.send_keyevent(keycode) self.scrcpy.control.send_keyevent(keycode)
@retry
def send_text(self, text: str) -> None: def send_text(self, text: str) -> None:
"""send a text""" """send a text"""
logger.debug(repr(text)) logger.debug(repr(text))
@ -139,6 +140,7 @@ class Device:
return bytes2img(data) return bytes2img(data)
@retry @retry
@retry_adb
def screencap(self) -> bytes: def screencap(self) -> bytes:
start_time = datetime.now() start_time = datetime.now()
min_time = config.screenshot_time + timedelta( min_time = config.screenshot_time + timedelta(
@ -176,14 +178,12 @@ class Device:
save_screenshot(img) save_screenshot(img)
return img return img
@retry
def current_focus(self) -> str: def current_focus(self) -> str:
"""detect current focus app""" """detect current focus app"""
command = "dumpsys window | grep mCurrentFocus" command = "dumpsys window | grep mCurrentFocus"
line = self.run(command) line = self.run(command)
return line.strip()[:-1].split(" ")[-1] return line.strip()[:-1].split(" ")[-1]
@retry
def display_frames(self) -> tuple[int, int, int]: def display_frames(self) -> tuple[int, int, int]:
"""get display frames if in compatibility mode""" """get display frames if in compatibility mode"""
if not config.MNT_COMPATIBILITY_MODE: if not config.MNT_COMPATIBILITY_MODE:
@ -196,17 +196,20 @@ class Device:
return int(res[2]), int(res[4]), int(res[6]) return int(res[2]), int(res[4]), int(res[6])
@retry @retry
@retry_adb
def tap(self, point: tuple[int, int]) -> None: def tap(self, point: tuple[int, int]) -> None:
"""tap""" """tap"""
logger.debug(point) logger.debug(point)
self.scrcpy.tap(point[0], point[1]) self.scrcpy.tap(point[0], point[1])
@retry @retry
@retry_adb
def back(self): def back(self):
logger.debug("back") logger.debug("back")
self.scrcpy.back() self.scrcpy.back()
@retry @retry
@retry_adb
def swipe( def swipe(
self, start: tuple[int, int], end: tuple[int, int], duration: int = 100 self, start: tuple[int, int], end: tuple[int, int], duration: int = 100
) -> None: ) -> None:
@ -215,6 +218,7 @@ class Device:
self.scrcpy.swipe(start[0], start[1], end[0], end[1], duration / 1000) self.scrcpy.swipe(start[0], start[1], end[0], end[1], duration / 1000)
@retry @retry
@retry_adb
def swipe_ext( def swipe_ext(
self, self,
points: list[tp.Coordinate], points: list[tp.Coordinate],
@ -238,7 +242,6 @@ class Device:
logger.debug(f"{points=} {durations=} {update=} {interval=} {func=}") logger.debug(f"{points=} {durations=} {update=} {interval=} {func=}")
return self.scrcpy.swipe_ext(points, durations, update, interval, func) return self.scrcpy.swipe_ext(points, durations, update, interval, func)
@retry
def check_current_focus(self) -> bool: def check_current_focus(self) -> bool:
"""check if the application is in the foreground""" """check if the application is in the foreground"""
update = False update = False
@ -264,7 +267,6 @@ class Device:
logger.exception(e) logger.exception(e)
update = True update = True
@retry
def check_device_screen(self) -> bool: def check_device_screen(self) -> bool:
"""检查分辨率和DPI """检查分辨率和DPI

View file

@ -9,33 +9,26 @@ from adbutils import AdbClient, AdbDevice
from mower import __system__ from mower import __system__
from mower.utils import config from mower.utils import config
from mower.utils.csleep import MowerExit from mower.utils.csleep import MowerExit
from mower.utils.device.exception import SimulatorError from mower.utils.device.exception import ADBError, SimulatorError
from mower.utils.device.utils import RETRY_TRIES, retry_sleep
from mower.utils.log import logger from mower.utils.log import logger
from .utils import subprocess_run from .utils import subprocess_run
def retry(func): def retry_adb(func):
@wraps(func) @wraps(func)
def retry_wrapper(self, *args, **kwargs): def retry_wrapper(self, *args, **kwargs):
init = None for attempt in range(2):
for _ in range(RETRY_TRIES):
try: try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except MowerExit: except MowerExit:
break raise
except Exception as e: except ADBError as e:
logger.warning(f"ADB error, retrying... ({attempt + 1}/2)")
logger.exception(e) logger.exception(e)
self.adb_reconnect()
def init(): # After adb retries fail, escalate to simulator error
self.adb_reconnect() raise SimulatorError("ADB failed after 3 attempts.")
logger.error(f"Retry {func.__name__}() failed")
raise SimulatorError("无法连接ADB")
return retry_wrapper return retry_wrapper
@ -46,21 +39,21 @@ class ADB:
self.check_server_status() self.check_server_status()
@cached_property @cached_property
@retry @retry_adb
def adb(self) -> AdbDevice: def adb(self) -> AdbDevice:
if len(self.adb_client.list()) == 0: if len(self.adb_client.list()) == 0:
self.adb_client.connect(config.conf.adb, 3) self.adb_client.connect(config.conf.adb, 5)
return AdbDevice(self.adb_client, config.conf.adb) return AdbDevice(self.adb_client, config.conf.adb)
@cached_property @cached_property
@retry @retry_adb
def adb_client(self) -> AdbClient: def adb_client(self) -> AdbClient:
try: try:
return AdbClient("127.0.0.1", config.adb_server_port, 5) return AdbClient("127.0.0.1", config.adb_server_port, 5)
except Exception as e: except Exception as e:
raise e raise e
@retry @retry_adb
def adb_command(self, cmd, timeout=10): def adb_command(self, cmd, timeout=10):
""" """
Execute ADB commands in a subprocess, Execute ADB commands in a subprocess,
@ -83,27 +76,30 @@ class ADB:
logger.info(stdout) logger.info(stdout)
return stdout return stdout
def clear_cached_property(self, property_name: str):
if property_name in self.__dict__:
del self.__dict__[property_name]
def kill_server(self) -> None: def kill_server(self) -> None:
self.adb_command(["kill-server"]) self.adb_command(["kill-server"])
del self.__dict__["adb_client"] self.clear_cached_property("adb_client")
del self.__dict__["adb"] self.clear_cached_property("adb")
def restart_server(self) -> None: def restart_server(self) -> None:
self.kill_server() self.kill_server()
self.check_server_status() self.check_server_status()
def adb_disconnect(self): def adb_disconnect(self):
self.adb_client.disconnect(config.conf.adb) self.adb_client.disconnect(config.conf.adb, raise_error=False)
del self.__dict__["adb"] self.clear_cached_property("adb")
def adb_reconnect(self): def adb_reconnect(self):
status = self.check_server_status() status = self.check_server_status()
if "offline" in status: if "offline" in status:
self.adb_disconnect() self.adb_disconnect()
elif config.conf.adb in status: else:
self.restart_server() self.restart_server()
@retry
def adb_shell( def adb_shell(
self, cmd, stream=False, timeout=10, encoding: str | None = "utf-8", rstrip=True self, cmd, stream=False, timeout=10, encoding: str | None = "utf-8", rstrip=True
): ):

View file

@ -9,7 +9,6 @@ from mower.utils import config
from mower.utils.csleep import MowerExit from mower.utils.csleep import MowerExit
from mower.utils.device.exception import ADBError from mower.utils.device.exception import ADBError
from mower.utils.device.method.adb import ADB from mower.utils.device.method.adb import ADB
from mower.utils.device.utils import retry_sleep
from mower.utils.image import bytes2img from mower.utils.image import bytes2img
from mower.utils.log import logger from mower.utils.log import logger
from mower.utils.network import get_new_port, is_port_in_use from mower.utils.network import get_new_port, is_port_in_use
@ -19,27 +18,20 @@ class DroidCastError(Exception):
pass pass
def retry(func): def retry_droidcast(func):
@wraps(func) @wraps(func)
def retry_wrapper(self, *args, **kwargs): def retry_wrapper(self, *args, **kwargs):
init = None for try_count in range(2):
exception = ""
for _ in range(3):
try: try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except MowerExit: except MowerExit:
break raise
except Exception as e: except Exception as e:
exception = e logger.exception(e)
self.start_droidcast()
def init(): try_count += 1
self.start_droidcast() # After scrcpy retries fail, escalate to adb error
raise ADBError("Droidcast failed after 2 attempts.")
logger.error(f"Retry {func.__name__}() failed")
raise ADBError(exception)
return retry_wrapper return retry_wrapper
@ -119,7 +111,7 @@ class DroidCast:
time.sleep(1) # TODO: 更好地等待DroidCast启动 time.sleep(1) # TODO: 更好地等待DroidCast启动
return True return True
@retry @retry_droidcast
def capture_display(self): def capture_display(self):
try: try:
url = f"http://127.0.0.1:{self.port}/screenshot" url = f"http://127.0.0.1:{self.port}/screenshot"

View file

@ -4,6 +4,7 @@ import functools
import socket import socket
import threading import threading
import time import time
from functools import wraps
from typing import Any, Callable, Optional from typing import Any, Callable, Optional
import numpy as np import numpy as np
@ -11,7 +12,8 @@ from adbutils import AdbConnection, Network
from mower.utils import config from mower.utils import config
from mower.utils import typealias as tp from mower.utils import typealias as tp
from mower.utils.device.exception import ADBError from mower.utils.csleep import MowerExit
from mower.utils.device.exception import ADBError, SimulatorError
from mower.utils.device.method.adb import ADB from mower.utils.device.method.adb import ADB
from mower.utils.device.method.adb.const import KeyCode from mower.utils.device.method.adb.const import KeyCode
from mower.utils.log import logger from mower.utils.log import logger
@ -35,6 +37,30 @@ def recog_start(func: Callable[[tp.Image], Any] = lambda _: None):
swipe_update_result = func(config.recog.img) swipe_update_result = func(config.recog.img)
def retry_scrcpy(func):
@wraps(func)
def retry_wrapper(self, *args, **kwargs):
for try_count in range(2):
try:
return func(self, *args, **kwargs)
except MowerExit:
raise
except (
ConnectionResetError,
BrokenPipeError,
ConnectionAbortedError,
) as e:
logger.exception(e)
self.stop()
time.sleep(1)
self.start()
try_count += 1
# After scrcpy retries fail, escalate to adb error
raise ADBError("Scrcpy failed after 2 attempts.")
return retry_wrapper
class Client: class Client:
def __init__( def __init__(
self, self,
@ -184,20 +210,22 @@ class Client:
time.sleep(1) time.sleep(1)
self.start() self.start()
try_count += 1 try_count += 1
except SimulatorError as e:
logger.exception(e)
else: else:
raise ADBError("Failed to start scrcpy-server.") raise ADBError("Failed to start scrcpy-server.")
return inner return inner
@stable @retry_scrcpy
def tap(self, x: int, y: int) -> None: def tap(self, x: int, y: int) -> None:
self.control.tap(x, y) self.control.tap(x, y)
@stable @retry_scrcpy
def back(self): def back(self):
self.control.send_keyevent(KeyCode.KEYCODE_BACK) self.control.send_keyevent(KeyCode.KEYCODE_BACK)
@stable @retry_scrcpy
def swipe( def swipe(
self, self,
x0: int, x0: int,
@ -258,7 +286,7 @@ class Client:
if interval > 0: if interval > 0:
time.sleep(interval) time.sleep(interval)
@stable @retry_scrcpy
def swipe_ext( def swipe_ext(
self, self,
points: list[tp.Coordinate], points: list[tp.Coordinate],

View file

@ -1,5 +1,4 @@
import subprocess import subprocess
from datetime import datetime
from enum import Enum from enum import Enum
from os import system from os import system
@ -131,13 +130,6 @@ def exec_cmd(cmd, folder_path, wait_time):
except subprocess.TimeoutExpired: except subprocess.TimeoutExpired:
wait_time -= 1 wait_time -= 1
else: else:
start_time = datetime.now() csleep(wait_time)
while (datetime.now() - start_time).total_seconds() < wait_time: return True
try:
config.device.check_device_screen()
except MowerExit:
raise
except Exception as e:
logger.debug_exception(e)
csleep(1)
return False return False

View file

@ -1,19 +0,0 @@
import time
RETRY_TRIES = 5
RETRY_DELAY = 3
def retry_sleep(trial):
# First trial
if trial == 0:
pass
# Failed once, fast retry
elif trial == 1:
pass
# Failed twice
elif trial == 2:
time.sleep(1)
# Failed more
else:
time.sleep(RETRY_DELAY)