最近客户在集成基于Jt808的产品协议的时候,经常会遇到一些问题,比如没有进行转义,或者转义的时机不对,导致校验码没有进行转义。为了让大家更熟悉Jt808的指令组包,我这里整理了一下转义的步骤。
以此应答包0x8001为例:7E 8001 0005 413050530988 0001 0009 0200 00 7D01 7E
组包过程如下:
我们先封装消息体,包含:应答流水号+应答消息ID+应答结果,即:0009 0200 00
我们封装需要计算校验码的消息体部分:包含:应答消息ID:8001 + 消息体属性(长度):0005 + 设备S/N:413050530988 + 消息流水号:00001 + 第一步中的消息体(应答流水号+应答消息ID+应答结果):0009 0200 00;
即:8001 0005 413050530988 0001 0009 0200 00
计算校验码,将第二步所有消息进行累加和,得到的结果为7D,可参考下面的方法
/**
* XOR every byte
*
* @param buf (第二步得到的Buf)
* @return 输出校验码,如果以上述为例得到的校验码为:7D
*/
public static int xor(ByteBuf buf) {
int checksum = 0;
while (buf.readableBytes() > 0) {
checksum ^= buf.readUnsignedByte();
}
return checksum;
}
将第二步的消息体与第三步得到的校验码组成完整的消息包:8001 0005 413050530988 0001 0009 0200 00 7D
对完整的消息包进行转义,即:7E——>7D02;7D——>7D01,可采用下面的方法;最终我们得到转义后的消息为:8001 0005 413050530988 0001 0009 0200 00 7D01
/**
* In the message header, message body and check code, 0x7E is escaped as 0x7D 0x02, and 0x7D is escaped as 0x7D 0x01
*
* @param out 待输出转义后的消息体
* @param bodyBuf 第四步中的消息内容
*/
public static void escape(ByteBuf out, ByteBuf bodyBuf) {
while (bodyBuf.readableBytes() > 0) {
int b = bodyBuf.readUnsignedByte();
if (b == 0x7E) {
out.writeShort(0x7D02);
} else if (b == 0x7D) {
out.writeShort(0x7D01);
} else {
out.writeByte(b);
}
}
}
增加包头包尾的7E,到此给设备应答的消息包组包完毕,最终下发给设备的消息体应该是:7E80010005413050530988000100090200007D017E
/**
* Platform general response
*
* @param ctx
* @param jt808Msg
* @param replyResultEnum
* @return
*/
public static ChannelFuture reply8001(ChannelHandlerContext ctx, Jt808Message jt808Msg, Jt808ReplyResultEnum replyResultEnum) {
ByteBuf msgBody = Unpooled.buffer(5);
msgBody.writeShort(jt808Msg.getMsgFlowId());
msgBody.writeShort(jt808Msg.getMsgId());
msgBody.writeByte(replyResultEnum.getValue());
return sendToTerminal(ctx, Jt808MessageIdEnum.MSG_8001.getMessageId(), jt808Msg.getProtocolType(), jt808Msg.getProtocolVersion(), jt808Msg.getPhoneNumberArr(), msgBody);
}
/**
* Send message to terminal
*
* @param ctx
* @param messageId
* @param protocolType
* @param protocolFlag
* @param phoneNumberArr
* @param msgBody
* @return
*/
private static ChannelFuture sendToTerminal(ChannelHandlerContext ctx, int messageId, ProtocolEnum protocolType, int protocolFlag, byte[] phoneNumberArr, ByteBuf msgBody) {
Jt808Message replyMsg = new Jt808Message();
replyMsg.setMsgId(messageId);
replyMsg.setProtocolType(protocolType);
replyMsg.setVersionFlag(protocolType == ProtocolEnum.JT808_2019 ? 1 : 0);
replyMsg.setProtocolVersion(protocolFlag);
replyMsg.setPhoneNumberArr(phoneNumberArr);
replyMsg.setMsgFlowId(SessionUtil.getNextMsgFlowId(ctx));
replyMsg.setMsgBody(msgBody);
ChannelFuture future = ctx.writeAndFlush(replyMsg);
future.addListener((ChannelFutureListener) channelFuture -> {
if (!channelFuture.isSuccess()) {
log.error("Sending data exception", channelFuture.cause());
}
});
return future;
}
/**
* Description: JT808 message entity encoder
*
* @author Mr.Li
* @date 2022-10-20
*/
@Slf4j
public class Jt808ProtocolEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, Jt808Message msg, ByteBuf out) throws Exception {
ByteBuf msgBody = msg.getMsgBody();
int msgBodyLen = msgBody == null ? 0 : msgBody.readableBytes();
//Protocol Type
ProtocolEnum protocolType = msg.getProtocolType();
//the length of remove the tip and tail
int contentLen = protocolType == ProtocolEnum.JT808_2019 ? Jt808Constant.JT2019_MSG_BASE_LENGTH + msgBodyLen - 2 : Jt808Constant.MSG_BASE_LENGTH + msgBodyLen - 2;
//Subcontracting is required to add the total number of messages and packet number 4 bytes
if (msg.isMultiPacket()) {
contentLen = contentLen + 4;
}
ByteBuf bodyBuf = ByteBufAllocator.DEFAULT.heapBuffer(contentLen);
try {
//message id
bodyBuf.writeShort(msg.getMsgId());
//The length of the message body in the message body property
int msgBodyAttr = msgBodyLen | (msg.getEncryptType() << 10);
//The subcontract identity in the message body property
if (msg.isMultiPacket()) {
msgBodyAttr = msgBodyAttr | 0b00100000_00000000;
}
//The Jt808-2019 version adds the version identifier and protocol version number
if (protocolType == ProtocolEnum.JT808_2019) {
//Version identification
msgBodyAttr = msgBodyAttr | 0b01000000_00000000;
//message body property
bodyBuf.writeShort(msgBodyAttr);
//Protocol version Number
bodyBuf.writeByte(msg.getProtocolVersion());
} else {
//message body properties
bodyBuf.writeShort(msgBodyAttr);
}
//terminal number
bodyBuf.writeBytes(msg.getPhoneNumberArr());
//Message serial number
bodyBuf.writeShort(msg.getMsgFlowId());
//Package item of message (subcontracting needs to add the total number of message packets and packet number)
if (msg.isMultiPacket()) {
bodyBuf.writeShort(msg.getPacketTotalCount());
bodyBuf.writeShort(msg.getPacketOrder());
}
//message body
if (msgBodyLen > 0) {
bodyBuf.writeBytes(msgBody);
}
//check code
int checkCode = CommonUtil.xor(bodyBuf);
bodyBuf.writeByte(checkCode);
//message header
out.writeByte(Jt808Constant.MSG_HEAD_TAIL_FLAG);
//The read index is reset to the starting position
bodyBuf.readerIndex(0);
//escape
Jt808PacketUtil.escape(out, bodyBuf);
//message tail
out.writeByte(Jt808Constant.MSG_HEAD_TAIL_FLAG);
log.info("downlink command:{}", ByteBufUtil.hexDump(out));
} catch (Exception e) {
log.error("{}message encoding exception,message:{}", protocolType, msg, e);
} finally {
//release message body
if (msgBody != null) {
ReferenceCountUtil.release(msgBody);
}
//release bodyBuf
ReferenceCountUtil.release(bodyBuf);
}
}
}
欢迎对物联网感兴趣的朋友加我微信交流学习。