import json
import os
import threading
import PySimpleGUI as sg
import telnetlib
import time
import queue
import logging
from datetime import datetime
import re
============== 全局配置 ==============
DATA_FILE = ‘pyremote_config.json’
PROJECTS_FILE = ‘projects_config.json’
END_STR = “ROUTER_MPU /home\x1b[m # "
LOG_FORMAT = ‘%(asctime)s - %(levelname)s - %(message)s’
ENCODING = ‘utf-8’
BUFFER_SIZE = 4096
CHUNK_SIZE = 10
CHUNK_DELAY = 0.15
COMMAND_DELAY = 0.5
MAX_PORTS = 2
AUTO_OPERATION_SUCCESS_STR = “Close watchdog”
BOOT_STRING = r"Press\s*[‘"]?Ctrl+T[’"]?\sto\sskip\s*boot”
SUCCESS_PATTERNS = {
“watchdog -close”: r"Close watchdog",
“ifconfig eth0”: r"Set eth IpAddr:.?Mask:.?Gateway:“,
“ftp -get”: r"Ftp get .? success”,
“flash -w”: r"Flash write .? ret=0"
}
ERROR_PATTERNS = [
r"error", r"fail", r"invalid", r"not found", r"timeout", r"unrecognized"
]
============== 日志配置 ==============
def setup_logger():
logger = logging.getLogger(‘RemoteControl’)
logger.setLevel(logging.DEBUG)
file_handler = logging.FileHandler('remote_control.log') file_handler.setFormatter(logging.Formatter(LOG_FORMAT)) console_handler = logging.StreamHandler() console_handler.setFormatter(logging.Formatter(LOG_FORMAT)) logger.addHandler(file_handler) logger.addHandler(console_handler) return logger
logger = setup_logger()
============== 项目管理类 ==============
class ProjectManager:
def init(self):
self.last_modified = {}
self.projects = self.load_projects()
def load_projects(self): default = {"projects": []} if not os.path.exists(PROJECTS_FILE): return default try: with open(PROJECTS_FILE, 'r', encoding='utf-8') as f: projects = json.load(f) if not isinstance(projects, dict) or "projects" not in projects: logger.warning("项目配置文件格式错误,使用默认结构") return default for project in projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return projects except Exception as e: logger.error(f"加载项目配置失败: {e}") return default def save_projects(self): try: with open(PROJECTS_FILE, 'w', encoding='utf-8') as f: json.dump(self.projects, f, indent=4, ensure_ascii=False) for project in self.projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return True except Exception as e: logger.error(f"保存项目配置失败: {e}") return False def add_or_update_project(self, name, commands, auto_operation=False, index=None): project = { "name": name, "commands": commands, "auto_operation": auto_operation, "bios_commands": [], "rbos_commands": [], "initrd_commands": [], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } if index is None: # 添加新项目 if "projects" not in self.projects: self.projects["projects"] = [] self.projects["projects"].append(project) else: # 更新项目 if 0 <= index < len(self.projects["projects"]): # 保留原有升级指令 for key in ["bios_commands", "rbos_commands", "initrd_commands"]: project[key] = self.projects["projects"][index].get(key, []) self.projects["projects"][index] = project self.last_modified[name] = datetime.now() return self.save_projects() def update_upgrade_commands(self, index, upgrade_type, commands): if 0 <= index < len(self.projects["projects"]): project = self.projects["projects"][index] project_name = project["name"] if upgrade_type in ["bios", "rbos", "initrd"]: project[f"{upgrade_type}_commands"] = commands self.last_modified[project_name] = datetime.now() return self.save_projects() return False def get_upgrade_commands(self, index, upgrade_type): if 0 <= index < len(self.projects["projects"]): project = self.projects["projects"][index] if upgrade_type in ["bios", "rbos", "initrd"]: return project.get(f"{upgrade_type}_commands", []) return [] def delete_project(self, index): if 0 <= index < len(self.projects["projects"]): project_name = self.projects["projects"][index]["name"] del self.projects["projects"][index] if project_name in self.last_modified: del self.last_modified[project_name] return self.save_projects() return False def import_export_projects(self, file_path, action='import'): try: if action == 'import': with open(file_path, 'r', encoding='utf-8') as f: imported = json.load(f) if not isinstance(imported, dict) or "projects" not in imported: logger.error("导入的项目文件格式不正确") return False self.projects = imported self.last_modified = {} for project in self.projects["projects"]: if "name" in project: self.last_modified[project["name"]] = datetime.now() return self.save_projects() else: # export with open(file_path, 'w', encoding='utf-8') as f: json.dump(self.projects, f, indent=4, ensure_ascii=False) return True except Exception as e: action_str = "导入" if action == 'import' else "导出" logger.error(f"{action_str}项目失败: {e}") return False def get_project_commands(self, index): if 0 <= index < len(self.projects["projects"]): return self.projects["projects"][index].get("commands", []) return [] def get_project_last_modified(self, name): return self.last_modified.get(name, datetime.min)
============== 指令执行类 ==============
class CommandExecutor:
def init(self, port_id):
self.port_id = port_id
self.tn = None
self.is_connected = False
self.log_queue = queue.Queue()
self.lock = threading.Lock()
self.stop_event = threading.Event()
self.pause_event = threading.Event()
self.last_response = “”
self.expected_prompt = END_STR
self.current_command_index = -1
self.current_command = “”
self.total_commands = 0
self.log_file = None
self.log_file_path = “”
self.auto_mode = False
self.boot_string_detected = False
self.auto_completed = False
self.auto_operation_callback = None
self.command_success = False
self.retry_count = 0
self.project_index = -1
self.project_name = “”
self.project_manager = None
self.max_retries = 3
self.retry_delay = 1
self.max_retry_delay = 30
self.upgrade_type = “”
self.max_attempts = 10
self.timeout = 30
def set_project_manager(self, manager): self.project_manager = manager def set_log_file(self, file_path): self.log_file_path = file_path try: if self.log_file: self.log_file.close() self.log_file = open(file_path, 'a', encoding=ENCODING) return True except Exception as e: self.log_queue.put(f"打开日志文件失败: {str(e)}") return False def log_to_file(self, message): if self.log_file: try: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-3] self.log_file.write(f"[{timestamp}] [Port {self.port_id}] {message}\n") self.log_file.flush() except Exception as e: self.log_queue.put(f"日志写入失败: {str(e)}") def connect(self, host, port): try: with self.lock: self.tn = telnetlib.Telnet(host, port, timeout=10) self.is_connected = True self.log_queue.put(f"端口 {self.port_id} 已连接到 {host}:{port}") self.log_to_file(f"Connected to {host}:{port}") self.read_until_prompt(timeout=3) self.boot_string_detected = False self.auto_completed = False return True except Exception as e: self.log_queue.put(f"端口 {self.port_id} 连接失败: {str(e)}") return False def disconnect(self): with self.lock: if self.tn: try: self.tn.close() except: pass self.is_connected = False self.log_queue.put(f"端口 {self.port_id} 连接已断开") self.log_to_file("Disconnected") if self.log_file: try: self.log_file.close() except: pass self.log_file = None def send(self, content): if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False try: with self.lock: self.current_command = content encoded_content = content.encode(ENCODING) for i in range(0, len(encoded_content), CHUNK_SIZE): if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 发送已中止") return False while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False chunk = encoded_content[i:i + CHUNK_SIZE] self.tn.write(chunk) log_msg = f"发送分块: {chunk.decode(ENCODING, errors='replace')}" self.log_queue.put(log_msg) self.log_to_file(log_msg) time.sleep(CHUNK_DELAY) self.tn.write(b"\r\n") self.log_queue.put(f"端口 {self.port_id} 发送回车符") self.log_to_file("Sent ENTER") self.log_queue.put(f"端口 {self.port_id} 完整发送: {content.strip()}") self.log_to_file(f"Sent: {content.strip()}") return True except Exception as e: self.log_queue.put(f"端口 {self.port_id} 发送命令失败: {str(e)}") self.log_to_file(f"Send error: {str(e)}") return False def read_until_prompt(self, timeout=5): if not self.is_connected: return "" try: response = self.tn.read_until(self.expected_prompt.encode(ENCODING), timeout=timeout) decoded_response = response.decode(ENCODING, errors='replace') if decoded_response.endswith(self.expected_prompt): decoded_response = decoded_response[:-len(self.expected_prompt)].rstrip() self.last_response = decoded_response if decoded_response.strip(): self.log_queue.put(f"端口 {self.port_id} 响应: {decoded_response}") self.log_to_file(f"Response: {decoded_response}") return decoded_response except Exception as e: error_msg = f"端口 {self.port_id} 接收响应失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return "" def execute_commands(self, commands, max_retries=None, retry_delay=None): if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False max_retries = max_retries or self.max_retries retry_delay = retry_delay or self.retry_delay self.stop_event.clear() self.pause_event.clear() self.total_commands = len(commands) self.log_queue.put(f"端口 {self.port_id} 开始执行 {self.total_commands} 条命令 (最大重试: {max_retries})") self.log_to_file(f"Starting execution of {self.total_commands} commands (max_retries={max_retries})") try: for idx, cmd in enumerate(commands): if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 命令执行已中止") self.log_to_file("Execution aborted") return False self.current_command_index = idx self.retry_count = 0 self.command_success = False while not self.command_success: if self.stop_event.is_set(): return False while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False if cmd.strip(): if not self.send(cmd): return False response = self.read_until_prompt(timeout=15) self.command_success = self.check_command_success(cmd, response) if not self.command_success: self.retry_count += 1 if self.retry_count < max_retries: current_delay = min(retry_delay * (2 ** self.retry_count), self.max_retry_delay) self.log_queue.put( f"端口 {self.port_id} 命令执行失败,正在重试 ({self.retry_count}/{max_retries}): {cmd}" f" (等待 {current_delay:.1f}秒)" ) self.log_to_file( f"Command failed, retrying ({self.retry_count}/{max_retries}): {cmd}" f" (delay={current_delay:.1f}s)" ) time.sleep(current_delay) continue else: self.log_queue.put( f"端口 {self.port_id} 命令执行失败: {cmd},已达到最大重试次数({max_retries})" ) self.log_to_file(f"Command failed after {max_retries} retries: {cmd}") self.pause_event.set() while self.pause_event.is_set(): time.sleep(0.1) if self.stop_event.is_set(): return False if self.command_success: delay_remaining = COMMAND_DELAY while delay_remaining > 0: if self.stop_event.is_set(): return False if self.pause_event.is_set(): time.sleep(0.1) continue time.sleep(0.1) delay_remaining -= 0.1 self.log_queue.put(f"端口 {self.port_id} 命令执行完成") self.log_to_file("Execution completed") self.current_command_index = -1 return True except Exception as e: error_msg = f"端口 {self.port_id} 命令执行失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return False def check_command_success(self, command, response): for cmd_key, pattern in SUCCESS_PATTERNS.items(): if cmd_key in command and re.search(pattern, response, re.IGNORECASE): return True for pattern in ERROR_PATTERNS: if re.search(pattern, response, re.IGNORECASE): return False if "ping" in command and "100% packet loss" in response: return False return bool(response.strip()) def stop_execution(self): self.stop_event.set() self.log_queue.put(f"端口 {self.port_id} 正在停止执行...") self.log_to_file("Stopping execution") def pause_execution(self): if not self.pause_event.is_set(): self.pause_event.set() self.log_queue.put(f"端口 {self.port_id} 执行已暂停") self.log_to_file("Execution paused") return True return False def resume_execution(self): if self.pause_event.is_set(): self.pause_event.clear() self.log_queue.put(f"端口 {self.port_id} 执行已继续") self.log_to_file("Execution resumed") return True return False def get_execution_status(self): return { "port_id": self.port_id, "is_connected": self.is_connected, "is_running": not self.stop_event.is_set() and not self.pause_event.is_set(), "is_paused": self.pause_event.is_set(), "is_stopped": self.stop_event.is_set(), "current_command": self.current_command.strip(), "current_index": self.current_command_index, "total_commands": self.total_commands, "log_file_path": self.log_file_path, "boot_string_detected": self.boot_string_detected, "auto_completed": self.auto_completed, "command_success": self.command_success, "retry_count": self.retry_count, "project_index": self.project_index, "project_name": self.project_name, "upgrade_type": self.upgrade_type } def detect_boot_string_and_auto_operation(self): """检测引导字符串并执行自动操作(修复版)""" if not self.is_connected or self.auto_completed: return False try: self.log_queue.put(f"端口 {self.port_id} 开始检测引导字符串...") self.log_to_file("Starting boot string detection") # 重置状态 self.boot_string_detected = False self.auto_completed = False # 第一步:检测引导字符串 start_time = time.time() while time.time() - start_time < self.timeout: if self.stop_event.is_set(): self.log_queue.put(f"端口 {self.port_id} 检测已中止") return False # 读取设备响应 response = self.read_until_prompt(timeout=1) # 使用正则检测引导字符串 if re.search(BOOT_STRING, response, re.IGNORECASE): self.boot_string_detected = True self.log_queue.put(f"端口 {self.port_id} 检测到引导字符串") self.log_to_file("Detected boot string") break time.sleep(0.5) if not self.boot_string_detected: self.log_queue.put(f"端口 {self.port_id} 未检测到引导字符串") self.log_to_file("Boot string not detected") return False # 第二步:发送Ctrl+T self.log_queue.put(f"端口 {self.port_id} 发送 Ctrl+T") if not self.send_ctrl_t(): self.log_queue.put(f"端口 {self.port_id} Ctrl+T发送失败") self.log_to_file("Failed to send Ctrl+T") return False # 第三步:等待6秒 self.log_queue.put(f"端口 {self.port_id} 等待6秒...") self.log_to_file("Waiting 6 seconds...") time.sleep(6) # 第四步:发送watchdog命令 self.log_queue.put(f"端口 {self.port_id} 发送 watchdog -close") self.log_to_file("Sending watchdog -close") self.send("watchdog -close") # 第五步:验证命令执行结果 watchdog_response = self.read_until_prompt(timeout=10) if re.search(AUTO_OPERATION_SUCCESS_STR, watchdog_response, re.IGNORECASE): self.auto_completed = True self.log_queue.put(f"端口 {self.port_id} watchdog命令执行成功") self.log_to_file("Watchdog command succeeded") return True else: self.log_queue.put(f"端口 {self.port_id} watchdog命令执行失败") self.log_to_file(f"Watchdog command failed, response: {watchdog_response}") return False except Exception as e: self.log_queue.put(f"端口 {self.port_id} 自动操作失败: {str(e)}") self.log_to_file(f"Auto operation failed: {str(e)}") return False def perform_auto_operation(self, callback=None): """执行自动操作(带重试机制)""" self.auto_operation_callback = callback # 尝试自动操作 success = False for attempt in range(1, self.max_attempts + 1): self.log_queue.put( f"端口 {self.port_id} 开始自动操作尝试 ({attempt}/{self.max_attempts})" ) self.log_to_file(f"Starting auto operation attempt {attempt}/{self.max_attempts}") success = self.detect_boot_string_and_auto_operation() if success: break # 计算指数退避延迟 delay = min(2 ** attempt, self.max_retry_delay) self.log_queue.put( f"端口 {self.port_id} 自动操作失败,正在重试 ({attempt}/{self.max_attempts}) " f"等待 {delay}秒..." ) self.log_to_file(f"Retrying in {delay} seconds...") time.sleep(delay) # 执行回调(如果有) if success and self.auto_operation_callback: self.log_queue.put(f"端口 {self.port_id} 触发自动升级回调") self.log_to_file("Triggering auto operation callback") self.auto_operation_callback() return success def send_ctrl_t(self): """发送Ctrl+T组合键(简化版)""" if not self.is_connected: return False try: with self.lock: if not self.tn: return False # 简化Ctrl+T序列 - 直接发送控制字符 # Ctrl+T的ASCII码是20(0x14) self.tn.write(b"\x14") # 添加回车确保命令被执行 self.tn.write(b"\r") self.log_queue.put(f"端口 {self.port_id} 已发送 Ctrl+T") self.log_to_file("Sent Ctrl+T") return True except Exception as e: error_msg = f"端口 {self.port_id} 发送Ctrl+T失败: {str(e)}" self.log_queue.put(error_msg) self.log_to_file(error_msg) return False
============== GUI 界面类 ==============
class RemoteControlApp:
def init(self):
self.project_manager = ProjectManager()
self.default_data = self.load_default_data()
self.executors = {}
self.window = None
for port_id in range(1, MAX_PORTS + 1): executor = CommandExecutor(port_id) executor.set_project_manager(self.project_manager) self.executors[port_id] = executor self.setup_gui() self.running = True def load_default_data(self): default_data = { "IP1": "192.168.1.200", "port1": "1000", "IP2": "71.19.2.130", "port2": "1260", "FTP_IP": "71.19.0.120", "芯片名称": "Hi1260SV100", "发送信息": "", "board_local_ip": "71.19.0.53", "interval": "1", "start_addr": "", "end_addr": "", "文件FTP路径": "", "log_dir": os.getcwd() } if os.path.exists(DATA_FILE): try: with open(DATA_FILE, 'r') as f: data = json.load(f) default_data.update({k: data[k] for k in default_data if k in data}) except Exception as e: logger.error(f"加载默认配置失败: {e}") with open(DATA_FILE, 'w') as f: json.dump(default_data, f) return default_data def save_config(self): try: values = self.window.read()[1] if self.window else {} config = {k: values.get(k, self.default_data[k]) for k in self.default_data} with open(DATA_FILE, 'w') as f: json.dump(config, f) self.default_data = config return True except Exception as e: logger.error(f"保存配置失败: {e}") return False def create_project_buttons(self): layout = [] projects = self.project_manager.projects["projects"] if not projects: layout.append([sg.Text("没有项目,请添加新项目", text_color='red')]) for i, project in enumerate(projects): row = [ sg.Button(project["name"], key=f'-PROJECT-{i}-', size=(20, 1), tooltip=f"创建于: {project['created_at']}\n命令数: {len(project['commands'])}"), sg.Text('○', font=('Arial', 12), text_color='gray', tooltip="自动操作状态: ○未执行 ●已检测到引导字符串 ✓已完成", key=f'-AUTO-INDICATOR-{i}-') ] layout.append(row) layout.append([ sg.Button("添加新项目", key='-ADD-PROJECT-', button_color=('white', 'purple')), sg.Button("导入项目", key='-IMPORT-PROJECTS-'), sg.Button("导出项目", key='-EXPORT-PROJECTS-'), sg.Button("生成模板", key='-CREATE-TEMPLATE-') ]) return layout def create_port_selection_dialog(self, title, port_ids): layout = [ [sg.Text(title)], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in port_ids], [sg.Button("确定"), sg.Button("取消")] ] return sg.Window("选择端口", layout, modal=True) def create_project_function_window(self, index): if index >= len(self.project_manager.projects["projects"]): return None project = self.project_manager.projects["projects"][index] project_name = project["name"] upgrade_types = [ ("定义升级", "base", 'blue'), ("BIOS升级", "bios", 'blue'), ("RBOS升级", "rbos", 'blue'), ("INITRD升级", "initrd", 'blue') ] layout = [ [sg.Text(f"项目: {project_name}", font=("Arial", 12, "bold"))] ] for text, upgrade_type, color in upgrade_types: layout.append([ sg.Button(text, size=(12, 1), key=f'-{upgrade_type.upper()}-UPGRADE-{index}-', button_color=('white', color)), sg.Button("✎", size=(2, 1), key=f'-EDIT-{upgrade_type.upper()}-{index}-', button_color=('white', 'gray'), tooltip=f"编辑{text}指令") ]) layout.extend([ [sg.Button("一键升级", size=(12, 1), key=f'-AUTO-{index}-', button_color=('white', 'purple'), tooltip="执行自动操作进入Shell环境并触发升级")], [sg.Button("编辑项目", key=f'-EDIT-{index}-', button_color=('white', 'orange'))], [sg.Button("删除项目", key=f'-DELETE-{index}-', button_color=('white', 'red'))], [sg.Button("关闭", key='-CLOSE-')] ]) return sg.Window(f"项目功能 - {project_name}", layout, modal=True, finalize=True) def edit_upgrade_commands(self, index, upgrade_type): """编辑指定项目的升级命令""" project = self.project_manager.projects["projects"][index] title = f"{upgrade_type.upper()}升级指令" if upgrade_type != "base" else "基础升级指令" if upgrade_type == "base": commands = project.get("commands", []) else: commands = project.get(f"{upgrade_type}_commands", []) layout = [ [sg.Text(f"{title}:")], [sg.Multiline(key='-UPGRADE-COMMANDS-', size=(60, 10), default_text='\n'.join(commands), tooltip="每行一个命令,命令将按顺序执行")], [sg.Button("保存并执行", key='-SAVE-EXECUTE-'), sg.Button("保存", key='-SAVE-'), sg.Button("取消", key='-CANCEL-')] ] window = sg.Window(title, layout, modal=True, finalize=True) while True: event, values = window.read() if event in (sg.WINDOW_CLOSED, '-CANCEL-'): break commands = [cmd.strip() for cmd in values['-UPGRADE-COMMANDS-'].split('\n') if cmd.strip()] if event in ('-SAVE-', '-SAVE-EXECUTE-'): if upgrade_type == "base": success = self.project_manager.add_or_update_project( project["name"], commands, project.get("auto_operation", False), index) else: success = self.project_manager.update_upgrade_commands(index, upgrade_type, commands) if success: sg.popup(f"{title}保存成功") if event == '-SAVE-EXECUTE-': # 保存后立即执行升级 self.handle_upgrade_event(f'-{upgrade_type.upper()}-UPGRADE-{index}-', index, upgrade_type) break else: sg.popup("操作失败,请查看日志") window.close() return True def handle_auto_event(self, event, index): """处理项目功能窗口中的自动操作按钮""" # 创建端口选择对话框 port_layout = [ [sg.Text("选择执行自动操作的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [ sg.Text("超时时间(秒):", size=(10, 1)), sg.Input(key='-TIMEOUT-', default_text="60", size=(5, 1)), sg.Text("重试次数:", size=(8, 1)), sg.Input(key='-RETRIES-', default_text="5", size=(5, 1)) ], [sg.Button("确定", key='-OK-'), sg.Button("取消", key='-CANCEL-')] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event != '-OK-' or not any(port_values.values()): return selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") return # 获取配置参数 try: timeout = int(port_values.get('-TIMEOUT-', 60)) max_retries = int(port_values.get('-RETRIES-', 10)) except ValueError: sg.popup("无效的超时或重试次数设置,使用默认值") timeout = 60 max_retries = 10 # 设置日志文件 log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") project_name = self.project_manager.projects["projects"][index]["name"] project_name_safe = re.sub(r'[\\/*?:"<>|]', "", project_name) # 创建升级类型选择对话框 upgrade_types = [ ("一键升级", "base"), ("BIOS升级", "bios"), ("RBOS升级", "rbos"), ("INITRD升级", "initrd") ] upgrade_layout = [ [sg.Text("请选择升级类型:")], *[[sg.Radio(text, "UPGRADE_TYPE", key=f'-TYPE-{upgrade_type}-', default=(i == 0))] for i, (text, upgrade_type) in enumerate(upgrade_types)], [sg.Button("确定", key='-UPGRADE-OK-'), sg.Button("取消", key='-UPGRADE-CANCEL-')] ] upgrade_window = sg.Window("选择升级类型", upgrade_layout, modal=True) upgrade_event, upgrade_values = upgrade_window.read() upgrade_window.close() if upgrade_event != '-UPGRADE-OK-': return # 确定选择的升级类型 selected_upgrade_type = None for _, upgrade_type in upgrade_types: if upgrade_values[f'-TYPE-{upgrade_type}-']: selected_upgrade_type = upgrade_type break if not selected_upgrade_type: sg.popup("请选择升级类型") return # 为每个端口设置日志文件 for port_id in selected_ports: executor = self.executors[port_id] log_file = os.path.join( log_dir, f"{project_name_safe}_auto_{selected_upgrade_type}_port{port_id}_{timestamp}.log" ) executor.set_log_file(log_file) executor.timeout = timeout executor.max_attempts = max_retries executor.project_index = index executor.project_name = project_name executor.upgrade_type = selected_upgrade_type # 在新线程中执行自动操作和升级 threading.Thread( target=self._execute_auto_with_upgrade, args=(index, selected_upgrade_type, selected_ports), daemon=True ).start() def _execute_auto_with_upgrade(self, index, upgrade_type, port_ids): """执行自动操作并在成功后触发升级""" for port_id in port_ids: executor = self.executors[port_id] self.window['-STATUS-'].update(f"端口 {port_id} 开始执行自动操作...") # 执行自动操作(进入Shell环境) success = executor.perform_auto_operation() if success: self.window['-STATUS-'].update(f"端口 {port_id} 自动操作成功,开始升级...") # 获取升级命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) # 执行升级命令 upgrade_success = executor.execute_commands(commands, max_retries=3, retry_delay=1) status = "成功" if upgrade_success else "失败" self.window['-STATUS-'].update(f"端口 {port_id} {upgrade_type.upper()}升级{status}") else: self.window['-STATUS-'].update(f"端口 {port_id} 自动操作失败,无法执行升级") def handle_upgrade_event(self, event, index, upgrade_type): project = self.project_manager.projects["projects"][index] project_name = project["name"] # 根据升级类型获取对应的命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) upgrade_type_name = "一键升级" else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) upgrade_type_name = f"{upgrade_type.upper()}升级" if not commands: sg.popup(f"未配置{upgrade_type.upper()}升级指令,请先配置") self.edit_upgrade_commands(index, upgrade_type) return port_window = self.create_port_selection_dialog( f"选择执行 {project_name} {upgrade_type_name} 的端口:", list(self.executors.keys()) ) port_event, port_values = port_window.read() port_window.close() if port_event != "确定" or not any(port_values.values()): sg.popup("请至少选择一个端口") return selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") project_name_safe = re.sub(r'[\\/*?:"<>|]', "", project_name) for port_id in selected_ports: log_file = os.path.join(log_dir, f"{project_name_safe}_{upgrade_type_name}_port{port_id}_{timestamp}.log") executor = self.executors[port_id] executor.set_log_file(log_file) executor.project_index = index executor.project_name = project_name executor.upgrade_type = upgrade_type threading.Thread( target=self.execute_upgrade, args=(index, upgrade_type, selected_ports), daemon=True ).start() def execute_upgrade(self, index, upgrade_type, port_ids): # 根据升级类型获取对应的命令 if upgrade_type == "base": commands = self.project_manager.get_project_commands(index) upgrade_type_name = "一键升级" else: commands = self.project_manager.get_upgrade_commands(index, upgrade_type) upgrade_type_name = f"{upgrade_type.upper()}升级" if not commands: for port_id in port_ids: self.executors[port_id].log_queue.put(f"错误:{upgrade_type_name}没有配置命令") return self.window['-CURRENT-COMMAND-'].update("") self.window['-COMMAND-PROGRESS-'].update("0/0") threads = [] for port_id in port_ids: executor = self.executors[port_id] thread = threading.Thread( target=self._execute_upgrade_on_port, args=(executor, commands, port_id, upgrade_type_name), daemon=True ) thread.start() threads.append(thread) for thread in threads: thread.join(timeout=300) self.window['-STATUS-'].update(f"{upgrade_type_name}任务已完成") def _execute_upgrade_on_port(self, executor, commands, port_id, upgrade_type_name): self.window['-STATUS-'].update(f"端口 {port_id} 开始{upgrade_type_name}...") success = executor.execute_commands(commands, max_retries=3, retry_delay=1) status = "成功" if success else "失败" self.window['-STATUS-'].update(f"端口 {port_id} {upgrade_type_name}{status}") def setup_gui(self): sg.theme('LightBlue1') # 输出区域 output = sg.Multiline( size=(80, 20), key='-OUTPUT-', autoscroll=True, background_color='#f0f0f0', text_color='black' ) # 状态信息区域 status_info = [ sg.Text("当前命令: ", size=(10, 1)), sg.Text("无", key='-CURRENT-COMMAND-', size=(40, 1), text_color='blue'), sg.Text("进度: ", size=(5, 1)), sg.Text("0/0", key='-COMMAND-PROGRESS-', size=(10, 1)), sg.Text("状态: ", size=(5, 1)), sg.Text("", key='-COMMAND-STATUS-', size=(10, 1), text_color='green'), sg.Text("项目: ", size=(5, 1)), sg.Text("无", key='-PROJECT-NAME-STATUS-', size=(20, 1), text_color='purple'), sg.Text("升级类型: ", size=(8, 1)), sg.Text("无", key='-UPGRADE-TYPE-STATUS-', size=(15, 1), text_color='red') ] # 项目列表区域 projects_frame = sg.Frame("项目列表", [ [sg.Column( self.create_project_buttons(), scrollable=True, vertical_scroll_only=True, size=(700, 100), key='-PROJECTS-COLUMN-' )] ], key='-PROJECTS-FRAME-') # 执行控制区域 control_buttons = [ sg.Button('暂停执行', key='-PAUSE-EXECUTION-', button_color=('white', 'orange'), size=(10, 1)), sg.Button('继续执行', key='-RESUME-EXECUTION-', button_color=('white', 'green'), size=(10, 1)), sg.Button('停止执行', key='-STOP-EXECUTION-', button_color=('white', 'red'), size=(10, 1)), sg.Button('跳过命令', key='-SKIP-COMMAND-', button_color=('white', 'blue'), size=(10, 1)), sg.Button('Go Shell', key='-GO-SHELL-', button_color=('white', 'purple'), size=(10, 1), tooltip="执行自动操作:发送Ctrl+T,等待6秒后发送watchdog -close") ] # 端口连接区域 port_layouts = [] for port_id in range(1, MAX_PORTS + 1): port_layout = [ sg.Text(f'端口 {port_id} IP:', size=(8, 1)), sg.Input(key=f"IP{port_id}", default_text=self.default_data[f"IP{port_id}"], size=(15, 1)), sg.Text('端口:', size=(5, 1)), sg.Input(key=f"port{port_id}", default_text=self.default_data[f"port{port_id}"], size=(8, 1)), sg.Button(f'连接{port_id}', key=f'-CONNECT-{port_id}-', button_color=('white', 'green')), sg.Button(f'断开{port_id}', key=f'-DISCONNECT-{port_id}-', button_color=('white', 'red')), sg.Text('●', key=f'-STATUS-LIGHT-{port_id}-', text_color='red', font=('Arial', 12)), sg.Text("未连接", key=f'-CONNECTION-STATUS-{port_id}-'), sg.Text('○', key=f'-AUTO-STATUS-{port_id}-', text_color='gray', font=('Arial', 12), tooltip="自动操作状态: ○未执行 ●已检测到引导字符串 ✓已完成") ] port_layouts.append(port_layout) # 日志保存区域 log_layout = [ sg.Text('日志目录:', size=(8, 1)), sg.Input(key='log_dir', default_text=self.default_data["log_dir"], size=(40, 1)), sg.FolderBrowse('浏览', key='-LOG-BROWSE-'), sg.Button('设置日志目录', key='-SET-LOG-DIR-') ] # 主布局 layout = [ *port_layouts, [projects_frame], status_info, [sg.Frame("执行控制", [control_buttons], key='-CONTROL-FRAME-')], [sg.Text("发送信息:", size=(8, 1)), sg.Input(key='发送信息', size=(50, 1), default_text=self.default_data["发送信息"])], [sg.Button('ROUTER_MPU下发送'), sg.Button('Shell下发送'), sg.Text('每隔', size=(3, 1)), sg.Input(key='interval', default_text=self.default_data["interval"], size=(5, 1)), sg.Text("秒", size=(2, 1)), sg.Button('定时发送'), sg.Button("停止定时发送")], [sg.Button('接收1s')], [sg.Button("使用说明", button_color=('white', 'blue'))], [sg.Text('起始地址:', size=(8, 1)), sg.Input(key='start_addr', default_text=self.default_data["start_addr"], size=(12, 1)), sg.Text('结束地址:', size=(8, 1)), sg.Input(key='end_addr', default_text=self.default_data["end_addr"], size=(12, 1)), sg.Button('dump寄存器')], log_layout, [output], [sg.StatusBar("就绪", key='-STATUS-', size=(50, 1))] ] self.window = sg.Window('远程单板连接工具 (多端口)', layout, finalize=True, resizable=True) self.update_status_lights() self.update_auto_status() def handle_go_shell(self): """处理Go Shell按钮点击事件(增强版)""" # 选择端口对话框 port_layout = [ [sg.Text("选择执行 Go Shell 操作的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [ sg.Text("超时时间(秒):", size=(10, 1)), sg.Input(key='-TIMEOUT-', default_text="60", size=(5, 1)), sg.Text("重试次数:", size=(8, 1)), sg.Input(key='-RETRIES-', default_text="5", size=(5, 1)) ], [sg.Button("确定", key='-OK-'), sg.Button("取消", key='-CANCEL-')] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event == '-OK-': selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") return # 获取配置参数 try: timeout = int(port_values.get('-TIMEOUT-', 60)) max_retries = int(port_values.get('-RETRIES-', 10)) except ValueError: sg.popup("无效的超时或重试次数设置,使用默认值") timeout = 60 max_retries = 10 # 设置日志文件 log_dir = self.default_data["log_dir"] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") for port_id in selected_ports: executor = self.executors[port_id] log_file = os.path.join(log_dir, f"go_shell_port{port_id}_{timestamp}.log") executor.set_log_file(log_file) # 设置自动操作的参数 executor.timeout = timeout executor.max_attempts = max_retries # 在新线程中执行自动操作 threading.Thread( target=self._execute_go_shell_on_port, args=(executor, port_id), daemon=True ).start() def _execute_go_shell_on_port(self, executor, port_id): """在单个端口上执行Go Shell操作""" self.window['-STATUS-'].update(f"端口 {port_id} 开始执行 Go Shell 操作...") # 执行自动操作(不关联项目) success = executor.perform_auto_operation() if success: self.window['-STATUS-'].update(f"端口 {port_id} Go Shell 操作成功") sg.popup_notify(f"端口 {port_id} Go Shell 操作成功", display_duration_in_ms=3000) else: self.window['-STATUS-'].update(f"端口 {port_id} Go Shell 操作失败") sg.popup_error(f"端口 {port_id} Go Shell 操作失败,请检查日志") def update_status_lights(self): for port_id, executor in self.executors.items(): color = 'green' if executor.is_connected else 'red' status = "已连接" if executor.is_connected else "未连接" self.window[f'-STATUS-LIGHT-{port_id}-'].update(text_color=color) self.window[f'-CONNECTION-STATUS-{port_id}-'].update(status) def update_auto_status(self): for port_id, executor in self.executors.items(): if executor.auto_completed: self.window[f'-AUTO-STATUS-{port_id}-'].update('✓', text_color='green') elif executor.boot_string_detected: self.window[f'-AUTO-STATUS-{port_id}-'].update('●', text_color='red') else: self.window[f'-AUTO-STATUS-{port_id}-'].update('○', text_color='gray') def update_output(self): output_text = self.window['-OUTPUT-'].get() updated = False for port_id, executor in self.executors.items(): while not executor.log_queue.empty(): try: message = executor.log_queue.get_nowait() output_text += message + '\n' updated = True except queue.Empty: break if updated: self.window['-OUTPUT-'].update(output_text) def update_status(self): for port_id, executor in self.executors.items(): status = executor.get_execution_status() if port_id == 1: # 只显示第一个端口的状态 if status["current_command"]: self.window['-CURRENT-COMMAND-'].update(status["current_command"]) if status["current_index"] >= 0 and status["total_commands"] > 0: progress_text = f"{status['current_index'] + 1}/{status['total_commands']}" self.window['-COMMAND-PROGRESS-'].update(progress_text) self.window['-COMMAND-STATUS-'].update( "成功" if status["command_success"] else "失败", text_color='green' if status["command_success"] else 'red' ) self.window['-PROJECT-NAME-STATUS-'].update(status["project_name"] or "无") upgrade_type = status["upgrade_type"] self.window['-UPGRADE-TYPE-STATUS-'].update( upgrade_type.upper() if upgrade_type else "无", text_color='red' if upgrade_type else None ) color = 'green' if status["is_connected"] else 'red' self.window[f'-STATUS-LIGHT-{port_id}-'].update(text_color=color) self.window[f'-CONNECTION-STATUS-{port_id}-'].update( "已连接" if status["is_connected"] else "未连接" ) self.update_auto_status() # 更新按钮状态(基于第一个端口) if self.executors[1].is_connected: status1 = self.executors[1].get_execution_status() if status1["is_running"]: self.window['-PAUSE-EXECUTION-'].update(disabled=False) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].update(disabled=False) self.window['-SKIP-COMMAND-'].update(disabled=True) elif status1["is_paused"]: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=False) self.window['-STOP-EXECUTION-'].update(disabled=False) self.window['-SKIP-COMMAND-'].update(disabled=False) else: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].update(disabled=True) self.window['-SKIP-COMMAND-'].update(disabled=True) def refresh_project_list(self): self.window['-PROJECTS-COLUMN-'].update(visible=False) self.window['-PROJECTS-COLUMN-'].update(self.create_project_buttons()) self.window['-PROJECTS-COLUMN-'].update(visible=True) def run(self): last_status_update = time.time() while True: event, values = self.window.read(timeout=100) if event == sg.WINDOW_CLOSED: break self.update_output() current_time = time.time() if current_time - last_status_update > 0.5: self.update_status() last_status_update = current_time # 事件处理 if event.startswith('-CONNECT-'): port_id = int(event.split('-')[2]) ip_key = f"IP{port_id}" port_key = f"port{port_id}" if values[ip_key] and values[port_key]: if self.executors[port_id].connect(values[ip_key], int(values[port_key])): self.window['-STATUS-'].update(f"端口 {port_id} 已连接") self.update_status_lights() elif event.startswith('-DISCONNECT-'): port_id = int(event.split('-')[2]) self.executors[port_id].disconnect() self.window['-STATUS-'].update(f"端口已断开") self.update_status_lights() elif event.startswith('-EDIT-'): parts = event.split('-') if len(parts) >= 4: try: upgrade_type = parts[2].lower() index = int(parts[3]) self.edit_upgrade_commands(index, upgrade_type) except (ValueError, IndexError): logger.error(f"解析编辑事件失败: {event}") elif event == '-SET-LOG-DIR-': if 'log_dir' in values and values['log_dir']: self.default_data["log_dir"] = values['log_dir'] self.save_config() sg.popup(f"日志目录已设置为: {values['log_dir']}") elif event == '-ADD-PROJECT-': self.edit_project_window() self.refresh_project_list() elif event.startswith('-PROJECT-'): index = int(event.split('-')[2]) project_window = self.create_project_function_window(index) if project_window: while True: win_event, win_values = project_window.read() if win_event in (sg.WINDOW_CLOSED, '-CLOSE-'): break if win_event.startswith('-EDIT-'): parts = win_event.split('-') if len(parts) >= 4: upgrade_type = parts[2].lower() self.edit_upgrade_commands(index, upgrade_type) elif win_event == f'-AUTO-{index}-': self.handle_auto_event(win_event, index) # 处理单独升级按钮事件 elif win_event.startswith('-') and win_event.endswith(f'-UPGRADE-{index}-'): # 解析升级类型 upgrade_type = win_event.split('-')[1].lower() self.handle_upgrade_event(win_event, index, upgrade_type) elif win_event == f'-EDIT-{index}-': self.edit_project_window(index) self.refresh_project_list() # 在事件处理部分添加对自动操作按钮的处理 elif event.startswith('-AUTO-'): parts = event.split('-') if len(parts) >= 3: try: index = int(parts[2]) self.handle_auto_event(event, index) except ValueError: pass elif win_event == f'-DELETE-{index}-': if self.project_manager.delete_project(index): sg.popup("项目删除成功") self.refresh_project_list() break else: sg.popup("项目删除失败") # 处理升级按钮事件(包括一键升级、BIOS升级、RBOS升级、INITRD升级) elif win_event.startswith('-') and win_event.endswith(f'-UPGRADE-{index}-'): parts = win_event.split('-') if len(parts) >= 4: upgrade_type = parts[1].lower() # 事件标识符的格式:-<升级类型>-UPGRADE-<索引>- self.handle_upgrade_event(win_event, index, upgrade_type) project_window.close() elif event == '-IMPORT-PROJECTS-': file_path = sg.popup_get_file("选择项目配置文件", file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_export_projects(file_path, 'import'): sg.popup("项目导入成功") self.refresh_project_list() else: sg.popup("项目导入失败") elif event == '-EXPORT-PROJECTS-': file_path = sg.popup_get_file("保存项目配置文件", save_as=True, file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_export_projects(file_path, 'export'): sg.popup("项目导出成功") else: sg.popup("项目导出失败") elif event == '-CREATE-TEMPLATE-': self.create_template_file() sg.popup("模板文件已生成: projects_template.json") # 执行控制 elif event == '-PAUSE-EXECUTION-': for executor in self.executors.values(): if executor.get_execution_status()["is_running"]: executor.pause_execution() self.window['-STATUS-'].update("执行已暂停") elif event == '-RESUME-EXECUTION-': for executor in self.executors.values(): if executor.get_execution_status()["is_paused"]: executor.resume_execution() self.window['-STATUS-'].update("执行已继续") elif event == '-STOP-EXECUTION-': for executor in self.executors.values(): executor.stop_execution() self.window['-STATUS-'].update("执行已停止") elif event == '-SKIP-COMMAND-': for executor in self.executors.values(): if executor.get_execution_status()["is_paused"]: executor.command_success = True executor.resume_execution() self.window['-STATUS-'].update("已跳过当前命令") elif event == '-GO-SHELL-': self.handle_go_shell() # 其他功能 elif event in ('ROUTER_MPU下发送', 'Shell下发送'): if self.save_config() and values["发送信息"]: for executor in self.executors.values(): if executor.is_connected: executor.send(values["发送信息"]) executor.read_until_prompt() elif event == '定时发送': if self.save_config() and values["发送信息"] and values["interval"]: try: interval = float(values["interval"]) if interval <= 0: raise ValueError("间隔必须大于0") threading.Thread( target=self.periodic_send, args=(values["发送信息"], interval), daemon=True ).start() except Exception as e: sg.popup(f"无效的间隔: {str(e)}") elif event == "停止定时发送": for executor in self.executors.values(): executor.stop_execution() elif event == '接收1s': for executor in self.executors.values(): if executor.is_connected: executor.read_until_prompt(timeout=1) elif event == 'dump寄存器': if values["start_addr"] and values["end_addr"]: for executor in self.executors.values(): if executor.is_connected: executor.send(f"dump {values['start_addr']} {values['end_addr']}") executor.read_until_prompt() elif event == '使用说明': self.show_help() self.running = False for executor in self.executors.values(): executor.disconnect() self.window.close() def periodic_send(self, message, interval): while not self.executors[1].stop_event.is_set(): for executor in self.executors.values(): if executor.is_connected: executor.send(message) executor.read_until_prompt() time.sleep(interval) def create_template_file(self): template = { "projects": [ { "name": "示例项目", "commands": [ "watchdog -close", "ifconfig eth0 71.19.0.53 255.255.255.0 71.19.0.1", "ftp -get ftp://user:pass@71.19.0.120/firmware.bin /home/firmware.bin", "flash -w /home/firmware.bin" ], "auto_operation": True, "bios_commands": ["bios_update_command1", "bios_update_command2"], "rbos_commands": ["rbos_update_command1", "rbos_update_command2"], "initrd_commands": ["initrd_update_command1", "initrd_update_command2"], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } ] } try: with open('projects_template.json', 'w', encoding='utf-8') as f: json.dump(template, f, indent=4, ensure_ascii=False) return True except Exception as e: logger.error(f"生成模板失败: {e}") return False def show_help(self): help_text = f"""=== 远程单板连接工具使用说明 (多端口) === 1. 多端口连接管理: - 支持同时连接 {MAX_PORTS} 个端口 - 每个端口独立显示连接状态(红:未连接, 绿:已连接) - 每个端口独立保存日志文件 2. 项目功能: - 点击项目名称打开功能窗口 - 一键升级:执行项目基础指令 - BIOS/RBOS/INITRD升级:执行特定升级指令 - 自动操作:执行引导字符串检测和自动操作 3. 升级功能: - 每种升级类型有独立的指令配置 - 点击"✎"按钮编辑对应升级类型的指令 - 支持分段发送和响应验证 - 可配置最大重试次数和重试延迟 - 在状态栏显示当前升级类型 4. Go Shell功能: - 自动检测引导字符串(支持多种变体) - 发送Ctrl+T组合键 - 等待6秒后发送watchdog -close命令 - 可配置超时时间和重试次数 5. 执行控制: - 暂停/继续/停止当前执行 - 跳过当前失败的命令 - 实时显示命令执行状态 6. 日志管理: - 每个端口独立日志文件 - 可设置日志保存目录 7. 其他功能: - 定时发送命令 - 寄存器dump - 项目导入导出 """ sg.popup(help_text, title="使用说明", font=("Arial", 10), non_blocking=True)
============== 主程序入口 ==============
if name == “main”:
try:
app = RemoteControlApp()
app.run()
except Exception as e:
logger.exception(“应用程序崩溃”)
sg.popup_error(f"应用程序发生致命错误: {str(e)}")
这是我的代码文件,请解析我的代码,并给出各模块代码的解析说明,和功能,以及操作说明,并以文件的形式给我