JT/T808 协议解析

409 阅读15分钟

一.协议介绍

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 字节
STRINGGBK 编码,若无数据,置空

2.2消息结构**

标识位消息头消息体校验码标识位
1byte(0x7e)16byte1byte1byte(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;

importcom.yanboot.iot.common.TPMSConsts;
importcom.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;
importcom.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);
}
}