UNPKG

@rflafla/f1-udp-parser

Version:

The F1 series of games support the outputting of key game data via a UDP data stream. This data can be interpreted by external apps or connected peripherals for a range of different uses, including providing additional telemetry information, customised HU

277 lines 9.79 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.FORWARD_ADDRESSES = exports.DEFAULT_PORT = exports.packetTypes = exports.constantsTypes = exports.constants = exports.F1TelemetryClient = void 0; // tslint:disable-next-line const dgram = __importStar(require("dgram")); const events_1 = require("events"); const fs_1 = __importDefault(require("fs")); const os_1 = __importDefault(require("os")); const path_1 = __importDefault(require("path")); const constants = __importStar(require("./constants")); exports.constants = constants; const constantsTypes = __importStar(require("./constants/types")); exports.constantsTypes = constantsTypes; const packets_1 = require("./parsers/packets"); const packetTypes = __importStar(require("./parsers/packets/types")); exports.packetTypes = packetTypes; const DEFAULT_PORT = 20777; exports.DEFAULT_PORT = DEFAULT_PORT; const FORWARD_ADDRESSES = undefined; exports.FORWARD_ADDRESSES = FORWARD_ADDRESSES; /** * */ class F1TelemetryClient extends events_1.EventEmitter { port; forwardAddresses; socket; testModeActive = false; testMode; constructor(opts = {}) { super(); const { port = DEFAULT_PORT, forwardAddresses = FORWARD_ADDRESSES, testModeActive, } = opts; this.port = port; this.forwardAddresses = forwardAddresses; this.socket = dgram.createSocket({ type: 'udp4', reuseAddr: true, }); this.testModeActive = testModeActive; if (testModeActive) this.testMode = initializeTestMode.call(this); } /** * * @param {Buffer} message */ static parseBufferMessage(message) { const { m_packetFormat, m_packetId } = F1TelemetryClient.parsePacketHeader(message); const parser = F1TelemetryClient.getParserByPacketId(m_packetId); if (!parser) { return; } const packetData = new parser(message, m_packetFormat); const packetID = Object.keys(constants.PACKETS)[m_packetId]; // emit parsed message return { packetData, packetID }; } /** * * @param {Buffer} buffer */ static parsePacketHeader(buffer // tslint:disable-next-line:no-any ) { const packetFormatParser = new packets_1.PacketFormatParser(); const { m_packetFormat } = packetFormatParser.fromBuffer(buffer); const packetHeaderParser = new packets_1.PacketHeaderParser(m_packetFormat); return packetHeaderParser.fromBuffer(buffer); } /** * * @param {Number} packetFormat * @param {Number} packetId */ static getPacketSize(packetFormat, packetId) { const { PACKET_SIZES } = constants; const packetValues = Object.values(PACKET_SIZES); return packetValues[packetId][packetFormat]; } /** * * @param {Number} packetId */ static getParserByPacketId(packetId) { const { PACKETS } = constants; const packetKeys = Object.keys(PACKETS); const packetType = packetKeys[packetId]; switch (packetType) { case PACKETS.session: return packets_1.PacketSessionDataParser; case PACKETS.motion: return packets_1.PacketMotionDataParser; case PACKETS.lapData: return packets_1.PacketLapDataParser; case PACKETS.event: return packets_1.PacketEventDataParser; case PACKETS.participants: return packets_1.PacketParticipantsDataParser; case PACKETS.carSetups: return packets_1.PacketCarSetupDataParser; case PACKETS.carTelemetry: return packets_1.PacketCarTelemetryDataParser; case PACKETS.carStatus: return packets_1.PacketCarStatusDataParser; case PACKETS.finalClassification: return packets_1.PacketFinalClassificationDataParser; case PACKETS.lobbyInfo: return packets_1.PacketLobbyInfoDataParser; case PACKETS.carDamage: return packets_1.PacketCarDamageDataParser; case PACKETS.sessionHistory: return packets_1.PacketSessionHistoryDataParser; case PACKETS.tyreSets: return packets_1.PacketTyreSetsDataParser; case PACKETS.motionEx: return packets_1.PacketMotionExDataParser; case PACKETS.timeTrial: return packets_1.PacketTimeTrialDataParser; case PACKETS.lapPositions: return packets_1.PacketLapPositionsDataParser; default: return null; } } /** * * @param {Buffer} message */ handleMessage(message) { if (this.forwardAddresses) { // bridge message this.bridgeMessage(message); } const parsedMessage = F1TelemetryClient.parseBufferMessage(message); if (!parsedMessage || !parsedMessage.packetData) { return; } // emit parsed message this.emit(parsedMessage.packetID, parsedMessage.packetData.data); } /** * * @param {Buffer} message */ handleTestModeMessage(message) { const { testMode } = this; if (!testMode) return; testMode.bufferStream.write(`${JSON.stringify(message.toJSON().data)},\n`); testMode.bufferCount += 1; if (testMode.bufferCount > 10000) { testMode.bufferStream.end(); testMode.fileCount++; testMode.bufferCount = 0; testMode.bufferStream = fs_1.default.createWriteStream(`${testMode.logDir}/chunk_${testMode.fileCount}.json`); } } /** * * @param {Buffer} message */ bridgeMessage(message) { if (!this.socket) { throw new Error('Socket is not initialized'); } if (!this.forwardAddresses) { throw new Error('No ports to bridge over'); } for (const address of this.forwardAddresses) { this.socket.send(message, 0, message.length, address.port, address.ip || '0.0.0.0'); } } /** * Method to start listening for packets */ start() { if (!this.socket) { return; } this.socket.on('listening', () => { if (!this.socket) { return; } const address = this.socket.address(); // console.log( // `UDP Client listening on ${address.address}:${address.port} 🏎` // ); this.socket.setBroadcast(true); }); this.socket.on('message', (m) => { if (this.testModeActive && this.testMode) { this.handleTestModeMessage(m); } else this.handleMessage(m); }); this.socket.bind({ port: this.port, exclusive: false, }); } /** * Method to close the client */ stop() { if (!this.socket) { return; } return this.socket.close(() => { // console.log(`UDP Client closed 🏁`); this.socket = undefined; }); } /** * Method to add a new forward address */ addForwardAddress(address) { if (!this.forwardAddresses) { this.forwardAddresses = []; } this.forwardAddresses.push(address); return this.forwardAddresses; } /** * Method to remove a forward address */ removeForwardAddress(address) { if (!this.forwardAddresses) { return; } // remove address by splicing array this.forwardAddresses.splice(this.forwardAddresses.findIndex((forwardAddress) => forwardAddress.port === address.port && forwardAddress.ip === address.ip), 1); return this.forwardAddresses; } } exports.F1TelemetryClient = F1TelemetryClient; function initializeTestMode() { const localAppDataDirectory = path_1.default.join(os_1.default.homedir(), 'AppData', 'Local'); // directory of logs from Test Mode const testLogDir = `${localAppDataDirectory}/Podium/udp_logs/${Date.now()}`; fs_1.default.mkdirSync(testLogDir); const testMode = { bufferStream: fs_1.default.createWriteStream(`${testLogDir}/chunk_${0}.json`), fileCount: 0, bufferCount: 0, logDir: testLogDir, }; return testMode; } //# sourceMappingURL=index.js.map