#[title: mi10rb-aut] #[language: python] #[class: 工具类] #[service: 10010] ADB设备重启管理 #[disable: false] #[admin: false] #[rule: ^重启Mi10$] #[priority: 999] #[platform: qq,qb,wx,tb,tg,web,wxmp] #[open_source: false] #[icon: 图标url] #[version: 1.0.0] #[public: false] #[price: 0.00] #[description: 通过ADB(TCP)连接到设备并执行重启;发送"重启Mi10"执行。
] # [param: {"required":true,"key":"mi10rb.address","placeholder":"127.0.0.1:5555","name":"ADB地址:","desc":"设备的 ADB 地址,如 192.168.1.100:5555"}] # [param: {"required":false,"key":"mi10rb.adb_path","placeholder":"adb","name":"adb路径:","desc":"adb 可执行文件路径,默认使用系统 adb"}] # [param: {"required":false,"key":"mi10rb.timeout","placeholder":"20","name":"超时:","desc":"单次命令超时时间(秒),默认20"}] import os import sys import shlex import logging import subprocess import time import colorlog import middleware import sender from typing import Tuple, Optional # 读取插件参数 CONF_ADDRESS = middleware.bucketGet('mi10rb', 'address') CONF_ADB_PATH = middleware.bucketGet('mi10rb', 'adb_path') or 'adb' CONF_TIMEOUT = int(middleware.bucketGet('mi10rb', 'timeout') or '20') sender = middleware.Sender(middleware.getSenderID()) class TOOL: def __init__(self): self.plugin_pre = "mi10rb" self.color_formatter = colorlog.ColoredFormatter( '%(log_color)s%(levelname)s: %(message)s', log_colors={ 'DEBUG': 'cyan', 'INFO': 'green', 'WARNING': 'yellow', 'ERROR': 'red', 'CRITICAL': 'red,bg_white', } ) self.logger = self._configure_logger() def _configure_logger(self): logger_obj = logging.getLogger(self.plugin_pre) logger_obj.setLevel(logging.DEBUG) if not any(isinstance(h, logging.StreamHandler) for h in logger_obj.handlers): console_handler = logging.StreamHandler() console_handler.setLevel(logging.DEBUG) console_handler.setFormatter(self.color_formatter) logger_obj.addHandler(console_handler) return logger_obj def log(self, level, message): self.logger.log(level, message) def reply(self, content): return sender.reply(content) def run_command(command: str, timeout_seconds: int = 20) -> Tuple[int, str, str]: """ 以非交互方式执行命令,返回 (exit_code, stdout, stderr) """ try: process = subprocess.Popen( shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, ) stdout, stderr = process.communicate(timeout=timeout_seconds) return process.returncode, (stdout or '').strip(), (stderr or '').strip() except subprocess.TimeoutExpired: try: process.kill() except Exception: pass return 124, "", f"Command timed out after {timeout_seconds}s: {command}" except FileNotFoundError: return 127, "", f"Command not found: {command.split()[0]}" class ADBRebootClient: def __init__(self, address: str, adb_path: str = 'adb', timeout: int = 20, tool: Optional[TOOL] = None): self.address = address self.adb = adb_path or 'adb' self.timeout = timeout self.tool = tool def _log(self, level, message: str): if self.tool: self.tool.log(level, message) def disconnect(self) -> None: # 断开指定地址,忽略错误 run_command(f"{self.adb} disconnect {self.address}", timeout_seconds=min(self.timeout, 5)) def kill_server(self) -> None: # 尝试重启 adb 服务端,忽略错误 run_command(f"{self.adb} kill-server", timeout_seconds=min(self.timeout, 5)) def ensure_adb(self) -> bool: code, out, err = run_command(f"{self.adb} version", timeout_seconds=self.timeout) if code != 0: self._log(logging.ERROR, "未找到可用的 adb,请安装或在参数中指定 adb 路径") if err: self._log(logging.ERROR, err) return False self._log(logging.INFO, (out or 'adb 可用')) return True def connect(self) -> bool: # 先尝试断开可能残留的连接 self.disconnect() # 首次连接 code, out, err = run_command(f"{self.adb} connect {self.address}", timeout_seconds=self.timeout) msg = ((out or '') + ("\n" + err if err else '')).strip() if code != 0: self._log(logging.WARNING, f"adb connect 首次失败(code={code}),尝试重启 adb 并重试...\n{msg}") # 重启 adb 后重试一次 self.kill_server() self.disconnect() code, out, err = run_command(f"{self.adb} connect {self.address}", timeout_seconds=self.timeout) msg = ((out or '') + ("\n" + err if err else '')).strip() if code != 0: self._log(logging.ERROR, f"adb connect 重试失败(code={code}):\n{msg}") return False low = msg.lower() if ("connected to" in low) or ("already connected to" in low): self._log(logging.INFO, "✓ ADB 连接成功") else: self._log(logging.WARNING, "adb connect 返回0,但输出未包含已连接提示") if msg: self._log(logging.INFO, msg) # 连接校验:确认设备在线 ok, detail = self.get_state() if not ok: self._log(logging.ERROR, f"连接校验失败:{detail}") return False self._log(logging.INFO, f"设备状态:{detail}") return True def get_state(self) -> Tuple[bool, str]: code, out, err = run_command(f"{self.adb} -s {self.address} get-state", timeout_seconds=min(self.timeout, 10)) text = (out or err or '').strip() if code == 0 and (out or '').strip() == 'device': return True, 'device' # 某些 ROM 会返回 unknown/unauthorized,这里给出更友好的提示 return False, (text or f"get-state 失败(code={code})") def reboot(self) -> bool: def looks_like_reboot_sent(message: str) -> bool: low = (message or '').lower() # 设备重启会导致连接被关闭,adb 可能返回如下错误,但指令已下发 hints = [ 'closed', 'device offline', 'no devices/emulators found', 'cannot connect to daemon', 'daemon not running', 'failed to connect to', 'connection refused', 'connection reset', 'transport endpoint is not connected', 'broken pipe', 'read failed', 'i/o error', 'more than one device/emulator', ] return any(h in low for h in hints) def observe_post_reboot_disconnect(max_attempts: int = 8, interval_seconds: float = 0.5) -> bool: # 在短时间内观察设备从在线到离线/不可达的过渡,以此判断重启已生效 for _ in range(max_attempts): code_s, out_s, err_s = run_command( f"{self.adb} -s {self.address} get-state", timeout_seconds=min(self.timeout, 5) ) text_s = ((out_s or '') + ('\n' + err_s if err_s else '')).strip().lower() if code_s != 0: # 命令失败,多半是设备掉线,视为重启进行中 return True if (out_s or '').strip() != 'device': # 非 device 状态(例如 offline/unknown),也视为重启进行中 return True # 仍为在线,继续等待 time.sleep(interval_seconds) return False code, out, err = run_command(f"{self.adb} -s {self.address} reboot", timeout_seconds=self.timeout) combined = ((out or '') + ("\n" + err if err else '')).strip() if code == 0: self._log(logging.INFO, "✓ 已发送重启指令") return True # 非零返回,但疑似已下发 if looks_like_reboot_sent(combined): self._log(logging.WARNING, f"adb reboot 返回非零(code={code}),但检测到重启迹象,视为成功:\n{combined}") return True # 回退方案:走 shell reboot self._log(logging.WARNING, f"adb reboot 失败(code={code}),尝试使用 shell reboot 回退...\n{combined}") code2, out2, err2 = run_command(f"{self.adb} -s {self.address} shell reboot", timeout_seconds=self.timeout) combined2 = ((out2 or '') + ("\n" + err2 if err2 else '')).strip() if code2 == 0 or looks_like_reboot_sent(combined2): self._log(logging.INFO, "✓ 已通过 shell reboot 发送重启指令") return True # 最终失败前,观察设备是否已开始断连(重启进行中) if observe_post_reboot_disconnect(): self._log(logging.WARNING, "检测到重启后的断连迹象,尽管命令返回失败,仍视为已下发成功") return True self._log(logging.ERROR, f"shell reboot 仍失败(code={code2}):\n{combined2}") return False def run_reboot_flow(tool: TOOL): """执行 ADB 设备重启流程""" tool.reply("开始执行 Mi10 ADB 重启流程...") # 校验参数 if not CONF_ADDRESS: tool.reply("❌ 配置参数不完整,请设置 mi10rb.address (如 192.168.1.100:5555)") return tool.log(logging.INFO, "配置信息:") tool.log(logging.INFO, f" ADB地址: {CONF_ADDRESS}") tool.log(logging.INFO, f" adb路径: {CONF_ADB_PATH}") tool.log(logging.INFO, f" 超时: {CONF_TIMEOUT}秒") client = ADBRebootClient(address=CONF_ADDRESS, adb_path=CONF_ADB_PATH, timeout=CONF_TIMEOUT, tool=tool) try: if not client.ensure_adb(): tool.reply("❌ 未检测到 adb,请安装或指定有效的 adb 路径") return tool.log(logging.INFO, "🚀 正在连接设备...") if not client.connect(): tool.reply("❌ ADB 连接失败,请检查地址与网络") return tool.log(logging.INFO, "🔁 正在发送重启指令...") if client.reboot(): tool.reply("✅ 重启指令已发送!设备即将重启") else: tool.reply("❌ 重启指令发送失败") except Exception as exc: tool.log(logging.ERROR, f"程序执行出错: {exc}") tool.reply("❌ 程序执行出错,请检查配置与网络") def exit_with_msg(msg: str): if msg: sender.reply(msg) sender.reply("已退出!") sys.exit(0) def _safe_get_message(): try: return sender.getMessage().strip('"') except Exception: return "" if __name__ == "__main__": tool = TOOL() msg = _safe_get_message() if "重启Mi10" in msg: run_reboot_flow(tool) sys.exit(0) sender.reply("指令不支持,请发送:重启Mi10")