1.前言
最近客户在集成基于Jt808的产品协议的时候,经常会遇到一些问题,比如没有进行转义,或者转义的时机不对,导致校验码没有进行转义。为了让大家更熟悉Jt808的指令组包,我这里整理了一下转义的步骤。
2.组包
以此应答包0x8001为例:7E 8001 0005 413050530988 0001 0009 0200 00 7D01 7E
组包过程如下:
2.1.第一步
我们先封装消息体,包含:应答流水号+应答消息ID+应答结果,即:0009 0200 00
2.2.第二步
我们封装需要计算校验码的消息体部分:包含:应答消息ID:8001 + 消息体属性(长度):0005 + 设备S/N:413050530988 + 消息流水号:00001 + 第一步中的消息体(应答流水号+应答消息ID+应答结果):0009 0200 00;
即:8001 0005 413050530988 0001 0009 0200 00
2.3.第三步
计算校验码,将第二步所有消息进行累加和,得到的结果为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;}
2.4.第四步
将第二步的消息体与第三步得到的校验码组成完整的消息包:8001 0005 413050530988 0001 0009 0200 00 7D
2.5.第五步
对完整的消息包进行转义,即: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);}}}
2.6.第六步
增加包头包尾的7E,到此给设备应答的消息包组包完毕,最终下发给设备的消息体应该是:7E80010005413050530988000100090200007D017E
3.最后附上我下发指令的完整代码
3.1.封装平台通用应答
/*** 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);}
3.2.封装指令下发到设备
/*** 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;}
3.3.最后组成完整的包,并将指令下发给设备
/*** <p>Description: JT808 message entity encoder</p>** @author Mr.Li* @date 2022-10-20*/
@Slf4j
public class Jt808ProtocolEncoder extends MessageToByteEncoder<Jt808Message> {@Overrideprotected void encode(ChannelHandlerContext ctx, Jt808Message msg, ByteBuf out) throws Exception {ByteBuf msgBody = msg.getMsgBody();int msgBodyLen = msgBody == null ? 0 : msgBody.readableBytes();//Protocol TypeProtocolEnum protocolType = msg.getProtocolType();//the length of remove the tip and tailint 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 bytesif (msg.isMultiPacket()) {contentLen = contentLen + 4;}ByteBuf bodyBuf = ByteBufAllocator.DEFAULT.heapBuffer(contentLen);try {//message idbodyBuf.writeShort(msg.getMsgId());//The length of the message body in the message body propertyint msgBodyAttr = msgBodyLen | (msg.getEncryptType() << 10);//The subcontract identity in the message body propertyif (msg.isMultiPacket()) {msgBodyAttr = msgBodyAttr | 0b00100000_00000000;}//The Jt808-2019 version adds the version identifier and protocol version numberif (protocolType == ProtocolEnum.JT808_2019) {//Version identificationmsgBodyAttr = msgBodyAttr | 0b01000000_00000000;//message body propertybodyBuf.writeShort(msgBodyAttr);//Protocol version NumberbodyBuf.writeByte(msg.getProtocolVersion());} else {//message body propertiesbodyBuf.writeShort(msgBodyAttr);}//terminal numberbodyBuf.writeBytes(msg.getPhoneNumberArr());//Message serial numberbodyBuf.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 bodyif (msgBodyLen > 0) {bodyBuf.writeBytes(msgBody);}//check codeint checkCode = CommonUtil.xor(bodyBuf);bodyBuf.writeByte(checkCode);//message headerout.writeByte(Jt808Constant.MSG_HEAD_TAIL_FLAG);//The read index is reset to the starting positionbodyBuf.readerIndex(0);//escapeJt808PacketUtil.escape(out, bodyBuf);//message tailout.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 bodyif (msgBody != null) {ReferenceCountUtil.release(msgBody);}//release bodyBufReferenceCountUtil.release(bodyBuf);}}
}
欢迎对物联网感兴趣的朋友加我微信交流学习。