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”,将这个功能包装成一个“自动”按钮,点击后进行上述操作,不点击不进行上述操作,并给出完整代码
最新发布