VIN not showing for TOPFLYtech TorchX 110

Anton Tananaev 25 days ago

And where's the message type 09 here?

Darkking 25 days ago

So that means if the GPS device sends the message type 09 in the initial session, then only Traccar will be able to decode it?

Because I have another session of data which is around 900 lines. In that session connect is there but disconnect was not there. In that session, the device did send 09 type message on line number 213.

I cant put that huge session log here. If you like, to analyze it then I can send it to you by mail or any of your prefered method.

Anton Tananaev 25 days ago

Just upload it somewhere. Obviously we need a session that the data you're talking about, right?

Darkking 24 days ago

I have marked the 09 message type in this file. Please check and let me know.

https://docs.google.com/spreadsheets/d/1TVFmoMAN2qlUyG6mgMHcHr0DD_BmrsXN2HVRA7rNmZA/edit?usp=sharing

Anton Tananaev 24 days ago

It's not decoded, from what I can see in your log.

Darkking 24 days ago

Did you find the protocol doc I had shared by mail?

Is there any chance you can make changes to Traccar so it reads the VIN?

Anton Tananaev 24 days ago

I want to hear the explanation for your original post first. You implied that something wasn't decoding correctly. So far I don't see any evidence of that. Can you please provide some supporting information for your original claims.

Darkking 24 days ago

2026-03-02 23_50_25-WhatsApp.png

In the above screenshot, I had another device that works on Huabao protocol which is able to show VIN. I have set up VIN to show in Pop up information.

2026-03-02 23_42_37-Settings.png

This is the screenshot for TOPFLYtech TorchX 110 device where VIN number is not showing in popup message. Both the devices are connected to the same Traccar server.

If you have a test server then please let me know I will set my topfly device to your server. You can check as it was not showing the VIN.

Anton Tananaev 24 days ago

I don't think that clarifies anything. Please read your original post.

Darkking 24 days ago

To summarise my original post,

  1. My Device is using t800x protocol.
  2. It was sending telemetric data, which was decoded by Traccar and showing position on the map.
  3. The engine data, which is coming from OBD2 was also showing on the server as popup message.
  4. When I check the session logs I can see there was VIN data in full format and in partial format. Hence, I was guessing maybe that was creating an issue for the VIN to be displayed on the server. This was a pure assumption on my side.

That is what I was just presenting my findings and assumptions to you so that it might help you to identify the issue. In case if it was represented as anything other than the above, then I apologise for that.

Anton Tananaev 23 days ago

The issue is very simple - it's not supported at all currently. You can submit a feature request on GitHub if you want. If you're interested in sponsoring the work, you can also email us.

Darkking 20 days ago

I did some changes to T800xProtocolDecoder.java file and able to read the VIN from the 09 message. You can check the code and publish it if you like so. Thanks for your support.

/*
 * Copyright 2015 - 2023 Anton Tananaev (anton@traccar.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.traccar.protocol;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import org.traccar.BaseProtocolDecoder;
import org.traccar.config.Keys;
import org.traccar.helper.model.AttributeUtil;
import org.traccar.session.DeviceSession;
import org.traccar.NetworkMessage;
import org.traccar.Protocol;
import org.traccar.helper.BcdUtil;
import org.traccar.helper.BitUtil;
import org.traccar.helper.DateBuilder;
import org.traccar.helper.UnitsConverter;
import org.traccar.model.CellTower;
import org.traccar.model.Network;
import org.traccar.model.Position;

import java.math.BigInteger;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class T800xProtocolDecoder extends BaseProtocolDecoder {

    private short header = DEFAULT_HEADER;

    public short getHeader() {
        return header;
    }

    public T800xProtocolDecoder(Protocol protocol) {
        super(protocol);
    }

    public static final short DEFAULT_HEADER = 0x2323;

    public static final int MSG_LOGIN = 0x01;
    public static final int MSG_GPS = 0x02;
    public static final int MSG_HEARTBEAT = 0x03;
    public static final int MSG_ALARM = 0x04;
    public static final int MSG_NETWORK = 0x05; // 0x2727
    public static final int MSG_DRIVER_BEHAVIOR_1 = 0x05; // 0x2626
    public static final int MSG_DRIVER_BEHAVIOR_2 = 0x06; // 0x2626
    public static final int MSG_OBD = 0x09;
    public static final int MSG_BLE = 0x10;
    public static final int MSG_NETWORK_2 = 0x11;
    public static final int MSG_GPS_2 = 0x13;
    public static final int MSG_ALARM_2 = 0x14;
    public static final int MSG_COMMAND = 0x81;

    private void sendResponse(Channel channel, short header, int type, int index, ByteBuf imei, int alarm) {
        if (channel != null) {
            ByteBuf response = Unpooled.buffer(alarm > 0 ? 16 : 15);
            response.writeShort(header);
            response.writeByte(type);
            response.writeShort(response.capacity()); // length
            response.writeShort(index);
            response.writeBytes(imei);
            if (alarm > 0) {
                response.writeByte(alarm);
            }
            channel.writeAndFlush(new NetworkMessage(response, channel.remoteAddress()));
        }
    }

    private String decodeAlarm1(int value) {
        return switch (value) {
            case 1 -> Position.ALARM_POWER_CUT;
            case 2 -> Position.ALARM_LOW_BATTERY;
            case 3 -> Position.ALARM_SOS;
            case 4 -> Position.ALARM_OVERSPEED;
            case 5 -> Position.ALARM_GEOFENCE_ENTER;
            case 6 -> Position.ALARM_GEOFENCE_EXIT;
            case 7 -> Position.ALARM_TOW;
            case 8, 10 -> Position.ALARM_VIBRATION;
            case 21 -> Position.ALARM_JAMMING;
            case 23 -> Position.ALARM_POWER_RESTORED;
            case 24 -> Position.ALARM_LOW_POWER;
            default -> null;
        };
    }

    private String decodeAlarm2(int value) {
        return switch (value) {
            case 1, 4 -> Position.ALARM_REMOVING;
            case 2 -> Position.ALARM_TAMPERING;
            case 3 -> Position.ALARM_SOS;
            case 5 -> Position.ALARM_FALL_DOWN;
            case 6 -> Position.ALARM_LOW_BATTERY;
            case 14 -> Position.ALARM_GEOFENCE_ENTER;
            case 15 -> Position.ALARM_GEOFENCE_EXIT;
            default -> null;
        };
    }

    private Date readDate(ByteBuf buf) {
        return new DateBuilder()
                .setYear(BcdUtil.readInteger(buf, 2))
                .setMonth(BcdUtil.readInteger(buf, 2))
                .setDay(BcdUtil.readInteger(buf, 2))
                .setHour(BcdUtil.readInteger(buf, 2))
                .setMinute(BcdUtil.readInteger(buf, 2))
                .setSecond(BcdUtil.readInteger(buf, 2))
                .getDate();
    }

    @Override
    protected Object decode(
            Channel channel, SocketAddress remoteAddress, Object msg) throws Exception {

        ByteBuf buf = (ByteBuf) msg;

        header = buf.readShort();
        int type = buf.readUnsignedByte();
        buf.readUnsignedShort(); // length
        int index = buf.readUnsignedShort();
        ByteBuf imei = buf.readSlice(8);

        DeviceSession deviceSession = getDeviceSession(
                channel, remoteAddress, ByteBufUtil.hexDump(imei).substring(1));
        if (deviceSession == null) {
            return null;
        }

        boolean positionType = type == MSG_GPS || type == MSG_GPS_2 || type == MSG_ALARM || type == MSG_ALARM_2;
        if (!positionType) {
            sendResponse(channel, header, type, header == 0x2323 ? 1 : index, imei, 0);
        }

        if (positionType) {

            return decodePosition(channel, deviceSession, buf, type, index, imei);

        } else if (type == MSG_NETWORK && header == 0x2727 || type == MSG_NETWORK_2) {

            Position position = new Position(getProtocolName());
            position.setDeviceId(deviceSession.getDeviceId());

            getLastLocation(position, readDate(buf));

            position.set(Position.KEY_OPERATOR, buf.readCharSequence(
                    buf.readUnsignedByte(), StandardCharsets.UTF_16LE).toString());
            position.set("networkTechnology", buf.readCharSequence(
                    buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString());
            position.set("networkBand", buf.readCharSequence(
                    buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString());
            buf.readCharSequence(buf.readUnsignedByte(), StandardCharsets.US_ASCII); // imsi
            position.set(Position.KEY_ICCID, buf.readCharSequence(
                    buf.readUnsignedByte(), StandardCharsets.US_ASCII).toString());

            return position;

        } else if ((type == MSG_DRIVER_BEHAVIOR_1 || type == MSG_DRIVER_BEHAVIOR_2) && header == 0x2626) {

            Position position = new Position(getProtocolName());
            position.setDeviceId(deviceSession.getDeviceId());

            switch (buf.readUnsignedByte()) {
                case 0, 4 -> position.addAlarm(Position.ALARM_BRAKING);
                case 1, 3, 5 -> position.addAlarm(Position.ALARM_ACCELERATION);
                case 2 -> {
                    if (type == MSG_DRIVER_BEHAVIOR_1) {
                        position.addAlarm(Position.ALARM_BRAKING);
                    } else {
                        position.addAlarm(Position.ALARM_CORNERING);
                    }
                }
            }

            position.setTime(readDate(buf));

            if (type == MSG_DRIVER_BEHAVIOR_2) {
                int status = buf.readUnsignedByte();
                position.setValid(!BitUtil.check(status, 7));
                buf.skipBytes(5); // acceleration
            } else {
                position.setValid(true);
            }

            position.setAltitude(buf.readFloatLE());
            position.setLongitude(buf.readFloatLE());
            position.setLatitude(buf.readFloatLE());
            position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) * 0.1));
            position.setCourse(buf.readUnsignedShort());

            position.set(Position.KEY_RPM, buf.readUnsignedShort());

            return position;

        } else if (type == MSG_OBD) {

            Position position = new Position(getProtocolName());
            position.setDeviceId(deviceSession.getDeviceId());

            getLastLocation(position, null);

            String payload = buf.toString(buf.readerIndex(), buf.readableBytes(), StandardCharsets.US_ASCII);

            Matcher matcher = Pattern.compile("[A-HJ-NPR-Z0-9]{17}").matcher(payload);

            if (matcher.find()) {
                position.set(Position.KEY_VIN, matcher.group());
                return position;
            }

            return null;

        } else if (type == MSG_BLE) {

            return decodeBle(channel, deviceSession, buf, type, index, imei);

        } else if (type == MSG_COMMAND) {

            Position position = new Position(getProtocolName());
            position.setDeviceId(deviceSession.getDeviceId());

            getLastLocation(position, null);

            buf.readUnsignedByte(); // protocol number

            position.set(Position.KEY_RESULT, buf.toString(StandardCharsets.UTF_16LE));

            return position;

        }

        return null;
    }

    private double decodeBleTemp(ByteBuf buf) {
        int value = buf.readUnsignedShort();
        return (BitUtil.check(value, 15) ? -BitUtil.to(value, 15) : BitUtil.to(value, 15)) * 0.01;
    }

    private Position decodeBle(
            Channel channel, DeviceSession deviceSession, ByteBuf buf, int type, int index, ByteBuf imei) {

        Position position = new Position(getProtocolName());
        position.setDeviceId(deviceSession.getDeviceId());

        getLastLocation(position, readDate(buf));

        position.set(Position.KEY_IGNITION, buf.readUnsignedByte() > 0);

        int i = 1;
        while (buf.isReadable()) {
            switch (buf.readUnsignedShort()) {
                case 0x01 -> {
                    position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 1.22);
                    position.set("tag" + i + "TirePressure", buf.readUnsignedByte() * 1.527 * 2);
                    position.set("tag" + i + "TireTemp", buf.readUnsignedByte() - 55);
                    position.set("tag" + i + "TireStatus", buf.readUnsignedByte());
                }
                case 0x02 -> {
                    position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", BcdUtil.readInteger(buf, 2) * 0.1);
                    switch (buf.readUnsignedByte()) {
                        case 0:
                            position.addAlarm(Position.ALARM_SOS);
                            break;
                        case 1:
                            position.addAlarm(Position.ALARM_LOW_BATTERY);
                            break;
                        default:
                            break;
                    }
                    buf.readUnsignedByte(); // status
                    buf.skipBytes(16); // location
                }
                case 0x03 -> {
                    position.set(Position.KEY_DRIVER_UNIQUE_ID, ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", BcdUtil.readInteger(buf, 2) * 0.1);
                    if (buf.readUnsignedByte() == 1) {
                        position.addAlarm(Position.ALARM_LOW_BATTERY);
                    }
                    buf.readUnsignedByte(); // status
                    buf.skipBytes(16); // location
                }
                case 0x04 -> {
                    position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 2);
                    buf.readUnsignedByte(); // battery level
                    position.set("tag" + i + "Temp", decodeBleTemp(buf));
                    position.set("tag" + i + "Humidity", buf.readUnsignedShort() * 0.01);
                    position.set("tag" + i + "LightSensor", buf.readUnsignedShort());
                    position.set("tag" + i + "Rssi", buf.readUnsignedByte() - 128);
                }
                case 0x05 -> {
                    position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 2);
                    buf.readUnsignedByte(); // battery level
                    position.set("tag" + i + "Temp", decodeBleTemp(buf));
                    position.set("tag" + i + "Door", buf.readUnsignedByte() > 0);
                    position.set("tag" + i + "Rssi", buf.readUnsignedByte() - 128);
                }
                case 0x06 -> {
                    position.set("tag" + i + "Id", ByteBufUtil.hexDump(buf.readSlice(6)));
                    position.set("tag" + i + "Battery", buf.readUnsignedByte() * 0.01 + 2);
                    position.set("tag" + i + "Output", buf.readUnsignedByte() > 0);
                    position.set("tag" + i + "Rssi", buf.readUnsignedByte() - 128);
                }
            }
            i += 1;
        }

        sendResponse(channel, header, type, index, imei, 0);

        return position;
    }

    private Position decodePosition(
            Channel channel, DeviceSession deviceSession, ByteBuf buf, int type, int index, ByteBuf imei) {

        Position position = new Position(getProtocolName());
        position.setDeviceId(deviceSession.getDeviceId());

        position.set(Position.KEY_INDEX, index);

        if (header != 0x2727) {

            buf.readUnsignedShort(); // acc on interval
            buf.readUnsignedShort(); // acc off interval
            buf.readUnsignedByte(); // angle compensation
            buf.readUnsignedShort(); // distance compensation

            position.set(Position.KEY_RSSI, BitUtil.to(buf.readUnsignedShort(), 7));

        }

        int status = buf.readUnsignedByte();
        position.set(Position.KEY_SATELLITES, BitUtil.to(status, 5));

        if (header != 0x2727) {

            buf.readUnsignedByte(); // gsensor manager status
            buf.readUnsignedByte(); // other flags
            buf.readUnsignedByte(); // heartbeat
            buf.readUnsignedByte(); // relay status
            buf.readUnsignedShort(); // drag alarm setting

            int io = buf.readUnsignedShort();
            position.set(Position.KEY_IGNITION, BitUtil.check(io, 14));
            position.set("ac", BitUtil.check(io, 13));
            position.set(Position.PREFIX_IN + 3, BitUtil.check(io, 12));
            position.set(Position.PREFIX_IN + 4, BitUtil.check(io, 11));

            if (type == MSG_GPS_2 || type == MSG_ALARM_2) {
                position.set(Position.KEY_OUTPUT, buf.readUnsignedByte());
                buf.readUnsignedByte(); // reserved
            } else {
                position.set(Position.PREFIX_OUT + 1, BitUtil.check(io, 7));
                position.set(Position.PREFIX_OUT + 2, BitUtil.check(io, 8));
                position.set(Position.PREFIX_OUT + 3, BitUtil.check(io, 9));
            }

            if (header != 0x2626) {
                int adcCount = type == MSG_GPS_2 || type == MSG_ALARM_2 ? 5 : 2;
                for (int i = 1; i <= adcCount; i++) {
                    String value = ByteBufUtil.hexDump(buf.readSlice(2));
                    if (!value.equals("ffff")) {
                        position.set(Position.PREFIX_ADC + i, Integer.parseInt(value, 16) * 0.01);
                    }
                }
            }

        }

        int alarm = buf.readUnsignedByte();
        position.addAlarm(header != 0x2727 ? decodeAlarm1(alarm) : decodeAlarm2(alarm));
        position.set("alarmCode", alarm);

        if (header != 0x2727) {

            buf.readUnsignedByte(); // reserved

            position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());

            int battery = BcdUtil.readInteger(buf, 2);
            position.set(Position.KEY_BATTERY_LEVEL, battery > 0 ? battery : 100);

        }

        if (BitUtil.check(status, 6)) {

            position.setValid(true);
            position.setTime(readDate(buf));
            position.setAltitude(buf.readFloatLE());
            position.setLongitude(buf.readFloatLE());
            position.setLatitude(buf.readFloatLE());
            if (header == 0x2626) {
                buf.readUnsignedShort(); // reserved or hdop
            } else {
                position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) * 0.1));
            }
            position.setCourse(buf.readUnsignedShort());

        } else {

            getLastLocation(position, readDate(buf));

            int mcc = buf.readUnsignedShortLE();
            int mnc = buf.readUnsignedShortLE();

            if (mcc != 0xffff && mnc != 0xffff) {
                Network network = new Network();
                for (int i = 0; i < 3; i++) {
                    network.addCellTower(CellTower.from(
                            mcc, mnc, buf.readUnsignedShortLE(), buf.readUnsignedShortLE()));
                }
                position.setNetwork(network);
            }

        }

        if (header == 0x2727) {

            byte[] accelerationBytes = new byte[5];
            buf.readBytes(accelerationBytes);
            long acceleration = new BigInteger(accelerationBytes).longValue();
            double accelerationZ = BitUtil.between(acceleration, 8, 15) + BitUtil.between(acceleration, 4, 8) * 0.1;
            if (!BitUtil.check(acceleration, 15)) {
                accelerationZ = -accelerationZ;
            }
            double accelerationY = BitUtil.between(acceleration, 20, 27) + BitUtil.between(acceleration, 16, 20) * 0.1;
            if (!BitUtil.check(acceleration, 27)) {
                accelerationY = -accelerationY;
            }
            double accelerationX = BitUtil.between(acceleration, 28, 32) + BitUtil.between(acceleration, 32, 39) * 0.1;
            if (!BitUtil.check(acceleration, 39)) {
                accelerationX = -accelerationX;
            }
            position.set(Position.KEY_G_SENSOR, "[" + accelerationX + "," + accelerationY + "," + accelerationZ + "]");

            int battery = BcdUtil.readInteger(buf, 2);
            position.set(Position.KEY_BATTERY_LEVEL, battery > 0 ? battery : 100);
            position.set(Position.KEY_DEVICE_TEMP, (int) buf.readByte());
            position.set("lightSensor", BcdUtil.readInteger(buf, 2) * 0.1);
            position.set(Position.KEY_BATTERY, BcdUtil.readInteger(buf, 2) * 0.1);
            position.set("solarPanel", BcdUtil.readInteger(buf, 2) * 0.1);
            position.set(Position.KEY_ODOMETER, buf.readUnsignedInt());

            int inputStatus = buf.readUnsignedShort();
            position.set(Position.KEY_IGNITION, BitUtil.check(inputStatus, 2));
            position.set(Position.KEY_RSSI, BitUtil.between(inputStatus, 4, 11));
            position.set(Position.KEY_INPUT, inputStatus);

            buf.readUnsignedShort(); // ignition on upload interval
            buf.readUnsignedInt(); // ignition off upload interval
            buf.readUnsignedByte(); // angle upload interval
            buf.readUnsignedShort(); // distance upload interval
            buf.readUnsignedByte(); // heartbeat

        } else {

            String model = getDeviceModel(deviceSession);
            if ("TLW2-2BL".equals(model)) {
                position.set(Position.KEY_BATTERY, BcdUtil.readInteger(buf, 4) / 100.0);
            }
            if (buf.readableBytes() >= 2) {
                position.set(Position.KEY_POWER, BcdUtil.readInteger(buf, 4) / 100.0);
            }
            if (buf.readableBytes() >= 19) {
                position.setSpeed(UnitsConverter.knotsFromKph(BcdUtil.readInteger(buf, 4) / 10.0));
                position.set(Position.KEY_OBD_SPEED, BcdUtil.readInteger(buf, 4) * 0.01);
                position.set(Position.KEY_FUEL_USED, buf.readUnsignedInt() * 0.001);
                position.set(Position.KEY_FUEL_CONSUMPTION, buf.readUnsignedInt() * 0.001);
                position.set(Position.KEY_RPM, buf.readUnsignedShort());
                int value;
                value = buf.readUnsignedByte();
                if (value != 0xff) {
                    position.set("airInput", value);
                }
                if (value != 0xff) {
                    position.set("airPressure", value);
                }
                if (value != 0xff) {
                    position.set(Position.KEY_COOLANT_TEMP, value - 40);
                }
                if (value != 0xff) {
                    position.set("airTemp", value - 40);
                }
                if (value != 0xff) {
                    position.set(Position.KEY_ENGINE_LOAD, value);
                }
                if (value != 0xff) {
                    position.set(Position.KEY_THROTTLE, value);
                }
                if (value != 0xff) {
                    position.set(Position.KEY_FUEL, value);
                }
            }
        }

        boolean acknowledgement = AttributeUtil.lookup(
                getCacheManager(), Keys.PROTOCOL_ACK.withPrefix(getProtocolName()), deviceSession.getDeviceId());
        if (acknowledgement || type == MSG_ALARM || type == MSG_ALARM_2) {
            sendResponse(channel, header, type, header == 0x2323 ? 1 : index, imei, alarm);
        }

        return position;
    }

}

2026-03-06 16_39_37-WhatsApp.png

Anton Tananaev 20 days ago

You have to send a pull request on GitHub.