加群联系作者vx:xiaoda0423
仓库地址:webvueblog.github.io/JavaPlusDoc...
1. JT808消息结构
消息格式如下:
scss
| 起始标识(0x7E) | 消息头 | 消息体 | 校验码 | 结束标识(0x7E) |
- 消息头:包含消息ID、消息体属性(长度、加密等)、终端手机号、消息流水号等。
- 消息体:根据消息ID定义的具体业务数据(如位置、心跳等)。
- 校验码:从消息头到消息体的异或校验。
2. 编码过程(Java对象 → 字节流)
步骤:
- 构造消息头和消息体
- 合并并转义处理(0x7E → 0x7D 0x02,0x7D → 0x7D 0x01)
- 计算校验码(异或校验)
- 添加起始和结束标识
示例代码:
ini
public class JT808Encoder {
// 转义处理
private byte[] escape(byte[] data) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (byte b : data) {
if (b == 0x7D) {
out.write(0x7D);
out.write(0x01);
} else if (b == 0x7E) {
out.write(0x7D);
out.write(0x02);
} else {
out.write(b);
}
}
return out.toByteArray();
}
// 计算校验码
private byte calculateCheckCode(byte[] data) {
byte checkCode = 0;
for (byte b : data) {
checkCode ^= b;
}
return checkCode;
}
public byte[] encode(JT808Message message) {
ByteBuffer buffer = ByteBuffer.allocate(1024)
.order(ByteOrder.BIG_ENDIAN);
// 1. 构造消息头
buffer.putShort(message.getMsgId());
buffer.putShort(message.getMsgBodyProps());
buffer.put(BcdUtil.strToBcd(message.getTerminalId()));
buffer.putShort(message.getFlowId());
// 2. 构造消息体
buffer.put(message.getMsgBody());
// 3. 合并头体并转义
byte[] headerBody = Arrays.copyOf(buffer.array(), buffer.position());
byte[] escapedData = escape(headerBody);
// 4. 添加起始符、校验码、结束符
ByteArrayOutputStream finalPacket = new ByteArrayOutputStream();
finalPacket.write(0x7E);
finalPacket.writeBytes(escapedData);
finalPacket.write(calculateCheckCode(headerBody));
finalPacket.write(0x7E);
return finalPacket.toByteArray();
}
}
3. 解码过程(字节流 → Java对象)
步骤:
- 去除起始和结束标识
- 反转义处理
- 验证校验码
- 解析消息头和消息体
示例代码:
ini
public class JT808Decoder {
// 反转义处理
private byte[] unescape(byte[] data) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int i = 0; i < data.length; i++) {
if (data[i] == 0x7D) {
if (i + 1 < data.length) {
if (data[i + 1] == 0x01) {
out.write(0x7D);
} else if (data[i + 1] == 0x02) {
out.write(0x7E);
}
i++;
}
} else {
out.write(data[i]);
}
}
return out.toByteArray();
}
public JT808Message decode(byte[] rawData) {
// 1. 去除起始和结束符
int length = rawData.length;
byte[] data = Arrays.copyOfRange(rawData, 1, length - 2);
// 2. 反转义
byte[] unescapedData = unescape(data);
// 3. 校验码验证
byte receivedCheckCode = rawData[length - 2];
byte calculatedCheckCode = calculateCheckCode(unescapedData);
if (receivedCheckCode != calculatedCheckCode) {
throw new RuntimeException("校验失败");
}
ByteBuffer buffer = ByteBuffer.wrap(unescapedData)
.order(ByteOrder.BIG_ENDIAN);
// 4. 解析消息头
JT808Message message = new JT808Message();
message.setMsgId(buffer.getShort());
message.setMsgBodyProps(buffer.getShort());
byte[] terminalIdBytes = new byte[6];
buffer.get(terminalIdBytes);
message.setTerminalId(BcdUtil.bcdToStr(terminalIdBytes));
message.setFlowId(buffer.getShort());
// 5. 解析消息体(根据MsgId选择不同解析方式)
int bodyLength = message.getMsgBodyLength(); // 从msgBodyProps中获取长度
byte[] msgBody = new byte[bodyLength];
buffer.get(msgBody);
message.setMsgBody(parseMsgBody(message.getMsgId(), msgBody));
return message;
}
private Object parseMsgBody(short msgId, byte[] bodyData) {
switch (msgId) {
case 0x0200: // 位置汇报
return parseLocation(bodyData);
case 0x0100: // 终端注册
return parseRegistration(bodyData);
// 其他消息类型...
default:
return null;
}
}
private Location parseLocation(byte[] data) {
// 解析经纬度、速度、状态等
// 示例:解析纬度(4字节,1/10^6度)
ByteBuffer buffer = ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN);
int lat = buffer.getInt();
return new Location(lat / 1_000_000.0);
}
}
4. 关键工具类
BCD编码工具:
arduino
public class BcdUtil {
public static byte[] strToBcd(String s) {
if (s.length() % 2 != 0) s = "0" + s;
byte[] bytes = new byte[s.length() / 2];
for (int i = 0; i < s.length(); i += 2) {
bytes[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
| Character.digit(s.charAt(i + 1), 16);
}
return bytes;
}
public static String bcdToStr(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02X", b));
}
return sb.toString();
}
}
5. 注意事项
- 字节序:协议规定使用大端序(Big-Endian)。
- 转义处理:避免遗漏0x7D和0x7E的转义。
- 校验码:必须验证校验码以确保数据完整性。
- 消息体解析:根据不同的消息ID动态解析数据(如0x0200为位置信息)。
通过以上步骤,可完成JT808协议消息的Java编解码。实际应用中需结合协议文档处理具体字段。