mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-28 16:00:56 +00:00
98 lines
3.4 KiB
TypeScript
98 lines
3.4 KiB
TypeScript
import { Injectable, Logger, OnApplicationBootstrap, OnModuleDestroy } from '@nestjs/common';
|
|
import * as net from 'net';
|
|
|
|
import { GpsTrackingService } from '../gps-tracking.service';
|
|
import { buildAck, GT06_PROTOCOL, parseStream } from './gt06.codec';
|
|
|
|
interface Session {
|
|
buffer: Buffer;
|
|
imei: string | null;
|
|
}
|
|
|
|
const MAX_BUFFER = 64 * 1024;
|
|
|
|
/**
|
|
* Raw TCP listener for GT06 GPS trackers. Trackers open a socket, send a login
|
|
* (IMEI), then stream location/heartbeat/alarm packets; we decode, persist via
|
|
* {@link GpsTrackingService}, and ACK login/heartbeat/alarm so the device keeps
|
|
* the connection alive. Disabled when GT06_TCP_PORT=0.
|
|
*/
|
|
@Injectable()
|
|
export class Gt06Server implements OnApplicationBootstrap, OnModuleDestroy {
|
|
private readonly logger = new Logger(Gt06Server.name);
|
|
private server?: net.Server;
|
|
private readonly sessions = new Map<net.Socket, Session>();
|
|
|
|
constructor(private readonly gps: GpsTrackingService) {}
|
|
|
|
onApplicationBootstrap(): void {
|
|
const port = Number(process.env.GT06_TCP_PORT ?? 5023);
|
|
if (!port) {
|
|
this.logger.log('GT06 TCP listener disabled (GT06_TCP_PORT=0)');
|
|
return;
|
|
}
|
|
const host = process.env.GT06_TCP_HOST ?? '0.0.0.0';
|
|
this.server = net.createServer((socket) => this.onConnection(socket));
|
|
this.server.on('error', (err) => this.logger.error(`GT06 server error: ${String(err)}`));
|
|
this.server.listen(port, host, () => this.logger.log(`GT06 GPS tracker listener on ${host}:${port}`));
|
|
}
|
|
|
|
onModuleDestroy(): void {
|
|
for (const socket of this.sessions.keys()) socket.destroy();
|
|
this.sessions.clear();
|
|
this.server?.close();
|
|
}
|
|
|
|
private onConnection(socket: net.Socket): void {
|
|
this.sessions.set(socket, { buffer: Buffer.alloc(0), imei: null });
|
|
socket.on('data', (chunk) => void this.onData(socket, chunk));
|
|
socket.on('error', () => this.sessions.delete(socket));
|
|
socket.on('close', () => this.sessions.delete(socket));
|
|
}
|
|
|
|
private async onData(socket: net.Socket, chunk: Buffer): Promise<void> {
|
|
const session = this.sessions.get(socket);
|
|
if (!session) return;
|
|
session.buffer = Buffer.concat([session.buffer, chunk]);
|
|
if (session.buffer.length > MAX_BUFFER) session.buffer = Buffer.alloc(0); // drop garbage
|
|
|
|
const { packets, rest } = parseStream(session.buffer);
|
|
session.buffer = rest;
|
|
|
|
for (const pkt of packets) {
|
|
try {
|
|
await this.handle(socket, session, pkt);
|
|
} catch (err) {
|
|
this.logger.error(`Failed to handle GT06 packet (${pkt.type}): ${String(err)}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async handle(
|
|
socket: net.Socket,
|
|
session: Session,
|
|
pkt: ReturnType<typeof parseStream>['packets'][number],
|
|
): Promise<void> {
|
|
switch (pkt.type) {
|
|
case 'login':
|
|
session.imei = pkt.imei;
|
|
await this.gps.handleLogin(pkt.imei);
|
|
socket.write(buildAck(GT06_PROTOCOL.LOGIN, pkt.serial));
|
|
break;
|
|
case 'heartbeat':
|
|
if (session.imei) await this.gps.handleHeartbeat(session.imei, pkt.status);
|
|
socket.write(buildAck(GT06_PROTOCOL.HEARTBEAT, pkt.serial));
|
|
break;
|
|
case 'location':
|
|
if (session.imei) await this.gps.handleFix(session.imei, pkt.gps);
|
|
break;
|
|
case 'alarm':
|
|
if (session.imei) await this.gps.handleFix(session.imei, pkt.gps, pkt.status.alarm, pkt.status);
|
|
socket.write(buildAck(GT06_PROTOCOL.ALARM, pkt.serial));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|