// lib/telemetryBridge.ts // Bridge between C++ Unix socket and Next.js SSE import { Socket } from 'net'; const TELEMETRY_SOCKET_PATH = '/tmp/ACtelemetry_socket'; interface CarTelemetry { carID: number; driver_name: string; driver_guid: string; car_model: string; normalizedSplinePos: number; speed_kmh: number; gear: number; rpm: number; last_lap_time: number; best_lap_time: number; current_lap: number; position: number; } interface TelemetryPacket { server_id: number; car_count: number; cars: CarTelemetry[]; } type TelemetryCallback = (data: TelemetryPacket) => void; class TelemetryBridge { private socket: Socket | null = null; private connected: boolean = false; private callbacks: Set = new Set(); private reconnectTimeout: NodeJS.Timeout | null = null; private buffer: Buffer = Buffer.alloc(0); constructor() { this.connect(); } private connect() { console.log('[Telemetry] Connecting to', TELEMETRY_SOCKET_PATH); this.socket = new Socket(); this.socket.connect(TELEMETRY_SOCKET_PATH, () => { console.log('[Telemetry] Connected to C++ socket'); this.connected = true; this.buffer = Buffer.alloc(0); // Reset buffer on new connection }); this.socket.on('data', (data: Buffer) => { // Append new data to buffer this.buffer = Buffer.concat([this.buffer, data]); // Parse complete packets from buffer this.parsePackets(); }); this.socket.on('error', (err) => { console.error('[Telemetry] Socket error:', err.message); this.connected = false; }); this.socket.on('close', () => { console.log('[Telemetry] Connection closed, reconnecting in 2s...'); this.connected = false; this.socket = null; // Reconnect after 2 seconds if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout); this.reconnectTimeout = setTimeout(() => this.connect(), 2000); }); } private parsePackets() { // Telemetry packet structure from C++: // uint8_t server_id (1 byte) // uint8_t car_count (1 byte) // car_telemetry cars[64] (each car = 158 bytes) const HEADER_SIZE = 2; const CAR_SIZE = 158; // Size of car_telemetry struct while (this.buffer.length >= HEADER_SIZE) { const server_id = this.buffer.readUInt8(0); const car_count = this.buffer.readUInt8(1); const expected_size = HEADER_SIZE + (car_count * CAR_SIZE); if (this.buffer.length < expected_size) { // Not enough data yet, wait for more break; } // Parse the packet const packet: TelemetryPacket = { server_id, car_count, cars: [], }; let offset = HEADER_SIZE; for (let i = 0; i < car_count; i++) { const carID = this.buffer.readUInt8(offset); offset += 1; const driver_name = this.buffer.toString('utf8', offset, offset + 64).replace(/\0.*$/g, ''); offset += 64; const driver_guid = this.buffer.toString('utf8', offset, offset + 64).replace(/\0.*$/g, ''); offset += 64; const car_model = this.buffer.toString('utf8', offset, offset + 64).replace(/\0.*$/g, ''); offset += 64; const normalizedSplinePos = this.buffer.readFloatLE(offset); offset += 4; const speed_kmh = this.buffer.readFloatLE(offset); offset += 4; const gear = this.buffer.readUInt8(offset); offset += 1; const rpm = this.buffer.readUInt16LE(offset); offset += 2; const last_lap_time = this.buffer.readUInt32LE(offset); offset += 4; const best_lap_time = this.buffer.readUInt32LE(offset); offset += 4; const current_lap = this.buffer.readUInt16LE(offset); offset += 2; const position = this.buffer.readUInt8(offset); offset += 1; packet.cars.push({ carID, driver_name, driver_guid, car_model, normalizedSplinePos, speed_kmh, gear, rpm, last_lap_time, best_lap_time, current_lap, position, }); } // Emit packet to all callbacks this.callbacks.forEach(cb => cb(packet)); // Remove processed packet from buffer this.buffer = this.buffer.subarray(expected_size); } } public subscribe(callback: TelemetryCallback): () => void { this.callbacks.add(callback); console.log('[Telemetry] Subscriber added, total:', this.callbacks.size); // Return unsubscribe function return () => { this.callbacks.delete(callback); console.log('[Telemetry] Subscriber removed, total:', this.callbacks.size); }; } public isConnected(): boolean { return this.connected; } public disconnect() { if (this.reconnectTimeout) { clearTimeout(this.reconnectTimeout); } if (this.socket) { this.socket.destroy(); this.socket = null; } this.connected = false; } } // Singleton instance let bridge: TelemetryBridge | null = null; export function getTelemetryBridge(): TelemetryBridge { if (!bridge) { bridge = new TelemetryBridge(); } return bridge; }