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;
}
@Override
protected 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 0x01
message.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 ID
int 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 2
frame.readByte();
//CRC-16
frame.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);
}
}
}
}
专注各种物联网网关开发,有兴趣的朋友可以加一起交流学习。