From 3ad76f98fa8b4a9d3d95207cfb4ae4706087c964 Mon Sep 17 00:00:00 2001
From: 张世豪 <979909237@qq.com>
Date: 星期四, 04 十二月 2025 19:14:15 +0800
Subject: [PATCH] 新增20251204

---
 image/speedslow.png                       |    0 
 src/gecaoji/Device.java                   |   82 +
 dikuai.properties                         |    4 
 src/zhuye/LegendDialog.java               |   16 
 src/chuankou/dellmessage.java             |  245 +++++
 src/homein/Homein.java                    |    2 
 src/chuankou/TimestampUtil.java           |   16 
 src/zhuye/MapRenderer.java                |  166 +++
 set.properties                            |    9 
 src/chuankou/SerialDataReceiver.java      |  218 +++++
 src/udpdell/UDPServer.java                |   43 +
 src/zhuye/Shouye.java                     |  186 ++++
 src/zhuye/Coordinate.java                 |  163 ++-
 src/chuankou/sendmessage.java             |  105 ++
 lib/slf4j-simple-1.7.30.jar               |    0 
 src/chuankou/Sendmsg.java                 |  138 +++
 src/set/Sets.java                         |  126 ++
 src/set/debug.java                        |  555 +++++++++++++
 src/chuankou/SerialPortPreferences.java   |  101 ++
 lib/slf4j-api-1.7.30.jar                  |    0 
 src/chuankou/SerialPortAutoConnector.java |   38 
 src/chuankou/SerialPortService.java       |  262 ++++++
 22 files changed, 2,358 insertions(+), 117 deletions(-)

diff --git a/dikuai.properties b/dikuai.properties
index c5552e4..de8c6cd 100644
--- a/dikuai.properties
+++ b/dikuai.properties
@@ -1,5 +1,5 @@
 #Dikuai Properties
-#Wed Dec 03 18:54:07 CST 2025
+#Thu Dec 04 17:33:00 CST 2025
 DK-001.angleThreshold=-1 
 DK-001.baseStationCoordinates=3949.84110064,N,11616.74587312,E 
 DK-001.boundaryCoordinates=0,0;0.45,-2.86;1.81,-6.44;21.93,-40.79;27.22,-50.65;31.66,-54.38;33.17,-54.12;46.44,-47.04;56.61,-40.86;57.42,-39.55;53.19,-31.57;32.88,2.52;31.55,4.1;29.14,4.33;0,0
@@ -11,7 +11,7 @@
 DK-001.landName=鍓嶉櫌鑽夊潽
 DK-001.landNumber=DK-001
 DK-001.mowingPattern=-1
-DK-001.mowingTrack=-1
+DK-001.mowingTrack=-114.777,-24.033;-110.353,-30.889;-110.577,-30.916;-110.900,-30.955;-113.494,-31.968;-115.965,-32.890;-118.052,-33.648;-119.305,-34.099;-120.335,-34.349;-119.759,-34.271;-119.172,-34.103;-118.842,-34.017;-120.023,-34.112;-120.797,-34.015;-121.299,-33.602;-122.058,-33.137;-121.986,-32.878;-121.717,-32.606;-121.976,-32.489;-122.326,-32.521;-122.783,-32.548;-122.804,-32.405;-122.730,-32.551;-122.859,-32.641;-123.052,-32.678;-123.206,-32.624;-123.330,-32.587;-123.430,-32.576;-123.553,-32.345;-123.522,-32.133;-123.415,-31.958;-123.224,-31.858;-122.894,-31.881;-122.644,-31.855;-122.406,-31.794;-122.031,-31.745;-121.631,-31.747;-121.197,-31.740;-120.726,-31.679;-120.219,-31.530;-119.759,-31.384;-119.386,-31.248;-118.950,-31.129;-118.423,-30.945;-117.629,-30.704;-116.709,-30.480;-115.780,-30.291;-114.842,-30.113;-113.984,-30.004;-113.262,-29.882;-112.661,-29.719;-112.123,-29.476;-111.630,-29.132;-111.119,-28.824;-110.551,-28.603;-110.146,-28.480;-109.955,-28.296;-109.870,-28.116;-109.831,-27.988;-109.943,-28.021;-110.082,-28.019;-110.145,-27.903;-109.994,-27.999;-109.871,-28.374;-109.809,-28.832;-109.643,-28.754;-109.240,-28.672;-109.059,-28.789;-108.880,-29.031;-108.732,-29.337;-108.545,-29.562;-108.297,-29.718;-107.996,-29.800;-107.791,-29.888;-107.693,-29.987;-107.600,-30.067;-107.490,-30.040;-107.503,-30.189;-107.602,-30.308;-107.458,-30.222;-107.358,-30.206;-107.227,-30.238;-107.063,-30.285;-106.897,-30.382;-106.796,-30.473;-106.681,-30.505;-106.852,-30.638;-107.050,-30.727;-107.280,-30.807;-107.390,-30.835;-107.522,-30.900;-107.732,-30.994;-107.984,-31.126;-108.183,-31.220;-108.294,-31.287;-108.483,-31.394;-108.640,-31.471;-108.860,-31.592;-109.100,-31.724;-109.307,-31.840;-109.481,-31.915;-109.640,-31.959;-109.767,-32.008;-109.911,-32.072;-110.093,-32.137;-110.272,-32.213;-110.452,-32.318;-110.663,-32.371;-110.932,-32.548;-111.248,-32.772;-111.628,-33.002;-112.036,-33.226;-112.465,-33.439;-112.868,-33.644;-113.256,-33.833;-114.981,-31.630;-117.183,-30.084;-120.002,-29.318;-123.358,-29.117;-123.733,-29.214;-124.006,-29.306;-124.259,-29.345;-124.453,-29.335;-127.077,-28.924;-135.898,-26.760;-136.302,-26.931;-136.751,-27.149;-137.105,-27.365;-136.847,-26.907;-136.939,-27.068;-137.067,-27.121;-137.233,-27.159;-137.385,-27.161;-137.518,-27.197;-137.589,-27.115;-137.783,-27.031;-138.103,-27.047;-138.033,-26.949;-137.806,-26.824;-137.531,-26.644;-137.255,-26.524;-137.013,-26.464;-136.824,-26.434;-136.663,-26.411;-136.487,-26.369;-136.271,-26.276;-136.041,-26.143;-135.838,-26.042;-135.691,-25.950;-135.763,-25.857;-135.927,-25.825;-136.100,-25.821;-136.197,-25.784;-136.050,-25.737;-136.149,-25.619;-136.216,-25.536;-136.314,-25.584;-136.187,-25.542;-136.095,-25.488;-135.965,-25.472;-135.752,-25.473;-135.581,-25.525;-135.645,-25.708;-135.839,-25.851;-136.109,-26.001;-136.449,-26.178;-136.802,-26.336;-137.124,-26.437;-137.454,-26.549;-137.730,-26.665;-138.044,-26.784;-138.209,-26.794;-138.651,-26.878;-138.878,-26.895;-138.841,-26.800;-138.729,-26.631;-138.577,-26.473;-138.382,-26.310;-138.238,-26.176;-138.148,-26.061;-138.038,-26.021;-138.145,-26.035;-138.724,-26.011;-139.746,-25.880;-137.880,-26.205;-138.735,-26.226;-139.498,-26.303;-139.991,-26.334;-140.360,-26.346;-140.566,-26.301;-142.470,-25.570;-142.039,-25.377;-141.785,-25.267;-141.614,-25.166;-141.509,-25.122;-141.469,-24.948;-141.405,-24.832;-141.369,-24.727;-141.334,-24.614;-141.356,-24.503;-141.446,-24.445;-141.531,-24.596;-141.687,-24.602;-141.812,-24.610;-141.941,-24.603;-142.089,-24.612;-142.238,-24.660;-142.356,-24.733;-142.480,-24.772;-142.630,-24.798;-142.875,-24.856;-143.165,-24.888;-143.411,-24.918;-143.572,-24.986;-143.692,-25.077;-143.764,-25.173;-143.872,-25.178;-142.853,-25.945;-142.949,-25.819;-143.034,-25.645;-143.043,-25.380;-142.959,-25.059;-142.736,-24.805;-142.364,-24.551;-141.926,-24.286;-141.449,-24.104;-140.932,-23.887;-140.450,-23.617;-140.048,-23.308;-139.718,-23.031;-139.401,-22.861;-139.119,-22.790;-138.846,-22.778;-138.647,-22.817;-138.487,-22.859;-138.349,-22.905;-138.237,-22.944;-137.476,-22.963;-137.344,-23.087;-137.453,-23.039;-137.572,-22.977;-137.703,-22.971;-137.874,-22.961;-138.054,-22.948;-138.171,-22.948;-138.272,-22.918;-138.253,-23.024;-138.223,-23.158;-138.114,-23.118;-137.886,-23.077;-137.471,-22.950;-136.769,-22.799;-136.424,-22.719;-136.172,-22.662;-136.026,-22.640;-136.250,-22.700;-136.355,-22.716;-135.978,-22.703;-135.844,-22.697;-135.740,-22.658;-135.851,-22.673;-135.721,-22.646;-135.563,-22.596;-135.455,-22.594;-135.288,-22.585;-135.025,-22.558;-134.535,-22.676;-134.105,-22.802;-133.644,-22.828;-133.298,-22.856;-132.917,-22.765;-132.504,-22.586;-131.992,-22.498;-131.504,-22.452;-131.148,-22.388;-130.950,-22.343;-130.815,-22.320;-130.037,-22.363;-129.159,-22.106;-128.284,-22.267;-127.885,-22.088;-127.734,-21.770;-127.614,-21.492;-127.357,-21.459;-127.042,-21.434;-126.610,-21.436;-126.126,-21.441;-125.666,-21.454;-125.552,-21.433;-125.416,-21.401;-125.815,-21.472;-126.286,-21.558;-126.684,-21.626;-127.022,-21.699;-127.338,-21.753;-127.589,-21.798;-127.764,-21.830;-127.900,-21.838;-127.981,-21.758;-119.447,-22.703;-119.277,-22.756;-119.101,-22.854;-118.883,-23.017;-118.665,-23.177;-118.213,-23.350;-118.001,-23.333;-117.840,-23.345;-117.631,-23.321;-117.389,-23.304;-117.121,-23.298;-116.913,-23.278;-116.649,-23.269;-115.923,-23.323;-116.059,-23.393;-116.198,-23.436;-116.678,-23.704;-116.742,-23.813;-116.649,-23.857;-116.491,-23.926;-116.361,-23.953;-116.174,-23.939;-116.076,-23.886;-115.979,-23.816;-115.831,-23.781;-115.191,-23.296;-115.152,-23.194;-115.044,-23.035;-114.946,-23.003;-114.302,-22.378;-114.410,-22.260;-114.579,-22.282;-114.693,-22.305;-114.796,-22.336;-114.899,-22.367;-115.111,-22.585;-115.333,-22.688;-115.555,-22.749;-115.919,-22.831;-116.021,-22.857;-116.219,-23.118;-116.475,-23.306;-116.777,-23.328;-117.165,-23.333;-117.335,-23.330;-117.783,-23.275;-117.984,-23.232;-118.269,-23.224;-118.559,-23.195;-118.754,-23.149;-119.550,-23.021;-119.440,-22.985;-119.253,-22.789;-119.448,-22.872;-119.523,-22.967;-119.651,-23.136;-119.933,-23.429;-120.205,-23.516;-120.784,-23.498;-120.978,-23.490;-121.194,-23.465;-121.435,-22.650;-121.321,-22.675;-121.326,-22.792;-121.484,-22.864;-121.537,-22.982
 DK-001.mowingWidth=150
 DK-001.obstacleCoordinates=-3.74,-0.68;-102.65,-10.97;-181.14,-1.46;-58.05,14.38
 DK-001.obstacleOriginalCoordinates=3949.8407343,N,11616.7432457,E;3949.8351859,N,11616.6738272,E;3949.8403113,N,11616.6187363,E;3949.8488492,N,11616.7051305,E
diff --git a/image/speedslow.png b/image/speedslow.png
new file mode 100644
index 0000000..b1b2325
--- /dev/null
+++ b/image/speedslow.png
Binary files differ
diff --git a/lib/slf4j-api-1.7.30.jar b/lib/slf4j-api-1.7.30.jar
new file mode 100644
index 0000000..29ac26f
--- /dev/null
+++ b/lib/slf4j-api-1.7.30.jar
Binary files differ
diff --git a/lib/slf4j-simple-1.7.30.jar b/lib/slf4j-simple-1.7.30.jar
new file mode 100644
index 0000000..6debaa9
--- /dev/null
+++ b/lib/slf4j-simple-1.7.30.jar
Binary files differ
diff --git a/set.properties b/set.properties
index 3eb7238..17bc780 100644
--- a/set.properties
+++ b/set.properties
@@ -1,8 +1,11 @@
-#Mower Configuration Properties - Updated
-#Tue Dec 02 15:25:38 CST 2025
+#Serial Port Preferences Updated
+#Thu Dec 04 18:56:23 CST 2025
 appVersion=-1
 cuttingWidth=200
 firmwareVersion=-1
 handheldMarkerId=2354
-mowerId=7785
+mowerId=7468
+serialAutoConnect=true
+serialBaudRate=921600
+serialPortName=COM15
 simCardNumber=-1
diff --git a/src/chuankou/Sendmsg.java b/src/chuankou/Sendmsg.java
new file mode 100644
index 0000000..50f76d3
--- /dev/null
+++ b/src/chuankou/Sendmsg.java
@@ -0,0 +1,138 @@
+package chuankou;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+import javax.swing.SwingWorker;
+
+
+/**
+ * 涓插彛娑堟伅鍙戦�佸伐鍏风被
+ * 鎻愪緵楂樻�ц兘鐨勪覆鍙f秷鎭彂閫佸姛鑳斤紝閫傚悎楂橀璋冪敤
+ * 浼樺寲鍐呭瓨浣跨敤锛岄伩鍏嶉暱鏃堕棿杩愯鍐呭瓨娉勬紡
+ */
+public class Sendmsg {
+	// 闈欐�佷覆鍙f湇鍔″疄渚�
+	private static volatile SerialPortService serialService = null;
+	private static final AtomicBoolean isPortOpen = new AtomicBoolean(false);
+
+	// 鏀逛负闈瀎inal锛屾敮鎸佸姩鎬佹帶鍒�
+	private static volatile boolean DEBUG_MODE = false;
+
+	// 浣跨敤ThreadLocal淇濊瘉SimpleDateFormat绾跨▼瀹夊叏
+	private static final ThreadLocal<SimpleDateFormat> TIME_FORMATTER = 
+			ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss.SSS"));
+
+	// 缂撳瓨瀛楃涓叉瀯寤哄櫒锛屽噺灏戝璞″垱寤�
+	private static final ThreadLocal<StringBuilder> STRING_BUILDER_CACHE = 
+			ThreadLocal.withInitial(() -> new StringBuilder(128));
+
+	// 璁板綍娲昏穬鐨凷wingWorker锛屼究浜庣鐞�
+	private static final ConcurrentHashMap<String, SwingWorker<?, ?>> ACTIVE_WORKERS = 
+			new ConcurrentHashMap<>();
+
+
+	
+
+	/**
+	 * HEX瀛楃涓茶浆瀛楄妭鏁扮粍
+	 * @param s HEX瀛楃涓�
+	 * @return 瀛楄妭鏁扮粍
+	 */
+	private static byte[] hexStringToByteArray(String s) {
+		if (s == null || s.isEmpty()) {
+			return new byte[0];
+		}
+
+		s = s.replaceAll("\\s+", ""); // 绉婚櫎绌烘牸
+		int len = s.length();
+		if (len % 2 != 0) {
+			throw new IllegalArgumentException("HEX瀛楃涓查暱搴﹀繀椤讳负鍋舵暟锛屽綋鍓嶉暱搴�: " + len + ", 鏁版嵁: " + s);
+		}
+
+		byte[] data = new byte[len / 2];
+		for (int i = 0; i < len; i += 2) {
+			int high = Character.digit(s.charAt(i), 16);
+			int low = Character.digit(s.charAt(i + 1), 16);
+
+			if (high == -1 || low == -1) {
+				throw new IllegalArgumentException("鏃犳晥鐨凥EX瀛楃: '" + s.charAt(i) + s.charAt(i + 1) + "' 鍦ㄤ綅缃� " + i + "-" + (i+1) + ", 瀹屾暣鏁版嵁: " + s);
+			}
+
+			data[i / 2] = (byte) ((high << 4) + low);
+		}
+		return data;
+	}
+
+	/**
+	 * 瀛楄妭鏁扮粍杞琀EX瀛楃涓�
+	 * @param bytes 瀛楄妭鏁扮粍
+	 * @return HEX瀛楃涓�
+	 */
+	public static String bytesToHex(byte[] bytes) {
+		if (bytes == null || bytes.length == 0) {
+			return "";
+		}
+
+		StringBuilder sb = STRING_BUILDER_CACHE.get();
+		sb.setLength(0);
+		for (byte b : bytes) {
+			sb.append(String.format("%02x", b));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 鑾峰彇褰撳墠鏃堕棿瀛楃涓�
+	 * @return 鏃堕棿瀛楃涓�
+	 */
+	private static String getCurrentTime() {
+		return TIME_FORMATTER.get().format(new Date());
+	}
+
+	/**
+	 * 鍚敤璋冭瘯妯″紡
+	 */
+	public static void enableDebugMode() {
+		DEBUG_MODE = true;
+		System.out.println("[" + getCurrentTime() + "] Sendmsg璋冭瘯妯″紡宸插惎鐢�");
+	}
+
+	/**
+	 * 绂佺敤璋冭瘯妯″紡
+	 */
+	public static void disableDebugMode() {
+		DEBUG_MODE = false;
+		System.out.println("[" + getCurrentTime() + "] Sendmsg璋冭瘯妯″紡宸茬鐢�");
+	}
+
+	/**
+	 * 璁剧疆璋冭瘯妯″紡
+	 */
+	public static void setDebugMode(boolean debug) {
+		DEBUG_MODE = debug;
+		System.out.println("[" + getCurrentTime() + "] Sendmsg璋冭瘯妯″紡: " + (debug ? "鍚敤" : "绂佺敤"));
+	}
+
+	/**
+	 * 娓呯悊璧勬簮锛岄槻姝㈠唴瀛樻硠婕�
+	 */
+	public static void cleanup() {
+		
+
+		// 娓呯悊ThreadLocal璧勬簮
+		TIME_FORMATTER.remove();
+		STRING_BUILDER_CACHE.remove();
+
+		if (DEBUG_MODE) {
+			System.out.println("[" + getCurrentTime() + "] Sendmsg璧勬簮娓呯悊瀹屾垚");
+		}
+	}
+
+	/**
+	 * 鑾峰彇娲昏穬浠诲姟鏁伴噺
+	 */
+	public static int getActiveTaskCount() {
+		return ACTIVE_WORKERS.size();
+	}
+}
\ No newline at end of file
diff --git a/src/chuankou/SerialDataReceiver.java b/src/chuankou/SerialDataReceiver.java
new file mode 100644
index 0000000..f55892d
--- /dev/null
+++ b/src/chuankou/SerialDataReceiver.java
@@ -0,0 +1,218 @@
+package chuankou;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SerialDataReceiver {
+    private static final int BUFFER_SIZE = 1024;
+    private static final int MIN_PACKET_LENGTH = 9;
+    private static final byte[] START_MARKER = {(byte) 0xDD, (byte) 0xCC};
+    
+    // 浣跨敤闈為潤鎬佹垚鍛橀伩鍏嶅绾跨▼鐜涓嬬殑绔炰簤鏉′欢
+    private byte[] dataBuffer = new byte[BUFFER_SIZE];
+    private int bufferPosition = 0;
+    private final List<byte[]> reusablePackets = new ArrayList<>();
+    
+    /**
+     * 瀹炰緥鏂规硶锛氭帴鏀朵覆鍙e師濮嬫暟鎹苟瑙f瀽瀹屾暣鏁版嵁鍖�
+     * @param rawData 鍘熷鏁版嵁
+     * @param debugEnabled 鏄惁鍚敤璋冭瘯
+     * @param maxRawDataPrintLength 鏈�澶ф墦鍗伴暱搴�
+     * @return 瑙f瀽鍑虹殑瀹屾暣鏁版嵁鍖呭垪琛紝濡傛灉娌℃湁瀹屾暣鍖呭垯杩斿洖绌哄垪琛�
+     */
+    public List<byte[]> receiveData(byte[] rawData, boolean debugEnabled, int maxRawDataPrintLength) {
+        reusablePackets.clear();
+        
+        if (rawData == null || rawData.length == 0) {
+            return reusablePackets;
+        }
+        
+        // 鎵撳嵃鍘熷鎺ユ敹鏁版嵁锛堣皟璇曠敤锛�
+        if (debugEnabled) {
+            printRawData("鏀跺埌涓插彛鍘熷鏁版嵁", rawData, maxRawDataPrintLength);
+        }
+        
+        // 妫�鏌ョ紦鍐插尯瀹归噺锛屽姩鎬佸鐞�
+        if (!ensureBufferCapacity(rawData.length)) {
+            // 缂撳啿鍖轰笉瓒虫椂锛屾竻鐞嗗苟閲嶆柊寮�濮�
+            if (debugEnabled) {
+                System.out.println("缂撳啿鍖轰笉瓒筹紝娓呯┖缂撳啿鍖洪噸鏂板紑濮�");
+            }
+            bufferPosition = 0;
+        }
+        
+        // 灏嗘暟鎹坊鍔犲埌缂撳啿鍖�
+        System.arraycopy(rawData, 0, dataBuffer, bufferPosition, rawData.length);
+        bufferPosition += rawData.length;
+        
+        // 澶勭悊缂撳啿鍖轰腑鐨勬暟鎹苟鏀堕泦瀹屾暣鍖�
+        processBuffer(reusablePackets, debugEnabled);
+        
+        return new ArrayList<>(reusablePackets);
+    }
+    
+    /**
+     * 纭繚缂撳啿鍖烘湁瓒冲瀹归噺锛屽涓嶅鍒欏皾璇曞帇缂�
+     */
+    private boolean ensureBufferCapacity(int required) {
+        if (bufferPosition + required <= dataBuffer.length) {
+            return true;
+        }
+        
+        // 灏濊瘯閫氳繃鍘嬬缉缂撳啿鍖烘潵鑵惧嚭绌洪棿
+        int startIndex = findStartMarker();
+        if (startIndex > 0) {
+            compactBuffer(startIndex);
+            return bufferPosition + required <= dataBuffer.length;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * 澶勭悊缂撳啿鍖轰腑鐨勬暟鎹紝瑙f瀽瀹屾暣鏁版嵁鍖�
+     */
+    private void processBuffer(List<byte[]> completePackets, boolean debugEnabled) {
+        while (bufferPosition >= MIN_PACKET_LENGTH) {
+            // 鏌ユ壘璧峰鏍囪
+            int startIndex = findStartMarker();
+            if (startIndex == -1) {
+                // 娌℃湁鎵惧埌璧峰鏍囪锛屾竻绌烘棤鏁堟暟鎹�
+                if (debugEnabled) {
+                    System.out.println("鏈壘鍒拌捣濮嬫爣璁帮紝娓呯┖缂撳啿鍖�");
+                }
+                bufferPosition = 0;
+                return;
+            }
+            
+            // 妫�鏌ユ槸鍚︽湁瓒冲鐨勬暟鎹鍙栨暟鎹暱搴�
+            if (startIndex + 4 > bufferPosition) {
+                // 鏁版嵁涓嶈冻锛岀瓑寰呮洿澶氭暟鎹�
+                compactBuffer(startIndex);
+                return;
+            }
+            
+            // 璇诲彇鏁版嵁闀垮害 (澶х搴�)
+            int dataLength = ((dataBuffer[startIndex + 2] & 0xFF) << 8) | 
+                           (dataBuffer[startIndex + 3] & 0xFF);
+            int totalPacketLength = 2 + 2 + dataLength + 2; // 璧峰鏍囪2 + 鏁版嵁闀垮害2 + 鏁版嵁鍐呭 + CRC2
+            
+            // 妫�鏌ユ暟鎹暱搴︽湁鏁堟��
+            if (dataLength < 0 || totalPacketLength > BUFFER_SIZE) {
+                if (debugEnabled) {
+                    System.out.println("鏃犳晥鏁版嵁闀垮害: " + dataLength + ", 璺宠繃璧峰瀛楄妭");
+                }
+                // 璺宠繃閿欒鐨勮捣濮嬫爣璁帮紝缁х画鏌ユ壘
+                compactBuffer(startIndex + 1);
+                continue;
+            }
+            
+            // 妫�鏌ユ槸鍚︽敹鍒板畬鏁存暟鎹寘
+            if (startIndex + totalPacketLength > bufferPosition) {
+                // 鏁版嵁鍖呬笉瀹屾暣锛岀瓑寰呮洿澶氭暟鎹�
+                compactBuffer(startIndex);
+                return;
+            }
+            
+            // 鎻愬彇瀹屾暣鏁版嵁鍖�
+            byte[] packet = Arrays.copyOfRange(dataBuffer, startIndex, startIndex + totalPacketLength);
+            
+            if (debugEnabled) {
+                System.out.println("瑙f瀽鍒板畬鏁存暟鎹寘: " + bytesToHex(packet));
+            }
+            
+            // 娣诲姞鍒拌繑鍥炲垪琛�
+            completePackets.add(packet);
+            
+            // 绉诲姩缂撳啿鍖轰綅缃�
+            int remaining = bufferPosition - (startIndex + totalPacketLength);
+            if (remaining > 0) {
+                System.arraycopy(dataBuffer, startIndex + totalPacketLength, 
+                               dataBuffer, 0, remaining);
+            }
+            bufferPosition = remaining;
+        }
+    }
+    
+    /**
+     * 鏌ユ壘璧峰鏍囪浣嶇疆
+     */
+    private int findStartMarker() {
+        for (int i = 0; i <= bufferPosition - START_MARKER.length; i++) {
+            if (dataBuffer[i] == START_MARKER[0] && dataBuffer[i + 1] == START_MARKER[1]) {
+                return i;
+            }
+        }
+        return -1;
+    }
+    
+    /**
+     * 鍘嬬缉缂撳啿鍖猴紝灏嗘湁鏁堟暟鎹Щ鍒板紑澶�
+     */
+    private void compactBuffer(int startIndex) {
+        if (startIndex > 0 && startIndex < bufferPosition) {
+            System.arraycopy(dataBuffer, startIndex, dataBuffer, 0, 
+                           bufferPosition - startIndex);
+            bufferPosition -= startIndex;
+        }
+    }
+    
+    /**
+     * 鎵撳嵃鍘熷鏁版嵁锛堣皟璇曠敤锛�
+     */
+    private void printRawData(String prefix, byte[] data, int maxPrintLength) {
+        if (data == null || data.length == 0) {
+            System.out.println(prefix + ": 绌烘暟鎹�");
+            return;
+        }
+        
+        StringBuilder sb = new StringBuilder();
+        sb.append(prefix).append(" [闀垮害: ").append(data.length).append("]: ");
+        
+        int printLength = Math.min(data.length, maxPrintLength);
+        for (int i = 0; i < printLength; i++) {
+            sb.append(String.format("%02X ", data[i]));
+        }
+        
+        if (data.length > maxPrintLength) {
+            sb.append("... [鎴柇锛屾�婚暱搴�: ").append(data.length).append("]");
+        }
+        
+        System.out.println(sb.toString());
+    }
+    
+    /**
+     * 宸ュ叿鏂规硶锛氬瓧鑺傛暟缁勮浆鍗佸叚杩涘埗瀛楃涓�
+     */
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder();
+        for (byte b : bytes) {
+            sb.append(String.format("%02X ", b));
+        }
+        return sb.toString().trim();
+    }
+    
+    /**
+     * 娓呯┖缂撳啿鍖猴紙閬垮厤鍐呭瓨娉勬紡锛�
+     */
+    public void clearBuffer() {
+        bufferPosition = 0;
+        // 鍙�夛細娓呯┖缂撳啿鍖哄唴瀹�
+        Arrays.fill(dataBuffer, (byte) 0);
+    }
+    
+    /**
+     * 鑾峰彇褰撳墠缂撳啿鍖虹姸鎬�
+     */
+    public int getBufferStatus() {
+        return bufferPosition;
+    }
+    
+    /**
+     * 鑾峰彇缂撳啿鍖哄閲�
+     */
+    public int getBufferCapacity() {
+        return dataBuffer.length;
+    }
+}
\ No newline at end of file
diff --git a/src/chuankou/SerialPortAutoConnector.java b/src/chuankou/SerialPortAutoConnector.java
new file mode 100644
index 0000000..7acb7b8
--- /dev/null
+++ b/src/chuankou/SerialPortAutoConnector.java
@@ -0,0 +1,38 @@
+package chuankou;
+
+/**
+ * 鏍规嵁鎸佷箙鍖栭厤缃湪绋嬪簭鍚姩鏃惰嚜鍔ㄨ繛鎺ヤ覆鍙c��
+ */
+public final class SerialPortAutoConnector {
+    private SerialPortAutoConnector() {
+    }
+
+    public static void initialize() {
+        if (!SerialPortPreferences.isAutoConnectEnabled()) {
+            return;
+        }
+
+        String portName = SerialPortPreferences.getPortName();
+        if (portName == null || portName.isEmpty()) {
+            return;
+        }
+
+        int baudRate = SerialPortPreferences.getBaudRate();
+        SerialPortService service = sendmessage.getActiveService();
+
+        if (service.isOpen()) {
+            service.ensureCaptureRunning();
+            service.setPaused(false);
+            return;
+        }
+
+        boolean opened = service.open(portName, baudRate);
+        if (opened) {
+            service.ensureCaptureRunning();
+            service.setPaused(false);
+            System.out.println("涓插彛鑷姩杩炴帴鎴愬姛: " + portName + " @ " + baudRate);
+        } else {
+            System.err.println("涓插彛鑷姩杩炴帴澶辫触: " + portName + " @ " + baudRate);
+        }
+    }
+}
diff --git a/src/chuankou/SerialPortPreferences.java b/src/chuankou/SerialPortPreferences.java
new file mode 100644
index 0000000..d93bfea
--- /dev/null
+++ b/src/chuankou/SerialPortPreferences.java
@@ -0,0 +1,101 @@
+package chuankou;
+
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+/**
+ * 璐熻矗灏嗕覆鍙h皟璇曠浉鍏抽厤缃寔涔呭寲鍒� set.properties 鏂囦欢銆�
+ */
+public final class SerialPortPreferences {
+    private static final String PROPERTIES_FILE = "set.properties";
+    private static final String KEY_PORT = "serialPortName";
+    private static final String KEY_BAUD = "serialBaudRate";
+    private static final String KEY_AUTO = "serialAutoConnect";
+    private static final int DEFAULT_BAUD = 115200;
+
+    private SerialPortPreferences() {
+    }
+
+    public static String getPortName() {
+        return readProperty(KEY_PORT);
+    }
+
+    public static void setPortName(String portName) {
+        writeProperty(KEY_PORT, sanitizeString(portName));
+    }
+
+    public static int getBaudRate() {
+        String value = readProperty(KEY_BAUD);
+        if (value == null) {
+            return DEFAULT_BAUD;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            return DEFAULT_BAUD;
+        }
+    }
+
+    public static void setBaudRate(int baudRate) {
+        writeProperty(KEY_BAUD, String.valueOf(baudRate));
+    }
+
+    public static boolean isAutoConnectEnabled() {
+        String value = readProperty(KEY_AUTO);
+        if (value == null) {
+            return true; // 榛樿寮�鍚�
+        }
+        return Boolean.parseBoolean(value.trim());
+    }
+
+    public static void setAutoConnectEnabled(boolean enabled) {
+        writeProperty(KEY_AUTO, Boolean.toString(enabled));
+    }
+
+    private static String readProperty(String key) {
+        Properties props = new Properties();
+        try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
+            props.load(in);
+            String value = props.getProperty(key);
+            if (value == null || value.trim().isEmpty() || "-1".equals(value.trim())) {
+                return null;
+            }
+            return value.trim();
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+    private static void writeProperty(String key, String value) {
+        synchronized (SerialPortPreferences.class) {
+            Properties props = new Properties();
+            try (FileInputStream in = new FileInputStream(PROPERTIES_FILE)) {
+                props.load(in);
+            } catch (IOException ignored) {
+                // 鏂囦欢涓嶅瓨鍦ㄦ椂浣跨敤绌洪厤缃�
+            }
+
+            if (value == null || value.trim().isEmpty()) {
+                props.setProperty(key, "-1");
+            } else {
+                props.setProperty(key, value.trim());
+            }
+
+            try (FileOutputStream out = new FileOutputStream(PROPERTIES_FILE)) {
+                props.store(out, "Serial Port Preferences Updated");
+            } catch (IOException ex) {
+                System.err.println("淇濆瓨涓插彛閰嶇疆澶辫触: " + ex.getMessage());
+            }
+        }
+    }
+
+    private static String sanitizeString(String value) {
+        if (value == null) {
+            return null;
+        }
+        String trimmed = value.trim();
+        return trimmed.isEmpty() ? null : trimmed;
+    }
+}
diff --git a/src/chuankou/SerialPortService.java b/src/chuankou/SerialPortService.java
new file mode 100644
index 0000000..f1c7aa7
--- /dev/null
+++ b/src/chuankou/SerialPortService.java
@@ -0,0 +1,262 @@
+package chuankou;
+
+import com.fazecast.jSerialComm.SerialPort;
+import java.util.function.Consumer;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class SerialPortService {
+
+	private SerialPort port;
+	private volatile boolean capturing = false;
+	private volatile boolean paused = true;
+	private Thread readerThread;
+	private Consumer<byte[]> responseConsumer;
+
+	// 浼樺寲锛氶噸鐢ㄧ紦鍐插尯锛屽噺灏戝唴瀛樺垎閰�
+	private byte[] readBuffer = new byte[200];
+	private ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
+	private Consumer<byte[]> dataReceivedCallback;
+
+
+	// 鏂板锛氭暟鎹潯鏁拌鏁板櫒
+	public static  int receivedDataCount = 0;
+
+	// 鍏朵粬鐜版湁鏂规硶淇濇寔涓嶅彉...
+
+	/**
+	 * 鑾峰彇涓插彛鎺ユ敹鐨勬暟鎹潯鏁�
+	 * 褰撴潯鏁拌秴杩�1涓囨椂鑷姩浠�1寮�濮嬮噸鏂拌鏁�
+	 * @return 鏁版嵁鏉℃暟瀛楃涓�
+	 */
+	public static String getReceivedDataCount() {
+		receivedDataCount++;
+		if (receivedDataCount > 10000) {
+			receivedDataCount = 1;
+		}
+		return String.valueOf(receivedDataCount);
+	}
+
+	public static void setReceivedDataCount(int receivedDataCount) {
+		SerialPortService.receivedDataCount = receivedDataCount;
+	}
+
+
+	/**
+	 * 閲嶇疆鏁版嵁鏉℃暟璁℃暟鍣�
+	 */
+	public void resetReceivedDataCount() {
+		receivedDataCount = 0;
+	}
+
+	// 浠ヤ笅涓哄師鏈変唬鐮侊紝淇濇寔涓嶅彉...
+	public InputStream getInputStream() {
+		if (port != null && port.isOpen()) {
+			return port.getInputStream();
+		}
+		return null;
+	}
+
+	public OutputStream getOutputStream() {
+		if (port != null && port.isOpen()) {
+			return port.getOutputStream();
+		}
+		return null;
+	}
+
+	public void setComPortTimeouts(int timeoutMode, int readTimeout, int writeTimeout) {
+		if (port != null && port.isOpen()) {
+			port.setComPortTimeouts(timeoutMode, readTimeout, writeTimeout);
+		}
+	}
+
+	
+
+	/**
+	 * 鍚敤璋冭瘯杈撳嚭锛屽皢鎺ユ敹鍒扮殑鏁版嵁鎵撳嵃鍒版帶鍒跺彴
+	 */
+	public void enableDebugOutput() {
+		//System.out.println("涓插彛璋冭瘯杈撳嚭宸插惎鐢� - 寮�濮嬬洃鍚覆鍙f暟鎹�...");
+	}
+
+	/**
+	 * 鑾峰彇褰撳墠璋冭瘯鐘舵��
+	 */
+	public boolean isDebugEnabled() {
+		return capturing;
+	}
+
+	public void startCapture() {
+		if (dataReceivedCallback != null) {
+			startCapture(dataReceivedCallback);
+		} else {
+			System.err.println("No data received callback set. Please call startCapture(Consumer<byte[]> onReceived) first.");
+		}
+	}
+
+	/**
+	 * 鎵撳紑涓插彛
+	 */
+	public boolean open(String portName, int baud) {
+		if (port != null && port.isOpen()) {
+			return true;
+		}
+
+		port = SerialPort.getCommPort(portName);
+		port.setComPortParameters(baud, 8, 1, SerialPort.NO_PARITY);
+		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;
+	}
+
+	/**
+	 * 鍚姩鏁版嵁鎺ユ敹绾跨▼
+	 */
+	public void startCapture(Consumer<byte[]> onReceived) {
+	    this.dataReceivedCallback = onReceived;
+	    if (capturing || port == null || !port.isOpen()) return;
+	    capturing = true;
+	    paused = false;
+
+	    readerThread = new Thread(() -> {
+	        buffer.reset();
+	        long lastReceivedTime = 0;
+
+	        while (capturing && port.isOpen()) {
+	            long currentTime = System.currentTimeMillis();
+
+	            if (buffer.size() > 0 && (currentTime - lastReceivedTime) >= 20) {
+	                byte[] data = buffer.toByteArray();	                
+
+	                dellmessage.handleIncomingBytes(data);
+	                // 纭繚鏁版嵁鍥炶皟濮嬬粓鎵ц锛屼笉鍙楁殏鍋滅姸鎬佸奖鍝�
+	                if (dataReceivedCallback != null && !paused) {
+	                    dataReceivedCallback.accept(data);
+	                }
+	                if (responseConsumer != null && !paused) {
+	                    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;
+//	                System.out.println("鏀跺埌鍘熷鏁版嵁: " + bytesToHex(readBuffer, len)+"鏃堕棿"+TimestampUtil.getTimestamp());
+	            }
+
+	            if (len <= 0 && buffer.size() == 0) {
+	                try { Thread.sleep(1); } catch (InterruptedException ignore) {}
+	            }
+	        }
+
+	        if (buffer.size() > 0) {
+	            byte[] data = buffer.toByteArray();              
+	            
+
+	           
+
+	            // 纭繚鏁版嵁鍥炶皟濮嬬粓鎵ц锛屼笉鍙楁殏鍋滅姸鎬佸奖鍝�
+	            dellmessage.handleIncomingBytes(data);
+	            if (dataReceivedCallback != null && !paused) {
+	                dataReceivedCallback.accept(data);
+	            }
+	            if (responseConsumer != null && !paused) {
+	                responseConsumer.accept(data);
+	            }
+	        }
+	    });
+	    readerThread.setDaemon(true);
+	    readerThread.start();
+	}
+	// 鏂板锛氳缃殏鍋滅姸鎬佷絾涓嶅奖鍝嶅崗璁В鏋愬櫒
+	public void setPaused(boolean paused) {
+	    this.paused = paused;
+	    // 娉ㄦ剰锛氫笉鍋滄鍗忚瑙f瀽鍣紝鍙殏鍋淯I鍥炶皟
+	}
+
+	// 鏂板锛氬崟鐙仠姝㈡暟鎹崟鑾疯�屼笉褰卞搷鍗忚瑙f瀽鍣�
+	public void stopDataCaptureOnly() {
+	    // 鍙仠姝㈡暟鎹洖璋冿紝涓嶅奖鍝嶅崗璁В鏋愬櫒
+	    this.dataReceivedCallback = null;
+	    this.responseConsumer = null;
+	}
+	/**
+	 * 鍋滄鏁版嵁鎺ユ敹绾跨▼
+	 */
+	public void stopCapture() {
+		capturing = false;
+		if (readerThread != null) {
+			try { readerThread.join(500); } catch (InterruptedException ignore) {}
+			readerThread = null;
+		}
+	}
+
+
+	public boolean isPaused() {
+		return paused;
+	}
+
+	public boolean isCapturing() {
+		return capturing;
+	}
+
+	public void ensureCaptureRunning() {
+		if (!capturing) {
+			startCapture(null);
+		}
+	}
+
+	public boolean isOpen() {
+		return port != null && port.isOpen();
+	}
+	/**
+	 * 鍙戦�佹暟鎹紙浼樺寲鐗堟湰锛�
+	 */
+	public boolean send(byte[] data) {
+		if (!isOpen()) {
+			return false;
+		}
+
+		// 娣诲姞鍙戦�佸墠鐨勪覆鍙g姸鎬佹鏌�
+		if (port == null || !port.isOpen()) {
+			return false;
+		}
+
+		try {
+			// 娣诲姞灏忓欢杩燂紝閬垮厤杩炵画鍙戦��
+			Thread.sleep(2);
+		} catch (InterruptedException e) {
+			Thread.currentThread().interrupt();
+			return false;
+		}
+
+		int result = port.writeBytes(data, data.length);
+		return result > 0;
+	}
+	private String bytesToHex(byte[] bytes, int length) {
+	    StringBuilder sb = new StringBuilder();
+	    for (int i = 0; i < length; i++) {
+	        sb.append(String.format("%02X ", bytes[i]));
+	    }
+	    return sb.toString().trim();
+	}
+}
\ No newline at end of file
diff --git a/src/chuankou/TimestampUtil.java b/src/chuankou/TimestampUtil.java
new file mode 100644
index 0000000..f1fa099
--- /dev/null
+++ b/src/chuankou/TimestampUtil.java
@@ -0,0 +1,16 @@
+package chuankou;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class TimestampUtil {
+    /**
+     * 鑾峰彇骞存湀鏃ユ椂鍒嗙姣鐨勬椂闂存埑
+     * @return 鏃堕棿鎴冲瓧绗︿覆
+     */
+    public static String getTimestamp() {
+        LocalDateTime now = LocalDateTime.now();
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");
+        return now.format(formatter);
+    }
+
+}
\ No newline at end of file
diff --git a/src/chuankou/dellmessage.java b/src/chuankou/dellmessage.java
new file mode 100644
index 0000000..0469572
--- /dev/null
+++ b/src/chuankou/dellmessage.java
@@ -0,0 +1,245 @@
+package chuankou;
+
+import udpdell.UDPServer;
+import java.nio.charset.StandardCharsets;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+/**
+ * 涓插彛瀹炴椂鏁版嵁璋冨害涓績銆�
+ * <p>
+ * 璐熻矗鎺ユ敹 {@link SerialPortService} 鎹曡幏鐨勫師濮嬪瓧鑺傛祦銆�
+ * 鑱氬悎涓哄畬鏁寸殑鏂囨湰琛屽苟鍚戝凡娉ㄥ唽鐨勭洃鍚櫒鍒嗗彂锛屽悓鏃舵彁渚涘
+ * GNGGA 鏁版嵁鐨勫唴缃В鏋愭敮鎸侊紝澶嶇敤 UDP 澶勭悊閫昏緫銆�
+ */
+public final class dellmessage {
+    private static final CopyOnWriteArrayList<Consumer<byte[]>> RAW_CONSUMERS = new CopyOnWriteArrayList<>();
+    private static final CopyOnWriteArrayList<Consumer<String>> LINE_CONSUMERS = new CopyOnWriteArrayList<>();
+    private static final StringBuilder LINE_BUFFER = new StringBuilder(512);
+    private static final AtomicInteger LINE_COUNTER = new AtomicInteger(0);
+    private static final AtomicReference<String> LAST_LINE = new AtomicReference<>("");
+
+    private dellmessage() {
+        // utility
+    }
+
+    /**
+     * 娉ㄥ唽鍘熷瀛楄妭鐩戝惉鍣ㄣ��
+     *
+     * @param consumer 鎺ユ敹瀹屾暣鏁版嵁甯х殑鐩戝惉鍣�
+     */
+    public static void registerRawListener(Consumer<byte[]> consumer) {
+        // 鐢ㄦ硶锛氬湪闇�瑕佺洿鎺ュ鐞嗗師濮嬩覆鍙e瓧鑺傛祦鐨勬ā鍧楀惎鍔ㄦ椂璋冪敤锛屼紶鍏ュ洖璋冨鐞嗘暟鎹抚銆�
+        if (consumer != null) {
+            RAW_CONSUMERS.addIfAbsent(consumer);
+        }
+    }
+
+    /**
+     * 娉ㄩ攢鍘熷瀛楄妭鐩戝惉鍣ㄣ��
+     */
+    public static void unregisterRawListener(Consumer<byte[]> consumer) {
+        // 鐢ㄦ硶锛氭ā鍧楅攢姣佹垨涓嶅啀闇�瑕佹帴鏀跺師濮嬫暟鎹椂璋冪敤锛岄伩鍏嶅唴瀛樻硠婕忋��
+        if (consumer != null) {
+            RAW_CONSUMERS.remove(consumer);
+        }
+    }
+
+    /**
+     * 娉ㄥ唽鏂囨湰琛岀洃鍚櫒銆�
+     * <p>
+     * 姣忎竴鏉$粡鐢辨崲琛岀鎴柇鐨勫畬鏁存枃鏈灏嗚Е鍙戜竴娆″洖璋冦��
+     */
+    public static void registerLineListener(Consumer<String> consumer) {
+        // 鐢ㄦ硶锛氶渶瑕佹寜琛岃鍙栦覆鍙f枃鏈紙濡� NMEA 鎶ユ枃锛夋椂璋冪敤锛屽洖璋冩嬁鍒板畬鏁存枃鏈銆�
+        if (consumer != null) {
+            LINE_CONSUMERS.addIfAbsent(consumer);
+        }
+    }
+
+    /**
+     * 娉ㄩ攢鏂囨湰琛岀洃鍚櫒銆�
+     */
+    public static void unregisterLineListener(Consumer<String> consumer) {
+        // 鐢ㄦ硶锛氬搴� registerLineListener 鐨勫弽娉ㄥ唽鎿嶄綔锛岄�氬父鍦ㄧ獥鍙e叧闂垨鏈嶅姟鍋滄鏃惰皟鐢ㄣ��
+        if (consumer != null) {
+            LINE_CONSUMERS.remove(consumer);
+        }
+    }
+
+    /**
+     * 涓插彛鎹曡幏绾跨▼鏀跺埌鏁版嵁鍚庤皟鐢ㄦ鏂规硶銆�
+     *
+     * @param data 瀹屾暣鐨勪覆鍙f暟鎹抚
+     */
+    public static void handleIncomingBytes(byte[] data) {
+        // 鐢ㄦ硶锛氱敱涓插彛璇诲彇绾跨▼锛圫erialPortService锛夊湪鑾峰彇瀹屾暣鏁版嵁甯у悗鐩存帴璋冪敤銆�
+        if (data == null || data.length == 0) {
+            return;
+        }
+
+        notifyRawConsumers(data);
+
+        String ascii = new String(data, StandardCharsets.UTF_8);
+        if (ascii.isEmpty()) {
+            return;
+        }
+
+        synchronized (LINE_BUFFER) {
+            LINE_BUFFER.append(ascii);
+            String line;
+            while ((line = pollNextLine()) != null) {
+                dispatchLine(line);
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇褰撳墠绱澶勭悊鐨勬枃鏈鏁伴噺銆�
+     */
+    public static int getProcessedLineCount() {
+        // 鐢ㄦ硶锛氱敤浜庢樉绀烘垨鐩戞帶褰撳墠宸插鐞嗘枃鏈鐨勬暟閲忥紝渚嬪璋冭瘯鐣岄潰缁熻淇℃伅銆�
+        return LINE_COUNTER.get();
+    }
+
+    /**
+     * 鑾峰彇鏈�杩戜竴娆¤В鏋愬嚭鐨勫畬鏁磋銆�
+     */
+    public static String getLastLine() {
+        // 鐢ㄦ硶锛氫究鎹疯幏鍙栨渶杩戜竴娆¤В鏋愮殑瀹屾暣鏂囨湰琛岋紝鍙敤浜� UI 鏄剧ず鎴栬皟璇曡褰曘��
+        return LAST_LINE.get();
+    }
+
+    /**
+     * 娓呯┖鍐呴儴缂撳瓨銆�
+     */
+    public static void clearBuffer() {
+        // 鐢ㄦ硶锛氬湪闇�瑕侀噸缃В鏋愮姸鎬佹椂璋冪敤锛屼緥濡傛柇寮�涓插彛鎴栭噸鏂拌繛鎺ュ墠娓呴櫎缂撳瓨銆�
+        synchronized (LINE_BUFFER) {
+            LINE_BUFFER.setLength(0);
+        }
+        LAST_LINE.set("");
+        LINE_COUNTER.set(0);
+    }
+
+    private static void notifyRawConsumers(byte[] data) {
+        for (Consumer<byte[]> consumer : RAW_CONSUMERS) {
+            try {
+                consumer.accept(data);
+            } catch (Exception ex) {
+                logConsumerFailure(ex);
+            }
+        }
+    }
+
+    private static String pollNextLine() {
+        while (true) {
+            int length = LINE_BUFFER.length();
+            if (length == 0) {
+                return null;
+            }
+
+            int lineEnd = findLineBreakIndex();
+            if (lineEnd >= 0) {
+                String line = LINE_BUFFER.substring(0, lineEnd);
+                int skip = calculateSkipLength(lineEnd, length);
+                LINE_BUFFER.delete(0, lineEnd + skip);
+                return line;
+            }
+
+            int firstDollar = LINE_BUFFER.indexOf("$");
+            if (firstDollar >= 0) {
+                if (firstDollar > 0) {
+                    String prefix = LINE_BUFFER.substring(0, firstDollar);
+                    LINE_BUFFER.delete(0, firstDollar);
+                    if (!prefix.trim().isEmpty()) {
+                        return prefix;
+                    }
+                    continue;
+                }
+
+                int nextDollar = LINE_BUFFER.indexOf("$", firstDollar + 1);
+                if (nextDollar > 0) {
+                    String message = LINE_BUFFER.substring(firstDollar, nextDollar);
+                    LINE_BUFFER.delete(0, nextDollar);
+                    return message;
+                }
+            }
+
+            ensureBufferCapacity();
+            return null;
+        }
+    }
+
+    private static int findLineBreakIndex() {
+        for (int i = 0; i < LINE_BUFFER.length(); i++) {
+            char ch = LINE_BUFFER.charAt(i);
+            if (ch == '\n' || ch == '\r') {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    private static int calculateSkipLength(int breakIndex, int currentLength) {
+        if (breakIndex >= currentLength) {
+            return 0;
+        }
+        char separator = LINE_BUFFER.charAt(breakIndex);
+        if (separator == '\r') {
+            if (breakIndex + 1 < currentLength && LINE_BUFFER.charAt(breakIndex + 1) == '\n') {
+                return 2;
+            }
+            return 1;
+        }
+        // separator == '\n'
+        if (breakIndex + 1 < currentLength && LINE_BUFFER.charAt(breakIndex + 1) == '\r') {
+            return 2;
+        }
+        return 1;
+    }
+
+    private static void ensureBufferCapacity() {
+        final int maxCapacity = 4096;
+        if (LINE_BUFFER.length() > maxCapacity) {
+            LINE_BUFFER.delete(0, LINE_BUFFER.length() - maxCapacity);
+        }
+    }
+
+    private static void dispatchLine(String rawLine) {
+//    	 System.out.println("澶勭悊鏀跺埌鐨勬暟鎹�: " + rawLine);
+    	
+        if (rawLine == null) {
+            return;
+        }
+        String line = rawLine.trim();
+        if (line.isEmpty()) {
+            return;
+        }
+
+        LAST_LINE.set(line);
+        LINE_COUNTER.updateAndGet(count -> count >= 10000 ? 1 : count + 1);
+
+        for (Consumer<String> consumer : LINE_CONSUMERS) {
+            try {
+                consumer.accept(line);
+            } catch (Exception ex) {
+                logConsumerFailure(ex);
+            }
+        }
+
+        if (line.startsWith("$GNGGA")) {
+            try {
+                UDPServer.processSerialData(line);
+            } catch (Exception ex) {
+                System.err.println("dellmessage GNGGA parse error: " + ex.getMessage());
+            }
+        }
+    }
+
+    private static void logConsumerFailure(Exception ex) {
+        System.err.println("dellmessage listener exception: " + ex.getMessage());
+    }
+}
diff --git a/src/chuankou/sendmessage.java b/src/chuankou/sendmessage.java
new file mode 100644
index 0000000..e0a6da5
--- /dev/null
+++ b/src/chuankou/sendmessage.java
@@ -0,0 +1,105 @@
+package chuankou;
+
+import java.nio.charset.StandardCharsets;
+
+/**
+ * 涓插彛鍙戦�佸叆鍙o紝缁熶竴灏佽鍙戦�佹祦绋嬩究浜庤法妯″潡璋冪敤銆�
+ */
+public final class sendmessage {
+    private static final SerialPortService DEFAULT_SERVICE = new SerialPortService();
+    private static volatile SerialPortService activeService = DEFAULT_SERVICE;
+
+    private sendmessage() {
+        // utility class
+    }
+
+    /**
+     * 鍙戦�佸師濮嬪瓧鑺傛暟鎹��
+     *
+     * @param data 寰呭彂閫佸瓧鑺�
+     * @return true 琛ㄧず鍙戦�佹垚鍔�
+     */
+    public static boolean send(byte[] data) {
+        if (data == null || data.length == 0) {
+            return false;
+        }
+        SerialPortService service = getActiveService();
+        if (service == null || !service.isOpen()) {
+            return false;
+        }
+        return service.send(data);
+    }
+
+    /**
+     * 鍙戦�佸崄鍏繘鍒跺瓧绗︿覆锛堝厑璁稿寘鍚┖鏍硷級銆�
+     */
+    public static boolean sendHex(String hexString) {
+        byte[] data = hexToBytes(hexString);
+        return data != null && send(data);
+    }
+
+    /**
+     * 鍙戦�� UTF-8 鏂囨湰銆�
+     */
+    public static boolean sendText(String text) {
+        if (text == null || text.isEmpty()) {
+            return false;
+        }
+        return send(text.getBytes(StandardCharsets.UTF_8));
+    }
+
+    /**
+     * 灏嗗閮ㄦ墦寮�鐨勪覆鍙e疄渚嬫敞鍏ュ彂閫佸櫒锛岄伩鍏嶉噸澶嶆墦寮�銆�
+     */
+    public static void attachService(SerialPortService serialPortService) {
+        if (serialPortService == null) {
+            return;
+        }
+        synchronized (sendmessage.class) {
+            // 杩欓噷鍋囪璋冪敤鏂规彁渚涚殑 service 宸茬粡鎴愬姛鎵撳紑涓插彛
+            if (serialPortService.isOpen()) {
+                activeService = serialPortService;
+            }
+        }
+    }
+
+    /**
+     * 鑾峰彇褰撳墠浣跨敤鐨勪覆鍙f湇鍔°��
+     */
+    public static SerialPortService getActiveService() {
+        SerialPortService service = activeService;
+        return service != null ? service : DEFAULT_SERVICE;
+    }
+
+    /**
+     * 閫氳繃娲诲姩涓插彛鏈嶅姟鍙戦�佹暟鎹紙淇濊瘉寮曠敤鍚屾锛夈��
+     */
+    public static boolean sendViaActive(byte[] data) {
+        SerialPortService service = getActiveService();
+        if (service == null || !service.isOpen()) {
+            return false;
+        }
+        return service.send(data);
+    }
+
+    private static byte[] hexToBytes(String hexString) {
+        if (hexString == null) {
+            return null;
+        }
+        String normalized = hexString.replaceAll("\\s+", "").toUpperCase();
+        if (normalized.length() == 0 || normalized.length() % 2 != 0) {
+            return null;
+        }
+        byte[] result = new byte[normalized.length() / 2];
+        for (int i = 0; i < normalized.length(); i += 2) {
+            int high = Character.digit(normalized.charAt(i), 16);
+            int low = Character.digit(normalized.charAt(i + 1), 16);
+            if (high < 0 || low < 0) {
+                return null;
+            }
+            result[i / 2] = (byte) ((high << 4) + low);
+        }
+        return result;
+    }
+
+}
diff --git a/src/gecaoji/Device.java b/src/gecaoji/Device.java
index 91d9bcc..7b1c444 100644
--- a/src/gecaoji/Device.java
+++ b/src/gecaoji/Device.java
@@ -274,6 +274,14 @@
         device.applyGNGGAUpdate(gnggaData, deviceId);
     }
 
+    public static synchronized void updateFromSerialGNGGA(String gnggaData) { // 涓插彛鏁版嵁鏇存柊璺緞锛堟棤闇�璁惧缂栧彿鍖归厤锛�
+        Device device = gecaoji;
+        if (device == null) {
+            return;
+        }
+        device.chuankouGNGGAUpdate(gnggaData);
+    }
+
     private void applyGNGGAUpdate(String gnggaData, String deviceId) { // 鎵цGNGGA鏇存柊閫昏緫
         if (gnggaData == null) {
             return;
@@ -306,9 +314,19 @@
         String longitudeValue = sanitizeField(fields, 4);
         String longitudeHemisphere = sanitizeField(fields, 5);
 
-        realtimeLatitude = defaultIfEmpty(combineCoordinate(latitudeValue, latitudeHemisphere));
-        realtimeLongitude = defaultIfEmpty(combineCoordinate(longitudeValue, longitudeHemisphere));
-        realtimeAltitude = defaultIfEmpty(sanitizeField(fields, 9));
+        String combinedLatitude = combineCoordinate(latitudeValue, latitudeHemisphere);
+        if (hasMeaningfulValue(combinedLatitude)) {
+            realtimeLatitude = combinedLatitude;
+        }
+        String combinedLongitude = combineCoordinate(longitudeValue, longitudeHemisphere);
+        if (hasMeaningfulValue(combinedLongitude)) {
+            realtimeLongitude = combinedLongitude;
+        }
+
+        String altitudeValue = sanitizeField(fields, 9);
+        if (hasMeaningfulValue(altitudeValue)) {
+            realtimeAltitude = altitudeValue;
+        }
 
         positioningStatus = defaultIfEmpty(sanitizeField(fields, 6));
         satelliteCount = defaultIfEmpty(sanitizeField(fields, 7));
@@ -321,28 +339,68 @@
 
         updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere);
     }
+    
+    /**涓插彛鏇存柊GNGGA鏁版嵁*/
+    private void chuankouGNGGAUpdate(String gnggaData) { // 鎵цGNGGA鏇存柊閫昏緫
+        if (gnggaData == null) {
+            return;
+        }
+
+        String trimmed = gnggaData.trim();
+        if (trimmed.isEmpty() || !trimmed.startsWith("$GNGGA")) {
+            return;
+        }
+
+        String[] fields = trimmed.split(",");
+        if (fields.length < 15) {
+            System.err.println("GNGGA瀛楁鏁伴噺涓嶈冻: " + fields.length);
+            return;
+        }
+
+        
+        String latitudeValue = sanitizeField(fields, 2);
+        String latitudeHemisphere = sanitizeField(fields, 3);
+        String longitudeValue = sanitizeField(fields, 4);
+        String longitudeHemisphere = sanitizeField(fields, 5);
+
+        String combinedLatitude = combineCoordinate(latitudeValue, latitudeHemisphere);
+        if (hasMeaningfulValue(combinedLatitude)) {
+            realtimeLatitude = combinedLatitude;
+        }
+        String combinedLongitude = combineCoordinate(longitudeValue, longitudeHemisphere);
+        if (hasMeaningfulValue(combinedLongitude)) {
+            realtimeLongitude = combinedLongitude;
+        }
+
+        String altitudeValue = sanitizeField(fields, 9);
+        if (hasMeaningfulValue(altitudeValue)) {
+            realtimeAltitude = altitudeValue;
+        }
+
+        positioningStatus = defaultIfEmpty(sanitizeField(fields, 6));
+        satelliteCount = defaultIfEmpty(sanitizeField(fields, 7));
+        differentialAge = defaultIfEmpty(sanitizeField(fields, 13));       
+        realtimeSpeed ="0";        
+        GupdateTime = String.valueOf(System.currentTimeMillis());
+
+        updateRelativeCoordinates(latitudeValue, latitudeHemisphere, longitudeValue, longitudeHemisphere);
+    }
 
     private void updateRelativeCoordinates(String latValue, String latHemisphere,
                                            String lonValue, String lonHemisphere) { // 璁$畻鐩稿鍧愭爣
         if (!hasMeaningfulValue(latValue) || !hasMeaningfulValue(lonValue)
                 || !hasMeaningfulValue(latHemisphere) || !hasMeaningfulValue(lonHemisphere)) {
-            realtimeX = "-1";
-            realtimeY = "-1";
             return;
         }
 
         double mowerLat = toDecimalDegrees(latValue, latHemisphere);
         double mowerLon = toDecimalDegrees(lonValue, lonHemisphere);
         if (Double.isNaN(mowerLat) || Double.isNaN(mowerLon)) {
-            realtimeX = "-1";
-            realtimeY = "-1";
             return;
         }
 
         double[] baseLatLon = resolveBaseStationLatLon();
         if (baseLatLon == null) {
-            realtimeX = "-1";
-            realtimeY = "-1";
             return;
         }
 
@@ -354,8 +412,10 @@
         double eastMeters = deltaLonDeg * metersPerLon;
         double northMeters = deltaLatDeg * METERS_PER_DEGREE_LAT;
 
-        realtimeX = formatMeters(eastMeters);
-        realtimeY = formatMeters(northMeters);
+        if (Double.isFinite(eastMeters) && Double.isFinite(northMeters)) {
+            realtimeX = formatMeters(eastMeters);
+            realtimeY = formatMeters(northMeters);
+        }
     }
 
     private double[] resolveBaseStationLatLon() { // 瑙f瀽鍩虹珯缁忕含搴�
diff --git a/src/homein/Homein.java b/src/homein/Homein.java
index b4dab2a..fd2ad95 100644
--- a/src/homein/Homein.java
+++ b/src/homein/Homein.java
@@ -2,6 +2,7 @@
 
 import denglu.UserChuShiHua;
 import gecaoji.Device;
+import chuankou.SerialPortAutoConnector;
 import set.Setsys;
 import udpdell.UDPServer;
 import denglu.Denglu;
@@ -33,6 +34,7 @@
                 Device.initializeActiveDevice(setsys.getMowerId());
 
             UDPServer.startAsync();//鍚姩鏁版嵁鎺ユ敹绾跨▼
+            SerialPortAutoConnector.initialize();//鍚姩涓插彛鑷姩杩炴帴
             
             // 鏄剧ず鍒濆鏁版嵁
             System.out.println("鍒濆鐢ㄦ埛鍚�: " + UserChuShiHua.getProperty("userName"));
diff --git a/src/set/Sets.java b/src/set/Sets.java
index a5e2863..30829bd 100644
--- a/src/set/Sets.java
+++ b/src/set/Sets.java
@@ -1,4 +1,7 @@
 package set;
+
+import baseStation.BaseStation;
+
 import javax.swing.*;
 import javax.swing.filechooser.FileNameExtensionFilter;
 
@@ -26,21 +29,25 @@
     private JLabel mowerIdLabel;
     private JLabel handheldMarkerLabel;
     private JLabel simCardNumberLabel;
+    private JLabel baseStationSimLabel;
     private JLabel firmwareVersionLabel;
     private JLabel appVersionLabel;
     
     private JButton mowerIdEditBtn;
     private JButton handheldEditBtn;
     private JButton checkUpdateBtn;
+    private JButton systemDebugButton;
     private JButton feedbackButton;
     
     // 鏁版嵁妯″瀷
     private Setsys setData;
+    private final BaseStation baseStation;
     
     public Sets(JFrame parent, Color themeColor) {
         super(parent, "绯荤粺璁剧疆", true);
         this.THEME_COLOR = themeColor;
         this.setData = new Setsys();
+        this.baseStation = new BaseStation();
         initializeUI();
         loadData();
     }
@@ -49,6 +56,7 @@
         super(parent, "绯荤粺璁剧疆", true);
         this.THEME_COLOR = themeColor;
         this.setData = new Setsys();
+        this.baseStation = new BaseStation();
         initializeUI();
         loadData();
     }
@@ -99,9 +107,13 @@
         handheldEditBtn = (JButton) handheldPanel.getClientProperty("editButton");
 
         // 鐗╄仈鍗″彿
-        JPanel simCardPanel = createSettingItemPanel("鐗╄仈鍗″彿", 
+        JPanel simCardPanel = createSettingItemPanel("鍓茶崏鏈虹墿鑱旂綉鍗″彿", 
             setData.getSimCardNumber() != null ? setData.getSimCardNumber() : "鏈缃�", false);
         simCardNumberLabel = (JLabel) simCardPanel.getClientProperty("valueLabel");
+
+        JPanel baseStationSimPanel = createSettingItemPanel("鍩哄噯绔欑墿鑱旂綉鍗″彿",
+            resolveBaseStationSimCard(), false);
+        baseStationSimLabel = (JLabel) baseStationSimPanel.getClientProperty("valueLabel");
         
         // 鍥轰欢鐗堟湰
         JPanel firmwarePanel = createSettingItemPanel("鍥轰欢鐗堟湰", 
@@ -115,10 +127,12 @@
         
         addRowWithSpacing(panel, mowerIdPanel);
         addRowWithSpacing(panel, handheldPanel);
-        addRowWithSpacing(panel, simCardPanel);
-        addRowWithSpacing(panel, firmwarePanel);
-        addRowWithSpacing(panel, feedbackPanel);
-        panel.add(appVersionPanel);
+    addRowWithSpacing(panel, simCardPanel);
+    addRowWithSpacing(panel, baseStationSimPanel);
+    addRowWithSpacing(panel, firmwarePanel);
+    addRowWithSpacing(panel, feedbackPanel);
+    addRowWithSpacing(panel, appVersionPanel);
+    panel.add(createDebugPanel());
         
         return panel;
     }
@@ -209,14 +223,14 @@
         gbc.anchor = GridBagConstraints.EAST;
         panel.add(appVersionLabel, gbc);
 
-        checkUpdateBtn = new JButton("妫�鏌ユ洿鏂�");
-        checkUpdateBtn.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
-        checkUpdateBtn.setBackground(THEME_COLOR);
-        checkUpdateBtn.setForeground(Color.WHITE);
-        checkUpdateBtn.setBorder(BorderFactory.createEmptyBorder(0, 12, 0, 12));
-        checkUpdateBtn.setPreferredSize(new Dimension(90, 25));
-        checkUpdateBtn.setMinimumSize(new Dimension(90, 25));
-        checkUpdateBtn.setMaximumSize(new Dimension(90, 25));
+    checkUpdateBtn = new JButton("妫�鏌ユ洿鏂�");
+    checkUpdateBtn.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+    checkUpdateBtn.setBackground(THEME_COLOR);
+    checkUpdateBtn.setForeground(Color.WHITE);
+    checkUpdateBtn.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 20));
+    checkUpdateBtn.setPreferredSize(new Dimension(100, 28));
+    checkUpdateBtn.setMinimumSize(new Dimension(100, 28));
+    checkUpdateBtn.setMaximumSize(new Dimension(100, 28));
         checkUpdateBtn.setFocusPainted(false);
 
         checkUpdateBtn.addMouseListener(new MouseAdapter() {
@@ -242,6 +256,62 @@
         return panel;
     }
 
+    private JPanel createDebugPanel() {
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.setBackground(PANEL_BACKGROUND);
+        panel.setAlignmentX(Component.LEFT_ALIGNMENT);
+        panel.setMaximumSize(new Dimension(Integer.MAX_VALUE, ROW_HEIGHT));
+        panel.setPreferredSize(new Dimension(Integer.MAX_VALUE, ROW_HEIGHT));
+        panel.setMinimumSize(new Dimension(0, ROW_HEIGHT));
+
+        GridBagConstraints gbc = new GridBagConstraints();
+
+        JLabel titleLabel = new JLabel("绯荤粺璋冭瘯");
+        titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
+        titleLabel.setForeground(Color.BLACK);
+        titleLabel.setHorizontalAlignment(SwingConstants.RIGHT);
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.weightx = 0;
+        gbc.anchor = GridBagConstraints.EAST;
+        gbc.insets = new Insets(0, 0, 0, 12);
+        panel.add(titleLabel, gbc);
+
+        systemDebugButton = new JButton("绯荤粺璋冭瘯");
+        systemDebugButton.setFont(new Font("寰蒋闆呴粦", Font.PLAIN, 12));
+        systemDebugButton.setBackground(new Color(
+            Math.max(THEME_COLOR.getRed() - 20, 0),
+            Math.max(THEME_COLOR.getGreen() - 20, 0),
+            Math.max(THEME_COLOR.getBlue() - 20, 0)));
+        systemDebugButton.setForeground(Color.WHITE);
+        systemDebugButton.setBorder(BorderFactory.createEmptyBorder(0, 20, 0, 20));
+        systemDebugButton.setPreferredSize(new Dimension(100, 28));
+        systemDebugButton.setMinimumSize(new Dimension(100, 28));
+        systemDebugButton.setMaximumSize(new Dimension(100, 28));
+        systemDebugButton.setFocusPainted(false);
+
+        systemDebugButton.addMouseListener(new MouseAdapter() {
+            public void mouseEntered(MouseEvent e) {
+                systemDebugButton.setBackground(THEME_COLOR);
+            }
+            public void mouseExited(MouseEvent e) {
+                systemDebugButton.setBackground(new Color(
+                    Math.max(THEME_COLOR.getRed() - 20, 0),
+                    Math.max(THEME_COLOR.getGreen() - 20, 0),
+                    Math.max(THEME_COLOR.getBlue() - 20, 0)));
+            }
+        });
+
+        gbc = new GridBagConstraints();
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.weightx = 1.0;
+        gbc.anchor = GridBagConstraints.EAST;
+        panel.add(systemDebugButton, gbc);
+
+        return panel;
+    }
+
     private JPanel createFeedbackPanel() {
         JPanel panel = new JPanel(new GridBagLayout());
         panel.setBackground(PANEL_BACKGROUND);
@@ -333,6 +403,7 @@
     private void loadData() {
         // 浠嶴etsys绫诲姞杞芥暟鎹�
         setData.initializeFromProperties();
+        baseStation.load();
         updateDisplay();
     }
     
@@ -351,6 +422,10 @@
             simCardNumberLabel.setText(setData.getSimCardNumber() != null ? 
                 setData.getSimCardNumber() : "鏈缃�");
         }
+
+        if (baseStationSimLabel != null) {
+            baseStationSimLabel.setText(resolveBaseStationSimCard());
+        }
         
         // 鏇存柊鍥轰欢鐗堟湰鏄剧ず
         if (firmwareVersionLabel != null) {
@@ -364,6 +439,21 @@
                 setData.getAppVersion() : "鏈缃�");
         }
     }
+
+    private String resolveBaseStationSimCard() {
+        if (baseStation == null) {
+            return "鏈缃�";
+        }
+        String value = baseStation.getIotSimCardNumber();
+        if (value == null) {
+            return "鏈缃�";
+        }
+        String trimmed = value.trim();
+        if (trimmed.isEmpty() || "-1".equals(trimmed)) {
+            return "鏈缃�";
+        }
+        return trimmed;
+    }
     
     private void setupEventHandlers() {
         // 鍓茶崏鏈虹紪鍙风紪杈戞寜閽簨浠�
@@ -383,6 +473,10 @@
         if (feedbackButton != null) {
             feedbackButton.addActionListener(e -> showFeedbackDialog());
         }
+
+        if (systemDebugButton != null) {
+            systemDebugButton.addActionListener(e -> openSystemDebugDialog());
+        }
         
     }
     
@@ -609,6 +703,12 @@
         timer.setRepeats(false);
         timer.start();
     }
+
+    private void openSystemDebugDialog() {
+        debug dialog = new debug(this, THEME_COLOR);
+        dialog.setLocationRelativeTo(this);
+        dialog.setVisible(true);
+    }
     
     @Override
     public void setVisible(boolean visible) {
diff --git a/src/set/debug.java b/src/set/debug.java
new file mode 100644
index 0000000..ad6bf31
--- /dev/null
+++ b/src/set/debug.java
@@ -0,0 +1,555 @@
+package set;
+
+import chuankou.SerialPortPreferences;
+import chuankou.SerialPortService;
+import chuankou.sendmessage;
+import com.fazecast.jSerialComm.SerialPort;
+import ui.UIConfig;
+import javax.swing.*;
+import javax.swing.event.PopupMenuEvent;
+import javax.swing.event.PopupMenuListener;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+/**
+ * 绯荤粺璋冭瘯瀵硅瘽妗嗐��
+ */
+public class debug extends JDialog {
+    private static final long serialVersionUID = 1L;
+    private static final Dimension DEFAULT_SIZE = new Dimension(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT);
+
+    private final JTextArea logArea = new JTextArea();
+    private final SerialPortService serialService = sendmessage.getActiveService();
+    private final JComboBox<String> portComboBox = new JComboBox<>();
+    private final JComboBox<Integer> baudComboBox = new JComboBox<>(new Integer[]{115200, 921600, 57600});
+    private final JButton connectButton = new JButton("杩炴帴");
+    private final JButton pauseButton = new JButton("鏆傚仠鏄剧ず");
+    private final JButton closeButton = new JButton("鍏抽棴");
+    private final JButton clearButton = new JButton("娓呯┖鏃ュ織");
+    private final JCheckBox hexDisplayCheckBox = new JCheckBox("HEX鏄剧ず");
+    private final JCheckBox autoConnectCheckBox = new JCheckBox("鍚姩鏃惰繛鎺�");
+    private final JLabel statusLabel = new JLabel("鏈繛鎺�");
+    private final SimpleDateFormat timeFormatter = new SimpleDateFormat("HH:mm:ss");
+    private final Color themeColor;
+
+    private boolean isConnected = false;
+    private boolean isPaused = false;
+    private String activePortName;
+    private int activeBaudRate = 115200;
+    private String preferredPortName;
+    private boolean suppressPreferenceSync = false;
+
+    public debug(Window owner, Color themeColor) {
+        super(owner, "绯荤粺璋冭瘯", ModalityType.APPLICATION_MODAL);
+        this.themeColor = themeColor != null ? themeColor : new Color(52, 152, 219);
+        initializeUI();
+        loadPreferences();
+        refreshSerialPorts();
+        syncConnectionStateFromService();
+        appendLog("绯荤粺璋冭瘯宸ュ叿宸插惎鍔�");
+    }
+
+    private void initializeUI() {
+        setSize(DEFAULT_SIZE);
+        setPreferredSize(DEFAULT_SIZE);
+        setMinimumSize(DEFAULT_SIZE);
+        setResizable(false);
+        setDefaultCloseOperation(DISPOSE_ON_CLOSE);
+
+        JPanel contentPane = new JPanel(new BorderLayout(12, 12));
+        contentPane.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
+        contentPane.setBackground(Color.WHITE);
+
+        contentPane.add(buildControlPanel(), BorderLayout.NORTH);
+        contentPane.add(buildLogPanel(), BorderLayout.CENTER);
+        contentPane.add(buildActionPanel(), BorderLayout.SOUTH);
+
+        setContentPane(contentPane);
+
+        addWindowListener(new WindowAdapter() {
+            @Override
+            public void windowClosing(WindowEvent e) {
+                detachLogListener();
+            }
+
+            @Override
+            public void windowClosed(WindowEvent e) {
+                detachLogListener();
+            }
+        });
+
+        updateControlStates();
+    }
+
+    private JPanel buildControlPanel() {
+        JPanel panel = new JPanel(new GridBagLayout());
+        panel.setBackground(new Color(248, 248, 248));
+        panel.setBorder(BorderFactory.createCompoundBorder(
+                BorderFactory.createLineBorder(new Color(225, 225, 225)),
+                BorderFactory.createEmptyBorder(12, 12, 12, 12)));
+
+        GridBagConstraints gbc = new GridBagConstraints();
+        gbc.fill = GridBagConstraints.HORIZONTAL;
+        gbc.insets = new Insets(0, 0, 10, 10);
+        gbc.anchor = GridBagConstraints.WEST;
+
+        JLabel portLabel = new JLabel("閫夋嫨涓插彛");
+        portLabel.setFont(portLabel.getFont().deriveFont(Font.BOLD, 13f));
+        gbc.gridx = 0;
+        gbc.gridy = 0;
+        gbc.weightx = 0;
+        panel.add(portLabel, gbc);
+
+        portComboBox.setPrototypeDisplayValue("COM000 (USB Serial)");
+        portComboBox.setFont(portComboBox.getFont().deriveFont(13f));
+        portComboBox.setPreferredSize(new Dimension(220, 28));
+        portComboBox.addPopupMenuListener(new PopupMenuListener() {
+            @Override
+            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
+                SwingUtilities.invokeLater(() -> refreshSerialPorts());
+            }
+
+            @Override
+            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
+                // no-op
+            }
+
+            @Override
+            public void popupMenuCanceled(PopupMenuEvent e) {
+                // no-op
+            }
+        });
+        portComboBox.addActionListener(e -> handlePortSelectionChange());
+        gbc.gridx = 1;
+        gbc.gridy = 0;
+        gbc.weightx = 1;
+        panel.add(portComboBox, gbc);
+
+        connectButton.setFont(connectButton.getFont().deriveFont(Font.BOLD, 13f));
+        connectButton.setBackground(themeColor);
+        connectButton.setForeground(Color.WHITE);
+        connectButton.setFocusPainted(false);
+        connectButton.setPreferredSize(new Dimension(90, 32));
+        connectButton.addActionListener(this::handleConnectButton);
+        gbc.gridx = 2;
+        gbc.gridy = 0;
+        gbc.weightx = 0;
+        gbc.insets = new Insets(0, 0, 10, 0);
+        panel.add(connectButton, gbc);
+
+        JLabel baudLabel = new JLabel("娉㈢壒鐜�");
+        baudLabel.setFont(baudLabel.getFont().deriveFont(Font.BOLD, 13f));
+        gbc.gridx = 0;
+        gbc.gridy = 1;
+        gbc.insets = new Insets(0, 0, 0, 10);
+        panel.add(baudLabel, gbc);
+
+        baudComboBox.setFont(baudComboBox.getFont().deriveFont(13f));
+        baudComboBox.setSelectedItem(115200);
+        baudComboBox.setPreferredSize(new Dimension(140, 28));
+        baudComboBox.addActionListener(e -> handleBaudSelectionChange());
+        gbc.gridx = 1;
+        gbc.gridy = 1;
+        gbc.weightx = 1;
+        gbc.insets = new Insets(0, 0, 0, 10);
+        panel.add(baudComboBox, gbc);
+
+        statusLabel.setFont(statusLabel.getFont().deriveFont(Font.PLAIN, 12f));
+        statusLabel.setForeground(new Color(120, 120, 120));
+
+        autoConnectCheckBox.setOpaque(false);
+        autoConnectCheckBox.setFont(autoConnectCheckBox.getFont().deriveFont(Font.PLAIN, 12f));
+        autoConnectCheckBox.setForeground(new Color(90, 90, 90));
+        autoConnectCheckBox.addActionListener(e -> handleAutoConnectToggle());
+
+        gbc.gridx = 0;
+        gbc.gridy = 2;
+        gbc.gridwidth = 3;
+        gbc.weightx = 1;
+        gbc.insets = new Insets(12, 0, 0, 0);
+        panel.add(statusLabel, gbc);
+
+        gbc.gridy = 3;
+        gbc.insets = new Insets(6, 0, 0, 0);
+        panel.add(autoConnectCheckBox, gbc);
+
+        return panel;
+    }
+
+    private JScrollPane buildLogPanel() {
+        logArea.setEditable(false);
+        logArea.setLineWrap(true);
+        logArea.setWrapStyleWord(true);
+        logArea.setFont(new Font("Consolas", Font.PLAIN, 13));
+        logArea.setMargin(new Insets(10, 10, 10, 10));
+        logArea.setBackground(new Color(253, 253, 253));
+
+        JScrollPane scrollPane = new JScrollPane(logArea);
+        scrollPane.setBorder(BorderFactory.createCompoundBorder(
+                BorderFactory.createTitledBorder("璋冭瘯鏃ュ織"),
+                BorderFactory.createEmptyBorder(8, 8, 8, 8)));
+        return scrollPane;
+    }
+
+    private JPanel buildActionPanel() {
+        JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 12, 0));
+        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
+        panel.setOpaque(false);
+
+        closeButton.setFont(closeButton.getFont().deriveFont(Font.PLAIN, 13f));
+        closeButton.setPreferredSize(new Dimension(100, 32));
+        closeButton.setFocusPainted(false);
+        closeButton.setBackground(new Color(240, 240, 240));
+        closeButton.setForeground(new Color(70, 70, 70));
+        closeButton.setOpaque(true);
+        closeButton.setBorder(BorderFactory.createLineBorder(new Color(210, 210, 210)));
+        closeButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+        closeButton.addActionListener(e -> handleCloseAction());
+
+        pauseButton.setFont(pauseButton.getFont().deriveFont(Font.PLAIN, 13f));
+        pauseButton.setPreferredSize(new Dimension(100, 32));
+        pauseButton.setFocusPainted(false);
+        pauseButton.addActionListener(e -> togglePause());
+
+        clearButton.setFont(clearButton.getFont().deriveFont(Font.PLAIN, 13f));
+        clearButton.setPreferredSize(new Dimension(100, 32));
+    clearButton.setBackground(themeColor);
+    clearButton.setForeground(Color.WHITE);
+    clearButton.setFocusPainted(false);
+    clearButton.setOpaque(true);
+    clearButton.setBorderPainted(false);
+    clearButton.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
+    clearButton.addActionListener(e -> logArea.setText(""));
+
+        JPanel rightGroup = new JPanel(new FlowLayout(FlowLayout.LEFT, 8, 0));
+        rightGroup.setOpaque(false);
+
+        hexDisplayCheckBox.setOpaque(false);
+        hexDisplayCheckBox.setFont(hexDisplayCheckBox.getFont().deriveFont(Font.PLAIN, 12f));
+        hexDisplayCheckBox.setForeground(new Color(90, 90, 90));
+
+        rightGroup.add(clearButton);
+        rightGroup.add(hexDisplayCheckBox);
+
+        panel.add(closeButton);
+        panel.add(pauseButton);
+        panel.add(rightGroup);
+        return panel;
+    }
+
+    private void handleCloseAction() {
+        performDisconnect(false);
+        dispose();
+    }
+
+    private void handlePortSelectionChange() {
+        if (suppressPreferenceSync) {
+            return;
+        }
+        String selected = (String) portComboBox.getSelectedItem();
+        activePortName = selected;
+        preferredPortName = selected;
+        SerialPortPreferences.setPortName(selected);
+    }
+
+    private void handleBaudSelectionChange() {
+        if (suppressPreferenceSync) {
+            return;
+        }
+        Integer value = (Integer) baudComboBox.getSelectedItem();
+        if (value == null) {
+            return;
+        }
+        activeBaudRate = value;
+        SerialPortPreferences.setBaudRate(value);
+    }
+
+    private void handleAutoConnectToggle() {
+        if (suppressPreferenceSync) {
+            return;
+        }
+        boolean enabled = autoConnectCheckBox.isSelected();
+        SerialPortPreferences.setAutoConnectEnabled(enabled);
+        if (enabled && !serialService.isOpen()) {
+            String selected = (String) portComboBox.getSelectedItem();
+            if (selected != null && !selected.trim().isEmpty()) {
+                performConnect();
+            }
+        }
+    }
+
+    private void loadPreferences() {
+        suppressPreferenceSync = true;
+        try {
+            preferredPortName = SerialPortPreferences.getPortName();
+            activePortName = preferredPortName;
+
+            int savedBaud = SerialPortPreferences.getBaudRate();
+            if (isSupportedBaud(savedBaud)) {
+                activeBaudRate = savedBaud;
+                if (baudComboBox.getSelectedItem() == null || !baudComboBox.getSelectedItem().equals(savedBaud)) {
+                    baudComboBox.setSelectedItem(savedBaud);
+                }
+            }
+
+            boolean autoConnect = SerialPortPreferences.isAutoConnectEnabled();
+            autoConnectCheckBox.setSelected(autoConnect);
+        } finally {
+            suppressPreferenceSync = false;
+        }
+    }
+
+    private boolean isSupportedBaud(int baud) {
+        for (int i = 0; i < baudComboBox.getItemCount(); i++) {
+            if (baudComboBox.getItemAt(i) == baud) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void syncConnectionStateFromService() {
+        isConnected = serialService.isOpen();
+        if (isConnected) {
+            serialService.setPaused(false);
+            if (activePortName == null) {
+                activePortName = preferredPortName;
+            }
+            if (preferredPortName == null) {
+                preferredPortName = activePortName;
+            }
+        }
+        updateControlStates();
+    }
+
+    private void attachLogListener() {
+        serialService.startCapture(this::handleSerialData);
+        serialService.setPaused(false);
+        isPaused = false;
+    }
+
+    private void detachLogListener() {
+        serialService.stopDataCaptureOnly();
+        isPaused = false;
+        updateControlStates();
+    }
+
+    private void handleConnectButton(ActionEvent event) {
+        if (serialService.isOpen()) {
+            performDisconnect(true);
+        } else {
+            performConnect();
+        }
+    }
+
+    private void performConnect() {
+        String selectedPort = (String) portComboBox.getSelectedItem();
+        if (selectedPort == null || selectedPort.trim().isEmpty()) {
+            JOptionPane.showMessageDialog(this, "鏈娴嬪埌鍙敤涓插彛锛岃妫�鏌ヨ繛鎺ュ悗閲嶈瘯銆�", "鎻愮ず", JOptionPane.WARNING_MESSAGE);
+            return;
+        }
+
+        Integer baudSelection = (Integer) baudComboBox.getSelectedItem();
+        int baudRate = baudSelection != null ? baudSelection : activeBaudRate;
+
+        try {
+            boolean opened = serialService.open(selectedPort, baudRate);
+            if (!opened) {
+                JOptionPane.showMessageDialog(this, "涓插彛鎵撳紑澶辫触锛岃纭涓插彛鏈鍗犵敤銆�", "閿欒", JOptionPane.ERROR_MESSAGE);
+                appendLog("涓插彛杩炴帴澶辫触: " + selectedPort);
+                return;
+            }
+
+            serialService.startCapture(this::handleSerialData);
+            serialService.setPaused(false);
+
+            isConnected = true;
+            isPaused = false;
+            activePortName = selectedPort;
+            activeBaudRate = baudRate;
+            preferredPortName = selectedPort;
+
+            SerialPortPreferences.setPortName(selectedPort);
+            SerialPortPreferences.setBaudRate(baudRate);
+
+            appendLog("涓插彛宸茶繛鎺�: " + selectedPort + " @ " + baudRate);
+        } catch (Exception ex) {
+            appendLog("杩炴帴涓插彛鏃跺彂鐢熷紓甯�: " + ex.getMessage());
+            JOptionPane.showMessageDialog(this, "杩炴帴涓插彛鏃跺彂鐢熷紓甯�:\n" + ex.getMessage(), "閿欒", JOptionPane.ERROR_MESSAGE);
+        } finally {
+            updateControlStates();
+        }
+    }
+
+    private void performDisconnect(boolean logMessage) {
+        if (!serialService.isOpen()) {
+            isConnected = false;
+            updateControlStates();
+            return;
+        }
+
+        serialService.close();
+        serialService.stopDataCaptureOnly();
+        isConnected = false;
+        isPaused = false;
+        if (logMessage) {
+            appendLog("涓插彛杩炴帴宸叉柇寮�");
+        }
+        updateControlStates();
+    }
+
+    private void togglePause() {
+        if (!serialService.isOpen()) {
+            return;
+        }
+        isPaused = !isPaused;
+        serialService.setPaused(isPaused);
+        appendLog(isPaused ? "璋冭瘯鏃ュ織鏄剧ず宸叉殏鍋�" : "璋冭瘯鏃ュ織鏄剧ず宸叉仮澶�");
+        updateControlStates();
+    }
+
+    private void refreshSerialPorts() {
+        suppressPreferenceSync = true;
+        try {
+            String previousSelection = (String) portComboBox.getSelectedItem();
+            portComboBox.removeAllItems();
+
+            SerialPort[] ports = SerialPort.getCommPorts();
+            for (SerialPort port : ports) {
+                portComboBox.addItem(port.getSystemPortName());
+            }
+
+            if (portComboBox.getItemCount() == 0) {
+                statusLabel.setText("鏈娴嬪埌鍙敤涓插彛");
+                connectButton.setEnabled(false);
+            } else {
+                connectButton.setEnabled(true);
+
+                String target = activePortName != null ? activePortName : preferredPortName;
+                if (target != null) {
+                    portComboBox.setSelectedItem(target);
+                }
+
+                if (portComboBox.getSelectedItem() == null && previousSelection != null) {
+                    portComboBox.setSelectedItem(previousSelection);
+                }
+
+                if (portComboBox.getSelectedItem() == null) {
+                    portComboBox.setSelectedIndex(0);
+                }
+            }
+        } finally {
+            suppressPreferenceSync = false;
+        }
+
+        updateControlStates();
+    }
+
+    private void handleSerialData(byte[] data) {
+        if (data == null || data.length == 0) {
+            return;
+        }
+
+        if (hexDisplayCheckBox.isSelected()) {
+            String hex = bytesToHex(data);
+            appendLog(String.format("鎺ユ敹 %d 瀛楄妭 | HEX: %s", data.length, hex));
+        } else {
+                String ascii = sanitizeAscii(new String(data, StandardCharsets.UTF_8));
+                appendLog(String.format("鎺ユ敹 %d 瀛楄妭 | ASCII: %s", data.length, ascii.isEmpty() ? "(涓嶅彲鎵撳嵃)" : ascii));
+        }
+    }
+
+    private void appendLog(String message) {
+        SwingUtilities.invokeLater(() -> {
+            logArea.append(String.format("[%s] %s%n", timeFormatter.format(new Date()), message));
+            logArea.setCaretPosition(logArea.getDocument().getLength());
+        });
+    }
+
+    private void updateControlStates() {
+        isConnected = serialService.isOpen();
+
+        portComboBox.setEnabled(!isConnected);
+        baudComboBox.setEnabled(!isConnected);
+        pauseButton.setEnabled(isConnected);
+        pauseButton.setText(isPaused ? "寮�濮嬫樉绀�" : "鏆傚仠鏄剧ず");
+
+        boolean hasPorts = portComboBox.getItemCount() > 0;
+        connectButton.setEnabled(isConnected || hasPorts);
+
+        String displayPort = activePortName != null ? activePortName : preferredPortName;
+        if (displayPort == null || displayPort.trim().isEmpty()) {
+            displayPort = "--";
+        }
+
+        if (isConnected) {
+            connectButton.setText("鏂紑");
+            connectButton.setBackground(new Color(220, 68, 55));
+            statusLabel.setForeground(new Color(46, 139, 87));
+            statusLabel.setText(String.format("宸茶繛鎺ワ細%s @ %d", displayPort, activeBaudRate));
+        } else {
+            connectButton.setText("杩炴帴");
+            connectButton.setBackground(themeColor);
+            statusLabel.setForeground(new Color(120, 120, 120));
+            if (hasPorts) {
+                if (preferredPortName != null && !preferredPortName.trim().isEmpty()) {
+                    statusLabel.setText(String.format("鏈繛鎺ワ紙涓婃锛�%s @ %d锛�", displayPort, activeBaudRate));
+                } else {
+                    statusLabel.setText("鏈繛鎺�");
+                }
+            } else {
+                statusLabel.setText("鏈娴嬪埌鍙敤涓插彛");
+            }
+        }
+        connectButton.setOpaque(true);
+        connectButton.setBorderPainted(false);
+        clearButton.setBackground(themeColor);
+        clearButton.setForeground(Color.WHITE);
+        clearButton.setOpaque(true);
+        clearButton.setBorderPainted(false);
+    }
+
+    private String bytesToHex(byte[] bytes) {
+        StringBuilder sb = new StringBuilder(bytes.length * 3);
+        for (byte b : bytes) {
+            sb.append(String.format("%02X ", b));
+        }
+        if (sb.length() > 0) {
+            sb.setLength(sb.length() - 1);
+        }
+        return sb.toString();
+    }
+
+    private String sanitizeAscii(String value) {
+        StringBuilder sb = new StringBuilder(value.length());
+        for (char ch : value.toCharArray()) {
+            if (ch >= 32 && ch < 127) {
+                sb.append(ch);
+            } else if (ch == '\r' || ch == '\n' || ch == '\t') {
+                sb.append(ch);
+            }
+        }
+        return sb.toString().trim();
+    }
+
+    @Override
+    public void setVisible(boolean visible) {
+        if (visible) {
+            attachLogListener();
+        } else {
+            detachLogListener();
+        }
+        super.setVisible(visible);
+    }
+
+    @Override
+    public void dispose() {
+        detachLogListener();
+        super.dispose();
+    }
+}
diff --git a/src/udpdell/UDPServer.java b/src/udpdell/UDPServer.java
index 1ee1bde..7243090 100644
--- a/src/udpdell/UDPServer.java
+++ b/src/udpdell/UDPServer.java
@@ -5,6 +5,7 @@
 import java.net.SocketException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import gecaoji.Device;
 import zhuye.Coordinate;
@@ -14,6 +15,8 @@
 	private static final int BUFFER_SIZE = 65507; // UDP鏈�澶у寘澶у皬
 	private static final int THREAD_POOL_SIZE = 100; // 绾跨▼姹犲ぇ灏�
 
+	private static final AtomicInteger RECEIVED_PACKET_COUNTER = new AtomicInteger(0);
+
 	private static volatile Thread serverThread;
 
 	/**
@@ -82,13 +85,51 @@
 			System.err.println("Invalid message header: " + fields[0]);
 			return;
 		}
-		System.out.println("鏀跺埌浜嗗樊鍒嗘暟鎹細" + message);
+		int sequence = incrementReceivedPacketCounter();
+		System.out.println("鏀跺埌浜嗗樊鍒嗘暟鎹�(" + sequence + ")锛�" + message);
 		Coordinate.parseGNGGAToCoordinateList(message);
 		int count = Coordinate.coordinates.size();
 		System.out.println("savenum:" + count);
 
 		Device.updateFromGNGGA(message, fields[15]);
 	}
+	
+	/**澶勭悊涓插彛鎺ユ敹鍒扮殑鏁版嵁*/
+	public static void processSerialData(String message) {
+		String[] fields = message.split(",");
+		// 妫�鏌ュ瓧娈垫暟閲忔槸鍚﹀畬鏁�
+		if (fields.length < 15) {
+			System.err.println("Invalid serial GNGGA format, expected at least 15 fields but got " + fields.length);
+			return;
+		}
+
+		// 妫�鏌ュ寘澶存槸鍚︽纭�
+		if (!fields[0].equals("$GNGGA")) {
+			System.err.println("Invalid message header: " + fields[0]);
+			return;
+		}
+		int sequence = incrementReceivedPacketCounter();
+		System.out.println("鏀跺埌浜嗕覆鍙f暟鎹�(" + sequence + ")锛�" + message);
+		Coordinate.dellchuankougngga(message);
+		int count = Coordinate.coordinates.size();
+		System.out.println("savenum:" + count);
+
+		Device.updateFromSerialGNGGA(message);
+	}
+
+	private static int incrementReceivedPacketCounter() {
+		return RECEIVED_PACKET_COUNTER.updateAndGet(current -> {
+			int next = current + 1;
+			if (next > 10000 || next <= 0) {
+				next = 1;
+			}
+			return next;
+		});
+	}
+
+	public static int getReceivedPacketCount() {
+		return RECEIVED_PACKET_COUNTER.get();
+	}
 
 	private static class PacketProcessor implements Runnable {
 		private final DatagramPacket packet;
diff --git a/src/zhuye/Coordinate.java b/src/zhuye/Coordinate.java
index 9fa4fc8..d15579f 100644
--- a/src/zhuye/Coordinate.java
+++ b/src/zhuye/Coordinate.java
@@ -82,73 +82,106 @@
 	 * 瑙f瀽GNGGA鏁版嵁杩斿洖Coordinate瀵硅薄鍒楄〃锛堝寮虹増锛屽寘鍚珮绋嬫暟鎹級
 	 */
 	public static void parseGNGGAToCoordinateList(String gnggaData) {	
-		if(isStartSaveGngga) {		
-			String[] records = gnggaData.split("\\$GNGGA");
+		if (!isStartSaveGngga || gnggaData == null || gnggaData.isEmpty()) {
+			return;
+		}
 
-			for (String record : records) {
-				try {
-					String trimmedRecord = record.trim();
-					if (trimmedRecord.isEmpty()) continue;
-
-					if (!trimmedRecord.startsWith(",")) {
-						trimmedRecord = "," + trimmedRecord;
-					}
-
-					String[] fields = trimmedRecord.split(",");
-					if (fields.length < 10) { // 妫�鏌ュ瓧娈垫暟閲忥紝闇�瑕佸寘鍚珮绋嬪瓧娈�
-						continue;
-					}
-
-					String deviceId = fields.length > 15 ? sanitizeDeviceId(fields[15]) : null;
-					if (!isDeviceAccepted(deviceId)) {
-						continue;
-					}
-
-					// 妫�鏌ュ畾浣嶈川閲�
-					String fixQualityStr = fields[6];
-					if (fixQualityStr.isEmpty()) continue;
-
-					int fixQuality;
-					try {
-						fixQuality = Integer.parseInt(fixQualityStr);
-					} catch (NumberFormatException e) {
-						continue;
-					}
-
-					if (fixQuality != 4) continue;
-
-					// 鎻愬彇鍧愭爣鏁版嵁
-					String latitudeStr = fields[2];
-					String latDirection = fields[3];
-					String longitudeStr = fields[4];
-					String lonDirection = fields[5];
-
-					// 鎻愬彇娴锋嫈楂樺害锛堢10涓瓧娈碉紝绱㈠紩9锛�
-					double elevation = 0.0;
-					try {
-						String elevationStr = fields[9];
-						if (elevationStr != null && !elevationStr.isEmpty()) {
-							elevation = Double.parseDouble(elevationStr);
-						}
-					} catch (NumberFormatException e) {
-						// 楂樼▼瑙f瀽澶辫触锛屼娇鐢ㄩ粯璁ゅ��0
-						System.err.println("楂樼▼瑙f瀽澶辫触锛屼娇鐢ㄩ粯璁ゅ��0: " + fields[9]);
-					}
-
-					if (latitudeStr.isEmpty() || longitudeStr.isEmpty() || 
-							latDirection.isEmpty() || lonDirection.isEmpty()) {
-						continue;
-					}
-
-					// 鍒涘缓Coordinate瀵硅薄骞舵坊鍔犲埌鍒楄〃锛堝寘鍚珮绋嬫暟鎹級
-					Coordinate coord = new Coordinate(latitudeStr, latDirection, longitudeStr, lonDirection, elevation);
-					coordinates.add(coord);
-
-				} catch (Exception e) {
-					System.err.println("瑙f瀽GNGGA璁板綍澶辫触: " + record);
-				}
+		String[] records = gnggaData.split("\\$GNGGA");
+		for (String record : records) {
+			Coordinate coord = parseSingleGnggaRecord(record, false);
+			if (coord != null) {
+				coordinates.add(coord);
 			}
-		}	
+		}
+	}
+
+	/**
+	 * 涓插彛瀹炴椂鏁版嵁鐩存帴瑙f瀽鍏ュ彛銆�
+	 */
+	public static Coordinate dellchuankougngga(String gnggaData) {
+		if (!isStartSaveGngga || gnggaData == null) {
+			return null;
+		}
+
+		String cleaned = gnggaData.trim();
+		if (cleaned.isEmpty()) {
+			return null;
+		}
+
+		int markerIndex = cleaned.indexOf("$GNGGA");
+		String record = markerIndex >= 0
+				? cleaned.substring(markerIndex + "$GNGGA".length())
+				: cleaned;
+
+		Coordinate coordinate = parseSingleGnggaRecord(record, true);
+		if (coordinate != null) {
+			coordinates.add(coordinate);
+		}
+		return coordinate;
+	}
+
+	private static Coordinate parseSingleGnggaRecord(String record, boolean skipDeviceFilter) {
+		try {
+			String trimmedRecord = record == null ? "" : record.trim();
+			if (trimmedRecord.isEmpty()) {
+				return null;
+			}
+
+			if (!trimmedRecord.startsWith(",")) {
+				trimmedRecord = "," + trimmedRecord;
+			}
+
+			String[] fields = trimmedRecord.split(",");
+			if (fields.length < 10) {
+				return null;
+			}
+
+			String deviceId = fields.length > 15 ? sanitizeDeviceId(fields[15]) : null;
+			if (!skipDeviceFilter && !isDeviceAccepted(deviceId)) {
+				return null;
+			}
+
+			String fixQualityStr = fields[6];
+			if (fixQualityStr.isEmpty()) {
+				return null;
+			}
+
+			int fixQuality;
+			try {
+				fixQuality = Integer.parseInt(fixQualityStr);
+			} catch (NumberFormatException e) {
+				return null;
+			}
+
+			if (fixQuality != 4) {
+				return null;
+			}
+
+			String latitudeStr = fields[2];
+			String latDirection = fields[3];
+			String longitudeStr = fields[4];
+			String lonDirection = fields[5];
+
+			if (latitudeStr.isEmpty() || longitudeStr.isEmpty() ||
+					latDirection.isEmpty() || lonDirection.isEmpty()) {
+				return null;
+			}
+
+			double elevation = 0.0;
+			try {
+				String elevationStr = fields[9];
+				if (elevationStr != null && !elevationStr.isEmpty()) {
+					elevation = Double.parseDouble(elevationStr);
+				}
+			} catch (NumberFormatException e) {
+				System.err.println("楂樼▼瑙f瀽澶辫触锛屼娇鐢ㄩ粯璁ゅ��0: " + fields[9]);
+			}
+
+			return new Coordinate(latitudeStr, latDirection, longitudeStr, lonDirection, elevation);
+		} catch (Exception e) {
+			System.err.println("瑙f瀽GNGGA璁板綍澶辫触: " + record);
+			return null;
+		}
 	}
 
 	private static boolean isDeviceAccepted(String deviceId) {
diff --git a/src/zhuye/LegendDialog.java b/src/zhuye/LegendDialog.java
index 40998de..d595034 100644
--- a/src/zhuye/LegendDialog.java
+++ b/src/zhuye/LegendDialog.java
@@ -8,10 +8,11 @@
 public class LegendDialog extends JDialog {
 
     public LegendDialog(Component parent, Color ignoredThemeColor) {
-        super(parent != null ? (JFrame) SwingUtilities.getWindowAncestor(parent) : null,
-              "鍥句緥", true);
-    // 浣跨敤缁熶竴瀹藉害锛岄珮搴︾缉鍑忎负涓�鍗�
-    initializeDialog(UIConfig.DIALOG_WIDTH, UIConfig.DIALOG_HEIGHT / 2);
+      super(parent != null ? (JFrame) SwingUtilities.getWindowAncestor(parent) : null,
+          "鍥句緥", true);
+      int adjustedWidth = (int) Math.round(UIConfig.DIALOG_WIDTH * 0.8);
+    int adjustedHeight = (int) Math.round(UIConfig.DIALOG_HEIGHT * 0.4);
+    initializeDialog(adjustedWidth, adjustedHeight);
         initializeLegendContent();
         if (parent == null) {
             setLocationRelativeTo(null); // 灞呬腑鏄剧ず
@@ -31,13 +32,6 @@
         mainPanel.setBackground(Color.WHITE);
         mainPanel.setBorder(BorderFactory.createEmptyBorder(15, 15, 10, 15));
         
-        // 鏍囬 - 淇敼涓�"鍥句緥"
-        JLabel titleLabel = new JLabel("鍥句緥", JLabel.CENTER);
-        titleLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 14));
-        titleLabel.setForeground(new Color(60, 60, 60));
-        titleLabel.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
-        mainPanel.add(titleLabel, BorderLayout.NORTH);
-        
         // 鍥句緥鍐呭闈㈡澘 - 鐩存帴娣诲姞锛屼笉浣跨敤婊氬姩鏉�
         JPanel contentPanel = new JPanel();
         contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
diff --git a/src/zhuye/MapRenderer.java b/src/zhuye/MapRenderer.java
index 3352dc2..b5665ee 100644
--- a/src/zhuye/MapRenderer.java
+++ b/src/zhuye/MapRenderer.java
@@ -52,11 +52,13 @@
     private static final Color HANDHELD_BOUNDARY_BORDER = new Color(51, 102, 204, 220);
     private static final Color HANDHELD_BOUNDARY_POINT = new Color(51, 102, 204);
     private static final Color HANDHELD_BOUNDARY_LABEL = new Color(22, 62, 138);
+    private static final double BOUNDARY_CONTAINS_TOLERANCE = 0.05;
     
     // 缁勪欢寮曠敤
     private JPanel visualizationPanel;
     private List<Point2D.Double> currentBoundary;
     private Rectangle2D.Double boundaryBounds;
+    private Path2D.Double currentBoundaryPath;
     private List<Point2D.Double> currentPlannedPath;
     private Rectangle2D.Double plannedPathBounds;
     private List<Obstacledge.Obstacle> currentObstacles;
@@ -94,6 +96,7 @@
     private long lastTrackPersistTimeMillis;
     private boolean trackDirty;
     private boolean handheldBoundaryPreviewActive;
+    private boolean pendingTrackBreak = true;
 
     private static final double TRACK_SAMPLE_MIN_DISTANCE_METERS = 0.1d;
     private static final long TRACK_PERSIST_INTERVAL_MS = 5_000L;
@@ -432,15 +435,39 @@
     }
 
     private void captureRealtimeTrackPoint() {
+        if (!realtimeTrackRecording) {
+            return;
+        }
         if (realtimeTrackLandNumber == null || visualizationPanel == null) {
+            pendingTrackBreak = true;
+            return;
+        }
+        Device device = Device.getGecaoji();
+        if (device == null) {
+            pendingTrackBreak = true;
+            return;
+        }
+
+        String fixQuality = device.getPositioningStatus();
+        if (!isHighPrecisionFix(fixQuality)) {
+            pendingTrackBreak = true;
             return;
         }
         Point2D.Double position = mower.getPosition();
         if (position == null || !Double.isFinite(position.x) || !Double.isFinite(position.y)) {
+            pendingTrackBreak = true;
+            return;
+        }
+
+        if (!isPointInsideActiveBoundary(position)) {
+            pendingTrackBreak = true;
             return;
         }
 
         Point2D.Double lastPoint = realtimeMowingTrack.isEmpty() ? null : realtimeMowingTrack.get(realtimeMowingTrack.size() - 1);
+        if (pendingTrackBreak) {
+            lastPoint = null;
+        }
         double distance = 0.0;
         if (lastPoint != null) {
             double dx = position.x - lastPoint.x;
@@ -459,6 +486,7 @@
         updateCompletionMetrics();
         trackDirty = true;
         maybePersistRealtimeTrack(false);
+        pendingTrackBreak = false;
     }
 
     private void updateCompletionMetrics() {
@@ -575,16 +603,19 @@
 
         realtimeTrackLandNumber = normalizedLand;
         realtimeTrackRecording = true;
+        pendingTrackBreak = true;
         captureRealtimeTrackPoint();
     }
 
     public void pauseRealtimeTrackRecording() {
         realtimeTrackRecording = false;
+        pendingTrackBreak = true;
         maybePersistRealtimeTrack(true);
     }
 
     public void stopRealtimeTrackRecording() {
         realtimeTrackRecording = false;
+        pendingTrackBreak = true;
         maybePersistRealtimeTrack(true);
     }
 
@@ -602,15 +633,22 @@
         completedMowingAreaSqMeters = 0.0;
         mowingCompletionRatio = 0.0;
         trackDirty = true;
+        pendingTrackBreak = true;
         maybePersistRealtimeTrack(true);
         visualizationPanel.repaint();
     }
 
     public double getMowingCompletionRatio() {
+        if (!isMowerInsideSelectedBoundary()) {
+            return 0.0;
+        }
         return mowingCompletionRatio;
     }
 
     public double getCompletedMowingAreaSqMeters() {
+        if (!isMowerInsideSelectedBoundary()) {
+            return 0.0;
+        }
         return completedMowingAreaSqMeters;
     }
 
@@ -622,6 +660,14 @@
         return trackLengthMeters;
     }
 
+    private boolean isMowerInsideSelectedBoundary() {
+        Point2D.Double position = mower.getPosition();
+        if (position == null) {
+            return false;
+        }
+        return isPointInsideActiveBoundary(position);
+    }
+
     public void flushRealtimeTrack() {
         maybePersistRealtimeTrack(true);
     }
@@ -635,6 +681,7 @@
         mowingCompletionRatio = 0.0;
         trackDirty = false;
         lastTrackPersistTimeMillis = 0L;
+        pendingTrackBreak = true;
 
         String trimmed = normalizeValue(trackData);
         if (trimmed == null || trimmed.isEmpty()) {
@@ -1071,7 +1118,7 @@
         mowerNumberValueLabel.setText(formatDeviceValue(device.getMowerNumber()));
         realtimeXValueLabel.setText(formatDeviceValue(device.getRealtimeX()));
         realtimeYValueLabel.setText(formatDeviceValue(device.getRealtimeY()));
-        positioningStatusValueLabel.setText(formatDeviceValue(device.getPositioningStatus()));
+    positioningStatusValueLabel.setText(formatFixQualityValue(device.getPositioningStatus()));
         satelliteCountValueLabel.setText(formatDeviceValue(device.getSatelliteCount()));
         realtimeSpeedValueLabel.setText(formatDeviceValue(device.getRealtimeSpeed()));
         headingValueLabel.setText(formatDeviceValue(device.getHeading()));
@@ -1082,7 +1129,7 @@
         if (mowerNumberValueLabel != null) mowerNumberValueLabel.setText(value);
         if (realtimeXValueLabel != null) realtimeXValueLabel.setText(value);
         if (realtimeYValueLabel != null) realtimeYValueLabel.setText(value);
-        if (positioningStatusValueLabel != null) positioningStatusValueLabel.setText(value);
+    if (positioningStatusValueLabel != null) positioningStatusValueLabel.setText(value);
         if (satelliteCountValueLabel != null) satelliteCountValueLabel.setText(value);
         if (realtimeSpeedValueLabel != null) realtimeSpeedValueLabel.setText(value);
         if (headingValueLabel != null) headingValueLabel.setText(value);
@@ -1105,6 +1152,37 @@
         return sanitized == null ? "--" : sanitized;
     }
 
+    private String formatFixQualityValue(String value) {
+        String sanitized = sanitizeDeviceValue(value);
+        if (sanitized == null) {
+            return "--";
+        }
+        switch (sanitized) {
+            case "0":
+                return "鏈畾浣�";
+            case "1":
+                return "鍗曠偣瀹氫綅";
+            case "2":
+                return "鐮佸樊鍒�";
+            case "3":
+                return "鏃犳晥PPS";
+            case "4":
+                return "鍥哄畾瑙�";
+            case "5":
+                return "娴偣瑙�";
+            case "6":
+                return "姝e湪浼扮畻";
+            case "7":
+                return "浜哄伐杈撳叆鍥哄畾鍊�";
+            case "8":
+                return "妯℃嫙妯″紡";
+            case "9":
+                return "WAAS宸垎";
+            default:
+                return sanitized;
+        }
+    }
+
     private String formatTimestamp(String value) {
         String sanitized = sanitizeDeviceValue(value);
         if (sanitized == null) {
@@ -1207,6 +1285,7 @@
 
         if (updated.size() < 2) {
             currentBoundary = null;
+            currentBoundaryPath = null;
             boundaryBounds = null;
             boundaryPointsVisible = false;
             Dikuaiguanli.updateBoundaryPointVisibility(currentBoundaryLandNumber, false);
@@ -1214,10 +1293,12 @@
             adjustViewAfterBoundaryReset();
         } else {
             currentBoundary = updated;
+            rebuildBoundaryPath();
             boundaryBounds = computeBounds(updated);
             Dikuaiguanli.updateBoundaryPointVisibility(currentBoundaryLandNumber, boundaryPointsVisible);
             visualizationPanel.repaint();
         }
+        pendingTrackBreak = true;
     }
 
     private boolean persistBoundaryChanges(List<Point2D.Double> updatedBoundary) {
@@ -1279,6 +1360,79 @@
         return Math.hypot(dx, dy) <= BOUNDARY_POINT_MERGE_THRESHOLD;
     }
 
+    private boolean isHighPrecisionFix(String fixQuality) {
+        if (fixQuality == null) {
+            return false;
+        }
+        String trimmed = fixQuality.trim();
+        if (trimmed.isEmpty()) {
+            return false;
+        }
+        if ("4".equals(trimmed)) {
+            return true;
+        }
+        try {
+            double value = Double.parseDouble(trimmed);
+            return Math.abs(value - 4.0d) < 1e-6;
+        } catch (NumberFormatException ex) {
+            return false;
+        }
+    }
+
+    private boolean isPointInsideActiveBoundary(Point2D.Double point) {
+        if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
+            return false;
+        }
+        if (realtimeTrackLandNumber == null) {
+            return false;
+        }
+        if (currentBoundaryLandNumber != null && !currentBoundaryLandNumber.equals(realtimeTrackLandNumber)) {
+            return false;
+        }
+
+        Path2D.Double path = currentBoundaryPath;
+        if (path == null) {
+            path = buildBoundaryPath(currentBoundary);
+            currentBoundaryPath = path;
+        }
+        if (path == null) {
+            return false;
+        }
+        if (path.contains(point.x, point.y)) {
+            return true;
+        }
+        double size = BOUNDARY_CONTAINS_TOLERANCE * 2.0;
+        return path.intersects(point.x - BOUNDARY_CONTAINS_TOLERANCE, point.y - BOUNDARY_CONTAINS_TOLERANCE, size, size);
+    }
+
+    private void rebuildBoundaryPath() {
+        currentBoundaryPath = buildBoundaryPath(currentBoundary);
+    }
+
+    private Path2D.Double buildBoundaryPath(List<Point2D.Double> boundary) {
+        if (boundary == null || boundary.size() < 3) {
+            return null;
+        }
+        Path2D.Double path = new Path2D.Double();
+        boolean started = false;
+        for (Point2D.Double point : boundary) {
+            if (point == null || !Double.isFinite(point.x) || !Double.isFinite(point.y)) {
+                continue;
+            }
+            if (!started) {
+                path.moveTo(point.x, point.y);
+                started = true;
+            } else {
+                path.lineTo(point.x, point.y);
+            }
+        }
+        if (!started) {
+            return null;
+        }
+        path.closePath();
+        return path;
+    }
+
     
     /**
      * 缁樺埗瑙嗗浘淇℃伅
@@ -1360,8 +1514,10 @@
             return;
         }
 
-        currentBoundary = parsed;
-        boundaryBounds = computeBounds(parsed);
+    currentBoundary = parsed;
+    rebuildBoundaryPath();
+    pendingTrackBreak = true;
+    boundaryBounds = computeBounds(parsed);
 
         Rectangle2D.Double bounds = boundaryBounds;
         SwingUtilities.invokeLater(() -> {
@@ -1372,10 +1528,12 @@
 
     private void clearBoundaryData() {
         currentBoundary = null;
+        currentBoundaryPath = null;
         boundaryBounds = null;
         boundaryName = null;
         boundaryPointsVisible = false;
         currentBoundaryLandNumber = null;
+        pendingTrackBreak = true;
     }
 
     public void setCurrentObstacles(String obstaclesData, String landNumber) {
diff --git a/src/zhuye/Shouye.java b/src/zhuye/Shouye.java
index eb353c0..b626aee 100644
--- a/src/zhuye/Shouye.java
+++ b/src/zhuye/Shouye.java
@@ -9,6 +9,7 @@
 import java.awt.*;
 import java.awt.event.*;
 
+import chuankou.dellmessage;
 import dikuai.Dikuai;
 import dikuai.Dikuaiguanli;
 import dikuai.addzhangaiwu;
@@ -21,6 +22,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Consumer;
 import java.awt.geom.Point2D;
 
 /**
@@ -52,9 +55,11 @@
     private JButton areaSelectBtn;
     private JButton baseStationBtn;
     private JButton bluetoothBtn;
+    private JLabel dataPacketCountLabel;
     private JLabel mowerSpeedValueLabel;
     private JLabel mowerSpeedUnitLabel;
     private JLabel mowingProgressLabel;
+    private FixQualityIndicator fixQualityIndicator;
     
     // 瀵艰埅鎸夐挳
     private JButton homeNavBtn;
@@ -75,6 +80,8 @@
     private BaseStationDialog baseStationDialog;
     private Sets settingsDialog;
     private BaseStation baseStation;
+
+    private final Consumer<String> serialLineListener = line -> SwingUtilities.invokeLater(this::updateDataPacketCountLabel);
     
     // 鍦板浘娓叉煋鍣�
     private MapRenderer mapRenderer;
@@ -124,6 +131,7 @@
         instance = this;
         baseStation = new BaseStation();
         baseStation.load();
+    dellmessage.registerLineListener(serialLineListener);
         initializeUI();
         setupEventHandlers();
     }
@@ -293,7 +301,7 @@
         
         mainContentPanel.add(visualizationPanel, BorderLayout.CENTER);
 
-        startMowerSpeedUpdates();
+    startMowerSpeedUpdates();
     }
     
     private void createControlPanel() {
@@ -681,14 +689,14 @@
             return;
         }
         startButtonShowingPause = !startButtonShowingPause;
-        if (startButtonShowingPause) {
+        if (!startButtonShowingPause) {
             statusLabel.setText("浣滀笟涓�");
             if (stopButtonActive) {
                 stopButtonActive = false;
                 updateStopButtonIcon();
             }
             if (!beginMowingSession()) {
-                startButtonShowingPause = false;
+                startButtonShowingPause = true;
                 statusLabel.setText("寰呮満");
                 updateStartButtonAppearance();
                 return;
@@ -767,10 +775,14 @@
     }
 
     private JPanel createSpeedIndicatorPanel() {
-        JPanel panel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
+        JPanel panel = new JPanel(new BorderLayout());
         panel.setOpaque(false);
         panel.setBorder(BorderFactory.createEmptyBorder(10, 20, 5, 20));
 
+        JPanel rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0));
+        rightPanel.setOpaque(false);
+
+        fixQualityIndicator = new FixQualityIndicator();
         mowingProgressLabel = new JLabel("--%");
         mowingProgressLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
         mowingProgressLabel.setForeground(THEME_COLOR);
@@ -783,9 +795,32 @@
         mowerSpeedUnitLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 9));
         mowerSpeedUnitLabel.setForeground(THEME_COLOR);
 
-        panel.add(mowingProgressLabel);
-        panel.add(mowerSpeedValueLabel);
-        panel.add(mowerSpeedUnitLabel);
+        dataPacketCountLabel = new JLabel("--");
+        dataPacketCountLabel.setFont(new Font("寰蒋闆呴粦", Font.BOLD, 12));
+        dataPacketCountLabel.setForeground(THEME_COLOR);
+
+        rightPanel.add(fixQualityIndicator);
+
+        JSeparator areaSeparator = new JSeparator(SwingConstants.VERTICAL);
+        areaSeparator.setPreferredSize(new Dimension(1, 16));
+        rightPanel.add(areaSeparator);
+
+        rightPanel.add(mowingProgressLabel);
+    JSeparator speedSeparator = new JSeparator(SwingConstants.VERTICAL);
+    speedSeparator.setPreferredSize(new Dimension(1, 16));
+    rightPanel.add(speedSeparator);
+        rightPanel.add(mowerSpeedValueLabel);
+        rightPanel.add(mowerSpeedUnitLabel);
+
+        JSeparator separator = new JSeparator(SwingConstants.VERTICAL);
+        separator.setPreferredSize(new Dimension(1, 16));
+        rightPanel.add(separator);
+
+        rightPanel.add(dataPacketCountLabel);
+
+        panel.add(rightPanel, BorderLayout.EAST);
+        updateFixQualityIndicator();
+        updateDataPacketCountLabel();
         return panel;
     }
 
@@ -817,6 +852,89 @@
             mowerSpeedUnitLabel.setText("km/h");
         }
         updateMowingProgressLabel();
+        updateFixQualityIndicator();
+        updateDataPacketCountLabel();
+    }
+
+    private void updateDataPacketCountLabel() {
+        if (dataPacketCountLabel == null) {
+            return;
+        }
+        int udpCount = UDPServer.getReceivedPacketCount();
+        int serialCount = dellmessage.getProcessedLineCount();
+        int displayCount = Math.max(udpCount, serialCount);
+
+        if (displayCount <= 0) {
+            dataPacketCountLabel.setText("--");
+            dataPacketCountLabel.setToolTipText(null);
+        } else {
+            dataPacketCountLabel.setText(String.valueOf(displayCount));
+            dataPacketCountLabel.setToolTipText(String.format("涓插彛: %d  UDP: %d", serialCount, udpCount));
+        }
+    }
+
+    private void updateFixQualityIndicator() {
+        if (fixQualityIndicator == null) {
+            return;
+        }
+        Device device = Device.getGecaoji();
+        String code = null;
+        if (device != null) {
+            code = sanitizeDeviceValue(device.getPositioningStatus());
+        }
+        fixQualityIndicator.setQuality(code);
+    }
+
+    private Color resolveFixQualityColor(String code) {
+        if (code == null) {
+            return new Color(160, 160, 160);
+        }
+        switch (code) {
+            case "0":
+                return new Color(160, 160, 160);
+            case "1":
+                return new Color(52, 152, 219);
+            case "2":
+                return new Color(26, 188, 156);
+            case "3":
+                return new Color(155, 89, 182);
+            case "4":
+                return THEME_COLOR;
+            case "5":
+                return new Color(241, 196, 15);
+            case "6":
+                return new Color(231, 76, 60);
+            case "7":
+                return new Color(230, 126, 34);
+            default:
+                return new Color(95, 95, 95);
+        }
+    }
+
+    private String resolveFixQualityDescription(String code) {
+        if (code == null) {
+            return "鏈煡";
+        }
+        switch (code) {
+            case "0":
+                return "鏈畾浣�";
+            case "1":
+                return "鍗曠偣瀹氫綅";
+            case "2":
+                return "鐮佸樊鍒�";
+            case "3":
+                return "鏃犳晥PPS";
+            case "4":
+                return "鍥哄畾瑙�";
+            case "5":
+                return "娴偣瑙�";
+            case "6":
+                return "姝e湪浼扮畻";
+            case "7":
+                return "浜哄伐杈撳叆鍥哄畾鍊�";
+            default:
+                return "鍏朵粬";
+        }
     }
 
     private String sanitizeSpeedValue(String raw) {
@@ -833,6 +951,17 @@
         return trimmed;
     }
 
+    private String sanitizeDeviceValue(String raw) {
+        if (raw == null) {
+            return null;
+        }
+        String trimmed = raw.trim();
+        if (trimmed.isEmpty() || "-1".equals(trimmed) || "null".equalsIgnoreCase(trimmed)) {
+            return null;
+        }
+        return trimmed;
+    }
+
     private void updateMowingProgressLabel() {
         if (mowingProgressLabel == null) {
             return;
@@ -1621,6 +1750,7 @@
             return;
         }
         circleGuidanceStep = step;
+
         if (step == 1) {
             circleGuidanceLabel.setText("閲囬泦绗�1涓偣");
             circleGuidancePrimaryButton.setText("纭绗�1鐐�");
@@ -2267,6 +2397,48 @@
         return !"鏈�夋嫨鍦板潡".equals(trimmed);
     }
 
+    private final class FixQualityIndicator extends JComponent {
+        private static final long serialVersionUID = 1L;
+        private static final int DIAMETER = 16;
+        private String currentCode;
+        private Color currentColor = new Color(160, 160, 160);
+
+        private FixQualityIndicator() {
+            setPreferredSize(new Dimension(DIAMETER, DIAMETER));
+            setMinimumSize(new Dimension(DIAMETER, DIAMETER));
+            setMaximumSize(new Dimension(DIAMETER, DIAMETER));
+            setToolTipText("鏈煡");
+        }
+
+        private void setQuality(String code) {
+            if (Objects.equals(currentCode, code)) {
+                return;
+            }
+            currentCode = code;
+            currentColor = resolveFixQualityColor(code);
+            setToolTipText(resolveFixQualityDescription(code));
+            repaint();
+        }
+
+        @Override
+        protected void paintComponent(Graphics g) {
+            super.paintComponent(g);
+            Graphics2D g2 = (Graphics2D) g.create();
+            try {
+                g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+                int diameter = Math.min(getWidth(), getHeight()) - 2;
+                int x = (getWidth() - diameter) / 2;
+                int y = (getHeight() - diameter) / 2;
+                g2.setColor(currentColor);
+                g2.fillOval(x, y, diameter, diameter);
+                g2.setColor(new Color(255, 255, 255, 128));
+                g2.drawOval(x, y, diameter, diameter);
+            } finally {
+                g2.dispose();
+            }
+        }
+    }
+
     // 娴嬭瘯鏂规硶
     public static void main(String[] args) {
         JFrame frame = new JFrame("AutoMow - 棣栭〉");

--
Gitblit v1.10.0