张世豪
8 天以前 c57cb0cd9feb4495c89246b2faec6d5e45c23c30
新增了控制指令
已添加12个文件
已修改5个文件
1331 ■■■■■ 文件已修改
device.properties 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
image/dengoff.png 补丁 | 查看 | 原始文档 | blame | 历史
image/dengon.png 补丁 | 查看 | 原始文档 | blame | 历史
image/off.png 补丁 | 查看 | 原始文档 | blame | 历史
image/onanddown.png 补丁 | 查看 | 原始文档 | blame | 历史
image/starton.png 补丁 | 查看 | 原始文档 | blame | 历史
image/xia1.png 补丁 | 查看 | 原始文档 | blame | 历史
image/xia10.png 补丁 | 查看 | 原始文档 | blame | 历史
image/xia2.png 补丁 | 查看 | 原始文档 | blame | 历史
image/xia20.png 补丁 | 查看 | 原始文档 | blame | 历史
set.properties 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/gecaoji/Device.java 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/yaokong/Control03.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/yaokong/Control05.java 199 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/yaokong/Control06.java 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/yaokong/Control07.java 198 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/yaokong/RemoteControlDialog.java 563 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
device.properties
@@ -24,3 +24,6 @@
positioningStatus=-1
satelliteCount=-1
differentialAge=-1
mowerStartStatus=-1
mowerLightStatus=-1
mowerBladeHeight=-1
image/dengoff.png
image/dengon.png
image/off.png
image/onanddown.png
image/starton.png
image/xia1.png
image/xia10.png
image/xia2.png
image/xia20.png
set.properties
@@ -1,5 +1,5 @@
#Current work land selection updated
#Fri Dec 12 17:21:33 CST 2025
#Serial Port Preferences Updated
#Mon Dec 15 15:45:14 CST 2025
appVersion=-1
currentWorkLandNumber=LAND1
cuttingWidth=200
@@ -9,5 +9,5 @@
mowerId=1234
serialAutoConnect=true
serialBaudRate=115200
serialPortName=COM13
serialPortName=COM15
simCardNumber=-1
src/gecaoji/Device.java
@@ -2,7 +2,10 @@
import baseStation.BaseStation;
import set.Setsys;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Locale;
import java.util.Properties;
@@ -74,6 +77,12 @@
    // å·®åˆ†æ—¶é—´
    private String selfCheckStatus = "-1";
    // å‰²è‰æœºè‡ªæ£€çŠ¶æ€
    private String mowerStartStatus = "-1";
    // å‰²è‰æœºå¯åŠ¨çŠ¶æ€ï¼š1开启,0熄火,-1未知
    private String mowerLightStatus = "-1";
    // å‰²è‰æœºç¯å¼€å…³çŠ¶æ€ï¼š1开启,0关闭,-1未知
    private String mowerBladeHeight = "-1";
    // å‰²è‰æœºåˆ€ç›˜é«˜åº¦ï¼š-1未知
    private static final double METERS_PER_DEGREE_LAT = 111320.0d;
    
@@ -104,6 +113,58 @@
        applyDefaults(gecaoji);
    }
    /**
     * ä¿å­˜æ‰€æœ‰å±žæ€§åˆ°device.properties文件
     */
    public void saveToProperties() {
        Properties properties = new Properties();
        // åŠ è½½çŽ°æœ‰å±žæ€§ï¼ˆä¿ç•™å…¶ä»–å±žæ€§ï¼‰
        try (FileInputStream input = new FileInputStream("device.properties")) {
            properties.load(input);
        } catch (IOException e) {
            // å¦‚果文件不存在,继续创建新文件
        }
        // è®¾ç½®æ‰€æœ‰è®¾å¤‡å±žæ€§
        if (mowerName != null) properties.setProperty("mowerName", mowerName);
        if (mowerModel != null) properties.setProperty("mowerModel", mowerModel);
        if (mowerNumber != null) properties.setProperty("mowerNumber", mowerNumber);
        if (mowingWidth != null) properties.setProperty("mowingWidth", mowingWidth);
        if (mowingHeight != null) properties.setProperty("mowingHeight", mowingHeight);
        if (baseStationNumber != null) properties.setProperty("baseStationNumber", baseStationNumber);
        if (baseStationCardNumber != null) properties.setProperty("baseStationCardNumber", baseStationCardNumber);
        if (baseStationCoordinates != null) properties.setProperty("baseStationCoordinates", baseStationCoordinates);
        if (deviceCardnumber != null) properties.setProperty("deviceCardnumber", deviceCardnumber);
        if (createTime != null) properties.setProperty("createTime", createTime);
        if (GupdateTime != null) properties.setProperty("GupdateTime", GupdateTime);
        if (BupdateTime != null) properties.setProperty("BupdateTime", BupdateTime);
        if (realtimeLatitude != null) properties.setProperty("realtimeLatitude", realtimeLatitude);
        if (realtimeLongitude != null) properties.setProperty("realtimeLongitude", realtimeLongitude);
        if (realtimeAltitude != null) properties.setProperty("realtimeAltitude", realtimeAltitude);
        if (realtimeX != null) properties.setProperty("realtimeX", realtimeX);
        if (realtimeY != null) properties.setProperty("realtimeY", realtimeY);
        if (realtimeSpeed != null) properties.setProperty("realtimeSpeed", realtimeSpeed);
        if (heading != null) properties.setProperty("heading", heading);
        if (pitch != null) properties.setProperty("pitch", pitch);
        if (battery != null) properties.setProperty("battery", battery);
        if (positioningStatus != null) properties.setProperty("positioningStatus", positioningStatus);
        if (satelliteCount != null) properties.setProperty("satelliteCount", satelliteCount);
        if (differentialAge != null) properties.setProperty("differentialAge", differentialAge);
        if (selfCheckStatus != null) properties.setProperty("selfCheckStatus", selfCheckStatus);
        if (mowerStartStatus != null) properties.setProperty("mowerStartStatus", mowerStartStatus);
        if (mowerLightStatus != null) properties.setProperty("mowerLightStatus", mowerLightStatus);
        if (mowerBladeHeight != null) properties.setProperty("mowerBladeHeight", mowerBladeHeight);
        // ä¿å­˜åˆ°æ–‡ä»¶
        try (FileOutputStream output = new FileOutputStream("device.properties");
             OutputStreamWriter writer = new OutputStreamWriter(output, StandardCharsets.UTF_8)) {
            properties.store(writer, "Updated device properties");
        } catch (IOException ex) {
            System.err.println("无法保存 device.properties: " + ex.getMessage());
        }
    }
    private void loadPropertiesInto(Device target, Properties properties) {
        if (target == null) {
            return;
@@ -133,7 +194,10 @@
        target.positioningStatus = properties.getProperty("positioningStatus", "-1");
        target.satelliteCount = properties.getProperty("satelliteCount", "-1");
        target.differentialAge = properties.getProperty("differentialAge", "-1");
    target.selfCheckStatus = properties.getProperty("selfCheckStatus", "-1");
        target.selfCheckStatus = properties.getProperty("selfCheckStatus", "-1");
        target.mowerStartStatus = properties.getProperty("mowerStartStatus", "-1");
        target.mowerLightStatus = properties.getProperty("mowerLightStatus", "-1");
        target.mowerBladeHeight = properties.getProperty("mowerBladeHeight", "-1");
    }
    private void applyDefaults(Device target) {
@@ -165,7 +229,10 @@
        target.positioningStatus = "-1";
        target.satelliteCount = "-1";
        target.differentialAge = "-1";
    target.selfCheckStatus = "-1";
        target.selfCheckStatus = "-1";
        target.mowerStartStatus = "-1";
        target.mowerLightStatus = "-1";
        target.mowerBladeHeight = "-1";
    }
    public static synchronized Device initializeActiveDevice(String mowerId) { // æ ¹æ®è®¾å¤‡ID初始化活跃设备
@@ -267,6 +334,15 @@
            case "selfCheckStatus":
                this.selfCheckStatus = value;
                return true;
            case "mowerStartStatus":
                this.mowerStartStatus = value;
                return true;
            case "mowerLightStatus":
                this.mowerLightStatus = value;
                return true;
            case "mowerBladeHeight":
                this.mowerBladeHeight = value;
                return true;
            default:
                System.err.println("未知字段: " + fieldName);
                return false;
@@ -711,6 +787,30 @@
        this.selfCheckStatus = selfCheckStatus;
    }
    public String getMowerStartStatus() { // èŽ·å–å‰²è‰æœºå¯åŠ¨çŠ¶æ€
        return mowerStartStatus;
    }
    public void setMowerStartStatus(String mowerStartStatus) { // è®¾ç½®å‰²è‰æœºå¯åŠ¨çŠ¶æ€
        this.mowerStartStatus = mowerStartStatus;
    }
    public String getMowerLightStatus() { // èŽ·å–å‰²è‰æœºç¯å¼€å…³çŠ¶æ€
        return mowerLightStatus;
    }
    public void setMowerLightStatus(String mowerLightStatus) { // è®¾ç½®å‰²è‰æœºç¯å¼€å…³çŠ¶æ€
        this.mowerLightStatus = mowerLightStatus;
    }
    public String getMowerBladeHeight() { // èŽ·å–å‰²è‰æœºåˆ€ç›˜é«˜åº¦
        return mowerBladeHeight;
    }
    public void setMowerBladeHeight(String mowerBladeHeight) { // è®¾ç½®å‰²è‰æœºåˆ€ç›˜é«˜åº¦
        this.mowerBladeHeight = mowerBladeHeight;
    }
    @Override
    public String toString() { // è¾“出对象信息
        return "Device{" +
@@ -739,6 +839,9 @@
                ", satelliteCount='" + satelliteCount + '\'' +
                ", differentialAge='" + differentialAge + '\'' +
                ", selfCheckStatus='" + selfCheckStatus + '\'' +
                ", mowerStartStatus='" + mowerStartStatus + '\'' +
                ", mowerLightStatus='" + mowerLightStatus + '\'' +
                ", mowerBladeHeight='" + mowerBladeHeight + '\'' +
                '}';
    }
}
src/yaokong/Control03.java
@@ -198,6 +198,16 @@
        return currentSteeringSpeed;
    }
    /**
     * ç›´æŽ¥è®¾ç½®å¹¶å‘送转向和前进速度(用于持续发送控制指令)
     * @param steeringSpeed è½¬å‘速度,范围-100到100
     * @param forwardSpeed å‰è¿›é€Ÿåº¦ï¼ŒèŒƒå›´-100到100
     * @return æ˜¯å¦å‘送成功
     */
    public static synchronized boolean setAndSendSpeeds(int steeringSpeed, int forwardSpeed) {
        return sendSpeedsIfDebugSerialOpen(steeringSpeed, forwardSpeed);
    }
    private static boolean sendSpeedsIfDebugSerialOpen(int nextSteering, int nextForward) {
        SerialPortService service = sendmessage.getActiveService();
        if (service == null || !service.isOpen()) {
src/yaokong/Control05.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,199 @@
package yaokong;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import chuankou.SerialPortService;
import chuankou.sendmessage;
/**
 * Control05.java - é¥æŽ§æœºå™¨å¯åŠ¨å…³é—­æŒ‡ä»¤ç±»
 *
 * æŒ‡ä»¤ç±»åž‹: 0x05
 * åŠŸèƒ½: æŽ§åˆ¶å‰²è‰æœºæ•´æœºç”µæºçš„启动与关闭
 *
 * æ•°æ®æ ¼å¼:
 *   æŽ§åˆ¶å€¼(1字节): 0=关闭,1=启动
 *   ä¿ç•™å­—段(3字节): 0x00填充
 *
 * ç¤ºä¾‹ç”¨æ³•:
 *   1. å¯åŠ¨æœºå™¨: Control05.buildPowerCommandHex("1") æˆ– Control05.powerOn()
 *   2. å…³é—­æœºå™¨: Control05.buildPowerCommandHex("0") æˆ– Control05.powerOff()
 *   3. å‘送指令: Control05.sendPowerOnIfDebugSerialOpen()
 */
public class Control05 {
    /**
     * æž„建遥控机器启动关闭指令(指令类型0x05)的HEX格式字符串
     *
     * @param powerValue ç”µæºæŽ§åˆ¶å€¼å­—符串:"0"表示关闭,"1"表示启动
     * @return ç”µæºæŽ§åˆ¶æŒ‡ä»¤çš„HEX格式字符串
     */
    public static String buildPowerCommandHex(String powerValue) {
        // è§£æžæŽ§åˆ¶å€¼
        int power = parsePowerValue(powerValue);
        byte[] commandBytes = buildPowerCommandBytes((byte) power);
        return bytesToHex(commandBytes);
    }
    /**
     * æž„建遥控机器启动关闭指令的字节数组
     *
     * @param powerValue ç”µæºæŽ§åˆ¶å€¼ï¼š0=关闭,1=启动
     * @return ç”µæºæŽ§åˆ¶æŒ‡ä»¤çš„字节数组
     */
    public static byte[] buildPowerCommandBytes(byte powerValue) {
        // éªŒè¯æŽ§åˆ¶å€¼èŒƒå›´
        if (powerValue != 0 && powerValue != 1) {
            throw new IllegalArgumentException("电源控制值必须是0或1");
        }
        int dataLength = 4; // æŽ§åˆ¶å€¼1字节 + ä¿ç•™å­—段3字节
        ByteBuffer buffer = ByteBuffer.allocate(14); // æ€»é•¿åº¦14字节
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        // å¸§å¤´
        buffer.put(BluetoothProtocol.FRAME_HEADER);
        // æŒ‡ä»¤ç±»åž‹ (0x05)
        buffer.put((byte) 0x05);
        // æ•°æ®é•¿åº¦
        buffer.putShort((short) dataLength);
        // åºåˆ—号
        short sequence = (short) BluetoothProtocol.getNextSequence();
        buffer.putShort(sequence);
        // æŽ§åˆ¶å€¼
        buffer.put(powerValue);
        // ä¿ç•™å­—段 (3字节,全部填0)
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        // è®¡ç®—CRC16(从指令类型开始到数据内容结束)
        byte[] dataForCRC = new byte[9];
        ByteBuffer tempBuffer = ByteBuffer.allocate(9);
        tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
        tempBuffer.put((byte) 0x05); // æŒ‡ä»¤ç±»åž‹
        tempBuffer.putShort((short) dataLength); // æ•°æ®é•¿åº¦
        tempBuffer.putShort(sequence); // åºåˆ—号
        tempBuffer.put(powerValue); // æŽ§åˆ¶å€¼
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段1
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段2
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段3
        dataForCRC = tempBuffer.array();
        int crc = CRC16.calculateCRC16(dataForCRC, 0, dataForCRC.length);
        buffer.putShort((short) crc);
        // å¸§å°¾
        buffer.put((byte) 0x0D);
        return buffer.array();
    }
    /**
     * å½“调试串口打开时发送启动机器指令
     *
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendPowerOnIfDebugSerialOpen() {
        return sendPowerCommandIfDebugSerialOpen((byte) 1);
    }
    /**
     * å½“调试串口打开时发送关闭机器指令
     *
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendPowerOffIfDebugSerialOpen() {
        return sendPowerCommandIfDebugSerialOpen((byte) 0);
    }
    /**
     * å¿«æ·æ–¹æ³•:构建启动机器指令
     *
     * @return å¯åŠ¨æŒ‡ä»¤çš„HEX字符串
     */
    public static String powerOn() {
        return buildPowerCommandHex("1");
    }
    /**
     * å¿«æ·æ–¹æ³•:构建关闭机器指令
     *
     * @return å…³é—­æŒ‡ä»¤çš„HEX字符串
     */
    public static String powerOff() {
        return buildPowerCommandHex("0");
    }
    /**
     * è§£æžæŽ§åˆ¶å€¼å­—符串
     */
    private static int parsePowerValue(String powerStr) {
        try {
            int power = Integer.parseInt(powerStr.trim());
            if (power != 0 && power != 1) {
                throw new IllegalArgumentException("电源控制值必须是0或1");
            }
            return power;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("电源控制值必须是整数: " + powerStr);
        }
    }
    /**
     * å‘送电源控制指令(内部方法)
     */
    private static boolean sendPowerCommandIfDebugSerialOpen(byte powerValue) {
        SerialPortService service = sendmessage.getActiveService();
        if (service == null || !service.isOpen()) {
            return false;
        }
        byte[] payload = buildPowerCommandBytes(powerValue);
        // è°ƒè¯•:打印发送的数据
        System.out.println("发送电源控制指令: " + bytesToHex(payload));
        return sendmessage.sendViaActive(payload);
    }
    /**
     * å­—节数组转换为HEX字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
            if (i < bytes.length - 1) {
                hexString.append(' ');
            }
        }
        return hexString.toString().toUpperCase();
    }
    /**
     * æµ‹è¯•函数:验证电源控制指令构建
     */
    public static void testBuildPowerCommand() {
        System.out.println("=== æµ‹è¯•电源控制指令构建 ===");
        // æµ‹è¯•1:启动指令
        byte[] cmd1 = buildPowerCommandBytes((byte)1);
        System.out.println("启动机器: " + bytesToHex(cmd1));
        // æµ‹è¯•2:关闭指令
        byte[] cmd2 = buildPowerCommandBytes((byte)0);
        System.out.println("关闭机器: " + bytesToHex(cmd2));
    }
}
src/yaokong/Control06.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,245 @@
package yaokong;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import chuankou.SerialPortService;
import chuankou.sendmessage;
/**
 * Control06.java - é¥æŽ§å‰²è‰åˆ€ç›˜å‡é™æŒ‡ä»¤ç±»
 *
 * æŒ‡ä»¤ç±»åž‹: 0x06
 * åŠŸèƒ½: æŽ§åˆ¶å‰²è‰æœºåˆ€ç›˜çš„升降高度
 *
 * æ•°æ®æ ¼å¼:
 *   å‡é™å€¼(1字节): -100~100,正数表示向上提升
 *   ä¿ç•™å­—段(3字节): 0x00填充
 *
 * ç¤ºä¾‹ç”¨æ³•:
 *   1. æå‡åˆ€ç›˜50%: Control06.buildBladeCommandHex("50")
 *   2. é™ä½Žåˆ€ç›˜30%: Control06.buildBladeCommandHex("-30")
 *   3. å‘送提升指令: Control06.sendBladeUpIfDebugSerialOpen(50)
 *   4. å‘送降低指令: Control06.sendBladeDownIfDebugSerialOpen(30)
 */
public class Control06 {
    private static final int MAX_BLADE_VALUE = 100;
    private static final int MIN_BLADE_VALUE = -100;
    private static final int BLADE_STEP = 10; // é»˜è®¤æ­¥è¿›å€¼
    private static int currentBladeHeight = 0; // å½“前刀盘高度百分比
    /**
     * æž„建遥控割草刀盘升降指令(指令类型0x06)的HEX格式字符串
     *
     * @param bladeValueStr åˆ€ç›˜é«˜åº¦å€¼å­—符串,范围-100到100,正数表示向上提升
     * @return åˆ€ç›˜å‡é™æŒ‡ä»¤çš„HEX格式字符串
     */
    public static String buildBladeCommandHex(String bladeValueStr) {
        int bladeValue = parseBladeValue(bladeValueStr);
        byte[] commandBytes = buildBladeCommandBytes((byte) bladeValue);
        return bytesToHex(commandBytes);
    }
    /**
     * æž„建遥控割草刀盘升降指令的字节数组
     *
     * @param bladeValue åˆ€ç›˜é«˜åº¦å€¼ï¼š-100~100,正数表示向上提升
     * @return åˆ€ç›˜å‡é™æŒ‡ä»¤çš„字节数组
     */
    public static byte[] buildBladeCommandBytes(byte bladeValue) {
        // éªŒè¯åˆ€ç›˜é«˜åº¦èŒƒå›´
        if (bladeValue < -100 || bladeValue > 100) {
            throw new IllegalArgumentException("刀盘高度值必须在-100到100之间");
        }
        int dataLength = 4; // å‡é™å€¼1字节 + ä¿ç•™å­—段3字节
        ByteBuffer buffer = ByteBuffer.allocate(14); // æ€»é•¿åº¦14字节
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        // å¸§å¤´
        buffer.put(BluetoothProtocol.FRAME_HEADER);
        // æŒ‡ä»¤ç±»åž‹ (0x06)
        buffer.put((byte) 0x06);
        // æ•°æ®é•¿åº¦
        buffer.putShort((short) dataLength);
        // åºåˆ—号
        short sequence = (short) BluetoothProtocol.getNextSequence();
        buffer.putShort(sequence);
        // åˆ€ç›˜é«˜åº¦å€¼
        buffer.put(bladeValue);
        // ä¿ç•™å­—段 (3字节,全部填0)
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        // è®¡ç®—CRC16(从指令类型开始到数据内容结束)
        byte[] dataForCRC = new byte[9];
        ByteBuffer tempBuffer = ByteBuffer.allocate(9);
        tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
        tempBuffer.put((byte) 0x06); // æŒ‡ä»¤ç±»åž‹
        tempBuffer.putShort((short) dataLength); // æ•°æ®é•¿åº¦
        tempBuffer.putShort(sequence); // åºåˆ—号
        tempBuffer.put(bladeValue); // åˆ€ç›˜é«˜åº¦å€¼
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段1
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段2
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段3
        dataForCRC = tempBuffer.array();
        int crc = CRC16.calculateCRC16(dataForCRC, 0, dataForCRC.length);
        buffer.putShort((short) crc);
        // å¸§å°¾
        buffer.put((byte) 0x0D);
        return buffer.array();
    }
    /**
     * å½“调试串口打开时发送提升刀盘指令
     *
     * @param value æå‡ç™¾åˆ†æ¯”值(0-100)
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendBladeUpIfDebugSerialOpen(int value) {
        int targetHeight = Math.min(currentBladeHeight + value, MAX_BLADE_VALUE);
        return sendBladeHeightIfDebugSerialOpen(targetHeight);
    }
    /**
     * å½“调试串口打开时发送降低刀盘指令
     *
     * @param value é™ä½Žç™¾åˆ†æ¯”值(0-100)
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendBladeDownIfDebugSerialOpen(int value) {
        int targetHeight = Math.max(currentBladeHeight - value, MIN_BLADE_VALUE);
        return sendBladeHeightIfDebugSerialOpen(targetHeight);
    }
    /**
     * å½“调试串口打开时发送刀盘归零指令
     *
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendBladeResetIfDebugSerialOpen() {
        return sendBladeHeightIfDebugSerialOpen(0);
    }
    /**
     * å¿«æ·æ–¹æ³•:构建提升刀盘指令
     *
     * @param value æå‡ç™¾åˆ†æ¯”值(0-100)
     * @return æå‡æŒ‡ä»¤çš„HEX字符串
     */
    public static String bladeUp(int value) {
        int targetValue = Math.min(Math.max(value, 0), MAX_BLADE_VALUE);
        return buildBladeCommandHex(String.valueOf(targetValue));
    }
    /**
     * å¿«æ·æ–¹æ³•:构建降低刀盘指令
     *
     * @param value é™ä½Žç™¾åˆ†æ¯”值(0-100)
     * @return é™ä½ŽæŒ‡ä»¤çš„HEX字符串
     */
    public static String bladeDown(int value) {
        int targetValue = Math.max(Math.min(-value, 0), MIN_BLADE_VALUE);
        return buildBladeCommandHex(String.valueOf(targetValue));
    }
    /**
     * è§£æžåˆ€ç›˜é«˜åº¦å€¼å­—符串
     */
    private static int parseBladeValue(String bladeStr) {
        try {
            int value = Integer.parseInt(bladeStr.trim());
            if (value < MIN_BLADE_VALUE || value > MAX_BLADE_VALUE) {
                throw new IllegalArgumentException("刀盘高度值必须在-100到100之间");
            }
            return value;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("刀盘高度值必须是整数: " + bladeStr);
        }
    }
    /**
     * å‘送刀盘高度控制指令(内部方法)
     */
    private static boolean sendBladeHeightIfDebugSerialOpen(int targetHeight) {
        SerialPortService service = sendmessage.getActiveService();
        if (service == null || !service.isOpen()) {
            return false;
        }
        byte[] payload = buildBladeCommandBytes((byte) targetHeight);
        // è°ƒè¯•:打印发送的数据
        System.out.println("发送刀盘升降指令: " + bytesToHex(payload));
        boolean success = sendmessage.sendViaActive(payload);
        if (success) {
            currentBladeHeight = targetHeight;
        }
        return success;
    }
    /**
     * èŽ·å–å½“å‰åˆ€ç›˜é«˜åº¦
     *
     * @return å½“前刀盘高度百分比(-100到100)
     */
    public static int getCurrentBladeHeight() {
        return currentBladeHeight;
    }
    /**
     * é‡ç½®åˆ€ç›˜é«˜åº¦ä¸º0
     */
    public static void resetBladeHeight() {
        currentBladeHeight = 0;
    }
    /**
     * å­—节数组转换为HEX字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
            if (i < bytes.length - 1) {
                hexString.append(' ');
            }
        }
        return hexString.toString().toUpperCase();
    }
    /**
     * æµ‹è¯•函数:验证刀盘升降指令构建
     */
    public static void testBuildBladeCommand() {
        System.out.println("=== æµ‹è¯•刀盘升降指令构建 ===");
        // æµ‹è¯•1:提升指令
        byte[] cmd1 = buildBladeCommandBytes((byte)50);
        System.out.println("提升50%: " + bytesToHex(cmd1));
        // æµ‹è¯•2:降低指令
        byte[] cmd2 = buildBladeCommandBytes((byte)-30);
        System.out.println("降低30%: " + bytesToHex(cmd2));
        // æµ‹è¯•3:归零指令
        byte[] cmd3 = buildBladeCommandBytes((byte)0);
        System.out.println("归零: " + bytesToHex(cmd3));
    }
}
src/yaokong/Control07.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,198 @@
package yaokong;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import chuankou.SerialPortService;
import chuankou.sendmessage;
/**
 * Control07.java - é¥æŽ§å¤§ç¯å¼€å…³æŒ‡ä»¤ç±»
 *
 * æŒ‡ä»¤ç±»åž‹: 0x07
 * åŠŸèƒ½: æŽ§åˆ¶å‰²è‰æœºå¤§ç¯çš„开关
 *
 * æ•°æ®æ ¼å¼:
 *   å¼€å…³çŠ¶æ€(1字节): 0=关闭,1=开启
 *   ä¿ç•™å­—段(3字节): 0x00填充
 *
 * ç¤ºä¾‹ç”¨æ³•:
 *   1. å¼€å¯å¤§ç¯: Control07.buildLightCommandHex("1") æˆ– Control07.lightOn()
 *   2. å…³é—­å¤§ç¯: Control07.buildLightCommandHex("0") æˆ– Control07.lightOff()
 *   3. å‘送开灯指令: Control07.sendLightOnIfDebugSerialOpen()
 *   4. å‘送关灯指令: Control07.sendLightOffIfDebugSerialOpen()
 */
public class Control07 {
    /**
     * æž„建遥控大灯开关指令(指令类型0x07)的HEX格式字符串
     *
     * @param lightValueStr å¤§ç¯å¼€å…³çŠ¶æ€å­—ç¬¦ä¸²ï¼š"0"表示关闭,"1"表示开启
     * @return å¤§ç¯æŽ§åˆ¶æŒ‡ä»¤çš„HEX格式字符串
     */
    public static String buildLightCommandHex(String lightValueStr) {
        int lightValue = parseLightValue(lightValueStr);
        byte[] commandBytes = buildLightCommandBytes((byte) lightValue);
        return bytesToHex(commandBytes);
    }
    /**
     * æž„建遥控大灯开关指令的字节数组
     *
     * @param lightValue å¤§ç¯å¼€å…³çŠ¶æ€ï¼š0=关闭,1=开启
     * @return å¤§ç¯æŽ§åˆ¶æŒ‡ä»¤çš„字节数组
     */
    public static byte[] buildLightCommandBytes(byte lightValue) {
        // éªŒè¯å¼€å…³çŠ¶æ€èŒƒå›´
        if (lightValue != 0 && lightValue != 1) {
            throw new IllegalArgumentException("大灯开关状态必须是0或1");
        }
        int dataLength = 4; // å¼€å…³çŠ¶æ€1字节 + ä¿ç•™å­—段3字节
        ByteBuffer buffer = ByteBuffer.allocate(14); // æ€»é•¿åº¦14字节
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        // å¸§å¤´
        buffer.put(BluetoothProtocol.FRAME_HEADER);
        // æŒ‡ä»¤ç±»åž‹ (0x07)
        buffer.put((byte) 0x07);
        // æ•°æ®é•¿åº¦
        buffer.putShort((short) dataLength);
        // åºåˆ—号
        short sequence = (short) BluetoothProtocol.getNextSequence();
        buffer.putShort(sequence);
        // å¤§ç¯å¼€å…³çŠ¶æ€
        buffer.put(lightValue);
        // ä¿ç•™å­—段 (3字节,全部填0)
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        buffer.put((byte) 0x00);
        // è®¡ç®—CRC16(从指令类型开始到数据内容结束)
        byte[] dataForCRC = new byte[9];
        ByteBuffer tempBuffer = ByteBuffer.allocate(9);
        tempBuffer.order(ByteOrder.LITTLE_ENDIAN);
        tempBuffer.put((byte) 0x07); // æŒ‡ä»¤ç±»åž‹
        tempBuffer.putShort((short) dataLength); // æ•°æ®é•¿åº¦
        tempBuffer.putShort(sequence); // åºåˆ—号
        tempBuffer.put(lightValue); // å¤§ç¯å¼€å…³çŠ¶æ€
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段1
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段2
        tempBuffer.put((byte) 0x00); // ä¿ç•™å­—段3
        dataForCRC = tempBuffer.array();
        int crc = CRC16.calculateCRC16(dataForCRC, 0, dataForCRC.length);
        buffer.putShort((short) crc);
        // å¸§å°¾
        buffer.put((byte) 0x0D);
        return buffer.array();
    }
    /**
     * å½“调试串口打开时发送开启大灯指令
     *
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendLightOnIfDebugSerialOpen() {
        return sendLightCommandIfDebugSerialOpen((byte) 1);
    }
    /**
     * å½“调试串口打开时发送关闭大灯指令
     *
     * @return å‘送成功返回true,否则返回false
     */
    public static boolean sendLightOffIfDebugSerialOpen() {
        return sendLightCommandIfDebugSerialOpen((byte) 0);
    }
    /**
     * å¿«æ·æ–¹æ³•:构建开启大灯指令
     *
     * @return å¼€ç¯æŒ‡ä»¤çš„HEX字符串
     */
    public static String lightOn() {
        return buildLightCommandHex("1");
    }
    /**
     * å¿«æ·æ–¹æ³•:构建关闭大灯指令
     *
     * @return å…³ç¯æŒ‡ä»¤çš„HEX字符串
     */
    public static String lightOff() {
        return buildLightCommandHex("0");
    }
    /**
     * è§£æžå¤§ç¯å¼€å…³çŠ¶æ€å­—ç¬¦ä¸²
     */
    private static int parseLightValue(String lightStr) {
        try {
            int light = Integer.parseInt(lightStr.trim());
            if (light != 0 && light != 1) {
                throw new IllegalArgumentException("大灯开关状态必须是0或1");
            }
            return light;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("大灯开关状态必须是整数: " + lightStr);
        }
    }
    /**
     * å‘送大灯控制指令(内部方法)
     */
    private static boolean sendLightCommandIfDebugSerialOpen(byte lightValue) {
        SerialPortService service = sendmessage.getActiveService();
        if (service == null || !service.isOpen()) {
            return false;
        }
        byte[] payload = buildLightCommandBytes(lightValue);
        // è°ƒè¯•:打印发送的数据
        System.out.println("发送大灯控制指令: " + bytesToHex(payload));
        return sendmessage.sendViaActive(payload);
    }
    /**
     * å­—节数组转换为HEX字符串
     */
    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexString = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                hexString.append('0');
            }
            hexString.append(hex);
            if (i < bytes.length - 1) {
                hexString.append(' ');
            }
        }
        return hexString.toString().toUpperCase();
    }
    /**
     * æµ‹è¯•函数:验证大灯控制指令构建
     */
    public static void testBuildLightCommand() {
        System.out.println("=== æµ‹è¯•大灯控制指令构建 ===");
        // æµ‹è¯•1:开灯指令
        byte[] cmd1 = buildLightCommandBytes((byte)1);
        System.out.println("开启大灯: " + bytesToHex(cmd1));
        // æµ‹è¯•2:关灯指令
        byte[] cmd2 = buildLightCommandBytes((byte)0);
        System.out.println("关闭大灯: " + bytesToHex(cmd2));
    }
}
src/yaokong/RemoteControlDialog.java
@@ -20,6 +20,14 @@
    private JLabel moveJoystickValueLabel;
    private JLabel turnJoystickValueLabel;
    private Timer speedUpdateTimer;  // é€Ÿåº¦æ›´æ–°å®šæ—¶å™¨
    // æ‘‡æ†æŽ§åˆ¶å®šæ—¶å™¨ï¼šæŒç»­å‘送控制指令
    private Timer forwardControlTimer;  // å‰è¿›/后退控制定时器
    private Timer steeringControlTimer;  // è½¬å‘控制定时器
    private int targetForwardSpeed = 0;  // ç›®æ ‡å‰è¿›/后退速度
    private int targetSteeringSpeed = 0;  // ç›®æ ‡è½¬å‘速度
    private List<JButton> bladeButtons = new ArrayList<>();  // å­˜å‚¨åˆ€ç›˜æŽ§åˆ¶æŒ‰é’®ï¼Œç”¨äºŽæ¸…理定时器
    private String bladeUpDefaultText = "刀盘升";  // åˆ€ç›˜å‡æŒ‰é’®é»˜è®¤æ–‡å­—
    private String bladeDownDefaultText = "刀盘降";  // åˆ€ç›˜é™æŒ‰é’®é»˜è®¤æ–‡å­—
    
    public RemoteControlDialog(Component parent, Color themeColor, JLabel speedLabel) {
        super(parent != null ? (JFrame) SwingUtilities.getWindowAncestor(parent) : null,
@@ -31,7 +39,7 @@
        Control03.resetSpeeds();
        Control03.sendNeutralCommandIfDebugSerialOpen();
        int dialogWidth = UIConfig.DIALOG_WIDTH;  // ä¸Žé¦–页宽度一致
        int dialogHeight = (int) (UIConfig.DIALOG_HEIGHT / 3.0 * 0.7) + 50 + 40;  // å¢žåŠ 40像素
        int dialogHeight = (int) (UIConfig.DIALOG_HEIGHT / 3.0 * 0.7) +150;  // å¢žåŠ 90像素(原40+新增40+再增10)
        initializeDialog(dialogWidth, dialogHeight);
        initializeRemoteContent();
        startSpeedUpdateTimer();  // å¯åŠ¨é€Ÿåº¦æ›´æ–°å®šæ—¶å™¨
@@ -54,13 +62,69 @@
        setSize(width, height);
        setLocationRelativeTo(getOwner());
        setResizable(false);
        getContentPane().setBackground(new Color(26, 26, 46)); // æ·±è‰²èƒŒæ™¯
        Container contentPane = getContentPane();
        if (contentPane instanceof JComponent) {
            JComponent jContentPane = (JComponent) contentPane;
            jContentPane.setBackground(new Color(0, 0, 0, 0)); // é€æ˜ŽèƒŒæ™¯
            jContentPane.setOpaque(false);
        }
    }
    
    private void initializeRemoteContent() {
        JPanel contentPanel = new JPanel(new BorderLayout());
        contentPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16));
        contentPanel.setBackground(Color.WHITE);
        contentPanel.setOpaque(false); // è®¾ç½®ä¸ºé€æ˜Ž
        // åˆ›å»ºé¡¶éƒ¨é¢æ¿ï¼ŒåŒ…含按钮和数值显示
        JPanel topPanel = new JPanel(new BorderLayout());
        topPanel.setOpaque(false);
        // åˆ›å»ºæŒ‰é’®é¢æ¿ï¼ˆ4个按钮,间隔30像素)
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 30, 0));
        buttonPanel.setOpaque(false);
        buttonPanel.setBorder(BorderFactory.createEmptyBorder(0, 0, 8, 0)); // åº•部间距8像素
        // æŒ‰é’®1:off.png <-> starton.png,文字:启动 <-> ç†„火
        JLabel label1 = new JLabel("启动", SwingConstants.CENTER);
        label1.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label1.setForeground(new Color(40, 40, 40));
        JButton button1 = createIconButtonWithLabel("image/off.png", "image/starton.png", 27, 27,
                                                     label1, "启动", "熄火", "POWER");
        JPanel panel1 = createButtonWithLabel(button1, label1);
        buttonPanel.add(panel1);
        // æŒ‰é’®2:dengoff.png <-> dengon.png,文字:开灯 <-> å…³ç¯
        JLabel label2 = new JLabel("开灯", SwingConstants.CENTER);
        label2.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label2.setForeground(new Color(40, 40, 40));
        JButton button2 = createIconButtonWithLabel("image/dengoff.png", "image/dengon.png", 30, 30,
                                                     label2, "开灯", "关灯", "LIGHT");
        JPanel panel2 = createButtonWithLabel(button2, label2);
        buttonPanel.add(panel2);
        // æŒ‰é’®3:xia1.png(固定图标),文字:刀盘升
        JButton button3 = createBladeControlButton("image/xia1.png", "image/xia20.png", 30, 30, true);
        JLabel label3 = new JLabel("刀盘升", SwingConstants.CENTER);
        label3.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label3.setForeground(new Color(40, 40, 40));
        button3.putClientProperty("label", label3);
        button3.putClientProperty("defaultText", bladeUpDefaultText);
        bladeButtons.add(button3);  // æ·»åŠ åˆ°åˆ—è¡¨ä»¥ä¾¿æ¸…ç†
        JPanel panel3 = createButtonWithLabel(button3, label3);
        buttonPanel.add(panel3);
        // æŒ‰é’®4:xia2.png(固定图标),文字:刀盘降
        JButton button4 = createBladeControlButton("image/xia2.png", "image/xia10.png", 30, 30, false);
        JLabel label4 = new JLabel("刀盘降", SwingConstants.CENTER);
        label4.setFont(new Font("微软雅黑", Font.PLAIN, 12));
        label4.setForeground(new Color(40, 40, 40));
        button4.putClientProperty("label", label4);
        button4.putClientProperty("defaultText", bladeDownDefaultText);
        bladeButtons.add(button4);  // æ·»åŠ åˆ°åˆ—è¡¨ä»¥ä¾¿æ¸…ç†
        JPanel panel4 = createButtonWithLabel(button4, label4);
        buttonPanel.add(panel4);
        topPanel.add(buttonPanel, BorderLayout.NORTH);
        // åœ¨æ¯ä¸ªæ‘‡æ†ä¸Šæ–¹åˆ†åˆ«æ˜¾ç¤ºå…¶çŠ¶æ€/数值(分两列)
        JPanel valuesPanel = new JPanel(new GridLayout(1, 2, 20, 0));
@@ -80,14 +144,16 @@
        valuesPanel.add(moveJoystickValueLabel);
        valuesPanel.add(turnJoystickValueLabel);
        contentPanel.add(valuesPanel, BorderLayout.NORTH);
        topPanel.add(valuesPanel, BorderLayout.CENTER);
        contentPanel.add(topPanel, BorderLayout.NORTH);
        // åˆ›å»ºæ‘‡æ†é¢æ¿
        JPanel joystickPanel = new JPanel(new GridLayout(1, 2, 20, 0));
        joystickPanel.setOpaque(false);
        // ç§»åŠ¨æ‘‡æ†ï¼ˆç»¿è‰²ä¸»é¢˜ï¼‰
        moveJoystick = new ModernJoystickComponent("前进/后退",
        moveJoystick = new ModernJoystickComponent(" ",
            new Color(46, 204, 113), true);  // ç»¿è‰²ä¸»é¢˜
        moveJoystick.setJoystickListener(new JoystickListener() {
            @Override
@@ -98,10 +164,15 @@
                // é™åˆ¶åœ¨ [-100, 100]
                forwardVal = Math.max(-100, Math.min(100, forwardVal));
                // æ›´æ–°ç›®æ ‡é€Ÿåº¦
                targetForwardSpeed = forwardVal;
                if (Math.abs(y) > 0.1) {
                    applyForwardSpeed(forwardVal);
                    // æ‘‡æ†ä¸åœ¨ä¸­å¿ƒä½ç½®ï¼Œå¯åŠ¨æŒç»­å‘é€å®šæ—¶å™¨
                    startForwardControlTimer();
                } else {
                    // å‰åŽ
                    // æ‘‡æ†å›žåˆ°ä¸­å¿ƒä½ç½®ï¼Œåœæ­¢å‘送
                    stopForwardControlTimer();
                    stopForward();
                }
@@ -111,7 +182,7 @@
            }
        });
        // è½¬å‘摇杆(蓝色主题)
        turnJoystick = new ModernJoystickComponent("左转/右转",
        turnJoystick = new ModernJoystickComponent("",
            new Color(52, 152, 219), false);  // è“è‰²ä¸»é¢˜
        turnJoystick.setJoystickListener(new JoystickListener() {
            @Override
@@ -121,10 +192,15 @@
                int steeringVal = (int) Math.round(x * 100.0);
                steeringVal = Math.max(-100, Math.min(100, steeringVal));
                // æ›´æ–°ç›®æ ‡é€Ÿåº¦
                targetSteeringSpeed = steeringVal;
                if (Math.abs(x) > 0.1) {
                    applySteeringSpeed(steeringVal);
                    // æ‘‡æ†ä¸åœ¨ä¸­å¿ƒä½ç½®ï¼Œå¯åŠ¨æŒç»­å‘é€å®šæ—¶å™¨
                    startSteeringControlTimer();
                } else {
                    // å·¦å³
                    // æ‘‡æ†å›žåˆ°ä¸­å¿ƒä½ç½®ï¼Œåœæ­¢å‘送
                    stopSteeringControlTimer();
                    stopSteering();
                }
@@ -142,6 +218,360 @@
        getContentPane().add(contentPanel);
    }
    /**
     * åˆ›å»ºå¸¦æ ‡ç­¾çš„æŒ‰é’®é¢æ¿ï¼ˆåž‚直布局:按钮在上,标签在下)
     * @param button æŒ‰é’®
     * @param label æ ‡ç­¾
     * @return åŒ…含按钮和标签的面板
     */
    private JPanel createButtonWithLabel(JButton button, JLabel label) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setOpaque(false);
        panel.add(button, BorderLayout.CENTER);
        panel.add(label, BorderLayout.SOUTH);
        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
        return panel;
    }
    /**
     * åˆ›å»ºå¯åˆ‡æ¢å›¾æ ‡çš„æŒ‰é’®ï¼ˆå¸¦æ ‡ç­¾æ–‡å­—切换)
     * @param defaultIconPath é»˜è®¤å›¾æ ‡è·¯å¾„
     * @param clickedIconPath ç‚¹å‡»åŽçš„图标路径
     * @param width å›¾æ ‡å®½åº¦
     * @param height å›¾æ ‡é«˜åº¦
     * @param label æ ‡ç­¾ç»„ä»¶
     * @param defaultText é»˜è®¤æ–‡å­—
     * @param clickedText ç‚¹å‡»åŽçš„æ–‡å­—
     * @param controlType æŽ§åˆ¶æŒ‡ä»¤ç±»åž‹ï¼š"POWER"表示启动/熄火,"LIGHT"表示开灯/关灯,null表示无控制指令
     * @return é…ç½®å¥½çš„æŒ‰é’®
     */
    private JButton createIconButtonWithLabel(String defaultIconPath, String clickedIconPath,
                                               int width, int height, JLabel label,
                                               String defaultText, String clickedText, String controlType) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        // åŠ è½½é»˜è®¤å›¾æ ‡å’Œç‚¹å‡»å›¾æ ‡
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        // ä½¿ç”¨ clientProperty æ¥è·Ÿè¸ªæŒ‰é’®çŠ¶æ€ï¼ˆfalse=默认图标,true=点击图标)
        button.putClientProperty("isClicked", false);
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        button.putClientProperty("label", label);
        button.putClientProperty("defaultText", defaultText);
        button.putClientProperty("clickedText", clickedText);
        button.putClientProperty("controlType", controlType);
        // æ·»åŠ ç‚¹å‡»äº‹ä»¶ï¼Œåˆ‡æ¢å›¾æ ‡å’Œæ–‡å­—ï¼Œå¹¶å‘é€æŽ§åˆ¶æŒ‡ä»¤
        button.addActionListener(e -> {
            Boolean isClicked = (Boolean) button.getClientProperty("isClicked");
            ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
            ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
            JLabel labelRef = (JLabel) button.getClientProperty("label");
            String defaultTextRef = (String) button.getClientProperty("defaultText");
            String clickedTextRef = (String) button.getClientProperty("clickedText");
            String controlTypeRef = (String) button.getClientProperty("controlType");
            if (isClicked == null || !isClicked) {
                // å½“前是默认图标,切换到点击图标
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                    button.putClientProperty("isClicked", true);
                }
                // æ›´æ–°æ ‡ç­¾æ–‡å­—
                if (labelRef != null && clickedTextRef != null) {
                    labelRef.setText(clickedTextRef);
                }
                // å‘送控制指令(开启)
                if ("POWER".equals(controlTypeRef)) {
                    boolean success = Control05.sendPowerOnIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                } else if ("LIGHT".equals(controlTypeRef)) {
                    boolean success = Control07.sendLightOnIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                }
            } else {
                // å½“前是点击图标,切换回默认图标
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                    button.putClientProperty("isClicked", false);
                }
                // æ›´æ–°æ ‡ç­¾æ–‡å­—
                if (labelRef != null && defaultTextRef != null) {
                    labelRef.setText(defaultTextRef);
                }
                // å‘送控制指令(关闭)
                if ("POWER".equals(controlTypeRef)) {
                    boolean success = Control05.sendPowerOffIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                } else if ("LIGHT".equals(controlTypeRef)) {
                    boolean success = Control07.sendLightOffIfDebugSerialOpen();
                    if (!success) {
                        showSerialClosedWarning();
                    }
                }
            }
        });
        return button;
    }
    /**
     * åˆ›å»ºå¯åˆ‡æ¢å›¾æ ‡çš„æŒ‰é’®
     * @param defaultIconPath é»˜è®¤å›¾æ ‡è·¯å¾„
     * @param clickedIconPath ç‚¹å‡»åŽçš„图标路径
     * @param width å›¾æ ‡å®½åº¦
     * @param height å›¾æ ‡é«˜åº¦
     * @return é…ç½®å¥½çš„æŒ‰é’®
     */
    private JButton createIconButton(String defaultIconPath, String clickedIconPath, int width, int height) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        // åŠ è½½é»˜è®¤å›¾æ ‡å’Œç‚¹å‡»å›¾æ ‡
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        // ä½¿ç”¨ clientProperty æ¥è·Ÿè¸ªæŒ‰é’®çŠ¶æ€ï¼ˆfalse=默认图标,true=点击图标)
        button.putClientProperty("isClicked", false);
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        // æ·»åŠ ç‚¹å‡»äº‹ä»¶ï¼Œåˆ‡æ¢å›¾æ ‡
        button.addActionListener(e -> {
            Boolean isClicked = (Boolean) button.getClientProperty("isClicked");
            ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
            ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
            if (isClicked == null || !isClicked) {
                // å½“前是默认图标,切换到点击图标
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                    button.putClientProperty("isClicked", true);
                }
            } else {
                // å½“前是点击图标,切换回默认图标
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                    button.putClientProperty("isClicked", false);
                }
            }
        });
        return button;
    }
    /**
     * åˆ›å»ºå›ºå®šå›¾æ ‡çš„æŒ‰é’®
     * @param iconPath å›¾æ ‡è·¯å¾„
     * @param width å›¾æ ‡å®½åº¦
     * @param height å›¾æ ‡é«˜åº¦
     * @return é…ç½®å¥½çš„æŒ‰é’®
     */
    private JButton createFixedIconButton(String iconPath, int width, int height) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        ImageIcon icon = loadIcon(iconPath, width, height);
        if (icon != null) {
            button.setIcon(icon);
        }
        return button;
    }
    /**
     * åˆ›å»ºåˆ€ç›˜å‡é™æŽ§åˆ¶æŒ‰é’®ï¼ˆæ”¯æŒç‚¹å‡»å’Œé•¿æŒ‰ï¼‰
     * @param defaultIconPath é»˜è®¤å›¾æ ‡è·¯å¾„
     * @param clickedIconPath ç‚¹å‡»/长按时的图标路径
     * @param width å›¾æ ‡å®½åº¦
     * @param height å›¾æ ‡é«˜åº¦
     * @param isUp true表示刀盘升,false表示刀盘降
     * @return é…ç½®å¥½çš„æŒ‰é’®
     */
    private JButton createBladeControlButton(String defaultIconPath, String clickedIconPath,
                                             int width, int height, boolean isUp) {
        JButton button = new JButton();
        button.setPreferredSize(new Dimension(width, height));
        button.setMinimumSize(new Dimension(width, height));
        button.setMaximumSize(new Dimension(width, height));
        button.setContentAreaFilled(false);
        button.setBorderPainted(false);
        button.setFocusPainted(false);
        // åŠ è½½å›¾æ ‡
        ImageIcon defaultIcon = loadIcon(defaultIconPath, width, height);
        ImageIcon clickedIcon = loadIcon(clickedIconPath, width, height);
        if (defaultIcon != null) {
            button.setIcon(defaultIcon);
        }
        button.putClientProperty("defaultIcon", defaultIcon);
        button.putClientProperty("clickedIcon", clickedIcon);
        button.putClientProperty("isUp", isUp);
        button.putClientProperty("isPressed", false);
        // é•¿æŒ‰å®šæ—¶å™¨
        Timer longPressTimer = new Timer(100, e -> {  // æ¯100ms执行一次
            if (button.getClientProperty("isPressed") == Boolean.TRUE) {
                boolean isUpButton = (Boolean) button.getClientProperty("isUp");
                int currentHeight = Control06.getCurrentBladeHeight();
                // ä½¿ç”¨Control06的方法发送指令
                boolean success;
                if (isUpButton) {
                    success = Control06.sendBladeUpIfDebugSerialOpen(1);
                } else {
                    success = Control06.sendBladeDownIfDebugSerialOpen(1);
                }
                if (!success) {
                    showSerialClosedWarning();
                } else {
                    // æ›´æ–°æŒ‰é’®æ ‡ç­¾æ˜¾ç¤ºå½“前数值
                    updateBladeButtonLabel(button);
                }
            }
        });
        longPressTimer.setInitialDelay(500);  // é•¿æŒ‰500ms后开始连续变化
        button.putClientProperty("longPressTimer", longPressTimer);  // å­˜å‚¨å®šæ—¶å™¨å¼•用
        // é¼ æ ‡æŒ‰ä¸‹äº‹ä»¶
        button.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                button.putClientProperty("isPressed", true);
                // ç«‹å³åˆ‡æ¢å›¾æ ‡
                ImageIcon clickedIconRef = (ImageIcon) button.getClientProperty("clickedIcon");
                if (clickedIconRef != null) {
                    button.setIcon(clickedIconRef);
                }
                // ç‚¹å‡»æ—¶ç«‹å³å¢žåŠ /减少10,使用Control06的方法发送指令
                boolean isUpButton = (Boolean) button.getClientProperty("isUp");
                boolean success;
                if (isUpButton) {
                    success = Control06.sendBladeUpIfDebugSerialOpen(10);
                } else {
                    success = Control06.sendBladeDownIfDebugSerialOpen(10);
                }
                if (!success) {
                    showSerialClosedWarning();
                } else {
                    // æ›´æ–°æŒ‰é’®æ ‡ç­¾æ˜¾ç¤ºå½“前数值
                    updateBladeButtonLabel(button);
                }
                // å¯åŠ¨é•¿æŒ‰å®šæ—¶å™¨
                longPressTimer.start();
            }
            @Override
            public void mouseReleased(MouseEvent e) {
                button.putClientProperty("isPressed", false);
                // åœæ­¢é•¿æŒ‰å®šæ—¶å™¨
                longPressTimer.stop();
                // æ¢å¤é»˜è®¤å›¾æ ‡
                ImageIcon defaultIconRef = (ImageIcon) button.getClientProperty("defaultIcon");
                if (defaultIconRef != null) {
                    button.setIcon(defaultIconRef);
                }
                // æ¢å¤é»˜è®¤æ–‡å­—
                JLabel labelRef = (JLabel) button.getClientProperty("label");
                String defaultTextRef = (String) button.getClientProperty("defaultText");
                if (labelRef != null && defaultTextRef != null) {
                    labelRef.setText(defaultTextRef);
                }
            }
        });
        return button;
    }
    /**
     * æ›´æ–°åˆ€ç›˜æŒ‰é’®æ ‡ç­¾æ˜¾ç¤ºå½“前数值
     * @param button æŒ‰é’®
     */
    private void updateBladeButtonLabel(JButton button) {
        JLabel labelRef = (JLabel) button.getClientProperty("label");
        if (labelRef != null) {
            String defaultTextRef = (String) button.getClientProperty("defaultText");
            int currentHeight = Control06.getCurrentBladeHeight();
            String displayText = defaultTextRef + " " + currentHeight;
            labelRef.setText(displayText);
        }
    }
    /**
     * åŠ è½½å¹¶ç¼©æ”¾å›¾æ ‡
     * @param iconPath å›¾æ ‡è·¯å¾„
     * @param width ç›®æ ‡å®½åº¦
     * @param height ç›®æ ‡é«˜åº¦
     * @return ç¼©æ”¾åŽçš„图标
     */
    private ImageIcon loadIcon(String iconPath, int width, int height) {
        try {
            java.net.URL imgURL = getClass().getClassLoader().getResource(iconPath);
            if (imgURL == null) {
                // å°è¯•从文件系统加载
                java.io.File imgFile = new java.io.File(iconPath);
                if (imgFile.exists()) {
                    ImageIcon originalIcon = new ImageIcon(imgFile.getAbsolutePath());
                    Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
                    ImageIcon scaledIcon = new ImageIcon(scaledImage);
                    scaledIcon.setDescription(iconPath);
                    return scaledIcon;
                }
            } else {
                ImageIcon originalIcon = new ImageIcon(imgURL);
                Image scaledImage = originalIcon.getImage().getScaledInstance(width, height, Image.SCALE_SMOOTH);
                ImageIcon scaledIcon = new ImageIcon(scaledImage);
                scaledIcon.setDescription(iconPath);
                return scaledIcon;
            }
        } catch (Exception e) {
            System.err.println("无法加载图标: " + iconPath + " - " + e.getMessage());
        }
        return null;
    }
    private JPanel createStatusPanel(String label, String value, Color color) {
        JPanel panel = new JPanel(new BorderLayout(0, 5));
        panel.setOpaque(false);
@@ -235,6 +665,102 @@
        }
    }
    /**
     * å¯åŠ¨å‰è¿›/后退控制定时器,持续发送控制指令
     */
    private void startForwardControlTimer() {
        // å¦‚果定时器已经在运行,先停止它
        if (forwardControlTimer != null && forwardControlTimer.isRunning()) {
            forwardControlTimer.stop();
        }
        // åˆ›å»ºæ–°çš„定时器,每100ms发送一次指令
        forwardControlTimer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // æŒç»­å‘送目标速度的指令
                applyForwardSpeedContinuously(targetForwardSpeed);
            }
        });
        forwardControlTimer.setInitialDelay(0);
        forwardControlTimer.start();
    }
    /**
     * åœæ­¢å‰è¿›/后退控制定时器
     */
    private void stopForwardControlTimer() {
        if (forwardControlTimer != null && forwardControlTimer.isRunning()) {
            forwardControlTimer.stop();
        }
    }
    /**
     * å¯åŠ¨è½¬å‘æŽ§åˆ¶å®šæ—¶å™¨ï¼ŒæŒç»­å‘é€æŽ§åˆ¶æŒ‡ä»¤
     */
    private void startSteeringControlTimer() {
        // å¦‚果定时器已经在运行,先停止它
        if (steeringControlTimer != null && steeringControlTimer.isRunning()) {
            steeringControlTimer.stop();
        }
        // åˆ›å»ºæ–°çš„定时器,每100ms发送一次指令
        steeringControlTimer = new Timer(100, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // æŒç»­å‘送目标速度的指令
                applySteeringSpeedContinuously(targetSteeringSpeed);
            }
        });
        steeringControlTimer.setInitialDelay(0);
        steeringControlTimer.start();
    }
    /**
     * åœæ­¢è½¬å‘控制定时器
     */
    private void stopSteeringControlTimer() {
        if (steeringControlTimer != null && steeringControlTimer.isRunning()) {
            steeringControlTimer.stop();
        }
    }
    /**
     * æŒç»­å‘送前进/后退速度指令
     */
    private void applyForwardSpeedContinuously(int targetSpeed) {
        int currentSpeed = Control03.getCurrentForwardSpeed();
        int currentSteeringSpeed = Control03.getCurrentSteeringSpeed();
        // å¦‚果已经达到目标速度,直接发送一次以保持状态
        if (currentSpeed == targetSpeed) {
            // ç›´æŽ¥å‘送目标速度指令以保持状态(即使速度相同也要发送)
            Control03.setAndSendSpeeds(currentSteeringSpeed, targetSpeed);
        } else {
            // é€æ­¥è°ƒæ•´åˆ°ç›®æ ‡é€Ÿåº¦
            int delta = targetSpeed > currentSpeed ? 10 : -10;
            Control03.adjustForwardSpeed(delta);
        }
    }
    /**
     * æŒç»­å‘送转向速度指令
     */
    private void applySteeringSpeedContinuously(int targetSpeed) {
        int currentSpeed = Control03.getCurrentSteeringSpeed();
        int currentForwardSpeed = Control03.getCurrentForwardSpeed();
        // å¦‚果已经达到目标速度,直接发送一次以保持状态
        if (currentSpeed == targetSpeed) {
            // ç›´æŽ¥å‘送目标速度指令以保持状态(即使速度相同也要发送)
            Control03.setAndSendSpeeds(targetSpeed, currentForwardSpeed);
        } else {
            // é€æ­¥è°ƒæ•´åˆ°ç›®æ ‡é€Ÿåº¦
            int delta = targetSpeed > currentSpeed ? 15 : -15;
            Control03.adjustSteeringSpeed(delta);
        }
    }
    // æ›´æ–°é¡¶éƒ¨æ˜¾ç¤ºçš„æ‘‡æ†æ•°å€¼ï¼ˆåœ¨ EDT ä¸Šè°ƒç”¨ï¼‰ï¼Œæ–‡å­—根据数值映射为方向描述
    private void updateJoystickValues(int forwardVal, int steeringVal) {
        // è®¡ç®—移动/转向描述文本
@@ -330,6 +856,15 @@
    @Override
    public void dispose() {
        // åœæ­¢å¹¶æ¸…理控制定时器
        stopForwardControlTimer();
        stopSteeringControlTimer();
        if (forwardControlTimer != null) {
            forwardControlTimer = null;
        }
        if (steeringControlTimer != null) {
            steeringControlTimer = null;
        }
        // å‰åŽé€Ÿåº¦æ›´æ–°å®šæ—¶å™¨
        if (speedUpdateTimer != null) {
            speedUpdateTimer.stop();
@@ -349,6 +884,14 @@
        if (turnJoystick != null) {
            turnJoystick.dispose();
        }
        // åœæ­¢å¹¶æ¸…理刀盘按钮的定时器
        for (JButton bladeButton : bladeButtons) {
            Timer timer = (Timer) bladeButton.getClientProperty("longPressTimer");
            if (timer != null && timer.isRunning()) {
                timer.stop();
            }
        }
        bladeButtons.clear();
        super.dispose();
    }