简化重试函数
This commit is contained in:
parent
7eb8655724
commit
23c1109cae
6 changed files with 100 additions and 109 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
):
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
Loading…
Add table
Add a link
Reference in a new issue