UNPKG

@z0mt3c/f1-telemetry-client

Version:

[![Node.js CI](https://github.com/z0mt3c/f1-telemetry-client/actions/workflows/node.js.yml/badge.svg)](https://github.com/z0mt3c/f1-telemetry-client/actions/workflows/node.js.yml) [![NPM Release](https://img.shields.io/npm/v/@z0mt3c/f1-telemetry-client.sv

228 lines (227 loc) 8.7 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.constants = exports.F1TelemetryClient = exports.BIGINT_ENABLED = exports.FORWARD_ADDRESSES = exports.DEFAULT_PORT = void 0; const dgram = __importStar(require("dgram")); const events_1 = require("events"); const packets_1 = require("./parsers/packets"); const types_1 = require("./types"); const PacketTyreSetsDataParser_1 = require("./parsers/packets/PacketTyreSetsDataParser"); const PacketMotionExDataParser_1 = require("./parsers/packets/PacketMotionExDataParser"); const PacketTimeTrialDataParser_1 = require("./parsers/packets/PacketTimeTrialDataParser"); const PacketLapPositionsDataParser_1 = require("./parsers/packets/PacketLapPositionsDataParser"); const constants = __importStar(require("./constants")); exports.constants = constants; const { PACKET_SIZES, PACKET_ID_TO_PACKET, PACKETS } = constants; exports.DEFAULT_PORT = 20777; exports.FORWARD_ADDRESSES = undefined; exports.BIGINT_ENABLED = true; /** * */ class F1TelemetryClient extends events_1.EventEmitter { constructor(opts = {}) { super(); const { port = exports.DEFAULT_PORT, bigintEnabled = exports.BIGINT_ENABLED, forwardAddresses = exports.FORWARD_ADDRESSES } = opts; this.port = port; this.bigintEnabled = bigintEnabled; this.forwardAddresses = forwardAddresses; this.socket = dgram.createSocket('udp4'); } /** * * @param {Buffer} message * @param bigintEnabled * @param remoteInfo */ static parseBufferMessage(message, bigintEnabled = false, remoteInfo) { const packetHeader = F1TelemetryClient.parsePacketHeader(message, bigintEnabled); const { m_packetFormat: format, m_packetId: id, m_gameYear: year } = packetHeader; const context = { id, year, format, message, remoteInfo, name: 'unknown', data: packetHeader }; const Parser = F1TelemetryClient.getParserByPacketId(id); if (Parser == null) throw new types_1.ParserError('No parser available', undefined, context); context.name = Object.keys(PACKETS)[id]; try { const { data } = new Parser(message, format, bigintEnabled); return { ...context, data }; } catch (error) { throw new types_1.ParserError('Parsing failed', error, context); } } /** * * @param {Buffer} buffer * @param {Boolean} bigintEnabled */ static parsePacketHeader(buffer, bigintEnabled) { const packetFormatParser = new packets_1.PacketFormatParser(); const { m_packetFormat } = packetFormatParser.fromBuffer(buffer); const packetHeaderParser = new packets_1.PacketHeaderParser(m_packetFormat, bigintEnabled); return packetHeaderParser.fromBuffer(buffer); } /** * * @param {Number} packetFormat * @param {Number} packetId */ static getPacketSize(packetFormat, packetId) { const packetValues = Object.values(PACKET_SIZES); return packetValues[packetId][packetFormat]; } /** * * @param {Number} packetId */ static getParserByPacketId(packetId) { const packetType = PACKET_ID_TO_PACKET[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 PacketTyreSetsDataParser_1.PacketTyreSetsDataParser; case PACKETS.motionEx: return PacketMotionExDataParser_1.PacketMotionExDataParser; case PACKETS.timeTrial: return PacketTimeTrialDataParser_1.PacketTimeTrialDataParser; case PACKETS.lapPositions: return PacketLapPositionsDataParser_1.PacketLapPositionsDataParser; default: return null; } } /** * * @param {Buffer} message * @param remoteInfo */ handleMessage(message, remoteInfo) { if (this.forwardAddresses != null) { // bridge message this.bridgeMessage(message); } try { const parsedMessage = F1TelemetryClient.parseBufferMessage(message, this.bigintEnabled, remoteInfo); this.emitPackage(parsedMessage); } catch (error) { this.emit('error', error); if (error instanceof types_1.ParserError) this.emit('*', error.context); } } emitPackage(parsedMessage) { if (parsedMessage?.data == null) return; this.emit(parsedMessage.name + ':raw', parsedMessage); this.emit(parsedMessage.name, parsedMessage.data); this.emit('*', parsedMessage); } /** * * @param {Buffer} message */ bridgeMessage(message) { if (this.socket == null) { throw new Error('Socket is not initialized'); } if (this.forwardAddresses == null) { 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 == null) { return; } this.socket.on('listening', () => { if (this.socket == null) { 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, remoteInfo) => { this.handleMessage(m, remoteInfo); }); this.socket.bind({ port: this.port, exclusive: false, }); } /** * Method to close the client */ stop() { if (this.socket == null) { return; } return this.socket.close(() => { console.log('UDP Client closed 🏁'); this.socket = undefined; }); } } exports.F1TelemetryClient = F1TelemetryClient; exports.default = F1TelemetryClient;