From 487a5b943571d6ab57c4baddca1cbcc7b2062e73 Mon Sep 17 00:00:00 2001 From: 826220679@qq.com <826220679@qq.com> Date: 星期三, 27 八月 2025 23:06:57 +0800 Subject: [PATCH] 20250827 --- src/home/Dell55AA1FParser.java | 36 + bin/home/Dell55AA1FParser.class | 0 bin/home/AOAFollowSystem$HomePanel.class | 0 bin/home/VisualizationPanel$1.class | 0 bin/home/AOAFollowSystem$1.class | 0 src/home/TagDell55AA03Parser.java | 236 +++++++++++++ systemfile/logfile/openlog.txt | 220 ++++++++++++ bin/home/AOAFollowSystem$HomePanel$1.class | 0 systemfile/1.png | 0 systemfile/Messages_en.properties | 5 systemfile/Messages_zh.properties | 5 bin/home/VisualizationPanel.class | 0 src/home/VisualizationPanel.java | 56 ++ src/home/AOAFollowSystem.java | 217 ++++++++---- bin/home/AOAFollowSystem$HomePanel$2.class | 0 bin/home/AOAFollowSystem.class | 0 src/home/Dell55AA51Parser.java | 228 +++++++++++++ bin/home/Dell55AA1FParser$ParseResult.class | 0 18 files changed, 909 insertions(+), 94 deletions(-) diff --git a/bin/home/AOAFollowSystem$1.class b/bin/home/AOAFollowSystem$1.class index a4024a2..9185775 100644 --- a/bin/home/AOAFollowSystem$1.class +++ b/bin/home/AOAFollowSystem$1.class Binary files differ diff --git a/bin/home/AOAFollowSystem$HomePanel$1.class b/bin/home/AOAFollowSystem$HomePanel$1.class index d46b37e..a26d366 100644 --- a/bin/home/AOAFollowSystem$HomePanel$1.class +++ b/bin/home/AOAFollowSystem$HomePanel$1.class Binary files differ diff --git a/bin/home/AOAFollowSystem$HomePanel$2.class b/bin/home/AOAFollowSystem$HomePanel$2.class index 8ca4a8f..5e4cc60 100644 --- a/bin/home/AOAFollowSystem$HomePanel$2.class +++ b/bin/home/AOAFollowSystem$HomePanel$2.class Binary files differ diff --git a/bin/home/AOAFollowSystem$HomePanel.class b/bin/home/AOAFollowSystem$HomePanel.class index 7c2825e..ed94e59 100644 --- a/bin/home/AOAFollowSystem$HomePanel.class +++ b/bin/home/AOAFollowSystem$HomePanel.class Binary files differ diff --git a/bin/home/AOAFollowSystem.class b/bin/home/AOAFollowSystem.class index 86ebcc8..42f24c4 100644 --- a/bin/home/AOAFollowSystem.class +++ b/bin/home/AOAFollowSystem.class Binary files differ diff --git a/bin/home/Dell55AA1FParser$ParseResult.class b/bin/home/Dell55AA1FParser$ParseResult.class index 5179218..b4e7737 100644 --- a/bin/home/Dell55AA1FParser$ParseResult.class +++ b/bin/home/Dell55AA1FParser$ParseResult.class Binary files differ diff --git a/bin/home/Dell55AA1FParser.class b/bin/home/Dell55AA1FParser.class index 82d8494..123f8f8 100644 --- a/bin/home/Dell55AA1FParser.class +++ b/bin/home/Dell55AA1FParser.class Binary files differ diff --git a/bin/home/VisualizationPanel$1.class b/bin/home/VisualizationPanel$1.class index 0fbade4..8c57660 100644 --- a/bin/home/VisualizationPanel$1.class +++ b/bin/home/VisualizationPanel$1.class Binary files differ diff --git a/bin/home/VisualizationPanel.class b/bin/home/VisualizationPanel.class index b288737..4813e2b 100644 --- a/bin/home/VisualizationPanel.class +++ b/bin/home/VisualizationPanel.class Binary files differ diff --git a/src/home/AOAFollowSystem.java b/src/home/AOAFollowSystem.java index 432b760..499cbea 100644 --- a/src/home/AOAFollowSystem.java +++ b/src/home/AOAFollowSystem.java @@ -110,6 +110,12 @@ case "ascii": return "ASCII"; case "hex_send": return "HEX鍙戦��"; case "display_format": return "鏄剧ず鏍煎紡"; + case "pair_tooltip": + return currentLocale.equals(Locale.SIMPLIFIED_CHINESE) ? + "鐐瑰嚮鍙戦�佸悗杩涘叆20绉掗厤瀵规ā寮�" : + "Enter 20-second pairing mode after clicking send"; + case "scale": + return currentLocale.equals(Locale.SIMPLIFIED_CHINESE) ? "缂╂斁" : "Scale"; default: return key; } } @@ -188,7 +194,10 @@ // 鏇存柊鍚勯潰鏉挎枃鏈� homePanel.updateLanguage(); configPanel.updateLanguage(); + // 鏇存柊閰嶅澶嶉�夋鐨勬彁绀轰俊鎭� + homePanel.updatePairCheckboxTooltip(); } + private JButton createNavButton(String text, Color color) { JButton button = new JButton(text); @@ -301,10 +310,26 @@ formatPanel.add(displayFormatLabel); ButtonGroup formatGroup = new ButtonGroup(); - hexRadio = new JRadioButton(getString("hex"), true); // 榛樿閫変腑HEX - asciiRadio = new JRadioButton(getString("ascii")); - + asciiRadio = new JRadioButton(getString("ascii"), true); // 榛樿閫変腑ASCI + asciiRadio.addItemListener(e -> { + if (asciiRadio.isSelected()) { + asciiRadio.setForeground(Color.RED); + } else { + asciiRadio.setForeground(null); // 鎭㈠榛樿棰滆壊 + } + }); + hexRadio = new JRadioButton(getString("hex"), false); // 榛樿涓嶉�変腑HEX + hexRadio.addItemListener(e -> { + if (hexRadio.isSelected()) { + hexRadio.setForeground(Color.RED); + } else { + hexRadio.setForeground(null); // 鎭㈠榛樿棰滆壊 + } + }); + pairCheckBox = new JCheckBox(getString("pair"), false); // 榛樿涓嶅嬀閫� + // 涓洪厤瀵瑰閫夋娣诲姞鎻愮ず淇℃伅 + pairCheckBox.setToolTipText(getString("pair_tooltip")); pairCheckBox.addItemListener(e -> { if (pairCheckBox.isSelected()) { sendField.setText("55AA030304F5FF"); @@ -413,7 +438,7 @@ sendBtn.addActionListener(e -> sendData()); // 鏂板锛欻EX鍙戦�佸閫夋 - hexSendCheckBox = new JCheckBox(getString("hex_send"), true); // 榛樿鍕鹃�� + hexSendCheckBox = new JCheckBox(getString("hex_send"), false); // 榛樿鍕鹃�� sendControlPanel.add(hexSendCheckBox, BorderLayout.WEST); sendControlPanel.add(sendBtn, BorderLayout.EAST); @@ -426,6 +451,11 @@ // 娣诲姞宸﹀彸闈㈡澘 add(leftPanel, BorderLayout.CENTER); add(rightPanel, BorderLayout.EAST); + } + + // 鍦℉omePanel绫讳腑娣诲姞鏇存柊閰嶅澶嶉�夋鎻愮ず淇℃伅鐨勬柟娉� + public void updatePairCheckboxTooltip() { + pairCheckBox.setToolTipText(getString("pair_tooltip")); } private JButton createColoredButton(String text, Color color) { @@ -566,74 +596,116 @@ } private void processReceivedData(byte[] data) { - // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 - hexBuilder.setLength(0); - for (byte b : data) { - hexBuilder.append(String.format("%02X", b)); - } - String displayText = hexBuilder.toString(); - - String displayText1 = new String(data).replaceAll("\\s+", ""); - - if (displayText.startsWith("55AA1F")) { - Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0); - if (result != null) { - updateTable(result); - visualizationPanel.updatePosition(result.distance, result.angle,result.signalQuality); - visualizationPanel.setTagId(result.tagId); - } - }else if(displayText.startsWith("55AA01")) { - ParseResult result=Dell55AA01Parser.parse(displayText, "127.0.0.1", 0); - visualizationPanel.updatePosition(result.distance,270,0); - visualizationPanel.setTagId(result.tagId); - - } + // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 + hexBuilder.setLength(0); + for (byte b : data) { + hexBuilder.append(String.format("%02X", b)); + } + String displayText = hexBuilder.toString(); + + String displayText1 = new String(data).replaceAll("\\s+", ""); + + // 鍏堟鏌ユ暟鎹暱搴︽槸鍚﹁冻澶熻繘琛岃В鏋� + if (displayText.length() < 30) { // 鑷冲皯闇�瑕�30涓瓧绗�(15瀛楄妭)鎵嶈兘瑙f瀽鍩烘湰瀛楁 + appendLog("鏁版嵁闀垮害涓嶈冻: " + displayText); + return; + } + + if (displayText.startsWith("55AA1F")) { + Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + updateTable(result); + visualizationPanel.updatePosition(result.distance, result.angle, result.signalQuality); + visualizationPanel.setTagId(result.tagId); + } else { + appendLog("55AA1F鍗忚瑙f瀽澶辫触: " + displayText); + } + } else if(displayText.startsWith("55AA01")) { + ParseResult result = Dell55AA01Parser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + visualizationPanel.updatePosition(result.distance, 270, 0); + visualizationPanel.setTagId(result.tagId); + } else { + appendLog("55AA01鍗忚瑙f瀽澶辫触: " + displayText); + } + } else if(displayText.startsWith("55AA51")) { + Dell55AA51Parser.ParseResult result = Dell55AA51Parser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + visualizationPanel.updatePosition(result.distance, result.horizontalAngle, result.signalStrength); + visualizationPanel.setTagId(result.tagId); + } else { + appendLog("55AA51鍗忚瑙f瀽澶辫触: " + displayText); + } + } else { + // 濡傛灉涓嶆槸宸茬煡鍗忚锛岀洿鎺ヨ褰曞師濮嬫暟鎹� + appendLog("鏈煡鍗忚: " + displayText); + } - if (hexRadio.isSelected()) { - appendLog(displayText); - } else { - if (displayText.startsWith("55AA1F")) { - Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0); - // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 - displayBuilder.setLength(0); - displayBuilder.append("1F:") - .append(result.dataLength) - .append(",") - .append(result.messageType) - .append(",id:") - .append(result.tagId) - .append(",Dis:") - .append(result.distance) - .append("cm,Angle:") - .append(result.angle) - .append("掳,Signal:") - .append(result.signalQuality) - .append(",Button:") - .append(result.buttonPressed) - .append(",Power:") - .append(result.power); - appendLog(displayBuilder.toString()); - }else if(displayText.startsWith("55AA01")) { - ParseResult result=Dell55AA01Parser.parse(displayText, "127.0.0.1", 0); - // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 - displayBuilder.setLength(0); - displayBuilder.append("55AA01 Seq:") - .append(result.sequenceNum) - .append(",Tagid:") - .append(result.tagId) - .append(",Anchorid:") - .append(result.anchorId) - .append(",Distance:") - .append(result.distance) - .append(",Power:") - .append(result.power) - .append(",Button:") - .append(result.buttonPressed); - appendLog(displayBuilder.toString()); - }else { - appendLog(displayText1); - } - } + if (hexRadio.isSelected()) { + appendLog(displayText); + } else { + if (displayText.startsWith("55AA1F")) { + Dell55AA1FParser.ParseResult result = Dell55AA1FParser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 + displayBuilder.setLength(0); + displayBuilder.append(result.tagId) + .append(",Dis:") + .append(result.distance) + .append("cm,Angle:") + .append(result.angle) + .append("掳,Signal:") + .append(result.signalQuality) + .append(",Button:") + .append(result.buttonPressed) + .append(",Power:") + .append(result.power); + appendLog(displayBuilder.toString()); + } + } else if(displayText.startsWith("55AA01")) { + ParseResult result = Dell55AA01Parser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + // 浼樺寲锛氶噸鐢⊿tringBuilder瀵硅薄 + displayBuilder.setLength(0); + displayBuilder.append("55AA01 Seq:") + .append(result.sequenceNum) + .append(",Tagid:") + .append(result.tagId) + .append(",Anchorid:") + .append(result.anchorId) + .append(",Distance:") + .append(result.distance) + .append(",Power:") + .append(result.power) + .append(",Button:") + .append(result.buttonPressed); + appendLog(displayBuilder.toString()); + } + } else if(displayText.startsWith("55AA51")) { + Dell55AA51Parser.ParseResult result = Dell55AA51Parser.parse(displayText, "127.0.0.1", 0); + if (result != null) { + displayBuilder.setLength(0); + displayBuilder.append(result.tagId) + .append(",Dis:") + .append(result.distance) + .append("cm,HAngle:") + .append(result.horizontalAngle) + .append("掳,VAngle:") + .append(result.verticalAngle) + .append("掳,Signal:") + .append(result.signalStrength) + .append("掳,ASignal:") + .append(result.angleConfidence) + .append(",Power:") + .append(result.tagBattery) + .append("%,Status:") + .append(result.deviceStatus); + appendLog(displayBuilder.toString()); + } + } else { + appendLog(displayText1); + } + } } private void appendLog(String message) { @@ -732,7 +804,8 @@ asciiRadio.setText(getString("ascii")); hexSendCheckBox.setText(getString("hex_send")); pairCheckBox.setText(getString("pair")); - + // 鏇存柊閰嶅澶嶉�夋鐨勬彁绀轰俊鎭� + updatePairCheckboxTooltip(); // 鏇存柊杈规鏍囬 updateBorderTitles(this); diff --git a/src/home/Dell55AA1FParser.java b/src/home/Dell55AA1FParser.java index ad2cdf4..31f2104 100644 --- a/src/home/Dell55AA1FParser.java +++ b/src/home/Dell55AA1FParser.java @@ -4,6 +4,7 @@ public class Dell55AA1FParser { // 常量定义 private static final String EXPECTED_HEADER = "55AA1F"; // 协议头 + private static final int MIN_PACKET_LENGTH = 36; // 最小包长度(字符数): 固定字段18字节=36字符 private static final int FIXED_FIELDS_LENGTH = 11; // 从标签ID到保留字段的固定长度(字节) private static final ThreadLocal<ParseResult> RESULT_CACHE = ThreadLocal.withInitial(ParseResult::new); @@ -44,6 +45,7 @@ */ public static ParseResult parse(String message, String ip, int port) { if (message == null || message.isEmpty()) { + System.err.println("Empty message from " + ip + ":" + port); return null; } @@ -51,12 +53,22 @@ char[] cleanedMessage = cleanMessage(message); if (cleanedMessage == null) { + System.err.println("Invalid characters in message from " + ip + ":" + port); + return null; + } + + // 检查最小长度 + if (cleanedMessage.length < MIN_PACKET_LENGTH) { + System.err.println("Message too short from " + ip + ":" + port + + ". Expected at least " + MIN_PACKET_LENGTH + + " characters, got " + cleanedMessage.length); return null; } // 协议头校验 (55AA1F) if (cleanedMessage.length < 6 || !new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) { + System.err.println("Invalid header in message from " + ip + ":" + port); return null; } @@ -68,13 +80,12 @@ result.dataLength = HexUtils.fastHexToByte(cleanedMessage[6], cleanedMessage[7]); // 计算期望的总字符长度 - int expectedCharLength = 8 + // 包头(4字符) + 消息类型(2字符) + 数据长度(2字符) - result.dataLength * 2 + // 数据部分 - 4; // 校验和(4字符) + int expectedCharLength = 14 + result.dataLength * 2; // 14=包头6+消息类型2+数据长度2+校验和4 -// if (cleanedMessage.length != expectedCharLength) { -// System.err.println("Data length mismatch: expected " + expectedCharLength + -// ", got " + cleanedMessage.length); +// if (cleanedMessage.length < expectedCharLength) { +// System.err.println("Incomplete message from " + ip + ":" + port + +// ". Expected " + expectedCharLength + +// " characters, got " + cleanedMessage.length); // return null; // } @@ -83,6 +94,12 @@ // 解析标签ID (位置8-11),直接取字符串 result.tagId = new String(cleanedMessage, 8, 4); + + // 检查是否有足够长度解析固定字段 + if (cleanedMessage.length < 30) { // 需要至少15字节=30字符 + System.err.println("Message too short for fixed fields from " + ip + ":" + port); + return null; + } // 解析距离 (位置12-15, 2字节小端整数) int distLow = HexUtils.fastHexToByte(cleanedMessage[12], cleanedMessage[13]); @@ -111,7 +128,12 @@ // int userDataStart = 30; // int userDataCharLength = (result.dataLength - FIXED_FIELDS_LENGTH) * 2; // if (userDataCharLength > 0) { -// result.userData = new String(cleanedMessage, userDataStart, userDataCharLength); +// if (userDataStart + userDataCharLength <= cleanedMessage.length) { +// result.userData = new String(cleanedMessage, userDataStart, userDataCharLength); +// } else { +// System.err.println("User data truncated in message from " + ip + ":" + port); +// result.userData = ""; +// } // } else { // result.userData = ""; // } diff --git a/src/home/Dell55AA51Parser.java b/src/home/Dell55AA51Parser.java new file mode 100644 index 0000000..f40e10e --- /dev/null +++ b/src/home/Dell55AA51Parser.java @@ -0,0 +1,228 @@ +package home; + +import java.util.Arrays; + +public class Dell55AA51Parser { + // 预期包头 + private static final String EXPECTED_HEADER = "55AA51"; + private static final ThreadLocal<ParseResult> RESULT_CACHE = + ThreadLocal.withInitial(ParseResult::new); + + // 解析结果类 + public static class ParseResult { + public int dataLength; // 数据长度(十进制) + public String baseStationId; // 基站ID(2字节,低位在前) + public String tagId; // 标签ID(2字节,低位在前) + public int packetSequence; // 包序(十进制) + public int distance; // 测距距离(十进制) + public int horizontalAngle; // 水平角度(十进制) + public int verticalAngle; // 俯仰角度(十进制,有符号) + public int signalStrength; // 信号强度(十进制) + public int angleConfidence; // 角度置信度(十进制) + public int tagBattery; // 标签电量(十进制) + public int deviceStatus; // 设备状态 + public int airPressure; // 气压值(十进制) + public String reserved; // 保留字段 + public String checksum; // 校验和 + + public void reset() { + dataLength = 0; + baseStationId = ""; + tagId = ""; + packetSequence = 0; + distance = 0; + horizontalAngle = 0; + verticalAngle = 0; + signalStrength = 0; + angleConfidence = 0; + tagBattery = 0; + deviceStatus = 0; + airPressure = 0; + reserved = ""; + checksum = ""; + } + + @Override + public String toString() { + return "ParseResult{" + + "dataLength=" + dataLength + + ", baseStationId='" + baseStationId + '\'' + + ", tagId='" + tagId + '\'' + + ", packetSequence=" + packetSequence + + ", distance=" + distance + + ", horizontalAngle=" + horizontalAngle + + ", verticalAngle=" + verticalAngle + + ", signalStrength=" + signalStrength + + ", angleConfidence=" + angleConfidence + + ", tagBattery=" + tagBattery + + ", deviceStatus=" + deviceStatus + + ", airPressure=" + airPressure + + ", reserved='" + reserved + '\'' + + ", checksum='" + checksum + '\'' + + '}'; + } + } + + /** + * 解析55AA51数据包 + * @param message 原始十六进制字符串 + * @return 解析结果(解析失败返回null) + */ + public static ParseResult parse(String message, String ip, int port) { + if (message == null || message.isEmpty()) { + return null; + } + + // 清理消息:移除非十六进制字符 + char[] cleanedMessage = cleanMessage(message); + + if (cleanedMessage == null) { + return null; + } + + // 检查包头 (55AA51) + if (cleanedMessage.length < 6 || + !new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) { + return null; + } + + ParseResult result = RESULT_CACHE.get(); + result.reset(); + + try { + // 解析数据长度 (位置6-7),转换为十进制 + result.dataLength = HexUtils.fastHexToByte(cleanedMessage[6], cleanedMessage[7]); + + // 计算期望的消息长度 + int expectedCharLength = 6 + // 包头(3字节) + 2 + // 数据长度(1字节) + result.dataLength * 2 + // 数据内容 + 4; // 校验和(2字节) + + if (cleanedMessage.length != expectedCharLength) { + System.err.println("Data length mismatch: expected " + expectedCharLength + + ", got " + cleanedMessage.length); + return null; + } + + // 解析基站ID (位置8-11,2字节,低位在前) + int baseIdLow = HexUtils.fastHexToByte(cleanedMessage[8], cleanedMessage[9]); + int baseIdHigh = HexUtils.fastHexToByte(cleanedMessage[10], cleanedMessage[11]); + int baseIdValue = (baseIdHigh << 8) | baseIdLow; + result.baseStationId = String.valueOf(baseIdValue); // 转换为十进制字符串 + + // 解析标签ID (位置12-15,2字节,低位在前) + int tagIdLow = HexUtils.fastHexToByte(cleanedMessage[12], cleanedMessage[13]); + int tagIdHigh = HexUtils.fastHexToByte(cleanedMessage[14], cleanedMessage[15]); + int tagIdValue = (tagIdHigh << 8) | tagIdLow; + result.tagId = String.valueOf(tagIdValue); // 转换为十进制字符串 + + // 解析包序 (位置16-17),转换为十进制 + result.packetSequence = HexUtils.fastHexToByte(cleanedMessage[16], cleanedMessage[17]); + + // 解析测距距离 (位置18-21,2字节,低位在前),转换为十进制 + int distLow = HexUtils.fastHexToByte(cleanedMessage[18], cleanedMessage[19]); + int distHigh = HexUtils.fastHexToByte(cleanedMessage[20], cleanedMessage[21]); + result.distance = (distHigh << 8) | distLow; + + // 解析水平角度 (位置22-25,2字节,低位在前),转换为十进制 + int hAngleLow = HexUtils.fastHexToByte(cleanedMessage[22], cleanedMessage[23]); + int hAngleHigh = HexUtils.fastHexToByte(cleanedMessage[24], cleanedMessage[25]); + result.horizontalAngle = (hAngleHigh << 8) | hAngleLow; + + // 解析俯仰角度 (位置26-29,2字节,低位在前),转换为十进制 + // 注意:这是有符号数,范围-180到180度 + int vAngleLow = HexUtils.fastHexToByte(cleanedMessage[26], cleanedMessage[27]); + int vAngleHigh = HexUtils.fastHexToByte(cleanedMessage[28], cleanedMessage[29]); + short vAngleShort = (short) ((vAngleHigh << 8) | vAngleLow); + result.verticalAngle = vAngleShort; + + // 解析信号强度 (位置30-31),转换为十进制 + result.signalStrength = HexUtils.fastHexToByte(cleanedMessage[30], cleanedMessage[31]); + + // 解析角度置信度 (位置32-33),转换为十进制 + result.angleConfidence = HexUtils.fastHexToByte(cleanedMessage[32], cleanedMessage[33]); + + // 解析标签电量 (位置34-35),转换为十进制 + result.tagBattery = HexUtils.fastHexToByte(cleanedMessage[34], cleanedMessage[35]); + + // 解析设备状态 (位置36-39,2字节,低位在前) + int statusLow = HexUtils.fastHexToByte(cleanedMessage[36], cleanedMessage[37]); + int statusHigh = HexUtils.fastHexToByte(cleanedMessage[38], cleanedMessage[39]); + result.deviceStatus = (statusHigh << 8) | statusLow; + + // 解析气压值 (位置40-43,2字节,低位在前),转换为十进制 + int pressureLow = HexUtils.fastHexToByte(cleanedMessage[40], cleanedMessage[41]); + int pressureHigh = HexUtils.fastHexToByte(cleanedMessage[42], cleanedMessage[43]); + result.airPressure = (pressureHigh << 8) | pressureLow; + + // 解析保留字段 (位置44-51,4字节) + result.reserved = new String(cleanedMessage, 44, 8); + + // 解析校验和 (最后4个字符) + result.checksum = new String(cleanedMessage, cleanedMessage.length - 4, 4); + + // 验证校验和 + byte[] packetBytes = hexStringToByteArray(new String(cleanedMessage)); + if (!verifyChecksum(packetBytes)) { + System.err.println("Checksum verification failed for packet from " + ip + ":" + port); + return null; + } + + } catch (IndexOutOfBoundsException | NumberFormatException e) { + System.err.println("Parsing error in 55AA51 packet from " + ip + ":" + port); + e.printStackTrace(); + return null; + } + + return result; + } + + /** + * 将十六进制字符串转换为字节数组 + */ + private static byte[] hexStringToByteArray(String s) { + int len = s.length(); + byte[] data = new byte[len / 2]; + for (int i = 0; i < len; i += 2) { + data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + + Character.digit(s.charAt(i+1), 16)); + } + return data; + } + + /** + * 验证数据包校验和 + * 从数据类型字段开始到保留字段结束,校验和为双字节补码 + */ + private static boolean verifyChecksum(byte[] packet) { + int len = packet.length; + if (len < 4) return false; + + int sum = 0; + // 从数据类型字段开始到保留字段结束 (跳过包头2字节) + for (int i = 3; i < len - 2; i++) { + sum += packet[i] & 0xFF; + } + sum = ~sum & 0xFFFF; // 取反并保留16位 + + // 获取接收到的校验和 (低位在前) + int receivedChecksum = ((packet[len - 2] & 0xFF) << 8) | (packet[len - 1] & 0xFF); + + return sum == receivedChecksum; + } + + private static char[] cleanMessage(String message) { + char[] cleaned = new char[message.length()]; + int j = 0; + for (char c : message.toCharArray()) { + if (Character.isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { + cleaned[j++] = Character.toUpperCase(c); + } + } + if (j == 0) return null; + return Arrays.copyOf(cleaned, j); + } + + +} \ No newline at end of file diff --git a/src/home/TagDell55AA03Parser.java b/src/home/TagDell55AA03Parser.java new file mode 100644 index 0000000..4882e6a --- /dev/null +++ b/src/home/TagDell55AA03Parser.java @@ -0,0 +1,236 @@ +package home; + +import java.util.Arrays; + +public class TagDell55AA03Parser { + // 预期报文头 + private static final String EXPECTED_HEADER = "55AA03"; + // 最小长度(根据文档,至少应有40个字段,每个字段2字节,加上包头和类型共4字节) + private static final int MIN_LENGTH = 84; // (4 + 40*2) * 2字符 = 168字符,但实际可能更多 + + private static final ThreadLocal<ParseResult> RESULT_CACHE = + ThreadLocal.withInitial(ParseResult::new); + + // 解析结果类 + public static class ParseResult { + public String header; // 数据包头(55AA) + public int dataType; // 数据类型(03) + public int dataLength; // 数据长度 + public String version; // 版本号(XX.XX格式) + public int deviceId; // 设备编号(小端序) + public int commFrequency; // 通信频率(毫秒) + public int maxAnchors; // 单次通信基站数 + public int minAnchors; // 小组编号/最少基站数 + public int distanceCalibration;// 距离校准值 + public int deviceType; // 设备类型(0:基站, 1:标签) + public int anchorActiveRanging;// 基站主动测距 + public int alarmDevice; // 报警设备 + public int imuIdleTime; // IMU静止时间 + public int pressureAnchorId; // 气压基站编号 + public int peripheralControl; // 外设控制(1:打开RTK, 2:关闭RTK) + public int pairingId; // 配对编号 + public int uwbStatus; // UWB开关状态(0:关闭) + public int modbusMode; // MODBUS模式 + public int vibrationDuration; // 震动时长(秒) + public int[] neighborAnchorIds = new int[10]; // 临近基站ID(10个) + public int power; // 功率 + public int accelerometerThreshold; // 加速计阈值 + public int idleSleepTime; // 静止休眠时间 + public int vibrationEnable; // 震动使能 + public int accelerometerEnable;// 加速计使能 + public int configStatus; // 标签下发配置状态 + public int accelerometerDuration; // 加速计时长 + public int activationStatus; // 设备激活状态位(0:未激活, 1:激活) + public int syncAnchorType; // 同步基站类型(0:不发同步, 1:主基站, 2:从基站) + public int speedFilterLimit; // 速度滤波限值 + public int pressureCalibration;// 气压高度校准值 + + public void reset() { + header = ""; + dataType = 0; + dataLength = 0; + version = ""; + deviceId = 0; + commFrequency = 0; + maxAnchors = 0; + minAnchors = 0; + distanceCalibration = 0; + deviceType = 0; + anchorActiveRanging = 0; + alarmDevice = 0; + imuIdleTime = 0; + pressureAnchorId = 0; + peripheralControl = 0; + pairingId = 0; + uwbStatus = 0; + modbusMode = 0; + vibrationDuration = 0; + Arrays.fill(neighborAnchorIds, 0); + power = 0; + accelerometerThreshold = 0; + idleSleepTime = 0; + vibrationEnable = 0; + accelerometerEnable = 0; + configStatus = 0; + accelerometerDuration = 0; + activationStatus = 0; + syncAnchorType = 0; + speedFilterLimit = 0; + pressureCalibration = 0; + } + } + + /** + * 解析55AA03协议报文 + * @param message 接收到的十六进制字符串 + * @return 解析结果(解析失败返回null) + */ + public static ParseResult parse(String message, String ip, int port) { + if (message == null || message.isEmpty()) { + return null; + } + + // 清理消息:只保留数字和A-F字符 + char[] cleanedMessage = cleanMessage(message); + + // 检查最小长度 + if (cleanedMessage == null || cleanedMessage.length < MIN_LENGTH) { + return null; + } + + // 检查报文头 (55AA03) + if (!new String(cleanedMessage, 0, 6).equals(EXPECTED_HEADER)) { + return null; + } + + ParseResult result = RESULT_CACHE.get(); + result.reset(); + + try { + // 设置包头和数据类型 + result.header = new String(cleanedMessage, 0, 4); + result.dataType = HexUtils.fastHexToByte(cleanedMessage[4], cleanedMessage[5]); + + // 解析数据长度 (位置6-7) + result.dataLength = HexUtils.fastHexToByte(cleanedMessage[6], cleanedMessage[7]); + + // 解析版本号 (位置8-11, 形如XX.XX) + int versionPart1 = HexUtils.fastHexToByte(cleanedMessage[8], cleanedMessage[9]); + int versionPart2 = HexUtils.fastHexToByte(cleanedMessage[10], cleanedMessage[11]); + result.version = String.format("%d.%02d", versionPart1, versionPart2); + + // 解析设备编号 (位置12-15, 低位在前) + result.deviceId = parseLittleEndian(cleanedMessage, 12); + + // 解析通信频率 (位置16-19) + result.commFrequency = parseLittleEndian(cleanedMessage, 16); + + // 解析单次通信基站数 (位置20-23) + result.maxAnchors = parseLittleEndian(cleanedMessage, 20); + + // 解析小组编号/最少基站数 (位置24-27) + result.minAnchors = parseLittleEndian(cleanedMessage, 24); + + // 解析距离校准值 (位置28-31) + result.distanceCalibration = parseLittleEndian(cleanedMessage, 28); + + // 解析设备类型 (位置32-35, 0:基站, 1:标签) + result.deviceType = parseLittleEndian(cleanedMessage, 32); + + // 解析基站主动测距 (位置36-39) + result.anchorActiveRanging = parseLittleEndian(cleanedMessage, 36); + + // 解析报警设备 (位置40-43) + result.alarmDevice = parseLittleEndian(cleanedMessage, 40); + + // 解析IMU静止时间 (位置44-47) + result.imuIdleTime = parseLittleEndian(cleanedMessage, 44); + + // 解析气压基站编号 (位置48-51) + result.pressureAnchorId = parseLittleEndian(cleanedMessage, 48); + + // 解析外设控制 (位置52-55, 1:打开RTK, 2:关闭RTK) + result.peripheralControl = parseLittleEndian(cleanedMessage, 52); + + // 解析配对编号 (位置56-59) + result.pairingId = parseLittleEndian(cleanedMessage, 56); + + // 解析UWB开关状态 (位置60-63, 0:关闭) + result.uwbStatus = parseLittleEndian(cleanedMessage, 60); + + // 解析MODBUS模式 (位置64-67) + result.modbusMode = parseLittleEndian(cleanedMessage, 64); + + // 解析震动时长 (位置68-71, 单位秒) + result.vibrationDuration = parseLittleEndian(cleanedMessage, 68); + + // 解析临近基站ID (位置72-111, 10个基站) + for (int i = 0; i < 10; i++) { + result.neighborAnchorIds[i] = parseLittleEndian(cleanedMessage, 72 + i * 4); + } + + // 解析功率 (位置112-115) + result.power = parseLittleEndian(cleanedMessage, 112); + + // 解析加速计阈值 (位置116-119) + result.accelerometerThreshold = parseLittleEndian(cleanedMessage, 116); + + // 解析静止休眠时间 (位置120-123) + result.idleSleepTime = parseLittleEndian(cleanedMessage, 120); + + // 解析震动使能 (位置124-127) + result.vibrationEnable = parseLittleEndian(cleanedMessage, 124); + + // 解析加速计使能 (位置128-131) + result.accelerometerEnable = parseLittleEndian(cleanedMessage, 128); + + // 解析标签下发配置状态 (位置132-135) + result.configStatus = parseLittleEndian(cleanedMessage, 132); + + // 解析加速计时长 (位置136-139) + result.accelerometerDuration = parseLittleEndian(cleanedMessage, 136); + + // 解析设备激活状态位 (位置140-143, 0:未激活, 1:激活) + result.activationStatus = parseLittleEndian(cleanedMessage, 140); + + // 解析同步基站类型 (位置144-147, 0:不发同步, 1:主基站, 2:从基站) + result.syncAnchorType = parseLittleEndian(cleanedMessage, 144); + + // 解析速度滤波限值 (位置148-151) + result.speedFilterLimit = parseLittleEndian(cleanedMessage, 148); + + // 解析气压高度校准值 (位置152-155) + result.pressureCalibration = parseLittleEndian(cleanedMessage, 152); + + } catch (IndexOutOfBoundsException | NumberFormatException e) { + System.err.println("Parsing error in packet from " + ip + ":" + port); + return null; + } + + return result; + } + + /** + * 解析小端序的16位整数(4个字符) + */ + private static int parseLittleEndian(char[] message, int startIndex) { + int lowByte = HexUtils.fastHexToByte(message[startIndex], message[startIndex + 1]); + int highByte = HexUtils.fastHexToByte(message[startIndex + 2], message[startIndex + 3]); + return (highByte << 8) | lowByte; + } + + /** + * 清理消息,只保留十六进制字符 + */ + private static char[] cleanMessage(String message) { + char[] cleaned = new char[message.length()]; + int j = 0; + for (char c : message.toCharArray()) { + if (Character.isDigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')) { + cleaned[j++] = Character.toUpperCase(c); + } + } + if (j == 0) return null; + return Arrays.copyOf(cleaned, j); + } +} \ No newline at end of file diff --git a/src/home/VisualizationPanel.java b/src/home/VisualizationPanel.java index 8b24e30..47e2180 100644 --- a/src/home/VisualizationPanel.java +++ b/src/home/VisualizationPanel.java @@ -4,11 +4,16 @@ import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; +import java.awt.Image; import java.awt.RenderingHints; + +import javax.imageio.ImageIO; import javax.swing.BorderFactory; import javax.swing.JPanel; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; +import java.io.File; +import java.io.IOException; // 可视化面板类 class VisualizationPanel extends JPanel { @@ -30,12 +35,20 @@ private Font boldFont = new Font("Microsoft YaHei", Font.BOLD, 14); private Font normalFont = new Font("Microsoft YaHei", Font.PLAIN, 12); private Font titleFont = new Font("Microsoft YaHei", Font.BOLD, 12); + private Image logoImage; // 添加图片变量 public VisualizationPanel(AOAFollowSystem parentFrame) { this.parentFrame = parentFrame; setPreferredSize(new Dimension(400, 400)); setBorder(BorderFactory.createTitledBorder(parentFrame.getString("visualization"))); + // 加载图片 + try { + logoImage = ImageIO.read(new File("systemfile/1.png")); + } catch (IOException e) { + System.err.println("无法加载图片: systemfile/1.png"); + e.printStackTrace(); + } // 添加鼠标滚轮监听器 addMouseWheelListener(new MouseWheelListener() { @Override @@ -84,11 +97,22 @@ int centerX = getWidth() / 2; int centerY = getHeight()/ 2 - 30; - int maxRadius = (int) (Math.min(centerX, centerY) * scaleFactor - 20); + int baseRadius = Math.min(centerX, centerY) - 20; // 基础半径(不缩放) + int maxRadius = (int) (baseRadius * scaleFactor); // 缩放后的半径 // 设置背景颜色 g2d.setColor(new Color(240, 240, 240)); g2d.fillRect(0, 0, getWidth(), getHeight()); + + // 在左下角绘制图片 + if (logoImage != null) { + int imgWidth = logoImage.getWidth(this); + int imgHeight = logoImage.getHeight(this); + // 调整图片大小,使其适合面板 + int scaledWidth =150; // 设置图片宽度 + int scaledHeight = (int) (imgHeight * (scaledWidth * 1.0 / imgWidth)); // 按比例计算高度 + g2d.drawImage(logoImage, 5, getHeight() - scaledHeight - 5, scaledWidth, scaledHeight, this); + } // 绘制坐标轴 g2d.setColor(Color.LIGHT_GRAY); @@ -123,8 +147,8 @@ for (int i = 1; i <= 5; i++) { int radius = maxRadius * i / 5; g2d.drawOval(centerX - radius, centerY - radius, radius * 2, radius * 2); - // 修改:将距离标签显示在圆圈正南方中间 - String distanceLabel = (int)(i * 200 * scaleFactor) + "cm"; // 修改:乘以scaleFactor而不是除以 + // 修改:距离标签不随缩放变化,保持实际距离 + String distanceLabel = i * 200 + "cm"; int labelWidth = g2d.getFontMetrics().stringWidth(distanceLabel); g2d.drawString(distanceLabel, centerX - labelWidth / 2, centerY + radius + 15); } @@ -140,7 +164,7 @@ // 绘制B点(标签点) if (distance > 0) { // 根据距离计算缩放比例(最大1000厘米) - double scaledDistance = Math.min(distance, 1000) * maxRadius / 1000.0 ; // 修改:添加乘以scaleFactor + double scaledDistance = Math.min(distance, 1000) * maxRadius / 1000.0; // 将导航角度转换为数学角度(从正东方向开始,逆时针为正) double mathAngle = 90 - angle; // 导航角度转换为数学角度 @@ -162,13 +186,7 @@ g2d.setFont(boldFont); g2d.setColor(Color.RED); - // 计算文本位置(B点正下方) - int textY = bY + 25; - g2d.drawString(distanceText, bX - g2d.getFontMetrics().stringWidth(distanceText)/2, textY); - g2d.drawString(angleText, bX - g2d.getFontMetrics().stringWidth(angleText)/2, textY + 20); - // 恢复原始字体 - g2d.setFont(normalFont); // 在面板底部显示距离和角度信息 - 实时获取字符串,不使用缓存 g2d.setFont(boldFont); @@ -179,18 +197,30 @@ g2d.drawString(parentFrame.getString("signal") + signalQuality, 10, 100); g2d.setFont(normalFont); g2d.setColor(Color.BLUE); - g2d.fillOval(bX - 5, bY - 5, 10, 10); + g2d.fillOval(bX -10, bY -10, 20, 20); // B点正上方显示设备编号 g2d.setFont(titleFont); - g2d.drawString(tagid, bX - g2d.getFontMetrics().stringWidth(tagid)/2, bY - 15); + g2d.setColor(Color.BLUE); + g2d.drawString(tagid, bX - g2d.getFontMetrics().stringWidth(tagid)/2, bY - 25); g2d.setFont(normalFont); + + // 计算文本位置(B点正下方) + int textY = bY + 25; + g2d.setColor(Color.BLUE); + g2d.drawString(distanceText, bX - g2d.getFontMetrics().stringWidth(distanceText)/2, textY); + g2d.drawString(angleText, bX - g2d.getFontMetrics().stringWidth(angleText)/2, textY + 20); + g2d.setFont(normalFont); + + } // 显示当前缩放比例 g2d.setColor(Color.DARK_GRAY); - g2d.drawString(String.format("缩放: %.1fx", scaleFactor), getWidth() - 80, 20); + g2d.drawString(String.format("%s: %.1fx", parentFrame.getString("scale"), scaleFactor), getWidth() - 80, 20); + + } public void updateLanguage() { diff --git a/systemfile/1.png b/systemfile/1.png new file mode 100644 index 0000000..07830e5 --- /dev/null +++ b/systemfile/1.png Binary files differ diff --git a/systemfile/Messages_en.properties b/systemfile/Messages_en.properties index 002aef8..c178b19 100644 --- a/systemfile/Messages_en.properties +++ b/systemfile/Messages_en.properties @@ -48,4 +48,7 @@ hex=HEX ascii=ASCII hex_send=HEX Send -close_serial=Close Serial \ No newline at end of file +close_serial=Close Serial +signal=Signal: +pair=Pair +pair_tooltip=Enter 20-second pairing mode after clicking send \ No newline at end of file diff --git a/systemfile/Messages_zh.properties b/systemfile/Messages_zh.properties index 2cb98e5..ef70e9e 100644 --- a/systemfile/Messages_zh.properties +++ b/systemfile/Messages_zh.properties @@ -48,4 +48,7 @@ hex=HEX ascii=ASCII hex_send=HEX鍙戦�� -close_serial=鍏抽棴涓插彛 \ No newline at end of file +close_serial=鍏抽棴涓插彛 +signal=淇″彿锛� +pair=閰嶅 +pair_tooltip=鐐瑰嚮鍙戦�佸悗杩涘叆20绉掗厤瀵规ā寮� \ No newline at end of file diff --git a/systemfile/logfile/openlog.txt b/systemfile/logfile/openlog.txt index 59defae..4b9c4e7 100644 --- a/systemfile/logfile/openlog.txt +++ b/systemfile/logfile/openlog.txt @@ -380,3 +380,223 @@ 工作时长: 1小时 9分钟 40秒 ----------------------------------- 程序启动: 2025-08-26 17:12:20 +程序关闭: 2025-08-26 17:22:39 +工作时长: 0小时 10分钟 18秒 +----------------------------------- +程序启动: 2025-08-26 17:22:41 +程序启动: 2025-08-26 21:05:09 +程序关闭: 2025-08-26 21:27:08 +工作时长: 0小时 21分钟 58秒 +----------------------------------- +程序启动: 2025-08-26 21:29:00 +程序关闭: 2025-08-26 21:29:29 +工作时长: 0小时 0分钟 29秒 +----------------------------------- +程序启动: 2025-08-26 21:29:42 +程序关闭: 2025-08-26 21:35:17 +工作时长: 0小时 5分钟 35秒 +----------------------------------- +程序启动: 2025-08-26 21:35:22 +程序关闭: 2025-08-26 21:35:38 +工作时长: 0小时 0分钟 16秒 +----------------------------------- +程序启动: 2025-08-26 21:35:48 +程序启动: 2025-08-26 21:40:46 +程序启动: 2025-08-26 21:40:46 +程序关闭: 2025-08-26 21:48:25 +工作时长: 0小时 12分钟 36秒 +----------------------------------- +程序启动: 2025-08-26 21:56:14 +程序关闭: 2025-08-26 21:58:13 +工作时长: 0小时 1分钟 58秒 +----------------------------------- +程序启动: 2025-08-26 21:58:15 +程序关闭: 2025-08-26 21:58:35 +工作时长: 0小时 0分钟 20秒 +----------------------------------- +程序启动: 2025-08-26 21:59:03 +程序启动: 2025-08-26 22:14:47 +程序关闭: 2025-08-26 22:14:51 +工作时长: 0小时 15分钟 47秒 +----------------------------------- +程序启动: 2025-08-26 22:14:53 +程序关闭: 2025-08-26 22:23:31 +工作时长: 0小时 8分钟 38秒 +----------------------------------- +程序启动: 2025-08-26 22:28:20 +程序关闭: 2025-08-26 22:30:20 +工作时长: 0小时 2分钟 0秒 +----------------------------------- +程序启动: 2025-08-26 22:37:12 +程序关闭: 2025-08-26 22:37:24 +工作时长: 0小时 0分钟 11秒 +----------------------------------- +程序启动: 2025-08-26 22:39:02 +程序启动: 2025-08-26 22:40:39 +程序关闭: 2025-08-26 22:40:44 +工作时长: 0小时 1分钟 41秒 +----------------------------------- +程序启动: 2025-08-26 22:40:46 +程序关闭: 2025-08-26 22:41:08 +工作时长: 0小时 0分钟 22秒 +----------------------------------- +程序启动: 2025-08-26 22:44:30 +程序关闭: 2025-08-26 22:45:02 +工作时长: 0小时 0分钟 32秒 +----------------------------------- +程序启动: 2025-08-26 22:45:54 +程序关闭: 2025-08-26 22:47:55 +工作时长: 0小时 2分钟 1秒 +----------------------------------- +程序启动: 2025-08-26 22:47:58 +程序关闭: 2025-08-26 22:48:09 +工作时长: 0小时 0分钟 10秒 +----------------------------------- +程序启动: 2025-08-26 22:52:06 +程序启动: 2025-08-26 22:52:52 +程序关闭: 2025-08-26 22:52:56 +工作时长: 0小时 0分钟 50秒 +----------------------------------- +程序启动: 2025-08-26 22:52:57 +程序关闭: 2025-08-26 22:54:36 +工作时长: 0小时 1分钟 38秒 +----------------------------------- +程序启动: 2025-08-26 22:55:43 +程序关闭: 2025-08-26 22:56:31 +工作时长: 0小时 0分钟 48秒 +----------------------------------- +程序启动: 2025-08-26 23:00:05 +程序关闭: 2025-08-26 23:01:59 +工作时长: 0小时 1分钟 53秒 +----------------------------------- +程序启动: 2025-08-27 07:28:02 +程序关闭: 2025-08-27 07:38:02 +工作时长: 0小时 10分钟 0秒 +----------------------------------- +程序启动: 2025-08-27 07:38:04 +程序关闭: 2025-08-27 07:38:33 +工作时长: 0小时 0分钟 28秒 +----------------------------------- +程序启动: 2025-08-27 07:45:36 +程序关闭: 2025-08-27 07:45:46 +工作时长: 0小时 0分钟 10秒 +----------------------------------- +程序启动: 2025-08-27 07:46:16 +程序关闭: 2025-08-27 07:48:27 +工作时长: 0小时 2分钟 11秒 +----------------------------------- +程序启动: 2025-08-27 07:48:29 +程序关闭: 2025-08-27 07:48:40 +工作时长: 0小时 0分钟 10秒 +----------------------------------- +程序启动: 2025-08-27 07:49:58 +程序关闭: 2025-08-27 07:50:13 +工作时长: 0小时 0分钟 14秒 +----------------------------------- +程序启动: 2025-08-27 07:54:23 +程序关闭: 2025-08-27 08:00:10 +工作时长: 0小时 5分钟 46秒 +----------------------------------- +程序启动: 2025-08-27 08:00:12 +程序关闭: 2025-08-27 08:07:42 +工作时长: 0小时 7分钟 30秒 +----------------------------------- +程序启动: 2025-08-27 08:07:44 +程序关闭: 2025-08-27 08:29:47 +工作时长: 0小时 22分钟 2秒 +----------------------------------- +程序启动: 2025-08-27 08:29:50 +程序关闭: 2025-08-27 08:30:13 +工作时长: 0小时 0分钟 22秒 +----------------------------------- +程序启动: 2025-08-27 22:07:56 +程序关闭: 2025-08-27 22:08:12 +工作时长: 0小时 0分钟 15秒 +----------------------------------- +程序启动: 2025-08-27 22:09:24 +程序关闭: 2025-08-27 22:09:41 +工作时长: 0小时 0分钟 16秒 +----------------------------------- +程序启动: 2025-08-27 22:09:59 +程序关闭: 2025-08-27 22:10:10 +工作时长: 0小时 0分钟 10秒 +----------------------------------- +程序启动: 2025-08-27 22:10:34 +程序关闭: 2025-08-27 22:10:37 +工作时长: 0小时 0分钟 3秒 +----------------------------------- +程序启动: 2025-08-27 22:10:42 +程序关闭: 2025-08-27 22:10:53 +工作时长: 0小时 0分钟 10秒 +----------------------------------- +程序启动: 2025-08-27 22:11:22 +程序关闭: 2025-08-27 22:12:30 +工作时长: 0小时 1分钟 7秒 +----------------------------------- +程序启动: 2025-08-27 22:13:23 +程序关闭: 2025-08-27 22:13:56 +工作时长: 0小时 0分钟 32秒 +----------------------------------- +程序启动: 2025-08-27 22:15:06 +程序启动: 2025-08-27 22:16:54 +程序关闭: 2025-08-27 22:17:02 +工作时长: 0小时 0分钟 8秒 +----------------------------------- +程序启动: 2025-08-27 22:18:07 +程序关闭: 2025-08-27 22:18:25 +工作时长: 0小时 0分钟 18秒 +----------------------------------- +程序启动: 2025-08-27 22:18:38 +程序关闭: 2025-08-27 22:19:08 +工作时长: 0小时 0分钟 30秒 +----------------------------------- +程序启动: 2025-08-27 22:19:33 +程序关闭: 2025-08-27 22:19:45 +工作时长: 0小时 0分钟 12秒 +----------------------------------- +程序启动: 2025-08-27 22:20:55 +程序关闭: 2025-08-27 22:21:18 +工作时长: 0小时 0分钟 22秒 +----------------------------------- +程序启动: 2025-08-27 22:22:15 +程序关闭: 2025-08-27 22:22:36 +工作时长: 0小时 0分钟 21秒 +----------------------------------- +程序启动: 2025-08-27 22:24:27 +程序启动: 2025-08-27 22:36:01 +程序关闭: 2025-08-27 22:36:04 +工作时长: 0小时 11分钟 37秒 +----------------------------------- +程序启动: 2025-08-27 22:36:05 +程序关闭: 2025-08-27 22:36:07 +工作时长: 0小时 0分钟 1秒 +----------------------------------- +程序启动: 2025-08-27 22:36:59 +程序关闭: 2025-08-27 22:37:13 +工作时长: 0小时 0分钟 14秒 +----------------------------------- +程序启动: 2025-08-27 22:43:27 +程序启动: 2025-08-27 22:45:39 +程序关闭: 2025-08-27 22:45:43 +工作时长: 0小时 2分钟 15秒 +----------------------------------- +程序启动: 2025-08-27 22:45:45 +程序关闭: 2025-08-27 22:45:54 +工作时长: 0小时 0分钟 9秒 +----------------------------------- +程序启动: 2025-08-27 22:48:06 +程序关闭: 2025-08-27 22:49:36 +工作时长: 0小时 1分钟 29秒 +----------------------------------- +程序启动: 2025-08-27 22:49:38 +程序关闭: 2025-08-27 22:49:43 +工作时长: 0小时 0分钟 5秒 +----------------------------------- +程序启动: 2025-08-27 22:50:11 +程序关闭: 2025-08-27 22:50:25 +工作时长: 0小时 0分钟 13秒 +----------------------------------- +程序启动: 2025-08-27 22:51:03 +程序关闭: 2025-08-27 22:51:23 +工作时长: 0小时 0分钟 20秒 +----------------------------------- -- Gitblit v1.9.3