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