liusuyi
2024-10-31 b821735241678a21ddc033aba04ebaf7ce65a3d2
优化结构
1 files added
7 files modified
430 ■■■■■ changed files
ard-common/src/main/java/com/ard/common/utils/ByteUtils.java 71 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/controller/UavBaseController.java 1 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/netty/command/notice/UavReadyReplyCmd.java 3 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java 2 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java 259 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java 9 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/service/ITyService.java 4 ●●●●● patch | view | raw | blame | history
ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java 81 ●●●●● patch | view | raw | blame | history
ard-common/src/main/java/com/ard/common/utils/ByteUtils.java
New file
@@ -0,0 +1,71 @@
package com.ard.common.utils;
/**
 * 字节工具类
 *
 * @author ruoyi
 */
public class ByteUtils {
    /**
     * 将字节数组转换为十六进制字符串,每个字节以0x格式显示
     * @param bytes 字节数组
     * @return 格式化后的字符串
     */
    public static String bytesToHexString(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("0x%02X ", b));
        }
        return sb.toString().trim();
    }
    /**
     * 将字节数组转换为常规字符串
     * @param bytes 字节数组
     * @return 字符串
     */
    public static String bytesToString(byte[] bytes) {
        return new String(bytes);
    }
    /**
     * 将单个字节转换为0x十六进制格式字符串
     * @param b 字节
     * @return 0x格式的字符串
     */
    public static String byteToHex(byte b) {
        return String.format("0x%02X", b);
    }
    /**
     * 将十六进制字符串转换为字节数组
     * @param hexString 十六进制字符串,支持0x前缀
     * @return 字节数组
     */
    public static byte[] hexStringToBytes(String hexString) {
        hexString = hexString.replaceAll("0x", "").replaceAll("\\s+", "");
        int length = hexString.length();
        byte[] data = new byte[length / 2];
        for (int i = 0; i < length; i += 2) {
            data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4)
                                 + Character.digit(hexString.charAt(i+1), 16));
        }
        return data;
    }
    /**
     * 将字节数组转换为十六进制字符串,每个字节用空格分隔显示
     * @param bytes 字节数组
     * @return 格式化后的十六进制字符串
     */
    public static String bytesToHexWithSpaces(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            sb.append(String.format("%02X", bytes[i]));
            if (i < bytes.length - 1) {
                sb.append(" ");
            }
        }
        return sb.toString();
    }
}
ard-work/src/main/java/com/ard/tianyu/controller/UavBaseController.java
@@ -14,7 +14,6 @@
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
ard-work/src/main/java/com/ard/tianyu/netty/command/notice/UavReadyReplyCmd.java
@@ -18,7 +18,8 @@
    private final double longitude;         // 经度
    private final double latitude;          // 纬度
    private final int altitude;             // 海拔高度
    public UavReadyReplyCmd(String boxSn,byte batteryPower,double longitude,double latitude,int altitude) {
    public UavReadyReplyCmd(String boxSn, byte batteryPower, double longitude, double latitude, int altitude) {
        setDataHeader((short) 0x6A77);      // 设置帧头
        setCmdCode((byte) 0x3D);            // 设置指令编号
        setDataLength((short) 39);          // 数据长度
ard-work/src/main/java/com/ard/tianyu/netty/handler/ClientHandler.java
@@ -31,7 +31,7 @@
        InetSocketAddress ipSocket = (InetSocketAddress) ctx.channel().remoteAddress();
        String host = ipSocket.getHostString();
        int port = ipSocket.getPort();
        tyService.processMessage(host + ":" + port, command);
        tyService.processReplyMessage(host + ":" + port, command);
    }
    @Override
ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolDecoder.java
@@ -1,6 +1,7 @@
package com.ard.tianyu.netty.handler;// MessageProtocolDecoder.java
import com.ard.tianyu.netty.command.*;
import com.ard.common.utils.ByteUtils;
import com.ard.tianyu.netty.command.BaseCmd;
import com.ard.tianyu.netty.command.camera.CameraModeReplyCmd;
import com.ard.tianyu.netty.command.camera.CapturePicReplyCmd;
import com.ard.tianyu.netty.command.camera.RecordStartReplyCmd;
@@ -16,11 +17,11 @@
import com.ard.tianyu.netty.command.register.RegisterReplyCmd;
import com.ard.tianyu.netty.command.register.UavInitReplyCmd;
import com.ard.tianyu.netty.protobuf.TelemetryDataPB;
import com.google.protobuf.InvalidProtocolBufferException;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;
import java.util.List;
@Slf4j
@@ -45,6 +46,7 @@
        }
        byte cmdCode = in.readByte();
        BaseCmd baseCmd = null;
        byte actionCode;
        switch (cmdCode) {
            case 0x01:
                byte success = in.readByte(); // 读取是否成功
@@ -56,13 +58,10 @@
                baseCmd = new HeartbeatReplyCmd(timestamp);
                log.debug("心跳回复: 时间戳 {}", timestamp);
                break;
            case 0x03:
            case 0x04:
                break;
            case 0x0A:
                byte status = in.readByte();
                baseCmd = new BoxPowerStatusReplyCmd(status);
                log.debug("云盒开机/关机通知回复: 状态 {}", String.format("0x%02X", status & 0xFF));
                log.debug("云盒开机/关机通知回复: 状态 {}", ByteUtils.byteToHex(status));
                break;
            case (byte) 0xA8:
                byte[] uavStatus = new byte[dataLength - 1];
@@ -72,181 +71,109 @@
                log.debug("无人机状态数据: {}", uavState.getBoxSn());
                break;
            case (byte) 0xA9:
                byte[] uavData = new byte[dataLength - 1];
                in.readBytes(uavData);
                TelemetryDataPB.TelemetryData telemetryData = TelemetryDataPB.TelemetryData.parseFrom(uavData);
                byte[] telemetryDataBytes = new byte[dataLength - 1];
                in.readBytes(telemetryDataBytes);
                TelemetryDataPB.TelemetryData telemetryData = TelemetryDataPB.TelemetryData.parseFrom(telemetryDataBytes);
                baseCmd = new UavTelemetryDataCmd(telemetryData);
                log.debug("遥测数据: {}", telemetryData.getBoxSn());
                break;
            case (byte) 0x0C:
                int delayTime = in.readInt();
                uavData = new byte[15];
                in.readBytes(uavData);
                //  String boxSn12 = new String(uavData);
                uavData = new byte[dataLength - 20];
                in.readBytes(uavData);
                TelemetryDataPB.SignalInfo signalInfo = TelemetryDataPB.SignalInfo.parseFrom(uavData);
                byte[] signalInfoBytes = new byte[15];
                in.readBytes(signalInfoBytes);
                signalInfoBytes = new byte[dataLength - 20];
                in.readBytes(signalInfoBytes);
                TelemetryDataPB.SignalInfo signalInfo = TelemetryDataPB.SignalInfo.parseFrom(signalInfoBytes);
                baseCmd = new SignalInfoCmd(signalInfo);
                log.debug("信道质量数据: {}", delayTime);
                break;
            case (byte) 0xD1:
                byte encryptionFlag = in.readByte();
                byte actionCode = in.readByte();
                switch (actionCode) {
                    case 0x11:
                        byte result = in.readByte();
                        int errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        String boxSn = new String(uavData);
                        baseCmd = new TakeOffReplyCmd(boxSn, result, errorCode);
                        log.debug("起飞回复: {}", boxSn);
                        break;
                    case 0x12:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new ReturnHomeReplyCmd(boxSn, result, errorCode);
                        log.debug("返航回复: {}", boxSn);
                        break;
                    case 0x13:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new ReturnHomeCancelReplyCmd(boxSn, result, errorCode);
                        log.debug("取消返航回复: {}", boxSn);
                        break;
                    case 0x14:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new LandReplyCmd(boxSn, result, errorCode);
                        log.debug("降落回复: {}", boxSn);
                        break;
                    case 0x15:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new LandCancelReplyCmd(boxSn, result, errorCode);
                        log.debug("取消降落回复: {}", boxSn);
                        break;
                    case 0x29:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new LandForcedReplyCmd(boxSn, result, errorCode);
                        log.debug("强制降落回复: {}", boxSn);
                        break;
                    case 0x32:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new EmergencyBrakeReplyCmd(boxSn, result, errorCode);
                        log.debug("紧急制动回复: {}", boxSn);
                        break;
                    case 0x39:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new PointControlReachedReplyCmd(boxSn, result, errorCode);
                        log.debug("到达指点飞行位置回复: {}", boxSn);
                        break;
                    case 0x22:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new CameraModeReplyCmd(boxSn, result, errorCode);
                        log.debug("设置相机模式回复: {}", boxSn);
                        break;
                    case 0x23:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new CapturePicReplyCmd(boxSn, result, errorCode);
                        log.debug("拍照回复: {}", boxSn);
                        break;
                    case 0x24:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new RecordStartReplyCmd(boxSn, result, errorCode);
                        log.debug("开始录像回复: {}", boxSn);
                        break;
                    case 0x25:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new RecordStopReplyCmd(boxSn, result, errorCode);
                        log.debug("停止录像回复: {}", boxSn);
                        break;
                    case 0x2E:
                        result = in.readByte();
                        errorCode = in.readInt();
                        uavData = new byte[15];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new FlyWayEndReplyCmd(boxSn, result, errorCode);
                        log.debug("航线结束通知回复: {}", boxSn);
                        break;
                    case 0x3D:
                        byte batteryPower = in.readByte();
                        double longitude = in.readDouble();
                        double latitude = in.readDouble();
                        int altitude = in.readInt();
                        uavData = new byte[dataLength - 24];
                        in.readBytes(uavData);
                        boxSn = new String(uavData);
                        baseCmd = new UavReadyReplyCmd(boxSn, batteryPower, longitude, latitude, altitude);
                        log.debug("无人机准备完成通知回复: 电池电量 {}", String.format("0x%02X", batteryPower & 0xFF));
                        break;
                    case 0x42:
                        byte[] uavData1 = new byte[dataLength - 18];
                        in.readBytes(uavData1);
                        byte[] uavData2 = new byte[15];
                        in.readBytes(uavData2);
                        boxSn = new String(uavData2);
                        TelemetryDataPB.UavInitInfo uavInitInfo = TelemetryDataPB.UavInitInfo.parseFrom(uavData1);
                        baseCmd = new UavInitReplyCmd(boxSn, uavInitInfo);
                        log.debug("无人机初始化数据: {}", boxSn);
                        break;
                    default:
                        log.warn("无法识别的命令码动作编号:{}", String.format("0x%02X", actionCode & 0xFF));
                        break;
                }
                in.readByte();//加密标志
                actionCode = in.readByte();//动作编号
                baseCmd = processD1ReplyCmd(in, actionCode);
                break;
            default:
                encryptionFlag = in.readByte();
                in.readByte();//加密标志
                actionCode = in.readByte();
                log.warn("无法识别的命令码: {},加密标志:{},动作编号:{}",
                        String.format("0x%02X", cmdCode & 0xFF),
                        String.format("0x%02X", encryptionFlag & 0xFF),
                        String.format("0x%02X", actionCode & 0xFF));//String.format("0x%02X", cmdCode & 0xFF)
                log.warn("无法识别的命令码: {},动作编号:{}",
                        ByteUtils.byteToHex(cmdCode),
                        ByteUtils.byteToHex(actionCode)
                );
        }
        if (baseCmd != null) {
            out.add(baseCmd);
        }
    }
    //处理D1指令回复
    private BaseCmd processD1ReplyCmd(ByteBuf in, byte actionCode) {
        byte result = in.readByte();
        int errorCode = in.readInt();
        byte[] uavData = new byte[15];
        in.readBytes(uavData);
        String boxSn = new String(uavData);
        switch (actionCode) {
            case 0x11:
                return new TakeOffReplyCmd(boxSn, result, errorCode);
            case 0x12:
                return new ReturnHomeReplyCmd(boxSn, result, errorCode);
            case 0x13:
                return new ReturnHomeCancelReplyCmd(boxSn, result, errorCode);
            case 0x14:
                return new LandReplyCmd(boxSn, result, errorCode);
            case 0x15:
                return new LandCancelReplyCmd(boxSn, result, errorCode);
            case 0x29:
                return new LandForcedReplyCmd(boxSn, result, errorCode);
            case 0x32:
                return new EmergencyBrakeReplyCmd(boxSn, result, errorCode);
            case 0x39:
                return new PointControlReachedReplyCmd(boxSn, result, errorCode);
            case 0x22:
                return new CameraModeReplyCmd(boxSn, result, errorCode);
            case 0x23:
                return new CapturePicReplyCmd(boxSn, result, errorCode);
            case 0x24:
                return new RecordStartReplyCmd(boxSn, result, errorCode);
            case 0x25:
                return new RecordStopReplyCmd(boxSn, result, errorCode);
            case 0x2E:
                return new FlyWayEndReplyCmd(boxSn, result, errorCode);
            // Special cases
            case 0x3D:
            case 0x42:
                return processSpecialD1ReplyCmd(in, actionCode, boxSn);
            // Add other cases...
            default:
                log.warn("无法识别的动作编号:{}", ByteUtils.byteToHex(actionCode));
        }
        return null;
    }
    //处理特殊D1指令回复
    private BaseCmd processSpecialD1ReplyCmd(ByteBuf in, byte actionCode, String boxSn) {
        switch (actionCode) {
            //无人机准备完成通知回复
            case 0x3D:
                byte batteryPower = in.readByte();
                double longitude = in.readDouble();
                double latitude = in.readDouble();
                int altitude = in.readInt();
                return new UavReadyReplyCmd(boxSn, batteryPower, longitude, latitude, altitude);
            //无人机初始化信息回复
            case 0x42:
                byte[] uavData1 = new byte[in.readableBytes() - 15];
                in.readBytes(uavData1);
                TelemetryDataPB.UavInitInfo uavInitInfo;
                try {
                    uavInitInfo = TelemetryDataPB.UavInitInfo.parseFrom(uavData1);
                } catch (InvalidProtocolBufferException e) {
                    throw new RuntimeException(e);
                }
                return new UavInitReplyCmd(boxSn, uavInitInfo);
            default:
                return null;
        }
    }
}
ard-work/src/main/java/com/ard/tianyu/netty/handler/MessageProtocolEncoder.java
@@ -1,5 +1,6 @@
package com.ard.tianyu.netty.handler;// MessageProtocolEncoder.java
import com.ard.common.utils.ByteUtils;
import com.ard.tianyu.netty.command.BaseCmd;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
@@ -25,11 +26,7 @@
        out.getBytes(out.readerIndex(), encodedBytes); // 读取ByteBuf中的字节到字节数组中
        // log.warn("Encoded message bytes: {}", Arrays.toString(encodedBytes));
        // 将字节数组转换为十六进制格式
        StringBuilder hexString = new StringBuilder();
        for (byte b : encodedBytes) {
            hexString.append(String.format("%02X ", b)); // 将每个字节转换为两位十六进制并加上空格
        }
        log.warn("Encoded message in hex: {}", hexString.toString().trim());
        String hexString = ByteUtils.bytesToHexWithSpaces(encodedBytes);
        log.warn("Encoded message in hex: {}", hexString);
    }
}
ard-work/src/main/java/com/ard/tianyu/service/ITyService.java
@@ -193,7 +193,7 @@
    SignalInfo getSignalInfo(String boxSn);
    /**
     * 解析天宇服务端发来的消息
     * 处理回复消息
     */
    void processMessage(String key, BaseCmd tyMessage);
    void processReplyMessage(String key, BaseCmd tyMessage);
}
ard-work/src/main/java/com/ard/tianyu/service/impl/TyServiceImpl.java
@@ -1,5 +1,6 @@
package com.ard.tianyu.service.impl;
import com.ard.common.utils.ByteUtils;
import com.ard.tianyu.config.TyConfiguration;
import com.ard.tianyu.http.TyClient;
import com.ard.tianyu.model.cache.UavSignalInfoCache;
@@ -47,6 +48,8 @@
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
 * TyServiceImpl
@@ -63,6 +66,9 @@
    @Resource
    private TyConfiguration tyConfiguration;
    // 存储每个无人机的最后遥测处理时间
    private Map<String, Long> lastTelemetryTimes = new ConcurrentHashMap<>();
    /**
     * 获取天宇token信息
@@ -139,29 +145,12 @@
     */
    @Override
    public void registerMessage() {
        AccessTokenReceiver tyToken = tyConfiguration.getAccessToken();
        RegisterCmd registerMessage = new RegisterCmd(tyToken.getCompanyId(), tyToken.getAccessToken().getBytes());
        this.sendMessage(tyConfiguration.getIpPort(), registerMessage);
    }
    /**
     * 发送消息
     */
    public void sendMessage(String key, BaseCmd msg) {
        try {
            ChannelFuture future = TyNettyTcpClient.channels.get(key).getChannelFuture();
            Channel channel = future.channel();
            if (channel.isActive()) {
                InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
                String host = ipSocket.getHostString();
                int port = ipSocket.getPort();
                channel.writeAndFlush(msg); // 发送编码后的数据
                log.debug("向天宇服务器【{}:{}】发送数据,命令码:{}", host, port, String.format("0x%02X", msg.getCmdCode() & 0xFF));
            } else {
                log.error("与天宇服务器链接断开,无法发送数据!");
            }
            AccessTokenReceiver tyToken = tyConfiguration.getAccessToken();
            RegisterCmd registerMessage = new RegisterCmd(tyToken.getCompanyId(), tyToken.getAccessToken().getBytes());
            this.sendMessage(tyConfiguration.getIpPort(), registerMessage);
        } catch (Exception e) {
            log.error("发送消息异常:{}", e.getMessage());
            log.error("注册天宇平台失败:{}", e.getMessage());
        }
    }
@@ -366,7 +355,7 @@
     */
    @Override
    public void emeragencyBrake(EmeragencyBrakeDTO emeragencyBrakeDTO) {
        EmergencyBrakeCmd emergencyBrakeCmd=new EmergencyBrakeCmd(emeragencyBrakeDTO);
        EmergencyBrakeCmd emergencyBrakeCmd = new EmergencyBrakeCmd(emeragencyBrakeDTO);
        this.sendMessage(tyConfiguration.getIpPort(), emergencyBrakeCmd);
    }
@@ -398,7 +387,7 @@
     * 处理天宇服务器返回的消息
     */
    @Override
    public void processMessage(String key, BaseCmd tyCommand) {
    public void processReplyMessage(String key, BaseCmd tyCommand) {
        byte cmdCode = tyCommand.getCmdCode();
        switch (cmdCode) {
            case 0x01:
@@ -438,10 +427,25 @@
            case (byte) 0xA9:
                // 处理无人机遥测消息
                UavTelemetryDataCmd uavTelemetryDataCmd = (UavTelemetryDataCmd) tyCommand;
                TelemetryData telemetryData = UavTelemetryDataConverter.convertProtoToEntity(uavTelemetryDataCmd.getTelemetryData());
                UavTelemetryDataCache.putUavTelemetryData(telemetryData);
                logPrint("无人机遥测", telemetryData.getBoxSn());
                String boxSn = ByteUtils.bytesToString(uavTelemetryDataCmd.getBoxSn()); // 假设有获取无人机ID的方法
                long currentTime = System.currentTimeMillis();
                // 获取无人机的最后处理时间,如果没有则设置为0
                long lastTelemetryTime = lastTelemetryTimes.getOrDefault(boxSn, 0L);
                // 检查与上次处理时间的间隔
                if (currentTime - lastTelemetryTime >= 1000) { // 1秒
                    TelemetryData telemetryData = UavTelemetryDataConverter.convertProtoToEntity(uavTelemetryDataCmd.getTelemetryData());
                    UavTelemetryDataCache.putUavTelemetryData(telemetryData);
                    logPrint("无人机遥测", telemetryData.getBoxSn());
                    // 更新最后处理时间
                    lastTelemetryTimes.put(boxSn, currentTime);
                } else {
                    log.debug("无人机 " + boxSn + " 遥测数据接收过于频繁,已跳过处理");
                }
                break;
            case (byte) 0x0C:
                // 处理无人机信道质量数据
                SignalInfoCmd signalInfoCmd = (SignalInfoCmd) tyCommand;
@@ -524,7 +528,7 @@
                break;
            default:
                // 处理其他消息
                log.debug("收到其他消息-->其他消息");
                log.warn("收到其他消息-->{}", tyCommand.getCmdCode());
        }
    }
@@ -537,7 +541,7 @@
    private void logPrint(String message, byte result, Integer errorCode) {
        log.info("收到{}回复:{} 错误码:{}【{}】",
                message,
                String.format("0x%02X", result & 0xFF),
                ByteUtils.byteToHex(result),
                errorCode,
                ErrorCodeEnum.getDescByCode(errorCode));
    }
@@ -552,4 +556,25 @@
        HeartbeatCmd heartbeatMessage = new HeartbeatCmd(timestamp);
        this.sendMessage(tyConfiguration.getIpPort(), heartbeatMessage);
    }
    /**
     * 发送消息
     */
    private void sendMessage(String key, BaseCmd msg) {
        try {
            ChannelFuture future = TyNettyTcpClient.channels.get(key).getChannelFuture();
            Channel channel = future.channel();
            if (channel.isActive()) {
                InetSocketAddress ipSocket = (InetSocketAddress) channel.remoteAddress();
                String host = ipSocket.getHostString();
                int port = ipSocket.getPort();
                channel.writeAndFlush(msg); // 发送编码后的数据
                log.debug("向天宇服务器【{}:{}】发送数据,命令码:{}", host, port, String.format("0x%02X", msg.getCmdCode() & 0xFF));
            } else {
                log.error("与天宇服务器链接断开,无法发送数据!");
            }
        } catch (Exception e) {
            log.error("发送消息异常:{}", e.getMessage());
        }
    }
}