826220679@qq.com
3 天以前 487a5b943571d6ab57c4baddca1cbcc7b2062e73
20250827
已添加3个文件
已修改15个文件
1003 ■■■■■ 文件已修改
bin/home/AOAFollowSystem$1.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/AOAFollowSystem$HomePanel$1.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/AOAFollowSystem$HomePanel$2.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/AOAFollowSystem$HomePanel.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/AOAFollowSystem.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/Dell55AA1FParser$ParseResult.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/Dell55AA1FParser.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/VisualizationPanel$1.class 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/VisualizationPanel.class 补丁 | 查看 | 原始文档 | blame | 历史
src/home/AOAFollowSystem.java 217 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/home/Dell55AA1FParser.java 36 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/home/Dell55AA51Parser.java 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/home/TagDell55AA03Parser.java 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/home/VisualizationPanel.java 56 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
systemfile/1.png 补丁 | 查看 | 原始文档 | blame | 历史
systemfile/Messages_en.properties 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
systemfile/Messages_zh.properties 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
systemfile/logfile/openlog.txt 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/home/AOAFollowSystem$1.class
Binary files differ
bin/home/AOAFollowSystem$HomePanel$1.class
Binary files differ
bin/home/AOAFollowSystem$HomePanel$2.class
Binary files differ
bin/home/AOAFollowSystem$HomePanel.class
Binary files differ
bin/home/AOAFollowSystem.class
Binary files differ
bin/home/Dell55AA1FParser$ParseResult.class
Binary files differ
bin/home/Dell55AA1FParser.class
Binary files differ
bin/home/VisualizationPanel$1.class
Binary files differ
bin/home/VisualizationPanel.class
Binary files differ
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());
            // æ–°å¢žï¼šHEX发送复选框
            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);
        }
        // åœ¨HomePanel类中添加更新配对复选框提示信息的方法
        public void updatePairCheckboxTooltip() {
            pairCheckBox.setToolTipText(getString("pair_tooltip"));
        }
        private JButton createColoredButton(String text, Color color) {
@@ -566,74 +596,116 @@
        }
        private void processReceivedData(byte[] data) {
            // ä¼˜åŒ–:重用StringBuilder对象
            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);
            }
            // ä¼˜åŒ–:重用StringBuilder对象
            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字节)才能解析基本字段
                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协议解析失败: " + 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协议解析失败: " + 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协议解析失败: " + 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);
                    // ä¼˜åŒ–:重用StringBuilder对象
                    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);
                    // ä¼˜åŒ–:重用StringBuilder对象
                    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) {
                        // ä¼˜åŒ–:重用StringBuilder对象
                        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) {
                        // ä¼˜åŒ–:重用StringBuilder对象
                        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);
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 = "";
//            }
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);
    }
}
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);
    }
}
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() {
systemfile/1.png
systemfile/Messages_en.properties
@@ -48,4 +48,7 @@
hex=HEX
ascii=ASCII
hex_send=HEX Send
close_serial=Close Serial
close_serial=Close Serial
signal=Signal:
pair=Pair
pair_tooltip=Enter 20-second pairing mode after clicking send
systemfile/Messages_zh.properties
@@ -48,4 +48,7 @@
hex=HEX
ascii=ASCII
hex_send=HEX发送
close_serial=关闭串口
close_serial=关闭串口
signal=信号:
pair=配对
pair_tooltip=点击发送后进入20秒配对模式
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秒
-----------------------------------