From 352da282b6c21700eb454407b92cabcf169a448e Mon Sep 17 00:00:00 2001
From: 826220679@qq.com <826220679@qq.com>
Date: 星期二, 26 八月 2025 17:21:27 +0800
Subject: [PATCH] 首次提交AOA自动跟随

---
 .classpath                                       |   11 
 bin/home/Mains.class                             |    0 
 bin/home/VisualizationPanel$1.class              |    0 
 bin/home/FirmwareUpgrader.class                  |    0 
 src/home/SingleInstanceLock.java                 |   77 +
 systemfile/logfile/openlog.txt                   |  382 ++++++++
 src/home/FirmwareUpgrader.java                   |  216 ++++
 bin/home/VisualizationPanel.class                |    0 
 bin/home/Dell55AA01Parser.class                  |    0 
 src/home/LogUtil.java                            |   87 +
 src/home/VisualizationPanel.java                 |  205 ++++
 src/home/AOAFollowSystem.java                    |  775 +++++++++++++++++
 bin/home/ConfigPanel.class                       |    0 
 bin/home/AOAFollowSystem$HomePanel$2.class       |    0 
 bin/home/AOAFollowSystem.class                   |    0 
 bin/home/Dell55AA1FParser$ParseResult.class      |    0 
 src/home/Dell55AA1FParser.java                   |  184 ++++
 bin/home/Dell55AA01Parser$ParseResult.class      |    0 
 bin/home/Dell55AA1FParser.class                  |    0 
 bin/home/ConfigPanel$1.class                     |    0 
 bin/home/AOAFollowSystem$HomePanel.class         |    0 
 bin/home/AOAFollowSystem$1.class                 |    0 
 lib/jSerialComm-2.10.4.jar                       |    0 
 src/home/Dell55AA01Parser.java                   |  111 ++
 src/home/HexUtils.java                           |   40 
 .project                                         |   17 
 bin/home/LogUtil.class                           |    0 
 bin/home/FirmwareUpgrader$ProgressCallback.class |    0 
 bin/home/AOAFollowSystem$HomePanel$1.class       |    0 
 systemfile/Messages_en.properties                |   51 +
 src/home/Mains.java                              |   44 
 systemfile/Messages_zh.properties                |   51 +
 src/home/SerialPortService.java                  |  171 +++
 src/home/ConfigPanel.java                        |  251 +++++
 bin/home/SerialPortService.class                 |    0 
 bin/home/SingleInstanceLock.class                |    0 
 bin/home/HexUtils.class                          |    0 
 37 files changed, 2,673 insertions(+), 0 deletions(-)

diff --git a/.classpath b/.classpath
new file mode 100644
index 0000000..f7fc6b9
--- /dev/null
+++ b/.classpath
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
+		<attributes>
+			<attribute name="module" value="true"/>
+		</attributes>
+	</classpathentry>
+	<classpathentry kind="lib" path="D:/eclipseworkspace/GIT/AOAFlow/lib/jSerialComm-2.10.4.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/.project b/.project
new file mode 100644
index 0000000..12c187d
--- /dev/null
+++ b/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>AOAFlow</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/bin/home/AOAFollowSystem$1.class b/bin/home/AOAFollowSystem$1.class
new file mode 100644
index 0000000..a4024a2
--- /dev/null
+++ b/bin/home/AOAFollowSystem$1.class
Binary files differ
diff --git a/bin/home/AOAFollowSystem$HomePanel$1.class b/bin/home/AOAFollowSystem$HomePanel$1.class
new file mode 100644
index 0000000..d46b37e
--- /dev/null
+++ b/bin/home/AOAFollowSystem$HomePanel$1.class
Binary files differ
diff --git a/bin/home/AOAFollowSystem$HomePanel$2.class b/bin/home/AOAFollowSystem$HomePanel$2.class
new file mode 100644
index 0000000..8ca4a8f
--- /dev/null
+++ b/bin/home/AOAFollowSystem$HomePanel$2.class
Binary files differ
diff --git a/bin/home/AOAFollowSystem$HomePanel.class b/bin/home/AOAFollowSystem$HomePanel.class
new file mode 100644
index 0000000..7c2825e
--- /dev/null
+++ b/bin/home/AOAFollowSystem$HomePanel.class
Binary files differ
diff --git a/bin/home/AOAFollowSystem.class b/bin/home/AOAFollowSystem.class
new file mode 100644
index 0000000..86ebcc8
--- /dev/null
+++ b/bin/home/AOAFollowSystem.class
Binary files differ
diff --git a/bin/home/ConfigPanel$1.class b/bin/home/ConfigPanel$1.class
new file mode 100644
index 0000000..d07c2d3
--- /dev/null
+++ b/bin/home/ConfigPanel$1.class
Binary files differ
diff --git a/bin/home/ConfigPanel.class b/bin/home/ConfigPanel.class
new file mode 100644
index 0000000..b11a0f7
--- /dev/null
+++ b/bin/home/ConfigPanel.class
Binary files differ
diff --git a/bin/home/Dell55AA01Parser$ParseResult.class b/bin/home/Dell55AA01Parser$ParseResult.class
new file mode 100644
index 0000000..db29366
--- /dev/null
+++ b/bin/home/Dell55AA01Parser$ParseResult.class
Binary files differ
diff --git a/bin/home/Dell55AA01Parser.class b/bin/home/Dell55AA01Parser.class
new file mode 100644
index 0000000..2549b15
--- /dev/null
+++ b/bin/home/Dell55AA01Parser.class
Binary files differ
diff --git a/bin/home/Dell55AA1FParser$ParseResult.class b/bin/home/Dell55AA1FParser$ParseResult.class
new file mode 100644
index 0000000..5179218
--- /dev/null
+++ b/bin/home/Dell55AA1FParser$ParseResult.class
Binary files differ
diff --git a/bin/home/Dell55AA1FParser.class b/bin/home/Dell55AA1FParser.class
new file mode 100644
index 0000000..82d8494
--- /dev/null
+++ b/bin/home/Dell55AA1FParser.class
Binary files differ
diff --git a/bin/home/FirmwareUpgrader$ProgressCallback.class b/bin/home/FirmwareUpgrader$ProgressCallback.class
new file mode 100644
index 0000000..f7d0ce8
--- /dev/null
+++ b/bin/home/FirmwareUpgrader$ProgressCallback.class
Binary files differ
diff --git a/bin/home/FirmwareUpgrader.class b/bin/home/FirmwareUpgrader.class
new file mode 100644
index 0000000..351e115
--- /dev/null
+++ b/bin/home/FirmwareUpgrader.class
Binary files differ
diff --git a/bin/home/HexUtils.class b/bin/home/HexUtils.class
new file mode 100644
index 0000000..b382534
--- /dev/null
+++ b/bin/home/HexUtils.class
Binary files differ
diff --git a/bin/home/LogUtil.class b/bin/home/LogUtil.class
new file mode 100644
index 0000000..c5e73ad
--- /dev/null
+++ b/bin/home/LogUtil.class
Binary files differ
diff --git a/bin/home/Mains.class b/bin/home/Mains.class
new file mode 100644
index 0000000..4b597f2
--- /dev/null
+++ b/bin/home/Mains.class
Binary files differ
diff --git a/bin/home/SerialPortService.class b/bin/home/SerialPortService.class
new file mode 100644
index 0000000..7f8e9ea
--- /dev/null
+++ b/bin/home/SerialPortService.class
Binary files differ
diff --git a/bin/home/SingleInstanceLock.class b/bin/home/SingleInstanceLock.class
new file mode 100644
index 0000000..7dd3e7d
--- /dev/null
+++ b/bin/home/SingleInstanceLock.class
Binary files differ
diff --git a/bin/home/VisualizationPanel$1.class b/bin/home/VisualizationPanel$1.class
new file mode 100644
index 0000000..0fbade4
--- /dev/null
+++ b/bin/home/VisualizationPanel$1.class
Binary files differ
diff --git a/bin/home/VisualizationPanel.class b/bin/home/VisualizationPanel.class
new file mode 100644
index 0000000..b288737
--- /dev/null
+++ b/bin/home/VisualizationPanel.class
Binary files differ
diff --git a/lib/jSerialComm-2.10.4.jar b/lib/jSerialComm-2.10.4.jar
new file mode 100644
index 0000000..2a29395
--- /dev/null
+++ b/lib/jSerialComm-2.10.4.jar
Binary files differ
diff --git a/src/home/AOAFollowSystem.java b/src/home/AOAFollowSystem.java
new file mode 100644
index 0000000..6b1c318
--- /dev/null
+++ b/src/home/AOAFollowSystem.java
@@ -0,0 +1,775 @@
+package home;
+
+import javax.swing.*;
+import javax.swing.border.Border;
+import javax.swing.border.TitledBorder;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import java.awt.*;
+import java.awt.event.*;
+import java.util.*;
+import java.io.InputStream;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.io.File;
+import java.io.FileInputStream;
+import java.util.PropertyResourceBundle;
+import com.fazecast.jSerialComm.SerialPort;
+import home.Dell55AA01Parser.ParseResult;
+
+public class AOAFollowSystem extends JFrame {
+	private static final long serialVersionUID = 1L;
+	private CardLayout cardLayout;
+	private JPanel mainPanel;
+	private HomePanel homePanel;
+	private ConfigPanel configPanel;
+	private ResourceBundle messages;
+	private Locale currentLocale;
+	private JButton homeBtn;
+	private JButton configBtn;
+	private JComboBox<String> languageCombo;
+	private JLabel languageLabel;
+	private SerialPortService serialService;
+	
+
+	public AOAFollowSystem() {
+		this.currentLocale = Locale.SIMPLIFIED_CHINESE;
+		this.messages = loadResourceBundle(currentLocale);
+		this.serialService = new SerialPortService();
+		initializeUI();
+	}
+
+	private ResourceBundle loadResourceBundle(Locale locale) {
+		String fileName = locale.equals(Locale.ENGLISH) ? 
+				"Messages_en.properties" : "Messages_zh.properties";
+
+		File langFile = new File("systemfile/" + fileName);
+
+		if (!langFile.exists()) {
+			System.err.println("榛樿璧勬簮鏂囦欢鏈壘鍒�: " + langFile.getAbsolutePath());
+			return ResourceBundle.getBundle("systemfile.Messages");
+		}
+
+		try (InputStream inputStream = new FileInputStream(langFile)) {
+			return new PropertyResourceBundle(inputStream);
+		} catch (IOException e) {
+			System.err.println("鏃犳硶鍔犺浇璧勬簮鏂囦欢: " + e.getMessage());
+			return ResourceBundle.getBundle("systemfile.Messages");
+		}
+	}
+
+	public String getString(String key) {
+		if (messages != null && messages.containsKey(key)) {
+			return messages.getString(key);
+		}
+		// 榛樿鏂囨湰锛堜腑鏂囷級
+		switch (key) {
+		case "home": return "棣栭〉";
+		case "config": return "閰嶇疆";
+		case "device_id": return "璁惧缂栧彿";
+		case "group": return "閫氫俊灏忕粍";
+		case "frequency": return "閫氫俊棰戠巼";
+		case "read_config": return "璇诲彇閰嶇疆";
+		case "save_config": return "淇濆瓨閰嶇疆";
+		case "serial_port": return "涓插彛";
+		case "baud_rate": return "娉㈢壒鐜�";
+		case "open_serial": return "鎵撳紑涓插彛";
+		case "close_serial": return "鍏抽棴涓插彛";
+		case "start": return "寮�濮�";
+		case "pause": return "鏆傚仠";
+		case "clear": return "娓呯┖";
+		case "send": return "鍙戦��";
+		case "device_id_table": return "璁惧缂栧彿";
+		case "distance_table": return "瀹炴椂璺濈";
+		case "angle_table": return "瀹炴椂瑙掑害";
+		case "signal_table": return "淇″彿璐ㄩ噺";
+		case "power_table": return "鐢甸噺";
+		case "button_table": return "鎸夐挳";
+		case "time_table": return "鏃堕棿";
+		case "LANGUAGE": return "璇█";
+		case "log": return "鏃ュ織";
+		case "send_data": return "鍙戦�佹暟鎹�";
+		case "select_serial_port": return "璇烽�夋嫨涓插彛";
+		case "error": return "閿欒";
+		case "open_serial_first": return "璇峰厛鎵撳紑涓插彛";
+		case "input_data_to_send": return "璇疯緭鍏ヨ鍙戦�佺殑鏁版嵁";
+		case "send_failed": return "鍙戦�佸け璐�";
+		case "receive": return "鎺ユ敹";
+		case "visualization": return "鍙鍖�";
+		case "config_read_success": return "閰嶇疆璇诲彇鎴愬姛";
+		case "info": return "淇℃伅";
+		case "input_valid_number": return "璇疯緭鍏ユ湁鏁堢殑鏁板瓧";
+		case "config_save_success": return "閰嶇疆淇濆瓨鎴愬姛";
+		case "select_file": return "閫夋嫨鏂囦欢";
+		case "upgrade": return "鍗囩骇";
+		case "upgrade_progress": return "鍗囩骇杩涘害";
+		case "select_bin_file": return "璇烽�夋嫨bin鏂囦欢";
+		case "upgrade_success": return "鍗囩骇鎴愬姛";
+		case "upgrade_failed": return "鍗囩骇澶辫触";
+		case "hex": return "HEX";
+		case "ascii": return "ASCII";
+		case "hex_send": return "HEX鍙戦��";
+		case "display_format": return "鏄剧ず鏍煎紡";
+		default: return key;
+		}
+	}
+
+	private void initializeUI() {
+		setTitle("HXZK_AOA");
+		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+		setSize(1000, 700);
+		setLocationRelativeTo(null);
+
+		// 鍒涘缓椤堕儴瀵艰埅
+		JPanel navPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+		homeBtn = createNavButton(getString("home"), new Color(70, 130, 180));
+		configBtn = createNavButton(getString("config"), new Color(70, 130, 180));
+
+		languageCombo = new JComboBox<>(new String[]{"涓枃", "English"});
+		languageCombo.setSelectedIndex(0);
+		languageCombo.addActionListener(e -> {
+			Locale newLocale = languageCombo.getSelectedIndex() == 0 ? 
+					Locale.SIMPLIFIED_CHINESE : Locale.ENGLISH;
+			switchLanguage(newLocale);
+		});
+
+		navPanel.add(homeBtn);
+		navPanel.add(configBtn);
+		navPanel.add(Box.createHorizontalStrut(20));
+
+		languageLabel = new JLabel(getString("LANGUAGE") + ":");
+		navPanel.add(languageLabel);
+		navPanel.add(languageCombo);
+
+		// 鍒涘缓涓婚潰鏉匡紙鍗$墖甯冨眬锛�
+		cardLayout = new CardLayout();
+		mainPanel = new JPanel(cardLayout);
+		homePanel = new HomePanel(serialService, this);
+		configPanel = new ConfigPanel(serialService, this);
+
+		mainPanel.add(homePanel, "home");
+		mainPanel.add(configPanel, "config");
+
+		// 娣诲姞瀵艰埅鎸夐挳浜嬩欢
+		homeBtn.addActionListener(e -> cardLayout.show(mainPanel, "home"));
+		configBtn.addActionListener(e -> cardLayout.show(mainPanel, "config"));
+
+		// 璁剧疆甯冨眬
+		setLayout(new BorderLayout());
+		add(navPanel, BorderLayout.NORTH);
+		add(mainPanel, BorderLayout.CENTER);
+
+		// 璁剧疆榛樿鏄剧ず棣栭〉
+		cardLayout.show(mainPanel, "home");
+	}
+
+	private void switchLanguage(Locale newLocale) {
+		this.currentLocale = newLocale;
+		this.messages = loadResourceBundle(currentLocale);
+
+		// 鏇存柊鐣岄潰鏂囨湰
+		updateUILanguage();
+
+		// 鏇存柊璇█閫夋嫨妗�
+		languageCombo.setSelectedIndex(newLocale.equals(Locale.SIMPLIFIED_CHINESE) ? 0 : 1);
+
+		revalidate();
+		repaint();
+	}
+
+	private void updateUILanguage() {
+		// 鏇存柊瀵艰埅鎸夐挳鏂囨湰
+		homeBtn.setText(getString("home"));
+		configBtn.setText(getString("config"));
+
+		// 鏇存柊璇█鏍囩
+		languageLabel.setText(getString("LANGUAGE") + ":");
+
+		// 鏇存柊鍚勯潰鏉挎枃鏈�
+		homePanel.updateLanguage();
+		configPanel.updateLanguage();
+	}
+
+	private JButton createNavButton(String text, Color color) {
+		JButton button = new JButton(text);
+		button.setBackground(color);
+		button.setForeground(Color.WHITE);
+		button.setFocusPainted(false);
+		button.setBorder(BorderFactory.createEmptyBorder(5, 15, 5, 15));
+
+		button.addMouseListener(new MouseAdapter() {
+			@Override
+			public void mouseEntered(MouseEvent e) {
+				button.setBackground(color.darker());
+			}
+
+			@Override
+			public void mouseExited(MouseEvent e) {
+				button.setBackground(color);
+			}
+		});
+
+		return button;
+	}
+
+	// 棣栭〉闈㈡澘
+	class HomePanel extends JPanel {
+		/**
+		 * 
+		 */
+		private static final long serialVersionUID = 1L;
+		private VisualizationPanel visualizationPanel;
+		private JTable dataTable;
+		private JTextArea logArea;
+		private JComboBox<String> serialPortCombo;
+		private JComboBox<String> baudRateCombo;
+		private JButton openSerialBtn;
+		private JButton startPauseBtn;
+		private JButton clearBtn;
+		private JButton sendBtn;
+		private JTextField sendField;
+		private JLabel displayFormatLabel; // 鏂板锛氭樉绀烘牸寮忔爣绛�
+		private JLabel serialPortLabel;    // 鏂板锛氫覆鍙f爣绛�
+		private JLabel baudRateLabel;      // 鏂板锛氭尝鐗圭巼鏍囩
+
+		private SerialPortService serialService;
+		private AOAFollowSystem parentFrame;
+		private boolean serialOpened = false;
+		private boolean isRunning = false;
+
+		// 鏂板锛氭樉绀烘牸寮忓崟閫夋寜閽�
+		private JRadioButton hexRadio;
+		private JRadioButton asciiRadio;
+		private JCheckBox hexSendCheckBox;
+
+		// 琛ㄦ牸妯″瀷 - 鍙湁1琛�
+		private Object[][] tableData = new Object[1][7];
+		private String[] columnNames = {
+				getString("device_id_table"), 
+				getString("distance_table"), 
+				getString("angle_table"), 
+				getString("signal_table"), 
+				getString("power_table"), 
+				getString("button_table"), 
+				getString("time_table")
+		};
+		
+		// 浼樺寲锛氶噸鐢ㄥ璞″噺灏戝唴瀛樺垎閰�
+		private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
+		private StringBuilder hexBuilder = new StringBuilder();
+		private StringBuilder displayBuilder = new StringBuilder();
+
+		public HomePanel(SerialPortService serialService, AOAFollowSystem parentFrame) {
+			this.serialService = serialService;
+			this.parentFrame = parentFrame;
+			setLayout(new BorderLayout(10, 10));
+			setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
+
+			// 鍒涘缓宸︿晶闈㈡澘锛堝彲瑙嗗寲鍖哄煙鍜岃〃鏍硷級
+			JPanel leftPanel = new JPanel(new BorderLayout(10, 10));
+
+			// 鍙鍖栭潰鏉�
+			visualizationPanel = new VisualizationPanel(parentFrame);
+			leftPanel.add(visualizationPanel, BorderLayout.CENTER);
+
+			// 鏁版嵁琛ㄦ牸
+			JPanel tablePanel = new JPanel(new BorderLayout());
+			tablePanel.setBorder(BorderFactory.createTitledBorder(getString("device_id_table")));
+			dataTable = new JTable(tableData, columnNames);
+			JScrollPane tableScroll = new JScrollPane(dataTable);
+			tableScroll.setPreferredSize(new Dimension(0, 80));
+			tablePanel.add(tableScroll, BorderLayout.CENTER);
+			leftPanel.add(tablePanel, BorderLayout.SOUTH);
+
+			// 鍒涘缓鍙充晶闈㈡澘锛堟棩蹇楀拰鎺у埗锛�
+			JPanel rightPanel = new JPanel(new BorderLayout(10, 10));
+
+			// 鏃ュ織鍖哄煙
+			JPanel logPanel = new JPanel(new BorderLayout());
+			logPanel.setBorder(BorderFactory.createTitledBorder(getString("log")));
+			logArea = new JTextArea();
+			logArea.setEditable(false);
+			JScrollPane logScroll = new JScrollPane(logArea);
+			logScroll.setPreferredSize(new Dimension(400, 0));
+			logPanel.add(logScroll, BorderLayout.CENTER);
+
+			// 鏂板锛氭樉绀烘牸寮忛�夋嫨闈㈡澘
+			JPanel formatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
+			formatPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
+			displayFormatLabel = new JLabel(getString("display_format") + ":"); // 浣跨敤getString鑾峰彇鏂囨湰
+			formatPanel.add(displayFormatLabel);
+
+			ButtonGroup formatGroup = new ButtonGroup();
+			hexRadio = new JRadioButton(getString("hex"), true); // 榛樿閫変腑HEX
+			asciiRadio = new JRadioButton(getString("ascii"));
+
+			formatGroup.add(hexRadio);
+			formatGroup.add(asciiRadio);
+
+			formatPanel.add(hexRadio);
+			formatPanel.add(asciiRadio);
+
+			// 灏嗘牸寮忛�夋嫨闈㈡澘娣诲姞鍒版棩蹇楅潰鏉跨殑搴曢儴
+			logPanel.add(formatPanel, BorderLayout.SOUTH);
+
+			rightPanel.add(logPanel, BorderLayout.CENTER);
+
+			// 鎺у埗闈㈡澘
+			JPanel controlPanel = new JPanel(new GridBagLayout());
+			controlPanel.setBorder(BorderFactory.createTitledBorder(getString("serial_port")));
+
+			GridBagConstraints gbc = new GridBagConstraints();
+			gbc.insets = new Insets(5, 2, 5, 5);
+			gbc.fill = GridBagConstraints.HORIZONTAL;
+
+			// 涓插彛閫夋嫨
+			gbc.gridx = 0;
+			gbc.gridy = 0;
+			gbc.weightx = 0;
+			serialPortLabel = new JLabel(getString("serial_port") + ":"); // 浣跨敤getString鑾峰彇鏂囨湰
+			controlPanel.add(serialPortLabel, gbc);
+
+			gbc.gridx = 1;
+			gbc.weightx = 1;
+			serialPortCombo = new JComboBox<>();
+			// 娣诲姞寮瑰嚭鑿滃崟鐩戝惉鍣紝鐐瑰嚮鏃跺埛鏂颁覆鍙e垪琛�
+			serialPortCombo.addPopupMenuListener(new PopupMenuListener() {
+				@Override
+				public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+					refreshSerialPorts();
+				}
+
+				@Override
+				public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}
+
+				@Override
+				public void popupMenuCanceled(PopupMenuEvent e) {}
+			});
+			refreshSerialPorts();
+			controlPanel.add(serialPortCombo, gbc);
+
+			// 娉㈢壒鐜囬�夋嫨
+			gbc.gridx = 0;
+			gbc.gridy = 1;
+			gbc.weightx = 0;
+			baudRateLabel = new JLabel(getString("baud_rate") + ":"); // 浣跨敤getString鑾峰彇鏂囨湰
+			controlPanel.add(baudRateLabel, gbc);
+
+			gbc.gridx = 1;
+			gbc.weightx = 1;
+			baudRateCombo = new JComboBox<>(new String[]{"115200", "921600","9600", "19200", "38400", "57600"});
+			baudRateCombo.setSelectedIndex(0);
+			controlPanel.add(baudRateCombo, gbc);
+
+			// 涓插彛鎺у埗鎸夐挳
+			gbc.gridx = 0;
+			gbc.gridy = 2;
+			gbc.gridwidth = 1;  // 淇敼锛氬彧鍗犱竴鍒�
+			gbc.weightx = 0.5;  // 淇敼锛氳缃潈閲�
+			openSerialBtn = createColoredButton(getString("open_serial"), new Color(70, 130, 180));
+			openSerialBtn.setPreferredSize(new Dimension(100, 30));  // 淇敼锛氳缃笌寮�濮嬫寜閽浉鍚屽昂瀵�
+			openSerialBtn.addActionListener(e -> toggleSerialPort());
+			controlPanel.add(openSerialBtn, gbc);
+			openSerialBtn.setBackground(Color.GRAY);
+
+			// 鍚姩/鏆傚仠鎸夐挳锛堝悎骞朵负涓�涓級
+			gbc.gridx = 1;  // 淇敼锛氭斁鍦ㄧ浜屽垪
+			gbc.gridy = 2;
+			JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 10, 0));
+			startPauseBtn = createColoredButton(getString("start"), new Color(50, 205, 50));
+			startPauseBtn.setPreferredSize(new Dimension(100, 30));
+			startPauseBtn.addActionListener(e -> toggleCapture());
+			buttonPanel.add(startPauseBtn);
+
+			clearBtn = createColoredButton(getString("clear"), new Color(192, 192, 192));
+			clearBtn.setPreferredSize(new Dimension(100, 30));
+			clearBtn.addActionListener(e -> clearLog());
+			buttonPanel.add(clearBtn);
+			controlPanel.add(buttonPanel, gbc);
+
+			rightPanel.add(controlPanel, BorderLayout.SOUTH);
+
+			// 鍙戦�佹暟鎹潰鏉� - 绉诲埌琛ㄦ牸涓嬫柟
+			JPanel sendDataPanel = new JPanel(new BorderLayout(5, 5));
+			sendDataPanel.setBorder(BorderFactory.createTitledBorder(getString("send_data")));
+
+			JPanel sendControlPanel = new JPanel(new BorderLayout(5, 0));
+			sendField = new JTextField();
+			sendBtn = createColoredButton(getString("send"), new Color(70, 130, 180));
+			sendBtn.setPreferredSize(new Dimension(100, 30));
+			sendBtn.addActionListener(e -> sendData());
+
+			// 鏂板锛欻EX鍙戦�佸閫夋
+			hexSendCheckBox = new JCheckBox(getString("hex_send"), true); // 榛樿鍕鹃��
+			sendControlPanel.add(hexSendCheckBox, BorderLayout.WEST);
+			sendControlPanel.add(sendBtn, BorderLayout.EAST);
+
+			sendDataPanel.add(sendField, BorderLayout.CENTER);
+			sendDataPanel.add(sendControlPanel, BorderLayout.EAST);
+
+			// 娣诲姞鍙戦�佹暟鎹潰鏉垮埌宸︿晶闈㈡澘鐨勬渶搴曢儴
+			leftPanel.add(sendDataPanel, BorderLayout.SOUTH);
+
+			// 娣诲姞宸﹀彸闈㈡澘
+			add(leftPanel, BorderLayout.CENTER);
+			add(rightPanel, BorderLayout.EAST);
+		}
+
+		private JButton createColoredButton(String text, Color color) {
+			JButton button = new JButton(text);
+			button.setBackground(color);
+			button.setForeground(Color.WHITE);
+			button.setFocusPainted(false);
+			button.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
+
+			button.addMouseListener(new MouseAdapter() {
+				@Override
+				public void mouseEntered(MouseEvent e) {
+					button.setBackground(color.darker());
+				}
+
+				@Override
+				public void mouseExited(MouseEvent e) {
+					button.setBackground(color);
+				}
+			});
+
+			return button;
+		}
+
+		private void refreshSerialPorts() {
+			String selectedPort = (String) serialPortCombo.getSelectedItem();
+			serialPortCombo.removeAllItems();
+			SerialPort[] ports = SerialPort.getCommPorts();
+			for (SerialPort port : ports) {
+				serialPortCombo.addItem(port.getSystemPortName());
+			}
+
+			// 灏濊瘯鎭㈠涔嬪墠鐨勯�夋嫨
+			if (selectedPort != null) {
+				serialPortCombo.setSelectedItem(selectedPort);
+			} else if (ports.length > 0) {
+				serialPortCombo.setSelectedIndex(0);
+			}
+		}
+
+		private void toggleSerialPort() {
+			if (serialOpened) {
+				closeSerialPort();
+				openSerialBtn.setText(getString("open_serial"));
+				openSerialBtn.setBackground(Color.GRAY); // 鍏抽棴鏃剁伆鑹�
+				serialOpened = false;
+
+				// 鍏抽棴涓插彛鏃跺仠姝㈡崟鑾�
+				if (isRunning) {
+					toggleCapture();
+				}
+			} else {
+				if (openSerialPort()) {
+					openSerialBtn.setText(getString("close_serial"));
+					openSerialBtn.setBackground(Color.GREEN);
+					serialOpened = true;
+				}
+			}
+		}
+
+		private boolean openSerialPort() {
+			String portName = (String) serialPortCombo.getSelectedItem();
+			if (portName == null) {
+				JOptionPane.showMessageDialog(this, getString("select_serial_port"), getString("error"), JOptionPane.ERROR_MESSAGE);
+				return false;
+			}
+
+			int baudRate = Integer.parseInt((String) baudRateCombo.getSelectedItem());
+			return serialService.open(portName, baudRate);
+		}
+
+		private void closeSerialPort() {
+			serialService.close();
+		}
+
+		private void toggleCapture() {
+			if (!serialOpened) {
+				JOptionPane.showMessageDialog(this, getString("open_serial_first"), getString("error"), JOptionPane.ERROR_MESSAGE);
+				return;
+			}
+
+			isRunning = !isRunning;
+			serialService.setPaused(!isRunning);
+
+			if (isRunning) {
+				startPauseBtn.setText(getString("pause"));
+				startPauseBtn.setBackground(new Color(255, 165, 0));
+
+				// 鍚姩鏁版嵁鎹曡幏
+				serialService.startCapture(this::processReceivedData);
+			} else {
+				startPauseBtn.setText(getString("start"));
+				startPauseBtn.setBackground(new Color(50, 205, 50));
+			}
+		}
+
+		private void clearLog() {
+			logArea.setText("");
+		}
+
+		private void sendData() {
+			if (!serialOpened) {
+				JOptionPane.showMessageDialog(this, getString("open_serial_first"), getString("error"), JOptionPane.ERROR_MESSAGE);
+				return;
+			}
+
+			String data = sendField.getText();
+			if (data.isEmpty()) {
+				JOptionPane.showMessageDialog(this, getString("input_data_to_send"), getString("error"), JOptionPane.ERROR_MESSAGE);
+				return;
+			}
+
+			byte[] sendBytes;
+			if (hexSendCheckBox.isSelected()) {
+				// HEX鍙戦�佹ā寮�
+				try {
+					sendBytes = hexStringToByteArray(data.replaceAll("\\s+", ""));
+					if (sendBytes == null) {
+						JOptionPane.showMessageDialog(this, "鏃犳晥鐨凥EX鏍煎紡", getString("error"), JOptionPane.ERROR_MESSAGE);
+						return;
+					}
+				} catch (Exception e) {
+					JOptionPane.showMessageDialog(this, "HEX杞崲閿欒: " + e.getMessage(), getString("error"), JOptionPane.ERROR_MESSAGE);
+					return;
+				}
+			} else {
+				// ASCII鍙戦�佹ā寮�
+				sendBytes = data.getBytes();
+			}
+
+			boolean success = serialService.send(sendBytes);
+			if (success) {
+				appendLog(getString("send") + ": " + (hexSendCheckBox.isSelected() ? bytesToHex(sendBytes) : data));
+			} else {
+				appendLog(getString("send_failed") + ": " + data);
+			}
+			sendField.setText("");
+		}
+
+		private void processReceivedData(byte[] data) {
+			// 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄
+			hexBuilder.setLength(0);
+			for (byte b : data) {
+				hexBuilder.append(String.format("%02X", b));
+			}
+			String displayText = hexBuilder.toString();
+			
+			String displayText1 = new String(data).replaceAll("\\s+", "");
+			
+			if (displayText.startsWith("55AA1F")) {
+				Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0);
+				if (result != null) {
+					updateTable(result);
+					visualizationPanel.updatePosition(result.distance, result.angle);
+					visualizationPanel.setTagId(result.tagId);
+				}
+			}else if(displayText.startsWith("55AA01")) {            	
+				ParseResult result=Dell55AA01Parser.parse(displayText, "127.0.0.1", 0);
+				visualizationPanel.updatePosition(result.distance,270);				
+				visualizationPanel.setTagId(result.tagId);
+				
+			}
+
+			if (hexRadio.isSelected()) {				
+				appendLog(displayText);
+			} else {
+				if (displayText.startsWith("55AA1F")) {
+					Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0);								
+					// 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄
+					displayBuilder.setLength(0);
+					displayBuilder.append("1F:")
+					.append(result.dataLength)
+					.append(",")
+					.append(result.messageType)
+					.append(",id:")
+					.append(result.tagId)
+					.append(",Dis:")
+					.append(result.distance)
+					.append("cm,Angle:")
+					.append(result.angle)
+					.append("掳,Signal:")
+					.append(result.signalQuality)
+					.append(",Button:")
+					.append(result.buttonPressed)
+					.append(",Power:")
+					.append(result.power);
+					appendLog(displayBuilder.toString());
+				}else if(displayText.startsWith("55AA01")) {            	
+					ParseResult result=Dell55AA01Parser.parse(displayText, "127.0.0.1", 0);
+					// 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄
+					displayBuilder.setLength(0);
+					displayBuilder.append("55AA01 Seq:")
+					.append(result.sequenceNum)
+					.append(",Tagid:")
+					.append(result.tagId)
+					.append(",Anchorid:")
+					.append(result.anchorId)
+					.append(",Distance:")
+					.append(result.distance)
+					.append(",Power:")
+					.append(result.power)
+					.append(",Button:")
+					.append(result.buttonPressed);
+					appendLog(displayBuilder.toString());
+				}else {					
+					appendLog(displayText1);
+				}
+			}
+		}
+
+		private void appendLog(String message) {
+			SwingUtilities.invokeLater(() -> {
+				// 浼樺寲锛氶檺鍒舵棩蹇楅暱搴︼紝闃叉鍐呭瓨鏃犻檺澧為暱
+				if (logArea.getLineCount() > 1000) {
+					try {
+						int end = logArea.getDocument().getLength();
+						int start = logArea.getDocument().getText(0, end).indexOf("\n") + 1;
+						logArea.getDocument().remove(0, start);
+					} catch (Exception e) {
+						logArea.setText(""); // 濡傛灉鍑洪敊鍒欐竻绌烘棩蹇�
+					}
+				}
+				
+				String time = sdf.format(new Date());
+				logArea.append("[" + time + "] " + message + "\n");
+
+				// 鑷姩婊氬姩鍒版渶鍚�
+				logArea.setCaretPosition(logArea.getDocument().getLength());
+			});
+		}
+
+		private void updateTable(Dell55AA1FParser.ParseResult result) {
+			SwingUtilities.invokeLater(() -> {
+				// 鍙湁1琛屾暟鎹紝鐩存帴鏇存柊
+				String time = sdf.format(new Date());
+
+				tableData[0] = new Object[]{
+						result.tagId,
+						result.distance + "cm",
+						result.angle + "掳",
+						result.signalQuality,
+						result.power + "%",
+						result.buttonPressed,
+						time
+				};
+
+				// 鍒锋柊琛ㄦ牸
+				dataTable.repaint();
+			});
+		}
+
+		// 杈呭姪鏂规硶锛氬皢瀛楄妭鏁扮粍杞崲涓篐EX瀛楃涓�
+		private String bytesToHex(byte[] bytes) {
+			// 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄
+			hexBuilder.setLength(0);
+			for (byte b : bytes) {
+				hexBuilder.append(String.format("%02X ", b));
+			}
+			return hexBuilder.toString().trim();
+		}
+
+		// 杈呭姪鏂规硶锛氬皢HEX瀛楃涓茶浆鎹负瀛楄妭鏁扮粍
+		private byte[] hexStringToByteArray(String s) {
+			int len = s.length();
+			if (len % 2 != 0) {
+				return null;
+			}
+
+			byte[] data = new byte[len / 2];
+			for (int i = 0; i < len; i += 2) {
+				try {
+					data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+							+ Character.digit(s.charAt(i+1), 16));
+				} catch (Exception e) {
+					return null;
+				}
+			}
+			return data;
+		}
+
+		public void updateLanguage() {
+			// 鏇存柊鐣岄潰鏂囨湰
+			openSerialBtn.setText(serialOpened ? getString("close_serial") : getString("open_serial"));
+			startPauseBtn.setText(isRunning ? getString("pause") : getString("start"));
+			clearBtn.setText(getString("clear"));
+			sendBtn.setText(getString("send"));
+
+			// 鏇存柊鏍囩鏂囨湰
+			displayFormatLabel.setText(getString("display_format") + ":");
+			serialPortLabel.setText(getString("serial_port") + ":");
+			baudRateLabel.setText(getString("baud_rate") + ":");
+
+			// 鏇存柊鍒楀悕
+			columnNames[0] = getString("device_id_table");
+			columnNames[1] = getString("distance_table");
+			columnNames[2] = getString("angle_table");
+			columnNames[3] = getString("signal_table");
+			columnNames[4] = getString("power_table");
+			columnNames[5] = getString("button_table");
+			columnNames[6] = getString("time_table");
+
+			// 鏇存柊鏄剧ず鏍煎紡鍗曢�夋寜閽�
+			hexRadio.setText(getString("hex"));
+			asciiRadio.setText(getString("ascii"));
+			hexSendCheckBox.setText(getString("hex_send"));
+
+			// 鏇存柊杈规鏍囬
+			updateBorderTitles(this);
+
+			dataTable.repaint();
+		}
+
+		private void updateBorderTitles(Container container) {
+			for (Component comp : container.getComponents()) {
+				if (comp instanceof JPanel) {
+					JPanel panel = (JPanel) comp;
+					Border border = panel.getBorder();
+					if (border instanceof TitledBorder) {
+						TitledBorder titledBorder = (TitledBorder) border;
+						String title = titledBorder.getTitle();
+
+						if (title.equals("鏃ュ織") || title.equals("Log")) {
+							titledBorder.setTitle(getString("log"));
+						} else if (title.equals(getString("device_id_table")) || title.equals("Device ID")) {
+							titledBorder.setTitle(getString("device_id_table"));
+						} else if (title.equals(getString("serial_port")) || title.equals("Serial Port")) {
+							titledBorder.setTitle(getString("serial_port"));
+						} else if (title.equals("鍙戦�佹暟鎹�") || title.equals("Send Data")) {
+							titledBorder.setTitle(getString("send_data"));
+						} else if (title.equals("鍙鍖�") || title.equals("Visualization")) {
+							titledBorder.setTitle(getString("visualization"));
+						}
+						panel.repaint();
+					}
+
+					// 閫掑綊鏇存柊瀛愮粍浠�
+					updateBorderTitles(panel);
+				}
+			}
+		}
+
+		private String getString(String key) {
+			if (parentFrame != null) {
+				return parentFrame.getString(key);
+			} else {
+				// 杩斿洖榛樿鏂囨湰锛堜腑鏂囷級鎴� key 鏈韩浣滀负澶囩敤
+				switch (key) {
+				case "home": return "棣栭〉";
+				case "config": return "閰嶇疆";
+				case "display_format": return "鏄剧ず鏍煎紡";
+				case "serial_port": return "涓插彛";
+				case "baud_rate": return "娉㈢壒鐜�";
+				case "close_serial": return "鍏抽棴涓插彛";
+				// 鍏朵粬 key 鐨勯粯璁ゅ��...
+				default: return key;
+				}
+			}
+		}
+	}
+}
\ No newline at end of file
diff --git a/src/home/ConfigPanel.java b/src/home/ConfigPanel.java
new file mode 100644
index 0000000..086ff4f
--- /dev/null
+++ b/src/home/ConfigPanel.java
@@ -0,0 +1,251 @@
+package home;
+
+import javax.swing.*;
+import javax.swing.Timer;
+import java.awt.*;
+import java.awt.event.*;
+import java.io.File;
+
+public class ConfigPanel extends JPanel {
+    private JTextField deviceIdField;
+    private JTextField groupField;
+    private JTextField frequencyField;
+    private JButton readConfigBtn;
+    private JButton saveConfigBtn;
+    private JTextField filePathField;
+    private JButton selectFileBtn;
+    private JButton upgradeBtn;
+    private JProgressBar progressBar;
+    private SerialPortService serialService;
+    private FirmwareUpgrader firmwareUpgrader;
+    private AOAFollowSystem parentFrame;
+    
+    // 用于界面文本的标签
+    private JLabel deviceIdLabel;
+    private JLabel groupLabel;
+    private JLabel frequencyLabel;
+    private JLabel selectFileLabel;
+    
+    public ConfigPanel(SerialPortService serialService, AOAFollowSystem parentFrame) {
+        this.serialService = serialService;
+        this.parentFrame = parentFrame;
+        this.firmwareUpgrader = new FirmwareUpgrader(serialService);
+        setLayout(new GridBagLayout());
+        setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
+        
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.insets = new Insets(10, 10, 10, 10);
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        
+        // 设备编号 - 使用getString()方法
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        deviceIdLabel = new JLabel(getString("device_id") + ":");
+        add(deviceIdLabel, gbc);
+        
+        gbc.gridx = 1;
+        deviceIdField = new JTextField(15);
+        deviceIdField.setText("2548");
+        add(deviceIdField, gbc);
+        
+        // 通信小组 - 使用getString()方法
+        gbc.gridx = 0;
+        gbc.gridy = 1;
+        groupLabel = new JLabel(getString("group") + ":");
+        add(groupLabel, gbc);
+        
+        gbc.gridx = 1;
+        groupField = new JTextField(15);
+        groupField.setText("2");
+        add(groupField, gbc);
+        
+        // 通信频率 - 使用getString()方法
+        gbc.gridx = 0;
+        gbc.gridy = 2;
+        frequencyLabel = new JLabel(getString("frequency") + ":");
+        add(frequencyLabel, gbc);
+        
+        gbc.gridx = 1;
+        frequencyField = new JTextField(15);
+        frequencyField.setText("1");
+        add(frequencyField, gbc);
+        
+        // 选择文件 - 使用getString()方法
+        gbc.gridx = 0;
+        gbc.gridy = 3;
+        selectFileLabel = new JLabel(getString("select_file") + ":");
+        add(selectFileLabel, gbc);
+        
+        gbc.gridx = 1;
+        JPanel filePanel = new JPanel(new BorderLayout(5, 0));
+        filePathField = new JTextField();
+        filePathField.setEditable(false);
+        selectFileBtn = new JButton("...");
+        selectFileBtn.setPreferredSize(new Dimension(30, 25));
+        selectFileBtn.addActionListener(e -> selectFile());
+        filePanel.add(filePathField, BorderLayout.CENTER);
+        filePanel.add(selectFileBtn, BorderLayout.EAST);
+        add(filePanel, gbc);
+        
+        // 固件升级
+        gbc.gridx = 0;
+        gbc.gridy = 4;
+        gbc.gridwidth = 2;
+        upgradeBtn = createColoredButton(getString("upgrade"), new Color(70, 130, 180));
+        upgradeBtn.setPreferredSize(new Dimension(100, 30));
+        upgradeBtn.addActionListener(e -> upgradeFirmware());
+        add(upgradeBtn, gbc);
+        
+        // 进度条
+        gbc.gridx = 0;
+        gbc.gridy = 5;
+        gbc.gridwidth = 2;
+        progressBar = new JProgressBar(0, 100);
+        progressBar.setStringPainted(true);
+        progressBar.setVisible(false);
+        add(progressBar, gbc);
+        
+        // 操作按钮
+        gbc.gridx = 0;
+        gbc.gridy = 6;
+        gbc.gridwidth = 2;
+        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 20, 10));
+        readConfigBtn = createColoredButton(getString("read_config"), new Color(70, 130, 180));
+        readConfigBtn.setPreferredSize(new Dimension(100, 30));
+        saveConfigBtn = createColoredButton(getString("save_config"), new Color(50, 205, 50));
+        saveConfigBtn.setPreferredSize(new Dimension(100, 30));
+        
+        readConfigBtn.addActionListener(e -> readConfig());
+        saveConfigBtn.addActionListener(e -> saveConfig());
+        
+        buttonPanel.add(readConfigBtn);
+        buttonPanel.add(saveConfigBtn);
+        
+        add(buttonPanel, gbc);
+    }
+    
+    private JButton createColoredButton(String text, Color color) {
+        JButton button = new JButton(text);
+        button.setBackground(color);
+        button.setForeground(Color.WHITE);
+        button.setFocusPainted(false);
+        button.setBorder(BorderFactory.createEmptyBorder(8, 15, 8, 15));
+        
+        button.addMouseListener(new MouseAdapter() {
+            @Override
+            public void mouseEntered(MouseEvent e) {
+                button.setBackground(color.darker());
+            }
+            
+            @Override
+            public void mouseExited(MouseEvent e) {
+                button.setBackground(color);
+            }
+        });
+        
+        return button;
+    }
+    
+    private void selectFile() {
+        JFileChooser fileChooser = new JFileChooser();
+        fileChooser.setFileFilter(new javax.swing.filechooser.FileNameExtensionFilter("BIN Files", "bin"));
+        int result = fileChooser.showOpenDialog(this);
+        if (result == JFileChooser.APPROVE_OPTION) {
+            File selectedFile = fileChooser.getSelectedFile();
+            filePathField.setText(selectedFile.getAbsolutePath());
+        }
+    }
+    
+    private void upgradeFirmware() {
+        String filePath = filePathField.getText();
+        if (filePath.isEmpty()) {
+            JOptionPane.showMessageDialog(this, getString("select_bin_file"), getString("error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+        
+        if (!serialService.isOpen()) {
+            JOptionPane.showMessageDialog(this, getString("open_serial_first"), getString("error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+        
+        // 进行升级,显示进度条
+        upgradeBtn.setEnabled(false);
+        selectFileBtn.setEnabled(false);
+        progressBar.setVisible(true);
+        progressBar.setValue(0);
+        
+        // 在新线程中执行升级操作
+        new Thread(() -> {
+            try {
+                firmwareUpgrader.upgradeFirmware(filePath, progress -> {
+                    SwingUtilities.invokeLater(() -> {
+                        progressBar.setValue(progress);
+                    });
+                });
+                
+                SwingUtilities.invokeLater(() -> {
+                    JOptionPane.showMessageDialog(this, getString("upgrade_success"), getString("info"), JOptionPane.INFORMATION_MESSAGE);
+                    upgradeBtn.setEnabled(true);
+                    selectFileBtn.setEnabled(true);
+                });
+            } catch (Exception e) {
+                SwingUtilities.invokeLater(() -> {
+                    JOptionPane.showMessageDialog(this, getString("upgrade_failed") + ": " + e.getMessage(), getString("error"), JOptionPane.ERROR_MESSAGE);
+                    upgradeBtn.setEnabled(true);
+                    selectFileBtn.setEnabled(true);
+                });
+            }
+        }).start();
+    }
+    
+    private void readConfig() {
+        // 模拟读取配置
+        readConfigBtn.setBackground(Color.GREEN);
+        Timer timer = new Timer(500, e -> {
+            readConfigBtn.setBackground(new Color(70, 130, 180));
+        });
+        timer.setRepeats(false);
+        timer.start();
+        
+        JOptionPane.showMessageDialog(this, getString("config_read_success"), getString("info"), JOptionPane.INFORMATION_MESSAGE);
+    }
+    
+    private void saveConfig() {
+        // 验证输入
+        try {
+            Integer.parseInt(deviceIdField.getText());
+            Integer.parseInt(groupField.getText());
+            Integer.parseInt(frequencyField.getText());
+        } catch (NumberFormatException e) {
+            JOptionPane.showMessageDialog(this, getString("input_valid_number"), getString("error"), JOptionPane.ERROR_MESSAGE);
+            return;
+        }
+        
+        // 模拟保存配置
+        saveConfigBtn.setBackground(Color.GREEN);
+        Timer timer = new Timer(500, e -> {
+            saveConfigBtn.setBackground(new Color(50, 205, 50));
+        });
+        timer.setRepeats(false);
+        timer.start();
+        
+        JOptionPane.showMessageDialog(this, getString("config_save_success"), getString("info"), JOptionPane.INFORMATION_MESSAGE);
+    }
+    
+    public void updateLanguage() {
+        // 更新按钮文本
+        readConfigBtn.setText(getString("read_config"));
+        saveConfigBtn.setText(getString("save_config"));
+        upgradeBtn.setText(getString("upgrade"));
+        
+        // 更新标签文本
+        deviceIdLabel.setText(getString("device_id") + ":");
+        groupLabel.setText(getString("group") + ":");
+        frequencyLabel.setText(getString("frequency") + ":");
+        selectFileLabel.setText(getString("select_file") + ":");
+    }
+    
+    private String getString(String key) {
+        return parentFrame.getString(key);
+    }
+}
\ No newline at end of file
diff --git a/src/home/Dell55AA01Parser.java b/src/home/Dell55AA01Parser.java
new file mode 100644
index 0000000..4c86947
--- /dev/null
+++ b/src/home/Dell55AA01Parser.java
@@ -0,0 +1,111 @@
+package home;
+import java.util.Arrays;
+public class Dell55AA01Parser {
+	// 常量定义
+	private static final String EXPECTED_HEADER = "55AA01"; // 协议头
+	private static final int MIN_LENGTH = 42; // 最小长度(21字节*2字符)
+	private static final ThreadLocal<ParseResult> RESULT_CACHE = 
+			ThreadLocal.withInitial(ParseResult::new);
+
+	// 解析结果类
+	public static class ParseResult {
+		public int sequenceNum;   // 序列号
+		public String tagId;      // 标签ID(4字节小端序)
+		public String anchorId;   // 锚点ID(4字节小端序)
+		public int distance;      // 距离(毫米)
+		public int power;         // 电量(0-100)
+		public int buttonPressed; // 按钮状态
+		public boolean buttonPressed2;
+		public void reset() {
+			sequenceNum = 0;
+			tagId = "";
+			anchorId = "";
+			distance = 0;
+			power = 0;
+			buttonPressed = 0;
+			buttonPressed2=false;
+		}
+	}
+
+	/**
+	 * 解析55AA01协议数据
+	 * @param message 十六进制字符串
+	 * @return 解析结果(错误时返回null)
+	 */
+	public static ParseResult parse(String message, String ip, int port) {
+		if (message == null || message.isEmpty()) {
+			return null;
+		}
+
+		// 清洗数据:移除所有非十六进制字符
+		char[] cleanedMessage = cleanMessage(message);
+
+		// 数据校验
+		if (cleanedMessage == null || cleanedMessage.length < MIN_LENGTH) {
+			return null;
+		}
+
+		// 协议头校验 (55AA01)
+		if (!new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) {
+			return null;
+		}
+
+		ParseResult result = RESULT_CACHE.get();
+		result.reset();
+
+		try {
+			if (cleanedMessage.length < 30) { // 确保有足够长度访问charAt(28)
+				return null;
+			}
+
+			// 解析序列号 (位置8-9)
+			result.sequenceNum = HexUtils.fastHexToByte(cleanedMessage[8], cleanedMessage[9]);
+
+			// 解析标签ID (位置10-13, 小端序)
+			result.tagId = new String(new char[]{
+					cleanedMessage[12], cleanedMessage[13], // 高位
+					cleanedMessage[10], cleanedMessage[11]  // 低位
+			});
+
+			// 解析锚点ID (位置14-17, 小端序)
+			result.anchorId = new String(new char[]{
+					cleanedMessage[16], cleanedMessage[17], // 高位
+					cleanedMessage[14], cleanedMessage[15]  // 低位
+			});
+
+			// 解析距离 (位置18-25, 4字节小端整数)
+			int b0 = HexUtils.fastHexToByte(cleanedMessage[18], cleanedMessage[19]); // 最低位
+			int b1 = HexUtils.fastHexToByte(cleanedMessage[20], cleanedMessage[21]);
+			int b2 = HexUtils.fastHexToByte(cleanedMessage[22], cleanedMessage[23]);
+			int b3 = HexUtils.fastHexToByte(cleanedMessage[24], cleanedMessage[25]); // 最高位
+			int raw = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
+			result.distance = raw; // 保持原始整数值
+
+			// 解析电量 (位置26-27)
+			result.power = HexUtils.fastHexToByte(cleanedMessage[26], cleanedMessage[27]);
+
+			// 解析按钮状态 (位置28-29)
+			result.buttonPressed = HexUtils.fastHexToByte(cleanedMessage[28], cleanedMessage[29]);
+			result.buttonPressed2 =result.buttonPressed==1;			
+
+		} catch (IndexOutOfBoundsException | NumberFormatException e) {
+			System.err.println("Parsing error in packet from " + ip + ":" + port);
+			return null;
+		}
+
+		return result;
+	}
+
+	private static char[] cleanMessage(String message) {
+		char[] cleaned = new char[message.length()];
+		int j = 0;
+		for (char c : message.toCharArray()) {
+			if (Character.isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
+				cleaned[j++] = Character.toUpperCase(c);
+			}
+		}
+		if (j == 0) return null;
+		return Arrays.copyOf(cleaned, j);
+	}
+	
+}
\ No newline at end of file
diff --git a/src/home/Dell55AA1FParser.java b/src/home/Dell55AA1FParser.java
new file mode 100644
index 0000000..ad2cdf4
--- /dev/null
+++ b/src/home/Dell55AA1FParser.java
@@ -0,0 +1,184 @@
+package home;
+import java.util.Arrays;
+
+public class Dell55AA1FParser {
+    // 常量定义
+    private static final String EXPECTED_HEADER = "55AA1F"; // 协议头
+    private static final int FIXED_FIELDS_LENGTH = 11; // 从标签ID到保留字段的固定长度(字节)
+    private static final ThreadLocal<ParseResult> RESULT_CACHE = 
+            ThreadLocal.withInitial(ParseResult::new);
+
+    // 解析结果类
+    public static class ParseResult {
+        public int dataLength;    // 数据长度
+        public int messageType;    // 消息类型
+        public String tagId;       // 标签ID(2字节)
+        public int distance;       // 距离(厘米)
+        public int angle;          // 角度(度)
+        public int signalQuality;  // 信号质量(0-255)
+        public int buttonPressed;  // 按钮状态
+        public int power;          // 电量
+        public String reserved;    // 保留字段
+        public String userData;    // 用户数据
+        public String checksum;    // 校验和
+        
+        public void reset() {
+            dataLength = 0;
+            messageType = 0;
+            tagId = "";
+            distance = 0;
+            angle = 0;
+            signalQuality = 0;
+            buttonPressed = 0;
+            power = 0;
+            reserved = "";
+            userData = "";
+            checksum = "";
+        }
+    }
+
+    /**
+     * 解析55AA1F协议数据
+     * @param message 十六进制字符串
+     * @return 解析结果(错误时返回null)
+     */
+    public static ParseResult parse(String message, String ip, int port) {
+        if (message == null || message.isEmpty()) {
+            return null;
+        }
+
+        // 清洗数据:移除所有非十六进制字符
+        char[] cleanedMessage = cleanMessage(message);
+
+        if (cleanedMessage == null) {
+            return null;
+        }
+
+        // 协议头校验 (55AA1F)
+        if (cleanedMessage.length < 6 || 
+            !new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) {
+            return null;
+        }
+
+        ParseResult result = RESULT_CACHE.get();
+        result.reset();
+
+        try {
+            // 解析数据长度 (位置6-7)
+            result.dataLength = HexUtils.fastHexToByte(cleanedMessage[6], cleanedMessage[7]);
+            
+            // 计算期望的总字符长度
+            int expectedCharLength = 8 + // 包头(4字符) + 消息类型(2字符) + 数据长度(2字符)
+                                    result.dataLength * 2 + // 数据部分
+                                    4; // 校验和(4字符)
+
+//            if (cleanedMessage.length != expectedCharLength) {
+//                System.err.println("Data length mismatch: expected " + expectedCharLength + 
+//                                 ", got " + cleanedMessage.length);
+//                return null;
+//            }
+
+            // 解析消息类型 (位置4-5)
+            result.messageType = HexUtils.fastHexToByte(cleanedMessage[4], cleanedMessage[5]);
+
+            // 解析标签ID (位置8-11),直接取字符串
+            result.tagId = new String(cleanedMessage, 8, 4);
+
+            // 解析距离 (位置12-15, 2字节小端整数)
+            int distLow = HexUtils.fastHexToByte(cleanedMessage[12], cleanedMessage[13]);
+            int distHigh = HexUtils.fastHexToByte(cleanedMessage[14], cleanedMessage[15]);
+            result.distance = (distHigh << 8) | distLow;
+
+            // 解析角度 (位置16-19, 2字节小端有符号整数)
+            int angleLow = HexUtils.fastHexToByte(cleanedMessage[16], cleanedMessage[17]);
+            int angleHigh = HexUtils.fastHexToByte(cleanedMessage[18], cleanedMessage[19]);
+            short angleShort = (short) ((angleHigh << 8) | angleLow);
+            result.angle = angleShort;
+
+            // 解析信号质量 (位置20-21)
+            result.signalQuality = HexUtils.fastHexToByte(cleanedMessage[20], cleanedMessage[21]);
+
+            // 解析按钮状态 (位置22-23)
+            result.buttonPressed = HexUtils.fastHexToByte(cleanedMessage[22], cleanedMessage[23]);
+
+            // 解析电量 (位置24-25)
+            result.power = HexUtils.fastHexToByte(cleanedMessage[24], cleanedMessage[25]);
+
+            // 解析保留字段 (位置26-29)
+            result.reserved = new String(cleanedMessage, 26, 4);
+
+            // 解析用户数据
+//            int userDataStart = 30;
+//            int userDataCharLength = (result.dataLength - FIXED_FIELDS_LENGTH) * 2;
+//            if (userDataCharLength > 0) {
+//                result.userData = new String(cleanedMessage, userDataStart, userDataCharLength);
+//            } else {
+//                result.userData = "";
+//            }
+
+            // 解析校验和 (最后4个字符)
+            result.checksum = new String(cleanedMessage, cleanedMessage.length - 4, 4);
+
+            // 验证校验和
+//            byte[] packetBytes = hexStringToByteArray(new String(cleanedMessage));
+//            if (!verifyChecksum(packetBytes)) {
+//                System.err.println("Checksum verification failed for packet from " + ip + ":" + port);
+//                return null;
+//            }
+
+        } catch (IndexOutOfBoundsException | NumberFormatException e) {
+            System.err.println("Parsing error in 55AA1F packet from " + ip + ":" + port);
+            e.printStackTrace();
+            return null;
+        }
+
+        return result;
+    }
+
+    /**
+     * 将十六进制字符串转换为字节数组
+     */
+    private static byte[] hexStringToByteArray(String s) {
+        int len = s.length();
+        byte[] data = new byte[len / 2];
+        for (int i = 0; i < len; i += 2) {
+            data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+                                 + Character.digit(s.charAt(i+1), 16));
+        }
+        return data;
+    }
+
+    /**
+     * 校验数据包
+     * 去掉包头求和取反,校验码为小端模式
+     */
+    private static boolean verifyChecksum(byte[] packet) {
+        int len = packet.length;
+        if (len < 4) return false;
+
+        int sum = 0;
+        // 从消息类型开始到校验码前结束 (跳过包头2字节)
+        for (int i = 2; i < len - 2; i++) {
+            sum += packet[i] & 0xFF;
+        }
+        sum = ~sum & 0xFFFF; // 取反并保留16位
+
+        // 获取包中的校验码 (小端模式)
+        int receivedChecksum = ((packet[len - 2] & 0xFF) << 8) | (packet[len - 1] & 0xFF);
+
+        return sum == receivedChecksum;
+    }
+
+    private static char[] cleanMessage(String message) {
+        char[] cleaned = new char[message.length()];
+        int j = 0;
+        for (char c : message.toCharArray()) {
+            if (Character.isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) {
+                cleaned[j++] = Character.toUpperCase(c);
+            }
+        }
+        if (j == 0) return null;
+        return Arrays.copyOf(cleaned, j);
+    }
+   
+}
\ No newline at end of file
diff --git a/src/home/FirmwareUpgrader.java b/src/home/FirmwareUpgrader.java
new file mode 100644
index 0000000..10953c4
--- /dev/null
+++ b/src/home/FirmwareUpgrader.java
@@ -0,0 +1,216 @@
+package home;
+
+import java.io.*;
+import java.util.Arrays;
+import java.util.function.Consumer;
+
+public class FirmwareUpgrader {
+	private static final int SOH = 0x01;   // Start of Header
+	private static final int STX = 0x02;   // Start of Text (for 1024-byte blocks)
+	private static final int EOT = 0x04;   // End of Transmission
+	private static final int ACK = 0x06;   // Acknowledge
+	private static final int NAK = 0x15;   // Negative Acknowledge
+	private static final int CAN = 0x18;   // Cancel
+	private static final int C = 0x43;     // 'C' for CRC mode
+
+	private final SerialPortService serialService;
+
+	public FirmwareUpgrader(SerialPortService serialService) {
+		this.serialService = serialService;
+	}
+
+	public void upgradeFirmware(String filePath, ProgressCallback progressCallback) throws IOException {
+		File file = new File(filePath);
+		if (!file.exists() || !file.isFile()) {
+			throw new IOException("File not found: " + filePath);
+		}
+
+		// 发送开始信号
+		sendStartSignal();
+
+		// 发送文件
+		sendFile(file, progressCallback);
+
+		// 发送结束信号
+		sendEndSignal();
+	}
+
+	private void sendStartSignal() throws IOException {
+		// 发送'C'表示使用CRC校验
+		serialService.send(new byte[]{C});
+
+		// 等待ACK
+		if (!waitForAck()) {
+			throw new IOException("Device not ready for YModem transfer");
+		}
+	}
+
+	private void sendFile(File file, ProgressCallback progressCallback) throws IOException {
+		long fileSize = file.length();
+		String fileName = file.getName();
+
+		// 发送文件头块
+		byte[] header = new byte[133];
+		Arrays.fill(header, (byte)0);
+		header[0] = SOH;
+		header[1] = 0x00; // 块编号
+		header[2] = (byte)0xFF; // 块编号补码
+
+		// 文件名和大小
+		byte[] fileNameBytes = fileName.getBytes();
+		System.arraycopy(fileNameBytes, 0, header, 3, Math.min(fileNameBytes.length, 99));
+		String sizeStr = String.valueOf(fileSize);
+		byte[] sizeBytes = sizeStr.getBytes();
+		System.arraycopy(sizeBytes, 0, header, 3 + 99, Math.min(sizeBytes.length, 99));
+
+		// 计算CRC
+		int crc = calculateCRC(header, 3, 128);
+		header[131] = (byte)((crc >> 8) & 0xFF);
+		header[132] = (byte)(crc & 0xFF);
+
+		serialService.send(header);
+
+		// 等待ACK
+		if (!waitForAck()) {
+			throw new IOException("Header not acknowledged");
+		}
+
+		// 发送文件数据
+		try (FileInputStream fis = new FileInputStream(file)) {
+			byte[] buffer = new byte[1024];
+			int bytesRead;
+			int blockNum = 1;
+			long totalSent = 0;
+
+			while ((bytesRead = fis.read(buffer)) != -1) {
+				// 填充不足的部分
+				if (bytesRead < 1024) {
+					Arrays.fill(buffer, bytesRead, 1024, (byte)0x1A);
+				}
+
+				// 发送数据块
+				sendDataBlock(blockNum, buffer);
+				blockNum++;
+				totalSent += bytesRead;
+
+				// 更新进度
+				int progress = (int)((totalSent * 100) / fileSize);
+				progressCallback.onProgress(progress);
+
+				// 等待ACK
+				if (!waitForAck()) {
+					throw new IOException("Data block not acknowledged");
+				}
+			}
+		}
+	}
+
+	private void sendDataBlock(int blockNum, byte[] data) throws IOException {
+		byte[] block = new byte[1029];
+		block[0] = STX; // 1024字节块
+		block[1] = (byte)(blockNum & 0xFF);
+		block[2] = (byte)(~blockNum & 0xFF);
+
+		// 拷贝数据
+		System.arraycopy(data, 0, block, 3, data.length);
+
+		// 计算CRC
+		int crc = calculateCRC(block, 3, 1024);
+		block[1027] = (byte)((crc >> 8) & 0xFF);
+		block[1028] = (byte)(crc & 0xFF);
+
+		serialService.send(block);
+	}
+
+	private void sendEndSignal() throws IOException {
+		// 发送EOT
+		serialService.send(new byte[]{EOT});
+
+		// 等待ACK
+		if (!waitForAck()) {
+			throw new IOException("EOT not acknowledged");
+		}
+
+		// 发送空文件头块表示结束
+		byte[] endHeader = new byte[133];
+		Arrays.fill(endHeader, (byte)0);
+		endHeader[0] = SOH;
+		endHeader[1] = 0x00; // 块编号
+		endHeader[2] = (byte)0xFF; // 块编号补码
+
+		// 计算CRC
+		int crc = calculateCRC(endHeader, 3, 128);
+		endHeader[131] = (byte)((crc >> 8) & 0xFF);
+		endHeader[132] = (byte)(crc & 0xFF);
+
+		serialService.send(endHeader);
+
+		// 等待ACK
+		if (!waitForAck()) {
+			throw new IOException("End header not acknowledged");
+		}
+	}
+
+	private boolean waitForAck() {
+		final Object lock = new Object();
+		final boolean[] result = new boolean[1];
+		final boolean[] receivedResponse = new boolean[1];
+
+		// 创建一个临时消费者来处理接收到的数据
+		Consumer<byte[]> ackConsumer = data -> {
+			if (data.length > 0) {
+				synchronized (lock) {
+					if (data[0] == ACK) {
+						result[0] = true;
+						receivedResponse[0] = true;
+					} else if (data[0] == NAK) {
+						result[0] = false;
+						receivedResponse[0] = true;
+					} else if (data[0] == CAN) {
+						throw new RuntimeException("Transfer canceled by device");
+					}
+					lock.notifyAll();
+				}
+			}
+		};
+
+		// 注册临时消费者
+		serialService.setResponseConsumer(ackConsumer);
+
+		long startTime = System.currentTimeMillis();
+		synchronized (lock) {
+			while (System.currentTimeMillis() - startTime < 5000 && !receivedResponse[0]) {
+				try {
+					lock.wait(100);
+				} catch (InterruptedException e) {
+					Thread.currentThread().interrupt();
+					return false;
+				}
+			}
+		}
+
+		// 取消注册临时消费者
+		serialService.setResponseConsumer(null);
+
+		return receivedResponse[0] && result[0];
+	}
+
+	private int calculateCRC(byte[] data, int offset, int length) {
+		int crc = 0;
+		for (int i = 0; i < length; i++) {
+			crc = crc ^ (data[offset + i] << 8);
+			for (int j = 0; j < 8; j++) {
+				if ((crc & 0x8000) != 0) {
+					crc = (crc << 1) ^ 0x1021;
+				} else {
+					crc = crc << 1;
+				}
+			}
+		}
+		return crc & 0xFFFF;
+	}
+
+	public interface ProgressCallback {
+		void onProgress(int progress);
+	}
+}
\ No newline at end of file
diff --git a/src/home/HexUtils.java b/src/home/HexUtils.java
new file mode 100644
index 0000000..94a3480
--- /dev/null
+++ b/src/home/HexUtils.java
@@ -0,0 +1,40 @@
+package home;
+
+public class HexUtils {
+    // 十六进制字符快速转换表 (ASCII范围内)
+    private static final int[] HEX_VALUES = new int[128];
+    static {
+        for (int i = 0; i < HEX_VALUES.length; i++) {
+            char c = (char) i;
+            if (c >= '0' && c <= '9') HEX_VALUES[i] = c - '0';
+            else if (c >= 'A' && c <= 'F') HEX_VALUES[i] = 10 + (c - 'A');
+            else if (c >= 'a' && c <= 'f') HEX_VALUES[i] = 10 + (c - 'a');
+            else HEX_VALUES[i] = -1;
+        }
+    }
+
+    // 线程安全的字符缓冲区 (初始大小256)
+    private static final ThreadLocal<char[]> CHAR_BUF_CACHE = 
+        ThreadLocal.withInitial(() -> new char[256]);
+
+    /**
+     * 获取线程本地字符缓冲区
+     * @return 可复用的char[256]缓冲区
+     */
+    public static char[] getThreadLocalBuffer() {
+        return CHAR_BUF_CACHE.get();
+    }
+
+    /**
+     * 快速将两个十六进制字符转换为字节
+     * @param c1 高位字符 (0-9, A-F, a-f)
+     * @param c2 低位字符 (0-9, A-F, a-f)
+     * @return 转换后的字节值 (无效字符返回0)
+     */
+    public static int fastHexToByte(char c1, char c2) {
+        int high = (c1 < 128) ? HEX_VALUES[c1] : -1;
+        int low = (c2 < 128) ? HEX_VALUES[c2] : -1;
+        if (high < 0 || low < 0) return 0;
+        return (high << 4) | low;
+    }
+}
\ No newline at end of file
diff --git a/src/home/LogUtil.java b/src/home/LogUtil.java
new file mode 100644
index 0000000..86465f7
--- /dev/null
+++ b/src/home/LogUtil.java
@@ -0,0 +1,87 @@
+package home;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.Date; // 修复:使用 java.util.Date 而不是 java.sql.Date
+import java.text.SimpleDateFormat;
+
+/**
+ * 日志记录工具类
+ */
+public class LogUtil {
+    private static final String LOG_DIR = "systemfile/logfile";
+    private static final String LOG_FILE = LOG_DIR + "/openlog.txt";
+    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+    
+    /**
+     * 记录程序启动日志
+     */
+    public static void logOpen() {
+        ensureLogDirExists();
+        String timestamp = DATE_FORMAT.format(new Date());
+        writeLog("程序启动: " + timestamp + "\n");
+    }
+    
+    /**
+     * 记录程序关闭日志并计算工作时长
+     * @param startTime 程序启动时间戳
+     */
+    public static void logClose(long startTime) {
+        ensureLogDirExists();
+        long endTime = System.currentTimeMillis();
+        String closeTime = DATE_FORMAT.format(new Date(endTime));
+        
+        // 计算工作时长
+        long duration = endTime - startTime;
+        String durationStr = formatDuration(duration);
+        
+        String log = "程序关闭: " + closeTime + "\n" +
+                     "工作时长: " + durationStr + "\n" +
+                     "-----------------------------------\n";
+        writeLog(log);
+    }
+    
+    /**
+     * 确保日志目录存在
+     */
+    private static void ensureLogDirExists() {
+        File dir = new File(LOG_DIR);
+        if (!dir.exists()) {
+            boolean created = dir.mkdirs(); // 创建多级目录
+            if (!created) {
+                System.err.println("无法创建日志目录: " + LOG_DIR);
+            }
+        }
+    }
+    
+    /**
+     * 格式化时长(毫秒→可读字符串)
+     */
+    private static String formatDuration(long millis) {
+        long seconds = millis / 1000;
+        long hours = seconds / 3600;
+        long minutes = (seconds % 3600) / 60;
+        seconds = seconds % 60;
+        return String.format("%d小时 %d分钟 %d秒", hours, minutes, seconds);
+    }
+    
+    /**
+     * 写入日志到文件(追加模式)
+     */
+    private static void writeLog(String content) {
+        try (FileWriter fw = new FileWriter(LOG_FILE, true);
+             BufferedWriter bw = new BufferedWriter(fw)) {
+            bw.write(content);
+            bw.flush(); // 确保内容立即写入
+        } catch (IOException e) {
+            System.err.println("写入日志失败: " + e.getMessage());
+        }
+    }
+
+	public static void log(String string) {
+		// TODO 自动生成的方法存根
+		
+	}
+}
\ No newline at end of file
diff --git a/src/home/Mains.java b/src/home/Mains.java
new file mode 100644
index 0000000..6153836
--- /dev/null
+++ b/src/home/Mains.java
@@ -0,0 +1,44 @@
+package home;
+
+import javax.swing.SwingUtilities;
+
+public class Mains {
+    // 记录启动时间
+    private static long startTime;
+    
+    public static void main(String[] args) {
+        // 记录启动时间
+        startTime = System.currentTimeMillis();
+        // 记录打开日志
+        LogUtil.logOpen();
+        
+        if (SingleInstanceLock.lock()) {
+            try {
+                // 在事件调度线程中创建和显示GUI
+                SwingUtilities.invokeLater(() -> {
+                    try {
+                        // 创建并显示主界面
+                        AOAFollowSystem system = new AOAFollowSystem();
+                        system.setVisible(true);
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                        LogUtil.log("启动可视化界面时发生错误: " + e.getMessage());
+                    }
+                });
+            } finally {
+                // 添加关闭钩子
+                Runtime.getRuntime().addShutdownHook(new Thread(() -> {
+                    // 记录关闭日志
+                    LogUtil.logClose(startTime);
+                    // 释放单实例锁
+                    SingleInstanceLock.release();
+                }));
+            }
+        } else {
+            // 显示程序已在运行的警告
+            SingleInstanceLock.showAlreadyRunningWarning();
+            // 退出程序
+            System.exit(0);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/home/SerialPortService.java b/src/home/SerialPortService.java
new file mode 100644
index 0000000..dce0fe6
--- /dev/null
+++ b/src/home/SerialPortService.java
@@ -0,0 +1,171 @@
+// 声明包名 dell_system
+package home;
+
+// 导入串口通信库
+import com.fazecast.jSerialComm.SerialPort;
+// 导入 Java 函数式接口 Consumer,用于数据接收回调
+import java.util.function.Consumer;
+// 导入字节数组输出流
+import java.io.ByteArrayOutputStream;
+
+/**
+ * 串口服务类
+ * 负责串口的打开、关闭、数据收发
+ */
+public class SerialPortService {
+
+    // 串口对象,用于与硬件通信
+    private SerialPort port;
+    // 标记是否正在捕获数据(线程运行中)
+    private volatile boolean capturing = false;
+    // 标记是否暂停接收数据
+    private volatile boolean paused    = false;
+
+    // 数据读取线程
+    private Thread readerThread;
+    private Consumer<byte[]> responseConsumer;
+    
+    // 优化:重用缓冲区,减少内存分配
+    private byte[] readBuffer = new byte[200];
+    private ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+
+    
+
+    /**
+     * 打开串口
+     * @param portName 串口名称(如 COM3)
+     * @param baud 波特率(如 9600)
+     * @return 是否成功打开
+     */
+    public boolean open(String portName, int baud) {
+        // 如果串口已打开,直接返回成功
+        if (port != null && port.isOpen()) {
+            return true;
+        }
+
+        // 根据名称获取串口实例
+        port = SerialPort.getCommPort(portName);
+        // 设置串口参数:波特率、数据位8、停止位1、无校验位
+        port.setComPortParameters(baud, 8, 1, SerialPort.NO_PARITY);
+        // 修改为半阻塞模式,读超时1ms
+        port.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1, 0);
+
+        // 打开串口并返回结果
+        return port.openPort();
+    }
+    
+    public void setResponseConsumer(Consumer<byte[]> consumer) {
+        this.responseConsumer = consumer;
+    }
+
+    /**
+     * 关闭串口
+     */
+    public void close() {
+        // 先停止数据接收线程
+        stopCapture();
+        // 如果串口已打开,关闭它
+        if (port != null && port.isOpen()) {
+            port.closePort();
+        }
+        // 释放串口引用
+        port = null;
+    }
+
+    /**
+     * 启动数据接收线程
+     * @param onReceived 数据接收回调函数,收到数据时调用
+     */
+    public void startCapture(Consumer<byte[]> onReceived) {
+        if (capturing || port == null || !port.isOpen()) return;
+        capturing = true;
+        paused = false;
+
+        readerThread = new Thread(() -> {
+            buffer.reset();
+            long lastReceivedTime = 0;
+            
+            while (capturing && port.isOpen()) {
+                if (paused) {
+                    buffer.reset();
+                    int len;
+                    do {
+                        len = port.readBytes(readBuffer, readBuffer.length);
+                    } while (len > 0);
+                    try { Thread.sleep(100); } catch (InterruptedException ignore) {}
+                    continue;
+                }
+
+                long currentTime = System.currentTimeMillis();
+                if (buffer.size() > 0 && (currentTime - lastReceivedTime) >= 20) {
+                    byte[] data = buffer.toByteArray();
+                    onReceived.accept(data);
+                    if (responseConsumer != null) {
+                        responseConsumer.accept(data);
+                    }
+                    buffer.reset();
+                }
+
+                int len = port.readBytes(readBuffer, readBuffer.length);
+                currentTime = System.currentTimeMillis();
+
+                if (len > 0) {
+                    buffer.write(readBuffer, 0, len);
+                    lastReceivedTime = currentTime;
+                }
+                
+                if (len <= 0 && buffer.size() == 0) {
+                    try { Thread.sleep(1); } catch (InterruptedException ignore) {}
+                }
+            }
+            
+            if (buffer.size() > 0) {
+                byte[] data = buffer.toByteArray();
+                onReceived.accept(data);
+                if (responseConsumer != null) {
+                    responseConsumer.accept(data);
+                }
+            }
+        });
+        readerThread.setDaemon(true);
+        readerThread.start();
+    }
+    /**
+     * 停止数据接收线程
+     */
+    public void stopCapture() {
+        // 设置捕获标志为 false,通知线程退出
+        capturing = false;
+        // 如果线程存在,等待最多500ms确保线程结束
+        if (readerThread != null) {
+            try { readerThread.join(500); } catch (InterruptedException ignore) {}
+            // 清空线程引用
+            readerThread = null;
+        }
+    }
+
+    // 设置暂停状态
+    public void setPaused(boolean paused) {
+        this.paused = paused;
+    }
+
+    // 获取当前是否暂停
+    public boolean isPaused() {
+        return paused;
+    }
+
+    // 判断串口是否已打开
+    public boolean isOpen() {
+        return port != null && port.isOpen();
+    }
+
+    /**
+     * 发送数据
+     * @param data 要发送的字节数组
+     * @return 是否成功发送
+     */
+    public boolean send(byte[] data) {
+        // 如果串口已初始化且已打开,发送数据并返回结果
+        return port != null && port.isOpen() && port.writeBytes(data, data.length) > 0;
+    }
+}
\ No newline at end of file
diff --git a/src/home/SingleInstanceLock.java b/src/home/SingleInstanceLock.java
new file mode 100644
index 0000000..3d54fcc
--- /dev/null
+++ b/src/home/SingleInstanceLock.java
@@ -0,0 +1,77 @@
+package home;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import javax.swing.JOptionPane;
+
+public class SingleInstanceLock {
+    private static final String LOCK_FILE = System.getProperty("user.home") + 
+                                           File.separator + ".myapp.lock";
+    private static FileLock lock;
+    private static FileChannel channel;
+    
+    /**
+     * 尝试获取应用程序锁
+     * @return true-成功获取锁(第一次运行), false-获取失败(已有实例运行)
+     */
+    @SuppressWarnings("resource")
+	public static boolean lock() {
+        try {
+            // 创建文件对象
+            File file = new File(LOCK_FILE);
+            
+            // 如果文件存在但无法删除,可能是其他实例正在运行
+            if (file.exists() && !file.delete()) {
+                return false;
+            }
+            
+            // 创建文件并获取通道
+            RandomAccessFile raf = new RandomAccessFile(file, "rw");
+            channel = raf.getChannel();
+            
+            // 尝试获取独占锁
+            lock = channel.tryLock();
+            
+            // 如果获取到锁,返回true
+            return lock != null;
+        } catch (IOException e) {
+            // 发生异常说明可能已有实例运行
+            return false;
+        }
+    }
+    
+    /**
+     * 释放应用程序锁
+     */
+    public static void release() {
+        try {
+            // 释放锁
+            if (lock != null && lock.isValid()) {
+                lock.release();
+            }
+            // 关闭通道
+            if (channel != null && channel.isOpen()) {
+                channel.close();
+            }
+            // 删除锁文件
+            File file = new File(LOCK_FILE);
+            if (file.exists()) {
+                file.delete();
+            }
+        } catch (IOException e) {
+            // 释放锁时忽略异常
+        }
+    }
+    
+    /**
+     * 检查程序是否已在运行并显示警告
+     */
+    public static void showAlreadyRunningWarning() {
+        JOptionPane.showMessageDialog(null,
+            "程序已经打开,请勿重复打开",
+            "警告",
+            JOptionPane.WARNING_MESSAGE);
+    }
+}
\ No newline at end of file
diff --git a/src/home/VisualizationPanel.java b/src/home/VisualizationPanel.java
new file mode 100644
index 0000000..bde7067
--- /dev/null
+++ b/src/home/VisualizationPanel.java
@@ -0,0 +1,205 @@
+package home;
+import java.awt.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import javax.swing.BorderFactory;
+import javax.swing.JPanel;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
+
+// 可视化面板类
+class VisualizationPanel extends JPanel {
+    /**
+     * 
+     */
+    private static final long serialVersionUID = 1L;
+    private int distance = 0;
+    private int angle = 0;
+    private AOAFollowSystem parentFrame;
+    private String anchorid = "Anchor";
+    private String tagid = "Tag";
+    private double scaleFactor = 1.0; // 缩放因子
+    private static final double MIN_SCALE = 0.2; // 最小缩放倍数
+    private static final double MAX_SCALE = 5.0; // 最大缩放倍数
+    
+    // 使用支持中文的字体
+    private Font boldFont = new Font("Microsoft YaHei", Font.BOLD, 14);
+    private Font normalFont = new Font("Microsoft YaHei", Font.PLAIN, 12);
+    private Font titleFont = new Font("Microsoft YaHei", Font.BOLD, 12);
+    
+    public VisualizationPanel(AOAFollowSystem parentFrame) {
+        this.parentFrame = parentFrame;
+        setPreferredSize(new Dimension(400, 400));
+        setBorder(BorderFactory.createTitledBorder(parentFrame.getString("visualization")));
+        
+        // 添加鼠标滚轮监听器
+        addMouseWheelListener(new MouseWheelListener() {
+            @Override
+            public void mouseWheelMoved(MouseWheelEvent e) {
+                int notches = e.getWheelRotation();
+                if (notches < 0) {
+                    // 向上滚动,放大
+                    scaleFactor = Math.min(scaleFactor * 1.1, MAX_SCALE);
+                } else {
+                    // 向下滚动,缩小
+                    scaleFactor = Math.max(scaleFactor / 1.1, MIN_SCALE);
+                }
+                repaint();
+            }
+        });
+    }
+
+    public void updatePosition(int distance, int angle) {
+        // 优化:只有数据变化时才重绘
+        if (this.distance != distance || this.angle != angle) {
+            this.distance = distance;
+            // 将角度范围转换为-180到180度,正下方为0度
+            this.angle = normalizeAngle(angle);
+            repaint();
+        }
+    }
+    
+    // 角度归一化方法,将角度转换为-180到180度范围,正下方为0度
+    private int normalizeAngle(int angle) {
+        // 首先将角度转换为0-360度范围
+        angle = angle % 360;
+        if (angle < 0) {
+            angle += 360;
+        }
+        
+        // 将0-360度转换为-180到180度,正下方为0度
+        // 0度对应正下方,90度对应正左方,180/-180度对应正上方,-90度对应正右方
+        int normalizedAngle = angle - 180;
+        if (normalizedAngle > 180) {
+            normalizedAngle -= 360;
+        } else if (normalizedAngle <= -180) {
+            normalizedAngle += 360;
+        }
+        
+        return normalizedAngle;
+    }
+
+    public void setAnchorId(String anchorid) {
+        this.anchorid = anchorid;
+    }
+
+    public void setTagId(String tagid) {
+        this.tagid = tagid;
+    }
+
+    @Override
+    protected void paintComponent(Graphics g) {
+        super.paintComponent(g);
+
+        Graphics2D g2d = (Graphics2D) g;
+        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        
+        // 确保使用支持中文的字体
+        g2d.setFont(normalFont);
+
+        int centerX = getWidth() / 2;
+        int centerY = getHeight()/ 2 - 30;
+        int maxRadius = (int) (Math.min(centerX, centerY) * scaleFactor - 20);
+
+        // 设置背景颜色
+        g2d.setColor(new Color(240, 240, 240));
+        g2d.fillRect(0, 0, getWidth(), getHeight());
+
+        // 绘制坐标轴
+        g2d.setColor(Color.LIGHT_GRAY);
+        g2d.drawLine(centerX, 0, centerX, getHeight());
+        g2d.drawLine(0, centerY, getWidth(), centerY);
+
+        // 绘制角度刻度线 (-180° 到 180°)
+        g2d.setColor(new Color(150, 150, 150));
+        for (int i = -180; i <= 180; i += 30) {
+            // 转换为弧度,正下方为0度
+            double rad = Math.toRadians(i);
+            int x1 = centerX + (int) (maxRadius * Math.sin(rad));
+            int y1 = centerY - (int) (maxRadius * Math.cos(rad));
+            g2d.setColor(new Color(100, 100, 100));
+            g2d.drawLine(centerX, centerY, x1, y1);
+
+            // 绘制角度标签
+            int labelX = centerX + (int) ((maxRadius + 15) * Math.sin(rad));
+            int labelY = centerY - (int) ((maxRadius + 15) * Math.cos(rad));
+            g2d.setColor(Color.blue);
+            g2d.drawString(i + "°", labelX - 10, labelY + 5);
+        }
+
+        // 绘制距离圆圈和标签
+        g2d.setColor(new Color(100, 100, 100));
+        for (int i = 1; i <= 5; i++) {
+            int radius = maxRadius * i / 5;
+            g2d.drawOval(centerX - radius, centerY - radius, radius * 2, radius * 2);
+            // 修改:将距离标签显示在圆圈正南方中间
+            String distanceLabel = (int)(i * 200 / scaleFactor) + "cm";
+            int labelWidth = g2d.getFontMetrics().stringWidth(distanceLabel);
+            g2d.drawString(distanceLabel, centerX - labelWidth / 2, centerY + radius + 15);
+        }
+
+        // 绘制A点(锚点)
+        g2d.setColor(Color.BLACK);
+        g2d.fillOval(centerX - 5, centerY - 5, 10, 10);
+        // A点正上方显示设备编号
+        g2d.setFont(titleFont);
+        g2d.drawString(anchorid, centerX - g2d.getFontMetrics().stringWidth(anchorid)/2, centerY - 15);
+        g2d.setFont(normalFont);
+
+        // 绘制B点(标签点)
+        if (distance > 0) {
+            // 根据距离计算缩放比例(最大1000厘米)
+            double scaledDistance = Math.min(distance, 1000) * maxRadius / 1000.0;
+            double rad = Math.toRadians(angle);
+
+            int bX = centerX + (int) (scaledDistance * Math.sin(rad));
+            int bY = centerY - (int) (scaledDistance * Math.cos(rad));
+            // 绘制连接线
+            g2d.setColor(new Color(0, 100, 200, 150));
+            g2d.drawLine(centerX, centerY, bX, bY);
+
+            // B点正下方显示距离和角度信息
+            String distanceText = distance + "cm";
+            String angleText = angle + "°";
+            
+            // 设置加粗字体和加大字号
+            g2d.setFont(boldFont);
+            g2d.setColor(Color.RED);
+            
+            // 计算文本位置(B点正下方)
+            int textY = bY + 25;
+            g2d.drawString(distanceText, bX - g2d.getFontMetrics().stringWidth(distanceText)/2, textY);
+            g2d.drawString(angleText, bX - g2d.getFontMetrics().stringWidth(angleText)/2, textY + 20);
+            
+            // 恢复原始字体
+            g2d.setFont(normalFont);
+
+            // 在面板底部显示距离和角度信息 - 实时获取字符串,不使用缓存
+            g2d.setFont(boldFont);
+            // 直接从父框架获取当前语言的字符串
+            g2d.drawString(parentFrame.getString("distance") + distance + "cm", 10, 40);
+            g2d.drawString(parentFrame.getString("angle") + angle + "°", 10, 70);
+            g2d.setFont(normalFont);
+            g2d.setColor(Color.BLUE);
+            g2d.fillOval(bX - 5, bY - 5, 10, 10);
+            
+            // B点正上方显示设备编号
+            g2d.setFont(titleFont);
+            g2d.drawString(tagid, bX - g2d.getFontMetrics().stringWidth(tagid)/2, bY - 15);
+            g2d.setFont(normalFont);
+        }
+        
+        // 显示当前缩放比例
+        g2d.setColor(Color.DARK_GRAY);
+        g2d.drawString(String.format("缩放: %.1fx", scaleFactor), getWidth() - 80, 20);
+    }
+
+    public void updateLanguage() {
+        setBorder(BorderFactory.createTitledBorder(parentFrame.getString("visualization")));
+        // 移除字符串缓存,改为在paintComponent中实时获取
+        repaint();
+    }
+}
\ No newline at end of file
diff --git a/systemfile/Messages_en.properties b/systemfile/Messages_en.properties
new file mode 100644
index 0000000..002aef8
--- /dev/null
+++ b/systemfile/Messages_en.properties
@@ -0,0 +1,51 @@
+angle=Angle:
+distance=Distance:
+LANGUAGE=Language
+display_format=Display Format
+serial_port=Serial Port
+baud_rate=Baud Rate
+close_serial=Close Serial
+home=Home
+config=Configuration
+device_id=Device ID
+group=Communication Group
+frequency=Communication Frequency
+read_config=Read Configuration
+save_config=Save Configuration
+serial_port=Serial Port
+baud_rate=Baud Rate
+open_serial=Open Serial Port
+start=Start
+pause=Pause
+clear=Clear
+send=Send
+device_id_table=Device ID
+distance_table=Real-time Distance
+angle_table=Real-time Angle
+signal_table=Signal Quality
+power_table=Power Level
+button_table=Button
+time_table=Time
+log=Log
+ send_data=Send Data
+ select_serial_port=Please select a serial port
+ error=Error
+ open_serial_first=Please open the serial port first
+ input_data_to_send=Please input data to send
+ send_failed=Send failed
+ receive=Receive
+ visualization=Visualization
+ config_read_success=Configuration read successfully
+ info=Information
+ input_valid_number=Please input valid numbers
+ config_save_success=Configuration saved successfully
+ select_file=Select File
+upgrade=Upgrade
+upgrade_progress=Upgrade Progress
+select_bin_file=Please select bin file
+upgrade_success=Upgrade Success
+upgrade_failed=Upgrade Failed
+hex=HEX
+ascii=ASCII
+hex_send=HEX Send
+close_serial=Close Serial
\ No newline at end of file
diff --git a/systemfile/Messages_zh.properties b/systemfile/Messages_zh.properties
new file mode 100644
index 0000000..2cb98e5
--- /dev/null
+++ b/systemfile/Messages_zh.properties
@@ -0,0 +1,51 @@
+angle=瑙掑害锛�
+distance=璺濈锛�
+LANGUAGE=璇█
+display_format=鏄剧ず鏍煎紡
+serial_port=涓插彛
+baud_rate=娉㈢壒鐜�
+close_serial=鍏抽棴涓插彛
+home=棣栭〉
+config=閰嶇疆
+device_id=璁惧缂栧彿
+group=閫氫俊灏忕粍
+frequency=閫氫俊棰戠巼
+read_config=璇诲彇閰嶇疆
+save_config=淇濆瓨閰嶇疆
+serial_port=涓插彛
+baud_rate=娉㈢壒鐜�
+open_serial=鎵撳紑涓插彛
+start=寮�濮�
+pause=鏆傚仠
+clear=娓呯┖
+send=鍙戦��
+device_id_table=璁惧缂栧彿
+distance_table=瀹炴椂璺濈
+angle_table=瀹炴椂瑙掑害
+signal_table=淇″彿璐ㄩ噺
+power_table=鐢甸噺
+button_table=鎸夐敭
+time_table=鏃堕棿
+log=鏃ュ織
+ send_data=鍙戦�佹暟鎹�
+ select_serial_port=璇烽�夋嫨涓插彛
+ error=閿欒
+ open_serial_first=璇峰厛鎵撳紑涓插彛
+ input_data_to_send=璇疯緭鍏ヨ鍙戦�佺殑鏁版嵁
+ send_failed=鍙戦�佸け璐�
+ receive=鎺ユ敹
+ visualization=鍙鍖�
+ config_read_success=閰嶇疆璇诲彇鎴愬姛
+ info=鎻愮ず
+ input_valid_number=璇疯緭鍏ユ湁鏁堢殑鏁板瓧
+ config_save_success=閰嶇疆淇濆瓨鎴愬姛
+ select_file=閫夋嫨鏂囦欢
+upgrade=鍗囩骇
+upgrade_progress=鍗囩骇杩涘害
+select_bin_file=璇烽�夋嫨bin鏂囦欢
+upgrade_success=鍗囩骇鎴愬姛
+upgrade_failed=鍗囩骇澶辫触
+hex=HEX
+ascii=ASCII
+hex_send=HEX鍙戦��
+close_serial=鍏抽棴涓插彛
\ No newline at end of file
diff --git a/systemfile/logfile/openlog.txt b/systemfile/logfile/openlog.txt
new file mode 100644
index 0000000..59defae
--- /dev/null
+++ b/systemfile/logfile/openlog.txt
@@ -0,0 +1,382 @@
+程序启动: 2025-08-20 22:25:39
+程序关闭: 2025-08-20 22:27:05
+工作时长: 0小时 1分钟 26秒
+-----------------------------------
+程序启动: 2025-08-20 22:27:08
+程序关闭: 2025-08-20 22:27:10
+工作时长: 0小时 0分钟 1秒
+-----------------------------------
+程序启动: 2025-08-21 22:06:32
+程序启动: 2025-08-21 22:22:27
+程序关闭: 2025-08-21 22:22:31
+工作时长: 0小时 15分钟 59秒
+-----------------------------------
+程序启动: 2025-08-21 22:22:39
+程序关闭: 2025-08-21 22:23:26
+工作时长: 0小时 0分钟 46秒
+-----------------------------------
+程序启动: 2025-08-21 22:33:26
+程序关闭: 2025-08-21 22:34:02
+工作时长: 0小时 0分钟 35秒
+-----------------------------------
+程序启动: 2025-08-21 22:52:48
+程序关闭: 2025-08-21 22:53:09
+工作时长: 0小时 0分钟 21秒
+-----------------------------------
+程序启动: 2025-08-21 23:02:51
+程序关闭: 2025-08-21 23:03:18
+工作时长: 0小时 0分钟 26秒
+-----------------------------------
+程序启动: 2025-08-21 23:03:48
+程序关闭: 2025-08-21 23:04:58
+工作时长: 0小时 1分钟 10秒
+-----------------------------------
+程序启动: 2025-08-21 23:07:06
+程序关闭: 2025-08-21 23:10:42
+工作时长: 0小时 3分钟 35秒
+-----------------------------------
+程序启动: 2025-08-21 23:10:43
+程序关闭: 2025-08-21 23:16:26
+工作时长: 0小时 5分钟 42秒
+-----------------------------------
+程序启动: 2025-08-21 23:16:30
+程序关闭: 2025-08-21 23:16:53
+工作时长: 0小时 0分钟 23秒
+-----------------------------------
+程序启动: 2025-08-23 09:40:59
+程序关闭: 2025-08-23 10:18:56
+工作时长: 0小时 37分钟 56秒
+-----------------------------------
+程序启动: 2025-08-23 10:19:01
+程序启动: 2025-08-23 10:32:55
+程序关闭: 2025-08-23 10:32:59
+工作时长: 0小时 13分钟 58秒
+-----------------------------------
+程序启动: 2025-08-23 10:33:01
+程序启动: 2025-08-23 10:40:28
+程序关闭: 2025-08-23 10:40:31
+工作时长: 0小时 7分钟 30秒
+-----------------------------------
+程序启动: 2025-08-23 10:40:33
+程序关闭: 2025-08-23 10:40:40
+工作时长: 0小时 0分钟 7秒
+-----------------------------------
+程序启动: 2025-08-23 10:44:02
+程序关闭: 2025-08-23 10:44:10
+工作时长: 0小时 0分钟 8秒
+-----------------------------------
+程序启动: 2025-08-23 11:02:49
+程序关闭: 2025-08-23 11:02:50
+工作时长: 0小时 0分钟 1秒
+-----------------------------------
+程序启动: 2025-08-23 11:03:13
+程序关闭: 2025-08-23 11:03:15
+工作时长: 0小时 0分钟 1秒
+-----------------------------------
+程序启动: 2025-08-23 11:08:59
+程序关闭: 2025-08-23 11:20:47
+工作时长: 0小时 11分钟 48秒
+-----------------------------------
+程序启动: 2025-08-23 11:20:49
+程序关闭: 2025-08-23 11:45:08
+工作时长: 0小时 24分钟 19秒
+-----------------------------------
+程序启动: 2025-08-23 11:45:10
+程序关闭: 2025-08-23 11:45:11
+工作时长: 0小时 0分钟 1秒
+-----------------------------------
+程序启动: 2025-08-23 11:46:25
+程序关闭: 2025-08-23 11:52:37
+工作时长: 0小时 6分钟 11秒
+-----------------------------------
+程序启动: 2025-08-23 11:52:38
+程序启动: 2025-08-23 11:53:48
+程序关闭: 2025-08-23 11:53:51
+工作时长: 0小时 1分钟 12秒
+-----------------------------------
+程序启动: 2025-08-23 11:53:53
+程序关闭: 2025-08-23 12:20:32
+工作时长: 0小时 26分钟 39秒
+-----------------------------------
+程序启动: 2025-08-23 12:20:34
+程序关闭: 2025-08-23 12:21:04
+工作时长: 0小时 0分钟 30秒
+-----------------------------------
+程序启动: 2025-08-23 12:24:07
+程序关闭: 2025-08-23 12:32:02
+工作时长: 0小时 7分钟 54秒
+-----------------------------------
+程序启动: 2025-08-23 12:32:05
+程序关闭: 2025-08-23 12:32:20
+工作时长: 0小时 0分钟 15秒
+-----------------------------------
+程序启动: 2025-08-23 12:32:52
+程序启动: 2025-08-23 12:34:29
+程序关闭: 2025-08-23 12:34:46
+工作时长: 0小时 0分钟 16秒
+-----------------------------------
+程序启动: 2025-08-23 12:35:10
+程序启动: 2025-08-23 12:36:54
+程序关闭: 2025-08-23 12:36:55
+工作时长: 0小时 0分钟 1秒
+-----------------------------------
+程序启动: 2025-08-23 12:37:21
+程序关闭: 2025-08-23 12:50:13
+工作时长: 0小时 12分钟 52秒
+-----------------------------------
+程序启动: 2025-08-23 12:50:15
+程序关闭: 2025-08-23 12:55:28
+工作时长: 0小时 5分钟 12秒
+-----------------------------------
+程序启动: 2025-08-23 12:55:30
+程序关闭: 2025-08-23 12:55:45
+工作时长: 0小时 0分钟 15秒
+-----------------------------------
+程序启动: 2025-08-23 12:59:36
+程序启动: 2025-08-23 12:59:46
+程序启动: 2025-08-23 12:59:50
+程序启动: 2025-08-23 13:00:14
+程序关闭: 2025-08-23 13:00:31
+工作时长: 0小时 0分钟 16秒
+-----------------------------------
+程序启动: 2025-08-23 13:01:24
+程序启动: 2025-08-23 13:07:11
+程序关闭: 2025-08-23 13:07:14
+工作时长: 0小时 5分钟 50秒
+-----------------------------------
+程序启动: 2025-08-23 13:07:16
+程序关闭: 2025-08-23 13:09:02
+工作时长: 0小时 1分钟 45秒
+-----------------------------------
+程序启动: 2025-08-23 13:11:28
+程序关闭: 2025-08-23 13:11:50
+工作时长: 0小时 0分钟 22秒
+-----------------------------------
+程序启动: 2025-08-23 13:15:40
+程序关闭: 2025-08-23 13:16:03
+工作时长: 0小时 0分钟 23秒
+-----------------------------------
+程序启动: 2025-08-23 13:18:57
+程序启动: 2025-08-23 13:26:38
+程序关闭: 2025-08-23 13:27:34
+工作时长: 0小时 0分钟 56秒
+-----------------------------------
+程序启动: 2025-08-23 13:30:01
+程序关闭: 2025-08-23 13:43:02
+工作时长: 0小时 13分钟 1秒
+-----------------------------------
+程序启动: 2025-08-23 13:47:21
+程序关闭: 2025-08-23 13:47:27
+工作时长: 0小时 0分钟 6秒
+-----------------------------------
+程序启动: 2025-08-23 13:49:29
+程序关闭: 2025-08-23 13:55:16
+工作时长: 0小时 5分钟 47秒
+-----------------------------------
+程序启动: 2025-08-23 13:55:18
+程序关闭: 2025-08-23 13:55:42
+工作时长: 0小时 0分钟 23秒
+-----------------------------------
+程序启动: 2025-08-23 13:57:28
+程序关闭: 2025-08-23 14:00:15
+工作时长: 0小时 2分钟 47秒
+-----------------------------------
+程序启动: 2025-08-23 14:04:46
+程序关闭: 2025-08-23 14:06:11
+工作时长: 0小时 1分钟 24秒
+-----------------------------------
+程序启动: 2025-08-23 14:10:00
+程序关闭: 2025-08-23 14:10:50
+工作时长: 0小时 0分钟 50秒
+-----------------------------------
+程序启动: 2025-08-23 14:11:46
+程序关闭: 2025-08-23 14:12:00
+工作时长: 0小时 0分钟 14秒
+-----------------------------------
+程序启动: 2025-08-23 14:12:45
+程序关闭: 2025-08-23 14:14:12
+工作时长: 0小时 1分钟 27秒
+-----------------------------------
+程序启动: 2025-08-23 14:15:18
+程序关闭: 2025-08-23 14:15:41
+工作时长: 0小时 0分钟 23秒
+-----------------------------------
+程序启动: 2025-08-23 14:16:02
+程序关闭: 2025-08-23 14:16:33
+工作时长: 0小时 0分钟 30秒
+-----------------------------------
+程序启动: 2025-08-23 14:16:45
+程序关闭: 2025-08-23 14:20:49
+工作时长: 0小时 4分钟 4秒
+-----------------------------------
+程序启动: 2025-08-23 14:20:52
+程序关闭: 2025-08-23 14:22:58
+工作时长: 0小时 2分钟 6秒
+-----------------------------------
+程序启动: 2025-08-23 14:24:55
+程序关闭: 2025-08-23 14:36:39
+工作时长: 0小时 11分钟 43秒
+-----------------------------------
+程序启动: 2025-08-23 14:36:41
+程序关闭: 2025-08-23 14:51:47
+工作时长: 0小时 15分钟 5秒
+-----------------------------------
+程序启动: 2025-08-23 14:51:48
+程序关闭: 2025-08-23 14:52:27
+工作时长: 0小时 0分钟 38秒
+-----------------------------------
+程序启动: 2025-08-23 14:53:43
+程序关闭: 2025-08-23 14:56:54
+工作时长: 0小时 3分钟 10秒
+-----------------------------------
+程序启动: 2025-08-23 14:56:56
+程序关闭: 2025-08-23 14:58:50
+工作时长: 0小时 1分钟 54秒
+-----------------------------------
+程序启动: 2025-08-23 15:01:18
+程序关闭: 2025-08-23 15:01:46
+工作时长: 0小时 0分钟 27秒
+-----------------------------------
+程序启动: 2025-08-23 15:02:42
+程序关闭: 2025-08-23 15:02:55
+工作时长: 0小时 0分钟 13秒
+-----------------------------------
+程序启动: 2025-08-23 15:07:22
+程序关闭: 2025-08-23 15:07:43
+工作时长: 0小时 0分钟 21秒
+-----------------------------------
+程序启动: 2025-08-23 15:09:08
+程序关闭: 2025-08-23 15:09:20
+工作时长: 0小时 0分钟 11秒
+-----------------------------------
+程序启动: 2025-08-23 15:13:00
+程序关闭: 2025-08-23 15:13:14
+工作时长: 0小时 0分钟 14秒
+-----------------------------------
+程序启动: 2025-08-23 15:16:58
+程序关闭: 2025-08-23 15:17:26
+工作时长: 0小时 0分钟 28秒
+-----------------------------------
+程序启动: 2025-08-23 15:21:38
+程序关闭: 2025-08-23 15:30:58
+工作时长: 0小时 9分钟 20秒
+-----------------------------------
+程序启动: 2025-08-24 22:40:40
+程序关闭: 2025-08-24 22:40:51
+工作时长: 0小时 0分钟 11秒
+-----------------------------------
+程序启动: 2025-08-25 14:33:34
+程序启动: 2025-08-25 15:38:47
+程序启动: 2025-08-25 16:02:07
+程序启动: 2025-08-25 16:08:36
+程序关闭: 2025-08-25 16:20:36
+工作时长: 0小时 11分钟 59秒
+-----------------------------------
+程序启动: 2025-08-25 16:20:38
+程序关闭: 2025-08-25 16:20:54
+工作时长: 0小时 0分钟 16秒
+-----------------------------------
+程序启动: 2025-08-25 16:21:39
+程序关闭: 2025-08-25 16:21:48
+工作时长: 0小时 0分钟 8秒
+-----------------------------------
+程序启动: 2025-08-25 16:26:43
+程序关闭: 2025-08-25 16:27:15
+工作时长: 0小时 0分钟 31秒
+-----------------------------------
+程序启动: 2025-08-25 16:36:31
+程序关闭: 2025-08-25 16:44:31
+工作时长: 0小时 7分钟 59秒
+-----------------------------------
+程序启动: 2025-08-25 21:47:03
+程序关闭: 2025-08-25 21:47:25
+工作时长: 0小时 0分钟 22秒
+-----------------------------------
+程序启动: 2025-08-25 21:47:39
+程序关闭: 2025-08-25 21:48:51
+工作时长: 0小时 1分钟 11秒
+-----------------------------------
+程序启动: 2025-08-25 21:49:22
+程序关闭: 2025-08-25 21:52:09
+工作时长: 0小时 2分钟 46秒
+-----------------------------------
+程序启动: 2025-08-25 21:52:10
+程序关闭: 2025-08-25 21:53:22
+工作时长: 0小时 1分钟 11秒
+-----------------------------------
+程序启动: 2025-08-25 21:53:28
+程序关闭: 2025-08-25 21:53:46
+工作时长: 0小时 0分钟 17秒
+-----------------------------------
+程序启动: 2025-08-25 21:54:00
+程序关闭: 2025-08-25 22:13:45
+工作时长: 0小时 19分钟 44秒
+-----------------------------------
+程序启动: 2025-08-25 22:14:44
+程序关闭: 2025-08-25 22:14:56
+工作时长: 0小时 0分钟 12秒
+-----------------------------------
+程序启动: 2025-08-25 22:15:26
+程序关闭: 2025-08-25 22:15:54
+工作时长: 0小时 0分钟 27秒
+-----------------------------------
+程序启动: 2025-08-25 22:15:58
+程序关闭: 2025-08-25 22:16:19
+工作时长: 0小时 0分钟 20秒
+-----------------------------------
+程序启动: 2025-08-25 22:18:58
+程序启动: 2025-08-25 22:24:19
+程序关闭: 2025-08-25 22:27:56
+工作时长: 0小时 8分钟 58秒
+-----------------------------------
+程序启动: 2025-08-25 22:27:58
+程序启动: 2025-08-25 22:31:32
+程序关闭: 2025-08-25 22:31:42
+工作时长: 0小时 0分钟 9秒
+-----------------------------------
+程序启动: 2025-08-25 22:33:09
+程序关闭: 2025-08-25 22:33:36
+工作时长: 0小时 0分钟 26秒
+-----------------------------------
+程序启动: 2025-08-25 22:35:48
+程序启动: 2025-08-25 22:36:24
+程序关闭: 2025-08-25 22:36:28
+工作时长: 0小时 0分钟 40秒
+-----------------------------------
+程序启动: 2025-08-25 22:36:30
+程序关闭: 2025-08-25 22:37:35
+工作时长: 0小时 1分钟 5秒
+-----------------------------------
+程序启动: 2025-08-25 22:48:24
+程序关闭: 2025-08-25 22:59:07
+工作时长: 0小时 10分钟 43秒
+-----------------------------------
+程序启动: 2025-08-25 23:04:12
+程序关闭: 2025-08-25 23:04:23
+工作时长: 0小时 0分钟 11秒
+-----------------------------------
+程序启动: 2025-08-25 23:04:36
+程序关闭: 2025-08-25 23:04:44
+工作时长: 0小时 0分钟 8秒
+-----------------------------------
+程序启动: 2025-08-26 14:02:07
+程序关闭: 2025-08-26 14:03:39
+工作时长: 0小时 1分钟 31秒
+-----------------------------------
+程序启动: 2025-08-26 14:08:31
+程序关闭: 2025-08-26 14:08:42
+工作时长: 0小时 0分钟 10秒
+-----------------------------------
+程序启动: 2025-08-26 14:15:00
+程序关闭: 2025-08-26 14:16:29
+工作时长: 0小时 1分钟 29秒
+-----------------------------------
+程序启动: 2025-08-26 16:01:22
+程序关闭: 2025-08-26 16:02:35
+工作时长: 0小时 1分钟 13秒
+-----------------------------------
+程序启动: 2025-08-26 16:02:37
+程序关闭: 2025-08-26 17:12:18
+工作时长: 1小时 9分钟 40秒
+-----------------------------------
+程序启动: 2025-08-26 17:12:20

--
Gitblit v1.9.3