mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
125 lines
4.2 KiB
TypeScript
125 lines
4.2 KiB
TypeScript
import { BadRequestException, Injectable, Logger, NotFoundException } from '@nestjs/common';
|
|
|
|
import { GpsDeviceRepository, GpsPositionRepository } from './gps-tracking.repository';
|
|
import { GpsDevice } from './entities/gps-device.entity';
|
|
import { Gt06Gps, Gt06Status } from './gt06/gt06.codec';
|
|
|
|
/** A device is considered ONLINE if seen within this window. */
|
|
const ONLINE_WINDOW_MS = 5 * 60 * 1000;
|
|
|
|
@Injectable()
|
|
export class GpsTrackingService {
|
|
private readonly logger = new Logger(GpsTrackingService.name);
|
|
|
|
constructor(
|
|
private readonly devices: GpsDeviceRepository,
|
|
private readonly positions: GpsPositionRepository,
|
|
) {}
|
|
|
|
private isOnline(d: GpsDevice): boolean {
|
|
return Boolean(d.lastSeenAt && Date.now() - new Date(d.lastSeenAt).getTime() < ONLINE_WINDOW_MS);
|
|
}
|
|
|
|
/** Find the device for an IMEI, auto-registering it on first contact. */
|
|
private async ensureDevice(imei: string): Promise<GpsDevice> {
|
|
const existing = await this.devices.findByImei(imei);
|
|
if (existing) return existing;
|
|
this.logger.log(`Auto-registering new GPS tracker ${imei}`);
|
|
return this.devices.create({ imei, status: 'REGISTERED', lastSeenAt: new Date() });
|
|
}
|
|
|
|
// ── Ingestion (called by the TCP server) ──
|
|
|
|
async handleLogin(imei: string): Promise<void> {
|
|
const device = await this.ensureDevice(imei);
|
|
await this.devices.update(device.id, { lastSeenAt: new Date(), status: 'ONLINE' });
|
|
}
|
|
|
|
async handleHeartbeat(imei: string, status: Gt06Status): Promise<void> {
|
|
const device = await this.ensureDevice(imei);
|
|
await this.devices.update(device.id, {
|
|
lastSeenAt: new Date(),
|
|
status: 'ONLINE',
|
|
voltageLevel: status.voltageLevel,
|
|
gsmLevel: status.gsmLevel,
|
|
});
|
|
}
|
|
|
|
async handleFix(imei: string, gps: Gt06Gps, alarm = 0, status?: Gt06Status): Promise<void> {
|
|
const device = await this.ensureDevice(imei);
|
|
const now = new Date();
|
|
await this.devices.update(device.id, {
|
|
lastSeenAt: now,
|
|
status: 'ONLINE',
|
|
lastLat: gps.latitude,
|
|
lastLng: gps.longitude,
|
|
lastSpeed: gps.speed,
|
|
lastCourse: gps.course,
|
|
lastFixAt: new Date(gps.time),
|
|
...(status ? { voltageLevel: status.voltageLevel, gsmLevel: status.gsmLevel } : {}),
|
|
});
|
|
await this.positions.create({
|
|
deviceId: device.id,
|
|
imei,
|
|
vehicleId: device.vehicleId ?? null,
|
|
lat: gps.latitude,
|
|
lng: gps.longitude,
|
|
speed: gps.speed,
|
|
course: gps.course,
|
|
satellites: gps.satellites,
|
|
positioned: gps.positioned,
|
|
gpsTime: new Date(gps.time),
|
|
alarm,
|
|
});
|
|
}
|
|
|
|
// ── Queries / management (REST) ──
|
|
|
|
private decorate(d: GpsDevice) {
|
|
return { ...d, online: this.isOnline(d) };
|
|
}
|
|
|
|
async listDevices() {
|
|
const rows = await this.devices.findAll({ relations: { vehicle: true }, order: { createdAt: 'DESC' } });
|
|
return rows.map((d) => this.decorate(d));
|
|
}
|
|
|
|
/** Live map feed — devices that have at least one fix. */
|
|
async latest() {
|
|
const rows = await this.devices.findAll({ relations: { vehicle: true } });
|
|
return rows.filter((d) => d.lastLat != null && d.lastLng != null).map((d) => this.decorate(d));
|
|
}
|
|
|
|
async history(vehicleId: string, limit = 200) {
|
|
return this.positions.findAll({
|
|
where: { vehicleId },
|
|
order: { gpsTime: 'DESC' },
|
|
take: Math.min(limit, 1000),
|
|
});
|
|
}
|
|
|
|
async registerDevice(dto: { imei: string; name?: string; vehicleId?: string | null }) {
|
|
const existing = await this.devices.findByImei(dto.imei);
|
|
if (existing) throw new BadRequestException(`A device with IMEI ${dto.imei} already exists`);
|
|
return this.devices.create({
|
|
imei: dto.imei,
|
|
name: dto.name ?? null,
|
|
vehicleId: dto.vehicleId ?? null,
|
|
status: 'REGISTERED',
|
|
});
|
|
}
|
|
|
|
async updateDevice(id: string, dto: { name?: string; vehicleId?: string | null }) {
|
|
const updated = await this.devices.update(id, {
|
|
...(dto.name !== undefined ? { name: dto.name } : {}),
|
|
...(dto.vehicleId !== undefined ? { vehicleId: dto.vehicleId } : {}),
|
|
});
|
|
if (!updated) throw new NotFoundException(`GPS device ${id} not found`);
|
|
return updated;
|
|
}
|
|
|
|
async removeDevice(id: string): Promise<void> {
|
|
await this.devices.softDelete(id);
|
|
}
|
|
}
|