请解析我的代码
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)}")