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