简化重试函数

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

View file

@ -9,33 +9,26 @@ from adbutils import AdbClient, AdbDevice
from mower import __system__
from mower.utils import config
from mower.utils.csleep import MowerExit
from mower.utils.device.exception import SimulatorError
from mower.utils.device.utils import RETRY_TRIES, retry_sleep
from mower.utils.device.exception import ADBError, SimulatorError
from mower.utils.log import logger
from .utils import subprocess_run
def retry(func):
def retry_adb(func):
@wraps(func)
def retry_wrapper(self, *args, **kwargs):
init = None
for _ in range(RETRY_TRIES):
for attempt in range(2):
try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs)
except MowerExit:
break
except Exception as e:
raise
except ADBError as e:
logger.warning(f"ADB error, retrying... ({attempt + 1}/2)")
logger.exception(e)
def init():
self.adb_reconnect()
logger.error(f"Retry {func.__name__}() failed")
raise SimulatorError("无法连接ADB")
# After adb retries fail, escalate to simulator error
raise SimulatorError("ADB failed after 3 attempts.")
return retry_wrapper
@ -46,21 +39,21 @@ class ADB:
self.check_server_status()
@cached_property
@retry
@retry_adb
def adb(self) -> AdbDevice:
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)
@cached_property
@retry
@retry_adb
def adb_client(self) -> AdbClient:
try:
return AdbClient("127.0.0.1", config.adb_server_port, 5)
except Exception as e:
raise e
@retry
@retry_adb
def adb_command(self, cmd, timeout=10):
"""
Execute ADB commands in a subprocess,
@ -83,27 +76,30 @@ class ADB:
logger.info(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:
self.adb_command(["kill-server"])
del self.__dict__["adb_client"]
del self.__dict__["adb"]
self.clear_cached_property("adb_client")
self.clear_cached_property("adb")
def restart_server(self) -> None:
self.kill_server()
self.check_server_status()
def adb_disconnect(self):
self.adb_client.disconnect(config.conf.adb)
del self.__dict__["adb"]
self.adb_client.disconnect(config.conf.adb, raise_error=False)
self.clear_cached_property("adb")
def adb_reconnect(self):
status = self.check_server_status()
if "offline" in status:
self.adb_disconnect()
elif config.conf.adb in status:
else:
self.restart_server()
@retry
def adb_shell(
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.device.exception import ADBError
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.log import logger
from mower.utils.network import get_new_port, is_port_in_use
@ -19,27 +18,20 @@ class DroidCastError(Exception):
pass
def retry(func):
def retry_droidcast(func):
@wraps(func)
def retry_wrapper(self, *args, **kwargs):
init = None
exception = ""
for _ in range(3):
for try_count in range(2):
try:
if callable(init):
retry_sleep(_)
init()
return func(self, *args, **kwargs)
except MowerExit:
break
raise
except Exception as e:
exception = e
def init():
logger.exception(e)
self.start_droidcast()
logger.error(f"Retry {func.__name__}() failed")
raise ADBError(exception)
try_count += 1
# After scrcpy retries fail, escalate to adb error
raise ADBError("Droidcast failed after 2 attempts.")
return retry_wrapper
@ -119,7 +111,7 @@ class DroidCast:
time.sleep(1) # TODO: 更好地等待DroidCast启动
return True
@retry
@retry_droidcast
def capture_display(self):
try:
url = f"http://127.0.0.1:{self.port}/screenshot"

View file

@ -4,6 +4,7 @@ import functools
import socket
import threading
import time
from functools import wraps
from typing import Any, Callable, Optional
import numpy as np
@ -11,7 +12,8 @@ from adbutils import AdbConnection, Network
from mower.utils import config
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.const import KeyCode
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)
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:
def __init__(
self,
@ -184,20 +210,22 @@ class Client:
time.sleep(1)
self.start()
try_count += 1
except SimulatorError as e:
logger.exception(e)
else:
raise ADBError("Failed to start scrcpy-server.")
return inner
@stable
@retry_scrcpy
def tap(self, x: int, y: int) -> None:
self.control.tap(x, y)
@stable
@retry_scrcpy
def back(self):
self.control.send_keyevent(KeyCode.KEYCODE_BACK)
@stable
@retry_scrcpy
def swipe(
self,
x0: int,
@ -258,7 +286,7 @@ class Client:
if interval > 0:
time.sleep(interval)
@stable
@retry_scrcpy
def swipe_ext(
self,
points: list[tp.Coordinate],

View file

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