写在前面:
通讯协议一般格式:包头 — 消息头 — 消息体 — 校验位 — 包尾;
通讯数据格式 : bit 、 byte ;
bit是指的就是二进制 , 也就是位,1 byte = 8 bit ;
JT808协议:
数据类型:
通俗来讲:WORD就是长度是2的byte数组(byte[2]),DWORD就是长度是4的byte数组(byte[4]);
消息结构:
- 标识位:
采用 0x7e 表示,若校验码、消息头以及消息体中出现 0x7e,则要进行转义处理,转义
规则定义如下:
0x7e <————> 0x7d 后紧跟一个 0x02;
0x7d <————> 0x7d 后紧跟一个 0x01。
转义处理过程如下:
发送消息时:消息封装——>计算并填充校验码——>转义;
接收消息时:转义还原——>验证校验码——>解析消息。
示例:
发送一包内容为 0x30 0x7e 0x08 0x7d 0x55 的数据包,
则经过封装如下:0x7e 0x30 7d 0x02 0x08 0x7d 0x01 0x55 0x7e
- 消息头:
- 消息体:就是你要发送的数据(byte[]);
- 校验码:
校验码指从消息头开始,同后一字节异或,直到校验码前一个字节,占用一个字节。
消息编码和解码:
- 消息编码:
/**
* 包装808数据,分包消息
*
* @param msgId 消息id
* @param phone 终端手机号
* @param msgBody 消息体
* @param subpackage 是否分包 0:不分包 1:分包
* @param totalPkg 总包数
* @param pkgNo 当前序号
*/
public static byte[] generate808(int msgId, String phone, byte[] msgBody, int subpackage, long totalPkg, long pkgNo) {
//=========================标识位==================================//
byte[] flag = new byte[]{0x7E};
//=========================消息头==================================//
//[0,1]消息Id
byte[] msgIdb = BitOperator.numToByteArray(msgId, 2);
//[2,3]消息体属性
byte[] msgBodyAttributes = msgBodyAttributes(msgBody.length, subpackage);
//[4,9]终端手机号 BCD[6](占6位)
byte[] terminalPhone = BCD8421Operater.string2Bcd(phone);
//[10,11]流水号
byte[] flowNum = BitOperator.numToByteArray(SocketConfig.getSocketMsgCount(), 2);
//[12]消息包封装项 不分包 就没有
//[12]消息包封装项 不分包 就没有
byte[] msgHeader;
if (subpackage == 1) {
//分包
byte[] totalPkgB = BitOperator.numToByteArray(totalPkg, 2);
byte[] pkgNob = BitOperator.numToByteArray(pkgNo, 2);
msgHeader = ByteUtil.byteMergerAll(msgIdb, msgBodyAttributes, terminalPhone, flowNum, totalPkgB, pkgNob);
} else {
msgHeader = ByteUtil.byteMergerAll(msgIdb, msgBodyAttributes, terminalPhone, flowNum);
}
//=========================数据合并(消息头,消息体)=====================//
byte[] bytes = ByteUtil.byteMergerAll(msgHeader, msgBody);
//=========================计算校验码==================================//
String checkCodeHexStr = HexUtil.getBCC(bytes);
byte[] checkCode = HexUtil.hexStringToByte(checkCodeHexStr);
//=========================合并:消息头 消息体 校验码 得到总数据============//
byte[] AllData = ByteUtil.byteMergerAll(bytes, checkCode);
//=========================转义 7E和7D==================================//
// 转成16进制字符串
String hexStr = HexUtil.byte2HexStr(AllData);
// 替换 7E和7D
String replaceHexStr = hexStr.replaceAll(FLAG_7D, " 7D 01")
.replaceAll(FLAG_7E, " 7D 02")
// 最后去除空格
.replaceAll(" ", "");
//替换好后 转回byte[]
byte[] replaceByte = HexUtil.hexStringToByte(replaceHexStr);
//=========================最终传输给服务器的数据==================================//
return ByteUtil.byteMergerAll(flag, replaceByte, flag);
}
/**
* 生成消息体属性
*
* @param subpackage [13]是否分包 0:不分包 1:分包
*/
private static byte[] msgBodyAttributes(int msgLength, int subpackage) {
byte[] length = BitOperator.numToByteArray(msgLength, 2);
//[0,9]消息体长度
String msgBodyLength = "" +
//第一个字节最后2bit
+(byte) ((length[0] >> 1) & 0x1) + (byte) ((length[0] >> 0) & 0x1)
//第二个字节8bit
+ (byte) ((length[1] >> 7) & 0x1) + (byte) ((length[1] >> 6) & 0x1)
+ (byte) ((length[1] >> 5) & 0x1) + (byte) ((length[1] >> 4) & 0x1)
+ (byte) ((length[1] >> 3) & 0x1) + (byte) ((length[1] >> 2) & 0x1)
+ (byte) ((length[1] >> 1) & 0x1) + (byte) ((length[1] >> 0) & 0x1);
//[10,12]数据加密方式 0 表示不加密
String encryption = SocketConfig.JT808HEADER_ENCRYPT;
//[13]分包
String subpackageB = String.valueOf(subpackage);
//[14,15]保留位
String reserve = SocketConfig.JT808HEADER_RESERVE;
String msgAttributes = reserve + subpackageB + encryption + msgBodyLength;
// 消息体属性
int msgBodyAttr = Integer.parseInt(msgAttributes, 2);
return BitOperator.numToByteArray(msgBodyAttr, 2);
}
/**
* 包装808数据
*
* @param msgId 消息id
* @param phone 终端手机号
* @param msgBody 消息体
* @return
*/
public static byte[] generate808(int msgId, String phone, byte[] msgBody) {
return generate808(msgId, phone, msgBody, 0, 0, 0);
}
- 消息解码:
/**
* 解析服务器返回的808数据包
*
* @return not null有用的数据包 null 数据校验失败
*/
public static byte[] check808Data(byte[] bytes) {
//去除包头 包尾的7E标识 在去掉校验码
byte[] del7eBytes = Arrays.copyOfRange(bytes, 1, bytes.length - 2);
//获取数据上的校验码
String checkCode = HexUtil.byte2HexStr(Arrays.copyOfRange(bytes, bytes.length - 2, bytes.length - 1));
// 转成16进制字符串
String hexStr = HexUtil.byte2HexStr(del7eBytes);
// 替换 7D 02->7E 7D 01->7D
String replaceHexStr = hexStr.replaceAll(" 7D 02", " 7E")
.replaceAll(" 7D 01", " 7D")
// 最后去除空格
.replaceAll(" ", "");
byte[] data = HexUtil.hexStringToByte(replaceHexStr);
//计算校验码
String sumCode = HexUtil.getBCC(data);
if (!checkCode.equals(sumCode)) {
return null;
}
return data;
}
注册、鉴权、心跳、位置信息汇报
generate808方法就是上面的编码!!!
- 消息 ID:0x0002。 终端心跳数据消息体 为空。
- 心跳:
JTT808Coding.generate808(0x0002, phone, new byte[]{})
- 注册:
/**
* 终端注册
*
* @param manufacturerId 制造商 ID
* @param terminalModel 终端型号
* @param terminalId 终端 ID
* @return
*/
public static byte[] register(String manufacturerId, String terminalModel, String terminalId) {
//省域 ID
byte[] p = BitOperator.numToByteArray(31, 2);
//省域 市县域 ID
byte[] c = BitOperator.numToByteArray(72, 2);
//制造商 ID
byte[] mId = manufacturerId.getBytes();
//终端型号
byte[] tmId = terminalModel.getBytes();
//终端 ID
byte[] tId = terminalId.getBytes();
//车牌颜色
byte[] s = {0};
// 车辆标识
byte[] vin = "LSFAM630000000008".getBytes();
return ByteUtil.byteMergerAll(p, c, mId, tmId, tId, s,vin);
}
byte[] register = JT808Directive.register("CCCBB","ANDROID0000000000000","AAAAAAA");
byte[] body = JTT808Coding.generate808(0x0100,SocketConfig.getmPhont(),register);
socketManager.send((body));
- 鉴权 , authCode是注册返回的鉴权码:
byte[] body = JTT808Coding.generate808(0x0102,SocketConfig.getmPhont(),authCode);
socketManager.send((body));
- 上传位置信息:
/**
* 位置信息汇报
*
* @param lat 纬度
* @param lng 经度
* @param altitude 高度
* @param speed 速度
* @param bearing 角度/方向
* @param accuracy 精度
* @param time 时间
* @return
*/
public static byte[] reportLocation(double lat, double lng, double altitude, float speed, float bearing, float accuracy, String time) {
byte[] alarm = {0, 0, 0, 0};
// byte[] state = {0, 0, 0, 0};
byte[] state = stateLocationData(lat,lng);
//DWORD经纬度
double pow106 = Math.pow(10, 6);
double lat106 = lat * pow106;
double lng106 = lng * pow106;
byte[] latb = ByteUtil.longToDword(Math.round(lat106));
byte[] lngb = ByteUtil.longToDword(Math.round(lng106));
// WORD 高度 速度 方向
byte[] gaoChen = BitOperator.numToByteArray((int) altitude, 2);
byte[] speedb = BitOperator.numToByteArray((int) (speed * 3.6), 2);
byte[] orientation = BitOperator.numToByteArray((int) bearing, 2);
//bcd时间
byte[] bcdTime = BCD8421Operater.string2Bcd(time);
//位置信息附加项
byte[] additionLocation = additionLocationData(SocketConfig.getmOrderId(),(int) altitude,String.valueOf(accuracy));
return ByteUtil.byteMergerAll(alarm, state, latb, lngb, gaoChen, speedb, orientation, bcdTime,additionLocation);
}
public static byte[] reportLocation(Jt808MapLocation jtData){
return reportLocation(jtData.getLat()
,jtData.getLng()
,jtData.getAccuracy()
,jtData.getSpeed()
,jtData.getBearing()
,jtData.getAccuracy()
,jtData.getTime());
}
/**
* 位置信息状态项
*
* @param
* @return
*/
private static byte[] stateLocationData(double lat, double lng) {
String state = "00";
state = state + (lat < 0 ? "1" : "0");
state = state + (lng < 0 ? "1" : "0");
state = state + "0000000000000000000000000000";
byte[] stateByte = ByteUtil.int2DWord(Integer.parseInt(state, 2));
return stateByte;
}
/**
* 位置信息附加项
* @param order 订单号
* @param altitude 高度
* @param accuracy 精度
* @return
*/
private static byte[] additionLocationData(String order, int altitude, String accuracy) {
byte[] orderType = new byte[2]; //订单号
orderType[0] = (byte) (0xE1);
orderType[1] = ByteUtil.int2Byte(order.getBytes().length);
byte[] orderMsg = ByteUtil.byteMergerAll(orderType, order.getBytes());
byte[] altitudeType = new byte[3]; //高度的正负
altitudeType[0] = (byte) (0xE2);
altitudeType[1] = (byte) (0x01);
altitudeType[2] = (byte) (altitude < 0 ? 0x31 : 0x30);
byte[] accuracyType = new byte[2]; //订单号
accuracyType[0] = (byte) (0xE3);
accuracyType[1] = ByteUtil.int2Byte(accuracy.getBytes().length);
byte[] accuracyMsg = ByteUtil.byteMergerAll(accuracyType, accuracy.getBytes());
return ByteUtil.byteMergerAll(orderMsg, altitudeType, accuracyMsg);
}
- 批量上传位置信息:
8.12位置信息汇报 就是第3节的上传位置信息;
byte[] batchBytes = JT808Directive.batchReportLocation(locations);
byte[] body = JTT808Coding.generate808(0x0704, SocketConfig.getmPhont(), batchBytes);
socketManager.send((body));
/**
* 批量位置信息汇报
* @return
*/
public static byte[] batchReportLocation(List<byte[]> locations) {
byte[] counts = ByteUtil.int2Word(locations.size()); //数据项个数
byte[] batchType = {0}; //位置数据类型 0:正常位置批量汇报,1:盲区补报
List<byte[]> formatLocations = new ArrayList<>();
for (int i = 0; i < locations.size(); i++) {
//取出每一条的位置数据,然后拼接(位置汇报数据体长度+位置汇报数据体)
byte[] cLocation = locations.get(i);
byte[] clocationLength = ByteUtil.int2Word(cLocation.length); //位置汇报数据体长度
formatLocations.add(ByteUtil.byteMergerAll(clocationLength, cLocation));
}
byte[] batchLocations = ByteUtil.byteMergerAll(formatLocations); //位置汇报数据体
return ByteUtil.byteMergerAll(counts, batchType, batchLocations);
}
OK!
文中有大量的工具类、例如:校验码的算法、byte和int相互转换 、 bit和byte互相转换、byte和String互相转换;