You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Teltonika FMC003 GPS追踪器获取IMEI至Node.js服务器并接收AVL数据

问题描述

我已在Teltonika FMC003的GPRS设置中配置了部署在DigitalOcean上的Node.js服务器域名及端口,并在配置工具中设置了TCP协议。但配置工具的「状态→GSM信息」显示socket已关闭,最后发送记录为1970年1月1日。我编写了如下Node.js TCP服务器代码,但不清楚如何正确获取IMEI、发送确认并接收AVL数据:

const net = require("net");
const logger = require("./helpers/logger");
require("dotenv").config();

const port = process.env.PORT;

// TCP Server for handling Teltonika devices
const tcpServer = net.createServer((socket) => {
  logger.info("TCP Client connected");

  let buffer = Buffer.alloc(0);

  socket.on("data", async (data) => {
    buffer = Buffer.concat([buffer, data]);

    // Log received data for debugging
    console.log("Received data:", buffer.toString("hex"));
    logger.info({ buffer }, "Data received from tracker");

    // Handle IMEI processing if buffer is at least 17 bytes
    if (buffer.length >= 17) {
      const imeiProcessed = await handleIMEI(buffer, socket);
      logger.info({ imeiProcessed }, "imeiProcessed is:");
     
    }

    // Handle AVL Data if buffer contains more data
    if (buffer.length > 0) {
      handleAVLData(buffer, socket);
      buffer = Buffer.alloc(0); // Clear buffer after processing
    }
  });

  socket.on("close", () => {
    logger.info("TCP Client disconnected");
  });

  socket.on("error", (err) => {
    logger.error(`Socket error: ${err.message}`);
  });
});

tcpServer.listen(port, () => {
  logger.info("TCP Server is running on port 8080");
});

async function handleIMEI(buffer, socket) {
  const imeiLength = buffer.readUInt16BE(0);
  const imei = buffer.slice(2, 2 + imeiLength).toString("ascii");

  logger.info({ imei }, "Received IMEI:");

  const isAccepted = await validateIMEI(imei); // Implement IMEI validation logic

  if (isAccepted) {
    socket.write(Buffer.from([0x01])); // Send acknowledgment
    logger.info("IMEI accepted and acknowledgment sent");
    return true;
  } else {
    socket.write(Buffer.from([0x00])); // Send rejection
    logger.info("IMEI rejected and rejection sent");
    return false;
  }
}

问题排查与代码修复方案

一、先解决Socket连接失败问题

  • 防火墙与端口检查:确保DigitalOcean服务器的目标端口(如8080)已开放TCP入站流量,同时设备侧无防火墙拦截。
  • 域名解析验证:用nslookup或ping命令确认配置的域名能正确解析到服务器公网IP。
  • 设备网络与时间同步:
    1. 确认FMC003的SIM卡有正常GPRS信号,可在配置工具中测试PING公网地址。
    2. 1970年时间戳说明设备未同步时间,需在配置工具中开启NTP同步,设置合法NTP服务器地址。

二、IMEI获取与确认逻辑修复

原代码存在缓冲区未清理、逻辑顺序错误的问题,修复后代码如下:

1. IMEI处理函数优化

function handleIMEI(buffer, socket) {
  const imeiLength = buffer.readUInt16BE(0);
  // Teltonika设备IMEI固定为15位,增加长度校验
  if (imeiLength !== 15) {
    socket.write(Buffer.from([0x00]));
    logger.info("Invalid IMEI length, rejected");
    return { success: false, processedBytes: 0 };
  }
  const imei = buffer.slice(2, 2 + imeiLength).toString("ascii");
  logger.info(`Received IMEI: ${imei}`);

  // 替换为你的IMEI验证逻辑(比如白名单检查)
  const isAccepted = validateIMEI(imei);

  if (isAccepted) {
    socket.write(Buffer.from([0x01]));
    logger.info("IMEI accepted, acknowledgment sent");
    return { success: true, processedBytes: 2 + imeiLength };
  } else {
    socket.write(Buffer.from([0x00]));
    logger.info("IMEI rejected, rejection sent");
    return { success: false, processedBytes: 2 + imeiLength };
  }
}

// 示例IMEI验证函数
function validateIMEI(imei) {
  const allowedIMEIs = ["你的设备IMEI"];
  return allowedIMEIs.includes(imei);
}

2. 数据接收逻辑调整

socket.on("data", (data) => {
  buffer = Buffer.concat([buffer, data]);
  console.log("Received data:", buffer.toString("hex"));
  logger.info({ buffer }, "Data received from tracker");

  // 仅在未完成IMEI验证时处理IMEI
  if (!socket.imeiValidated) {
    if (buffer.length >= 2) {
      const imeiLength = buffer.readUInt16BE(0);
      if (buffer.length >= 2 + imeiLength) {
        const result = handleIMEI(buffer, socket);
        // 移除已处理的字节
        buffer = buffer.slice(result.processedBytes);
        if (result.success) {
          socket.imeiValidated = true;
        } else {
          socket.end(); // 验证失败,关闭连接
          return;
        }
      }
    }
  }

  // 仅在IMEI验证通过后处理AVL数据
  if (socket.imeiValidated && buffer.length > 0) {
    const processedBytes = handleAVLData(buffer, socket);
    buffer = buffer.slice(processedBytes);
  }
});

三、AVL数据接收与确认实现

按照Teltonika AVL协议规范,实现数据解析与确认:

function handleAVLData(buffer, socket) {
  // AVL数据包最小长度检查:总长度(4)+码(1)+记录数(1)+结束记录数(1) = 7字节
  if (buffer.length < 7) return 0;

  const totalLength = buffer.readUInt32BE(0);
  if (buffer.length < totalLength) return 0; // 未收到完整数据包,等待后续字节

  const code = buffer.readUInt8(4);
  if (code !== 0x00) {
    logger.error(`Invalid AVL code: ${code.toString(16)}`);
    return totalLength;
  }

  const recordCount = buffer.readUInt8(5);
  const endRecordCount = buffer.readUInt8(totalLength - 1);
  if (recordCount !== endRecordCount) {
    logger.error("AVL record count mismatch");
    return totalLength;
  }

  // 解析AVL记录(FMC003每条记录固定100字节)
  const avlRecords = [];
  let offset = 6;
  for (let i = 0; i < recordCount; i++) {
    if (offset + 100 > totalLength) break;
    avlRecords.push({
      timestamp: buffer.readUInt32BE(offset) * 1000, // 秒转毫秒时间戳
      priority: buffer.readUInt8(offset + 4),
      longitude: buffer.readInt32BE(offset + 5) / 10000000,
      latitude: buffer.readInt32BE(offset + 9) / 10000000,
      altitude: buffer.readUInt16BE(offset + 13),
      angle: buffer.readUInt16BE(offset + 15),
      satellites: buffer.readUInt8(offset + 17),
      speed: buffer.readUInt16BE(offset + 18)
    });
    offset += 100;
  }

  logger.info(`Received ${recordCount} AVL records:`, avlRecords);
  // 发送AVL确认:返回接收的记录数量(4字节大端)
  socket.write(Buffer.from([0x00, 0x00, 0x00, recordCount]));
  logger.info(`Sent AVL acknowledgment for ${recordCount} records`);

  return totalLength;
}

四、其他细节修复

  • 服务器监听日志改为动态显示端口:
    tcpServer.listen(port, () => {
      logger.info(`TCP Server is running on port ${port}`);
    });
    
  • 避免缓冲区内存泄漏:严格按处理长度截取剩余缓冲区,不要直接清空。

内容的提问来源于stack exchange,提问作者Asmita

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 11:37:35