界面美化专家v1.0.0.3更新日志

import tkinter as tk import ttkbootstrap as ttk from ttkbootstrap.constants import * import math import random import time import sys class CornerMarkModule: def __init__(self, parent, width, height, module_id, label_text): self.parent = parent self.width = width self.height = height self.module_id = module_id self.label_text = label_text self.line_ids = [] self.animation_id = None self.active_flows = [] # 存储当前活动的数据流 # 创建画布 self.canvas = tk.Canvas( parent, width=width, height=height, bg="#121212", highlightthickness=0, bd=0, relief="flat" ) self.canvas.pack(fill=tk.BOTH, expand=True) # 计算中心位置 self.square_size = 80 self.start_x = width // 2 - self.square_size // 2 self.start_y = height // 2 - self.square_size // 2 - 10 # 创建蓝色圆角矩形 self.square = self.create_round_rect( self.start_x, self.start_y, self.start_x + self.square_size, self.start_y + self.square_size, radius=15, fill="#325b74", outline="#5a9bc0", width=2 ) # 添加标签文本 self.label_id = self.canvas.create_text( width // 2, height // 2 - 10, text=label_text, fill="#e0e0e0", font=("Microsoft YaHei", 11, "bold"), width=self.square_size - 10, justify="center" ) # 添加模块编号标签 self.canvas.create_text( width // 2, height - 30, text=f"模块 {module_id}", fill="#7a7a7a", font=("Microsoft YaHei", 9) ) # 添加装饰线条 self.decor_line = self.canvas.create_line( width//2 - 40, height - 25, width//2 + 40, height - 25, fill="#3a6b8c", width=1, dash=(4, 2) ) # 初始参数 self.base_gap = 12 self.base_length = 30 self.create_all_lines() # 动画计数器 self.counter = 0 # 绑定悬停效果 self.canvas.bind("<Enter>", self.on_enter) self.canvas.bind("<Leave>", self.on_leave) def on_enter(self, event): """鼠标悬停效果""" self.canvas.itemconfig(self.square, fill="#5a9bc0") for line_id in self.line_ids: self.canvas.itemconfig(line_id, fill="#5a9bc0") self.canvas.itemconfig(self.decor极, fill="#5a9bc0") def on_leave(self, event): """鼠标离开效果""" self.canvas.itemconfig(self.square, fill="#325b74") for line_id in self.line_ids: self.canvas.itemconfig(line_id, fill="#3a6b8c") self.canvas.itemconfig(self.decor_line, fill="#3a6b8c") def create_round_rect(self, x1, y1, x2, y2, radius=25, **kwargs): """创建圆角矩形""" points = [ x1 + radius, y1, # 左上角 x2 - radius, y1, x2, y1, x2, y1 + radius, # 右上角 x2, y2 - radius, x2, y2, x2 - radius, y2, # 右下角 x1 + radius, y2, x1, y2, x1, y2 - radius, # 左下角 x1, y1 + radius, x1, y1, x1 + radius, y1 ] return self.canvas.create_polygon(points, **kwargs, smooth=True) def create_all_lines(self): line_color = "#3a6b8c" # 左上角标记 self.line_ids.append(self.canvas.create_line( self.start_x - self.base_gap, self.start_y - self.base_gap, self.start_x - self.base_gap + self.base_length, self.start_y - self.base_gap, width=2, fill=line_color )) self.line_ids.append(self.canvas.create_line( self.start_x - self.base_gap, self.start_y - self.base_gap, self.start_x - self.base_gap, self.start_y - self.base_gap + self.base_length, width=2, fill=line_color )) # 右上角标记 self.line_ids.append(self.canvas.create_line( self.start_x + self.square_size + self.base_gap, self.start_y - self.base_gap, self.start_x + self.square_size + self.base_gap - self.base_length, self.start_y - self.base_gap, width=2, fill=line_color )) self.line_ids.append(self.canvas.create_line( self.start_x + self.square_size + self.base_gap, self.start_y - self.base_gap, self.start_x + self.square_size + self.base_gap, self.start_y - self.base_gap + self.base_length, width=2, fill=line_color )) # 左下角标记 self.line_ids.append(self.canvas.create_line( self.start_x - self.base_gap, self.start_y + self.square_size + self.base_gap, self.start_x - self.base_gap + self.base_length, self.start_y + self.square_size + self.base_gap, width=2, fill=line_color )) self.line_ids.append(self.canvas.create_line( self.start_x - self.base_gap, self.start_y + self.square_size + self.base_gap, self.start_x - self.base_gap, self.start_y + self.square_size + self.base_gap - self.base_length, width=2, fill=line_color )) # 右下角标记 self.line_ids.append(self.canvas.create_line( self.start_x + self.square_size + self.base_gap, self.start_y + self.square_size + self.base_gap, self.start_x + self.square_size + self.base_gap - self.base_length, self.start_y + self.square_size + self.base_gap, width=2, fill=line_color )) self.line_ids.append(self.canvas.create_line( self.start_x + self.square_size + self.base_gap, self.start_y + self.square_size + self.base_gap, self.start_x + self.square_size + self.base_gap, self.start_y + self.square_size + self.base_gap - self.base_length, width=2, fill=line_color )) def update_animation(self): """更新角标动画""" phase = self.counter * 0.08 self.counter += 1 phase_offset = self.module_id * 0.5 distance_factor = 0.5 * math.sin(phase + phase_offset) + 0.5 current_gap = 5 + distance_factor * 20 current_length = 25 + distance_factor * 15 self.update_lines(current_gap, current_length) self.animation_id = self.parent.after(50, self.update_animation) def pause_animation(self): """暂停动画""" if self.animation_id: self.parent.after_cancel(self.animation_id) self.animation_id = None def resume_animation(self): """恢复动画""" if not self.animation_id: self.update_animation() def reset_counter(self): """重置计数器""" self.counter = 0 def update_lines(self, gap, length): """更新角标线条位置""" # 左上角 self.canvas.coords(self.line_ids[0], self.start_x - gap, self.start_y - gap, self.start_x - gap + length, self.start_y - gap) self.canvas.coords(self.line_ids[1], self.start_x - gap, self.start_y - gap, self.start_x - gap, self.start_y - gap + length) # 右上角 self.canvas.coords(self.line_ids[2], self.start_x + self.square_size + gap, self.start_y - gap, self.start_x + self.square_size + gap - length, self.start_y - gap) self.canvas.coords(self.line_ids[3], self.start_x + self.square_size + gap, self.start_y - gap, self.start_x + self.square_size + gap, self.start_y - gap + length) # 左下角 self.canvas.coords(self.line_ids[4], self.start_x - gap, self.start_y + self.square_size + gap, self.start_x - gap + length, self.start_y + self.square_size + gap) self.canvas.coords(self.line_ids[5], self.start_x - gap, self.start_y + self.square_size + gap, self.start_x - gap, self.start_y + self.square_size + gap - length) # 右下角 self.canvas.coords(self.line_ids[6], self.start_x + self.square_size + gap, self.start_y + self.square_size + gap, self.start_x + self.square_size + gap - length, self.start_y + self.square_size + gap) self.canvas.coords(self.line_ids[7], self.start_x + self.square_size + gap, self.start_y + self.square_size + gap, self.start_x + self.square_size + gap, self.start_y + self.square_size + gap - length) def get_center(self): """获取模块中心位置""" return (self.width // 2, self.height // 2 - 10) class DataFlowManager: """管理模块间数据流动画的类""" def __init__(self, modules, canvas): self.modules = modules self.canvas = canvas self.flows = [] self.active_flows = [] self.connections = self.define_connections() def define_connections(self): """定义模块之间的连接关系""" return [ # (from_module_id, to_module_id, color) (1, 2, "#ff6b6b"), # 干扰 -> 威胁分析 (2, 4, "#4ecdc4"), # 威胁分析 -> 知识库 (2, 5, "#ffd166"), # 威胁分析 -> 推理引擎 (5, 6, "#1a936f"), # 推理引擎 -> 决策输出 (6, 4, "#9b5de5"), # 决策输出 -> 知识库 (3, 1, "#5a9bc0") # 抗干扰中 -> 干扰 ] def get_module_by_id(self, module_id): """根据ID获取模块实例""" for module in self.modules: if module.module_id == module_id: return module return None def start_flow(self, from_module_id, to_module_id, color): """启动数据流动画""" from_module = self.get_module_by_id(from_module_id) to_module = self.get_module_by_id(to_module_id) if not from_module or not to_module: return from_x, from_y = from_module.get_center() to_x, to_y = to_module.get_center() # 调整位置到模块容器的绝对位置 from_x += from_module.winfo_x() from_y += from_module.winfo_y() to_x += to_module.winfo_x() to_y += to_module.winfo_y() # 创建流动点 flow_id = self.canvas.create_oval(0, 0, 0, 0, fill=color, outline="") flow = { &#39;id&#39;: flow_id, &#39;from&#39;: (from_x, from_y), &#39;to&#39;: (to_x, to_y), &#39;progress&#39;: 0.0, &#39;speed&#39;: random.uniform(0.02, 0.05), &#39;color&#39;: color } self.active_flows.append(flow) def update_flows(self): """更新所有数据流动画""" flows_to_remove = [] for flow in self.active_flows: flow[&#39;progress&#39;] += flow[&#39;speed&#39;] if flow[&#39;progress&#39;] >= 1.0: flows_to_remove.append(flow) continue # 使用贝塞尔曲线使路径更自然 x, y = self.calculate_bezier_point( flow[&#39;from&#39;][0], flow[&#39;from&#39;][1], flow[&#39;to&#39;][0], flow[&#39;to&#39;][1], flow[&#39;progress&#39;] ) # 更新流动点位置 r = 6 self.canvas.coords(flow[&#39;id&#39;], x-r, y-r, x+r, y+r) # 移除已完成动画 for flow in flows_to_remove: self.canvas.delete(flow[&#39;id&#39;]) self.active_flows.remove(flow) # 随机生成新数据流 if random.random() < 0.2 and len(self.active_flows) < 15: conn = random.choice(self.connections) self.start_flow(conn[0], conn[1], conn[2]) # 继续动画 self.canvas.after(50, self.update_flows) def calculate_bezier_point(self, x0, y0, x1, y1, t): """计算贝塞尔曲线上的点""" # 控制点偏移量 ctrl_offset = 0.3 * math.sqrt((x1-x0)**2 + (y1-y0)**2) # 计算控制点 angle = math.atan2(y1 - y0, x1 - x0) ctrl_x1 = x0 + ctrl_offset * math.cos(angle + math.pi/4) ctrl_y1 = y0 + ctrl_offset * math.sin(angle + math.pi/4) ctrl_x2 = x1 - ctrl_offset * math.cos(angle - math.pi/4) ctrl_y2 = y1 - ctrl_offset * math.sin(angle - math.pi/4) # 三次贝塞尔曲线公式 u = 1 - t tt = t * t uu = u * u uuu = uu * u ttt = tt * t x = uuu * x0 + 3 * uu * t * ctrl_x1 + 3 * u * tt * ctrl_x2 + ttt * x1 y = uuu * y0 + 3 * uu * t * ctrl_y1 + 3 * u * tt * ctrl_y2 + ttt * y1 return x, y class SystemStatusBar(ttk.Frame): """系统状态栏组件""" def __init__(self, parent): super().__init__(parent, padding=(10, 5)) self.pack(fill=tk.X, pady=(15, 5)) # 系统状态 self.status_label = ttk.Label( self, text="系统状态: 运行中", font=("Microsoft YaHei", 10), bootstyle="success" ) self.status_label.pack(side=tk.LEFT, padx=10) # 数据流状态 self.flow_label = ttk.Label( self, text="数据流: 正常", font=("Microsoft YaHei", 10), bootstyle="info" ) self.flow_label.pack(side=tk.LEFT, padx=10) # 威胁级别 self.threat_label = ttk.Label( self, text="威胁级别: 低", font=("Microsoft YaHei", 10), bootstyle="warning" ) self.threat_label.pack(side=tk.LEFT, padx=10) # 更新时间 self.time_label = ttk.Label( self, text="", font=("Microsoft YaHei", 10), bootstyle="secondary" ) self.time_label.pack(side=tk.RIGHT, padx=10) self.update_time() def update_time(self): """更新时间显示""" current_time = time.strftime("%Y-%m-%d %H:%M:%S") self.time_label.config(text=f"更新时间: {current_time}") self.after(1000, self.update_time) def create_dynamic_flow_visualization(): """创建动态数据流可视化系统""" # 创建主窗口 root = ttk.Window(title="动态数据流可视化系统", themename="superhero") root.configure(bg="#121212") # 设置窗口大小并居中 window_width = 1400 window_height = 800 screen_width = root.winfo_screenwidth() screen_height = root.winfo_screenheight() center_x = int(screen_width/2 - window_width/2) center_y = int(screen_height/2 - window_height/2) root.geometry(f"{window_width}x{window_height}+{center_x}+{center_y}") # 创建主容器框架 main_container = ttk.Frame(root, padding=(30, 20, 30, 10)) main_container.pack(fill=tk.BOTH, expand=True) # 创建标题栏 header_frame = ttk.Frame(main_container) header_frame.pack(fill=tk.X, pady=(0, 20)) title_label = ttk.Label( header_frame, text="动态数据流可视化系统", font=("Microsoft YaHei", 18, "bold"), bootstyle="inverse-primary" ) title_label.pack(side=tk.LEFT) subtitle = ttk.Label( header_frame, text="实时监控与响应系统", font=("Microsoft YaHei", 12), bootstyle="secondary" ) subtitle.pack(side=tk.LEFT, padx=(15, 0), pady=(5, 0)) # 添加控制按钮 control_frame = ttk.Frame(header_frame) control_frame.pack(side=tk.RIGHT) # 创建网格容器 grid_container = ttk.Frame(main_container) grid_container.pack(fill=tk.BOTH, expand=True) # 配置网格布局 grid_container.grid_rowconfigure(0, weight=1) grid_container.grid_rowconfigure(1, weight=1) grid_container.grid_rowconfigure(2, weight=1) grid_container.grid_columnconfigure(0, weight=1) grid_container.grid_columnconfigure(1, weight=1) grid_container.grid_columnconfigure(2, weight=1) grid_container.grid_columnconfigure(3, weight=1) # 定义模块标签文本 labels = ["干扰", "威胁分析", "抗干扰中", "知识库", "推理引擎", "决策输出"] # 创建模块容器 modules = [] # 抗干扰中 - 第1行第2列 cell0 = ttk.Frame(grid_container) cell0.grid(row=0, column=1, padx=15, pady=15, sticky="nsew") module0 = CornerMarkModule(cell0, 220, 280, 3, labels[2]) modules.append(module0) # 知识库 - 第3行第2列 cell1 = ttk.Frame(grid_container) cell1.grid(row=2, column=1, padx=15, pady=15, sticky="nsew") module1 = CornerMarkModule(cell1, 220, 280, 4, labels[3]) modules.append(module1) # 干扰 - 第2行第1列 cell2 = ttk.Frame(grid_container) cell2.grid(row=1, column=0, padx=15, pady=15, sticky="nsew") module2 = CornerMarkModule(cell2, 220, 280, 1, labels[0]) modules.append(module2) # 威胁分析 - 第2行第2列 cell3 = ttk.Frame(grid_container) cell3.grid(row=1, column=1, padx=15, pady=15, sticky="nsew") module3 = CornerMarkModule(cell3, 220, 280, 2, labels[1]) modules.append(module3) # 推理引擎 - 第2行第3列 cell4 = ttk.Frame(grid_container) cell4.grid(row=1, column=2, padx=15, pady=15, sticky="nsew") module4 = CornerMarkModule(cell4, 220, 280, 5, labels[4]) modules.append(module4) # 决策输出 - 第2行第4列 cell5 = ttk.Frame(grid_container) cell5.grid(row=1, column=3, padx=15, pady=15, sticky="nsew") module5 = CornerMarkModule(cell5, 220, 280, 6, labels[5]) modules.append(module5) # 创建数据流画布 flow_canvas = tk.Canvas( grid_container, bg="#121212", highlightthickness=0, bd=0 ) flow_canvas.place(relx=0, rely=0, relwidth=1, relheight=1) flow_canvas.lower() # 置于底层 # 创建数据流管理器 flow_manager = DataFlowManager(modules, flow_canvas) # 启动所有模块的动画 for module in modules: module.update_animation() # 启动数据流动画 flow_manager.update_flows() # 添加系统状态栏 status_bar = SystemStatusBar(main_container) # 添加控制按钮 exit_button = ttk.Button( control_frame, text="安全退出", command=root.destroy, bootstyle="danger-outline", width=12 ) exit_button.pack(side=tk.RIGHT, padx=5) pause_button = ttk.Button( control_frame, text="暂停动画", command=lambda: toggle_animation(modules, pause_button, flow_manager), bootstyle="warning-outline", width=12 ) pause_button.pack(side=tk.RIGHT, padx=5) # 添加性能优化按钮 optimize_button = ttk.Button( control_frame, text="性能优化", command=lambda: optimize_performance(modules, flow_manager), bootstyle="info-outline", width=12 ) optimize_button.pack(side=tk.RIGHT, padx=5) # 绑定窗口关闭事件 root.protocol("WM_DELETE_WINDOW", lambda: safe_exit(root, modules, flow_manager)) root.mainloop() def safe_exit(root, modules, flow_manager): """安全退出程序""" for module in modules: module.pause_animation() root.destroy() sys.exit() def toggle_animation(modules, button, flow_manager): """切换动画状态""" if button.cget("text") == "暂停动画": for module in modules: module.pause_animation() button.configure(text="继续动画") else: for module in modules: module.resume_animation() button.configure(text="暂停动画") def optimize_performance(modules, flow_manager): """优化系统性能""" # 简化动画效果 for module in modules: module.pause_animation() module.resume_animation() # 减少数据流动画数量 # 实际应用中可添加更多优化逻辑 if __name__ == "__main__": create_dynamic_flow_visualization() 解析一下这段代码
07-09
package demo1; import javax.swing.*; public class Main { public static void main(String[] args) { SwingUtilities.invokeLater(() -> { TemperatureMonitorGUI gui = new TemperatureMonitorGUI(); gui.setVisible(true); }); } } package demo1; import java.util.Arrays; public class ModbusRequestBuilder { public static byte[] buildReadRequest(byte slaveId, int registerAddr) { return new byte[]{ slaveId, 0x03, // 功能码:读保持寄存器 (byte) (registerAddr >> 8), (byte) (registerAddr & 0xFF), 0x00, 0x01 // 读取1个寄存器 }; } public static byte[] buildWriteRequest(byte slaveId, int registerAddr, int value) { return new byte[]{ slaveId, 0x06, // 功能码:写单个寄存器 (byte) (registerAddr >> 8), (byte) (registerAddr & 0xFF), (byte) (value >> 8), (byte) (value & 0xFF) }; } public static byte[] calculateCRC(byte[] data) { int crc = 0xFFFF; for (int pos = 0; pos < data.length; pos++) { crc ^= data[pos] & 0xFF; for (int i = 8; i != 0; i--) { if ((crc & 0x0001) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[]{(byte) (crc & 0xFF), (byte) (crc >> 8)}; } public static String bytesToHex(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (byte b : bytes) { sb.append(String.format("%02X ", b)); } return sb.toString().trim(); } } package demo1; public class RegisterAddress { // 测量值类 public static final int TEMP_MEASURED = 0x0000; public static final int HUMIDITY_MEASURED = 0x0001; public static final int RPM_MEASURED = 0x000D; public static final int CO2_MEASURED = 0x0012; public static final int PRESSURE_MEASURED = 0x0013; public static final int O2_MEASURED = 0x00E8; // 控制设定类 public static final int RUN_CONTROL = 0x0002; public static final int TEMP_SETPOINT = 0x0003; public static final int TIMER_SETPOINT = 0x0006; public static final int HUMIDITY_SETPOINT = 0x0019; public static final int RPM_SETPOINT = 0x0018; public static final int LIGHT_GROUP1 = 0x001A; public static final int LIGHT_GROUP2 = 0x001E; public static final int LIGHT_GROUP3 = 0x001F; public static final int CO2_SETPOINT = 0x001B; public static final int O2_SETPOINT = 0x001C; public static final int PRESSURE_SETPOINT = 0x001D; // 状态与报警类 public static final int ALARM_STATUS = 0x000A; public static final int DEVICE_STATUS = 0x000B; // 连续读取 public static final int CONTINUOUS_READ = 0x00F8; } package demo1; import com.fazecast.jSerialComm.SerialPort; import com.fazecast.jSerialComm.SerialPortDataListener; import com.fazecast.jSerialComm.SerialPortEvent; public class SerialPortDataListenerImpl implements SerialPortDataListener { private final SerialPort serialPort; private final TemperatureMonitorGUI gui; private final TemperatureController controller; public SerialPortDataListenerImpl(SerialPort serialPort, TemperatureMonitorGUI gui, TemperatureController controller) { this.serialPort = serialPort; this.gui = gui; this.controller = controller; } @Override public int getListeningEvents() { return SerialPort.LISTENING_EVENT_DATA_AVAILABLE; } @Override public void serialEvent(SerialPortEvent event) { if (event.getEventType() == SerialPort.LISTENING_EVENT_DATA_AVAILABLE) { byte[] responseData = new byte[serialPort.bytesAvailable()]; int numRead = serialPort.readBytes(responseData, responseData.length); if (numRead > 0) { System.out.println("收到响应: " + ModbusRequestBuilder.bytesToHex(responseData)); if (responseData.length >= 5) { int functionCode = responseData[1] & 0xFF; if (functionCode == 0x03 && responseData.length >= 5) { int value = ((responseData[3] & 0xFF) << 8) | (responseData[4] & 0xFF); double temperature = value / 10.0; controller.updateTemperature(temperature); } } } } } } package demo1; class SerialRequest { public byte[] data; public boolean isManual; public SerialRequest(byte[] data, boolean isManual) { this.data = data; this.isManual = isManual; } } package demo1; import com.fazecast.jSerialComm.SerialPort; import static demo1.ModbusRequestBuilder.buildReadRequest; import static demo1.ModbusRequestBuilder.buildWriteRequest; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class TemperatureController { private final TemperatureMonitorGUI gui; private final String portName; private final int baudRate; private final int dateBite; private final int stopBits; private final int parity; private SerialPort serialPort; private volatile boolean running = false; private final BlockingQueue<SerialRequest> requestQueue = new LinkedBlockingQueue<>(); private ScheduledExecutorService queueExecutor; public final TemperatureProfileManager profileManager; public TemperatureController(TemperatureMonitorGUI gui, String portName, int baudRate, int dateBite, int stopBits, int parity) { this.gui = gui; this.portName = portName; this.baudRate = baudRate; this.dateBite = dateBite; this.stopBits = stopBits; this.parity = parity; this.profileManager = new TemperatureProfileManager(this); } public void start() { new Thread(this::connectSerialPort).start(); } public void stop() { running = false; if (queueExecutor != null) { queueExecutor.shutdownNow(); } if (serialPort != null && serialPort.isOpen()) { serialPort.closePort(); } } private void connectSerialPort() { serialPort = SerialPort.getCommPort(portName); serialPort.setComPortParameters(baudRate, 8, stopBits, parity); serialPort.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0); if (serialPort.openPort()) { System.out.println("✅ 成功打开串口"); gui.setStatus("已连接到 " + portName); serialPort.addDataListener(new SerialPortDataListenerImpl(serialPort, gui, this)); running = true; startQueueConsumer(); startTemperaturePolling(); } else { System.err.println("❌ 无法打开串口"); gui.setStatus("无法打开串口:" + portName); } } private void startQueueConsumer() { queueExecutor = new ScheduledThreadPoolExecutor(1); queueExecutor.submit(() -> { while (!Thread.interrupted() && running) { try { SerialRequest request = requestQueue.take(); sendRequest(request.data); if (request.isManual) { Thread.sleep(500); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } }); } private void startTemperaturePolling() { queueExecutor.scheduleAtFixedRate(() -> { enqueueRequest(buildReadRequest((byte) 0x01, RegisterAddress.TEMP_MEASURED), false); }, 0, 1, TimeUnit.SECONDS); } public void readRegister(byte slaveId, int registerAddr) { enqueueRequest(buildReadRequest(slaveId, registerAddr), true); } public void writeRegister(byte slaveId, int registerAddr, int value) { enqueueRequest(buildWriteRequest(slaveId, registerAddr, value), true); } private void enqueueRequest(byte[] data, boolean isManual) { requestQueue.offer(new SerialRequest(data, isManual)); } public void sendRequest(byte[] data) { if (serialPort == null || !serialPort.isOpen()) { System.err.println("串口未打开,无法发送数据"); return; } byte[] crc = ModbusRequestBuilder.calculateCRC(data); byte[] fullRequest = new byte[data.length + 2]; System.arraycopy(data, 0, fullRequest, 0, data.length); fullRequest[data.length] = crc[0]; // CRC低字节 fullRequest[data.length + 1] = crc[1]; // CRC高字节 serialPort.writeBytes(fullRequest, fullRequest.length); System.out.println("发送请求: " + ModbusRequestBuilder.bytesToHex(fullRequest)); } public void updateTemperature(double temperature) { gui.updateTemperature(temperature); } public void readActualTemperature(byte slaveId) { enqueueRequest(buildReadRequest(slaveId, RegisterAddress.TEMP_MEASURED), true); } public void readSetTemperature(byte slaveId) { enqueueRequest(buildReadRequest(slaveId, RegisterAddress.TEMP_SETPOINT), true); } public void setTargetTemperature(byte slaveId, float temp) { int value = (int)(temp * 10); enqueueRequest(buildWriteRequest(slaveId, RegisterAddress.TEMP_SETPOINT, value), true); } /** * 启动温控逻辑(定值模式) */ public void startControl(float targetTemp, int transitionTime) { if (serialPort == null || !serialPort.isOpen()) return; int registerValue = (int)(targetTemp * 10); byte[] request = buildWriteRequest((byte) 0x01, RegisterAddress.TEMP_SETPOINT, registerValue); sendRequest(request); System.out.println("开始升温,目标温度:" + targetTemp + "°C,过渡时间:" + transitionTime + "秒"); } } package demo1; import com.fazecast.jSerialComm.SerialPort; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; import java.util.Arrays; import java.util.HashMap; import java.util.Map; public class TemperatureMonitorGUI extends JFrame { private JLabel temperatureLabel; private JLabel statusLabel; private JButton connectButton; private JButton readButton; private JButton startControlButton; private JButton refreshPortButton; private JComboBox<String> portComboBox; private JComboBox<String> baudRateComboBox; private JComboBox<String> dateBiteComboBox; private JComboBox<String> stopBitsComboBox; private JComboBox<String> parityComboBox; private JComboBox<String> modeComboBox; private TemperatureController controller; // 输入组件 private JTextField targetTempField; private JTextField transitionTimeField; private Map<String, Component> modeComponents = new HashMap<>(); // 曲线模式字段 private JTextField[] curveTempFields = new JTextField[3]; private JTextField[] curveDurationFields = new JTextField[3]; private JTextField[] curveHoldFields = new JTextField[3]; private JTextField finalTempField; private JTextField finalTransitionField; public TemperatureMonitorGUI() { setTitle("MODBUS RTU 温控系统"); setSize(1100, 700); // 增加整体尺寸 setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); getContentPane().setBackground(new Color(240, 245, 250)); // 设置背景色 // 使用主面板管理布局 JPanel mainPanel = new JPanel(new BorderLayout(15, 15)); mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); add(mainPanel); // 顶部温度显示面板 - 更醒目的设计 JPanel topPanel = createTopPanel(); mainPanel.add(topPanel, BorderLayout.NORTH); // 中央区域使用网格袋布局 JPanel centerPanel = new JPanel(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(8, 8, 8, 8); // 组件间距 gbc.fill = GridBagConstraints.BOTH; // 左侧输入配置面板 - 宽度调整 JPanel inputPanel = createInputPanel(); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 0.3; // 比例调整 gbc.weighty = 1.0; centerPanel.add(inputPanel, gbc); // 中间按钮面板 - 更紧凑 JPanel buttonPanel = createButtonPanel(); gbc.gridx = 1; gbc.weightx = 0.2; // 比例调整 centerPanel.add(buttonPanel, gbc); // 右侧动态面板 - 宽度增加 JPanel rightPanel = createRightPanel(); gbc.gridx = 2; gbc.weightx = 0.4; // 比例调整 centerPanel.add(rightPanel, gbc); mainPanel.add(centerPanel, BorderLayout.CENTER); // 底部状态栏 JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); bottomPanel.setBorder(BorderFactory.createEtchedBorder()); bottomPanel.setBackground(new Color(230, 240, 255)); statusLabel = new JLabel("状态: 未连接"); bottomPanel.add(statusLabel); mainPanel.add(bottomPanel, BorderLayout.SOUTH); // 初始化控制器 controller = null; } private JPanel createTopPanel() { JPanel topPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); topPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 15, 0)); topPanel.setBackground(new Color(70, 130, 180)); temperatureLabel = new JLabel("当前温度: -- °C"); temperatureLabel.setFont(new Font("微软雅黑", Font.BOLD, 28)); temperatureLabel.setForeground(Color.WHITE); topPanel.add(temperatureLabel); return topPanel; } private JPanel createInputPanel() { JPanel inputPanel = new JPanel(new GridBagLayout()); inputPanel.setBorder(BorderFactory.createTitledBorder("通信配置")); inputPanel.setBackground(Color.WHITE); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(5, 5, 5, 5); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; int row = 0; // 串口选择 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("串口:"), gbc); gbc.gridx = 1; JPanel portPanel = new JPanel(new BorderLayout(5, 0)); portComboBox = new JComboBox<>(); portComboBox.setPreferredSize(new Dimension(180, 25)); refreshPortList(); portPanel.add(portComboBox, BorderLayout.CENTER); refreshPortButton = new JButton("刷新"); refreshPortButton.addActionListener(e -> refreshPortList()); portPanel.add(refreshPortButton, BorderLayout.EAST); inputPanel.add(portPanel, gbc); // 波特率 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("波特率:"), gbc); gbc.gridx = 1; baudRateComboBox = new JComboBox<>(new String[]{"1200", "2400", "4800", "9600","19200"}); baudRateComboBox.setSelectedIndex(0); inputPanel.add(baudRateComboBox, gbc); // 数据位 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("数据位"), gbc); gbc.gridx = 1; dateBiteComboBox = new JComboBox<>(new String[]{"6", "7", "8"}); dateBiteComboBox.setSelectedIndex(0); inputPanel.add(dateBiteComboBox, gbc); // 停止位 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("停止位:"), gbc); gbc.gridx = 1; stopBitsComboBox = new JComboBox<>(new String[]{"1", "1.5", "2"}); stopBitsComboBox.setSelectedIndex(0); inputPanel.add(stopBitsComboBox, gbc); // 校验方式 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("校验方式:"), gbc); gbc.gridx = 1; parityComboBox = new JComboBox<>(new String[]{"None", "Even", "Odd"}); parityComboBox.setSelectedIndex(0); inputPanel.add(parityComboBox, gbc); // 分隔线 gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 2; inputPanel.add(new JSeparator(), gbc); // 查询当前温度 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("初始温度:"), gbc); gbc.gridx = 1; JButton queryCurrentTempButton = new JButton("查询"); queryCurrentTempButton.setPreferredSize(new Dimension(80, 25)); queryCurrentTempButton.addActionListener(this::queryCurrentTemperature); inputPanel.add(queryCurrentTempButton, gbc); // 查询设定温度 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("设定温度:"), gbc); gbc.gridx = 1; JButton querySetTempButton = new JButton("查询"); querySetTempButton.setPreferredSize(new Dimension(80, 25)); querySetTempButton.addActionListener(this::querySetTemperature); inputPanel.add(querySetTempButton, gbc); // 分隔线 gbc.gridx = 0; gbc.gridy = row++; gbc.gridwidth = 2; inputPanel.add(new JSeparator(), gbc); // 模式选择 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("模式选择:"), gbc); gbc.gridx = 1; modeComboBox = new JComboBox<>(new String[]{"定值模式", "曲线模式"}); modeComboBox.setSelectedIndex(0); inputPanel.add(modeComboBox, gbc); // 启动控制 gbc.gridx = 0; gbc.gridy = row++; inputPanel.add(new JLabel("启动控制:"), gbc); gbc.gridx = 1; startControlButton = new JButton("启动"); startControlButton.setPreferredSize(new Dimension(100, 30)); startControlButton.setBackground(new Color(50, 150, 50)); startControlButton.setForeground(Color.WHITE); startControlButton.addActionListener(this::startControl); inputPanel.add(startControlButton, gbc); return inputPanel; } private JPanel createButtonPanel() { JPanel buttonPanel = new JPanel(new GridBagLayout()); buttonPanel.setBorder(BorderFactory.createTitledBorder("操作")); buttonPanel.setBackground(Color.WHITE); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(15, 10, 15, 10); gbc.fill = GridBagConstraints.HORIZONTAL; // 连接按钮 connectButton = new JButton("连接串口"); connectButton.setPreferredSize(new Dimension(150, 40)); connectButton.setFont(new Font("微软雅黑", Font.BOLD, 14)); connectButton.setBackground(new Color(70, 130, 180)); connectButton.setForeground(Color.WHITE); connectButton.addActionListener(this::connectSerialPort); gbc.gridy = 0; buttonPanel.add(connectButton, gbc); // 读取温度按钮 readButton = new JButton("读取温度"); readButton.setPreferredSize(new Dimension(150, 40)); readButton.setFont(new Font("微软雅黑", Font.BOLD, 14)); readButton.setBackground(new Color(60, 179, 113)); readButton.setForeground(Color.WHITE); readButton.addActionListener(this::readTemperature); gbc.gridy = 1; buttonPanel.add(readButton, gbc); return buttonPanel; } private JPanel createRightPanel() { JPanel rightPanel = new JPanel(new BorderLayout()); rightPanel.setBorder(BorderFactory.createTitledBorder("模式配置")); rightPanel.setBackground(Color.WHITE); // 定值模式面板 JPanel constantModePanel = createConstantModePanel(); modeComponents.put("定值模式", constantModePanel); // 曲线模式面板 JPanel curveModePanel = createCurveModePanel(); modeComponents.put("曲线模式", curveModePanel); // 默认加载定值模式 rightPanel.add(modeComponents.get("定值模式"), BorderLayout.CENTER); // 模式切换监听器 modeComboBox.addActionListener(e -> { String selectedMode = (String) modeComboBox.getSelectedItem(); rightPanel.removeAll(); rightPanel.add(modeComponents.get(selectedMode), BorderLayout.CENTER); rightPanel.revalidate(); rightPanel.repaint(); }); return rightPanel; } private JPanel createConstantModePanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBackground(Color.WHITE); panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(10, 10, 10, 10); gbc.anchor = GridBagConstraints.WEST; gbc.fill = GridBagConstraints.HORIZONTAL; gbc.gridx = 0; gbc.gridy = 0; panel.add(new JLabel("目标温度 (°C):"), gbc); gbc.gridx = 1; targetTempField = new JTextField(10); targetTempField.setText("25.5"); panel.add(targetTempField, gbc); gbc.gridx = 0; gbc.gridy = 1; panel.add(new JLabel("过渡时间 (秒):"), gbc); gbc.gridx = 1; transitionTimeField = new JTextField(10); transitionTimeField.setText("30"); panel.add(transitionTimeField, gbc); // 添加说明标签 gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 2; JLabel infoLabel = new JLabel("<html><div style=&#39;width:250px;&#39;>定值模式将温度稳定在设定值,过渡时间表示达到目标温度所需时间</div></html>"); infoLabel.setForeground(new Color(100, 100, 100)); panel.add(infoLabel, gbc); return panel; } // 曲线模式面板创建方法 private JPanel createCurveModePanel() { JPanel panel = new JPanel(new GridBagLayout()); panel.setBackground(Color.WHITE); panel.setBorder(BorderFactory.createEmptyBorder(15, 15, 15, 15)); GridBagConstraints gbc = new GridBagConstraints(); gbc.insets = new Insets(10, 10, 10, 10); gbc.fill = GridBagConstraints.HORIZONTAL; // 表头 gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; panel.add(new JLabel("阶段"), gbc); gbc.gridx = 1; panel.add(new JLabel("目标温度 (°C)"), gbc); gbc.gridx = 2; panel.add(new JLabel("过渡时间 (秒)"), gbc); gbc.gridx = 3; panel.add(new JLabel("保持时间 (秒)"), gbc); // 阶段输入区域 for (int i = 0; i < 3; i++) { gbc.gridx = 0; gbc.gridy = i + 1; gbc.anchor = GridBagConstraints.WEST; JLabel stageLabel = new JLabel("阶段 " + (i + 1) + ":"); panel.add(stageLabel, gbc); gbc.gridx = 1; curveTempFields[i] = new JTextField("0.0"); curveTempFields[i].setColumns(8); panel.add(curveTempFields[i], gbc); gbc.gridx = 2; curveDurationFields[i] = new JTextField("30"); curveDurationFields[i].setColumns(8); panel.add(curveDurationFields[i], gbc); gbc.gridx = 3; curveHoldFields[i] = new JTextField("60"); curveHoldFields[i].setColumns(8); panel.add(curveHoldFields[i], gbc); if (i > 0) { stageLabel.setVisible(false); curveTempFields[i].setVisible(false); curveDurationFields[i].setVisible(false); curveHoldFields[i].setVisible(false); } } // 添加/删除按钮 gbc.gridy = 4; gbc.gridx = 0; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.CENTER; JPanel controlButtonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 0)); JButton addStageButton = new JButton("+"); JButton removeStageButton = new JButton("-"); controlButtonPanel.add(addStageButton); controlButtonPanel.add(removeStageButton); panel.add(controlButtonPanel, gbc); // 最终目标 gbc.gridy = 5; gbc.gridx = 0; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.WEST; panel.add(new JLabel("最终目标:"), gbc); gbc.gridx = 1; finalTempField = new JTextField("30.0", 8); panel.add(finalTempField, gbc); gbc.gridx = 2; finalTransitionField = new JTextField("30", 8); panel.add(finalTransitionField, gbc); // 说明文本 gbc.gridy = 6; gbc.gridx = 0; gbc.gridwidth = 4; gbc.anchor = GridBagConstraints.WEST; JLabel infoLabel = new JLabel("<html><div style=&#39;width:350px;&#39;>点击 &#39;+&#39; 增加阶段,&#39;-&#39; 减少阶段。未填写或为 0 的阶段将被跳过</div></html>"); infoLabel.setForeground(new Color(100, 100, 100)); panel.add(infoLabel, gbc); // 当前最大可见阶段数 final int[] maxVisibleStages = {1}; // 添加 "+" 按钮事件 addStageButton.addActionListener(e -> { if (maxVisibleStages[0] < 3) { maxVisibleStages[0]++; for (int i = 0; i < 3; i++) { boolean visible = i < maxVisibleStages[0]; curveTempFields[i].setVisible(visible); curveDurationFields[i].setVisible(visible); curveHoldFields[i].setVisible(visible); if (i < 3) { ((JLabel) ((JPanel) curveTempFields[i].getParent()).getComponent(0)).setVisible(visible); } } panel.revalidate(); panel.repaint(); } }); // 添加 "-" 按钮事件 removeStageButton.addActionListener(e -> { if (maxVisibleStages[0] > 1) { maxVisibleStages[0]--; for (int i = 0; i < 3; i++) { boolean visible = i < maxVisibleStages[0]; curveTempFields[i].setVisible(visible); curveDurationFields[i].setVisible(visible); curveHoldFields[i].setVisible(visible); if (i < 3) { ((JLabel) ((JPanel) curveTempFields[i].getParent()).getComponent(0)).setVisible(visible); } } panel.revalidate(); panel.repaint(); } }); return panel; } // =========== 以下为功能方法 =========== private void connectSerialPort(ActionEvent e) { String selectedPort = (String) portComboBox.getSelectedItem(); if (selectedPort != null && controller == null) { int baudRate = Integer.parseInt((String) baudRateComboBox.getSelectedItem()); int dateBite = Integer.parseInt((String) dateBiteComboBox.getSelectedItem()); int stopBits = getStopBitsValue(); int parity = getParityValue(); controller = new TemperatureController(this, selectedPort, baudRate, dateBite,stopBits, parity); controller.start(); connectButton.setText("断开连接"); connectButton.setBackground(new Color(246, 115, 115)); // 红色表示断开状态 } else if (controller != null) { // 断开连接逻辑 controller.stop(); controller = null; setStatus("已断开连接"); connectButton.setText("连接串口"); connectButton.setBackground(new Color(70, 130, 180)); // 蓝色表示连接状态 } } private int getStopBitsValue() { String stopBits = (String) stopBitsComboBox.getSelectedItem(); switch (stopBits) { case "1": return SerialPort.ONE_STOP_BIT; case "1.5": return SerialPort.ONE_POINT_FIVE_STOP_BITS; case "2": return SerialPort.TWO_STOP_BITS; default: return SerialPort.ONE_STOP_BIT; } } private int getParityValue() { String parity = (String) parityComboBox.getSelectedItem(); switch (parity) { case "None": return SerialPort.NO_PARITY; case "Even": return SerialPort.EVEN_PARITY; case "Odd": return SerialPort.ODD_PARITY; default: return SerialPort.NO_PARITY; } } private void readTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readActualTemperature(slaveId); } catch (Exception ex) { showError("读取温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void queryCurrentTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readActualTemperature(slaveId); } catch (Exception ex) { showError("查询当前温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void querySetTemperature(ActionEvent e) { if (controller != null) { try { byte slaveId = 0x01; controller.readSetTemperature(slaveId); } catch (Exception ex) { showError("查询设定温度时发生错误: " + ex.getMessage()); } } else { showError("请先连接串口"); } } private void startControl() { String selectedMode = (String) modeComboBox.getSelectedItem(); if ("恒温模式".equals(selectedMode)) { try { float targetTemp = Float.parseFloat(constantTempField.getText()); controller.setConstantTemperature(targetTemp); showInfo("恒温模式启动: " + targetTemp + "°C"); } catch (NumberFormatException ex) { showError("请输入有效的温度值"); } } else if ("曲线模式".equals(selectedMode)) { float[] temps = new float[4]; int[] durations = new int[4]; int[] holdTimes = new int[3]; int stageCount = 0; for (int i = 0; i < 3; i++) { try { float temp = Float.parseFloat(curveTempFields[i].getText()); int duration = Integer.parseInt(curveDurationFields[i].getText()); int holdTime = Integer.parseInt(curveHoldFields[i].getText()); // ✅ 支持目标温度设为 0°C if (temp >= 0 && duration > 0 && holdTime >= 0) { temps[stageCount] = temp; durations[stageCount] = duration; holdTimes[stageCount] = holdTime; stageCount++; } } catch (NumberFormatException ignored) {} } // 设置最终阶段 try { temps[stageCount] = Float.parseFloat(finalTempField.getText()); durations[stageCount] = Integer.parseInt(finalTransitionField.getText()); } catch (NumberFormatException ex) { showError("请正确输入最终目标温度和过渡时间"); return; } if (stageCount == 0) { showError("至少需要一个有效阶段"); return; } controller.profileManager.startCurveControl( Arrays.copyOf(temps, stageCount + 1), Arrays.copyOf(durations, stageCount + 1), Arrays.copyOf(holdTimes, stageCount) ); showInfo("曲线模式启动: " + stageCount + "个阶段 + 最终目标"); } } public void refreshPortList() { portComboBox.removeAllItems(); for (SerialPort port : SerialPort.getCommPorts()) { portComboBox.addItem(port.getSystemPortName()); } } public void updateTemperature(double temperature) { SwingUtilities.invokeLater(() -> { temperatureLabel.setText(String.format("当前温度: %.1f°C", temperature)); // 根据温度变化改变颜色 if (temperature > 35) { temperatureLabel.setForeground(new Color(220, 20, 60)); // 高温红色 } else if (temperature < 10) { temperatureLabel.setForeground(new Color(30, 144, 255)); // 低温蓝色 } else { temperatureLabel.setForeground(Color.WHITE); // 正常温度白色 } }); } public void setStatus(String status) { SwingUtilities.invokeLater(() -> statusLabel.setText("状态: " + status) ); } private void showError(String message) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, message, "错误", JOptionPane.ERROR_MESSAGE) ); } private void showInfo(String message) { SwingUtilities.invokeLater(() -> JOptionPane.showMessageDialog(this, message, "信息", JOptionPane.INFORMATION_MESSAGE) ); } public static void main(String[] args) { SwingUtilities.invokeLater(() -> { TemperatureMonitorGUI gui = new TemperatureMonitorGUI(); gui.setLocationRelativeTo(null); // 居中显示 gui.setVisible(true); }); } } package demo1; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; public class TemperatureProfileManager { private final TemperatureController controller; private final AtomicBoolean running = new AtomicBoolean(false); private ExecutorService executor; public TemperatureProfileManager(TemperatureController controller) { this.controller = controller; } /** * 启动多段温度曲线控制 * * @param temps 温度数组(单位:°C) * @param durations 每段升温时间(单位:秒) * @param holdTimes 每段保持时间(单位:秒) */ public void startCurveControl(float[] temps, int[] durations, int[] holdTimes) { if (temps.length != durations.length || temps.length != holdTimes.length + 1) { throw new IllegalArgumentException("参数数组长度不匹配"); } if (running.getAndSet(true)) { System.out.println("已有任务正在运行,无法重复启动"); return; } executor = Executors.newSingleThreadExecutor(); executor.submit(() -> { try { for (int i = 0; i < temps.length - 1 && running.get(); i++) { float targetTemp = temps[i]; int duration = durations[i]; int holdTime = holdTimes[i]; System.out.printf("阶段 %d: 设置温度 %.1f°C,升温时间 %d 秒%n", i + 1, targetTemp, duration); controller.setTargetTemperature((byte)0x01, targetTemp); // 等待升温完成 long startTime = System.currentTimeMillis(); while (running.get() && System.currentTimeMillis() - startTime < duration * 1000L) { Thread.sleep(500); } if (!running.get()) break; System.out.printf("阶段 %d: 已达到目标温度 %.1f°C,保持 %d 秒%n", i + 1, targetTemp, holdTime); // 保持阶段 startTime = System.currentTimeMillis(); while (running.get() && System.currentTimeMillis() - startTime < holdTime * 1000L) { Thread.sleep(500); } } // 最终阶段 if (running.get()) { float finalTemp = temps[temps.length - 1]; int finalDuration = durations[durations.length - 1]; System.out.printf("最终阶段: 设置温度 %.1f°C,升温时间 %d 秒%n", finalTemp, finalDuration); controller.setTargetTemperature((byte)0x01, finalTemp); long startTime = System.currentTimeMillis(); while (running.get() && System.currentTimeMillis() - startTime < finalDuration * 1000L) { Thread.sleep(500); } } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { running.set(false); System.out.println("温度曲线控制已完成或已停止"); } }); } /** * 停止当前正在进行的温度曲线控制 */ public void stop() { running.set(false); if (executor != null) { executor.shutdownNow(); } System.out.println("温度曲线控制已停止"); } } 帮我根据GUI界面,完善一下其他类
07-18
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link href="style/css/common.css" rel="stylesheet"> <title>硬件运维系统 - 报告管理</title> <link href="style/css/report.css" rel="stylesheet"> </head> <body> <div id="header-container"></div> <div class="container"> <h2 class="page-title">报告管理</h2> <div class="action-bar"> <div class="search-box"> <input type="text" id="search-report" placeholder="搜索报告名称/类型"> <button class="btn" id="search-btn">搜索</button> </div> <div> <button class="btn" id="sort-by-date">按日期排序</button> <button class="btn btn-download" id="download-all">下载全部报告</button> <button class="btn" id="add-report">新增报告</button> </div> </div> <!-- 新增:添加滚动容器 --> <div class="report-container"> <div id="report-list" class="report-list"> <!-- 报告卡片将动态生成 --> </div> </div> </div> <!-- 报告编辑模态框 --> <div id="report-modal" class="modal"> <div class="modal-content"> <div class="modal-header"> <h2 id="modal-title">新增报告</h2> <span class="close">×</span> </div> <form id="report-form"> <input type="hidden" id="report-id"> <div class="form-group"> <label for="report-name">报告名称</label> <input type="text" id="report-name" required> </div> <div class="form-group"> <label for="report-type">报告类型</label> <select id="report-type" required> <option value="硬件故障处理报告">硬件故障处理报告</option> <option value="软件运维报告">软件运维报告</option> <option value="设备健康度报告">设备健康度报告</option> <option value="产线运行分析报告">产线运行分析报告</option> <option value="硬件日志采集报告">硬件选择 - 获取指定时间段的日志</option> </select> </div> <!-- 日志时间段选择区 --> <div class="form-group log-time-range"> <label for="log-start-date">日志开始日期</label> <input type="date" id="log-start-date" required> <label for="log-end-date" style="margin-top: 10px;">日志结束日期</label> <input type="date" id="log-end-date" required> </div> <div class="form-group"> <label for="report-date">报告生成日期</label> <input type="date" id="report-date" required> </div> <div class="form-group"> <label for="report-content">报告内容</label> <div class="template-desc" id="content-desc">请根据报告类型填写内容...</div> <textarea id="report-content" required></textarea> </div> <div class="modal-footer"> <button type="button" class="btn" id="cancel-btn">取消</button> <button type="submit" class="btn">保存</button> </div> </form> </div> </div> <!-- 引入本地 jQuery --> <script src="style/js/jquery.min.js"></script> <script> // 检查登录状态 if (localStorage.getItem(&#39;isLogin&#39;) !== &#39;1&#39;) { window.location.href = &#39;login.html&#39;; } // 初始化报告数据和硬件日志数据 let reports = []; let hardwareLogs = []; // DOM 元素缓存 const $reportList = $(&#39;#report-list&#39;); const $reportModal = $(&#39;#report-modal&#39;); const $modalTitle = $(&#39;#modal-title&#39;); const $reportForm = $(&#39;#report-form&#39;); const $reportId = $(&#39;#report-id&#39;); const $reportName = $(&#39;#report-name&#39;); const $reportType = $(&#39;#report-type&#39;); const $reportDate = $(&#39;#report-date&#39;); const $reportContent = $(&#39;#report-content&#39;); const $addReportBtn = $(&#39;#add-report&#39;); const $cancelBtn = $(&#39;#cancel-btn&#39;); const $closeBtn = $(&#39;.close&#39;); const $searchBtn = $(&#39;#search-btn&#39;); const $searchInput = $(&#39;#search-report&#39;); const $sortByDateBtn = $(&#39;#sort-by-date&#39;); const $downloadAllBtn = $(&#39;#download-all&#39;); const $logTimeRange = $(&#39;.log-time-range&#39;); const $contentDesc = $(&#39;#content-desc&#39;); // 报告类型模板映射 const typeTemplates = { &#39;硬件故障处理报告&#39;: `1. 故障现象: 2. 故障排查过程: 3. 处理方案: 4. 处理结果: 5. 预防建议:`, &#39;软件运维报告&#39;: `1. 运维内容: 2. 操作过程: 3. 运行状态: 4. 遗留问题: 5. 后续计划:`, &#39;设备健康度报告&#39;: `1. 设备状态: 2. 检查项目: 3. 异常项: 4. 维护建议: 5. 预测分析:`, &#39;产线运行分析报告&#39;: `1. 生产数据: 2. 设备表现: 3. 瓶颈分析: 4. 改进措施: 5. 提升建议:`, &#39;硬件日志采集报告&#39;: `### 日志采集说明 - 采集时间段:{startDate} 至 {endDate} - 涉及设备:自动从日志中提取 - 异常日志数量:{errorCount} ### 关键日志摘要 {logSummary} ` }; // 从API加载报告数据 function loadReports() { $.ajax({ url: &#39;api/reports.php&#39;, method: &#39;GET&#39;, dataType: &#39;json&#39;, success: function (data) { reports = data || []; renderReports(reports); }, error: function (xhr, status, error) { console.error(&#39;加载报告失败:&#39;, error); $reportList.html(&#39;<p style="text-align:center;padding:20px;">加载报告数据失败,请稍后重试</p>&#39;); } }); } // 加载硬件日志数据 function loadHardwareLogs() { $.getJSON(&#39;data/hardware_logs.json&#39;, (data) => { hardwareLogs = data.logs || []; console.log(&#39;硬件日志加载成功&#39;); }).fail((xhr, status, error) => { console.error(&#39;加载硬件日志失败:&#39;, error); hardwareLogs = []; }); } // 渲染报告列表 function renderReports(reportData) { $reportList.empty(); // 解绑之前的事件处理器,避免重复绑定 $reportList.off(&#39;click&#39;, &#39;.edit-btn&#39;); $reportList.off(&#39;click&#39;, &#39;.delete-btn&#39;); $reportList.off(&#39;click&#39;, &#39;.download-btn&#39;); if (reportData.length === 0) { $reportList.html(&#39;<p style="text-align:center;padding:20px;">暂无报告数据,请点击"新增报告"创建</p>&#39;); return; } reportData.forEach(report => { const $card = $(` <div class="report-card" data-id="${report.id}"> <div class="report-info"> <h3>${report.name}</h3> <div class="report-meta"> <span>${report.type} | </span> <span>生成日期:${report.date}</span> </div> </div> <div class="report-action"> <button class="action-btn download-btn">下载</button> <button class="action-btn edit-btn">编辑</button> <button class="action-btn delete-btn">删除</button> </div> </div> `); $reportList.append($card); }); // 绑定事件 $reportList.on(&#39;click&#39;, &#39;.edit-btn&#39;, function () { const reportId = $(this).closest(&#39;.report-card&#39;).data(&#39;id&#39;); editReport(reportId); }); $reportList.on(&#39;click&#39;, &#39;.delete-btn&#39;, function () { const reportId = $(this).closest(&#39;.report-card&#39;).data(&#39;id&#39;); deleteReport(reportId); }); $reportList.on(&#39;click&#39;, &#39;.download-btn&#39;, function () { const reportId = $(this).closest(&#39;.report-card&#39;).data(&#39;id&#39;); downloadReport(reportId); }); } // 打开模态框 function openModal(isEdit = false, report = null) { $reportForm[0].reset(); $logTimeRange.hide(); $reportContent.val(&#39;&#39;); if (isEdit && report) { $modalTitle.text(&#39;编辑报告&#39;); $reportId.val(report.id); $reportName.val(report.name); $reportType.val(report.type); $reportDate.val(report.date); $reportContent.val(report.content); if (report.type === &#39;硬件日志采集报告&#39;) { $logTimeRange.show(); $(&#39;#log-start-date&#39;).val(report.logStartDate || &#39;&#39;); $(&#39;#log-end-date&#39;).val(report.logEndDate || &#39;&#39;); } } else { $modalTitle.text(&#39;新增报告&#39;); $reportId.val(&#39;&#39;); $reportDate.val(new Date().toISOString().split(&#39;T&#39;)[0]); } $reportModal.show(); } // 关闭模态框 function closeModal() { $reportModal.hide(); } // 编辑报告 function editReport(id) { console.log(&#39;编辑报告:&#39;, reports); console.log(&#39;报告ID:&#39;, id); const stringId = String(id); const report = reports.find(r => r.id === stringId); console.log(&#39;编辑报告:&#39;, report); // console.log(r.id); if (report) { openModal(true, report); } else { alert(&#39;未找到该报告&#39;); loadReports(); } } // 删除报告 function deleteReport(id) { const stringId = String(id); const report = reports.find(r => r.id === stringId); if (!report) { alert(&#39;未找到该报告&#39;); loadReports(); return; } if (confirm(`确定要删除"${report.name}"这份报告吗?删除后无法恢复!`)) { $.ajax({ url: &#39;api/reports.php&#39;, method: &#39;DELETE&#39;, contentType: &#39;application/json&#39;, data: JSON.stringify({ id: id }), dataType: &#39;json&#39;, success: function (response) { if (response.success) { loadReports(); // 重新加载数据 } else { alert(&#39;删除失败:&#39; + (response.message || &#39;未知错误&#39;)); } }, error: function (xhr, status, error) { console.error(&#39;删除报告失败:&#39;, error); alert(&#39;删除报告失败,请检查网络连接&#39;); } }); } } // 下载单个报告 function downloadReport(id) { const report = reports.find(r => r.id === id); if (!report) return; let content = ` 报告名称:${report.name} 报告类型:${report.type} 生成日期:${report.date} 报告内容: ${report.content} `.trim(); if (report.type === &#39;硬件日志采集报告&#39; && report.attachments && report.attachments.length) { content += `\n\n=== 日志附件 ===\n`; report.attachments.forEach(attach => { content += `\n【附件:${attach.filename}】\n${attach.content}\n`; }); } const blob = new Blob([content], { type: &#39;text/plain&#39; }); const url = URL.createObjectURL(blob); const a = $(&#39;<a>&#39;).attr({ href: url, download: `${report.name.replace(/[\/:*?"<>|]/g, &#39;-&#39;)}.txt` }); $(&#39;body&#39;).append(a); a[0].click(); a.remove(); URL.revokeObjectURL(url); } // 下载全部报告 function downloadAllReports() { if (reports.length === 0) { alert(&#39;没有报告可下载&#39;); return; } let allContent = &#39;=== 所有报告汇总 ===\n\n&#39;; reports.forEach((report, idx) => { allContent += `【报告 ${idx + 1}】\n`; allContent += `名称:${report.name}\n类型:${report.type}\n日期:${report.date}\n\n`; allContent += `内容:\n${report.content}\n\n`; if (report.type === &#39;硬件日志采集报告&#39; && report.attachments && report.attachments.length) { allContent += `=== 日志附件 ===\n`; report.attachments.forEach(attach => { allContent += `\n【附件:${attach.filename}】\n${attach.content}\n`; }); } allContent += &#39;========================================\n\n&#39;; }); const blob = new Blob([allContent], { type: &#39;text/plain&#39; }); const url = URL.createObjectURL(blob); const a = $(&#39;<a>&#39;).attr({ href: url, download: `所有报告汇总_${new Date().toISOString().split(&#39;T&#39;)[0]}.txt` }); $(&#39;body&#39;).append(a); a[0].click(); a.remove(); URL.revokeObjectURL(url); } // 保存报告 $reportForm.on(&#39;submit&#39;, function (e) { e.preventDefault(); const formData = { id: $reportId.val(), name: $reportName.val(), type: $reportType.val(), date: $reportDate.val(), content: $reportContent.val(), attachments: [] }; if (formData.type === &#39;硬件日志采集报告&#39;) { const startDate = $(&#39;#log-start-date&#39;).val(); const endDate = $(&#39;#log-end-date&#39;).val(); formData.logStartDate = startDate; formData.logEndDate = endDate; const filteredLogs = hardwareLogs.filter(log => { const logTime = new Date(log.timestamp); const start = new Date(startDate); const end = new Date(endDate); return logTime >= start && logTime <= end; }); if (filteredLogs.length > 0) { const errorCount = filteredLogs.filter(log => log.level === &#39;ERROR&#39;).length; const logSummary = filteredLogs.map(log => `[${log.timestamp}] [${log.device}] [${log.level}]:${log.content}` ).join(&#39;\n&#39;); formData.content = typeTemplates[&#39;硬件日志采集报告&#39;] .replace(&#39;{startDate}&#39;, startDate) .replace(&#39;{endDate}&#39;, endDate) .replace(&#39;{errorCount}&#39;, errorCount) .replace(&#39;{logSummary}&#39;, logSummary); formData.attachments.push({ filename: `hardware_logs_${startDate}_${endDate}.txt`, content: filteredLogs.map(log => `${log.timestamp}\n${log.device}\n${log.level}\n${log.content}\n-----\n` ).join(&#39;&#39;) }); } else { formData.content = &#39;未找到指定时间段的日志数据&#39;; } } const method = formData.id ? &#39;PUT&#39; : &#39;POST&#39;; $.ajax({ url: &#39;api/reports.php&#39;, method: method, contentType: &#39;application/json&#39;, data: JSON.stringify(formData), dataType: &#39;json&#39;, success: function (response) { if (response.success) { loadReports(); closeModal(); } else { alert(&#39;保存失败:&#39; + (response.message || &#39;未知错误&#39;)); } }, error: function (xhr, status, error) { console.error(&#39;保存报告失败:&#39;, error); alert(&#39;保存报告失败,请检查网络连接&#39;); } }); }); // 搜索报告 function handleSearch() { const keyword = $searchInput.val().toLowerCase(); const filtered = reports.filter(report => report.name.toLowerCase().includes(keyword) || report.type.toLowerCase().includes(keyword) ); renderReports(filtered); } // 按日期排序 function sortReportsByDate() { const sorted = [...reports].sort((a, b) => new Date(b.date) - new Date(a.date) ); renderReports(sorted); } // 事件绑定 function bindEvents() { // 报告类型切换 // / 修改报告类型切换事件 $reportType.on(&#39;change&#39;, function () { const type = $(this).val(); const $startDate = $(&#39;#log-start-date&#39;); const $endDate = $(&#39;#log-end-date&#39;); $logTimeRange.hide(); $contentDesc.text(&#39;请根据报告类型填写内容...&#39;); if (type === &#39;硬件日志采集报告&#39;) { $logTimeRange.show(); $contentDesc.text(&#39;选择时间段后将自动生成日志摘要&#39;); $reportContent.val(&#39;&#39;); // 仅在日志类型下添加必填属性 $startDate.attr(&#39;required&#39;, true); $endDate.attr(&#39;required&#39;, true); } else { $reportContent.val(typeTemplates[type] || &#39;&#39;); // 其他类型下移除必填属性 $startDate.removeAttr(&#39;required&#39;); $endDate.removeAttr(&#39;required&#39;); } }); // 新增报告 $addReportBtn.on(&#39;click&#39;, () => openModal()); // 关闭模态框 $closeBtn.on(&#39;click&#39;, closeModal); $cancelBtn.on(&#39;click&#39;, closeModal); // 点击模态框外部关闭 $(window).on(&#39;click&#39;, (e) => { if ($(e.target).is($reportModal)) closeModal(); }); // 搜索 $searchBtn.on(&#39;click&#39;, handleSearch); $searchInput.on(&#39;keypress&#39;, (e) => { if (e.key === &#39;Enter&#39;) handleSearch(); }); // 排序 $sortByDateBtn.on(&#39;click&#39;, sortReportsByDate); // 下载全部 $downloadAllBtn.on(&#39;click&#39;, downloadAllReports); } // 初始化 function init() { bindEvents(); loadHardwareLogs(); loadReports(); } // 页面加载完成后初始化 $(document).ready(init); </script> </body> </html> 优化代码.让拟态窗更加好看
07-24
光伏储能虚拟同步发电机VSG并网仿真模型(Similink仿真实现)内容概要:本文档介绍了光伏储能虚拟同步发电机(VSG)并网仿真模型的Simulink实现方法,重点在于通过建立光伏储能系统与虚拟同步发电机相结合的仿真模型,模拟其在并网过程中的动态响应与控制特性。该模型借鉴了同步发电机的惯性和阻尼特性,提升了新能源并网系统的频率和电压支撑能力,增强了系统的稳定性与可控性。文档还提及相关电力系统仿真技术的应用,包括逆变器控制、储能配置、并网稳定性分析等,并提供了完整的Simulink仿真文件及技术支持资源链接,便于科研人员复现与二次开发。; 适合人群:电气工程、自动化、能源系统等相关专业的研究生、科研人员及从事新能源并网技术开发的工程师。; 使用场景及目标:①用于研究光伏储能系统在弱电网条件下的并网稳定性问题;②掌握虚拟同步发电机(VSG)控制策略的设计与仿真方法;③支持高水平论文(如EI/SCI)的模型复现与创新研究;④为微电网、智能电网中的分布式能源接入提供技术参考。; 阅读建议:建议结合提供的Simulink模型文件与文档说明逐步操作,重点关注VSG控制模块的参数设置与动态响应分析,同时可延伸学习文中提及的MPPT、储能管理、谐波分析等相关技术,以提升综合仿真能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值