如何从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。 - 设备网络与时间同步:
- 确认FMC003的SIM卡有正常GPRS信号,可在配置工具中测试PING公网地址。
- 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
相关产品推荐
相关产品推荐

