在实际应用中,车载终端需要通过JT/T808协议接入到监控平台中。接入过程中,车载终端需要进行注册和鉴权,以确保通信双方的身份合法性。一旦接入成功,车载终端就可以向监控平台发送位置、状态、报警等信息,并接收来自监控平台的控制指令。同时,监控平台也可以对车载终端进行远程管理和控制。
(1)终端注册
车载终端在安装完成后,需要向监控平台进行注册。
注册过程中,终端需要提供设备的相关信息,如设备ID、制造商ID、车辆VIN码等。
监控平台在接收到注册信息后,会验证这些信息的有效性,并分配一个唯一的终端ID给车载终端。
终端收到平台返回的终端ID后,注册流程完成。
(2)鉴权
注册成功后,车载终端需要进行鉴权,以确保只有经过授权的终端才能与监控平台进行通信。
终端会向监控平台发送鉴权请求,请求中包含终端ID和鉴权码。
监控平台会校验鉴权码的正确性,以确认车载终端的合法性。
如果鉴权码校验通过,则车载终端与监控平台之间的通信链路建立成功。
(3)数据上报
车载终端会根据预设的上报间隔,定期向监控平台发送数据,包括位置信息、状态信息、报警信息等。
这些数据会按照JT/T808协议规定的消息格式进行封装,并通过之前建立的通信链路发送给监控平台。
(4)命令下发
监控平台可以向车载终端发送控制命令,如远程控制、信息查询等。
这些命令同样会按照JT/T808协议规定的消息格式进行封装,并通过通信链路发送给车载终端。
车载终端在接收到命令后,会进行相应的处理,并将处理结果返回给监控平台。
(5)连接维护
在通信过程中,车载终端会周期性地向监控平台发送心跳消息,以保持连接的活跃状态。
如果连接中断,车载终端会尝试重新建立连接,并重新进行鉴权流程。
(6)数据解析与处理
监控平台在接收到车载终端发送的数据后,会进行解析和处理。
根据数据的类型和内容,监控平台可以进行车辆定位、状态监测、报警处理等操作。
同时,监控平台还可以根据需要对车载终端发送控制命令,实现远程监管和服务。
通过以上步骤,车载终端可以成功通过JT/T808协议接入到监控平台,实现远程监管和服务的功能。
JT/T808标准广泛应用于车辆远程监管、物流管理、车辆安防等领域。例如,在车队管理系统中,通过卫星定位和通信技术,监控中心可以实时追踪车辆位置、行驶状态和驾驶行为;在物流行业中,物流公司可以实现对运输车辆的实时监控和路径优化,提高运输效率;在车辆安全方面,通过实时监控和追踪,可以及时发现车辆异常情况,如变更路线、事故等,以便及时采取应对措施。
数据类型 |
描述及要求 |
BYTE |
无符号单字节整形(字节, 8 位) |
WORD |
无符号双字节整形(字, 16 位) |
DWORD |
无符号四字节整形(双字, 32 位) |
BYTE[n] |
n 字节 |
BCD[n] |
8421 码, n 字节 |
STRING |
GBK 编码,若无数据,置空 |
标识位 |
消息头 |
消息体 |
校验码 |
标识位 |
1byte(0x7e) |
16byte |
1byte |
1byte(0x7e) |
消息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);
}
}