1.产品外观
2.数据包说明
2.1.登录包
**模块第一次上线,先发送其对应的IMEI号,数据包如下:**
000F313233343536373839303132333435**数据包解析如下:**
000F --packet starts
313233343536373839303132333435 --IMEI 123456789012345**服务器收到此模块IMEI后,如果允许上线,则回复01,否则回复00**
2.2.定位数据包Codec 8
**模块运行上线后,上报定位数据包Codec 8**
000000000000003608010000016B40D8EA30010000000000000000000000000000000105021503010101425E0F01F10000601A014E0000000000000000010000C7CF**数据包解析如下:**
00 00 00 00 --packet starts
00 00 00 36 --Data field length
08 --Codec ID(Codec 8)
01 --Priority(0:Low;1:High;2:Panic;3:Security)
00 00 01 6B 40 D8 EA 30 --Timestamp
01 --Number of Data 1
00 00 00 00 --Longitude
00 00 00 00 --Latitude
00 00 --Altitude(In meters above sea level)
00 00 --Angle(In degrees, 0 is north, increasing clock-wise)
00 --Satellites(Number of visible satellites)
00 00 --Speed (Speed in km/h)
01 --Event IO ID – if data is acquired on event – this field defines which IO property has changed and generated an event. If data cause is not event – the value is 0.
05 --total number of properties coming with record (N=N1+N2+N4+N8)
02 --N1 number of properties, which length is 1 byte
15 03
01 01
01 --N2 number of properties, which length is 2 bytes
42 5E0F
01 --N4 number of properties, which length is 4 bytes
F1 0000601A
01 --N8 number of properties, which length is 8 bytes
4E 0000000000000000
01 --Number of Data2(This number must be the same as "number of Data 1")
0000C7CF --CRC-16**服务器收到数据后,应答收到的数据条数,对应的是数据包中的Number of Data,应答4个字节**
00000001
3.代码实例
服务端收到数据包,先进行初步解包:
package com.gnss.teltonika.netty.codec;import com.gnss.common.utils.SessionUtil;
import com.gnss.core.constants.ProtocolEnum;
import com.gnss.core.proto.TerminalProto;
import com.gnss.teltonika.model.TeltonikaMessage;
import com.gnss.teltonika.utils.PacketUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;import java.util.List;/*** Preprocess the received data* @author Mr.Li* @date 2024-07-25*/
@Slf4j
public class ProtocolDecoder extends ByteToMessageDecoder {private ProtocolEnum protocolType;public ProtocolDecoder(ProtocolEnum protocolType) {this.protocolType = protocolType;}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {TerminalProto terminalProto= SessionUtil.getTerminalInfo(ctx);if(terminalProto!=null) {log.info("received:{}:{}", terminalProto.getTerminalNum(), ByteBufUtil.hexDump(in));}else {log.info("received:{}", ByteBufUtil.hexDump(in));}Object decoded = null;in.markReaderIndex();if(in.readUnsignedShort()==0x000F) {in.resetReaderIndex();decoded = decodeLogin(in);}else{in.resetReaderIndex();Object obj = PacketUtil.decodePacket(in);if (obj != null) {decoded = decodeMessage(ctx,(ByteBuf) obj);}}if (decoded != null) {out.add(decoded);}}/*** Login package* @param frame* @return*/private TeltonikaMessage decodeLogin(ByteBuf frame){int msgLen=frame.readUnsignedShort();byte[] terminalNumArr=new byte[msgLen];frame.readBytes(terminalNumArr);String terminalNum=new String(terminalNumArr);TeltonikaMessage message=new TeltonikaMessage();//Give the login frame a default message ID of 0x01message.setMsgId(0x01);message.setMsgLen(msgLen);message.setTerminalNum(terminalNum);message.setTerminalNumArr(terminalNumArr);message.setProtocolType(protocolType);return message;}/*** Preliminary analysis of the message body to obtain basic information of the device* @param frame* @return*/private TeltonikaMessage decodeMessage(ChannelHandlerContext ctx,ByteBuf frame){frame.readInt();long bodyLen=frame.readUnsignedInt();//Codec IDint msgId=frame.readUnsignedByte();//记录条数int records=frame.readUnsignedByte();//size is calculated starting from Codec ID to Number of Data 2.byte[] bodyArr=new byte[(int)bodyLen-3];frame.readBytes(bodyArr);//Number Of Data 2frame.readByte();//CRC-16frame.readInt();String terminalNum = SessionUtil.getTerminalInfo(ctx).getTerminalNum();TeltonikaMessage message=new TeltonikaMessage();message.setMsgId(msgId);message.setMsgLen(bodyLen);message.setTerminalNum(terminalNum);message.setTerminalNumArr(terminalNum.getBytes());message.setProtocolType(protocolType);message.setMsgBodyArr(bodyArr);message.setMsgBody(Unpooled.wrappedBuffer(bodyArr));message.setDataNum(records);return message;}
}
然后根据不同的数据包ID进行详细解码Codec 8
package com.gnss.teltonika.utils;import com.gnss.core.proto.LocationProto;
import com.gnss.core.proto.TerminalProto;
import com.gnss.teltonika.model.TeltonikaMessage;
import io.netty.buffer.ByteBuf;import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;/*** Decoding of positioning data class* @author Mr.Li* @date 2024-07-26*/
public class LocationParser {/*** Decoding of positioning data* @param msg* @param terminalProto* @param msgBodyBuf* @return*/public static List<LocationProto> parseLocation(TeltonikaMessage msg, TerminalProto terminalProto, ByteBuf msgBodyBuf){List<LocationProto> locationProtoList=new ArrayList<>();while (msgBodyBuf.readableBytes()>24){long gpsTimestamp=msgBodyBuf.readLong();int priority=msgBodyBuf.readUnsignedByte();double lon=msgBodyBuf.readInt()/10000000.0;double lat=msgBodyBuf.readInt()/10000000.0;int altitude=msgBodyBuf.readUnsignedShort();int direction=msgBodyBuf.readUnsignedShort();//卫星个数int gpsSignal=msgBodyBuf.readUnsignedByte();int speed=msgBodyBuf.readUnsignedShort();int locationType=1;if(lon==0||lat==0) {locationType=0;}LocationProto locationProto=new LocationProto();if(msg.getMsgId()==0x08) {int eventIO = msgBodyBuf.readUnsignedByte();int totalIO = msgBodyBuf.readUnsignedByte();parseIO08(locationProto, msgBodyBuf, totalIO);}else if(msg.getMsgId()==0x8E||msg.getMsgId()==0x10){int eventIO = msgBodyBuf.readUnsignedShort();int totalIO = msgBodyBuf.readUnsignedShort();parseIO8E(locationProto, msgBodyBuf, totalIO);}//当前时间ZonedDateTime currentDateTime = ZonedDateTime.now(ZoneOffset.UTC);locationProto.setTerminalId(terminalProto.getTerminalId());locationProto.setTerminalNum(terminalProto.getTerminalNum());locationProto.setTerminalType(terminalProto.getTerminalType());locationProto.setVehicleId(terminalProto.getVehicleId());locationProto.setVehicleNum(terminalProto.getVehicleNum());locationProto.setGnssTime(ZonedDateTime.ofInstant(Instant.ofEpochMilli(gpsTimestamp), ZoneOffset.UTC).toString());locationProto.setRecvTime(currentDateTime.toString());locationProto.setGnssTimestamp(gpsTimestamp);locationProto.setRecvTimestamp(currentDateTime.toInstant().toEpochMilli());locationProto.setLocationType(locationType);locationProto.setLat(lat);locationProto.setLon(lon);locationProto.setSpeed(speed*1.0);locationProto.setDirection(direction);locationProto.setGnssValue(gpsSignal);locationProto.setBattery(-1);locationProto.setAltitude(altitude);locationProtoList.add(locationProto);}return locationProtoList;}/*** Analyze the IO content* @param locationProto* @param msgBodyBuf* @param totalIO*/private static void parseIO08(LocationProto locationProto,ByteBuf msgBodyBuf,int totalIO) {int n1Count=msgBodyBuf.readUnsignedByte();for(int n1=0;n1<n1Count;n1++){int avlID=msgBodyBuf.readUnsignedByte();int value=msgBodyBuf.readUnsignedByte();if(avlID==0x15){locationProto.setGsmValue(value);}}int n2Count =0;if(totalIO>n1Count && msgBodyBuf.readableBytes()>0) {n2Count = msgBodyBuf.readUnsignedByte();for(int n2=0;n2<n2Count;n2++){int avlID=msgBodyBuf.readUnsignedByte();int value=msgBodyBuf.readUnsignedShort();}}int n4Count =0;if(totalIO>(n1Count+n2Count) && msgBodyBuf.readableBytes()>0) {n4Count = msgBodyBuf.readUnsignedByte();for(int n4=0;n4<n4Count;n4++){int avlID=msgBodyBuf.readUnsignedByte();long value=msgBodyBuf.readUnsignedInt();if(avlID==0x10) {locationProto.setMileage(value*1.0);}}}if(totalIO>(n1Count+n2Count+n4Count) && msgBodyBuf.readableBytes()>0) {int n8Count = msgBodyBuf.readUnsignedByte();for(int n8=0;n8<n8Count;n8++){int avlID=msgBodyBuf.readUnsignedByte();long value=msgBodyBuf.readLong();}}}/*** Analyze the IO content* @param locationProto* @param msgBodyBuf* @param totalIO*/private static void parseIO8E(LocationProto locationProto,ByteBuf msgBodyBuf,int totalIO) {int n1Count=msgBodyBuf.readUnsignedShort();for(int n1=0;n1<n1Count;n1++){int avlID=msgBodyBuf.readUnsignedShort();int value=msgBodyBuf.readUnsignedByte();if(avlID==21){locationProto.setGsmValue(value);}}int n2Count =0;if(totalIO>n1Count && msgBodyBuf.readableBytes()>0) {n2Count = msgBodyBuf.readUnsignedShort();for(int n2=0;n2<n2Count;n2++){int avlID=msgBodyBuf.readUnsignedShort();int value=msgBodyBuf.readUnsignedShort();}}int n4Count =0;if(totalIO>(n1Count+n2Count) && msgBodyBuf.readableBytes()>0) {n4Count = msgBodyBuf.readUnsignedShort();for(int n4=0;n4<n4Count;n4++){int avlID=msgBodyBuf.readUnsignedShort();long value=msgBodyBuf.readUnsignedInt();if(avlID==0x10) {locationProto.setMileage(value*1.0);}}}int n8Count =0;if(totalIO>(n1Count+n2Count+n4Count) && msgBodyBuf.readableBytes()>0) {n8Count = msgBodyBuf.readUnsignedShort();for(int n8=0;n8<n8Count;n8++){int avlID=msgBodyBuf.readUnsignedShort();long value=msgBodyBuf.readLong();}}if(totalIO>(n1Count+n2Count+n4Count+n8Count) && msgBodyBuf.readableBytes()>0) {int nxCount = msgBodyBuf.readUnsignedShort();for(int nx=0;nx<nxCount;nx++){int length=msgBodyBuf.readUnsignedShort();byte[] nxArr=new byte[length];msgBodyBuf.readBytes(nxArr);}}}
}
专注各种物联网网关开发,有兴趣的朋友可以加一起交流学习。