Teltonika FMXXX系列定位器解析说明

38 篇文章 1 订阅
24 篇文章 2 订阅

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);
            }
        }
    }
}

专注各种物联网网关开发,有兴趣的朋友可以加一起交流学习。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鱼>

一分也是爱

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值