首页 - 关于研博 - 技术笔记 - JT/T808 协议解析
JT/T808 协议解析
2025.01.10

 一.协议介绍

1.1概述

在实际应用中,车载终端需要通过JT/T808协议接入到监控平台中。接入过程中,车载终端需要进行注册和鉴权,以确保通信双方的身份合法性。一旦接入成功,车载终端就可以向监控平台发送位置、状态、报警等信息,并接收来自监控平台的控制指令。同时,监控平台也可以对车载终端进行远程管理和控制。

1.2 接入过程详解

(1)终端注册

车载终端在安装完成后,需要向监控平台进行注册。

注册过程中,终端需要提供设备的相关信息,如设备ID、制造商ID、车辆VIN码等。

监控平台在接收到注册信息后,会验证这些信息的有效性,并分配一个唯一的终端ID给车载终端。

终端收到平台返回的终端ID后,注册流程完成。

(2)鉴权

注册成功后,车载终端需要进行鉴权,以确保只有经过授权的终端才能与监控平台进行通信。

终端会向监控平台发送鉴权请求,请求中包含终端ID和鉴权码。

监控平台会校验鉴权码的正确性,以确认车载终端的合法性。

如果鉴权码校验通过,则车载终端与监控平台之间的通信链路建立成功。

(3)数据上报

车载终端会根据预设的上报间隔,定期向监控平台发送数据,包括位置信息、状态信息、报警信息等。

这些数据会按照JT/T808协议规定的消息格式进行封装,并通过之前建立的通信链路发送给监控平台。

(4)命令下发

监控平台可以向车载终端发送控制命令,如远程控制、信息查询等。

这些命令同样会按照JT/T808协议规定的消息格式进行封装,并通过通信链路发送给车载终端。

车载终端在接收到命令后,会进行相应的处理,并将处理结果返回给监控平台。

(5)连接维护

在通信过程中,车载终端会周期性地向监控平台发送心跳消息,以保持连接的活跃状态。

如果连接中断,车载终端会尝试重新建立连接,并重新进行鉴权流程。

(6)数据解析与处理

监控平台在接收到车载终端发送的数据后,会进行解析和处理。

根据数据的类型和内容,监控平台可以进行车辆定位、状态监测、报警处理等操作。

同时,监控平台还可以根据需要对车载终端发送控制命令,实现远程监管和服务。

通过以上步骤,车载终端可以成功通过JT/T808协议接入到监控平台,实现远程监管和服务的功能。

1.3应用场景

JT/T808标准广泛应用于车辆远程监管、物流管理、车辆安防等领域。例如,在车队管理系统中,通过卫星定位和通信技术,监控中心可以实时追踪车辆位置、行驶状态和驾驶行为;在物流行业中,物流公司可以实现对运输车辆的实时监控和路径优化,提高运输效率;在车辆安全方面,通过实时监控和追踪,可以及时发现车辆异常情况,如变更路线、事故等,以便及时采取应对措施。

 

二.报文介绍

2.1数据类型介绍

数据类型

描述及要求

BYTE

无符号单字节整形(字节, 8 位)

WORD

无符号双字节整形(字, 16 位)

DWORD

无符号四字节整形(双字, 32 位)

BYTE[n]

n 字节

BCD[n]

8421 码, n 字节

STRING

GBK 编码,若无数据,置空

2.2消息结构

标识位

消息头

消息体

校验码

标识位

1byte(0x7e)

16byte

 

1byte

1byte(0x7e)

2.3消息头

消息ID(0-1) 消息体属性(2-3)  终端手机号(4-9)  消息流水号(10-11)    消息包封装项(12-15)

byte[0-1]   消息ID word(16)
byte[2-3]   消息体属性 word(16)
        bit[0-9]    消息体长度
        bit[10-12]  数据加密方式
                        此三位都为 0,表示消息体不加密
                        第 10 位为 1,表示消息体经过 RSA 算法加密
                        其它保留
        bit[13]     分包
                        1:消息体卫长消息,进行分包发送处理,具体分包信息由消息包封装项决定
                        0:则消息头中无消息包封装项字段
        bit[14-15]  保留
byte[4-9]   终端手机号或设备ID bcd[6]
        根据安装后终端自身的手机号转换
        手机号不足12 位,则在前面补 0
byte[10-11]     消息流水号 word(16)
        按发送顺序从 0 开始循环累加
byte[12-15]     消息包封装项
        byte[0-1]   消息包总数(word(16))
                        该消息分包后得总包数
        byte[2-3]   包序号(word(16))
                        从 1 开始
        如果消息体属性中相关标识位确定消息分包处理,则该项有内容
        否则无该项

 

三.协议解析开发

本次协议解析基于研博工业物联网统一接入系统(stew-ot)协议扩展规范开发。

解析协议中用到的实体类

public class PackageData {

    /**
   * 16byte 消息头
   */
    protected MsgHeader msgHeader;

    // 消息体
    protected  byte[] msgHeaderBytes;

    // 消息体字节数组
    @JSONField(serialize=false)
    protected byte[] msgBodyBytes;

    /**
   * 校验码 1byte
   */
    protected int checkSum;

    @JSONField(serialize=false)
    protected Channel channel;

    public MsgHeader getMsgHeader() {
        return msgHeader;
    }

    public void setMsgHeader(MsgHeader msgHeader) {
        this.msgHeader = msgHeader;
    }

    public byte[] getMsgBodyBytes() {
        return msgBodyBytes;
    }

    public byte[] getMsgHeaderBytes() {
        return msgHeaderBytes;
    }

    public void setMsgHeaderBytes(byte[] msgHeaderBytes) {
        this.msgHeaderBytes = msgHeaderBytes;
    }


    public void setMsgBodyBytes(byte[] msgBodyBytes) {
        this.msgBodyBytes = msgBodyBytes;
    }

    public int getCheckSum() {
        return checkSum;
    }

    public void setCheckSum(int checkSum) {
        this.checkSum = checkSum;
    }

    public Channel getChannel() {
        return channel;
    }

    public void setChannel(Channel channel) {
        this.channel = channel;
    }

    @Override
    public String toString() {
        return "PackageData [msgHeader=" + msgHeader + ", msgBodyBytes=" + Arrays.toString(msgBodyBytes) + ", checkSum="
        + checkSum + ", address=" + channel + "]";
    }

    public static class MsgHeader {
        // 消息ID
        protected int msgId;

        /////// ========消息体属性
        // byte[2-3]
        protected int msgBodyPropsField;
        // 消息体长度
        protected int msgBodyLength;
        // 数据加密方式
        protected int encryptionType;
        // 是否分包,true==>有消息包封装项
        protected boolean hasSubPackage;
        // 保留位[14-15]
        protected String reservedBit;
        /////// ========消息体属性

        // 终端手机号
        protected String terminalPhone;
        // 流水号
        protected int flowId;

        //////// =====消息包封装项
        // byte[12-15]
        protected int packageInfoField;
        // 消息包总数(word(16))
        protected long totalSubPackage;
        // 包序号(word(16))这次发送的这个消息包是分包中的第几个消息包, 从 1 开始
        protected long subPackageSeq;
        //////// =====消息包封装项

        public int getMsgId() {
            return msgId;
        }

        public void setMsgId(int msgId) {
            this.msgId = msgId;
        }

        public int getMsgBodyLength() {
            return msgBodyLength;
        }

        public void setMsgBodyLength(int msgBodyLength) {
            this.msgBodyLength = msgBodyLength;
        }

        public int getEncryptionType() {
            return encryptionType;
        }

        public void setEncryptionType(int encryptionType) {
            this.encryptionType = encryptionType;
        }

        public String getTerminalPhone() {
            return terminalPhone;
        }

        public void setTerminalPhone(String terminalPhone) {
            this.terminalPhone = terminalPhone;
        }

        public int getFlowId() {
            return flowId;
        }

        public void setFlowId(int flowId) {
            this.flowId = flowId;
        }

        public boolean isHasSubPackage() {
            return hasSubPackage;
        }

        public void setHasSubPackage(boolean hasSubPackage) {
            this.hasSubPackage = hasSubPackage;
        }

        public String getReservedBit() {
            return reservedBit;
        }

        public void setReservedBit(String reservedBit) {
            this.reservedBit = reservedBit;
        }

        public long getTotalSubPackage() {
            return totalSubPackage;
    }

    public void setTotalSubPackage(long totalPackage) {
      this.totalSubPackage = totalPackage;
    }

    public long getSubPackageSeq() {
      return subPackageSeq;
    }

    public void setSubPackageSeq(long packageSeq) {
      this.subPackageSeq = packageSeq;
    }

    public int getMsgBodyPropsField() {
      return msgBodyPropsField;
    }

    public void setMsgBodyPropsField(int msgBodyPropsField) {
      this.msgBodyPropsField = msgBodyPropsField;
    }

    public void setPackageInfoField(int packageInfoField) {
      this.packageInfoField = packageInfoField;
    }

    public int getPackageInfoField() {
      return packageInfoField;
    }

    @Override
    public String toString() {
      return "MsgHeader [msgId=" + msgId + ", msgBodyPropsField=" + msgBodyPropsField + ", msgBodyLength="
          + msgBodyLength + ", encryptionType=" + encryptionType + ", hasSubPackage=" + hasSubPackage
          + ", reservedBit=" + reservedBit + ", terminalPhone=" + terminalPhone + ", flowId=" + flowId
          + ", packageInfoField=" + packageInfoField + ", totalSubPackage=" + totalSubPackage
          + ", subPackageSeq=" + subPackageSeq + "]";
    }

  }

}

实现com.yanboot.iot.sdk.protocol.ProtocolCodec接口,重写support方法,指定协议的唯一标识、名称、特性等内容。

@Override
    public ProtocolSupport support() {

        return new ProtocolSupport(TransportProtocol.TCP)
                .id("JT/T808-tcp-codec")
                .name("JT/T808-tcp协议")
                .description("JT/T808-2019版")
                .feature(new 
ProtocolFeature().remoteUpgrade(false).keepOnline(true).keepOnlineTimeoutSeconds(360));
    }

实现com.yanboot.iot.sdk.protocol.ProtocolCodec接口的decode方法,完成协议的解码工作。

@Override
    public void decode(OperatorSupplier supplier, DeviceSession deviceSession, ProtocolMessage<?> message, MessageExporter<DeviceMessage<?>> messageExporter) {
        TcpProtocolMessage tcpProtocolMessage = (TcpProtocolMessage) message;
        ByteBuf payload = tcpProtocolMessage.getPayload();
        if (payload.readableBytes() <= 0) {
            throw new RuntimeException("数据包异常");
        }
        byte[] bytes = new byte[payload.readableBytes()];
        payload.readBytes(bytes);
        PackageData pkg = DecodeHelper.bytes2PackageData(bytes);
        log.info("pkg{}", pkg);
        processPackageData(pkg, deviceSession);
    }

   //业务处理
    private static void processPackageData(PackageData packageData, DeviceSession deviceSession) {
        final PackageData.MsgHeader header = packageData.getMsgHeader();

        // 1. 终端心跳-消息体为空 ==> 平台通用应答
        if (TPMSConsts.msg_id_terminal_heart_beat == header.getMsgId()) {
            log.info(">>>>>[终端心跳],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
                sendCommonReplay(deviceSession, packageData);
            } catch (Exception e) {
                log.error("<<<<<[终端心跳]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }

        // 5. 终端鉴权 ==> 平台通用应答
        else if (TPMSConsts.msg_id_terminal_authentication == header.getMsgId()) {
            log.info(">>>>>[终端鉴权],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
//                TerminalAuthenticationMsg authenticationMsg = new TerminalAuthenticationMsg(packageData);
//                this.msgProcessService.processAuthMsg(authenticationMsg);
                //TODO 待解析鉴权消息
                sendCommonReplay(deviceSession, packageData);
                log.info("<<<<<[终端鉴权],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            } catch (Exception e) {
                log.error("<<<<<[终端鉴权]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 6. 终端注册 ==> 终端注册应答
        else if (TPMSConsts.msg_id_terminal_register == header.getMsgId()) {
            log.info(">>>>>[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
                TcpProtocolMessage tcpProtocolMessage = new TcpProtocolMessage();
                TerminalRegisterMsg msg = DecodeHelper.toTerminalRegisterMsg(packageData);
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                outputStream.write(packageData.getMsgHeader().getFlowId());
                //TODO
                outputStream.write(0);
                outputStream.write(0);
                byte[] byteArray = outputStream.toByteArray();
                ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
                byteArrayOutputStream.write(126);
                byteArrayOutputStream.write(packageData.getMsgHeaderBytes());
                byteArrayOutputStream.write(byteArray);
                byte[] byteArray1 = byteArrayOutputStream.toByteArray();
                byte checkSum = 0;
                for (int i = 1; i < byteArray1.length; i++) {
                    checkSum ^= byteArray1[i];
                }
                ByteArrayOutputStream result = new ByteArrayOutputStream(byteArray1.length + 2);
                result.write(byteArray1);
                result.write(checkSum);
                result.write(126);
                tcpProtocolMessage.payload(result.toByteArray());
                deviceSession.send(tcpProtocolMessage);
                log.info("<<<<<[终端注册],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
                log.info("注册信息:{}", msg.getTerminalRegInfo());
            } catch (Exception e) {
                log.error("<<<<<[终端注册]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 7. 终端注销(终端注销数据消息体为空) ==> 平台通用应答
        else if (TPMSConsts.msg_id_terminal_log_out == header.getMsgId()) {
            log.info(">>>>>[终端注销],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            try {
//                this.msgProcessService.processTerminalLogoutMsg(packageData);
                sendCommonReplay(deviceSession, packageData);
                log.info("<<<<<[终端注销],phone={},flowid={}", header.getTerminalPhone(), header.getFlowId());
            } catch (Exception e) {
                log.error("<<<<<[终端注销]处理错误,phone={},flowid={},err={}", header.getTerminalPhone(), header.getFlowId(),
                        e.getMessage());
                e.printStackTrace();
            }
        }
        // 其他情况
        else {
            log.error(">>>>>>[未知消息类型],phone={},msgId={},package={}", header.getTerminalPhone(), header.getMsgId(),
                    packageData);
        }
    }
 public static void sendCommonReplay(DeviceSession deviceSession, PackageData packageData) {
        try {
            PackageData.MsgHeader header = packageData.getMsgHeader();
            TcpProtocolMessage tcpProtocolMessage1 = new TcpProtocolMessage();
            int msgId = header.getMsgId();
            int flowId = header.getFlowId();
            byte success = ServerCommonRespMsgBody.success;
//                byte[] bytes = JT808ProtocolUtils.generateMsgHeader(header.getTerminalPhone(), msgId, header.getMsgBodyPropsField(), header.getFlowId());
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            // 1. 消息ID word(16)
            BitOperator bitOperator = new BitOperator();
            baos.write(bitOperator.integerTo2Bytes(msgId));
            baos.write(bitOperator.integerTo2Bytes(flowId));
            baos.write(success);
            byte[] byteBody = baos.toByteArray();
            byte checkSum = 0;
            for (int i = 0; i < byteBody.length; i++) {
                checkSum ^= byteBody[i];
            }
            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            byteArrayOutputStream.write(126);
            byteArrayOutputStream.write(packageData.getMsgHeaderBytes());
            byteArrayOutputStream.write(byteBody);
            byteArrayOutputStream.write(checkSum);
            byteArrayOutputStream.write(126);
            byte[] result = byteArrayOutputStream.toByteArray();

            tcpProtocolMessage1.payload(result);
            deviceSession.send(tcpProtocolMessage1);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

解析协议中用到的工具类

package com.yanboot.iot;

import com.yanboot.iot.common.TPMSConsts;
import com.yanboot.iot.util.BCD8421Operater;
import com.yanboot.iot.util.BitOperator;
import com.yanboot.iot.vo.PackageData;
import com.yanboot.iot.vo.req.TerminalRegisterMsg;
import lombok.extern.slf4j.Slf4j;
import com.yanboot.iot.vo.req.TerminalRegisterMsg.TerminalRegInfo;
import com.yanboot.iot.vo.PackageData.MsgHeader;
import java.nio.charset.StandardCharsets;
@Slf4j
public class DecodeHelper {
    private static final BitOperator bitOperator = new BitOperator();
    private static final BCD8421Operater bcd8421Operater = new BCD8421Operater();
    public DecodeHelper() {
//        this.bitOperator = new BitOperator();
//        this.bcd8421Operater = new BCD8421Operater();
    }
    public static PackageData bytes2PackageData(byte[] data) {
        PackageData ret = new PackageData();
        // 0. 终端套接字地址信息
        // ret.setChannel(msg.getChannel());
        // 1. 16byte 或 12byte 消息头
        MsgHeader msgHeader = parseMsgHeaderFromBytes(data);
        ret.setMsgHeader(msgHeader);
        int msgBodyByteStartIndex = 12;
        // 2. 消息体
        // 有子包信息,消息体起始字节后移四个字节:消息包总数(word(16))+包序号(word(16))
        if (msgHeader.isHasSubPackage()) {
            msgBodyByteStartIndex = 16;
        }
        byte[] tmp = new byte[msgHeader.getMsgBodyLength()];
        byte[] headerBytes = new byte[msgBodyByteStartIndex];
        System.arraycopy(data, 0, headerBytes, 0, headerBytes.length);
        System.arraycopy(data, msgBodyByteStartIndex, tmp, 0, tmp.length);
        ret.setMsgHeaderBytes(headerBytes);
        ret.setMsgBodyBytes(tmp);
        // 3. 去掉分隔符之后,最后一位就是校验码
        // int checkSumInPkg =
        // this.bitOperator.oneByteToInteger(data[data.length - 1]);
        int checkSumInPkg = data[data.length - 1];
        int calculatedCheckSum = bitOperator.getCheckSum4JT808(data, 0, data.length - 1);
        ret.setCheckSum(checkSumInPkg);
        if (checkSumInPkg != calculatedCheckSum) {
            log.warn("检验码不一致,msgid:{},pkg:{},calculated:{}", msgHeader.getMsgId(), checkSumInPkg, calculatedCheckSum);
        }
        return ret;
    }
    //消息头解析
    private static MsgHeader parseMsgHeaderFromBytes(byte[] data) {
        MsgHeader msgHeader = new MsgHeader();
        // 1. 消息ID word(16)
        // byte[] tmp = new byte[2];
        // System.arraycopy(data, 0, tmp, 0, 2);
        // msgHeader.setMsgId(this.bitOperator.twoBytesToInteger(tmp));
        msgHeader.setMsgId(parseIntFromBytes(data, 0, 2));
        // 2. 消息体属性 word(16)=================>
        // System.arraycopy(data, 2, tmp, 0, 2);
        // int msgBodyProps = this.bitOperator.twoBytesToInteger(tmp);
        int msgBodyProps = parseIntFromBytes(data, 2, 2);
        msgHeader.setMsgBodyPropsField(msgBodyProps);
        // [ 0-9 ] 0000,0011,1111,1111(3FF)(消息体长度)
        msgHeader.setMsgBodyLength(msgBodyProps & 0x1ff);
        // [10-12] 0001,1100,0000,0000(1C00)(加密类型)
        msgHeader.setEncryptionType((msgBodyProps & 0xe00) >> 10);
        // [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包)
        msgHeader.setHasSubPackage(((msgBodyProps & 0x2000) >> 13) == 1);
        // [14-15] 1100,0000,0000,0000(C000)(保留位)
        msgHeader.setReservedBit(((msgBodyProps & 0xc000) >> 14) + "");
        // 消息体属性 word(16)<=================
        // 3. 终端手机号 bcd[6]
        // tmp = new byte[6];
        // System.arraycopy(data, 4, tmp, 0, 6);
        // msgHeader.setTerminalPhone(this.bcd8421Operater.bcd2String(tmp));
        msgHeader.setTerminalPhone(parseBcdStringFromBytes(data, 4, 6));
        // 4. 消息流水号 word(16) 按发送顺序从 0 开始循环累加
        // tmp = new byte[2];
        // System.arraycopy(data, 10, tmp, 0, 2);
        // msgHeader.setFlowId(this.bitOperator.twoBytesToInteger(tmp));
        msgHeader.setFlowId(parseIntFromBytes(data, 10, 2));
        // 5. 消息包封装项
        // 有子包信息
        if (msgHeader.isHasSubPackage()) {
            // 消息包封装项字段
            msgHeader.setPackageInfoField(parseIntFromBytes(data, 12, 4));
            // byte[0-1] 消息包总数(word(16))
            // tmp = new byte[2];
            // System.arraycopy(data, 12, tmp, 0, 2);
            // msgHeader.setTotalSubPackage(this.bitOperator.twoBytesToInteger(tmp));
            msgHeader.setTotalSubPackage(parseIntFromBytes(data, 12, 2));
            // byte[2-3] 包序号(word(16)) 从 1 开始
            // tmp = new byte[2];
            // System.arraycopy(data, 14, tmp, 0, 2);
            // msgHeader.setSubPackageSeq(this.bitOperator.twoBytesToInteger(tmp));
            msgHeader.setSubPackageSeq(parseIntFromBytes(data, 12, 2));
        }
        return msgHeader;
    }
//    protected static String parseStringFromBytes(byte[] data, int startIndex, int lenth) {
//        return parseStringFromBytes(data, startIndex, lenth, null);
//    }
    private static String parseStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return new String(tmp, TPMSConsts.string_charset);
        } catch (Exception e) {
            log.error("解析字符串出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }
    private static String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth) {
        return parseBcdStringFromBytes(data, startIndex, lenth, null);
    }
    private static String parseBcdStringFromBytes(byte[] data, int startIndex, int lenth, String defaultVal) {
        try {
            byte[] tmp = new byte[lenth];
            log.debug("拷贝的数组长度:{},实际拷贝长度:{},开始的未知:{}", data.length, lenth, startIndex);
            System.arraycopy(data, startIndex, tmp, 0, lenth);
            return bcd8421Operater.bcd2String(tmp);
        } catch (Exception e) {
            log.error("解析BCD(8421码)出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }
//    private static int parseIntFromBytes(byte[] data, int startIndex, int length) {
//        return parseIntFromBytes(data, startIndex, length, 0);
//    }
    private static int parseIntFromBytes(byte[] data, int startIndex, int length, int defaultVal) {
        try {
            // 字节数大于4,从起始索引开始向后处理4个字节,其余超出部分丢弃
            final int len = length > 4 ? 4 : length;
            byte[] tmp = new byte[len];
            System.arraycopy(data, startIndex, tmp, 0, len);
            return bitOperator.byteToInteger(tmp);
        } catch (Exception e) {
            log.error("解析整数出错:{}", e.getMessage());
            e.printStackTrace();
            return defaultVal;
        }
    }
    private static int parseIntFromBytes(byte[] data, int offset, int length) {
        int value = 0;
        for (int i = 0; i < length; i++) {
            value = (value << 8) | (data[offset + i] & 0xFF);
        }
        return value;
    }
    private static String parseStringFromBytes(byte[] data, int offset, int length) {
        return new String(data, offset, length, StandardCharsets.ISO_8859_1).trim();
    }
    //终端注册
    public static TerminalRegisterMsg toTerminalRegisterMsg(PackageData packageData) {
        TerminalRegisterMsg ret = new TerminalRegisterMsg(packageData);
        byte[] data = ret.getMsgBodyBytes();
        TerminalRegInfo body = new TerminalRegInfo();
        // 1. byte[0-1] 省域ID(WORD)
        // 设备安装车辆所在的省域,省域ID采用GB/T2260中规定的行政区划代码6位中前两位
        // 0保留,由平台取默认值
        body.setProvinceId(parseIntFromBytes(data, 0, 2));
        // 2. byte[2-3] 设备安装车辆所在的市域或县域,市县域ID采用GB/T2260中规定的行 政区划代码6位中后四位
        // 0保留,由平台取默认值
        body.setCityId(parseIntFromBytes(data, 2, 2));
        // 3. byte[4-8] 制造商ID(BYTE[5]) 5 个字节,终端制造商编码
        // byte[] tmp = new byte[5];
        body.setManufacturerId(parseStringFromBytes(data, 4, 5));
        // 4. byte[9-16] 终端型号(BYTE[8]) 八个字节, 此终端型号 由制造商自行定义 位数不足八位的,补空格。
        body.setTerminalType(parseStringFromBytes(data, 9, 20));
        // 5. byte[17-23] 终端ID(BYTE[7]) 七个字节, 由大写字母 和数字组成, 此终端 ID由制造 商自行定义
        body.setTerminalId(parseStringFromBytes(data, 29, 7));
        // 6. byte[24] 车牌颜色(BYTE) 车牌颜 色按照JT/T415-2006 中5.4.12 的规定
        body.setLicensePlateColor(parseIntFromBytes(data, 36, 1));
        // 7. byte[25-x] 车牌(STRING) 公安交 通管理部门颁 发的机动车号牌
        body.setLicensePlate(parseStringFromBytes(data, 37, data.length - 37));
        ret.setTerminalRegInfo(body);
        return ret;
    }
    //终端鉴权
    public static TerminalRegisterMsg toTerminalAuth(PackageData packageData) {
        return null;
    }
}
package com.yanboot.iot.util;
import java.io.ByteArrayOutputStream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * JT808协议转义工具类
 *
 * <pre>
 * 0x7d01 <====> 0x7d
 * 0x7d02 <====> 0x7e
 * </pre>
 *
 * @author hylexus
 */
public class JT808ProtocolUtils {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private static BitOperator bitOperator = new BitOperator();
    private static BCD8421Operater bcd8421Operater = new BCD8421Operater();
    public JT808ProtocolUtils() {
//        this.bitOperator = new BitOperator();
//        this.bcd8421Operater = new BCD8421Operater();
    }
    /**
     * 接收消息时转义<br>
     *
     * <pre>
     * 0x7d01 <====> 0x7d
     * 0x7d02 <====> 0x7e
     * </pre>
     *
     * @param bs    要转义的字节数组
     * @param start 起始索引
     * @param end   结束索引
     * @return 转义后的字节数组
     * @throws Exception
     */
    public byte[] doEscape4Receive(byte[] bs, int start, int end) throws Exception {
        if (start < 0 || end > bs.length)
            throw new ArrayIndexOutOfBoundsException("doEscape4Receive error : index out of bounds(start=" + start
                    + ",end=" + end + ",bytes length=" + bs.length + ")");
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            for (int i = 0; i < start; i++) {
                baos.write(bs[i]);
            }
            for (int i = start; i < end - 1; i++) {
                if (bs[i] == 0x7d && bs[i + 1] == 0x01) {
                    baos.write(0x7d);
                    i++;
                } else if (bs[i] == 0x7d && bs[i + 1] == 0x02) {
                    baos.write(0x7e);
                    i++;
                } else {
                    baos.write(bs[i]);
                }
            }
            for (int i = end - 1; i < bs.length; i++) {
                baos.write(bs[i]);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            throw e;
        } finally {
            if (baos != null) {
                baos.close();
                baos = null;
            }
        }
    }
    /**
     * 发送消息时转义<br>
     *
     * <pre>
     *  0x7e <====> 0x7d02
     * </pre>
     *
     * @param bs    要转义的字节数组
     * @param start 起始索引
     * @param end   结束索引
     * @return 转义后的字节数组
     * @throws Exception
     */
    public byte[] doEscape4Send(byte[] bs, int start, int end) throws Exception {
        if (start < 0 || end > bs.length)
            throw new ArrayIndexOutOfBoundsException("doEscape4Send error : index out of bounds(start=" + start
                    + ",end=" + end + ",bytes length=" + bs.length + ")");
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            for (int i = 0; i < start; i++) {
                baos.write(bs[i]);
            }
            for (int i = start; i < end; i++) {
                if (bs[i] == 0x7e) {
                    baos.write(0x7d);
                    baos.write(0x02);
                } else {
                    baos.write(bs[i]);
                }
            }
            for (int i = end; i < bs.length; i++) {
                baos.write(bs[i]);
            }
            return baos.toByteArray();
        } catch (Exception e) {
            throw e;
        } finally {
            if (baos != null) {
                baos.close();
                baos = null;
            }
        }
    }
    public int generateMsgBodyProps(int msgLen, int enctyptionType, boolean isSubPackage, int reversed_14_15) {
        // [ 0-9 ] 0000,0011,1111,1111(3FF)(消息体长度)
        // [10-12] 0001,1100,0000,0000(1C00)(加密类型)
        // [ 13_ ] 0010,0000,0000,0000(2000)(是否有子包)
        // [14-15] 1100,0000,0000,0000(C000)(保留位)
        if (msgLen >= 1024)
            log.warn("The max value of msgLen is 1023, but {} .", msgLen);
        int subPkg = isSubPackage ? 1 : 0;
        int ret = (msgLen & 0x3FF) | ((enctyptionType << 10) & 0x1C00) | ((subPkg << 13) & 0x2000)
                | ((reversed_14_15 << 14) & 0xC000);
        return ret & 0xffff;
    }
    public static byte[] generateMsgHeader(String phone, int msgType, int msgBodyProps, int flowId)
            throws Exception {
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            // 1. 消息ID word(16)
            baos.write(bitOperator.integerTo2Bytes(msgType));
            // 2. 消息体属性 word(16)
            baos.write(bitOperator.integerTo2Bytes(msgBodyProps));
            // 3. 终端手机号 bcd[6]
            baos.write(bcd8421Operater.string2Bcd(phone));
            // 4. 消息流水号 word(16),按发送顺序从 0 开始循环累加
            baos.write(bitOperator.integerTo2Bytes(flowId));
            // 消息包封装项 此处不予考虑
            return baos.toByteArray();
        } finally {
            if (baos != null) {
                baos.close();
            }
        }
    }
}
package com.yanboot.iot.util;
public class HexStringUtils {
  private static final char[] DIGITS_HEX = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
  protected static char[] encodeHex(byte[] data) {
    int l = data.length;
    char[] out = new char[l << 1];
    for (int i = 0, j = 0; i < l; i++) {
      out[j++] = DIGITS_HEX[(0xF0 & data[i]) >>> 4];
      out[j++] = DIGITS_HEX[0x0F & data[i]];
    }
    return out;
  }
  protected static byte[] decodeHex(char[] data) {
    int len = data.length;
    if ((len & 0x01) != 0) {
      throw new RuntimeException("字符个数应该为偶数");
    }
    byte[] out = new byte[len >> 1];
    for (int i = 0, j = 0; j < len; i++) {
      int f = toDigit(data[j], j) << 4;
      j++;
      f |= toDigit(data[j], j);
      j++;
      out[i] = (byte) (f & 0xFF);
    }
    return out;
  }
  protected static int toDigit(char ch, int index) {
    int digit = Character.digit(ch, 16);
    if (digit == -1) {
      throw new RuntimeException("Illegal hexadecimal character " + ch + " at index " + index);
    }
    return digit;
  }
  public static String toHexString(byte[] bs) {
    return new String(encodeHex(bs));
  }
  public static String hexString2Bytes(String hex) {
    return new String(decodeHex(hex.toCharArray()));
  }
  public static byte[] chars2Bytes(char[] bs) {
    return decodeHex(bs);
  }
  public static void main(String[] args) {
    String s = "abc你好";
    String hex = toHexString(s.getBytes());
    String decode = hexString2Bytes(hex);
    System.out.println("原字符串:" + s);
    System.out.println("十六进制字符串:" + hex);
    System.out.println("还原:" + decode);
  }
}

 

获取相关资料
下载地址将会发送至您填写的邮箱
相关新闻
电能表DLT645协议解析
2025-02-07
水文SL651协议解析
2025-01-17
环保HJ212-2017协议介绍开发
2025-01-03
  • 在线客服
  • 电话咨询
  • 微信
  • 短视频