projects for resume

1. Web application using api

2. Bronchure site

3. E-commerce site

4. Personal Portfolio

5. Simple game

My first big project was a text-adventure website. I startet off with the basic html/css layout, then got into form validation for the commands, added some animations with TimeLine, added sounds. Then, after the programm got bigger, I went into local storage for progress saving and item inventory and currently (getting back to it every now and then), I am implementing some user identification and OOP for enemies. Long sotry short, I recommend something easy to begin with which is scaleable.

I'd say building a full-fledged e-commerce could take months, but a simple prototype that could impress some recruiters can be deployed in a week or so. I'd highly recommend to have 'game' project in your resume because it would familiarize you with how events work, some simple mathematics and how to handle collision, things like this.

请解析我的代码 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'\s*to\s*skip\s*boot" # 引导字符串正则表达式 # ============== 日志配置 ============== 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.projects = self.load_projects() def load_projects(self): """加载项目配置""" if os.path.exists(PROJECTS_FILE): try: with open(PROJECTS_FILE, 'r') as f: return json.load(f) except Exception as e: logger.error(f"加载项目配置失败: {e}") return {"projects": []} return {"projects": []} def save_projects(self): """保存项目配置""" try: with open(PROJECTS_FILE, 'w') as f: json.dump(self.projects, f, indent=4) return True except Exception as e: logger.error(f"保存项目配置失败: {e}") return False def add_project(self, name, commands, auto_operation=False): """添加新项目""" project = { "name": name, "commands": commands, "auto_operation": auto_operation, # 新增:自动操作标志 "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.projects["projects"].append(project) if self.save_projects(): return project return None def update_project(self, index, name, commands, auto_operation=False): """更新项目""" if 0 <= index < len(self.projects["projects"]): self.projects["projects"][index]["name"] = name self.projects["projects"][index]["commands"] = commands self.projects["projects"][index]["auto_operation"] = auto_operation # 新增 if self.save_projects(): return True return False def delete_project(self, index): """删除项目""" if 极 <= index < len(self.projects["projects"]): del self.projects["projects"][index] return self.save_projects() return False def import_projects(self, file_path): """导入项目配置""" try: with open(file_path, 'r') as f: imported = json.load(f) if "projects" in imported and isinstance(imported["projects"], list): self.projects = imported return self.save_projects() except Exception as e: logger.error(f"导入项目失败: {e}") return False def export_projects(self, file极ath): """导出项目配置""" try: with open(file_path, 'w') as f: json.dump(self.projects, f, indent=4) return True except Exception as e: logger.error(f"导出项目失败: {e}") return False def get_project_commands(self, index): """获取项目的命令列表""" if 0 <= index < len(self.projects["projects"]): return self.projects["projects"][index]["commands"] return [] # ============== 指令执行类(多端口支持) ============== class CommandExecutor: def __init__(self, port_id): self.port_id = port_id # 端口标识符 (1, 2, ...) self.tn = None self.is_connected = False self.response = "" self.prev_cmd = "" 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 # 新增:自动操作成功回调 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) # 分块发送 chunks = [encoded_content[i:i+CHUNK_SIZE] for i in range(0, len(encoded_content), CHUNK_SIZE)] for chunk in chunks: 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 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()}") self.prev_cmd = content 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 self.prev_cmd and decoded_response.startswith(self.prev_cmd.strip()): decoded_response = decoded_response[len(self.prev_cmd.strip()):].lstrip() # 移除尾部的提示符 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): """执行命令序列(增强版)""" if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False self.stop_event.clear() self.pause_event.clear() self.total_commands = len(commands) self.log_queue.put(f"端口 {self.port_id} 开始执行 {self.total_commands} 条命令") self.log_to_file(f"Starting execution of {self.total_commands} commands") 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 # 检查暂停状态 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 # 等待命令完成 self.read_until_prompt(timeout=10) # 命令间延迟 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 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 } # ============== 增强自动操作功能 ============== def detect_boot_string_and_auto_operation(self): """检测引导字符串并执行自动操作""" if not self.is_connected or self.auto_completed: return False try: # 读取响应并检测引导字符串 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, performing auto operation") # 在1秒内发送Ctrl+T self.log_queue.put(f"端口 {self.port_id} 发送 Ctrl+T") self.log_to_file("Sending Ctrl+T") self.send_ctrl_t() # 等待5秒后发送watchdog命令 time.sleep(6) 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=5) if AUTO_OPERATION_SUCCESS_STR in watchdog_response: self.log_queue.put(f"端口 {self.port_id} watchdog命令执行成功") self.auto_completed = True # 如果设置了回调,执行回调(触发升级) if self.auto_operation_callback: self.log_queue.put(f"端口 {self.port_id} 触发自动升级") self.auto_operation_callback() else: self.log_queue.put(f"端口 {self.port_id} watchdog命令执行失败") self.log_to_file(f"Watchdog命令失败,响应: {watchdog_response}") return self.auto_completed 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 = self.detect_boot_string_and_auto_operation() # 如果未检测到,尝试多次检测 if not self.boot_string_detected: for _ in range(10): # 最多尝试3次 time.sleep(1) success = self.detect_boot_string_and_auto_operation() if success: break return success def send_ctrl_t(self): """发送Ctrl+T组合键""" if not self.is_connected: return False try: with self.lock: # Ctrl+T 的ASCII码是20 self.tn.write(b"\x14") self.log_queue.put(f"端口 {self.port_id} 已发送 Ctrl+T") self.log_to_file("Sent Ctrl+T") return True except Exception as e: self.log_queue.put(f"端口 {self.port_id} 发送Ctrl+T失败: {str(e)}") self.log_to_file(f"Send Ctrl+T error: {str(e)}") return False # ============== GUI 界面类(多端口支持) ============== class RemoteControlApp: def __init__(self): self.project_manager = ProjectManager() self.default_data = self.load_default_data() self.executors = {} # 端口ID到执行器的映射 self.window = None self.setup_gui() self.running = True # 初始化端口执行器 for port_id in range(1, MAX_PORTS + 1): self.executors[port_id] = CommandExecutor(port_id) def load_default_data(self): """加载默认配置""" default_data = { "IP1": "71.19.0.120", "port1": "1001", "IP2": "71.19.0.120", "port2": "1002", "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) else: 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 = { "IP1": values.get("IP1", self.default_data["极1"]), "port1": values.get("port1", self.default_data["port1"]), "IP2": values.get("IP2", self.default_data["IP2"]), "port2": values.get("port2", self.default_data["port2"]), "FTP_IP": values.get("FTP_IP", self.default_data["FTP_IP"]), "芯片名称": values.get("芯片名称", self.default_data["芯片名称"]), '发送信息': values.get("发送信息", self.default_data["发送信息"]), "board_local_ip": values.get("board_local_ip", self.default_data["board_local_ip"]), 'interval': values.get("interval", self.default_data["interval"]), "start_addr": values.get("start_addr", self.default_data["start_addr"]), "end_addr": values.get("end_addr", self.default_data["end_addr"]), "文件FTP路径": values.get("文件FTP路径", self.default_data["文件FTP路径"]), "log_dir": values.get("log_dir", self.default_data["log_dir"]) } 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): # 添加自动操作状态指示器 auto_status = '✓' if project.get("auto_operation", False) else '○' auto_color = 'green' if project.get("auto_operation", False) else 'gray' row = [ sg.Button(project["name"], key=f'-PROJECT-{i}-', size=(15,1), tooltip=f"创建于: {project['created_at']}\n命令数: {len(project['commands'])}"), sg.Text(auto_status, text_color=auto_color, font=('Arial', 12), tooltip="自动操作: ○禁用 ✓启用", size=(2,1)), sg.Button("一键升级", key=f'-UPGRADE-{i}-', button_color=('white', 'green')), sg.Button("自动", key=f'-AUTO-{i}-', button_color=('white', 'purple'), tooltip="执行自动操作并触发升级"), sg.Button("编辑", key=f'-EDIT-{i}-', button_color=('white', 'blue')), sg.Button("删除", key=f'-DELETE-{i}-', button_color=('white', 'red')) ] 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 edit_project_window(self, index=None): """项目编辑窗口(增加自动操作选项)""" project = None if index is not None and 0 <= index < len(self.project_manager.projects["projects"]): project = self.project_manager.projects["projects"][index] layout = [ [sg.Text("项目名称:"), sg.Input(key='-PROJECT-NAME-', default_text=project["name"] if project else "")], [sg.Checkbox("启用自动操作", key='-AUTO-OPERATION-', default=project.get("auto_operation", False) if project else False, tooltip="如果启用,将在执行升级命令前自动检测引导字符串并执行Ctrl+T和watchdog -close")], [sg.Text("升级指令:")], [sg.Multiline(key='-PROJECT-COMMANDS-', size=(60, 10), default_text='\n'.join(project["commands"]) if project else "", tooltip="每行一个命令,命令将按顺序执行")], [sg.Button("保存", key='-SAVE-PROJECT-'), sg.Button("取消", key='-CANCEL-PROJECT-')] ] window = sg.Window("项目编辑", layout, modal=True) while True: event, values = window.read() if event in (sg.WINDOW_CLOSED, '-CANCEL-PROJECT-'): break elif event == '-SAVE-PROJECT-': name = values['-PROJECT-NAME-'].strip() commands = [cmd.strip() for cmd in values['-PROJECT-COMMANDS-'].split('\n') if cmd.strip()] auto_operation = values['-AUTO-OPERATION-'] # 获取自动操作选项 if not name: sg.popup("项目名称不能为空") continue if index is None: new_project = self.project_manager.add_project(name, commands, auto_operation) if new_project: sg.popup(f"项目 '{name}' 添加成功") break else: if self.project_manager.update_project(index, name, commands, auto_operation): sg.popup(f"项目 '{name}' 更新成功") break sg.popup("操作失败,请查看日志") window.close() return True def setup_gui(self): """设置GUI界面(多端口支持)""" 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)) ] # 项目列表区域 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)), ] # 端口连接区域 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 update_status_lights(self): """更新所有端口的状态指示灯""" for port_id in self.executors: executor = self.executors[port_id] 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 in self.executors: executor = self.executors[port_id] 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 status["current_command"] and port_id == 1: # 只显示第一个端口的命令 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) # 更新状态指示灯 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) 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) else: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].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() stop_loop = 0 interval = 10000 loop_msg = "" while True: event, values = self.window.read(timeout=100) # 100ms超时 if event == sg.WINDOW_CLOSED: break # 更新输出区域 self.update_output() # 定期更新状态(每0.5秒一次) 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() else: sg.popup(f"请输入端口 {port_id} 的IP和端口号") elif event.startswith('-DISCONNECT-'): port_id = int(event.split('-')[2]) self.executors[port_id].disconnect() self.window['-STATUS-'].update(f"端口 {port_id} 已断开") self.update_status_lights() # 日志目录设置 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('-EDIT-'): index = int(event.split('-')[2]) self.edit_project_window(index) self.refresh_project_list() elif event.startswith('-DELETE-'): index = int(event.split('-')[2]) if self.project_manager.delete_project(index): sg.popup("项目删除成功") self.refresh_project_list() else: sg.popup("项目删除失败") elif event == '-IMPORT-PROJECTS-': file_path = sg.popup_get_file("选择项目配置文件", file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_projects(file_path): 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.export_projects(file_path): sg.popup("项目导出成功") else: sg.popup("项目导出失败") elif event == '-CREATE-TEMPLATE-': self.create_template_file() sg.popup("模板文件已生成: projects_template.json") # 一键升级(多端口支持) elif event.startswith('-UPGRADE-'): index = int(event.split('-')[2]) if index < len(self.project_manager.projects["projects"]): project = self.project_manager.projects["projects"][index] # 选择端口对话框 port_layout = [ [sg.Text(f"选择执行 {project['name']} 的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [sg.Button("确定"), sg.Button("取消")] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event == "确定": selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") continue # 设置日志文件 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}_port{port_id}_{timestamp}.log") self.executors[port_id].set_log_file(log_file) # 在新线程中执行升级 threading.Thread( target=self.execute_project_upgrade, args=(index, selected_ports), daemon=True ).start() # 自动操作按钮 elif event.startswith('-AUTO-'): index = int(event.split('-')[2]) if index < len(self.project_manager.projects["projects"]): project = self.project_manager.projects["projects"][index] # 选择端口对话框 port_layout = [ [sg.Text(f"选择执行 {project['name']} 自动操作的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [sg.Button("确定"), sg.Button("取消")] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event == "确定": selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") continue # 设置日志文件 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}_auto_port{port_id}_{timestamp}.log") self.executors[port_id].set_log_file(log_file) # 在新线程中执行自动操作 threading.Thread( target=self.execute_project_auto_operation, args=(index, selected_ports), daemon=True ).start() # 执行控制 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 == 'ROUTER_MPU下发送': if self.save_config() and values["发送信息"]: for port_id, executor in self.executors.items(): if executor.is_connected: executor.send(values["发送信息"]) executor.read_until_prompt() elif event == 'Shell下发送': if self.save_config() and values["发送信息"]: for port_id, executor in self.executors.items(): 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") loop_msg = values["发送信息"] threading.Thread( target=self.periodic_send, args=(loop_msg, interval), daemon=True ).start() except Exception as e: sg.popup(f"无效的间隔: {str(e)}") else: sg.popup("请输入发送信息和有效间隔") 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 == '重新获取sdk.out': for executor in self.executors.values(): if executor.is_connected: executor.send("get_sdk.out") executor.read_until_prompt() elif event == '一键升级MT1': for executor in self.executors.values(): if executor.is_connected: executor.send("upgrade_mt1") executor.read_until_prompt() elif event == '一键升级MT2': for executor in self.executors.values(): if executor.is_connected: executor.send("upgrade_mt2") executor.read_until_prompt() elif event == '使用说明': self.show_help() self.running = False for executor in self.executors.values(): executor.disconnect() self.window.close() def execute_project_auto_operation(self, index, port_ids): """执行项目自动操作并触发升级""" project = self.project_manager.projects["projects"][index] commands = project["commands"] # 为每个端口启动自动操作线程 threads = [] for port_id in port_ids: executor = self.executors[port_id] # 设置自动操作回调(触发升级) upgrade_callback = lambda e=executor, c=commands: self._execute_upgrade_commands(e, c) thread = threading.Thread( target=self._perform_auto_operation_on_port, args=(executor, upgrade_callback, port_id), daemon=True ) thread.start() threads.append(thread) # 等待所有线程完成 for thread in threads: thread.join(timeout=300) # 5分钟超时 self.window['-STATUS-'].update("自动操作完成") def _perform_auto_operation_on_port(self, executor, upgrade_callback, port_id): """在单个端口上执行自动操作""" self.window['-STATUS-'].update(f"端口 {port_id} 开始自动操作...") success = executor.perform_auto_operation(callback=upgrade_callback) self.window['-STATUS-'].update(f"端口 {port_id} 自动操作{'成功' if success else '失败'}") def _execute_upgrade_commands(self, executor, commands): """执行升级命令序列""" return executor.execute_commands(commands) def execute_project_upgrade(self, index, port_ids): """在指定端口上执行项目升级""" commands = self.project_manager.get_project_commands(index) if commands: # 重置进度显示 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), daemon=True ) thread.start() threads.append(thread) # 等待所有线程完成 for thread in threads: thread.join(timeout=300) # 5分钟超时 self.window['-STATUS-'].update("升级任务已完成") else: for port_id in port_ids: self.executors[port_id].log_queue.put("错误:项目没有配置命令") def _execute_upgrade_on_port(self, executor, commands, port_id): """在单个端口上执行升级命令""" self.window['-STATUS-'].update(f"端口 {port_id} 开始升级...") success = executor.execute_commands(commands) self.window['-STATUS-'].update(f"端口 {port_id} 升级{'成功' if success else '失败'}") 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 show_help(self): """显示使用说明(多端口版)""" help_text = f""" === 远程单板连接工具使用说明 (多端口) === 1. 多端口连接管理: - 支持同时连接 {MAX_PORTS} 个端口 - 每个端口独立显示连接状态(红:未连接, 绿:已连接) - 每个端口独立保存日志文件 2. 自动操作功能: - 点击"自动"按钮执行特殊引导操作 - 检测"Press 'Ctrl+T' to skip boot"字符串 - 自动发送Ctrl+T组合键 - 5秒后发送"watchdog -close"命令 - 成功执行后自动触发项目升级 - 状态指示器: ○未执行 ●已检测 ✓已完成 3. 项目升级: - 选择项目后,会弹出端口选择对话框 - 可以为每个选择的端口设置独立的日志文件 - 日志文件保存在指定目录,文件名格式:项目名_portX_时间戳.log 4. 日志管理: - 默认日志目录:{self.default_data.get('log_dir', os.getcwd())} - 可以随时修改日志保存目录 - 每个端口连接会记录详细的操作日志 5. 命令执行控制: - 暂停执行:暂停所有端口的命令执行 - 继续执行:继续所有端口的命令执行 - 停止执行:停止所有端口的命令执行 6. 发送机制: - 命令发送到所有已连接的端口 - 每个端口独立处理响应 - 支持定时发送到所有端口 """ sg.popup(help_text, title="使用说明", font=("Arial", 11)) def create_template_file(self): """创建项目配置模板""" try: template = { "projects": [ { "name": "项目A", "commands": [ "ifconfig eth0 192.168.0.100", "ftpget -u user -p pass 192.168.0.1 /home/firmware.bin firmware.bin", "flash -w 0 0x0000000 0xa000000 0x2000000" ], "auto_operation": True, # 新增:自动操作标志 "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }, { "name": "项目B", "commands": [ "ifconfig eth0 192.168.0.101", "ping 192.168.0.1", "reboot" ], "auto_operation": False, # 新增:自动操作标志 "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } ] } with open("projects_template.json", 'w') as f: json.dump(template, f, indent=4) return True except Exception as e: logger.error(f"创建模板文件失败: {e}") return False # ============== 主程序入口 ============== if __name__ == "__main__": # 启动应用 try: app = RemoteControlApp() app.run() except Exception as e: logger.exception("应用程序崩溃") sg.popup_error(f"应用程序发生致命错误: {str(e)}")
07-08
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 # 支持的最大端口数 # ============== 日志配置 ============== 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.projects = self.load_projects() def load_projects(self): """加载项目配置""" if os.path.exists(PROJECTS_FILE): try: with open(PROJECTS_FILE, 'r') as f: return json.load(f) except Exception as e: logger.error(f"加载项目配置失败: {e}") return {"projects": []} return {"projects": []} def save_projects(self): """保存项目配置""" try: with open(PROJECTS_FILE, 'w') as f: json.dump(self.projects, f, indent=4) return True except Exception as e: logger.error(f"保存项目配置失败: {e}") return False def add_project(self, name, commands): """添加新项目""" project = { "name": name, "commands": commands, "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } self.projects["projects"].append(project) if self.save_projects(): return project return None def update_project(self, index, name, commands): """更新项目""" if 0 <= index < len(self.projects["projects"]): self.projects["projects"][index]["name"] = name self.projects["projects"][index]["commands"] = commands if self.save_projects(): return True return False def delete_project(self, index): """删除项目""" if 0 <= index < len(self.projects["projects"]): del self.projects["projects"][index] return self.save_projects() return False def import_projects(self, file_path): """导入项目配置""" try: with open(file_path, 'r') as f: imported = json.load(f) if "projects" in imported and isinstance(imported["projects"], list): self.projects = imported return self.save_projects() except Exception as e: logger.error(f"导入项目失败: {e}") return False def export_projects(self, file_path): """导出项目配置""" try: with open(file_path, 'w') as f: json.dump(self.projects, f, indent=4) return True except Exception as e: logger.error(f"导出项目失败: {e}") return False def get_project_commands(self, index): """获取项目的命令列表""" if 0 <= index < len(self.projects["projects"]): return self.projects["projects"][index]["commands"] return [] # ============== 指令执行类(多端口支持) ============== class CommandExecutor: def __init__(self, port_id): self.port_id = port_id # 端口标识符 (1, 2, ...) self.tn = None self.is_connected = False self.response = "" self.prev_cmd = "" 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 = "" # 日志文件路径 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) 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) # 分块发送 chunks = [encoded_content[i:i+CHUNK_SIZE] for i in range(0, len(encoded_content), CHUNK_SIZE)] for chunk in chunks: 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 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()}") self.prev_cmd = content 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 self.prev_cmd and decoded_response.startswith(self.prev_cmd.strip()): decoded_response = decoded_response[len(self.prev_cmd.strip()):].lstrip() # 移除尾部的提示符 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): """执行命令序列(增强版)""" if not self.is_connected: self.log_queue.put(f"端口 {self.port_id} 错误:未连接到设备") return False self.stop_event.clear() self.pause_event.clear() self.total_commands = len(commands) self.log_queue.put(f"端口 {self.port_id} 开始执行 {self.total_commands} 条命令") self.log_to_file(f"Starting execution of {self.total_commands} commands") 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 # 检查暂停状态 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 # 等待命令完成 self.read_until_prompt(timeout=10) # 命令间延迟 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 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 } # ============== GUI 界面类(多端口支持) ============== class RemoteControlApp: def __init__(self): self.project_manager = ProjectManager() self.default_data = self.load_default_data() self.executors = {} # 端口ID到执行器的映射 self.window = None self.setup_gui() self.running = True # 初始化端口执行器 for port_id in range(1, MAX_PORTS + 1): self.executors[port_id] = CommandExecutor(port_id) def load_default_data(self): """加载默认配置""" default_data = { "IP1": "71.19.0.120", "port1": "1001", "IP2": "71.19.0.120", "port2": "1002", "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) else: 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 = { "IP1": values.get("IP1", self.default_data["IP1"]), "port1": values.get("port1", self.default_data["port1"]), "IP2": values.get("IP2", self.default_data["IP2"]), "port2": values.get("port2", self.default_data["port2"]), "FTP_IP": values.get("FTP_IP", self.default_data["FTP_IP"]), "芯片名称": values.get("芯片名称", self.default_data["芯片名称"]), '发送信息': values.get("发送信息", self.default_data["发送信息"]), "board_local_ip": values.get("board_local_ip", self.default_data["board_local_ip"]), 'interval': values.get("interval", self.default_data["interval"]), "start_addr": values.get("start_addr", self.default_data["start_addr"]), "end_addr": values.get("end_addr", self.default_data["end_addr"]), "文件FTP路径": values.get("文件FTP路径", self.default_data["文件FTP路径"]), "log_dir": values.get("log_dir", self.default_data["log_dir"]) } 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=(15,1), tooltip=f"创建于: {project['created_at']}\n命令数: {len(project['commands'])}"), sg.Button("一键升级", key=f'-UPGRADE-{i}-', button_color=('white', 'green')), sg.Button("编辑", key=f'-EDIT-{i}-', button_color=('white', 'blue')), sg.Button("删除", key=f'-DELETE-{i}-', button_color=('white', 'red')) ] 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 edit_project_window(self, index=None): """项目编辑窗口""" project = None if index is not None and 0 <= index < len(self.project_manager.projects["projects"]): project = self.project_manager.projects["projects"][index] layout = [ [sg.Text("项目名称:"), sg.Input(key='-PROJECT-NAME-', default_text=project["name"] if project else "")], [sg.Text("升级指令:")], [sg.Multiline(key='-PROJECT-COMMANDS-', size=(60, 10), default_text='\n'.join(project["commands"]) if project else "", tooltip="每行一个命令,命令将按顺序执行")], [sg.Button("保存", key='-SAVE-PROJECT-'), sg.Button("取消", key='-CANCEL-PROJECT-')] ] window = sg.Window("项目编辑", layout, modal=True) while True: event, values = window.read() if event in (sg.WINDOW_CLOSED, '-CANCEL-PROJECT-'): break elif event == '-SAVE-PROJECT-': name = values['-PROJECT-NAME-'].strip() commands = [cmd.strip() for cmd in values['-PROJECT-COMMANDS-'].split('\n') if cmd.strip()] if not name: sg.popup("项目名称不能为空") continue if index is None: new_project = self.project_manager.add_project(name, commands) if new_project: sg.popup(f"项目 '{name}' 添加成功") break else: if self.project_manager.update_project(index, name, commands): sg.popup(f"项目 '{name}' 更新成功") break sg.popup("操作失败,请查看日志") window.close() return True def setup_gui(self): """设置GUI界面(多端口支持)""" 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)) ] # 项目列表区域 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)) ] # 端口连接区域 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}-') ] 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.Text('芯片名称:', size=(8,1)), sg.Input(key="芯片名称", default_text=self.default_data["芯片名称"], size=(15,1)), # sg.Text('文件FTP路径:', size=(10,1)), sg.Input(key="文件FTP路径", default_text=self.default_data["文件FTP路径"], size=(30,1))], # # [sg.Text('FTP_IP:', size=(6,1)), sg.Input(key="FTP_IP", default_text=self.default_data["FTP_IP"], size=(15,1)), # sg.Text('单板IP:', size=(6,1)), sg.Input(key="board_local_ip", default_text=self.default_data["board_local_ip"], size=(15,1))], # # [sg.Text("SLT测试用"), sg.Button('重新获取sdk.out'), sg.Button("一键升级MT1"), sg.Button("一键升级MT2")], [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() def update_status_lights(self): """更新所有端口的状态指示灯""" for port_id in self.executors: executor = self.executors[port_id] 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_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 status["current_command"] and port_id == 1: # 只显示第一个端口的命令 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) # 更新状态指示灯 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 "未连接") # 更新按钮状态(基于第一个端口的状态) 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) 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) else: self.window['-PAUSE-EXECUTION-'].update(disabled=True) self.window['-RESUME-EXECUTION-'].update(disabled=True) self.window['-STOP-EXECUTION-'].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() stop_loop = 0 interval = 10000 loop_msg = "" while True: event, values = self.window.read(timeout=100) # 100ms超时 if event == sg.WINDOW_CLOSED: break # 更新输出区域 self.update_output() # 定期更新状态(每0.5秒一次) 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() else: sg.popup(f"请输入端口 {port_id} 的IP和端口号") elif event.startswith('-DISCONNECT-'): port_id = int(event.split('-')[2]) self.executors[port_id].disconnect() self.window['-STATUS-'].update(f"端口 {port_id} 已断开") self.update_status_lights() # 日志目录设置 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('-EDIT-'): index = int(event.split('-')[2]) self.edit_project_window(index) self.refresh_project_list() elif event.startswith('-DELETE-'): index = int(event.split('-')[2]) if self.project_manager.delete_project(index): sg.popup("项目删除成功") self.refresh_project_list() else: sg.popup("项目删除失败") elif event == '-IMPORT-PROJECTS-': file_path = sg.popup_get_file("选择项目配置文件", file_types=(("JSON Files", "*.json"),)) if file_path and self.project_manager.import_projects(file_path): 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.export_projects(file_path): sg.popup("项目导出成功") else: sg.popup("项目导出失败") elif event == '-CREATE-TEMPLATE-': self.create_template_file() sg.popup("模板文件已生成: projects_template.json") # 一键升级(多端口支持) elif event.startswith('-UPGRADE-'): index = int(event.split('-')[2]) if index < len(self.project_manager.projects["projects"]): project = self.project_manager.projects["projects"][index] # 选择端口对话框 port_layout = [ [sg.Text(f"选择执行 {project['name']} 的端口:")], *[[sg.Checkbox(f"端口 {port_id}", key=f'-PORT-{port_id}-', default=True)] for port_id in self.executors], [sg.Button("确定"), sg.Button("取消")] ] port_window = sg.Window("选择端口", port_layout, modal=True) port_event, port_values = port_window.read() port_window.close() if port_event == "确定": selected_ports = [port_id for port_id in self.executors if port_values[f'-PORT-{port_id}-']] if not selected_ports: sg.popup("请至少选择一个端口") continue # 设置日志文件 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}_port{port_id}_{timestamp}.log") self.executors[port_id].set_log_file(log_file) # 在新线程中执行升级 threading.Thread( target=self.execute_project_upgrade, args=(index, selected_ports), daemon=True ).start() # 执行控制 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 == 'ROUTER_MPU下发送': if self.save_config() and values["发送信息"]: for port_id, executor in self.executors.items(): if executor.is_connected: executor.send(values["发送信息"]) executor.read_until_prompt() elif event == 'Shell下发送': if self.save_config() and values["发送信息"]: for port_id, executor in self.executors.items(): 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") loop_msg = values["发送信息"] threading.Thread( target=self.periodic_send, args=(loop_msg, interval), daemon=True ).start() except Exception as e: sg.popup(f"无效的间隔: {str(e)}") else: sg.popup("请输入发送信息和有效间隔") 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 == '重新获取sdk.out': for executor in self.executors.values(): if executor.is_connected: executor.send("get_sdk.out") executor.read_until_prompt() elif event == '一键升级MT1': for executor in self.executors.values(): if executor.is_connected: executor.send("upgrade_mt1") executor.read_until_prompt() elif event == '一键升级MT2': for executor in self.executors.values(): if executor.is_connected: executor.send("upgrade_mt2") executor.read_until_prompt() elif event == '使用说明': self.show_help() self.running = False for executor in self.executors.values(): executor.disconnect() self.window.close() def execute_project_upgrade(self, index, port_ids): """在指定端口上执行项目升级""" commands = self.project_manager.get_project_commands(index) if commands: # 重置进度显示 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), daemon=True ) thread.start() threads.append(thread) # 等待所有线程完成 for thread in threads: thread.join(timeout=300) # 5分钟超时 self.window['-STATUS-'].update("升级任务已完成") else: for port_id in port_ids: self.executors[port_id].log_queue.put("错误:项目没有配置命令") def _execute_upgrade_on_port(self, executor, commands, port_id): """在单个端口上执行升级命令""" self.window['-STATUS-'].update(f"端口 {port_id} 开始升级...") success = executor.execute_commands(commands) self.window['-STATUS-'].update(f"端口 {port_id} 升级{'成功' if success else '失败'}") 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 show_help(self): """显示使用说明(多端口版)""" help_text = f""" === 远程单板连接工具使用说明 (多端口) === 1. 多端口连接管理: - 支持同时连接 {MAX_PORTS} 个端口 - 每个端口独立显示连接状态(红:未连接, 绿:已连接) - 每个端口独立保存日志文件 2. 项目升级: - 选择项目后,会弹出端口选择对话框 - 可以为每个选择的端口设置独立的日志文件 - 日志文件保存在指定目录,文件名格式:项目名_portX_时间戳.log 3. 日志管理: - 默认日志目录:{self.default_data.get('log_dir', os.getcwd())} - 可以随时修改日志保存目录 - 每个端口连接会记录详细的操作日志 4. 命令执行控制: - 暂停执行:暂停所有端口的命令执行 - 继续执行:继续所有端口的命令执行 - 停止执行:停止所有端口的命令执行 5. 发送机制: - 命令发送到所有已连接的端口 - 每个端口独立处理响应 - 支持定时发送到所有端口 """ sg.popup(help_text, title="使用说明", font=("Arial", 11)) def create_template_file(self): """创建项目配置模板""" try: template = { "projects": [ { "name": "项目A", "commands": [ "ifconfig eth0 192.168.0.100", "ftpget -u user -p pass 192.168.0.1 /home/firmware.bin firmware.bin", "flash -w 0 0x0000000 0xa000000 0x2000000" ], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") }, { "name": "项目B", "commands": [ "ifconfig eth0 192.168.0.101", "ping 192.168.0.1", "reboot" ], "created_at": datetime.now().strftime("%Y-%m-%d %H:%M:%S") } ] } with open("projects_template.json", 'w') as f: json.dump(template, f, indent=4) return True except Exception as e: logger.error(f"创建模板文件失败: {e}") return False # ============== 主程序入口 ============== if __name__ == "__main__": # 启动应用 try: app = RemoteControlApp() app.run() except Exception as e: logger.exception("应用程序崩溃") sg.popup_error(f"应用程序发生致命错误: {str(e)}") 以上是我的代码,请分析我的代码,在我的代码功能保持不变的情况下,请帮我增加一个需求,链接端口后,当识别端口到的打印有“Press 'Ctrl+T' to skip boot”的字符串时,在1s内模拟键盘敲击Ctrl+T键,并且等待5s后,在该端口下发指令“watchdog -close”,将这个功能包装成一个“自动”按钮,点击后进行上述操作,不点击不进行上述操作,并给出完整代码
07-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值