Java实现JT/T808以及T/GDRTA002-2020车辆协议对接

简介

JT/T808,道路运输车辆卫星定位系统终端通信协议及数据格式,本标准规定了道路运输车辆卫星定位系统车载终端与监管/监控平台之间的通信协议与数据格式 , 包括协议基础、通信连接、消息处理、协议分类与要求及数据格式。
T/GDRTA002-2020,道路运输车辆智能视频监控报警系统通讯协议规范,广东省道路运输协会发布。
目前仅实现了JT/T808-2013、JT/T808-2019以及粤标T/GDRTA002-2020协议对接,通讯方为TCP,UDP未实现,不符合项目需求可参考已实现的代码更改。项目采用SpringBoot框架,整体结构如下图所示

在这里插入图片描述

主体实现

终端消息上报流程
Jtt808Decoder——》Jtt808ServerHandler——》AbstractJtt808Decoder——》IProcessor——》Jtt808Encoder

Jtt808Decoder

拿到任何一份协议时,首先看的是消息的组成结构,根据它的消息组成结构选择对应的解码器,例如固定长度、分隔符以及自定义长度LengthFieldBasedFrameDecoder解码器。从而解决粘包拆包问题,业务层只处理完整的一条消息,无需考虑其它。
JTT808协议是以0x7e开头0x7e结尾,可考虑分隔符解码器;
在这里插入图片描述
T/GDRTA002-2020协议文件数据上传是以0x30 0x31 0x63 0x64开头,可根据数据长度判断当前接收数据是否完整,可考虑使用自定义长度解码器。
在这里插入图片描述
由于需要同时支持以上两种消息,所以需要自己结合分隔符和自定义长度解码器实现对所有消息的区分。

package com.bho.jtt808.protocol;

import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import com.bho.jtt808.protocol.message.MsgBody;
import com.bho.jtt808.protocol.message.MsgHeader;
import com.bho.jtt808.protocol.message.MsgStructure;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.CorruptedFrameException;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;

import java.nio.ByteOrder;
import java.util.Arrays;
import java.util.List;

/**
 * 808消息解码器
 * @author wanzh
 * @date 2022/8/11
 */
@Slf4j
public class Jtt808Decoder extends ByteToMessageDecoder {

    private final ByteBuf[] delimiters;

    private final ByteOrder byteOrder;
    private final int maxFrameLength;
    private final int lengthFieldOffset;
    private final int lengthFieldLength;
    private final int lengthFieldEndOffset;
    private final int lengthAdjustment;
    private final int initialBytesToStrip;
    private final boolean failFast;

    private final boolean stripDelimiter;

    private boolean discardingTooLongFrame;
    private long tooLongFrameLength;
    private long bytesToDiscard;
    private int frameLengthInt = -1;


    public Jtt808Decoder() {
        //针对文件数据上传消息
        this.byteOrder = ByteOrder.BIG_ENDIAN;
        this.maxFrameLength = 65598;
        this.lengthFieldOffset = 58;
        this.lengthFieldLength = 4;
        this.lengthAdjustment = 0;
        this.lengthFieldEndOffset = 62;
        this.initialBytesToStrip = 0;
        this.failFast = true;
        //针对0x7e开头0x7e结尾消息
        this.stripDelimiter = true;
        ByteBuf byteBuf = Unpooled.buffer();
        byteBuf.writeByte(MsgConst.MSG_SIGN);
        this.delimiters = new ByteBuf[]{byteBuf.slice(byteBuf.readerIndex(), byteBuf.readableBytes())};
    }

    @Override
    protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        Object decoded = null;
        int len = in.readableBytes();
        if (len < MsgConst.MSG_V2011_SHORTEST_LENGTH - 1) {
            return;
        }
        Channel channel = ctx.channel();
        int msgType = Integer.parseInt(String.valueOf(channel.attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).get()));
        if (msgType == MsgConst.MSG_TYPE_INIT || msgType == MsgConst.MSG_TYPE_FILE) {
            in.markReaderIndex();
            byte firstByte = in.readByte();
            byte secondByte = in.readByte();
            byte thridByte = in.readByte();
            byte fourthByte = in.readByte();
            in.resetReaderIndex();
            if (firstByte == MsgConst.MSG_SIGN) {
                decoded = delimiterBaseFrameDecode(ctx, in);
            } else if (firstByte == MsgConst.ATTACHMENT_BIT_STREAM_FIRST_BYTE
                    && secondByte == MsgConst.ATTACHMENT_BIT_STREAM_SECOND_BYTE
                    && thridByte == MsgConst.ATTACHMENT_BIT_STREAM_THIRD_BYTE
                    && fourthByte == MsgConst.ATTACHMENT_BIT_STREAM_FOURTH_BYTE) {
                decoded = lengthFieldBaseFrameDecode(ctx, in);
            }
        } else {
            decoded = delimiterBaseFrameDecode(ctx, in);
        }

        if (decoded != null) {
            ByteBuf byteBuf = (ByteBuf) decoded;
            int readLen = byteBuf.readableBytes();
            if (readLen == 0) {
                channel.attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).set(MsgConst.MSG_TYPE_NORAML);
                return;
            }
            byte[] bytes = new byte[readLen];
            byteBuf.readBytes(bytes);
            short msgId = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 0, 2));
            if (bytes[0] == MsgConst.ATTACHMENT_BIT_STREAM_FIRST_BYTE
                    && bytes[1] == MsgConst.ATTACHMENT_BIT_STREAM_SECOND_BYTE
                    && bytes[2] == MsgConst.ATTACHMENT_BIT_STREAM_THIRD_BYTE
                    && bytes[3] == MsgConst.ATTACHMENT_BIT_STREAM_FOURTH_BYTE) {
                channel.attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).set(MsgConst.MSG_TYPE_FILE);
                MsgStructure msgStructure = MsgStructure.builder()
                        .msgHeader(MsgHeader.builder().msgId(MsgTypeEnum.attachment_data_upload.getMsgId()).build())
                        .msgBody(MsgBody.builder().bytes(bytes).build())
                        .build();
                log.info("{}:{}", MsgTypeEnum.attachment_data_upload.getName(), Helper.bytesToHex(bytes));
                out.add(msgStructure);
                return;
            }
            MsgTypeEnum msgTypeEnum = MsgTypeEnum.getInstanceByMsgId(msgId);
            if (msgId == MsgTypeEnum.attachment_begin_upload.getMsgId()
                    || msgId == MsgTypeEnum.attachment_end_upload.getMsgId()
                    || msgId == MsgTypeEnum.alarm_attachment.getMsgId()) {
                channel.attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).set(MsgConst.MSG_TYPE_FILE);
            } else {
                channel.attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).set(MsgConst.MSG_TYPE_NORAML);
            }
            byte[] tempBytes = new byte[readLen + 2];
            tempBytes[0] = MsgConst.MSG_SIGN;
            tempBytes[readLen + 1] = MsgConst.MSG_SIGN;
            System.arraycopy(bytes, 0, tempBytes, 1, readLen);
            log.info("{}:{}", msgTypeEnum == null ? "未知的消息类型" : msgTypeEnum.getName(), Helper.bytesToHex(tempBytes));
            MsgStructure msgStructure = MsgHelper.bytesToMsgStructure(tempBytes);
            if (msgStructure == null) {
                return;
            }
            out.add(msgStructure);
        }

    }



    protected Object lengthFieldBaseFrameDecode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
        long frameLength = 0;
        if (frameLengthInt == -1) { // new frame

            if (discardingTooLongFrame) {
                discardingTooLongFrame(in);
            }

            if (in.readableBytes() < lengthFieldEndOffset) {
                return null;
            }

            int actualLengthFieldOffset = in.readerIndex() + lengthFieldOffset;
            frameLength = getUnadjustedFrameLength(in, actualLengthFieldOffset, lengthFieldLength, byteOrder);

            if (frameLength < 0) {
                failOnNegativeLengthField(in, frameLength, lengthFieldEndOffset);
            }

            frameLength += lengthAdjustment + lengthFieldEndOffset;

            if (frameLength < lengthFieldEndOffset) {
                failOnFrameLengthLessThanLengthFieldEndOffset(in, frameLength, lengthFieldEndOffset);
            }

            if (frameLength > maxFrameLength) {
                exceededFrameLength(in, frameLength);
                return null;
            }
            // never overflows because it's less than maxFrameLength
            frameLengthInt = (int) frameLength;
        }
        if (in.readableBytes() < frameLengthInt) { // frameLengthInt exist , just check buf
            return null;
        }
        if (initialBytesToStrip > frameLengthInt) {
            failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, initialBytesToStrip);
        }
        in.skipBytes(initialBytesToStrip);

        // extract frame
        int readerIndex = in.readerIndex();
        int actualFrameLength = frameLengthInt - initialBytesToStrip;
        ByteBuf frame = extractFrame(ctx, in, readerIndex, actualFrameLength);
        in.readerIndex(readerIndex + actualFrameLength);
        frameLengthInt = -1; // start processing the next frame
        return frame;
    }


    protected Object delimiterBaseFrameDecode(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
        // Try all delimiters and choose the delimiter which yields the shortest frame.
        int minFrameLength = Integer.MAX_VALUE;
        ByteBuf minDelim = null;
        for (ByteBuf delim: delimiters) {
            int frameLength = indexOf(buffer, delim);
            if (frameLength >= 0 && frameLength < minFrameLength) {
                minFrameLength = frameLength;
                minDelim = delim;
            }
        }

        if (minDelim != null) {
            int minDelimLength = minDelim.capacity();
            ByteBuf frame;

            if (discardingTooLongFrame) {
                // We've just finished discarding a very large frame.
                // Go back to the initial state.
                discardingTooLongFrame = false;
                buffer.skipBytes(minFrameLength + minDelimLength);

                long tooLongFrameLength = this.tooLongFrameLength;
                this.tooLongFrameLength = 0;
                if (!failFast) {
                    fail(tooLongFrameLength);
                }
                return null;
            }

            if (minFrameLength > maxFrameLength) {
                // Discard read frame.
                buffer.skipBytes(minFrameLength + minDelimLength);
                fail(minFrameLength);
                return null;
            }

            if (stripDelimiter) {
                frame = buffer.readRetainedSlice(minFrameLength);
                buffer.skipBytes(minDelimLength);
            } else {
                frame = buffer.readRetainedSlice(minFrameLength + minDelimLength);
            }

            return frame;
        } else {
            if (!discardingTooLongFrame) {
                if (buffer.readableBytes() > maxFrameLength) {
                    // Discard the content of the buffer until a delimiter is found.
                    tooLongFrameLength = buffer.readableBytes();
                    buffer.skipBytes(buffer.readableBytes());
                    discardingTooLongFrame = true;
                    if (failFast) {
                        fail(tooLongFrameLength);
                    }
                }
            } else {
                // Still discarding the buffer since a delimiter is not found.
                tooLongFrameLength += buffer.readableBytes();
                buffer.skipBytes(buffer.readableBytes());
            }
            return null;
        }
    }

    private void fail(int frameLength) {
        if (frameLength > 0) {
            throw new TooLongFrameException(
                    "frame length exceeds " + maxFrameLength +
                            ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException(
                    "frame length exceeds " + maxFrameLength +
                            " - discarding");
        }
    }

    private static int indexOf(ByteBuf haystack, ByteBuf needle) {
        for (int i = haystack.readerIndex(); i < haystack.writerIndex(); i ++) {
            int haystackIndex = i;
            int needleIndex;
            for (needleIndex = 0; needleIndex < needle.capacity(); needleIndex ++) {
                if (haystack.getByte(haystackIndex) != needle.getByte(needleIndex)) {
                    break;
                } else {
                    haystackIndex ++;
                    if (haystackIndex == haystack.writerIndex() &&
                            needleIndex != needle.capacity() - 1) {
                        return -1;
                    }
                }
            }

            if (needleIndex == needle.capacity()) {
                // Found the needle from the haystack!
                return i - haystack.readerIndex();
            }
        }
        return -1;
    }
    private void discardingTooLongFrame(ByteBuf in) {
        long bytesToDiscard = this.bytesToDiscard;
        int localBytesToDiscard = (int) Math.min(bytesToDiscard, in.readableBytes());
        in.skipBytes(localBytesToDiscard);
        bytesToDiscard -= localBytesToDiscard;
        this.bytesToDiscard = bytesToDiscard;

        failIfNecessary(false);
    }

    protected long getUnadjustedFrameLength(ByteBuf buf, int offset, int length, ByteOrder order) {
        buf = buf.order(order);
        long frameLength;
        switch (length) {
            case 1:
                frameLength = buf.getUnsignedByte(offset);
                break;
            case 2:
                frameLength = buf.getUnsignedShort(offset);
                break;
            case 3:
                frameLength = buf.getUnsignedMedium(offset);
                break;
            case 4:
                frameLength = buf.getUnsignedInt(offset);
                break;
            case 8:
                frameLength = buf.getLong(offset);
                break;
            default:
                throw new DecoderException(
                        "unsupported lengthFieldLength: " + lengthFieldLength + " (expected: 1, 2, 3, 4, or 8)");
        }
        return frameLength;
    }

    private static void failOnNegativeLengthField(ByteBuf in, long frameLength, int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
                "negative pre-adjustment length field: " + frameLength);
    }

    private static void failOnFrameLengthLessThanLengthFieldEndOffset(ByteBuf in,
                                                                      long frameLength,
                                                                      int lengthFieldEndOffset) {
        in.skipBytes(lengthFieldEndOffset);
        throw new CorruptedFrameException(
                "Adjusted frame length (" + frameLength + ") is less " +
                        "than lengthFieldEndOffset: " + lengthFieldEndOffset);
    }

    private void exceededFrameLength(ByteBuf in, long frameLength) {
        long discard = frameLength - in.readableBytes();
        tooLongFrameLength = frameLength;

        if (discard < 0) {
            // buffer contains more bytes then the frameLength so we can discard all now
            in.skipBytes((int) frameLength);
        } else {
            // Enter the discard mode and discard everything received so far.
            discardingTooLongFrame = true;
            bytesToDiscard = discard;
            in.skipBytes(in.readableBytes());
        }
        failIfNecessary(true);
    }

    private static void failOnFrameLengthLessThanInitialBytesToStrip(ByteBuf in, long frameLength, int initialBytesToStrip) {
        in.skipBytes((int) frameLength);
        throw new CorruptedFrameException(
                "Adjusted frame length (" + frameLength + ") is less " +
                        "than initialBytesToStrip: " + initialBytesToStrip);
    }

    private void failIfNecessary(boolean firstDetectionOfTooLongFrame) {
        if (bytesToDiscard == 0) {
            // Reset to the initial state and tell the handlers that
            // the frame was too large.
            long tooLongFrameLength = this.tooLongFrameLength;
            this.tooLongFrameLength = 0;
            discardingTooLongFrame = false;
            if (!failFast || firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }
        } else {
            // Keep discarding and notify handlers if necessary.
            if (failFast && firstDetectionOfTooLongFrame) {
                fail(tooLongFrameLength);
            }
        }
    }


    protected ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, int length) {
        return buffer.retainedSlice(index, length);
    }

    private void fail(long frameLength) {
        if (frameLength > 0) {
            throw new TooLongFrameException(
                    "Adjusted frame length exceeds " + maxFrameLength +
                            ": " + frameLength + " - discarded");
        } else {
            throw new TooLongFrameException(
                    "Adjusted frame length exceeds " + maxFrameLength +
                            " - discarding");
        }
    }

}

Jtt808Encoder

编码器是下发消息到终端,可不考虑粘包拆包问题,比较简单。

package com.bho.jtt808.protocol;

import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import com.bho.jtt808.protocol.message.MsgStructure;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import lombok.extern.slf4j.Slf4j;

/**
 * 808消息编码器
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
public class Jtt808Encoder extends MessageToByteEncoder<MsgStructure> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, MsgStructure msgStructure, ByteBuf byteBuf) throws Exception {
        short msgId = msgStructure.getMsgHeader().getMsgId();
        MsgTypeEnum msgTypeEnum = MsgTypeEnum.getInstanceByMsgId(msgId);
        byte[] bytes = MsgHelper.msgStructureToBytes(msgStructure);
        log.info("{}:{}", msgTypeEnum.getName(), Helper.bytesToHex(bytes));
        byteBuf.writeBytes(bytes);
    }

}

AbstractJtt808Decoder

各类消息解码器抽象类,因为不需要控制终端下发指令,所以未实现AbstractJtt808Encoder。若需要控制终端,则需要将Channel信息缓存下来,具体可参考另外一篇文章。

package com.bho.jtt808.protocol.codec.decoder;


import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.protocol.message.MsgStructure;

/**
 * 消息解码器
 * @author wanzh
 * @date 2022/8/13
 */
public abstract class AbstractJtt808Decoder<U extends BaseUplinkMsg, R extends BaseReplyMsg> {

    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public U decode(MsgStructure reportMsg) {
        U u = null;
        if (reportMsg.getMsgHeader().getProtocolVersion() == MsgConst.PROTOCOL_VERSION_2019) {
            u = decode2019(reportMsg);
        } else {
            u = decode2013(reportMsg);
        }
        if (u != null) {
            u.setMobile(reportMsg.getMsgHeader().getMobile());
        }
        return u;
    }

    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public abstract U decode2019(MsgStructure reportMsg);


    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public abstract U decode2013(MsgStructure reportMsg);



    /**
     * 消息回复 需要经过业务逻辑处理才知道消息回复内容
     * 例如文件上传完成消息 需要判断是否需要补传
     * @param msgStructure 上报消息
     * @param r 应答结果
     * @return
     */
    public abstract MsgStructure reply(MsgStructure msgStructure, R r);


    /**
     * 消息回复 不需要经过业务逻辑处理就知道消息回复内容
     * @param msgStructure 上报消息
     * @param u 上行消息
     * @return
     */
    public abstract MsgStructure reply(MsgStructure msgStructure, U u);






}

Jtt808ServerHandler

package com.bho.jtt808.protocol;

import com.alibaba.fastjson.JSONObject;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.util.MsgHelper;
import com.bho.jtt808.util.SpringUtil;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.protocol.processor.IProcessor;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.util.AttributeKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

/**
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
public class Jtt808ServerHandler extends SimpleChannelInboundHandler<MsgStructure> {


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("新的连接:{}", ctx.channel().remoteAddress());
        super.channelActive(ctx);
        ctx.channel().attr(AttributeKey.valueOf(MsgConst.CHANNEL_MSG_TYPE)).set(MsgConst.MSG_TYPE_INIT);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("异常连接:{},错误信息:{}", ctx.channel().remoteAddress(), cause);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("断开的连接:{}", ctx.channel().remoteAddress());
        super.channelInactive(ctx);

    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        // 获取idle事件
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            // 读等待事件
            if (event.state() == IdleState.READER_IDLE) {
                if(ctx.channel().isActive() || ctx.channel().isOpen()) {
                    ctx.channel().close();
                }
            }
        } else {
            super.userEventTriggered(ctx, evt);
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, MsgStructure curMsg) throws Exception {
        short msgId = curMsg.getMsgHeader().getMsgId();
        MsgTypeEnum msgTypeEnum = MsgTypeEnum.getInstanceByMsgId(msgId);
        if (msgTypeEnum == null) {
            //未知的消息类型 默认回复通用应答
            byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(curMsg.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
            MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(curMsg.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
            ctx.writeAndFlush(replyMsgStructure);
            return;
        }
        if (StringUtils.hasLength(msgTypeEnum.getDecoderBeanId())) {
            try {
                //1.获取对应的解码器解析成对应的消息类
                AbstractJtt808Decoder abstractJtt808Decoder = SpringUtil.getBean(msgTypeEnum.getDecoderBeanId(), AbstractJtt808Decoder.class);
                BaseUplinkMsg baseUplinkMsg = abstractJtt808Decoder.decode(curMsg);
                if (baseUplinkMsg != null) {
                    log.info("上行消息: {}", JSONObject.toJSONString(baseUplinkMsg));
                }
                //2.不需要经过业务逻辑处理 消息应答
                MsgStructure replyMsgStructure = abstractJtt808Decoder.reply(curMsg, baseUplinkMsg);
                if (replyMsgStructure != null) {
                    ctx.writeAndFlush(replyMsgStructure);
                }
                //3.业务逻辑处理
                BaseReplyMsg replyMsg = null;
                if (StringUtils.hasLength(msgTypeEnum.getProcessorBeanId())) {
                    IProcessor iProcessor = SpringUtil.getBean(msgTypeEnum.getProcessorBeanId(), IProcessor.class);
                    replyMsg = iProcessor.process(baseUplinkMsg);
                }
                //4.需要经过业务逻辑处理 消息应答
                replyMsgStructure = abstractJtt808Decoder.reply(curMsg, replyMsg);
                if (replyMsgStructure != null) {
                    ctx.writeAndFlush(replyMsgStructure);
                }
            } catch (Exception e) {
                log.error("channelRead0 error {}", e);
            }

        }


    }
}

Jtt808Server

开启一个tcp服务监听终端上报消息

package com.bho.jtt808.protocol;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

/**
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
@Component
public class Jtt808Server {


    @Value("${netty.server.port:22222}")
    private Integer port;//Netty服务端端口号

    @Value("${netty.server.readerIdleTime:60}")
    private Integer readerIdleTime;//读取超时时间(分钟)


    private ServerBootstrap serverBootstrap;

    private Channel serverChannel;

    private EventLoopGroup bossEventLoopGroup;

    private EventLoopGroup workerEventLoopGroup;

    public void startNettyServer() throws InterruptedException {
        serverBootstrap = new ServerBootstrap();
        bossEventLoopGroup = new NioEventLoopGroup();
        workerEventLoopGroup = new NioEventLoopGroup();
        serverBootstrap.group(bossEventLoopGroup,workerEventLoopGroup);
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
        serverBootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
        serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
        serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
        serverBootstrap.childOption(ChannelOption.SO_LINGER, 0);
        serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            protected void initChannel(SocketChannel socketChannel) throws Exception {
                ChannelPipeline pipeline = socketChannel.pipeline();
                if(readerIdleTime > 0){
                    pipeline.addLast(new IdleStateHandler(readerIdleTime, 0, 0, TimeUnit.MINUTES));
                }
                pipeline.addLast(new Jtt808Decoder());
                pipeline.addLast(new Jtt808Encoder());
                pipeline.addLast(new Jtt808ServerHandler());
            }
        });
        ChannelFuture future = this.serverBootstrap.bind(this.port).sync();
        this.serverChannel = future.channel();
    }

    public void shutdownNettyServer() throws InterruptedException {
        serverChannel.close().sync();
        bossEventLoopGroup.shutdownGracefully().await();
        workerEventLoopGroup.shutdownGracefully().await();
    }

    @PostConstruct
    public void init() {
        log.info("run netty server");
        try {
            startNettyServer();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            Thread.currentThread().interrupt();
        }
    }

    /**
     * .
     */
    @PreDestroy
    public void destory() {
        log.info("shutdown netty server");
        try {
            shutdownNettyServer();
        } catch (InterruptedException e) {
            log.error(e.getMessage());
            Thread.currentThread().interrupt();
        }
    }

}



解码处理

AbstractJtt808Decoder

package com.bho.jtt808.protocol.codec.decoder;


import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.protocol.message.MsgStructure;

/**
 * 消息解码器
 * @author wanzh
 * @date 2022/8/13
 */
public abstract class AbstractJtt808Decoder<U extends BaseUplinkMsg, R extends BaseReplyMsg> {

    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public U decode(MsgStructure reportMsg) {
        U u = null;
        if (reportMsg.getMsgHeader().getProtocolVersion() == MsgConst.PROTOCOL_VERSION_2019) {
            u = decode2019(reportMsg);
        } else {
            u = decode2013(reportMsg);
        }
        if (u != null) {
            u.setMobile(reportMsg.getMsgHeader().getMobile());
        }
        return u;
    }

    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public abstract U decode2019(MsgStructure reportMsg);


    /**
     * 消息解码
     *
     * @param reportMsg 上报消息
     * @return
     */
    public abstract U decode2013(MsgStructure reportMsg);



    /**
     * 消息回复 需要经过业务逻辑处理才知道消息回复内容
     * 例如文件上传完成消息 需要判断是否需要补传
     * @param msgStructure 上报消息
     * @param r 应答结果
     * @return
     */
    public abstract MsgStructure reply(MsgStructure msgStructure, R r);


    /**
     * 消息回复 不需要经过业务逻辑处理就知道消息回复内容
     * @param msgStructure 上报消息
     * @param u 上行消息
     * @return
     */
    public abstract MsgStructure reply(MsgStructure msgStructure, U u);






}

AlarmAttachmentDecoder

package com.bho.jtt808.protocol.codec.decoder.attachment;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentDescInfo;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AlarmAttachmentUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.BCDUtil;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 报警附件信息消息解码器
 * @author wanzh
 * @date 2022/8/17
 */
@Component
public class AlarmAttachmentDecoder extends AbstractJtt808Decoder<AlarmAttachmentUplinkMsg, BaseReplyMsg> {
    @Override
    public AlarmAttachmentUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        String terminalId = Helper.trimStringOfBytes(Arrays.copyOfRange(bytes, 0, 30));
        String tagTerminalId = Helper.trimStringOfBytes(Arrays.copyOfRange(bytes, 30, 60));
        String tagAcqTime = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 60, 66));
        short tagAlertSN = Binary.beQuadBytesToShort(bytes[66]);
        short tagAttachmentNumber = Binary.beQuadBytesToShort(bytes[67]);
        String tagRetainCol = Helper.bytesToHexNoBlank(Arrays.copyOfRange(bytes, 68, 70));
        String alarmGuid = Helper.trimStringOfBytes(Arrays.copyOfRange(bytes, 70, 102));
        Short messageType = Binary.beQuadBytesToShort(bytes[102]);
        Short attachmentNumber = Binary.beQuadBytesToShort(bytes[103]);
        List<AttachmentDescInfo> attachmentInfoList = new ArrayList<>(attachmentNumber);
        for (short i = 104; i < bytes.length;) {
            Short fileNameLen = Binary.beQuadBytesToShort(bytes[i]);
            String fileName = Helper.gbkStringOfBytes(Arrays.copyOfRange(bytes, i + 1, i + 1 + fileNameLen));
            int fileSize = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, i + 1 + fileNameLen, i + 5 + fileNameLen));
            i += 5 + fileNameLen;
            AttachmentDescInfo attachmentInfo = AttachmentDescInfo.builder()
                    .fileName(fileName).fileSize(fileSize).fileNameLen(fileNameLen)
                    .build();
            attachmentInfoList.add(attachmentInfo);
        }

        return AlarmAttachmentUplinkMsg.builder()
                .terminalId(terminalId).alarmGuid(alarmGuid).tagTerminalId(tagTerminalId).tagAlertSN(tagAlertSN)
                .tagAcqTime(DateUtil.parse(Helper.acqTimeToDBFormat(tagAcqTime), DatePattern.NORM_DATETIME_PATTERN))
                .tagAttachmentNumber(tagAttachmentNumber).tagRetainCol(tagRetainCol).uploadTime(new Date())
                .messageType(messageType).attachmentNumber(attachmentNumber).attachmentInfoList(attachmentInfoList)
                .build();
    }

    @Override
    public AlarmAttachmentUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, AlarmAttachmentUplinkMsg alarmAttachmentUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }
}

AttachmentBeginUploadDecoder

package com.bho.jtt808.protocol.codec.decoder.attachment;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AttachmentBeginUploadUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 附件信息开始上传消息解码器
 * @author wanzh
 * @date 2022/8/17
 */
@Component
public class AttachmentBeginUploadDecoder extends AbstractJtt808Decoder<AttachmentBeginUploadUplinkMsg, BaseReplyMsg> {


    @Override
    public AttachmentBeginUploadUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        Short fileNameLen = Binary.beQuadBytesToShort(bytes[0]);
        String fileName = Helper.gbkStringOfBytes(Arrays.copyOfRange(bytes, 1, 1 + fileNameLen));
        Short fileType = Binary.beQuadBytesToShort(bytes[1 + fileNameLen]);
        int fileSize = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 2 + fileNameLen, 6 + fileNameLen));
        return AttachmentBeginUploadUplinkMsg.builder()
                .fileNameLen(fileNameLen).fileName(fileName)
                .fileType(fileType).fileSize(fileSize)
                .build();
    }

    @Override
    public AttachmentBeginUploadUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }


    @Override
    public MsgStructure reply(MsgStructure msgStructure, AttachmentBeginUploadUplinkMsg attachmentBeginUploadUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }
}

AttachmentDataUploadDecoder

package com.bho.jtt808.protocol.codec.decoder.attachment;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AttachmentDataUploadUplinkMsg;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 附件数据上传解码器
 * @author wanzh
 * @date 2022/8/17
 */
@Component
public class AttachmentDataUploadDecoder extends AbstractJtt808Decoder<AttachmentDataUploadUplinkMsg, BaseReplyMsg> {
    @Override
    public AttachmentDataUploadUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        String fileName = Helper.gbkStringOfBytes(Arrays.copyOfRange(bytes, 4, 54));
        int dataOffset = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 54, 58));
        int dataLen = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 58, 62));
        byte[] dataBody = Arrays.copyOfRange(bytes, 62, 62 + dataLen);
        return AttachmentDataUploadUplinkMsg.builder()
                .fileName(fileName).dataOffset(dataOffset)
                .dataLen(dataLen).dataBody(dataBody)
                .build();
    }

    @Override
    public AttachmentDataUploadUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, AttachmentDataUploadUplinkMsg attachmentDataUploadUplinkMsg) {
        return null;
    }
}

AttachmentEndUploadDecoder

package com.bho.jtt808.protocol.codec.decoder.attachment;

import com.bho.jtt808.protocol.message.reply.AttachmentEndUploadReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AttachmentEndUploadUplinkMsg;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 附件信息结束上传消息解码器
 * @author wanzh
 * @date 2022/8/17
 */
@Component
public class AttachmentEndUploadDecoder extends AbstractJtt808Decoder<AttachmentEndUploadUplinkMsg, AttachmentEndUploadReplyMsg> {


    @Override
    public AttachmentEndUploadUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        Short fileNameLen = Binary.beQuadBytesToShort(bytes[0]);
        String fileName = Helper.gbkStringOfBytes(Arrays.copyOfRange(bytes, 1, 1 + fileNameLen));
        Short fileType = Binary.beQuadBytesToShort(bytes[1 + fileNameLen]);
        int fileSize = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 2 + fileNameLen, 6 + fileNameLen));
        return AttachmentEndUploadUplinkMsg.builder()
                .fileNameLen(fileNameLen).fileName(fileName)
                .fileType(fileType).fileSize(fileSize)
                .build();
    }

    @Override
    public AttachmentEndUploadUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, AttachmentEndUploadReplyMsg replyMsg) {
        short reissueDataPackNum = replyMsg.getReissueDataPackNum();
        short fileNameLen = replyMsg.getFileNameLen();
        byte[] bytes = new byte[4 + reissueDataPackNum * 8 + fileNameLen];
        bytes[0] = (byte) fileNameLen;
        System.arraycopy(Helper.bytesOfGBKString(replyMsg.getFileName()), 0, bytes, 1, fileNameLen);
        bytes[1 + fileNameLen] = (byte) replyMsg.getFileType();
        bytes[2 + fileNameLen] = (byte) replyMsg.getFileUploadResult();
        bytes[3 + fileNameLen] = (byte) reissueDataPackNum;
        List<AttachmentEndUploadReplyMsg.DataPackInfo> reissueDataPacks = replyMsg.getReissueDataPacks();
        for (int i = 0; i < reissueDataPackNum; i++) {
            int offset = 4 + fileNameLen + i * 8;
            AttachmentEndUploadReplyMsg.DataPackInfo dataPack = reissueDataPacks.get(i);
            System.arraycopy(Binary.intToBEQuadBytes(dataPack.getOffset()), 0, bytes,  offset, 4);
            System.arraycopy(Binary.intToBEQuadBytes(dataPack.getLen()), 0, bytes,  offset + 4, 4);
        }
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bytes, MsgTypeEnum.attachment_end_upload_reply);
        return replyMsgStructure;
    }


    @Override
    public MsgStructure reply(MsgStructure msgStructure, AttachmentEndUploadUplinkMsg uploadUplinkMsg) {
        return null;
    }
}

AuthorizationDecoder

package com.bho.jtt808.protocol.codec.decoder.authorization;

import com.bho.jtt808.protocol.message.reply.PlatformReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AuthorizationUplinkMsg;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.Arrays;


/**
 * 终端授权解码器
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
@Component
public class AuthorizationDecoder extends AbstractJtt808Decoder<AuthorizationUplinkMsg, PlatformReplyMsg> {


    @Override
    public AuthorizationUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bodyBytes = reportMsg.getMsgBody().getBytes();
        int authCodeLen = Binary.beQuadBytesToInt(bodyBytes[0]);
        String authCode = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 1, authCodeLen + 1));
        String imei = Helper.gbkStringOfBytes(Arrays.copyOfRange(bodyBytes, authCodeLen + 1, authCodeLen + 16));
        String softwareVersion = Helper.gbkStringOfBytes(Arrays.copyOfRange(bodyBytes, authCodeLen + 16, authCodeLen + 36));
        return AuthorizationUplinkMsg.builder()
                .authCodeLen(authCodeLen).authCode(authCode).imei(imei)
                .softwareVersion(softwareVersion)
                .build();
    }

    @Override
    public AuthorizationUplinkMsg decode2013(MsgStructure reportMsg) {
        byte[] bodyBytes = reportMsg.getMsgBody().getBytes();
        String authCode = Helper.trimStringOfBytes(bodyBytes);
        return AuthorizationUplinkMsg.builder()
                .authCode(authCode)
                .build();
    }


    @Override
    public MsgStructure reply(MsgStructure msgStructure, PlatformReplyMsg platformReplyMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), platformReplyMsg.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, AuthorizationUplinkMsg authorizationUplinkMsg) {
        return null;
    }
}

HeartbeatDecoder

package com.bho.jtt808.protocol.codec.decoder.heartbeat;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

/**
 * 终端心跳解码器
 * @author wanzh
 * @date 2022/8/20
 */
@Component
public class HeartbeatDecoder extends AbstractJtt808Decoder<BaseUplinkMsg, BaseReplyMsg> {
    @Override
    public BaseUplinkMsg decode2019(MsgStructure reportMsg) {
        return null;
    }

    @Override
    public BaseUplinkMsg decode2013(MsgStructure reportMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseUplinkMsg baseUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }
}

LocationDecoder

package com.bho.jtt808.protocol.codec.decoder.location;

import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import com.bho.jtt808.protocol.message.reply.LocationReplyMsg;
import com.bho.jtt808.protocol.message.uplink.LocationUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


/**
 * 位置上报解码器
 * @author wanzh
 * @date 2022/8/13
 */
@Component
public class LocationDecoder extends AbstractJtt808Decoder<LocationUplinkMsg, LocationReplyMsg> {

    @Value("${attachment.server.ip:127.0.0.1}")
    private String ip;

    @Value("${attachment.server.port.tcp:22222}")
    private short tcpPort;

    @Value("${attachment.server.port.udp:0}")
    private short udpPort;

    @Override
    public LocationUplinkMsg decode2019(MsgStructure reportMsg) {
        return LocationHelper.getLocationUplinkMsg(reportMsg.getMsgBody().getBytes());
    }



    @Override
    public LocationUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }


    @Override
    public MsgStructure reply(MsgStructure msgStructure, LocationReplyMsg replyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, LocationUplinkMsg uplinkMsg) {
        LocationReplyMsg replyMsg = getLocationReplyMsg(uplinkMsg);
        if (replyMsg.getMsgId() == MsgTypeEnum.platform_reply.getMsgId()) {
            byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), replyMsg.getReplyVal());
            MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
            return replyMsgStructure;
        }
        byte[] ipBytes = Helper.bytesOfGBKString(replyMsg.getIp());
        int ipLength = ipBytes.length;
        byte[] bodyBytes = new byte[93 + ipLength];
        bodyBytes[0] = (byte) ipLength;
        System.arraycopy(ipBytes, 0, bodyBytes, 1, ipLength);
        System.arraycopy(Binary.shortToBEDBBytes(replyMsg.getTcpPort()), 0, bodyBytes, ipLength + 1, 2);
        System.arraycopy(Binary.shortToBEDBBytes(replyMsg.getUdpPort()), 0, bodyBytes, ipLength + 3, 2);
        System.arraycopy(replyMsg.getATag().getBytes(), 0, bodyBytes, ipLength + 5, 40);
        System.arraycopy(replyMsg.getAlarmGuid().getBytes(), 0, bodyBytes, ipLength + 45, 32);
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.attachment_uplod_reply);
        return replyMsgStructure;
    }

    private LocationReplyMsg getLocationReplyMsg(LocationUplinkMsg uplinkMsg) {
        ExtendAlarm extendAlarm = uplinkMsg.getExtendAlarm();
        LocationReplyMsg replyMsg = new LocationReplyMsg();
        int replyVal = GeneralReplyResultTypeEnum.sucess.getResult();
        short msgId = MsgTypeEnum.platform_reply.getMsgId();
        if (extendAlarm != null) {
            replyVal = GeneralReplyResultTypeEnum.alarm_confirm.getResult();
            if (extendAlarm.getATag().attachmentNumber > 0) {
                //如果有附件上传消息 则回复附件上传指令9208
                msgId = MsgTypeEnum.attachment_uplod_reply.getMsgId();
                replyMsg.setIp(ip);
                replyMsg.setTcpPort(tcpPort);
                replyMsg.setUdpPort(udpPort);
                replyMsg.setATag(extendAlarm.getATag());
                replyMsg.setAlarmGuid(extendAlarm.getAlarmGuid());
            }
        }
        replyMsg.setMsgId(msgId);
        replyMsg.setReplyVal(replyVal);
        return replyMsg;
    }




}

LocationHelper

package com.bho.jtt808.protocol.codec.decoder.location;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.jtt808.protocol.message.other.location.LocationData;
import com.bho.jtt808.protocol.message.other.location.ReportStatus;
import com.bho.jtt808.protocol.message.other.location.extra.BaseLocationExtraData;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import com.bho.jtt808.protocol.message.uplink.LocationUplinkMsg;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.enums.LocationExtraEnum;
import com.bho.jtt808.enums.LocationExtraTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.location.extra.ILocationExtraDecoder;
import com.bho.jtt808.util.BCDUtil;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.SpringUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 位置单独上报与位置批量上报都需要走共同逻辑 单独抽出一个解析类方便公用
 * @author wanzh
 * @date 2022/8/19
 */
public class LocationHelper {


    /***
     * 获取位置上报上行消息
     * @param bodyBytes
     * @return
     */
    public static LocationUplinkMsg getLocationUplinkMsg(byte[] bodyBytes) {
        // 获取位置基本信息
        LocationData locationData = getLocationData(bodyBytes);
        // 获取状态信息
        ReportStatus reportStatus = getStatusData(locationData.getStatusFlag());
        // 获取位置附加信息项
        List<BaseLocationExtraData> extraDataList = new ArrayList<>();
        // 告警信息
        ExtendAlarm extendAlarm = null;
        if (bodyBytes.length > MsgConst.LOCATION_REPORTING_BASIC_LENGTH) {
            byte[] locationExtraBytes = Arrays.copyOfRange(bodyBytes, MsgConst.LOCATION_REPORTING_BASIC_LENGTH, bodyBytes.length);
            for (int i = 0; i < locationExtraBytes.length;) {
                int extraId = Binary.beQuadBytesToInt(locationExtraBytes[i]);
                int extraLen = Binary.beQuadBytesToInt(locationExtraBytes[i + 1]);
                byte[] extraInfoBytes = Arrays.copyOfRange(locationExtraBytes, i + 2, i + 2 + extraLen);
                i += extraLen + 2;
                LocationExtraEnum locationExtraEnum = LocationExtraEnum.getInstance(extraId);
                if (locationExtraEnum == null) {
                    continue;
                }
                if (!StringUtils.hasLength(locationExtraEnum.getDecoderId())){
                    continue;
                }
                ILocationExtraDecoder locationExtraDecoder = SpringUtil.getBean(locationExtraEnum.getDecoderId(), ILocationExtraDecoder.class);
                if (locationExtraDecoder == null) {
                    continue;
                }
                BaseLocationExtraData baseLocationExtraData = locationExtraDecoder.decode(extraInfoBytes);
                baseLocationExtraData.setExtraId(extraId);
                baseLocationExtraData.setExtraLen(extraLen);
                baseLocationExtraData.setExtraVal(Helper.bytesToHexNoBlank(extraInfoBytes));
                LocationExtraTypeEnum extraType = locationExtraEnum.getExtraType();
                switch (extraType) {
                    case property:
                        BeanUtils.copyProperties(baseLocationExtraData, locationData);
                        extraDataList.add(baseLocationExtraData);
                        break;
                    case extra_alarm:
                        extendAlarm = (ExtendAlarm) baseLocationExtraData;
                        break;
                }
            }
        }
        return LocationUplinkMsg.builder()
                .locationData(locationData).reportStatus(reportStatus)
                .extendAlarm(extendAlarm).locationExtraDatas(extraDataList)
                .build();
    }

    /**
     * 获取位置基本信息
     * @param bytes
     * @return
     */
    private static LocationData getLocationData(byte[] bytes) {
        int alertFlag = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 0, 4));
        int statusFlag = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 4, 8));
        int latitude = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 8, 12));
        int longitude = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 12, 16));
        short height = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 16, 18));
        short gpsSpeed = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 18, 20));
        short direction = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 20, 22));
        String acqTime = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 22, 28));
        return LocationData.builder()
                .alertFlag(alertFlag).statusFlag(statusFlag).height(height).direction(direction)
                .latitude(BigDecimal.valueOf(latitude / MsgConst.DECIMAL_MILLION))
                .longitude(BigDecimal.valueOf(longitude / MsgConst.DECIMAL_MILLION))
                .gpsSpeed(BigDecimal.valueOf(gpsSpeed / MsgConst.DECIMAL_TEN))
                .acqTime(DateUtil.parse(Helper.acqTimeToDBFormat(acqTime), DatePattern.NORM_DATETIME_PATTERN))
                .build();
    }

    /**
     * 获取状态数据
     * @param statusVar
     * @return
     */
    private static ReportStatus getStatusData(Integer statusVar) {
        return ReportStatus.builder()
                .acc((statusVar >>> 0) & 1).located((statusVar >>> 1) & 1)
                .latitude((statusVar >>> 2) & 1).longitude((statusVar >>> 3) & 1)
                .operating((statusVar >>> 4) & 1).encrypted((statusVar >>> 5) & 1)
                .forwardCollisionWarn((statusVar >>> 6) & 1).deviationWarn((statusVar >>> 7) & 1)
                .payLoad((statusVar >>> 8) & 11).oilCircuit((statusVar >>> 10) & 1)
                .electricCircuit((statusVar >>> 11) & 1).doorLock((statusVar >>> 12) & 1)
                .frontDoor((statusVar >>> 13) & 1).midDoor((statusVar >>> 14) & 1)
                .rearDoor((statusVar >>> 15) & 1).cabsDoor((statusVar >>> 16) & 1)
                .customDoor((statusVar >>> 17) & 1).gps((statusVar >>> 18) & 1)
                .bds((statusVar >>> 19) & 1).glonass((statusVar >>> 20) & 1)
                .galileo((statusVar >>> 21) & 1).driving((statusVar >>> 22) & 1)
                .liftState((statusVar >>> 26) & 1)
                .build();

    }

}

LocationBatchDecoder

package com.bho.jtt808.protocol.codec.decoder.location;

import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.LocationBatchUplinkMsg;
import com.bho.jtt808.protocol.message.uplink.LocationUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * 位置批量上传解析器
 * @author wanzh
 * @date 2022/8/19
 */
@Component
public class LocationBatchDecoder extends AbstractJtt808Decoder<LocationBatchUplinkMsg, BaseReplyMsg> {
    @Override
    public LocationBatchUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        short dataNum = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 0, 2));
        short missedTag = Binary.beQuadBytesToShort(bytes[2]);
        List<LocationUplinkMsg> locations = new ArrayList<>(dataNum);
        for (int i = 3; i < bytes.length;) {
            short dataLen = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, i, i + 2));
            byte[] locationBytes = Arrays.copyOfRange(bytes, i + 2, i + 2 + dataLen);
            LocationUplinkMsg locationUplinkMsg = LocationHelper.getLocationUplinkMsg(locationBytes);
            locationUplinkMsg.getLocationData().setMissedTag(missedTag);
            locations.add(locationUplinkMsg);
            i += dataLen + 2;
        }
        return LocationBatchUplinkMsg.builder()
                .dataNum(dataNum).missedTag(missedTag).locations(locations)
                .build();
    }

    @Override
    public LocationBatchUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, LocationBatchUplinkMsg locationBatchUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;    }
}

ILocationExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra;


import com.bho.jtt808.protocol.message.other.location.extra.BaseLocationExtraData;

/**
 * 位置上报附加消息解码器接口
 * @author wanzh
 * @date 2022/8/15
 */
public interface ILocationExtraDecoder<E extends BaseLocationExtraData> {

    E decode(byte[] bytes);
}

OilNumExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra;

import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.protocol.message.other.location.extra.OilNumExtraData;
import com.bho.jtt808.util.Helper;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
 * 油量解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class OilNumExtraDecoder implements ILocationExtraDecoder<OilNumExtraData> {

    @Override
    public OilNumExtraData decode(byte[] bytes) {
        int oilNum = Integer.valueOf(Helper.readableHexOfBytes(bytes), 16);
        return OilNumExtraData.builder()
                .oilNum(BigDecimal.valueOf(oilNum / MsgConst.DECIMAL_TEN))
                .build();
    }
}

MileageExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra;

import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.protocol.message.other.location.extra.MileageExtraData;
import com.bho.jtt808.util.Binary;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;

/**
 * 里程解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class MileageExtraDecoder implements ILocationExtraDecoder<MileageExtraData> {

    @Override
    public MileageExtraData decode(byte[] bytes) {
        int mileage = Binary.beQuadBytesToInt(bytes);
        return MileageExtraData.builder()
                .mileage(BigDecimal.valueOf(mileage / MsgConst.DECIMAL_TEN))
                .build();
    }
}

InversionSensorExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra;

import com.bho.jtt808.protocol.message.other.location.extra.InversionSensorExtraData;
import com.bho.jtt808.util.Helper;
import org.springframework.stereotype.Component;

import java.util.Arrays;


/**
 * 正反转解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class InversionSensorExtraDecoder implements ILocationExtraDecoder<InversionSensorExtraData> {
    @Override
    public InversionSensorExtraData decode(byte[] bytes) {
        byte[] inversionByte = Arrays.copyOfRange(bytes, 0, 1);
        byte[] speedByte = Arrays.copyOfRange(bytes, 1, 3);
        int inversion = Integer.valueOf(Helper.readableHexOfBytes(inversionByte), 16);
        int speed = Integer.valueOf(Helper.readableHexOfBytes(speedByte), 16);
        return InversionSensorExtraData.builder()
                .inversionSensor(inversion).inversionSpeed(speed * 17)
                .build();
    }
}

AbstractAlarmExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra.alarm;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.enums.LocationExtraEnum;
import com.bho.jtt808.protocol.codec.decoder.location.extra.ILocationExtraDecoder;
import com.bho.jtt808.protocol.message.other.location.extra.BaseLocationExtraData;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.AlarmTag;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.AlarmVehicleStatus;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import com.bho.jtt808.util.BCDUtil;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.IdGeneratorUtil;

import java.math.BigDecimal;
import java.util.Arrays;

/**
 * @author wanzh
 * @date 2022/8/16
 */

public abstract class AbstractAlarmExtraDecoder<A extends ExtendAlarm, E extends BaseLocationExtraData> implements ILocationExtraDecoder {

    /**
     * 获取偏移量
     * @return
     */
    protected abstract int getOffset();


    /**
     * 获取告警类型
     * @return
     */
    protected abstract LocationExtraEnum getAlarmType();


    /**
     * 设置告警公用字段
     */
    protected void setExtendAlarm(A a, byte[] bytes) {
        a.setAlarmId(Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes,0, 4)));
        a.setSignalStatus(Binary.beQuadBytesToShort(bytes[4]));
        a.setAlarmEventType(Binary.beQuadBytesToShort(bytes[5]));
        int offset = getOffset();
        a.setSpeed(Binary.beQuadBytesToShort(bytes[offset]));
        a.setHeight(Binary.beDBBytesToShort(Arrays.copyOfRange(bytes,offset + 1, offset + 3)));
        a.setLatitude(BigDecimal.valueOf(Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes,offset + 3, offset + 7)) / MsgConst.DECIMAL_MILLION));
        a.setLongitude(BigDecimal.valueOf(Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes,offset + 7, offset + 11)) / MsgConst.DECIMAL_MILLION));
        String acqTime = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, offset + 11, offset + 17));
        a.setAcqTime(DateUtil.parse(Helper.acqTimeToDBFormat(acqTime), DatePattern.NORM_DATETIME_PATTERN));
        short vehicleStatus = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, offset + 17, offset + 19));
        a.setAvs(getAvs(vehicleStatus));
        AlarmTag alarmTag = getAlarmTag(Arrays.copyOfRange(bytes, offset + 19, offset + 59));
        a.setATag(alarmTag);
        LocationExtraEnum alarmType = getAlarmType();
        a.setExtraId(alarmType.getExtId());
        a.setAlarmCategory(alarmType.getCode().toUpperCase());
        a.setAlarmGuid(IdGeneratorUtil.generateId());
        a.setId(IdGeneratorUtil.generateId());
    }

    private AlarmVehicleStatus getAvs(short valVs) {
        boolean acc = (valVs & 1) == 1;
        boolean turnLeft = (valVs & 2) == 1;
        boolean turnRight = (valVs & 4) == 1;
        boolean windshieldWiper = (valVs & 8) == 1;
        boolean brake = (valVs & 16) == 1;
        boolean cardPlugin = (valVs & 32) == 1;
        boolean located = (valVs & 1024) == 1;

        // bit6 - bit9
        short customized01 = (short) (valVs & 0x03c0);
        // bit11 - bit15
        short customized02 = (short) (valVs & 0xf800);
        return AlarmVehicleStatus.builder()
                .acc(acc).turnLeft(turnLeft).turnRight(turnRight)
                .windshieldWiper(windshieldWiper).brake(brake)
                .cardPlugin(cardPlugin).located(located)
                .customized01(customized01).customized02(customized02)
                .build();
    }

    private AlarmTag getAlarmTag(byte[] bytes) {
        String terminalId = Helper.trimStringOfBytes(Arrays.copyOfRange(bytes, 0, 30));
        String acqTime = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 30, 36));
        short alertSN = Binary.beQuadBytesToShort(bytes[36]);
        short attachmentNumber = Binary.beQuadBytesToShort(bytes[37]);
        return AlarmTag.builder()
                .terminalId(terminalId).alertSN(alertSN).attachmentNumber(attachmentNumber).bytes(bytes)
                .acqTime(DateUtil.parse(Helper.acqTimeToDBFormat(acqTime), DatePattern.NORM_DATETIME_PATTERN))
                .build();
    }



}

AdasAlarmExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra.alarm;
import com.bho.jtt808.enums.AlarmTypeEnum;
import com.bho.jtt808.enums.LocationExtraEnum;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ADASAlarm;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import com.bho.jtt808.util.Binary;
import org.springframework.stereotype.Component;

/**
 * 高级驾驶辅助报警信息数据解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class AdasAlarmExtraDecoder extends AbstractAlarmExtraDecoder<ADASAlarm, ExtendAlarm> {
    @Override
    protected int getOffset() {
        //车速偏移量
        return 12;
    }

    @Override
    protected LocationExtraEnum getAlarmType() {
        return LocationExtraEnum.ADAS;
    }

    @Override
    public ExtendAlarm decode(byte[] bytes) {
        ADASAlarm alarm = new ADASAlarm();
        setExtendAlarm(alarm, bytes);
        alarm.setAlarmLevel(Binary.beQuadBytesToShort(bytes[6]));
        alarm.setFrontSpeed(Binary.beQuadBytesToShort(bytes[7]));
        alarm.setFrontDistance(Binary.beQuadBytesToShort(bytes[8]));
        alarm.setOffTrackType(Binary.beQuadBytesToShort(bytes[9]));
        alarm.setRoadMark(Binary.beQuadBytesToShort(bytes[10]));
        alarm.setRoadMarkValue(Binary.beQuadBytesToShort(bytes[11]));
        AlarmTypeEnum instance = AlarmTypeEnum.getInstance(alarm.getAlarmCategory(), alarm.getAlarmEventType());
        alarm.setAlarmEventName(instance == null ? AlarmTypeEnum.ADAS_XX.getAlarmName() : instance.getAlarmName());
        return alarm;
    }
}

BsdAlarmExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra.alarm;

import com.bho.jtt808.enums.AlarmTypeEnum;
import com.bho.jtt808.enums.LocationExtraEnum;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.BSDAlarm;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import org.springframework.stereotype.Component;

/**
 * 盲区监测系统报警信息数据解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class BsdAlarmExtraDecoder extends AbstractAlarmExtraDecoder<BSDAlarm, ExtendAlarm> {
    @Override
    protected int getOffset() {
        //车速偏移量
        return 6;
    }

    @Override
    protected LocationExtraEnum getAlarmType() {
        return LocationExtraEnum.BSD;
    }

    @Override
    public ExtendAlarm decode(byte[] bytes) {
        BSDAlarm alarm = new BSDAlarm();
        setExtendAlarm(alarm, bytes);
        AlarmTypeEnum instance = AlarmTypeEnum.getInstance(alarm.getAlarmCategory(), alarm.getAlarmEventType());
        alarm.setAlarmEventName(instance == null ? AlarmTypeEnum.BSD_XX.getAlarmName() : instance.getAlarmName());
        return alarm;
    }
}

DsmAlarmExtraDecoder

package com.bho.jtt808.protocol.codec.decoder.location.extra.alarm;

import com.bho.jtt808.enums.AlarmTypeEnum;
import com.bho.jtt808.enums.LocationExtraEnum;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.DSMAlarm;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import com.bho.jtt808.util.Binary;
import org.springframework.stereotype.Component;

/**
 * 驾驶员状态监测系统报警信息解码器
 * @author wanzh
 * @date 2022/8/16
 */
@Component
public class DsmAlarmExtraDecoder extends AbstractAlarmExtraDecoder<DSMAlarm, ExtendAlarm> {
    @Override
    protected int getOffset() {
        //车速偏移量
        return 12;
    }

    @Override
    protected LocationExtraEnum getAlarmType() {
        return LocationExtraEnum.DSM;
    }

    @Override
    public ExtendAlarm decode(byte[] bytes) {
        DSMAlarm alarm = new DSMAlarm();
        setExtendAlarm(alarm, bytes);
        AlarmTypeEnum instance = AlarmTypeEnum.getInstance(alarm.getAlarmCategory(), alarm.getAlarmEventType());
        alarm.setAlarmEventName(instance == null ? AlarmTypeEnum.DSM_XX.getAlarmName() : instance.getAlarmName());
        alarm.setAlarmLevel(Binary.beQuadBytesToShort(bytes[6]));
        alarm.setFatigueLevel(Binary.beQuadBytesToShort(bytes[7]));
        return alarm;
    }
}

LogoutDecoder

package com.bho.jtt808.protocol.codec.decoder.logout;

import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

/**
 * 终端注销解码器
 * @author wanzh
 * @date 2022/8/20
 */
@Component
public class LogoutDecoder extends AbstractJtt808Decoder<BaseUplinkMsg, BaseReplyMsg> {
    @Override
    public BaseUplinkMsg decode2019(MsgStructure reportMsg) {
        return null;
    }

    @Override
    public BaseUplinkMsg decode2013(MsgStructure reportMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseUplinkMsg baseUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }
}

RegisterDecoder

package com.bho.jtt808.protocol.codec.decoder.register;

import com.bho.jtt808.protocol.message.reply.RegisterReplyMsg;
import com.bho.jtt808.protocol.message.uplink.RegisterUplinkMsg;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.enums.RegisterReplyResultTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgHeader;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.Arrays;


/**
 * 终端注册解码器
 * @author wanzh
 * @date 2022/8/13
 */
@Component
public class RegisterDecoder extends AbstractJtt808Decoder<RegisterUplinkMsg, RegisterReplyMsg> {

    @Override
    public RegisterUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bodyBytes = reportMsg.getMsgBody().getBytes();
        short provinceId = Binary.beDBBytesToShort(Arrays.copyOfRange(bodyBytes, 0, 2));
        short cityId = Binary.beDBBytesToShort(Arrays.copyOfRange(bodyBytes, 2, 4));
        String manufacturerId = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 4, 15));
        String termType = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 15, 45));
        String termId = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 45, 75));
        byte vehicleColor = bodyBytes[75];
        String  plateNumber = Helper.gbkStringOfBytes(Arrays.copyOfRange(bodyBytes, 76, bodyBytes.length));
        return RegisterUplinkMsg.builder()
                .provinceId(provinceId).cityId(cityId).manufacturerId(manufacturerId)
                .termType(termType).termId(termId).vehicleColor(vehicleColor).plateNumber(plateNumber)
                .build();
    }

    @Override
    public RegisterUplinkMsg decode2013(MsgStructure reportMsg) {
        byte[] bodyBytes = reportMsg.getMsgBody().getBytes();
        short provinceId = Binary.beDBBytesToShort(Arrays.copyOfRange(bodyBytes, 0, 2));
        short cityId = Binary.beDBBytesToShort(Arrays.copyOfRange(bodyBytes, 2, 4));
        String manufacturerId = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 4, 9));
        String termType = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 9, 29));
        String termId = Helper.trimStringOfBytes(Arrays.copyOfRange(bodyBytes, 29, 36));
        byte vehicleColor = bodyBytes[36];
        String  plateNumber = Helper.gbkStringOfBytes(Arrays.copyOfRange(bodyBytes, 37, bodyBytes.length));
        return RegisterUplinkMsg.builder()
                .provinceId(provinceId).cityId(cityId).manufacturerId(manufacturerId)
                .termType(termType).termId(termId).vehicleColor(vehicleColor).plateNumber(plateNumber)
                .build();
    }


    @Override
    public MsgStructure reply(MsgStructure msgStructure, RegisterReplyMsg registerReplyMsg) {
        byte[] atnCodeBytes = null;
        MsgHeader msgHeader = msgStructure.getMsgHeader();
        if (registerReplyMsg.getResult() != RegisterReplyResultTypeEnum.sucess.getResult()){
            atnCodeBytes = new byte[0];
        } else {
            atnCodeBytes = Helper.bytesOfGBKString(msgHeader.getMobile());
        }
        byte[] msgSNBytes = Binary.shortToBEDBBytes(msgHeader.getMsgSN());
        byte[] bodyBytes = new byte[atnCodeBytes.length + 3];
        System.arraycopy(msgSNBytes, 0, bodyBytes, 0, 2);
        bodyBytes[2] = (byte) registerReplyMsg.getResult();
        System.arraycopy(atnCodeBytes, 0, bodyBytes, 3, atnCodeBytes.length);
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgHeader, bodyBytes, MsgTypeEnum.register_reply);
        return replyMsgStructure;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, RegisterUplinkMsg registerUplinkMsg) {
        return null;
    }
}

QueryServerTimeDecoder

package com.bho.jtt808.protocol.codec.decoder.servertime;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.BCDUtil;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 查询服务器时间解码器
 * @author wanzh
 * @date 2022/8/20
 */
@Component
public class QueryServerTimeDecoder extends AbstractJtt808Decoder<BaseUplinkMsg, BaseReplyMsg> {
    @Override
    public BaseUplinkMsg decode2019(MsgStructure reportMsg) {
        return new BaseUplinkMsg();
    }

    @Override
    public BaseUplinkMsg decode2013(MsgStructure reportMsg) {
        return new BaseUplinkMsg();
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseUplinkMsg baseUplinkMsg) {
        String time = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN).substring(2);
        byte[] bytes = BCDUtil.nbcdOfString(time, BCDUtil.BCD_MOBILE_LENGTH_2013);
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bytes, MsgTypeEnum.query_server_time_reply);
        return replyMsgStructure;
    }
}

StatusDecoder

package com.bho.jtt808.protocol.codec.decoder.status;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.protocol.codec.decoder.AbstractJtt808Decoder;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.StatusUplinkMsg;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.message.MsgStructure;
import com.bho.jtt808.util.BCDUtil;
import com.bho.jtt808.util.Binary;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgHelper;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.Arrays;

/**
 *
 * 车辆状态解码器
 * @author wanzh
 * @date 2022/8/20
 */
@Component
public class StatusDecoder extends AbstractJtt808Decoder<StatusUplinkMsg, BaseReplyMsg> {


    @Override
    public StatusUplinkMsg decode2019(MsgStructure reportMsg) {
        byte[] bytes = reportMsg.getMsgBody().getBytes();
        int statusFlag = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 0, 2));
        int latitude = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 2, 6));
        int longitude = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 6, 10));
        short height = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 10, 12));
        short gpsSpeed = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 12, 14));
        short direction = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 14, 16));
        String acqTime = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 16, 22));
        String carNo = Helper.gbkStringOfBytes(Arrays.copyOfRange(bytes, 22, 32));
        short sweepStatus = Binary.beQuadBytesToShort(bytes[49]);
        short carriageStatus = Binary.beQuadBytesToShort(bytes[50]);
        short liftState = Binary.beQuadBytesToShort(bytes[51]);
        short emptyWeightStatus = Binary.beQuadBytesToShort(bytes[52]);
        short violationStatus = Binary.beQuadBytesToShort(bytes[53]);
        int loadSensor = Binary.beQuadBytesToInt(Arrays.copyOfRange(bytes, 54, 58));
        return StatusUplinkMsg.builder()
                .height(height).direction(direction).carNo(carNo)
                .latitude(BigDecimal.valueOf(latitude / MsgConst.DECIMAL_MILLION))
                .longitude(BigDecimal.valueOf(longitude / MsgConst.DECIMAL_MILLION))
                .gpsSpeed(BigDecimal.valueOf(gpsSpeed / MsgConst.DECIMAL_TEN))
                .acqTime(DateUtil.parse(Helper.acqTimeToDBFormat(acqTime), DatePattern.NORM_DATETIME_PATTERN))
                .sweepStatus(sweepStatus).carriageStatus(carriageStatus).liftState(liftState)
                .emptyWeightStatus(emptyWeightStatus).violationStatus(violationStatus)
                .loadSensor(BigDecimal.valueOf(loadSensor))
                .build();
    }

    @Override
    public StatusUplinkMsg decode2013(MsgStructure reportMsg) {
        return decode2019(reportMsg);
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, BaseReplyMsg baseReplyMsg) {
        return null;
    }

    @Override
    public MsgStructure reply(MsgStructure msgStructure, StatusUplinkMsg statusUplinkMsg) {
        byte[] bodyBytes = MsgHelper.getGeneralReplyMsgBody(msgStructure.getMsgHeader(), GeneralReplyResultTypeEnum.sucess.getResult());
        MsgStructure replyMsgStructure = MsgHelper.getReplyMsgStructure(msgStructure.getMsgHeader(), bodyBytes, MsgTypeEnum.platform_reply);
        return replyMsgStructure;
    }
}

业务处理

IProcessor

package com.bho.jtt808.protocol.processor;


import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.BaseUplinkMsg;

/**
 * 消息业务处理层
 * @author wanzh
 * @date 2022/8/13
 */
public interface IProcessor<U extends BaseUplinkMsg, R extends BaseReplyMsg> {


    /**
     * 业务逻辑处理
     */
    R process(U u);




}

AlarmAttachmentProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.constant.RedisConst;
import com.bho.jtt808.enums.AttachmentTypeEnum;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentCacheData;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentDescInfo;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentDetailInfo;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AlarmAttachmentUplinkMsg;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.IdGeneratorUtil;
import com.bho.jtt808.util.MsgUtil;
import com.bho.jtt808.util.RedisUtil;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 报警附件业务处理类
 * @author wanzh
 * @date 2022/8/17
 */
@Component
public class AlarmAttachmentProcessor implements IProcessor<AlarmAttachmentUplinkMsg, BaseReplyMsg> {


    @Override
    public BaseReplyMsg process(AlarmAttachmentUplinkMsg uplinkMsg) {
        //1将报警附件信息推送到Kafka
        String id = IdGeneratorUtil.generateId();
        uplinkMsg.setId(id);
        //todo 换成自己项目对应的处理逻辑
        MsgUtil.sendMsg("alarmAttachmentInfoTopic", uplinkMsg);
        //2将附件列表详情信息缓存起来并推送到Kafka
        List<AttachmentDescInfo> attachmentInfos = uplinkMsg.getAttachmentInfoList();
        for (AttachmentDescInfo attachment : attachmentInfos) {
            String fileName = attachment.getFileName();
            //文件名前两个字符代表文件类型
            short fileType = Short.parseShort(fileName.substring(0, 2));
            AttachmentDetailInfo detailInfo = AttachmentDetailInfo.builder()
                    .id(IdGeneratorUtil.generateId()).attachmentInfoId(id).alarmGuid(uplinkMsg.getAlarmGuid())
                    .fileType(AttachmentTypeEnum.getInstanceByType(fileType).getKey()).fileSize(attachment.getFileSize())
                    .fileNameLen(attachment.getFileNameLen()).fileName(fileName)
                    .build();
            //todo 换成自己项目对应的处理逻辑
            MsgUtil.sendMsg("attachmentDetailInfoTopic", detailInfo);
            String fileKey = Helper.removeSymbolsOfFileName(fileName);
            AttachmentCacheData cacheData = new AttachmentCacheData();
            BeanUtils.copyProperties(detailInfo, cacheData);
            cacheData.setUploadDatas(Collections.emptyList());
            cacheData.setMobile(uplinkMsg.getMobile());
            RedisUtil.set(String.format(RedisConst.ATTACHMENT_KEY, fileKey), cacheData, 30L, TimeUnit.MINUTES);

        }
        return null;
    }

}

AttachmentBeginUploadProcessor


import java.util.concurrent.TimeUnit;

/**
 * 附件开始上传业务处理类
 * @author wanzh
 * @date 2022/8/17
 */
@Slf4j
@Component
public class AttachmentBeginUploadProcessor implements IProcessor<AttachmentBeginUploadUplinkMsg, BaseReplyMsg> {

    @Override
    public BaseReplyMsg process(AttachmentBeginUploadUplinkMsg uplinkMsg) {
        String fileKey = String.format(RedisConst.ATTACHMENT_KEY,
                                Helper.removeSymbolsOfFileName(uplinkMsg.getFileName()));
        AttachmentCacheData cacheData = RedisUtil.get(fileKey);
        if (cacheData == null) {
            log.error("报警附件消息缓存为空,附件开始上传消息丢弃");
            return null;
        }
        //更新附件上传状态
        cacheData.setFileSize(uplinkMsg.getFileSize());
        cacheData.setUploadStatus(AttachmentUploadStatusEnum.BEGIN.getStatus());
        //将附件状态变更信息推送给Kafka
        AttachmentDetailInfo detailInfo = new AttachmentDetailInfo();
        BeanUtils.copyProperties(cacheData, detailInfo);
        //todo 换成自己项目对应的处理逻辑
        MsgUtil.sendMsg("attachmentDetailInfoTopic", detailInfo);
        //更新缓存中的数据
        RedisUtil.set(fileKey, cacheData, 30L, TimeUnit.MINUTES);
        return null;
    }
}

AttachmentDataUploadProcessor

package com.bho.jtt808.protocol.processor;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;

import com.bho.jtt808.constant.RedisConst;
import com.bho.jtt808.enums.AttachmentUploadStatusEnum;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentCacheData;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentDetailInfo;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentUploadData;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentUploadResult;
import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AttachmentDataUploadUplinkMsg;
import com.bho.jtt808.util.FileUtil;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.MsgUtil;
import com.bho.jtt808.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 附件数据上传业务处理类
 * @author wanzh
 * @date 2022/8/17
 */
@Slf4j
@Component
public class AttachmentDataUploadProcessor implements IProcessor<AttachmentDataUploadUplinkMsg, BaseReplyMsg> {


    @Value("${attachment.upload.dir:attachment}")
    private String uploadDir;


    @Override
    public BaseReplyMsg process(AttachmentDataUploadUplinkMsg uplinkMsg) {
        //获取附件缓存数据
        String fileKey = String.format(RedisConst.ATTACHMENT_KEY,
                                Helper.removeSymbolsOfFileName(uplinkMsg.getFileName()));
        AttachmentCacheData attachmentCacheData = RedisUtil.get(fileKey);
        if (attachmentCacheData == null) {
            log.error("报警附件消息缓存为空,附件数据上传消息丢弃");
            return null;
        }
        int uploadStatus = attachmentCacheData.getUploadStatus();
        //添加当前上传附件数据并排序
        List<AttachmentUploadData> uploadDatas = attachmentCacheData.getUploadDatas();
        AttachmentUploadData addUploadData = AttachmentUploadData.builder()
                .startOffset(uplinkMsg.getDataOffset()).data(uplinkMsg.getDataBody())
                .endOffset(uplinkMsg.getDataOffset() + uplinkMsg.getDataLen())
                .build();
        uploadDatas.add(addUploadData);
        Collections.sort(uploadDatas);
        //判断附件数据是否已上传完毕
        int curDataLen = 0;
        int preEndOffset = 0;
        for (int i = 0; i < uploadDatas.size(); i++) {
            AttachmentUploadData curData = uploadDatas.get(i);
            if (curData.getStartOffset() >= preEndOffset) {
                curDataLen += (curData.getEndOffset() - curData.getStartOffset());
                preEndOffset = curData.getEndOffset();
                continue;
            }
            int diffOffset = curData.getEndOffset() - preEndOffset;
            curDataLen += (diffOffset > 0 ? diffOffset : 0);
            preEndOffset = curData.getEndOffset();
        }
        AttachmentUploadResult uploadResult = null;
        if (attachmentCacheData.getFileSize() > curDataLen) {
            //附件数据还未上传完毕
            attachmentCacheData.setUploadStatus(AttachmentUploadStatusEnum.UPLOAD.getStatus());
            attachmentCacheData.setUploadDatas(uploadDatas);
        } else {
            //附件数据已上传完毕保存附件
            attachmentCacheData.setUploadStatus(AttachmentUploadStatusEnum.END.getStatus());
            byte[] body = new byte[curDataLen];
            for (int i = 0; i < uploadDatas.size(); i++) {
                AttachmentUploadData curData = uploadDatas.get(i);
                System.arraycopy(curData.getData(), 0, body, curData.getStartOffset(), curData.getEndOffset() - curData.getStartOffset());
            }
            uploadResult = saveAttachment(body, attachmentCacheData);
            attachmentCacheData.setUploadDatas(null);
        }
        //更新缓存中的数据
        RedisUtil.set(fileKey, attachmentCacheData, 30L, TimeUnit.MINUTES);
        //将状态变更信息推送给Kafka
        if (uploadStatus != attachmentCacheData.getUploadStatus()) {
            AttachmentDetailInfo detailInfo = new AttachmentDetailInfo();
            BeanUtils.copyProperties(attachmentCacheData, detailInfo);
            detailInfo.setStored(uploadResult != null);
            if (detailInfo.isStored()) {
                detailInfo.setBucketName(uploadResult.getData().getBucketName());
                detailInfo.setFilePath(uploadResult.getData().getFilePath());
            }
            //todo 换成自己项目对应的处理逻辑
            MsgUtil.sendMsg("attachmentDetailInfoTopic", detailInfo);
        }
        return null;
    }

    private AttachmentUploadResult saveAttachment(byte[] bytes, AttachmentCacheData cacheData) {
        String fileName = cacheData.getFileName();
        String mobile = cacheData.getMobile();
        /**
         *附件本地保存 实际是远程保存 oss或者其它
         */
        if (StringUtils.hasLength(uploadDir)) {
            String storeDir = uploadDir + File.separator + DateUtil.format(new Date(), DatePattern.NORM_DATE_PATTERN);
            FileUtil.isDirOMkdir(storeDir);
            Path hexPath = Paths.get(storeDir + File.separator + fileName);
            try (BufferedOutputStream fileOut = new BufferedOutputStream(
                    Files.newOutputStream(hexPath))) {
                fileOut.write(bytes, 0, bytes.length);
                fileOut.flush();
                log.info("{} {} File Success Upload Dir --> {}", mobile, fileName, storeDir);
            } catch (IOException e) {
                log.error("{} {} File Fail Upload Dir --> {}", mobile, fileName, e);
            }
        }
        return null;
    }


}

AttachmentEndUploadProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.constant.RedisConst;
import com.bho.jtt808.enums.AttachmentUploadStatusEnum;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentCacheData;
import com.bho.jtt808.protocol.message.other.attachment.AttachmentUploadData;
import com.bho.jtt808.protocol.message.reply.AttachmentEndUploadReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AttachmentEndUploadUplinkMsg;
import com.bho.jtt808.util.Helper;
import com.bho.jtt808.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * 附件结束上传业务处理类
 * @author wanzh
 * @date 2022/8/17
 */
@Slf4j
@Component
public class AttachmentEndUploadProcessor implements IProcessor<AttachmentEndUploadUplinkMsg, AttachmentEndUploadReplyMsg> {

    /**
     * 上传结果 已完成/补传
     */
    private static final short UPLOAD_RESULT_COMPLETE = 0;
    private static final short UPLOAD_RESULT_REISSUE = 1;


    @Override
    public AttachmentEndUploadReplyMsg process(AttachmentEndUploadUplinkMsg uplinkMsg) {
        //获取应答消息
        AttachmentEndUploadReplyMsg replyMsg = AttachmentEndUploadReplyMsg.builder()
                .fileNameLen(uplinkMsg.getFileNameLen()).fileName(uplinkMsg.getFileName())
                .fileType(uplinkMsg.getFileType())
                .build();
        //获取附件缓存数据
        String fileKey = String.format(RedisConst.ATTACHMENT_KEY,
                Helper.removeSymbolsOfFileName(uplinkMsg.getFileName()));
        AttachmentCacheData attachmentCacheData = RedisUtil.get(fileKey);
        if (attachmentCacheData == null) {
            log.error("报警附件消息缓存为空,附件结束上传消息丢弃");
            return replyMsg;
        }
        if (attachmentCacheData.getUploadStatus() == AttachmentUploadStatusEnum.END.getStatus()) {
            //上传完成无需补传
            replyMsg.setFileUploadResult(UPLOAD_RESULT_COMPLETE);
            //删除缓存中的数据
            RedisUtil.del(fileKey);
            return replyMsg;
        }
        //未上传完成则需要补传
        List<AttachmentUploadData> uploadDatas = attachmentCacheData.getUploadDatas();
        int preEndOffset = 0;
        List<AttachmentEndUploadReplyMsg.DataPackInfo> reissueDataPacks = new ArrayList<>();
        for (int i = 0; i < uploadDatas.size(); i++) {
            AttachmentUploadData curData = uploadDatas.get(i);
            if (i == 0 && curData.getStartOffset() > 0) {
                AttachmentEndUploadReplyMsg.DataPackInfo dataPackInfo = AttachmentEndUploadReplyMsg.DataPackInfo.builder()
                        .offset(0).len(curData.getStartOffset())
                        .build();
                reissueDataPacks.add(dataPackInfo);
                preEndOffset = curData.getEndOffset();
                continue;
            }
            if (i == uploadDatas.size() - 1 && curData.getEndOffset() < attachmentCacheData.getFileSize()) {
                AttachmentEndUploadReplyMsg.DataPackInfo dataPackInfo = AttachmentEndUploadReplyMsg.DataPackInfo.builder()
                        .offset(curData.getEndOffset()).len(uplinkMsg.getFileSize() - curData.getEndOffset())
                        .build();
                reissueDataPacks.add(dataPackInfo);
                preEndOffset = curData.getEndOffset();
                continue;
            }
            if (curData.getStartOffset() > preEndOffset) {
                AttachmentEndUploadReplyMsg.DataPackInfo dataPackInfo = AttachmentEndUploadReplyMsg.DataPackInfo.builder()
                        .offset(preEndOffset).len(curData.getStartOffset() - preEndOffset)
                        .build();
                reissueDataPacks.add(dataPackInfo);
            }
        }
        //一条数据也没有则重新开始补传完整附件数据
        if (uploadDatas.size() == 0) {
            AttachmentEndUploadReplyMsg.DataPackInfo dataPackInfo = AttachmentEndUploadReplyMsg.DataPackInfo.builder()
                    .offset(0).len(attachmentCacheData.getFileSize())
                    .build();
            reissueDataPacks.add(dataPackInfo);
        }
        replyMsg.setFileUploadResult(UPLOAD_RESULT_REISSUE);
        replyMsg.setReissueDataPacks(reissueDataPacks);
        replyMsg.setReissueDataPackNum((short) reissueDataPacks.size());
        return replyMsg;
    }
}

AuthorizationProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.constant.RedisConst;
import com.bho.jtt808.enums.GeneralReplyResultTypeEnum;
import com.bho.jtt808.protocol.message.reply.PlatformReplyMsg;
import com.bho.jtt808.protocol.message.uplink.AuthorizationUplinkMsg;
import com.bho.jtt808.util.MsgUtil;
import com.bho.jtt808.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;


/**
 * 终端授权业务处理类
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
@Component
public class AuthorizationProcessor implements IProcessor<AuthorizationUplinkMsg, PlatformReplyMsg> {



    @Override
    public PlatformReplyMsg process(AuthorizationUplinkMsg uplinkMsg) {
        Boolean isExist = null;
        try {
            String authCode = uplinkMsg.getAuthCode();
            isExist = RedisUtil.existKey(String.format(RedisConst.DEVICE_KEY, authCode));
            if (isExist) {
                log.info("{} device authenticated, authCoce:{}", uplinkMsg.getMobile(), authCode);
                if (StringUtils.hasLength(uplinkMsg.getImei())) {
                    MsgUtil.sendMsg("imeiInfoTopic", uplinkMsg);
                    log.info("{} device imei updated, imei:{}", uplinkMsg.getMobile(), uplinkMsg.getImei());
                }
            } else {
                log.error( "{} Failed to authenticate device, authCode:{}", uplinkMsg.getMobile(),  authCode);
            }
        } catch (Exception e) {
            log.error( "{} Failed to authenticate device {}", uplinkMsg.getMobile(),  e);
        }

        return PlatformReplyMsg.builder()
                .result(isExist ? GeneralReplyResultTypeEnum.sucess.getResult() : GeneralReplyResultTypeEnum.fail.getResult())
                .build();
    }



}

LocationBatchProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.LocationBatchUplinkMsg;
import org.springframework.stereotype.Component;

/**
 * 位置批量上报处理类
 * @author wanzh
 * @date 2022/8/19
 */
@Component
public class LocationBatchProcessor implements IProcessor<LocationBatchUplinkMsg, BaseReplyMsg> {

    @Override
    public BaseReplyMsg process(LocationBatchUplinkMsg locationBatchUplinkMsg) {
        return null;
    }

}

LocationProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.LocationBatchUplinkMsg;
import org.springframework.stereotype.Component;

/**
 * 位置批量上报处理类
 * @author wanzh
 * @date 2022/8/19
 */
@Component
public class LocationBatchProcessor implements IProcessor<LocationBatchUplinkMsg, BaseReplyMsg> {

    @Override
    public BaseReplyMsg process(LocationBatchUplinkMsg locationBatchUplinkMsg) {
        return null;
    }

}

StatusProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.protocol.message.reply.BaseReplyMsg;
import com.bho.jtt808.protocol.message.uplink.StatusUplinkMsg;
import org.springframework.stereotype.Component;

/**
 * 车辆状态上报业务处理层
 * @author wanzh
 * @date 2022/8/20
 */
@Component
public class StatusProcessor implements IProcessor<StatusUplinkMsg, BaseReplyMsg> {
    @Override
    public BaseReplyMsg process(StatusUplinkMsg statusUplinkMsg) {
        return null;
    }
}

RegisterProcessor

package com.bho.jtt808.protocol.processor;

import com.bho.jtt808.enums.RegisterReplyResultTypeEnum;
import com.bho.jtt808.protocol.message.reply.RegisterReplyMsg;
import com.bho.jtt808.protocol.message.uplink.RegisterUplinkMsg;
import org.springframework.stereotype.Component;


/**
 * 终端注册业务处理类
 * @author wanzh
 * @date 2022/8/13
 */
@Component
public class RegisterProcessor implements IProcessor<RegisterUplinkMsg, RegisterReplyMsg> {


    @Override
    public RegisterReplyMsg process(RegisterUplinkMsg registerUplinkMsg) {
        //todo 判断车辆、终端是否已经注册,或是否在数据库存在
        return RegisterReplyMsg.builder()
                .result(RegisterReplyResultTypeEnum.sucess.getResult())
                .build();
    }
}

枚举类

AlarmTypeEnum

package com.bho.jtt808.enums;

/**
 * 告警类型枚举类
 * @author wanzh
 * @date 2022/8/16
 */
public enum AlarmTypeEnum {


    ADAS_01("ADAS", (short)1, "前向碰撞报警"),
    ADAS_02("ADAS", (short)2, "车道偏离报警"),
    ADAS_03("ADAS", (short)3, "车距过近报警"),
    ADAS_04("ADAS", (short)4, "行人碰撞报警"),
    ADAS_05("ADAS", (short)5, "频繁变道报警"),
    ADAS_06("ADAS", (short)6, "道路标识超限报警"),
    ADAS_07("ADAS", (short)7, "障碍物报警"),
    ADAS_16("ADAS", (short)16, "道路标志识别事件"),
    ADAS_17("ADAS", (short)17, "主动抓拍事件"),
    ADAS_18("ADAS", (short)18, "实线变道报警"),
    ADAS_19("ADAS", (short)19, "车厢过道行人检测报警"),
    ADAS_XX("ADAS", null, "用户自定义"),

    DSM_01("DSM", (short)1, "疲劳驾驶报警"),
    DSM_02("DSM", (short)2, "接打手持电话报警"),
    DSM_03("DSM", (short)3, "抽烟报警"),
    DSM_04("DSM", (short)4, "不目视前方报警"),
    DSM_05("DSM", (short)5, "驾驶员异常报警"),
    DSM_06("DSM", (short)6, "探头遮挡报警"),
    DSM_08("DSM", (short)8, "超时驾驶报警"),
    DSM_10("DSM", (short)10, "未系安全带报警"),
    DSM_11("DSM", (short)11, "红外阻断型墨镜失效报警"),
    DSM_12("DSM", (short)12, "双脱把报警(双手同时脱离方向盘)"),
    DSM_13("DSM", (short)13, "玩手机报警"),
    DSM_16("DSM", (short)16, "自动抓拍事件"),
    DSM_17("DSM", (short)17, "驾驶员变更事件"),
    DSM_XX("DSM", null, "用户自定义"),
    
    BSD_01("BSD", (short)1, "后方接近报警"),
    BSD_02("BSD", (short)2, "左侧后方接近报警"),
    BSD_03("BSD", (short)3, "右侧后方接近报警"),
    BSD_XX("BSD", null, "用户自定义"),




    ;


    private String alarmCategory;
    private Short alarmType;
    private String alarmName;



    AlarmTypeEnum(String alarmCategory, Short alarmType, String alarmName) {
        this.alarmCategory = alarmCategory;
        this.alarmType = alarmType;
        this.alarmName = alarmName;

    }

    public static AlarmTypeEnum getInstance(String alarmCategory, Short alarmType) {
        AlarmTypeEnum[] values = values();
        for (AlarmTypeEnum alarmTypeEnum : values) {
            if (alarmTypeEnum.getAlarmType() != null
                && alarmTypeEnum.getAlarmType() == alarmType
                && alarmCategory.equals(alarmTypeEnum.alarmCategory)) {
                return alarmTypeEnum;
            }
        }
        return null;
    }

    public Short getAlarmType() {
        return alarmType;
    }

    public String getAlarmCategory() {
        return alarmCategory;
    }

    public String getAlarmName() {
        return alarmName;
    }
}

AttachmentTypeEnum

package com.bho.jtt808.enums;

/**
 * 附件类型枚举类
 * @author wanzh
 * @date 2022/8/17
 */
public enum AttachmentTypeEnum {


    PANORAMA((short) 0, "panorama", "全景图片"),
    MUSIC((short) 1, "music", "音频"),
    VIDEO((short) 2, "video", "视频"),
    TEXT((short) 3, "text", "文本"),
    FACE((short) 4, "face", "面部特征图片"),
    OTHER((short) 5, "other", "其他"),

    ;

    private short type;

    private String key;

    private String desc;

    AttachmentTypeEnum(short type, String key, String desc) {
        this.type = type;
        this.key = key;
        this.desc = desc;

    }

    public static AttachmentTypeEnum getInstanceByType(short type) {
        AttachmentTypeEnum[] values = values();
        for (AttachmentTypeEnum attachmentTypeEnum : values) {
            if (attachmentTypeEnum.getType() == type) {
                return attachmentTypeEnum;
            }
        }
        return OTHER;
    }

    public short getType() {
        return type;
    }

    public String getKey() {
        return key;
    }

    public String getDesc() {
        return desc;
    }
}

AttachmentUploadStatusEnum

package com.bho.jtt808.enums;

/**
 * 附件上传状态
 * @author wanzh
 * @date 2022/8/17
 */
public enum AttachmentUploadStatusEnum {

    PRE(0,"预备上传"),
    BEGIN(1,"开始上传"),
    UPLOAD(2,"上传中"),
    END(3,"结束上传"),

    ;

    private int status;

    private String desc;

    AttachmentUploadStatusEnum(int status, String desc) {
        this.status = status;
        this.desc = desc;
    }

    public int getStatus() {
        return status;
    }

    public String getDesc() {
        return desc;
    }
}

GeneralReplyResultTypeEnum

package com.bho.jtt808.enums;

/**
 * 通用应答结果类型枚举类
 * @author wanzh
 * @date 2022/8/15
 */
public enum GeneralReplyResultTypeEnum {

    sucess(0, "成功/确认"),
    fail(1, "失败"),
    error(2, "消息有误"),
    unsupported(3, "不支持"),
    alarm_confirm(4, "报警处理确认"),
    ;

    private int result;

    private String desc;

    GeneralReplyResultTypeEnum(int result, String desc) {
        this.result = result;
        this.desc = desc;
    }

    public int getResult() {
        return result;
    }


    public String getDesc() {
        return desc;
    }
}

LocationExtraEnum

package com.bho.jtt808.enums;


public enum LocationExtraEnum {

    MILEAGE(1, "mileage", "里程", "mileageExtraDecoder", LocationExtraTypeEnum.property),
    OIL_NUM(2, "oil_num", "油量", "oilNumExtraDecoder", LocationExtraTypeEnum.property),
    INVERSION_SENSOR(251, "inversion_sensor", "正反转", "inversionSensorExtraDecoder", LocationExtraTypeEnum.property),


    ADAS(100, "adas", "高级驾驶辅助系统报警信息", "adasAlarmExtraDecoder", LocationExtraTypeEnum.extra_alarm),
    DSM(101, "dsm", "驾驶员状态监测系统报警信息", "dsmAlarmExtraDecoder", LocationExtraTypeEnum.extra_alarm),
    BSD(103, "bsd", "盲区监测系统报警信息", "bsdAlarmExtraDecoder", LocationExtraTypeEnum.extra_alarm),



    ;


    private int extId;
    private String code;
    private String desc;

    private String decoderId;

    private LocationExtraTypeEnum extraType;



    LocationExtraEnum(int extId, String code, String desc, String decoderId, LocationExtraTypeEnum extraType) {
        this.extId = extId;
        this.code = code;
        this.desc = desc;
        this.decoderId = decoderId;
        this.extraType = extraType;
    }

    public static LocationExtraEnum getInstance(int extId) {
        LocationExtraEnum[] values = values();
        for (LocationExtraEnum extraEnum : values) {
            if(extId == extraEnum.getExtId()) {
                return extraEnum;
            }
        }
        return null;
    }


    public int getExtId() {
        return this.extId;
    }

    public String getCode() {
        return this.code;
    }

    public String getDesc() {
        return this.desc;
    }

    public String getDecoderId() {
        return decoderId;
    }

    public LocationExtraTypeEnum getExtraType() {
        return extraType;
    }
}

LocationExtraTypeEnum

package com.bho.jtt808.enums;

/**
 * @author wanzh
 * @date 2022/8/16
 */
public enum LocationExtraTypeEnum {


    /**
     * 常规告警
     */
    generate_alarm,

    /**
     * 扩展告警
     */
    extra_alarm,

    /**
     * 指标/属性
     */

    property,


    ;


}

MsgTypeEnum

package com.bho.jtt808.enums;

/**
 * 消息类型枚举类
 * @author wanzh
 * @date 2022/8/13
 */
public enum MsgTypeEnum {

    terminal_reply((short) 0x0001, "终端通用应答", null, null, null),
    heartbeat((short) 0x0002, "终端心跳", "heartbeatDecoder", null, null),
    logout((short) 0x0003, "终端注销", "logoutDecoder", null,null),
    register((short) 0x0100, "终端注册", "registerDecoder", null, "registerProcessor"),
    authorization((short) 0x0102, "终端鉴权", "authorizationDecoder", null, "authorizationProcessor"),
    query_server_time((short) 0x0004, "查询服务器时间", "queryServerTimeDecoder", null, null),


    query_server_time_reply((short) 0x8004, "查询服务器时间应答", null, null, null),
    platform_reply((short) 0x8001, "平台通用应答", null, null, null),
    register_reply((short) 0x8100, "终端注册应答", null, null, null),
    attachment_uplod_reply((short) 0x9208, "附件上传应答", null, null, null),
    attachment_end_upload_reply((short) 0x9212, "附件上传完毕应答", null, null,null),



    status((short) 0x0f01, "车辆状态", "statusDecoder", null, "statusProcessor"),
    location((short) 0x0200, "位置信息", "locationDecoder", null, "locationProcessor"),
    location_batch((short) 0x0704, "定位数据批量上传", "locationBatchDecoder", null, "locationBatchProcessor"),

    alarm_attachment((short) 0x1210, "报警附件信息", "alarmAttachmentDecoder", null,"alarmAttachmentProcessor"),
    attachment_begin_upload((short) 0x1211, "附件上传开始消息", "attachmentBeginUploadDecoder", null,"attachmentBeginUploadProcessor"),
    attachment_end_upload((short) 0x1212, "附件上传完毕消息", "attachmentEndUploadDecoder", null,"attachmentEndUploadProcessor"),
    /**
     * 附件数据上传是单独的格式以0x30 0x31 0x63 0x64开头 这里默认为0x0000
     */
    attachment_data_upload((short) 0x0000, "附件数据上传", "attachmentDataUploadDecoder", null,"attachmentDataUploadProcessor"),

    ;

    private short msgId;

    private String name;

    private String decoderBeanId;

    private String ecoderBeanId;

    private String processorBeanId;

    MsgTypeEnum(short msgId, String name, String decoderBeanId, String ecoderBeanId, String processorBeanId) {
        this.msgId = msgId;
        this.name = name;
        this.decoderBeanId = decoderBeanId;
        this.ecoderBeanId = ecoderBeanId;
        this.processorBeanId = processorBeanId;
    }

    public static MsgTypeEnum getInstanceByMsgId(short msgId) {
        MsgTypeEnum[] values = values();
        for (MsgTypeEnum msgTypeEnum : values) {
            if (msgId == msgTypeEnum.getMsgId()) {
                return msgTypeEnum;
            }
        }
        return null;
    }

    public short getMsgId() {
        return msgId;
    }

    public String getName() {
        return name;
    }

    public String getDecoderBeanId() {
        return decoderBeanId;
    }

    public String getEcoderBeanId() {
        return ecoderBeanId;
    }

    public String getProcessorBeanId() {
        return processorBeanId;
    }
}

RegisterReplyResultTypeEnum

package com.bho.jtt808.enums;

/**
 * 通用应答结果类型枚举类
 * @author wanzh
 * @date 2022/8/15
 */
public enum RegisterReplyResultTypeEnum {

    sucess(0, "成功"),
    car_registered(1, "车辆已被注册"),
    unkown_car(2, "数据库中无该车辆"),
    terminal_registered(3, "终端已被注册"),
    unkown_terminal(4, "数据库中无该终端"),
    ;

    private int result;

    private String desc;

    RegisterReplyResultTypeEnum(int result, String desc) {
        this.result = result;
        this.desc = desc;
    }

    public int getResult() {
        return result;
    }


    public String getDesc() {
        return desc;
    }
}

MsgConst

package com.bho.jtt808.constant;

/**
 * JTT808消息常量
 * 消息ID声明,
 */
public class MsgConst {
    public static final byte MSG_SIGN = 0x7e;
    public static final byte MSG_ESCAPE_CHAR = 0x7d;

    public static final int MSG_MOBILE_BYTES_OFFSET = 10;
    public static final int MSG_MOBILE_BYTES_LENGTH = 6;

    public static final boolean MSG_ENCRYPTED_DEFAULT = false;
    public static final boolean MSG_SEPARATE_PACKAGE_DEFAULT = false;

    public static final boolean MSG_PROTOCOL_REVISED_DEFAULT = true;
    public static final short MSG_BARE_LITE_HEADER_LENGTH_2019 = 17;
    public static final short MSG_BARE_LITE_HEADER_LENGTH_2013 = 12;
    public static final short MSG_BARE_LITE_HEADER_LENGTH = 17;
    public static final short MSG_V2019_SHORTEST_LENGTH = 20;
    public static final short MSG_V2011_SHORTEST_LENGTH = 15;

    public static final short MSG_BARE_FULL_HEADER_LENGTH = 21;
    public static final short MSG_BARE_FULL_HEADER_LENGTH_2013 = 16;
    public static final short MSG_ACQ_TIME_LENGTH = 12;

    public static final byte PROTOCOL_VERSION_DEFAULT = 1;

    public static final float DECIMAL_MILLION =  1000000f;

    public static final float DECIMAL_TEN = 10f;



    /**
     * 报警附件消息头
     */
    public static final byte ATTACHMENT_BIT_STREAM_FIRST_BYTE = 0x30;
    public static final byte ATTACHMENT_BIT_STREAM_SECOND_BYTE = 0x31;
    public static final byte ATTACHMENT_BIT_STREAM_THIRD_BYTE = 0x63;
    public static final byte ATTACHMENT_BIT_STREAM_FOURTH_BYTE = 0x64;

    /**
     * 位置信息汇报
     */
    public static final short LOCATION_REPORTING_BASIC_LENGTH = 28;


    public static final int PROTOCOL_VERSION_2019 = 2019;
    public static final int PROTOCOL_VERSION_2013 = 2013;


    public static final String CHANNEL_MSG_TYPE = "channelMsgType";

    /**
     * 设备刚接入 消息类型初始化状态
     */
    public static final int MSG_TYPE_INIT = 0;

    /**
     * 非附件上传相关消息
     */
    public static final int MSG_TYPE_NORAML = 1;

    /**
     * 附件上传相关消息
     */
    public static final int MSG_TYPE_FILE = 2;








}

工具类

MsgHelper

消息解析

package com.bho.jtt808.util;

import com.bho.jtt808.constant.MsgConst;
import com.bho.jtt808.enums.MsgTypeEnum;
import com.bho.jtt808.protocol.message.*;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;

/**
 * @author wanzh
 * @date 2022/8/13
 */
@Slf4j
public class MsgHelper {

    public static byte[] msgStructureToBytes(MsgStructure msgStructure) {
        MsgHeader msgHeader = msgStructure.getMsgHeader();
        byte[] headBytes = msgHeaderToBytes(msgHeader);
        byte[] bodyBytes = msgStructure.getMsgBody().getBytes();
        byte[] bytes = new byte[headBytes.length + bodyBytes.length + 1];
        System.arraycopy(headBytes, 0, bytes, 0, headBytes.length);
        System.arraycopy(bodyBytes, 0, bytes, headBytes.length, bodyBytes.length);
        byte xorCode = checkCodeWithoutLast(bytes);
        bytes[bytes.length - 1] = xorCode;
        bytes = escapeContent(bytes);
        byte[] allBytes = new byte[bytes.length + 2];
        allBytes[0] = MsgConst.MSG_SIGN;
        System.arraycopy(bytes, 0, allBytes, 1, bytes.length);
        allBytes[allBytes.length - 1] = MsgConst.MSG_SIGN;
        return allBytes;
    }

    private static byte[] msgHeaderToBytes(MsgHeader msgHeader) {
        byte[] msgIdBytes = Binary.shortToBEDBBytes(msgHeader.getMsgId());
        byte[] msgSNBytes = Binary.shortToBEDBBytes(msgHeader.getMsgSN());
        byte[] msgHeaderBytes = null;
        MsgProperty msgProp = msgHeader.getMsgPropInfo();
        if (msgHeader.getProtocolVersion() == MsgConst.PROTOCOL_VERSION_2019) {
            byte[] mobileBytes = BCDUtil.nbcdOfString(msgHeader.getMobile(), BCDUtil.BCD_MOBILE_LENGTH);
            short msgPropVal2019 = (short) ((msgProp.isNewProtocol() ? 1 << 13 : 0)
                    | (msgProp.isPacketed() ? 1 << 12 : 0)
                    | (msgProp.isEncrypted() ? 1 << 9 : 0)
                    | (msgProp.getMsgLength() & 0x1ff));
            byte[] msgProBytes = Binary.shortToBEDBBytes(msgPropVal2019);
            if (msgProp.isPacketed()) {
                msgHeaderBytes = new byte[MsgConst.MSG_BARE_FULL_HEADER_LENGTH];
            } else {
                msgHeaderBytes = new byte[MsgConst.MSG_BARE_LITE_HEADER_LENGTH];
            }
            System.arraycopy(msgIdBytes, 0, msgHeaderBytes, 0, 2);
            System.arraycopy(msgProBytes, 0, msgHeaderBytes, 2, 2);
            msgHeaderBytes[4] = MsgConst.PROTOCOL_VERSION_DEFAULT;
            System.arraycopy(mobileBytes, 0, msgHeaderBytes, 5, 10);
            System.arraycopy(msgSNBytes, 0, msgHeaderBytes, 15, 2);
            if (msgProp.isPacketed()) {
                HeaderPackOption hPack = msgHeader.getHPack();
                byte[] packBytes = Binary.intToBEQuadBytes((hPack.getPackOrder() << 16) | hPack.getPackAmount());
                System.arraycopy(packBytes, 0, msgHeaderBytes, MsgConst.MSG_BARE_LITE_HEADER_LENGTH,4);
            }
            return msgHeaderBytes;
        }
        byte[] mobileBytes = BCDUtil.nbcdOfString(msgHeader.getMobile(), BCDUtil.BCD_MOBILE_LENGTH_2013);
        short msgPropVal2013 = (short) ( (msgProp.isPacketed() ? 1 << 12 : 0) | (msgProp.isEncrypted() ? 1 << 9 : 0) | (msgProp.getMsgLength() & 0x1ff));
        byte[] msgProBytes = Binary.shortToBEDBBytes(msgPropVal2013);
        msgHeaderBytes = new byte[MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2013];
        System.arraycopy(msgIdBytes, 0, msgHeaderBytes, 0, 2);
        System.arraycopy(msgProBytes, 0, msgHeaderBytes, 2, 2);
        System.arraycopy(mobileBytes, 0, msgHeaderBytes, 4, 6);
        System.arraycopy(msgSNBytes, 0, msgHeaderBytes, 10, 2);
        if (msgProp.isPacketed()) {
            HeaderPackOption hPack = msgHeader.getHPack();
            byte[] packBytes = Binary.intToBEQuadBytes((hPack.getPackOrder() << 16) | hPack.getPackAmount());
            System.arraycopy(packBytes, 0, msgHeaderBytes, MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2013, 4);
        }
        return msgHeaderBytes;
    }


    public static MsgStructure bytesToMsgStructure(byte[] rawBytes){
        byte[] bareBytes = unescapeAndUncovered(rawBytes);
        byte xorCode = checkCodeWithoutLast(bareBytes);
        if (xorCode != bareBytes[bareBytes.length - 1]) {
            log.error("校验码不匹配,理论值:{},实际值:{}", bareBytes[bareBytes.length - 1], xorCode);
            return null;
        }
        int bodyOffset = 0;
        int protocolVersion = 0;
        if (isV2019ByRawNew(rawBytes)) {
            bodyOffset = MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2019;
            protocolVersion = 2019;
        } else {
            bodyOffset = MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2013;
            protocolVersion = 2013;
        }
        byte[] headerBytes = Arrays.copyOfRange(bareBytes, 0, bodyOffset);
        MsgHeader header = getMsgHeader(headerBytes, protocolVersion);
        header.setProtocolVersion(protocolVersion);
        if (header.getMsgPropInfo().isPacketed()) {
            HeaderPackOption hPack = getHeaderPackOption(Arrays.copyOfRange(bareBytes,
                    bodyOffset, MsgConst.MSG_BARE_FULL_HEADER_LENGTH));
            header.setHPack(hPack);
            bodyOffset = MsgConst.MSG_BARE_FULL_HEADER_LENGTH;
        }
        byte[] bodyBytes = null;
        if (header.getMsgPropInfo().getMsgLength() != 0) {
            bodyBytes = Arrays.copyOfRange(bareBytes, bodyOffset, bareBytes.length - 1);
        }
        MsgStructure msgStructure = MsgStructure.builder()
                .msgHeader(header).msgBody(MsgBody.builder().bytes(bodyBytes).build())
                .build();
        return msgStructure;
    }

    public static MsgHeader getMsgHeader(byte[] bytes, int proVersion) {
        if (proVersion == MsgConst.PROTOCOL_VERSION_2019) {
            Short msgId = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 0, 2));
            MsgProperty msgPropInfo = getMsgProperty(Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 2, 4)));
            byte version = bytes[4];
            String mobile = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 5, 15));
            Short msgSN = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 15, 17));
            HeaderPackOption hPack = null;
            if (msgPropInfo.isPacketed() && bytes.length == MsgConst.MSG_BARE_FULL_HEADER_LENGTH) {
                hPack = getHeaderPackOption(Arrays.copyOfRange(bytes,
                        MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2019, MsgConst.MSG_BARE_FULL_HEADER_LENGTH));
            }
            MsgHeader msgHeader = MsgHeader.builder()
                    .msgId(msgId).msgPropInfo(msgPropInfo).version(version)
                    .mobile(mobile).msgSN(msgSN).hPack(hPack)
                    .build();
            return msgHeader;
        }
        Short msgId = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 0, 2));
        MsgProperty msgPropInfo = getMsgProperty(Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 2, 4)));
        byte version = bytes[4];
        String mobile = BCDUtil.nzdStringOfNBCD(Arrays.copyOfRange(bytes, 4, 10));
        Short msgSN = Binary.beDBBytesToShort(Arrays.copyOfRange(bytes, 10, 12));
        HeaderPackOption hPack = null;
        if (msgPropInfo.isPacketed() && bytes.length == MsgConst.MSG_BARE_FULL_HEADER_LENGTH_2013) {
            hPack = getHeaderPackOption(Arrays.copyOfRange(bytes,
                    MsgConst.MSG_BARE_LITE_HEADER_LENGTH_2013, MsgConst.MSG_BARE_FULL_HEADER_LENGTH_2013));
        }
        MsgHeader msgHeader = MsgHeader.builder()
                .msgId(msgId).msgPropInfo(msgPropInfo).version(version)
                .mobile(mobile).msgSN(msgSN).hPack(hPack)
                .build();
        return msgHeader;
    }

    public static MsgProperty getMsgProperty(short mProp) {
        MsgProperty msgProperty = MsgProperty.builder()
                .msgLength(Binary.valueOfBEBits(mProp, 10))
                .encrypted(Binary.boolOfBEBit(mProp, 10))
                .packeted(Binary.boolOfBEBit(mProp, 13))
                .newProtocol(Binary.boolOfBEBit(mProp, 14))
                .reservedBit(Binary.boolOfBEBit(mProp, 15))
                .build();
        return msgProperty;
    }

    public static HeaderPackOption getHeaderPackOption(byte[] packBytes){
        if (packBytes == null || packBytes.length != 4) {
            return null;
        }
        HeaderPackOption headerPackOption = HeaderPackOption.builder()
                .packAmount(Binary.beDBBytesToShort(Arrays.copyOfRange(packBytes, 0, 2)))
                .packOrder(Binary.beDBBytesToShort(Arrays.copyOfRange(packBytes, 2, 4)))
                .build();
        return headerPackOption;

    }



    /**
     * 是否是2019版本协议
     * @param rawBytes
     * @return
     */
    public static boolean isV2019ByRawNew(byte[] rawBytes) {
        if (rawBytes == null || rawBytes.length < MsgConst.MSG_V2019_SHORTEST_LENGTH) {
            return false;
        }

        byte[] msgProp = Arrays.copyOfRange(rawBytes, 3, 5);
        int versionBit = msgProp[0] & 0x40;
        return versionBit == 0x40;
    }


    /**
     * 异或校验码
     * @param bareBytes
     * @return
     */
    private static byte checkCodeWithoutLast(byte[] bareBytes) {
        if (bareBytes == null || bareBytes.length == 0) {
            return (byte) 0;
        }
        byte code = bareBytes[0];
        for (int i = 1; i < bareBytes.length - 1; i++) {
            code ^= bareBytes[i];
        }
        return code;
    }

    /**
     * escape step:
     * 1). 0x7d --> 0x7d 0x01
     * 2). 0x7e --> 0x7d 0x02
     *
     * @param initBytes
     * @return
     */
    public static byte[] escapeContent(byte[] initBytes){
        if (initBytes == null || initBytes.length == 0) {
            return new byte[0];
        }

        int tobeEscNumber = 0;
        // count to be escaped char
        for (int i = 0; i < initBytes.length ; i++) {
            if (initBytes[i] == MsgConst.MSG_ESCAPE_CHAR
                    || initBytes[i] == MsgConst.MSG_SIGN) {
                tobeEscNumber++;
            }
        }
        int escapedCount = tobeEscNumber + initBytes.length;
        byte[] escBytes = new byte[escapedCount];
        for (int i = 0, j = 0; i < initBytes.length; i++, j++) {
            if ( initBytes[i] == MsgConst.MSG_ESCAPE_CHAR ) {
                escBytes[j++] = initBytes[i];
                escBytes[j] = 0x01;
            } else if( initBytes[i] == MsgConst.MSG_SIGN ){
                escBytes[j++] = MsgConst.MSG_ESCAPE_CHAR;
                escBytes[j] = 0x02;
            } else {
                escBytes[j] = initBytes[i];
            }
        }

        return escBytes;
    }

    /**
     * unescape step:
     * 1). 0x7d 0x02 -> 0x7e
     * 2). 0x7d 0x01 -> 0x7d
     * uncovered step:
     * remove first MSG_SIGN, last MSG_SIGN and the check code
     * @param rawBytes
     * @return
     */
    private static byte[] unescapeAndUncovered(byte[] rawBytes) {
        if (rawBytes == null || rawBytes.length == 0) {
            return null;
        }
        byte[] midBytes = new byte[rawBytes.length];
        int escapeCount = 2;
        for (int i = 1, j = 0; i < rawBytes.length - 1; i++, j++) {
            if (rawBytes[i] == MsgConst.MSG_ESCAPE_CHAR) {
                if (rawBytes[i + 1] == 0x02) {
                    midBytes[j] = MsgConst.MSG_SIGN;
                    i++;
                    escapeCount++;
                } else if (rawBytes[i + 1] == 0x01) {
                    i++;
                    midBytes[j] = MsgConst.MSG_ESCAPE_CHAR;
                    escapeCount++;
                }
            } else {
                midBytes[j] = rawBytes[i];
            }
        }
        return Arrays.copyOfRange(midBytes, 0, rawBytes.length - escapeCount);
    }

    /**
     * 获取消息回复内容
     * @param msgHeader 上报消息头
     * @param bodyBytes 回复消息体
     * @param msgTypeEnum 回复消息类型
     * @return
     */
    public static MsgStructure getReplyMsgStructure(MsgHeader msgHeader, byte[] bodyBytes, MsgTypeEnum msgTypeEnum) {
        MsgProperty msgProperty = MsgProperty.builder()
                .msgLength(bodyBytes.length)
                .newProtocol(msgHeader.getMsgPropInfo().isNewProtocol())
                .build();
        MsgHeader replyMsgHeader = MsgHeader.builder()
                .msgId(msgTypeEnum.getMsgId()).msgSN(msgHeader.getMsgSN())
                .mobile(msgHeader.getMobile()).msgPropInfo(msgProperty)
                .version(msgHeader.getVersion()).protocolVersion(msgHeader.getProtocolVersion())
                .build();
        MsgBody replyMsgBody = MsgBody.builder().bytes(bodyBytes).build();
        MsgStructure replyMsgStructure = MsgStructure.builder()
                .msgHeader(replyMsgHeader).msgBody(replyMsgBody)
                .build();
        return replyMsgStructure;
    }

    /**
     * 获取消息体
     * @param msgHeader 上报消息头
     * @param result 结果
     * @return
     */
    public static byte[] getGeneralReplyMsgBody(MsgHeader msgHeader, int result) {
        byte[] bodyBytes = new byte[5];
        byte[] msgSNBytes = Binary.shortToBEDBBytes(msgHeader.getMsgSN());
        byte[] msgIdBytes = Binary.shortToBEDBBytes(msgHeader.getMsgId());
        System.arraycopy(msgSNBytes, 0, bodyBytes, 0, 2);
        System.arraycopy(msgIdBytes, 0, bodyBytes, 2, 2);
        bodyBytes[4] = (byte) result;
        return bodyBytes;
    }





}

实体类

MsgStructure

package com.bho.jtt808.protocol.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 消息结构
 * @author wanzh
 * @date 2022/8/13
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MsgStructure {

    /**
     * 消息头
     */
    private MsgHeader msgHeader;

    /**
     * 消息体
     */
    private MsgBody msgBody;



}

MsgHeader

package com.bho.jtt808.protocol.message;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 消息头
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MsgHeader {
    /**
     * 消息ID
     */
    private short msgId;

    /**
     * 消息属性
     */
    private MsgProperty msgPropInfo;

    /**
     * 协议版本 ,每次关键修订递增 ,初始版本为 1
     */
    private byte version;

    /**
     * 协议版本号 2019/2013
     */
    private int protocolVersion;

    /**
     * 终端手机号
     */
    private String mobile;

    /**
     * 消息流水号 按发送顺序从0 开始循环累加
     */
    private short msgSN;

    /**
     * 消息包封装项
     */
    private HeaderPackOption hPack;


}

MsgProperty

package com.bho.jtt808.protocol.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 消息体属性双字节
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MsgProperty {

    private int msgLength;

    private boolean encrypted;

    private boolean packeted;

    private boolean newProtocol;

    private boolean reservedBit;



}

HeaderPackOption

package com.bho.jtt808.protocol.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class HeaderPackOption {
    /**
     *消息总包数
     */
    private short packAmount;
    /**
     * 包序号
     */
    private short packOrder;
}

MsgBody

package com.bho.jtt808.protocol.message;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 消息体数据
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MsgBody {

    private byte[] bytes;
}

BaseUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.Data;


/**
 * 上行消息基础类
 * @author wanzh
 * @date 2022/8/15
 */
@Data
public class BaseUplinkMsg {

    private String mobile;

}

LocationUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import com.bho.jtt808.protocol.message.other.location.LocationData;
import com.bho.jtt808.protocol.message.other.location.ReportStatus;
import com.bho.jtt808.protocol.message.other.location.extra.BaseLocationExtraData;
import com.bho.jtt808.protocol.message.other.location.extra.alarm.ExtendAlarm;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 位置上报消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LocationUplinkMsg extends BaseUplinkMsg{


    /**
     * 基本信息
     */
    private LocationData locationData;

    /**
     * 状态数据
     */
    private ReportStatus reportStatus;

    /**
     * 扩展信息
     */
    private List<BaseLocationExtraData> locationExtraDatas;


    /**
     * 扩展告警
     */
    private ExtendAlarm extendAlarm;



}

LocationBatchUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 位置批量上报消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LocationBatchUplinkMsg extends BaseUplinkMsg{


    /**
     * 数据项个数
     */
    private short dataNum;

    /**
     * 0:正常位置批量汇报 ,1:盲区补报
     */
    private short missedTag;



    /**
     * 位置信息列表
     */
    List<LocationUplinkMsg> locations;



}

RegisterUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 注册上行消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RegisterUplinkMsg extends BaseUplinkMsg {
    /**
     * 省域ID
     */
    private short provinceId;
    /**
     * 市县域ID
     */
    private short cityId;
    /**
     * 制造商ID
     */
    private String manufacturerId;
    /**
     * 终端型号
     */
    private String termType;
    /**
     * 终端ID
     */
    private String termId;
    /**
     * 车牌颜色
     */
    private byte vehicleColor;
    /**
     * 车牌号码
     */
    private String plateNumber;
}

StatusUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.Date;

/**
 * 车辆状态上报消息
 * @author wanzh
 * @date 2022/8/20
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class StatusUplinkMsg extends BaseUplinkMsg {

    /**
     * 经度
     */
    private BigDecimal longitude;
    /**
     * 纬度
     */
    private BigDecimal latitude;

    /**
     * 高度, 单位为米
     */
    private Short height;
    /**
     * gps速度(KM/H)
     */
    private BigDecimal gpsSpeed;

    /**
     * 方向, 0-359. 正北为0, 顺时针增长
     */
    private Short direction;

    /**
     * 数据采集时间
     */
    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
    private Date acqTime;

    /**
     * 车牌号
     */
    private String carNo;

    /**
     * 司机
     */
    private String driverId;



    /**
     * 扫盘状态 0: 无效;1 停转;2 工作中
     */
    private Short sweepStatus;

    /**
     * 车厢状态 1:关闭;2:打开; 10: 故障
     */
    private Short carriageStatus;

    /**
     * 举升状态 1:平放;2:丼升;3:完全丼升; 10: 故障
     */
    private Short liftState;

    /**
     * 空重状态 1:空车;2:半载; 3: 满载;4:超载; 10: 故障
     */
    private Short emptyWeightStatus;


    /**
     * 违规状态 1:违规;0:未违规
     */
    private Short violationStatus;
    /**
     * 车辆载重 单位为 KG
     */

    private BigDecimal loadSensor;
    /**
     * 正反转
     */
    private Short inversionSensor;


}

AuthorizationUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 鉴权上行消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthorizationUplinkMsg extends BaseUplinkMsg {
    /**
     * 授权码长度
     */
    private int authCodeLen;
    /**
     * 授权码
     */
    private String authCode;

    /**
     * 终端imei
     */
    private String imei;

    /**
     * 软件版本号
     */
    private String softwareVersion;


}

AlarmAttachmentUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import com.bho.jtt808.protocol.message.other.attachment.AttachmentDescInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;
import java.util.List;

/**
 * 报警附件上传消息
 * @author wanzh
 * @date 2022/8/17
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlarmAttachmentUplinkMsg extends BaseUplinkMsg{

    /**
     * 主键
     */
    private String id;

    /**
     * 终端 ID BYTE[30] 30 个字节,由大写字母和数字组成
     */
    private String terminalId;

    /**
     * 告警唯一编号
     */
    private String alarmGuid;

    /**
     * 消息类型
     */
    private Short messageType;

    /**
     * 附件数量 BYTE 表示该报警对应的附件数量
     */
    private Short attachmentNumber;

    /**
     * 附件信息列表
     */
    private List<AttachmentDescInfo> attachmentInfoList;

    /**
     * 报警标识终端 ID BYTE[30] 30 个字节,由大写字母和数字组成
     */
    private String tagTerminalId;
    /**
     * 报警标识时间 BCD[6] YY-MM-DD-hh-mm-ss(GMT+8 时间)
     */
    private Date tagAcqTime;
    /**
     * 报警标识序号 BYTE 同一时间点报警的序号,从 0 循环累加
     */
    private Short tagAlertSN;

    /**
     * 报警标识附件数量 BYTE 表示该报警对应的附件数量
     */
    private Short tagAttachmentNumber;

    /**
     * 告警标识保留字段
     */
    private String tagRetainCol;


    /**
     * 上传时间
     */
    private Date uploadTime;

}

AttachmentBeginUploadUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 附件开始上传消息
 * @author wanzh
 * @date 2022/8/17
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttachmentBeginUploadUplinkMsg extends BaseUplinkMsg{

    /**
     * 文件名称长度
     */
    private short fileNameLen;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 文件类型
     * 0x00:全景图片
     * 0x01:音频
     * 0x02:视频
     * 0x03:文本
     * 0x04:面部特征图片
     * 0x05:其它
     */
    private short fileType;

    /**
     * 文件大小
     */
    private int fileSize;

}

AttachmentDataUploadUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 附件数据上传消息
 * @author wanzh
 * @date 2022/8/17
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttachmentDataUploadUplinkMsg extends BaseUplinkMsg{


    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 数据偏移量
     */
    private int dataOffset;


    /**
     * 数据长度
     */
    private int dataLen;

    /**
     * 数据体
     */
    private byte[] dataBody;
}

AttachmentEndUploadUplinkMsg

package com.bho.jtt808.protocol.message.uplink;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 附件结束上传消息
 * @author wanzh
 * @date 2022/8/17
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttachmentEndUploadUplinkMsg extends BaseUplinkMsg{

    /**
     * 文件名称长度
     */
    private short fileNameLen;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 文件类型
     * 0x00:全景图片
     * 0x01:音频
     * 0x02:视频
     * 0x03:文本
     * 0x04:面部特征图片
     * 0x05:其它
     */
    private short fileType;

    /**
     * 文件大小
     */
    private int fileSize;

}

BaseReplyMsg

package com.bho.jtt808.protocol.message.reply;

import lombok.Data;

/**
 * 回复消息基础类
 * @author wanzh
 * @date 2022/8/15
 */
@Data
public class BaseReplyMsg {

}

LocationReplyMsg

package com.bho.jtt808.protocol.message.reply;

import com.bho.jtt808.protocol.message.other.location.extra.alarm.AlarmTag;
import lombok.Data;

/**
 * 位置上报回复消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
public class LocationReplyMsg extends BaseReplyMsg {
    /**
     * 消息ID
     */
    private short msgId;

    /**
     * 回复值
     */
    private int replyVal;

    /**
     *附件上传ip
     */
    private String ip;

    /**
     * 附件上传TCP端口
     */
    private short tcpPort;

    /**
     * 附件上传UDP端口
     */
    private short udpPort;

    /**
     * 平台给报警分配的唯一编号
     */
    private String alarmGuid;

    /**
     * 报警标识
     */
    private AlarmTag aTag;

}

PlatformReplyMsg

package com.bho.jtt808.protocol.message.reply;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 平台通用回复消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PlatformReplyMsg extends BaseReplyMsg {
    /**
     * 应答流水号-对应的终端消息的流水号
     */
    private short msgSn;
    /**
     * 应答ID-对应的终端消息的 ID
     */
    private short msgId;

    /**
     * 结果-0:成功/确认;1:失败;2:消息有误;3:不支持
     */
    private int result;

}

RegisterReplyMsg

package com.bho.jtt808.protocol.message.reply;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 注册应答消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class RegisterReplyMsg extends BaseReplyMsg {
    /**
     * 应答流水号-对应的终端注册消息的流水
     */
    private short msgSn;

    /**
     * 结果-0:成功;1:车辆已被注册;2:数据库中无该车 辆;3:终端已被注册;4:数据库中无该终端
     */
    private int result;

    /**
     * 鉴权码-注册结果为成功时 ,才有该字段
     */
    private String authCode;


}

AttachmentEndUploadReplyMsg

package com.bho.jtt808.protocol.message.reply;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 文件上传完成回复消息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttachmentEndUploadReplyMsg extends BaseReplyMsg {
    /**
     * 文件名称长度
     */
    private short fileNameLen;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 文件类型
     * 0x00:全景图片
     * 0x01:音频
     * 0x02:视频
     * 0x03:文本
     * 0x04:面部特征图片
     * 0x05:其它
     */
    private short fileType;

    /**
     * 0x00:完成
     * 0x01:需要补传
     */
    private short fileUploadResult;

    /**
     * 补发数据包数量
     */
    private short reissueDataPackNum;

    /**
     * 数据包列表
     */
    private List<DataPackInfo> reissueDataPacks;

    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class DataPackInfo {

        /**
         * 数据偏移量
         */
        private int offset;

        /**
         * 数据长度
         */
        private int len;
    }

}

LocationData

package com.bho.jtt808.protocol.message.other.location;

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;

/**
 * 位置基本信息
 * @author wanzh
 * @date 2022/8/15
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LocationData {


    /**
     * 终端手机号
     */
    private String mobile;

    /**
     * 报警标志
     */
    private Integer alertFlag;
    /**
     * 状态代码
     */
    private Integer statusFlag;

    /**
     * 0:正常位置批量汇报 ,1:盲区补报
     */
    private Short missedTag;

    /**
     * 经度
     */
    private BigDecimal longitude;
    /**
     * 维度
     */
    private BigDecimal latitude;

    /**
     * 高度, 单位为米
     */
    private Short height;
    /**
     * gps速度(KM/H)
     */
    private BigDecimal gpsSpeed;

    /**
     * 方向, 0-359. 正北为0, 顺时针增长
     */
    private Short direction;

    /**
     * 数据采集时间
     */
    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
    private Date acqTime;

    /**
     * 数据上报时间
     */
    private Date uploadTime;

    /**
     * 里程
     */
    private BigDecimal mileage;

    /**
     * 油量(L)
     */
    private BigDecimal oilNum;



    /**
     * 用水量-水位值(cm)
     */
    private Long waterStage;

    /**
     * 油量-油位值(cm)
     */
    private Long oilStage;

    /**
     * 水位传感器(L)
     */
    private BigDecimal waterSensor;

    /**
     * 油耗
     */
    private BigDecimal fuelConsumption;

    /**
     * 水位模拟量
     */
    private Long waterAdc;



    /**
     * 液位传感器数据
     */
    private BigDecimal liquid;

    /**
     * 载重传感器(L)
     */
    private BigDecimal loadSensor;


    /**
     * 正反转 0:正转 1:反转 2:停转
     */
    private Integer inversionSensor;

    /**
     * 转速(转/分钟)
     */
    private Integer inversionSpeed;



    /**
     * 湿度传感器数据
     */
    private BigDecimal humidity;

    /**
     * 温度传感器数据
     */
    private BigDecimal temperature;

    /**
     * 举升传感器 0:举起 1:未举起
     */
    private Integer liftSensor;





}


BaseLocationExtraData

package com.bho.jtt808.protocol.message.other.location.extra;

import lombok.Data;


/**
 * 位置附加信息基础类
 * @author wanzh
 * @date 2022/8/15
 */
@Data
public class BaseLocationExtraData {
    private int extraId;
    private int extraLen;
    private String extraVal;
}

InversionSensorExtraData

package com.bho.jtt808.protocol.message.other.location.extra;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 正反转数据
 * @author wanzh
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class InversionSensorExtraData extends BaseLocationExtraData {


    /**
     * 正反转 0:正转 1:反转 2:停转
     */
    private Integer inversionSensor;

    /**
     * 转速(转/分钟)
     */
    private Integer inversionSpeed;

}

MileageExtraData

package com.bho.jtt808.protocol.message.other.location.extra;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 里程数据
 * @author wanzh
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MileageExtraData extends BaseLocationExtraData {

    private BigDecimal mileage;

}

OilNumExtraData

package com.bho.jtt808.protocol.message.other.location.extra;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.math.BigDecimal;

/**
 * 油量数据
 * @author wanzh
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class OilNumExtraData extends BaseLocationExtraData {

    private BigDecimal oilNum;

}

ExtendAlarm

package com.bho.jtt808.protocol.message.other.location.extra.alarm;


import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.bho.jtt808.protocol.message.other.location.extra.BaseLocationExtraData;
import lombok.Data;

import java.math.BigDecimal;
import java.util.Date;

/**
 * 抽象报警类
 */
@Data
public class ExtendAlarm extends BaseLocationExtraData {

    private String id;


    /**
     * 平台分配的告警唯一标识
     */
    private String alarmGuid;

    /**
     * 告警分类
     */
    private String alarmCategory;
    /**
     * 报警ID DWORD 按照报警先后,从 0 开始循环累加,不区分报警类型
     */
    private Integer alarmId;
    /**
     * 标志状态
     * 0x00:不可用
     * 0x01:开始标志
     * 0x02:结束标志
     * 该字段仅适用于有开始和结束标志类型的报警或事件,
     * 报警类型或事件类型无开始和结束标志,则该位不可
     * 用,填入 0x00 即可
     */
    private Short signalStatus;
    /**
     * 告警事件类型
     */
    private Short alarmEventType;


    /**
     * 告警事件名称
     */
    private String alarmEventName;

    /**
     * 告警级别
     * 0x01:一级报警
     * 0x02:二级报警
     */
    private Short alarmLevel;
    /**
     *车速 BYTE 单位 km/h。范围 0-250
     */
    private short speed;
    /**
     * 高程 WORD 海拔高度,单位为米(m)
     */
    private short height;
    /**
     * 纬度 DWORD
     * 以度为单位的纬度值乘以 10 的 6 次方,精确到百万分之一度
     */
    private BigDecimal latitude;
    /**
     * 经度 DWORD
     * 以度为单位的纬度值乘以 10 的 6 次方,精确到百万分之一度
     */
    private BigDecimal longitude;
    /**
     * 事件日期
     */
    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
    private Date acqTime;

    /**
     * 车辆状态
     */
    private AlarmVehicleStatus avs;
    /**
     * 报警标识
     */
    private AlarmTag aTag;


}

ADASAlarm

package com.bho.jtt808.protocol.message.other.location.extra.alarm;

import lombok.Data;

/**
 * ADAS报警类
 * 注: 高级驾驶辅助系统(Advanced Driver Assistant System)
 * 0x01:前向碰撞报警
 * 0x02:车道偏离报警
 * 0x03:车距过近报警
 * 0x04:行人碰撞报警
 * 0x05:频繁变道报警
 * 0x06:道路标识超限报警
 * 0x07:障碍物报警
 * 0x08-0x0F:用户自定义
 * 0x10:道路标志识别事件
 * 0x11:主动抓拍事件
 * 0x12:实线变道报警
 * 0x13:车厢过道行人检测报警
 * 0x14-0x1F:用户自定义
 */
@Data
public class ADASAlarm extends ExtendAlarm {


    /**
     * 前车车速 BYTE
     * 单位 km/h。范围 0-250,仅报警类型为 0x01 和 0x02 时有效
     */
    private short frontSpeed;
    /**
     * 前车/行人距离 BYTE
     * 单位 100ms,范围 0-100,仅报警类型为 0x01、0x02 和0x04 时有效
     */
    private short frontDistance;
    /**
     * 偏离类型 BYTE
     * 0x01:左侧偏离
     * 0x02:右侧偏离
     * 仅报警类型为 0x02 时有效
     */
    private short offTrackType;
    /**
     * 道路标志识别类型 BYTE
     * 0x01:限速标志
     * 0x02:限高标志
     * 0x03:限重标志
     * 0x04:禁行标志
     * 0x05:禁停标志
     * 仅报警类型为 0x06 和 0x10 时有效
     */
    private short roadMark;
    /**
     * 道路标志识别数据 BYTE 识别到道路标志的数据
     */
    private short roadMarkValue;




}

BSDAlarm

package com.bho.jtt808.protocol.message.other.location.extra.alarm;

import lombok.Data;

/**
 * BSD报警类
 * 0x01:后方接近报警
 * 0x02:左侧后方接近报警
 * 0x03:右侧后方接近报警
 * 注: 盲点监测(Blind Spot Detection)
 */
@Data
public class BSDAlarm extends ExtendAlarm {
   
}

DSMAlarm

package com.bho.jtt808.protocol.message.other.location.extra.alarm;


import lombok.Data;

/**
 * DSM报警类
 * 注: 驾驶员状态监测 (Driving State Monitoring)
 * 0x01:疲劳驾驶报警
 * 0x02:接打手持电话报警
 * 0x03:抽烟报警
 * 0x04:不目视前方报警
 * 0x05:驾驶员异常报警
 * 0x06:探头遮挡报警
 * 0x07:用户自定义
 * 0x08:超时驾驶报警
 * 0x09:用户自定义
 * 0x0A:未系安全带报警
 * 0x0B:红外阻断型墨镜失效报警
 * 0x0C:双脱把报警(双手同时脱离方向盘)
 * 0x0D:玩手机报警
 * 0x0E-0x0F:用户自定义
 * 0x10:自动抓拍事件
 * 0x11:驾驶员变更事件
 * 0x12-0x1F:用户自定义
 */
@Data
public class DSMAlarm extends ExtendAlarm {

    /**
     * 疲劳程度 BYTE
     * 范围 1-9,数值越大表示疲劳程度越严重,仅在报警类
     * 型为 0x01 时有效
     */
    private short fatigueLevel;


}

AlarmMeta

package com.bho.jtt808.protocol.message.other.location.extra.alarm;

import lombok.Data;


@Data
public class AlarmMeta {
    public static final int ALARM_TIMEOUT_MINUTE = 60;
    public static final int ALARM_SIGNAL_STATUS_DISABLED = 0;

    private String mobileForLog;
    private byte alarmType;

    private String alarmName;
    private byte alarmLevel;

    private int signalStatus;

    private AlarmStateEnum alarmState = AlarmStateEnum.INIT;

    private String beginTime;
    private String endTime;

    private float beginLongitude;
    private float endLongitude;

    private float beginLatitude;
    private float endLatitude;


}

AlarmStateEnum

package com.bho.jtt808.protocol.message.other.location.extra.alarm;

public enum AlarmStateEnum {
    INIT(-1, "initialization"),
    START(0, "start"),
    STOP(1, "stop");

    private int id;
    private String code;

    AlarmStateEnum(int id, String code) {
        this.id = id;
        this.code = code;
    }
}

AlarmTag

package com.bho.jtt808.protocol.message.other.location.extra.alarm;

import cn.hutool.core.date.DatePattern;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Date;

/**
 * 报警标记
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlarmTag {
    /**
     * 终端 ID BYTE[30] 30 个字节,由大写字母和数字组成
     */
    public String terminalId;
    /**
     * 时间 BCD[6] YY-MM-DD-hh-mm-ss(GMT+8 时间)
     */
    @JsonFormat(pattern = DatePattern.NORM_DATETIME_PATTERN, timezone = "GMT+8")
    public Date acqTime;
    /**
     * 序号 BYTE 同一时间点报警的序号,从 0 循环累加
     */
    public short alertSN;
    /**
     * 附件数量 BYTE 表示该报警对应的附件数量
     */
    public short attachmentNumber;

    /**
     * 原始报文数据
     */
    private byte[] bytes;

}

AlarmVehicleStatus

package com.bho.jtt808.protocol.message.other.location.extra.alarm;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 报警车辆状态解析
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AlarmVehicleStatus {
    /**
     * Bit0 ACC 状态, 0:关闭,1:打开
     */
    private boolean acc;
    /**
     * Bit1 左转向状态,0:关闭,1:打开
     */
    private boolean turnLeft;
    /**
     * Bit2 右转向状态, 0:关闭,1:打开
     */
    private boolean turnRight;
    /**
     * Bit3 雨刮器状态, 0:关闭,1:打开
     */
    private boolean windshieldWiper;
    /**
     * Bit4 制动状态,0:未制动,1:制动
     */
    private boolean brake;
    /**
     * Bit5 插卡状态,0:未插卡,1:已插卡
     */
    private boolean cardPlugin;
    /**
     * Bit6-Bit9 自定义
     */
    private short customized01;
    /**
     * Bit10 定位状态,0:未定位,1:已定位
     */
    private boolean located;
    /**
     * Bit11-bit15 自定义
     */
    private short customized02;

}

ReportStatus

package com.bho.jtt808.protocol.message.other.location;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 状态数据
 * @author wanzh
 * @date 2022/8/16
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReportStatus {

    /**
     * 0:AC关;1: AC开
     */
    private int acc;
    /**
     * 0:未定位;1:定位
     */
    private int located;
    /**
     * 0:北纬;1:南纬
     */
    private int latitude;
    /**
     * 0:东经;1:西经
     */
    private int longitude;
    /**
     * 0:运营状态;1:停运状态
     */
    private int operating;
    /**
     * 0:经纬度未经保密插件加密;1:经纬度已经保密插件加密
     */
    private int encrypted;
    /**
     * 1:紧急刹车系统采集的前撞预警
     */
    private int forwardCollisionWarn;
    /**
     * 1:车道偏移预警
     */
    private int deviationWarn;
    /**
     * 00:空车;01:半载;10:保留;11:满载。
     * 可表示客车的空载状态 ,重车及货车的空载、满载状态 ,该状态可由人工输人或传感器获取
     */
    private int payLoad;
    /**
     * 0:车辆油路正常;1:车辆油路断开
     */
    private int oilCircuit;
    /**
     * 0:车辆电路正常;1:车辆电路断开
     */
    private int electricCircuit;
    /**
     * 0:车门解锁;1:车门加锁
     */
    private int doorLock;
    /**
     * 0:门 1 关;1:门 1 开(前门)
     */
    private int frontDoor;
    /**
     * 0:门2 关;1:门2 开(中门)
     */
    private int midDoor;
    /**
     * 0:门 3 关;1:门 3 开(后门)
     */
    private int rearDoor;
    /**
     * 0:门4 关;1:门4 开(驾驶席门)
     */
    private int cabsDoor;
    /**
     * 0:门5 关;1:门 5 开( 自定义)
     */
    private int customDoor;
    /**
     * 0:未使用 GPs 卫星进行定位;1:使用 GPs 卫星进行定位
     */
    private int gps;
    /**
     * 0:未使用北斗卫星进行定位;1:使用北斗卫星进行定位
     */
    private int bds;
    /**
     * 0:未使用 GL0NAss 卫星进行定位;1:使用 GL0NAss 卫星进行定位
     */
    private int glonass;
    /**
     * 0:未使用 Ga1i1eo卫星进行定位;1:使用 Ga1i1eo卫星进行定位
     */
    private int galileo;
    /**
     * 0:车辆处于停止状态;1:车辆处于行驶状态
     */
    private int driving;
    /**
     * 0为未举起或未安装,1为举起
     */
    private int liftState;
}

ReportWarn

package com.bho.jtt808.protocol.message.other.location;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 告警数据
 * @author wanzh
 * @date 2022/8/20
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReportWarn {

    /**
     *紧急报警 ,触动报警开关后触发
     */
    private int emergency;
    /**
     *超速报警
     */
    private int overSpeed;
    /**
     *疲劳驾驶报警
     */
    private int fatigue;
    /**
     *:危险驾驶行为报警
     */
    private int dangerousDriving;
    /**
     *GNss 模块发生故障报警
     */
    private int gnssModuleFailure;
    /**
     *GNss 天线未接或被剪断报警
     */
    private int gnssAerialCut;
    /**
     *GNss 天线短路报警
     */
    private int gnssintCircuit;
    /**
     *终端主电源欠压报警
     */
    private int terminalInsufficientVoltage;
    /**
     *终端主电源掉电报警
     */
    private int terminalPowerFailure;
    /**
     *终端 LCD或显示器故障报警
     */
    private int terminalLCDFailure;
    /**
     *TTs 模块故障报警
     */
    private int ttsModuleFailure;
    /**
     *摄像头故障报警
     */
    private int cameraFailure;
    /**
     *道路运输证 IC卡模块故障报警
     */
    private int transportPermitICFailure;
    /**
     *超速预警
     */
    private int overSpeedWarn;
    /**
     *疲劳驾驶预警
     */
    private int fatigueWarn;
    /**
     *违规行驶报警
     */
    private int drivingViolation;
    /**
     *胎压预警
     */
    private int tirePressureWarn;
    /**
     *右转盲区异常报警
     */
    private int rightBlindSpot;
    /**
     *当天累计驾驶超时报警
     */
    private int cumulativeTimeout;
    /**
     *超时停车报警
     */
    private int overtimeParking;
    /**
     *进出区域报警
     */
    private int areaEntryExit;
    /**
     *进出路线报警
     */
    private int routeEntryExit;
    /**
     *路段行驶时间不足/过长报警
     */
    private int drivingTime;
    /**
     *路线偏离报警
     */
    private int lineDeviation;
    /**
     *车辆 Vss 故障
     */
    private int vssFailure;
    /**
     *车辆油量异常报警
     */
    private int fuelLevel;
    /**
     *车辆被盗报警(通过车辆防盗器)
     */
    private int stolen;
    /**
     *车辆非法点火报警
     */
    private int illegalIgnition;
    /**
     *车辆非法位移报
     */
    private int illegalMove;
    /**
     *碰撞侧翻报警
     */
    private int crashRollover;
    /**
     *侧翻预警
     */
    private int rolloverWarn;


}

AttachmentCacheData

package com.bho.jtt808.protocol.message.other.attachment;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 附件缓存数据
 * @author wanzh
 * @date 2022/8/17
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AttachmentCacheData {

    private String id;


    /**
     * 手机号
     */
    private String mobile;


    /**
     * 实时报警附件消息
     */
    private String attachmentInfoId;

    /**
     * 平台给报警分配的唯一编号
     */
    private String alarmGuid;

    /**
     * 文件名称长度
     */
    private short fileNameLen;

    /**
     * 文件名称
     */
    private String fileName;

    /**
     * 文件大小
     */
    private int fileSize;

    /**
     * 文件类型 panorama全景图片, music音频, video视频, text文本, face面部特征图片, other其他
     */
    private String fileType;

    /**
     * 上传状态 0:预备上传 1:开始上传 2:正在上传 3 上传结束
     */
    private int uploadStatus;

    /**
     * 已上传数据长度
     */
    private int uploadDataLen;



    /**
     * 已上传数据列表
     */
    private List<AttachmentUploadData> uploadDatas;


}

### JT/T808 终端通讯协议Java 实现 对于JT/T808终端通讯协议,在Java中的实现通常依赖于Netty框架来处理网络通信。下面提供了一个简化版的消息解码器`Jtt808Decoder`和编码器`Jtt808Encoder`的例子,这些组件负责将字节数组转换成可读的对象形式,并反之亦然。 #### 解码器 `Jtt808Decoder` ```java public class Jtt808Decoder extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // 假设已经实现了具体的解析逻辑 while (in.readableBytes() >= 12 && checkPackage(in)) { // 检查包头和其他必要条件 int bodyLength = getBodyLength(in); // 获取消息体长度 if (in.readableBytes() >= bodyLength + 12) { // 确认有足够的数据用于当前帧 byte[] messageContent = new byte[bodyLength]; in.readBytes(messageContent); Message msg = parseMessage(messageContent); // 将byte数组转为message对象 out.add(msg); } else { break; } } } private boolean checkPackage(ByteBuf buffer){ // 这里应该加入实际的校验逻辑 return true; } private int getBodyLength(ByteBuf buffer){ // 返回消息体的实际长度计算方法 return 0; } private Message parseMessage(byte[] content){ // 把接收到的数据按照协议规定解析出来 return null; } } ``` 该类继承自`ByteToMessageDecoder`并重写了`decode()`函数以适应JT/T808的具体需求[^2]。 #### 编码器 `Jtt808Encoder` ```java public class Jtt808Encoder implements MessageToByteEncoder<Message> { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { byte[] bytes = serializeMessage(msg); out.writeBytes(bytes); } private byte[] serializeMessage(Message msg){ // 序列化message对象成为符合JT/T808格式的bytes[] return null; } } ``` 这个例子展示了如何创建一个简单的编解码器对,它能够接收来自客户端发送过来的原始字节流,并将其转化为易于理解的信息实体;同时也支持反向操作—即把应用程序内部使用的复杂结构重新打包回适合传输的形式[^4]。 为了更全面地理解和运用这套机制,建议深入研究官方文档以及现有开源项目的源代码,比如[jzx-jt808](https://gitcode.com/gh_mirrors/jt/JT808),这可以作为学习的良好起点[^5]。
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值