├── doc ├── examples.hbs └── README.hbs ├── prettier.config.js ├── .gitignore ├── src ├── utils │ ├── stringToEnum.js │ ├── padCarNum.js │ └── createSessionInfoParser.js ├── cpp │ ├── IrSdkCommand.h │ ├── IrSdkCommand.cpp │ ├── platform │ │ ├── platform.h │ │ └── platform.cpp │ ├── IrSdkNodeBindings.h │ ├── IRSDKWrapper.h │ ├── IrSdkNodeBindings.cpp │ ├── IrSdkBindingHelpers.cpp │ ├── IRSDKWrapper.cpp │ ├── IrSdkBindingHelpers.h │ └── irsdk │ │ └── irsdk_defines.h ├── iracing-sdk-js.js ├── consts │ └── IrSdkConsts.js └── JsIrSdk.js ├── .npmignore ├── test ├── IrsdkNodeWrapper-stub.js ├── utils │ └── stringToEnum-spec.js ├── iracing-sdk-js-spec.js ├── smoke-test.js └── JsIrSdk-spec.js ├── .github └── workflows │ ├── build-test.yml │ └── npm-publish.yml ├── examples ├── print-names.js ├── recorder.js ├── cam-example.js └── sample-writer.js ├── LICENSE ├── binding.gyp ├── package.json ├── README.md ├── sample-data └── sessioninfo.json └── index.d.ts /doc/examples.hbs: -------------------------------------------------------------------------------- 1 | {{#examples}} 2 | {{#if caption}} **Example:** {{caption}} {{else}} {{/if}} 3 | {{{inlineLinks example}}} 4 | {{/examples}} 5 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: true, 3 | printWidth: 80, 4 | proseWrap: 'never', 5 | semi: true, 6 | singleQuote: true, 7 | tabWidth: 2, 8 | trailingComma: 'es5', 9 | useTabs: false, 10 | }; 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2 | 3 | *.njsproj 4 | *.vcxproj.filters 5 | *.sln 6 | *.vcxproj 7 | .ntvs_analysis.dat 8 | *.sdf 9 | *.suo 10 | *.vcxproj.user 11 | 12 | # Misc 13 | .vscode 14 | node_modules/ 15 | build/ 16 | prebuilds/ 17 | npm-debug.log 18 | .claude/ 19 | -------------------------------------------------------------------------------- /src/utils/stringToEnum.js: -------------------------------------------------------------------------------- 1 | function stringToEnum(input, enumObj) { 2 | var enumKey = Object.keys(enumObj).find(function (key) { 3 | return key.toLowerCase() === input.toLowerCase(); 4 | }); 5 | if (enumKey) { 6 | return enumObj[enumKey]; 7 | } 8 | } 9 | 10 | module.exports = stringToEnum; 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2 | 3 | *.njsproj 4 | *.vcxproj.filters 5 | *.sln 6 | *.vcxproj 7 | .ntvs_analysis.dat 8 | *.sdf 9 | *.suo 10 | *.vcxproj.user 11 | 12 | # Misc 13 | 14 | prebuilds/ 15 | build/ 16 | sample-data/ 17 | examples/ 18 | test/ 19 | doc/ 20 | .eslintrc 21 | npm-debug.log 22 | .vscode 23 | prettier.config.js 24 | 25 | -------------------------------------------------------------------------------- /src/cpp/IrSdkCommand.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "irsdk/irsdk_defines.h" 4 | #include "platform/platform.h" 5 | 6 | namespace NodeIrSdk 7 | { 8 | #ifdef _WIN32 9 | void broadcastCmd(int cmd, int var1, int var2); 10 | void broadcastCmd(int cmd, int var1, int var2, int var3); 11 | #else 12 | // On non-Windows platforms, these are no-ops since iRacing only runs on Windows 13 | inline void broadcastCmd(int cmd, int var1, int var2) {} 14 | inline void broadcastCmd(int cmd, int var1, int var2, int var3) {} 15 | #endif 16 | } 17 | -------------------------------------------------------------------------------- /test/IrsdkNodeWrapper-stub.js: -------------------------------------------------------------------------------- 1 | const mock = { 2 | start() { 3 | return true; 4 | }, 5 | shutdown() {}, 6 | isInitialized() { 7 | return true; 8 | }, 9 | isConnected() { 10 | return false; 11 | }, 12 | updateSessionInfo() { 13 | return false; 14 | }, 15 | getSessionInfo() {}, 16 | updateTelemetry() { 17 | return false; 18 | }, 19 | getTelemetryDescription() { 20 | return { telemetry: 'is telemetry' }; 21 | }, 22 | getTelemetry() { 23 | return {}; 24 | }, 25 | sendCmd() {}, 26 | }; 27 | 28 | module.exports = mock; 29 | -------------------------------------------------------------------------------- /src/utils/padCarNum.js: -------------------------------------------------------------------------------- 1 | /** pad car number 2 | @function 3 | @param {string} numStr - car number as string 4 | @returns {number} - padded car number 5 | */ 6 | function padCarNum(numStr) { 7 | if (typeof numStr === "string") { 8 | var num = parseInt(numStr); 9 | var zeros = numStr.length - num.toString().length; 10 | if (!zeros) return num; 11 | 12 | var numPlaces = 1; 13 | if (num > 9) numPlaces = 2; 14 | if (num > 99) numPlaces = 3; 15 | 16 | return (numPlaces + zeros) * 1000 + num; 17 | } 18 | } 19 | 20 | module.exports = padCarNum; 21 | -------------------------------------------------------------------------------- /src/cpp/IrSdkCommand.cpp: -------------------------------------------------------------------------------- 1 | #include "IrSdkCommand.h" 2 | 3 | #ifdef _WIN32 4 | #include "irsdk/irsdk_defines.h" 5 | #define WIN32_LEAN_AND_MEAN 6 | #include 7 | 8 | void NodeIrSdk::broadcastCmd(int cmd, int var1, int var2) 9 | { 10 | static unsigned int msgId = RegisterWindowMessageA((LPCSTR)IRSDK_BROADCASTMSGNAME); 11 | 12 | if (cmd >= 0 && cmd < irsdk_BroadcastLast) 13 | { 14 | SendNotifyMessage(HWND_BROADCAST, msgId, MAKELONG(cmd, var1), var2); 15 | } 16 | } 17 | 18 | void NodeIrSdk::broadcastCmd(int cmd, int var1, int var2, int var3) 19 | { 20 | broadcastCmd(cmd, var1, MAKELONG(var2, var3)); 21 | } 22 | #endif 23 | -------------------------------------------------------------------------------- /.github/workflows/build-test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: windows-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v4 11 | - name: Use Node.js 21.x 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: '21.x' 15 | - name: Use Python 3.12 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: 3.12 19 | env: 20 | PYTHON_VERSION: 3.12 21 | - name: Set Windows Env 22 | if: runner.os == 'Windows' 23 | run: | 24 | echo 'GYP_MSVS_VERSION=2015' >> $Env:GITHUB_ENV 25 | echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV 26 | - run: yarn --frozen-lockfile 27 | - run: yarn test 28 | -------------------------------------------------------------------------------- /examples/print-names.js: -------------------------------------------------------------------------------- 1 | // prints team names 2 | 3 | const irsdk = require('../'); 4 | 5 | irsdk.init({ 6 | telemetryUpdateInterval: 1000, 7 | sessionInfoUpdateInterval: 1000, 8 | }); 9 | 10 | const iracing = irsdk.getInstance(); 11 | 12 | console.log('\nwaiting for iRacing...'); 13 | 14 | iracing.on('Connected', function () { 15 | console.log('\nConnected to iRacing.'); 16 | 17 | iracing.once('Disconnected', function () { 18 | console.log('iRacing shut down.'); 19 | }); 20 | 21 | iracing.once('SessionInfo', function (sessionInfo) { 22 | console.log('SessionInfo event received\n'); 23 | sessionInfo.data.DriverInfo.Drivers.forEach(function (driver) { 24 | console.log(driver.TeamName + ' - ' + driver.UserName); 25 | }); 26 | process.exit(); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /src/utils/createSessionInfoParser.js: -------------------------------------------------------------------------------- 1 | /** Default parser used for SessionInfo YAML 2 | Fixes TeamName issue, uses js-yaml for actual parsing 3 | @private 4 | @param {string} sessionInfoStr raw session info YAML string 5 | @returns {Object} parsed session info or falsy 6 | */ 7 | function createSessionInfoParser() { 8 | const yaml = require("js-yaml"); 9 | 10 | return function (sessionInfoStr) { 11 | const fixedYamlStr = sessionInfoStr.replace( 12 | /TeamName: ([^\n]+)/g, 13 | function (match, p1) { 14 | if ( 15 | (p1[0] === '"' && p1[p1.length - 1] === '"') || 16 | (p1[0] === "'" && p1[p1.length - 1] === "'") 17 | ) { 18 | return match; // skip if quoted already 19 | } else { 20 | // 2nd replace is unnecessary atm but its here just in case 21 | return "TeamName: '" + p1.replace(/'/g, "''") + "'"; 22 | } 23 | } 24 | ); 25 | return yaml.load(fixedYamlStr); 26 | }; 27 | } 28 | 29 | module.exports = createSessionInfoParser; 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Zachary Friss 4 | 5 | Original node-irsdk Copyright (c) 2015-2017 Antti Pihlaja 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 8 | 9 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 10 | 11 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 12 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "IrSdkNodeBindings", 5 | "cflags": [ 6 | "-Wall", 7 | "-std=c++17" 8 | ], 9 | 10 | "sources": [ 11 | "src/cpp/IrSdkNodeBindings.cpp", 12 | "src/cpp/IrSdkCommand.cpp", 13 | "src/cpp/IRSDKWrapper.cpp", 14 | "src/cpp/IrSdkBindingHelpers.cpp", 15 | "src/cpp/platform/platform.cpp" 16 | ], 17 | "include_dirs": [ 18 | " README.md", 14 | "ready": "yarn rebuild && yarn test && yarn smoke-test && yarn write-samples && yarn doc" 15 | }, 16 | "keywords": [ 17 | "iracing" 18 | ], 19 | "author": "Zachary Friss", 20 | "license": "MIT", 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/Friss/iracing-sdk-js" 24 | }, 25 | "engines": { 26 | "node": ">=21" 27 | }, 28 | "dependencies": { 29 | "@types/node": "24.2.0", 30 | "js-yaml": "4.1.0", 31 | "nan": "2.23.0" 32 | }, 33 | "devDependencies": { 34 | "jsdoc-to-markdown": "9.1.2", 35 | "node-gyp": "11.3.0", 36 | "prettier": "3.6.2", 37 | "sandboxed-module": "2.0.4" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/cam-example.js: -------------------------------------------------------------------------------- 1 | // camera / command example 2 | 3 | const irsdk = require('../'); 4 | 5 | irsdk.init({ 6 | telemetryUpdateInterval: 1000, 7 | sessionInfoUpdateInterval: 1000, 8 | }); 9 | 10 | const iracing = irsdk.getInstance(); 11 | 12 | console.log('\nwaiting for iRacing...'); 13 | 14 | iracing.on('Connected', function () { 15 | console.log('Connected to iRacing.'); 16 | 17 | iracing.once('Disconnected', function () { 18 | console.log('iRacing shut down.'); 19 | process.exit(); 20 | }); 21 | 22 | iracing.once('SessionInfo', function (sessionInfo) { 23 | console.log('SessionInfo event received\n'); 24 | 25 | // try to find rollbar cam group 26 | const camGroup = sessionInfo.data.CameraInfo.Groups.find( 27 | function (camGroup) { 28 | return camGroup.GroupName === 'Roll Bar'; 29 | } 30 | ); 31 | const camGroupNum = camGroup ? camGroup.GroupNum : 1; 32 | 33 | // loop thru top10, switch every 5 second 34 | const currentPosition = 1; 35 | 36 | setInterval(function () { 37 | console.log('showing P' + currentPosition); 38 | iracing.camControls.switchToPos(currentPosition++, camGroupNum, 0); 39 | 40 | if (currentPosition > 10) { 41 | currentPosition = 1; 42 | } 43 | }, 5000); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/cpp/platform/platform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #ifdef _WIN32 4 | #define WIN32_LEAN_AND_MEAN 5 | #include 6 | #include 7 | #else 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #endif 15 | 16 | namespace platform 17 | { 18 | 19 | #ifdef _WIN32 20 | using handle_t = HANDLE; 21 | using sem_handle_t = HANDLE; 22 | #else 23 | using handle_t = int; 24 | using sem_handle_t = sem_t *; 25 | #endif 26 | 27 | class SharedMemory 28 | { 29 | public: 30 | SharedMemory(); 31 | ~SharedMemory(); 32 | #ifdef _WIN32 33 | bool open(const _TCHAR *name); 34 | #else 35 | bool open(const char *name); 36 | #endif 37 | void close(); 38 | void *getData() const; 39 | bool isOpen() const; 40 | 41 | private: 42 | handle_t handle_; 43 | void *data_; 44 | size_t size_; 45 | 46 | #ifdef _WIN32 47 | HANDLE fileMapping_; 48 | #endif 49 | }; 50 | 51 | class Event 52 | { 53 | public: 54 | Event(); 55 | ~Event(); 56 | #ifdef _WIN32 57 | bool create(const _TCHAR *name); 58 | #else 59 | bool create(const char *name); 60 | #endif 61 | void close(); 62 | bool wait(int timeoutMs); 63 | bool isValid() const; 64 | 65 | private: 66 | sem_handle_t handle_; 67 | }; 68 | 69 | } // namespace platform 70 | -------------------------------------------------------------------------------- /test/utils/stringToEnum-spec.js: -------------------------------------------------------------------------------- 1 | const { describe, it } = require('node:test'); 2 | const assert = require('node:assert'); 3 | const stringToEnum = require('../../src/utils/stringToEnum'); 4 | 5 | describe('stringToEnum', function () { 6 | const TEST_ENUM = { 7 | FIRST_VALUE: 1, 8 | SECOND_VALUE: 2, 9 | THIRD_VALUE: 3, 10 | }; 11 | 12 | it('converts matching string to enum value', function () { 13 | assert.strictEqual(stringToEnum('FIRST_VALUE', TEST_ENUM), 1); 14 | assert.strictEqual(stringToEnum('SECOND_VALUE', TEST_ENUM), 2); 15 | assert.strictEqual(stringToEnum('THIRD_VALUE', TEST_ENUM), 3); 16 | }); 17 | 18 | it('is case insensitive', function () { 19 | assert.strictEqual(stringToEnum('first_value', TEST_ENUM), 1); 20 | assert.strictEqual(stringToEnum('First_Value', TEST_ENUM), 1); 21 | assert.strictEqual(stringToEnum('FIRST_value', TEST_ENUM), 1); 22 | }); 23 | 24 | it('returns undefined for non-matching strings', function () { 25 | assert.strictEqual(stringToEnum('NON_EXISTENT', TEST_ENUM), undefined); 26 | assert.strictEqual(stringToEnum('', TEST_ENUM), undefined); 27 | }); 28 | 29 | it('handles special characters correctly', function () { 30 | const SPECIAL_ENUM = { 31 | 'SPECIAL-VALUE': 1, 32 | 'ANOTHER.VALUE': 2, 33 | }; 34 | assert.strictEqual(stringToEnum('SPECIAL-VALUE', SPECIAL_ENUM), 1); 35 | assert.strictEqual(stringToEnum('special-value', SPECIAL_ENUM), 1); 36 | assert.strictEqual(stringToEnum('ANOTHER.VALUE', SPECIAL_ENUM), 2); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /src/iracing-sdk-js.js: -------------------------------------------------------------------------------- 1 | const IrSdkNodeWrapper = require('../build/Release/IrSdkNodeBindings.node'); 2 | const JsIrSdk = require('./JsIrSdk'); 3 | 4 | /** 5 | @module irsdk 6 | */ 7 | module.exports = {}; 8 | 9 | let instance; 10 | 11 | /** 12 | Initialize JsIrSdk, can be done once before using getInstance first time. 13 | @function 14 | @static 15 | @param {Object} [opts] Options 16 | @param {Integer} [opts.telemetryUpdateInterval=0] Telemetry update interval, milliseconds 17 | @param {Integer} [opts.sessionInfoUpdateInterval=0] SessionInfo update interval, milliseconds 18 | @param {iracing~sessionInfoParser} [opts.sessionInfoParser] Custom parser for session info 19 | @returns {iracing} Running instance of JsIrSdk 20 | @example 21 | * const irsdk = require('iracing-sdk-js') 22 | * // look for telemetry updates only once per 100 ms 23 | * const iracing = irsdk.init({telemetryUpdateInterval: 100}) 24 | */ 25 | var init = (module.exports.init = function (opts) { 26 | if (!instance) { 27 | instance = new JsIrSdk( 28 | IrSdkNodeWrapper, 29 | Object.assign( 30 | { 31 | telemetryUpdateInterval: 0, 32 | sessionInfoUpdateInterval: 0, 33 | }, 34 | opts 35 | ) 36 | ); 37 | } 38 | return instance; 39 | }); 40 | 41 | /** 42 | Get initialized instance of JsIrSdk 43 | @function 44 | @static 45 | @returns {iracing} Running instance of JsIrSdk 46 | @example 47 | * const irsdk = require('iracing-sdk-js') 48 | * const iracing = irsdk.getInstance() 49 | */ 50 | module.exports.getInstance = function () { 51 | return init(); 52 | }; 53 | -------------------------------------------------------------------------------- /test/iracing-sdk-js-spec.js: -------------------------------------------------------------------------------- 1 | const sandboxed = require('sandboxed-module'); 2 | const { describe, it } = require('node:test'); 3 | const assert = require('node:assert'); 4 | 5 | describe('iracing-sdk-js', function () { 6 | describe('#init', function () { 7 | it('instantiates JsIrSdk once', function (context) { 8 | const jsIrSdkSpy = context.mock.fn(); 9 | const nodeWrapperMock = {}; 10 | const opts = { 11 | telemetryUpdateInterval: 1, 12 | sessionInfoUpdateInterval: 2, 13 | }; 14 | const nodeIrSdk = sandboxed.require('../src/iracing-sdk-js', { 15 | requires: { 16 | '../build/Release/IrSdkNodeBindings.node': nodeWrapperMock, 17 | './JsIrSdk': jsIrSdkSpy, 18 | }, 19 | }); 20 | nodeIrSdk.init(opts); 21 | nodeIrSdk.init(opts); 22 | assert.strictEqual(jsIrSdkSpy.mock.callCount(), 1); 23 | assert.strictEqual( 24 | jsIrSdkSpy.mock.calls[0].arguments[0], 25 | nodeWrapperMock 26 | ); 27 | assert.strictEqual( 28 | jsIrSdkSpy.mock.calls[0].arguments[1].telemetryUpdateInterval, 29 | opts.telemetryUpdateInterval 30 | ); 31 | assert.strictEqual( 32 | jsIrSdkSpy.mock.calls[0].arguments[1].sessionInfoUpdateInterval, 33 | opts.sessionInfoUpdateInterval 34 | ); 35 | }); 36 | }); 37 | describe('#getInstance', function () { 38 | it('gives JsIrSdk singleton', function () { 39 | const nodeIrSdk = require('../src/iracing-sdk-js'); 40 | const instance1 = nodeIrSdk.getInstance(); 41 | const instance2 = nodeIrSdk.getInstance(); 42 | assert.strictEqual(instance1, instance2); 43 | instance2._stop(); 44 | }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /src/cpp/IrSdkNodeBindings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | using namespace v8; 7 | 8 | namespace NodeIrSdk 9 | { 10 | 11 | IRSDKWrapper irsdk; 12 | 13 | void start(const Nan::FunctionCallbackInfo &args); 14 | 15 | void shutdown(const Nan::FunctionCallbackInfo &args); 16 | 17 | void isInitialized(const Nan::FunctionCallbackInfo &args); 18 | 19 | void isConnected(const Nan::FunctionCallbackInfo &args); 20 | 21 | void updateSessionInfo(const Nan::FunctionCallbackInfo &args); 22 | 23 | void getSessionInfo(const Nan::FunctionCallbackInfo &args); 24 | 25 | void updateTelemetry(const Nan::FunctionCallbackInfo &args); 26 | 27 | void getTelemetry(const Nan::FunctionCallbackInfo &args); 28 | 29 | void getTelemetryDescription(const Nan::FunctionCallbackInfo &args); 30 | 31 | NAN_METHOD(sendCmd); 32 | 33 | static void cleanUp(void *arg); 34 | 35 | // this defines public api of native addon 36 | NAN_MODULE_INIT(init) 37 | { 38 | irsdk.startup(); 39 | 40 | node::Environment *env = 41 | node::GetCurrentEnvironment(Nan::GetCurrentContext()); 42 | node::AtExit(env, cleanUp, NULL); 43 | 44 | NAN_EXPORT(target, start); 45 | NAN_EXPORT(target, shutdown); 46 | 47 | NAN_EXPORT(target, isInitialized); 48 | NAN_EXPORT(target, isConnected); 49 | 50 | NAN_EXPORT(target, updateSessionInfo); 51 | NAN_EXPORT(target, getSessionInfo); 52 | 53 | NAN_EXPORT(target, updateTelemetry); 54 | NAN_EXPORT(target, getTelemetryDescription); 55 | NAN_EXPORT(target, getTelemetry); 56 | 57 | NAN_EXPORT(target, sendCmd); 58 | } 59 | 60 | // name of native addon 61 | NODE_MODULE(IrSdkNodeBindings, init) 62 | } 63 | -------------------------------------------------------------------------------- /doc/README.hbs: -------------------------------------------------------------------------------- 1 | # iracing-sdk-js 2 | 3 | (Another) Unofficial [iRacing](http://www.iracing.com/) SDK implementation for Node.js. 4 | 5 | [![npm version](https://img.shields.io/npm/v/iracing-sdk-js.svg)](https://www.npmjs.com/package/iracing-sdk-js) 6 | 7 | **iracing-sdk-js** provides data access (live telemetry and session info) and most of the available commands. You can find some usage examples in the [examples](https://github.com/friss/iracing-sdk-js/tree/main/examples) directory, and there are some [data samples](https://github.com/friss/iracing-sdk-js/tree/main/sample-data) too. 8 | 9 | Latest tested iRacing version: *2025 Season 4* 10 | 11 | ## Installing 12 | 13 | Make sure you are running [Node.js](https://nodejs.org/) v21 x64 or later. (Currently requires Node 21 for latest node test runner implementation, but should work with Node 20 too.) 14 | 15 | `npm install --save iracing-sdk-js` 16 | 17 | `yarn add iracing-sdk-js --save` 18 | 19 | ## API documentation 20 | 21 | {{>main}} 22 | 23 | ## Development 24 | 25 | To develop `iracing-sdk-js` itself, you need working working installation of [node-gyp](https://github.com/nodejs/node-gyp#on-windows). 26 | 27 | Useful commands: 28 | 29 | * `yarn rebuild` rebuilds binary addon 30 | * `yarn test` runs mocked tests 31 | * `yarn smoke-tests` runs tests that requires iRacing to be running 32 | * `yarn ready` runs all tests and updates docs 33 | 34 | ## License 35 | 36 | Released under the [MIT License](https://github.com/friss/iracing-sdk-js/blob/main/LICENSE.md). 37 | 38 | ## Credits 39 | Originally based on [node-irsdk](https://github.com/apihlaja/node-irsdk) by [apihlaja](https://github.com/apihlaja). 40 | 41 | Parts of original irsdk used, license available here: https://github.com/friss/iracing-sdk-js/blob/main/src/cpp/irsdk/irsdk_defines.h (BSD-3-Clause) 42 | -------------------------------------------------------------------------------- /examples/sample-writer.js: -------------------------------------------------------------------------------- 1 | const irsdk = require('../'); 2 | const fs = require('fs'); 3 | 4 | // kill the process when enough is done.. 5 | const done = (function () { 6 | var tasks = []; 7 | var totalTasks = 3; 8 | 9 | return function (taskName) { 10 | tasks.push(taskName); 11 | if (tasks.length >= totalTasks) { 12 | console.log(); 13 | console.log('checks done', new Date()); 14 | process.exit(); 15 | } 16 | }; 17 | })(); 18 | 19 | irsdk.init({ 20 | telemetryUpdateInterval: 100, 21 | sessionInfoUpdateInterval: 100, 22 | }); 23 | 24 | const iracing = irsdk.getInstance(); 25 | 26 | console.log('waiting for iRacing...'); 27 | 28 | iracing.on('Connected', function () { 29 | console.log('connected to iRacing..'); 30 | }); 31 | 32 | iracing.on('Disconnected', function () { 33 | console.log('iRacing shut down.\n'); 34 | }); 35 | 36 | iracing.once('TelemetryDescription', function (data) { 37 | console.log('got TelemetryDescription'); 38 | const fileName = './sample-data/telemetry-desc.json'; 39 | 40 | fs.writeFile(fileName, JSON.stringify(data, null, 2), function (err) { 41 | if (err) throw err; 42 | done('TelemetryDescription'); 43 | }); 44 | }); 45 | 46 | iracing.once('Telemetry', function (data) { 47 | console.log('got Telemetry'); 48 | const fileName = './sample-data/telemetry.json'; 49 | 50 | fs.writeFile(fileName, JSON.stringify(data, null, 2), function (err) { 51 | if (err) throw err; 52 | done('Telemetry'); 53 | }); 54 | }); 55 | 56 | iracing.once('SessionInfo', function (data) { 57 | console.log('got SessionInfo'); 58 | const jsonFileName = './sample-data/sessioninfo.json'; 59 | 60 | fs.writeFile(jsonFileName, JSON.stringify(data, null, 2), function (err) { 61 | if (err) throw err; 62 | done('SessionInfo'); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/cpp/IRSDKWrapper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "irsdk/irsdk_defines.h" 4 | #include "platform/platform.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace NodeIrSdk 11 | { 12 | 13 | class IRSDKWrapper 14 | { 15 | public: 16 | IRSDKWrapper(); 17 | ~IRSDKWrapper(); 18 | 19 | bool startup(); 20 | void shutdown(); 21 | 22 | bool isInitialized() const; 23 | bool isConnected() const; 24 | 25 | bool updateTelemetry(); // returns true if telemetry update available 26 | bool updateSessionInfo(); // returns true if session info update available 27 | 28 | const std::string getSessionInfo() const; // returns yaml string 29 | 30 | struct TelemetryVar 31 | { 32 | irsdk_varHeader *header; 33 | irsdk_VarType type; 34 | 35 | union 36 | { // choose correct based on irsdk_VarType 37 | char *value; 38 | float *floatValue; 39 | int *intValue; 40 | bool *boolValue; 41 | double *doubleValue; 42 | }; 43 | 44 | TelemetryVar(irsdk_varHeader *varHeader); 45 | ~TelemetryVar(); 46 | }; 47 | 48 | const std::vector getVarHeaders() const; 49 | 50 | bool getVarVal(TelemetryVar &var) const; 51 | 52 | double getLastTelemetryUpdateTS() const; // returns JS compatible TS 53 | 54 | private: 55 | platform::SharedMemory sharedMem_; 56 | platform::Event dataEvent_; 57 | const irsdk_header *pHeader; 58 | int lastTickCount; 59 | int lastSessionInfoUpdate; 60 | time_t lastValidTime; 61 | char *data; 62 | int dataLen; 63 | std::string sessionInfoStr; 64 | 65 | std::vector varHeadersArr; 66 | 67 | void updateVarHeaders(); // updates map and vector 68 | const char *getSessionInfoStr() const; 69 | }; 70 | 71 | } // namespace NodeIrSdk 72 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will run tests using node and then publish a package to GitHub Packages when a release is created 2 | # For more information see: https://docs.github.com/en/actions/publishing-packages/publishing-nodejs-packages 3 | 4 | name: Node.js Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | build: 12 | runs-on: windows-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js 21.x 16 | uses: actions/setup-node@v3 17 | with: 18 | node-version: '21.x' 19 | - name: Use Python 3.12 20 | uses: actions/setup-python@v4 21 | with: 22 | python-version: 3.12 23 | env: 24 | PYTHON_VERSION: 3.12 25 | - name: Set Windows Env 26 | if: runner.os == 'Windows' 27 | run: | 28 | echo 'GYP_MSVS_VERSION=2015' >> $Env:GITHUB_ENV 29 | echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV 30 | - run: yarn --frozen-lockfile 31 | - run: yarn test 32 | 33 | publish-npm: 34 | needs: build 35 | runs-on: windows-latest 36 | steps: 37 | - uses: actions/checkout@v4 38 | - name: Use Node.js 21.x 39 | uses: actions/setup-node@v3 40 | with: 41 | node-version: '21.x' 42 | registry-url: 'https://registry.npmjs.org' 43 | - name: Use Python 3.12 44 | uses: actions/setup-python@v4 45 | with: 46 | python-version: 3.12 47 | env: 48 | PYTHON_VERSION: 3.12 49 | - name: Set Windows Env 50 | if: runner.os == 'Windows' 51 | run: | 52 | echo 'GYP_MSVS_VERSION=2015' >> $Env:GITHUB_ENV 53 | echo 'GYP_MSVS_OVERRIDE_PATH=C:\\Dummy' >> $Env:GITHUB_ENV 54 | - run: yarn --frozen-lockfile 55 | - run: yarn test 56 | - run: npm ci 57 | - run: npm publish --provenance --access public 58 | env: 59 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 60 | -------------------------------------------------------------------------------- /test/smoke-test.js: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert'); 2 | const irsdk = require('../'); 3 | 4 | irsdk.init({ 5 | telemetryUpdateInterval: 0, 6 | sessionInfoUpdateInterval: 1000, 7 | }); 8 | 9 | const iracing = irsdk.getInstance(); 10 | 11 | // kill the process when enough is done.. 12 | const done = (function () { 13 | var tasks = []; 14 | var totalTasks = 3; 15 | 16 | return function (taskName) { 17 | tasks.push(taskName); 18 | if (tasks.length >= totalTasks) { 19 | console.log(); 20 | console.log('checks done', new Date()); 21 | process.exit(); 22 | } 23 | }; 24 | })(); 25 | 26 | function validateValue(val, desc) { 27 | if (desc.type !== 'bitField') { 28 | if (desc.unit.substr(0, 5) === 'irsdk') { 29 | assert.strictEqual( 30 | typeof val, 31 | 'string', 32 | `enums should be converted to strings ${JSON.stringify(desc)}` 33 | ); 34 | } else { 35 | if (desc.type === 'bool') { 36 | assert.strictEqual(typeof val, 'boolean'); 37 | } 38 | if (desc.type === 'int') { 39 | assert.strictEqual(typeof val, 'number'); 40 | } 41 | if (desc.type === 'float') { 42 | assert.strictEqual(typeof val, 'number'); 43 | } 44 | if (desc.type === 'double') { 45 | assert.strictEqual(typeof val, 'number'); 46 | } 47 | if (desc.type === 'char') { 48 | assert.strictEqual(typeof val, 'string'); 49 | assert.strictEqual(val.length, 1); 50 | } 51 | } 52 | } else { 53 | // expect bitField to be converted to array 54 | assert.strictEqual( 55 | Array.isArray(val), 56 | true, 57 | 'bitField should be converted to array' 58 | ); 59 | 60 | val.forEach(function (bitFieldVal) { 61 | assert.strictEqual(typeof bitFieldVal, 'string'); 62 | }); 63 | } 64 | } 65 | 66 | // tests that C++ module is working 67 | // correctly when doing unit & type conversions 68 | function checkTelemetryValues(telemetry, desc) { 69 | if (!desc || !telemetry) { 70 | return; 71 | } 72 | 73 | console.log('got telemetry and its description, validating output..'); 74 | 75 | for (const telemetryVarName in desc) { 76 | if (desc.hasOwnProperty(telemetryVarName)) { 77 | console.log('checking ' + telemetryVarName); 78 | const varDesc = desc[telemetryVarName]; 79 | const value = telemetry.values[telemetryVarName]; 80 | 81 | assert.strictEqual(typeof varDesc, 'object'); 82 | assert.notEqual(value, undefined); 83 | 84 | if (varDesc.count > 1) { 85 | assert.strictEqual(Array.isArray(value), true); 86 | 87 | value.forEach(function (val) { 88 | validateValue(val, varDesc); 89 | }); 90 | } else { 91 | validateValue(value, varDesc); 92 | } 93 | } 94 | } 95 | } 96 | 97 | console.log(); 98 | console.log('waiting for iRacing...'); 99 | 100 | iracing.once('Connected', function () { 101 | console.log(); 102 | console.log('Connected to iRacing.'); 103 | 104 | let telemetry, desc; 105 | 106 | iracing.once('TelemetryDescription', function (data) { 107 | console.log('TelemetryDescription event received'); 108 | assert.strictEqual(typeof data, 'object'); 109 | desc = data; 110 | checkTelemetryValues(telemetry, desc); 111 | done('desc'); 112 | }); 113 | 114 | iracing.once('Telemetry', function (data) { 115 | console.log('Telemetry event received'); 116 | assert.strictEqual(typeof data, 'object'); 117 | assert.strictEqual(typeof data.timestamp, 'object'); 118 | assert.strictEqual(typeof data.values, 'object'); 119 | 120 | telemetry = data; 121 | checkTelemetryValues(telemetry, desc); 122 | done('telemetry'); 123 | }); 124 | 125 | iracing.once('SessionInfo', function (data) { 126 | console.log('SessionInfo event received'); 127 | assert.strictEqual(typeof data, 'object'); 128 | assert.strictEqual(typeof data.timestamp, 'object'); 129 | assert.strictEqual(typeof data.data, 'object'); 130 | done('sessioninfo'); 131 | }); 132 | }); 133 | 134 | setTimeout(function () { 135 | console.log(`Test hasn't finished in 3 seconds, exiting..`); 136 | process.exit(0); 137 | }, 3000); 138 | -------------------------------------------------------------------------------- /src/cpp/IrSdkNodeBindings.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "IRSDKWrapper.h" 4 | #include "IrSdkBindingHelpers.h" 5 | #include "IrSdkNodeBindings.h" 6 | #include "IrSdkCommand.h" 7 | 8 | #include 9 | #include 10 | 11 | using namespace v8; 12 | 13 | namespace NodeIrSdk 14 | { 15 | 16 | void start(const Nan::FunctionCallbackInfo &args) 17 | { 18 | args.GetReturnValue().Set(Nan::New(irsdk.startup())); 19 | } 20 | 21 | void shutdown(const Nan::FunctionCallbackInfo &args) 22 | { 23 | irsdk.shutdown(); 24 | args.GetReturnValue().Set(Nan::Undefined()); 25 | } 26 | 27 | void isInitialized(const Nan::FunctionCallbackInfo &args) 28 | { 29 | args.GetReturnValue().Set(Nan::New(irsdk.isInitialized())); 30 | } 31 | 32 | void isConnected(const Nan::FunctionCallbackInfo &args) 33 | { 34 | args.GetReturnValue().Set(Nan::New(irsdk.isConnected())); 35 | } 36 | 37 | void updateSessionInfo(const Nan::FunctionCallbackInfo &args) 38 | { 39 | args.GetReturnValue().Set(Nan::New(irsdk.updateSessionInfo())); 40 | } 41 | 42 | void getSessionInfo(const Nan::FunctionCallbackInfo &args) 43 | { 44 | args.GetReturnValue().Set( 45 | Nan::Encode(irsdk.getSessionInfo().c_str(), irsdk.getSessionInfo().length(), Nan::BINARY)); 46 | } 47 | 48 | void updateTelemetry(const Nan::FunctionCallbackInfo &args) 49 | { 50 | args.GetReturnValue().Set(Nan::New(irsdk.updateTelemetry())); 51 | } 52 | 53 | void getTelemetry(const Nan::FunctionCallbackInfo &args) 54 | { 55 | Local rootObj = Nan::New(); 56 | Local valuesObj = Nan::New(); 57 | Nan::Set(rootObj, Nan::New("timestamp").ToLocalChecked(), Nan::New(irsdk.getLastTelemetryUpdateTS()).ToLocalChecked()); 58 | 59 | std::vector headers = irsdk.getVarHeaders(); 60 | 61 | for (const auto item : headers) 62 | { 63 | IRSDKWrapper::TelemetryVar var(item); 64 | irsdk.getVarVal(var); 65 | Local varValue = convertTelemetryVarToObject(var); 66 | Nan::Set(valuesObj, Nan::New(var.header->name).ToLocalChecked(), varValue); 67 | } 68 | Nan::Set(rootObj, Nan::New("values").ToLocalChecked(), valuesObj); 69 | args.GetReturnValue().Set(rootObj); 70 | } 71 | 72 | void getTelemetryDescription(const Nan::FunctionCallbackInfo &args) 73 | { 74 | Local obj = Nan::New(); 75 | std::vector headers = irsdk.getVarHeaders(); 76 | 77 | for (const auto item : headers) 78 | { 79 | IRSDKWrapper::TelemetryVar var(item); 80 | irsdk.getVarVal(var); 81 | Local varObj = Nan::New(); 82 | convertVarHeaderToObject(var, varObj); 83 | Nan::Set(obj, Nan::New(var.header->name).ToLocalChecked(), varObj); 84 | } 85 | args.GetReturnValue().Set(obj); 86 | } 87 | 88 | NAN_METHOD(sendCmd) 89 | { 90 | 91 | if (!irsdk.isInitialized() || !irsdk.isConnected()) 92 | return; 93 | 94 | if (info.Length() > 4 || info.Length() < 1) 95 | { 96 | std::cerr << "sendCommand: invalid arguments (1 to 4 accepted)" << std::endl; 97 | return; 98 | } 99 | 100 | for (int i = 0; i < info.Length(); ++i) 101 | { 102 | if (!info[i]->IsInt32()) 103 | { 104 | std::cerr << "sendCommand: invalid argument type, int32 needed" << std::endl; 105 | return; 106 | } 107 | } 108 | 109 | switch (info.Length()) 110 | { 111 | case 1: 112 | broadcastCmd( 113 | info[0]->Int32Value(Nan::GetCurrentContext()).FromJust(), 114 | 0, 115 | 0); 116 | break; 117 | case 2: 118 | broadcastCmd( 119 | info[0]->Int32Value(Nan::GetCurrentContext()).FromJust(), 120 | info[1]->Int32Value(Nan::GetCurrentContext()).FromJust(), 121 | 0); 122 | break; 123 | case 3: 124 | broadcastCmd( 125 | info[0]->Int32Value(Nan::GetCurrentContext()).FromJust(), 126 | info[1]->Int32Value(Nan::GetCurrentContext()).FromJust(), 127 | info[2]->Int32Value(Nan::GetCurrentContext()).FromJust()); 128 | break; 129 | case 4: 130 | broadcastCmd( 131 | info[0]->Int32Value(Nan::GetCurrentContext()).FromJust(), 132 | info[1]->Int32Value(Nan::GetCurrentContext()).FromJust(), 133 | info[2]->Int32Value(Nan::GetCurrentContext()).FromJust(), 134 | info[3]->Int32Value(Nan::GetCurrentContext()).FromJust()); 135 | break; 136 | } 137 | } 138 | 139 | static void cleanUp(void *arg) 140 | { 141 | irsdk.shutdown(); 142 | } 143 | } // namespace NodeIrSdk 144 | -------------------------------------------------------------------------------- /src/consts/IrSdkConsts.js: -------------------------------------------------------------------------------- 1 | /** 2 | IrSdkConsts, iRacing SDK constants/enums. 3 | 4 | @namespace 5 | @constant 6 | @example var IrSdkConsts = require('node-irsdk').getInstance().Consts 7 | */ 8 | var IrSdkConsts = { 9 | /** 10 | Available command messages. 11 | @enum 12 | */ 13 | BroadcastMsg: { 14 | /** Switch cam, args: car position, group, camera */ 15 | CamSwitchPos: 0, 16 | /** Switch cam, args, driver #, group, camera */ 17 | CamSwitchNum: 1, 18 | /** Set cam state, args: CameraState, unused, unused */ 19 | CamSetState: 2, 20 | /** Set replay speed, args: speed, slowMotion, unused */ 21 | ReplaySetPlaySpeed: 3, 22 | /** Jump to frame, args: RpyPosMode, Frame Number (high, low) */ 23 | ReplaySetPlayPosition: 4, 24 | /** Search things from replay, args: RpySrchMode, unused, unused */ 25 | ReplaySearch: 5, 26 | /** Set replay state, args: RpyStateMode, unused, unused */ 27 | ReplaySetState: 6, 28 | /** Reload textures, args: ReloadTexturesMode, carIdx, unused */ 29 | ReloadTextures: 7, 30 | /** Chat commands, args: ChatCommand, subCommand, unused */ 31 | ChatComand: 8, 32 | /** Pit commands, args: PitCommand, parameter */ 33 | PitCommand: 9, 34 | /** Disk telemetry commands, args: TelemCommand, unused, unused */ 35 | TelemCommand: 10, 36 | /** **not supported by node-irsdk**: Change FFB settings, args: FFBCommandMode, value (float, high, low) */ 37 | FFBCommand: 11, 38 | /** Search by timestamp, args: sessionNum, sessionTimeMS (high, low) */ 39 | ReplaySearchSessionTime: 12, 40 | }, 41 | /** Camera state 42 | Camera state is bitfield; use these values to compose a new state. 43 | @enum 44 | */ 45 | CameraState: { 46 | /** Is driver out of the car */ 47 | IsSessionScreen: 0x0001, // 48 | /** The scenic camera is active (no focus car) */ 49 | IsScenicActive: 0x0002, // 50 | 51 | // these can be changed with a broadcast message 52 | /** Activate camera tool */ 53 | CamToolActive: 0x0004, 54 | /** Hide UI */ 55 | UIHidden: 0x0008, 56 | /** Enable auto shot selection */ 57 | UseAutoShotSelection: 0x0010, 58 | /** Enable temporary edits */ 59 | UseTemporaryEdits: 0x0020, 60 | /** Enable key acceleration */ 61 | UseKeyAcceleration: 0x0040, 62 | /** Enable 10x key acceleration */ 63 | UseKey10xAcceleration: 0x0080, 64 | /** Enable mouse aim */ 65 | UseMouseAimMode: 0x0100, 66 | }, 67 | /** @enum */ 68 | RpyPosMode: { 69 | /** Frame number is relative to beginning */ 70 | Begin: 0, 71 | /** Frame number is relative to current frame */ 72 | Current: 1, 73 | /** Frame number is relative to end */ 74 | End: 2, 75 | }, 76 | /** @enum */ 77 | RpySrchMode: { 78 | ToStart: 0, 79 | ToEnd: 1, 80 | PrevSession: 2, 81 | NextSession: 3, 82 | PrevLap: 4, 83 | NextLap: 5, 84 | PrevFrame: 6, 85 | NextFrame: 7, 86 | PrevIncident: 8, 87 | NextIncident: 9, 88 | }, 89 | /** @enum */ 90 | RpyStateMode: { 91 | /** Clear any data in the replay tape (works only if replay spooling disabled) */ 92 | EraseTape: 0, 93 | }, 94 | /** @enum */ 95 | ReloadTexturesMode: { 96 | All: 0, 97 | CarIdx: 1, 98 | }, 99 | /** @enum */ 100 | ChatCommand: { 101 | /** Macro, give macro num (0-15) as argument */ 102 | Macro: 0, 103 | /** Open up a new chat window */ 104 | BeginChat: 1, 105 | /** Reply to last private chat */ 106 | Reply: 2, 107 | /** Close chat window */ 108 | Cancel: 3, 109 | }, 110 | /** @enum */ 111 | PitCommand: { 112 | /** Clear all pit checkboxes */ 113 | Clear: 0, 114 | /** Clean the winshield, using one tear off */ 115 | WS: 1, 116 | /** Request fuel, optional argument: liters */ 117 | Fuel: 2, 118 | /** Request new left front, optional argument: pressure in kPa */ 119 | LF: 3, 120 | /** Request new right front, optional argument: pressure in kPa */ 121 | RF: 4, 122 | /** Request new left rear, optional argument: pressure in kPa */ 123 | LR: 5, 124 | /** Request new right rear, optional argument: pressure in kPa */ 125 | RR: 6, 126 | /** Clear tire pit checkboxes */ 127 | ClearTires: 7, 128 | /** Request a fast repair */ 129 | FR: 8, 130 | /** Disable clear windshield */ 131 | ClearWS: 9, 132 | /** Disable fast repair */ 133 | ClearFR: 10, 134 | /** Disable refueling */ 135 | ClearFuel: 11, 136 | }, 137 | /** @enum */ 138 | TelemCommand: { 139 | /** Turn telemetry recording off */ 140 | Stop: 0, 141 | /** Turn telemetry recording on */ 142 | Start: 1, 143 | /** Write current file to disk and start a new one */ 144 | Restart: 2, 145 | }, 146 | /** When switching camera, these can be used instead of car number / position 147 | @enum 148 | */ 149 | CamFocusAt: { 150 | Incident: -3, 151 | Leader: -2, 152 | Exciting: -1, 153 | /** Use car number / position instead of this */ 154 | Driver: 0, 155 | }, 156 | }; 157 | 158 | module.exports = IrSdkConsts; 159 | -------------------------------------------------------------------------------- /src/cpp/platform/platform.cpp: -------------------------------------------------------------------------------- 1 | #include "platform.h" 2 | #include 3 | #include 4 | 5 | #ifndef _WIN32 6 | #include 7 | #include 8 | #define INFINITE -1 9 | #endif 10 | 11 | namespace platform 12 | { 13 | 14 | SharedMemory::SharedMemory() : handle_(0), data_(nullptr), size_(0) 15 | #ifdef _WIN32 16 | , 17 | fileMapping_(NULL) 18 | #endif 19 | { 20 | } 21 | 22 | SharedMemory::~SharedMemory() 23 | { 24 | close(); 25 | } 26 | #ifdef _WIN32 27 | bool SharedMemory::open(const _TCHAR *name) 28 | { 29 | 30 | fileMapping_ = OpenFileMapping(FILE_MAP_READ, FALSE, name); 31 | if (fileMapping_ == NULL) 32 | { 33 | return false; 34 | } 35 | data_ = MapViewOfFile(fileMapping_, FILE_MAP_READ, 0, 0, 0); 36 | return data_ != nullptr; 37 | } 38 | #else 39 | 40 | bool SharedMemory::open(const char *name) 41 | { 42 | 43 | // On Unix systems, shared memory names must start with / 44 | std::string shmName = std::string("/") + (name[0] == '/' ? name + 1 : name); 45 | 46 | // Replace Windows path separator with underscore 47 | size_t pos = 0; 48 | while ((pos = shmName.find('\\', pos)) != std::string::npos) 49 | { 50 | shmName.replace(pos, 1, "_"); 51 | } 52 | 53 | handle_ = shm_open(shmName.c_str(), O_RDONLY, 0666); 54 | if (handle_ == -1) 55 | { 56 | return false; 57 | } 58 | 59 | struct stat sb; 60 | if (fstat(handle_, &sb) == -1) 61 | { 62 | close(); 63 | return false; 64 | } 65 | size_ = sb.st_size; 66 | 67 | data_ = mmap(nullptr, size_, PROT_READ, MAP_SHARED, handle_, 0); 68 | if (data_ == MAP_FAILED) 69 | { 70 | close(); 71 | return false; 72 | } 73 | return true; 74 | } 75 | #endif 76 | void SharedMemory::close() 77 | { 78 | #ifdef _WIN32 79 | if (data_) 80 | { 81 | UnmapViewOfFile(data_); 82 | data_ = nullptr; 83 | } 84 | if (fileMapping_) 85 | { 86 | CloseHandle(fileMapping_); 87 | fileMapping_ = NULL; 88 | } 89 | #else 90 | if (data_ && data_ != MAP_FAILED) 91 | { 92 | munmap(data_, size_); 93 | data_ = nullptr; 94 | } 95 | if (handle_ != -1) 96 | { 97 | ::close(handle_); 98 | handle_ = -1; 99 | } 100 | #endif 101 | } 102 | 103 | void *SharedMemory::getData() const 104 | { 105 | return data_; 106 | } 107 | 108 | bool SharedMemory::isOpen() const 109 | { 110 | #ifdef _WIN32 111 | return fileMapping_ != NULL && data_ != nullptr; 112 | #else 113 | return handle_ != -1 && data_ != nullptr && data_ != MAP_FAILED; 114 | #endif 115 | } 116 | 117 | Event::Event() : handle_( 118 | #ifdef _WIN32 119 | NULL 120 | #else 121 | SEM_FAILED 122 | #endif 123 | ) 124 | { 125 | } 126 | 127 | Event::~Event() 128 | { 129 | close(); 130 | } 131 | #ifdef _WIN32 132 | bool Event::create(const _TCHAR *name) 133 | { 134 | handle_ = OpenEvent(SYNCHRONIZE, FALSE, name); 135 | return handle_ != NULL; 136 | } 137 | #else 138 | 139 | bool Event::create(const char *name) 140 | { 141 | 142 | // On Unix systems we'll use a semaphore instead of an event 143 | std::string semName = std::string("/") + (name[0] == '/' ? name + 1 : name); 144 | 145 | // Replace Windows path separator with underscore 146 | size_t pos = 0; 147 | while ((pos = semName.find('\\', pos)) != std::string::npos) 148 | { 149 | semName.replace(pos, 1, "_"); 150 | } 151 | 152 | handle_ = sem_open(semName.c_str(), O_CREAT, 0666, 0); 153 | return handle_ != SEM_FAILED; 154 | } 155 | #endif 156 | void Event::close() 157 | { 158 | #ifdef _WIN32 159 | if (handle_) 160 | { 161 | CloseHandle(handle_); 162 | handle_ = NULL; 163 | } 164 | #else 165 | if (handle_ != SEM_FAILED) 166 | { 167 | sem_close(handle_); 168 | handle_ = SEM_FAILED; 169 | } 170 | #endif 171 | } 172 | 173 | bool Event::wait(int timeoutMs) 174 | { 175 | #ifdef _WIN32 176 | return WaitForSingleObject(handle_, timeoutMs) == WAIT_OBJECT_0; 177 | #else 178 | if (timeoutMs == INFINITE) 179 | { 180 | return sem_wait(handle_) == 0; 181 | } 182 | 183 | struct timespec ts; 184 | clock_gettime(CLOCK_REALTIME, &ts); 185 | ts.tv_sec += timeoutMs / 1000; 186 | ts.tv_nsec += (timeoutMs % 1000) * 1000000; 187 | 188 | return sem_timedwait(handle_, &ts) == 0; 189 | #endif 190 | } 191 | 192 | bool Event::isValid() const 193 | { 194 | #ifdef _WIN32 195 | return handle_ != NULL; 196 | #else 197 | return handle_ != SEM_FAILED; 198 | #endif 199 | } 200 | 201 | } // namespace platform 202 | -------------------------------------------------------------------------------- /src/cpp/IrSdkBindingHelpers.cpp: -------------------------------------------------------------------------------- 1 | #include "IrSdkBindingHelpers.h" 2 | #include 3 | #include 4 | 5 | using namespace v8; 6 | using namespace std; 7 | 8 | Local NodeIrSdk::convertTelemetryValueToObject(IRSDKWrapper::TelemetryVar &var, const int &index) 9 | { 10 | switch (var.type) 11 | { 12 | case irsdk_char: 13 | return Nan::New((var.value[index]) + "\0").ToLocalChecked(); 14 | case irsdk_bool: 15 | return Nan::New(var.boolValue[index]); 16 | case irsdk_int: 17 | if (strcmp(var.header->unit, "irsdk_SessionState") == 0) 18 | { 19 | return getStringValue(var.intValue[index], SESSION_STATES); 20 | } 21 | if (strcmp(var.header->unit, "irsdk_TrkLoc") == 0) 22 | { 23 | return getStringValue(var.intValue[index], TRACK_LOCS); 24 | } 25 | if (strcmp(var.header->unit, "irsdk_TrkSurf") == 0) 26 | { 27 | return getStringValue(var.intValue[index], TRACK_SURF); 28 | } 29 | if (strcmp(var.header->unit, "irsdk_PitSvStatus") == 0) 30 | { 31 | return getStringValue(var.intValue[index], PIT_SV_STATUS); 32 | } 33 | if (strcmp(var.header->unit, "irsdk_PaceMode") == 0) 34 | { 35 | return getStringValue(var.intValue[index], PACE_MODE); 36 | } 37 | if (strcmp(var.header->unit, "irsdk_CarLeftRight") == 0) 38 | { 39 | return getStringValue(var.intValue[index], CAR_BESIDE); 40 | } 41 | if (strcmp(var.header->unit, "irsdk_TrackWetness") == 0) 42 | { 43 | return getStringValue(var.intValue[index], TRACK_WETNESS); 44 | } 45 | if (strcmp(var.header->unit, "irsdk_IncidentFlags") == 0) 46 | { 47 | return getStringValue(var.intValue[index], INCIDENT_FLAGS); 48 | } 49 | return Nan::New(static_cast(var.intValue[index])); 50 | case irsdk_bitField: 51 | return getMaskedValues(var.intValue[index], var.header->unit); 52 | case irsdk_float: 53 | return Nan::New(static_cast(var.floatValue[index])); 54 | case irsdk_double: 55 | return Nan::New(var.doubleValue[index]); 56 | default: 57 | return Nan::Undefined(); 58 | } 59 | } 60 | 61 | Local NodeIrSdk::convertTelemetryVarToObject(IRSDKWrapper::TelemetryVar &var) 62 | { 63 | if (var.header->count > 1) 64 | { 65 | Local arr = Nan::New(var.header->count); 66 | for (int i = 0; i < var.header->count; ++i) 67 | { 68 | Nan::Set(arr, i, convertTelemetryValueToObject(var, i)); 69 | } 70 | return arr; 71 | } 72 | else 73 | { 74 | return convertTelemetryValueToObject(var, 0); 75 | } 76 | } 77 | 78 | void NodeIrSdk::convertVarHeaderToObject(IRSDKWrapper::TelemetryVar &var, Local &obj) 79 | { 80 | Nan::Set(obj, Nan::New("name").ToLocalChecked(), Nan::New(var.header->name).ToLocalChecked()); 81 | Nan::Set(obj, Nan::New("desc").ToLocalChecked(), Nan::New(var.header->desc).ToLocalChecked()); 82 | Nan::Set(obj, Nan::New("unit").ToLocalChecked(), Nan::New(var.header->unit).ToLocalChecked()); 83 | Nan::Set(obj, Nan::New("count").ToLocalChecked(), Nan::New(var.header->count)); 84 | 85 | switch (var.header->type) 86 | { 87 | case irsdk_char: 88 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("char").ToLocalChecked()); 89 | break; 90 | case irsdk_bool: 91 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("bool").ToLocalChecked()); 92 | break; 93 | case irsdk_int: 94 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("int").ToLocalChecked()); 95 | break; 96 | case irsdk_bitField: 97 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("bitField").ToLocalChecked()); 98 | break; 99 | case irsdk_float: 100 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("float").ToLocalChecked()); 101 | break; 102 | case irsdk_double: 103 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("double").ToLocalChecked()); 104 | break; 105 | default: 106 | Nan::Set(obj, Nan::New("type").ToLocalChecked(), Nan::New("UNKNOWN").ToLocalChecked()); 107 | break; 108 | } 109 | } 110 | 111 | Local NodeIrSdk::getMaskedValues(const int &val, char *unit) 112 | { 113 | if (strcmp(unit, "irsdk_Flags") == 0) 114 | { 115 | return getValueArr(val, FLAG_MASKS); 116 | } 117 | if (strcmp(unit, "irsdk_CameraState") == 0) 118 | { 119 | return getValueArr(val, CAMERA_STATE_MASKS); 120 | } 121 | if (strcmp(unit, "irsdk_EngineWarnings") == 0) 122 | { 123 | return getValueArr(val, ENGINE_WARNINGS_MASKS); 124 | } 125 | if (strcmp(unit, "irsdk_PitSvFlags") == 0) 126 | { 127 | return getValueArr(val, PIT_SV_MASKS); 128 | } 129 | if (strcmp(unit, "irsdk_CarLeftRight") == 0) 130 | { 131 | return getValueArr(val, CAR_BESIDE); 132 | } 133 | if (strcmp(unit, "irsdk_PaceFlags") == 0) 134 | { 135 | return getValueArr(val, PACE_FLAGS); 136 | } 137 | cerr << "Missing converter for bitField: " << unit << endl; 138 | return Nan::New(static_cast(val)); 139 | } 140 | 141 | Local NodeIrSdk::getValueArr(const int &val, const std::vector MASKS) 142 | { 143 | Local arr = Nan::New(); 144 | int counter = 0; 145 | for (const auto &mask : MASKS) 146 | { 147 | if ((mask.val & val) == mask.val) 148 | { 149 | Nan::Set(arr, counter++, Nan::New(mask.name).ToLocalChecked()); 150 | } 151 | } 152 | return arr; 153 | } 154 | 155 | Local NodeIrSdk::getStringValue(const int &val, const std::vector &map) 156 | { 157 | for (const auto &mask : map) 158 | { 159 | if (mask.val == val) 160 | { 161 | return Nan::New(mask.name).ToLocalChecked(); 162 | } 163 | } 164 | return Nan::Undefined(); 165 | } 166 | 167 | NodeIrSdk::MaskName::MaskName(int val, const char *name) : val(val), name(name) 168 | { 169 | } 170 | -------------------------------------------------------------------------------- /src/cpp/IRSDKWrapper.cpp: -------------------------------------------------------------------------------- 1 | #include "IRSDKWrapper.h" 2 | #include 3 | #include 4 | 5 | // npm install --debug enables debug prints 6 | #ifdef _DEBUG 7 | #define debug(x) std::cout << x << std::endl; 8 | #else 9 | #define debug(x) 10 | #endif 11 | 12 | NodeIrSdk::IRSDKWrapper::IRSDKWrapper() : pHeader(NULL), lastTickCount(INT_MIN), lastSessionInfoUpdate(INT_MIN), 13 | data(NULL), dataLen(-1), sessionInfoStr() 14 | { 15 | debug("IRSDKWrapper: constructing..."); 16 | } 17 | 18 | NodeIrSdk::IRSDKWrapper::~IRSDKWrapper() 19 | { 20 | debug("IRSDKWrapper: deconstructing..."); 21 | shutdown(); 22 | } 23 | 24 | bool NodeIrSdk::IRSDKWrapper::startup() 25 | { 26 | debug("IRSDKWrapper: starting up..."); 27 | 28 | if (!sharedMem_.isOpen()) 29 | { 30 | debug("IRSDKWrapper: opening mem map..."); 31 | if (!sharedMem_.open(IRSDK_MEMMAPFILENAME)) 32 | { 33 | return false; 34 | } 35 | pHeader = (irsdk_header *)sharedMem_.getData(); 36 | lastTickCount = INT_MIN; 37 | 38 | if (!dataEvent_.create(IRSDK_DATAVALIDEVENTNAME)) 39 | { 40 | shutdown(); 41 | return false; 42 | } 43 | } 44 | debug("IRSDKWrapper: start up ready."); 45 | return true; 46 | } 47 | 48 | bool NodeIrSdk::IRSDKWrapper::isInitialized() const 49 | { 50 | if (!sharedMem_.isOpen()) 51 | { 52 | debug("IRSDKWrapper: not initialized..."); 53 | return false; 54 | } 55 | debug("IRSDKWrapper: is initialized..."); 56 | return true; 57 | } 58 | 59 | bool NodeIrSdk::IRSDKWrapper::isConnected() const 60 | { 61 | bool status = pHeader && pHeader->status == irsdk_stConnected; 62 | debug("IRSDKWrapper: sim status: " << status); 63 | return status; 64 | } 65 | 66 | void NodeIrSdk::IRSDKWrapper::shutdown() 67 | { 68 | debug("IRSDKWrapper: shutting down..."); 69 | 70 | sharedMem_.close(); 71 | dataEvent_.close(); 72 | 73 | pHeader = NULL; 74 | lastTickCount = INT_MIN; 75 | lastSessionInfoUpdate = INT_MIN; 76 | delete[] data; 77 | data = NULL; 78 | lastValidTime = time(NULL); 79 | varHeadersArr.clear(); 80 | sessionInfoStr = ""; 81 | 82 | debug("IRSDKWrapper: shutdown ready."); 83 | } 84 | 85 | bool NodeIrSdk::IRSDKWrapper::updateSessionInfo() 86 | { 87 | debug("IRSDKWrapper: updating session info..."); 88 | if (startup()) 89 | { 90 | int counter = pHeader->sessionInfoUpdate; 91 | 92 | if (counter > lastSessionInfoUpdate) 93 | { 94 | sessionInfoStr = getSessionInfoStr(); 95 | lastSessionInfoUpdate = counter; 96 | return true; 97 | } 98 | return false; 99 | } 100 | return false; 101 | } 102 | 103 | const std::string NodeIrSdk::IRSDKWrapper::getSessionInfo() const 104 | { 105 | return sessionInfoStr; 106 | } 107 | 108 | bool NodeIrSdk::IRSDKWrapper::updateTelemetry() 109 | { 110 | debug("IRSDKWrapper: updating telemetry..."); 111 | if (isInitialized() && isConnected()) 112 | { 113 | if (varHeadersArr.empty()) 114 | { 115 | updateVarHeaders(); 116 | } 117 | // if sim is not active, then no new data 118 | if (pHeader->status != irsdk_stConnected) 119 | { 120 | debug("IRSDKWrapper: not connected, break"); 121 | lastTickCount = INT_MIN; 122 | return false; 123 | } 124 | 125 | debug("IRSDKWrapper: finding latest buffer"); 126 | int latest = 0; 127 | for (int i = 1; i < pHeader->numBuf; i++) 128 | if (pHeader->varBuf[latest].tickCount < pHeader->varBuf[i].tickCount) 129 | latest = i; 130 | 131 | debug("IRSDKWrapper: latest buffer " << latest); 132 | 133 | // if newer than last received, then report new data 134 | if (lastTickCount < pHeader->varBuf[latest].tickCount) 135 | { 136 | debug("IRSDKWrapper: new data, attempting to copy"); 137 | if (data == NULL || dataLen != pHeader->bufLen) 138 | { 139 | debug("IRSDKWrapper: create new data array"); 140 | if (data != NULL) 141 | delete[] data; 142 | data = NULL; 143 | 144 | if (pHeader->bufLen > 0) 145 | { 146 | dataLen = pHeader->bufLen; 147 | data = new char[dataLen]; 148 | } 149 | else 150 | { 151 | debug("IRSDKWrapper: weird bufferLen.. skipping"); 152 | return false; 153 | } 154 | } 155 | // try to get data 156 | // try twice to get the data out 157 | for (int count = 0; count < 2; count++) 158 | { 159 | debug("IRSDKWrapper: copy attempt " << count); 160 | int curTickCount = pHeader->varBuf[latest].tickCount; 161 | const char *sharedData = static_cast(sharedMem_.getData()); 162 | memcpy(data, sharedData + pHeader->varBuf[latest].bufOffset, pHeader->bufLen); 163 | if (curTickCount == pHeader->varBuf[latest].tickCount) 164 | { 165 | lastTickCount = curTickCount; 166 | lastValidTime = time(NULL); 167 | debug("IRSDKWrapper: copy complete"); 168 | return true; 169 | } 170 | } 171 | // if here, the data changed out from under us. 172 | debug("IRSDKWrapper: copy failed"); 173 | return false; 174 | } 175 | // if older than last received, then reset, we probably disconnected 176 | else if (lastTickCount > pHeader->varBuf[latest].tickCount) 177 | { 178 | debug("IRSDKWrapper: ???"); 179 | lastTickCount = INT_MIN; 180 | return false; 181 | } 182 | // else the same, and nothing changed this tick 183 | } 184 | debug("IRSDKWrapper: no new telemetry data"); 185 | return false; 186 | } 187 | 188 | double NodeIrSdk::IRSDKWrapper::getLastTelemetryUpdateTS() const 189 | { 190 | return 1000.0f * lastValidTime; 191 | } 192 | 193 | const char *NodeIrSdk::IRSDKWrapper::getSessionInfoStr() const 194 | { 195 | debug("IRSDKWrapper: getSessionInfoStr"); 196 | if (isInitialized() && pHeader) 197 | { 198 | return static_cast(sharedMem_.getData()) + pHeader->sessionInfoOffset; 199 | } 200 | 201 | return NULL; 202 | } 203 | 204 | void NodeIrSdk::IRSDKWrapper::updateVarHeaders() 205 | { 206 | debug("IRSDKWrapper: updating varHeaders..."); 207 | varHeadersArr.clear(); 208 | 209 | if (!pHeader) 210 | return; 211 | 212 | const char *sharedData = static_cast(sharedMem_.getData()); 213 | for (int index = 0; index < pHeader->numVars; ++index) 214 | { 215 | irsdk_varHeader *pVarHeader = &((irsdk_varHeader *)(sharedData + pHeader->varHeaderOffset))[index]; 216 | varHeadersArr.push_back(pVarHeader); 217 | } 218 | debug("IRSDKWrapper: varHeaders update done."); 219 | } 220 | 221 | NodeIrSdk::IRSDKWrapper::TelemetryVar::TelemetryVar(irsdk_varHeader *varHeader) : header(varHeader) 222 | { 223 | value = new char[irsdk_VarTypeBytes[varHeader->type] * varHeader->count]; 224 | type = (irsdk_VarType)varHeader->type; 225 | } 226 | 227 | NodeIrSdk::IRSDKWrapper::TelemetryVar::~TelemetryVar() 228 | { 229 | delete value; 230 | } 231 | 232 | const std::vector NodeIrSdk::IRSDKWrapper::getVarHeaders() const 233 | { 234 | debug("IRSDKWrapper: getVarHeaders"); 235 | return varHeadersArr; 236 | } 237 | 238 | bool NodeIrSdk::IRSDKWrapper::getVarVal(TelemetryVar &var) const 239 | { 240 | debug("IRSDKWrapper: getVarVal " << var.header->name); 241 | if (data == NULL) 242 | { 243 | debug("no data available.."); 244 | return false; 245 | } 246 | 247 | int valueBytes = irsdk_VarTypeBytes[var.header->type] * var.header->count; 248 | memcpy(var.value, data + var.header->offset, valueBytes); 249 | return true; 250 | } 251 | -------------------------------------------------------------------------------- /src/cpp/IrSdkBindingHelpers.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "irsdk/irsdk_defines.h" 5 | #include "IRSDKWrapper.h" 6 | #include 7 | 8 | using namespace v8; 9 | 10 | namespace NodeIrSdk 11 | { 12 | 13 | Local convertTelemetryValueToObject(IRSDKWrapper::TelemetryVar &var, const int &index); 14 | Local convertTelemetryVarToObject(IRSDKWrapper::TelemetryVar &var); 15 | void convertVarHeaderToObject(IRSDKWrapper::TelemetryVar &var, Local &obj); 16 | 17 | struct MaskName 18 | { 19 | int val; 20 | const char *name; 21 | 22 | MaskName(int val, const char *name); 23 | }; 24 | 25 | Local getStringValue(const int &val, const std::vector &map); 26 | 27 | Local getMaskedValues(const int &val, char *unit); 28 | Local getValueArr(const int &val, const std::vector MASKS); 29 | 30 | const std::vector FLAG_MASKS = { 31 | MaskName((int)irsdk_checkered, "Checkered"), 32 | MaskName((int)irsdk_white, "White"), 33 | MaskName((int)irsdk_green, "Green"), 34 | MaskName((int)irsdk_yellow, "Yellow"), 35 | MaskName((int)irsdk_red, "Red"), 36 | MaskName((int)irsdk_blue, "Blue"), 37 | MaskName((int)irsdk_debris, "Debris"), 38 | MaskName((int)irsdk_crossed, "Crossed"), 39 | MaskName((int)irsdk_yellowWaving, "YellowWaving"), 40 | MaskName((int)irsdk_oneLapToGreen, "OneLapToGreen"), 41 | MaskName((int)irsdk_greenHeld, "GreenHeld"), 42 | MaskName((int)irsdk_tenToGo, "TenToGo"), 43 | MaskName((int)irsdk_fiveToGo, "FiveToGo"), 44 | MaskName((int)irsdk_randomWaving, "RandomWaving"), 45 | MaskName((int)irsdk_caution, "Caution"), 46 | MaskName((int)irsdk_cautionWaving, "CautionWaving"), 47 | 48 | // drivers black flags 49 | MaskName((int)irsdk_black, "Black"), 50 | MaskName((int)irsdk_disqualify, "Disqualify"), 51 | MaskName((int)irsdk_servicible, "Servicible"), 52 | MaskName((int)irsdk_furled, "Furled"), 53 | MaskName((int)irsdk_repair, "Repair"), 54 | MaskName((int)irsdk_dqScoringInvalid, "DQScoringInvalid"), 55 | 56 | // start lights 57 | MaskName((int)irsdk_startHidden, "StartHidden"), 58 | MaskName((int)irsdk_startReady, "StartReady"), 59 | MaskName((int)irsdk_startSet, "StartSet"), 60 | MaskName((int)irsdk_startGo, "StartGo")}; 61 | 62 | const std::vector PIT_SV_MASKS = { 63 | MaskName((int)irsdk_LFTireChange, "LFTireChange"), 64 | MaskName((int)irsdk_RFTireChange, "RFTireChange"), 65 | MaskName((int)irsdk_LRTireChange, "LRTireChange"), 66 | MaskName((int)irsdk_RRTireChange, "RRTireChange"), 67 | 68 | MaskName((int)irsdk_FuelFill, "FuelFill"), 69 | MaskName((int)irsdk_WindshieldTearoff, "WindshieldTearoff"), 70 | MaskName((int)irsdk_FastRepair, "FastRepair")}; 71 | 72 | const std::vector PIT_SV_STATUS = { 73 | MaskName((int)irsdk_PitSvNone, "PitSvNone"), 74 | MaskName((int)irsdk_PitSvInProgress, "PitSvInProgress"), 75 | MaskName((int)irsdk_PitSvComplete, "PitSvComplete"), 76 | MaskName((int)irsdk_PitSvTooFarLeft, "PitSvTooFarLeft"), 77 | MaskName((int)irsdk_PitSvTooFarRight, "PitSvTooFarRight"), 78 | MaskName((int)irsdk_PitSvTooFarForward, "PitSvTooFarForward"), 79 | MaskName((int)irsdk_PitSvTooFarBack, "PitSvTooFarBack"), 80 | MaskName((int)irsdk_PitSvBadAngle, "PitSvBadAngle"), 81 | MaskName((int)irsdk_PitSvCantFixThat, "PitSvCantFixThat"), 82 | }; 83 | 84 | const std::vector CAMERA_STATE_MASKS = { 85 | MaskName((int)irsdk_IsSessionScreen, "IsSessionScreen"), 86 | MaskName((int)irsdk_IsScenicActive, "IsScenicActive"), 87 | 88 | // these can be changed with a broadcast message 89 | MaskName((int)irsdk_CamToolActive, "CamToolActive"), 90 | MaskName((int)irsdk_UIHidden, "UIHidden"), 91 | MaskName((int)irsdk_UseAutoShotSelection, "UseAutoShotSelection"), 92 | MaskName((int)irsdk_UseTemporaryEdits, "UseTemporaryEdits"), 93 | MaskName((int)irsdk_UseKeyAcceleration, "UseKeyAcceleration"), 94 | MaskName((int)irsdk_UseKey10xAcceleration, "UseKey10xAcceleration"), 95 | MaskName((int)irsdk_UseMouseAimMode, "UseMouseAimMode")}; 96 | 97 | const std::vector ENGINE_WARNINGS_MASKS = { 98 | MaskName((int)irsdk_waterTempWarning, "WaterTempWarning"), 99 | MaskName((int)irsdk_fuelPressureWarning, "FuelPressureWarning"), 100 | MaskName((int)irsdk_oilPressureWarning, "OilPressureWarning"), 101 | MaskName((int)irsdk_engineStalled, "EngineStalled"), 102 | MaskName((int)irsdk_pitSpeedLimiter, "PitSpeedLimiter"), 103 | MaskName((int)irsdk_revLimiterActive, "RevLimiterActive"), 104 | MaskName((int)irsdk_oilTempWarning, "OilTempWarning"), 105 | MaskName((int)irsdk_mandRepNeeded, "MandatoryRepairNeeded"), 106 | MaskName((int)irsdk_optRepNeeded, "OptionalRepairNeeded")}; 107 | 108 | const std::vector SESSION_STATES = { 109 | MaskName((int)irsdk_StateInvalid, "Invalid"), 110 | MaskName((int)irsdk_StateGetInCar, "GetInCar"), 111 | MaskName((int)irsdk_StateWarmup, "Warmup"), 112 | MaskName((int)irsdk_StateParadeLaps, "ParadeLaps"), 113 | MaskName((int)irsdk_StateRacing, "Racing"), 114 | MaskName((int)irsdk_StateCheckered, "Checkered"), 115 | MaskName((int)irsdk_StateCoolDown, "CoolDown")}; 116 | 117 | const std::vector TRACK_LOCS = { 118 | MaskName((int)irsdk_NotInWorld, "NotInWorld"), 119 | MaskName((int)irsdk_OffTrack, "OffTrack"), 120 | MaskName((int)irsdk_InPitStall, "InPitStall"), 121 | MaskName((int)irsdk_AproachingPits, "AproachingPits"), 122 | MaskName((int)irsdk_OnTrack, "OnTrack")}; 123 | 124 | const std::vector TRACK_SURF = { 125 | MaskName((int)irsdk_SurfaceNotInWorld, "SurfaceNotInWorld"), 126 | MaskName((int)irsdk_UndefinedMaterial, "UndefinedMaterial"), 127 | 128 | MaskName((int)irsdk_Asphalt1Material, "Asphalt1Material"), 129 | MaskName((int)irsdk_Asphalt2Material, "Asphalt2Material"), 130 | MaskName((int)irsdk_Asphalt3Material, "Asphalt3Material"), 131 | MaskName((int)irsdk_Asphalt4Material, "Asphalt4Material"), 132 | MaskName((int)irsdk_Concrete1Material, "Concrete1Material"), 133 | MaskName((int)irsdk_Concrete2Material, "Concrete2Material"), 134 | MaskName((int)irsdk_RacingDirt1Material, "RacingDirt1Material"), 135 | MaskName((int)irsdk_RacingDirt2Material, "RacingDirt2Material"), 136 | MaskName((int)irsdk_Paint1Material, "Paint1Material"), 137 | MaskName((int)irsdk_Paint2Material, "Paint2Material"), 138 | MaskName((int)irsdk_Rumble1Material, "Rumble1Material"), 139 | MaskName((int)irsdk_Rumble2Material, "Rumble2Material"), 140 | MaskName((int)irsdk_Rumble3Material, "Rumble3Material"), 141 | MaskName((int)irsdk_Rumble4Material, "Rumble4Material"), 142 | 143 | MaskName((int)irsdk_Grass1Material, "Grass1Material"), 144 | MaskName((int)irsdk_Grass2Material, "Grass2Material"), 145 | MaskName((int)irsdk_Grass3Material, "Grass3Material"), 146 | MaskName((int)irsdk_Grass4Material, "Grass4Material"), 147 | MaskName((int)irsdk_Dirt1Material, "Dirt1Material"), 148 | MaskName((int)irsdk_Dirt2Material, "Dirt2Material"), 149 | MaskName((int)irsdk_Dirt3Material, "Dirt3Material"), 150 | MaskName((int)irsdk_Dirt4Material, "Dirt4Material"), 151 | MaskName((int)irsdk_SandMaterial, "SandMaterial"), 152 | MaskName((int)irsdk_Gravel1Material, "Gravel1Material"), 153 | MaskName((int)irsdk_Gravel2Material, "Gravel2Material"), 154 | MaskName((int)irsdk_GrasscreteMaterial, "GrasscreteMaterial"), 155 | MaskName((int)irsdk_AstroturfMaterial, "AstroturfMaterial"), 156 | }; 157 | 158 | const std::vector CAR_BESIDE = { 159 | MaskName((int)irsdk_LROff, "LROff"), 160 | MaskName((int)irsdk_LRClear, "LRClear"), // no cars around us. 161 | MaskName((int)irsdk_LRCarLeft, "LRCarLeft"), // there is a car to our left. 162 | MaskName((int)irsdk_LRCarRight, "LRCarRight"), // there is a car to our right. 163 | MaskName((int)irsdk_LRCarLeftRight, "LRCarLeftRight"), // there are cars on each side. 164 | MaskName((int)irsdk_LR2CarsLeft, "LR2CarsLeft"), // there are two cars to our left. 165 | MaskName((int)irsdk_LR2CarsRight, "LR2CarsRight") // there are two cars to our right. 166 | }; 167 | 168 | const std::vector PACE_MODE = { 169 | MaskName((int)irsdk_PaceModeSingleFileStart, "PaceModeSingleFile"), 170 | MaskName((int)irsdk_PaceModeDoubleFileStart, "PaceModeDoubleFile"), 171 | MaskName((int)irsdk_PaceModeSingleFileRestart, "PaceModeSingleFileRestart"), 172 | MaskName((int)irsdk_PaceModeDoubleFileRestart, "PaceModeDoubleFileRestart"), 173 | MaskName((int)irsdk_PaceModeNotPacing, "PaceModeNotPacing"), 174 | }; 175 | 176 | const std::vector PACE_FLAGS = { 177 | MaskName((int)irsdk_PaceFlagsEndOfLine, "PaceFlagsEndOfLine"), 178 | MaskName((int)irsdk_PaceFlagsFreePass, "PaceFlagsFreePass"), 179 | MaskName((int)irsdk_PaceFlagsFreePass, "PaceFlagsFreePass"), 180 | }; 181 | 182 | const std::vector TRACK_WETNESS = { 183 | MaskName((int)irsdk_TrackWetness_UNKNOWN, "Unknown"), 184 | MaskName((int)irsdk_TrackWetness_Dry, "Dry"), 185 | MaskName((int)irsdk_TrackWetness_MostlyDry, "MostlyDry"), 186 | MaskName((int)irsdk_TrackWetness_VeryLightlyWet, "VeryLightlyWet"), 187 | MaskName((int)irsdk_TrackWetness_LightlyWet, "LightlyWet"), 188 | MaskName((int)irsdk_TrackWetness_ModeratelyWet, "ModeratelyWet"), 189 | MaskName((int)irsdk_TrackWetness_VeryWet, "VeryWet"), 190 | MaskName((int)irsdk_TrackWetness_ExtremelyWet, "ExtremelyWet"), 191 | }; 192 | 193 | const std::vector INCIDENT_FLAGS = { 194 | MaskName((int)irsdk_Incident_RepNoReport, "NoPenalty(0x)"), // no penalty 195 | MaskName((int)irsdk_Incident_RepOutOfControl, "LossOfControll(2x)"), // "Loss of Control (2x)" 196 | MaskName((int)irsdk_Incident_RepOffTrack, "OffTrack(1x)"), // "Off Track (1x)" 197 | MaskName((int)irsdk_Incident_RepOffTrackOngoing, "OffTrackOngoing(0x)"), // not currently sent 198 | MaskName((int)irsdk_Incident_RepContactWithWorld, "Contact(0x)"), // "Contact (0x)" 199 | MaskName((int)irsdk_Incident_RepCollisionWithWorld, "Contact(2x)"), // "Contact (2x)" 200 | MaskName((int)irsdk_Incident_RepCollisionWithWorldOngoing, "Contact(0x)"), // not currently sent 201 | MaskName((int)irsdk_Incident_RepContactWithCar, "CarContact(0x)"), // "Car Contact (0x)" 202 | MaskName((int)irsdk_Incident_RepCollisionWithCar, "CarContact(4x)"), // "Car Contact (4x)" 203 | }; 204 | 205 | }; // namespace NodeIrSdk 206 | -------------------------------------------------------------------------------- /src/JsIrSdk.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('node:events'); 2 | const stringToEnum = require('./utils/stringToEnum'); 3 | const createSessionInfoParser = require('./utils/createSessionInfoParser'); 4 | const padCarNum = require('./utils/padCarNum'); 5 | const IrSdkConsts = require('./consts/IrSdkConsts'); 6 | const BroadcastMsg = IrSdkConsts.BroadcastMsg; 7 | 8 | /** 9 | JsIrSdk is javascript implementation of iRacing SDK. 10 | 11 | Don't use constructor directly, use {@link module:irsdk.getInstance}. 12 | 13 | @class 14 | @extends events.EventEmitter 15 | @see {@link https://nodejs.org/api/events.html#events_class_eventemitter|EventEmitter API} 16 | @alias iracing 17 | @fires iracing#Connected 18 | @fires iracing#Disconnected 19 | @fires iracing#Telemetry 20 | @fires iracing#TelemetryDescription 21 | @fires iracing#SessionInfo 22 | 23 | @example const iracing = require('iracing-sdk-js').getInstance() 24 | */ 25 | class JsIrSdk extends EventEmitter { 26 | constructor(IrSdkWrapper, opts) { 27 | super(); 28 | 29 | this.IrSdkWrapper = IrSdkWrapper; 30 | opts = opts || {}; 31 | 32 | /** Execute any of available commands, excl. FFB command 33 | @method 34 | @param {Integer} msgId Message id 35 | @param {Integer} [arg1] 1st argument 36 | @param {Integer} [arg2] 2nd argument 37 | @param {Integer} [arg3] 3rd argument 38 | */ 39 | this.execCmd = this.IrSdkWrapper.sendCmd; 40 | 41 | /** 42 | Parser for SessionInfo YAML 43 | @callback iracing~sessionInfoParser 44 | @param {String} sessionInfo SessionInfo YAML 45 | @returns {Object} parsed session info 46 | */ 47 | this.sessionInfoParser = opts.sessionInfoParser; 48 | if (!this.sessionInfoParser) { 49 | this.sessionInfoParser = createSessionInfoParser(); 50 | } 51 | 52 | this.connected = false; // if irsdk is available 53 | 54 | this.startIntervalId = setInterval(() => { 55 | if (!this.IrSdkWrapper.isInitialized()) { 56 | this.IrSdkWrapper.start(); 57 | } 58 | }, 10000); 59 | 60 | this.IrSdkWrapper.start(); 61 | 62 | /** Latest telemetry, may be null or undefined 63 | 64 | */ 65 | this.telemetry = null; 66 | 67 | /** Latest telemetry, may be null or undefined 68 | 69 | */ 70 | this.telemetryDescription = null; 71 | 72 | /** Latest telemetry, may be null or undefined 73 | 74 | */ 75 | this.sessionInfo = null; 76 | 77 | this.telemetryIntervalId = setInterval(() => { 78 | this.checkConnection(); 79 | if (this.connected && IrSdkWrapper.updateTelemetry()) { 80 | var now = new Date(); // date gives ms accuracy 81 | this.telemetry = IrSdkWrapper.getTelemetry(); 82 | // replace ctime timestamp 83 | this.telemetry.timestamp = now; 84 | setImmediate(() => { 85 | if (!this.telemetryDescription) { 86 | this.telemetryDescription = IrSdkWrapper.getTelemetryDescription(); 87 | /** 88 | Telemetry description, contains description of available telemetry values 89 | @event iracing#TelemetryDescription 90 | @type Object 91 | @example 92 | * iracing.on('TelemetryDescription', function (data) { 93 | * console.log(evt) 94 | * }) 95 | */ 96 | this.emit('update', { 97 | type: 'TelemetryDescription', 98 | data: this.telemetryDescription, 99 | timestamp: now, 100 | }); 101 | } 102 | /** 103 | Telemetry update 104 | @event iracing#Telemetry 105 | @type Object 106 | @example 107 | * iracing.on('Telemetry', function (evt) { 108 | * console.log(evt) 109 | * }) 110 | */ 111 | this.emit('update', { 112 | type: 'Telemetry', 113 | data: this.telemetry.values, 114 | timestamp: now, 115 | }); 116 | }); 117 | } 118 | }, opts.telemetryUpdateInterval); 119 | 120 | this.sessionInfoIntervalId = setInterval(() => { 121 | this.checkConnection(); 122 | if (this.connected && IrSdkWrapper.updateSessionInfo()) { 123 | var now = new Date(); 124 | var sessionInfo = IrSdkWrapper.getSessionInfo(); 125 | var doc; 126 | setImmediate(() => { 127 | try { 128 | doc = this.sessionInfoParser(sessionInfo); 129 | } catch (ex) { 130 | // TODO: log faulty yaml 131 | console.error('js-irsdk: yaml error: \n' + ex); 132 | } 133 | 134 | if (doc) { 135 | this.sessionInfo = { timestamp: now, data: doc }; 136 | /** 137 | SessionInfo update 138 | @event iracing#SessionInfo 139 | @type Object 140 | @example 141 | * iracing.on('SessionInfo', function (evt) { 142 | * console.log(evt) 143 | * }) 144 | */ 145 | this.emit('update', { 146 | type: 'SessionInfo', 147 | data: this.sessionInfo.data, 148 | timestamp: now, 149 | }); 150 | } 151 | }); 152 | } 153 | }, opts.sessionInfoUpdateInterval); 154 | 155 | /** 156 | any update event 157 | @event iracing#update 158 | @type Object 159 | @example 160 | * iracing.on('update', function (evt) { 161 | * console.log(evt) 162 | * }) 163 | */ 164 | this.on('update', (evt) => { 165 | // fire old events as well. 166 | const timestamp = evt.timestamp; 167 | const data = evt.data; 168 | const type = evt.type; 169 | 170 | switch (type) { 171 | case 'SessionInfo': 172 | this.emit('SessionInfo', { timestamp, data }); 173 | break; 174 | case 'Telemetry': 175 | this.emit('Telemetry', { timestamp, values: data }); 176 | break; 177 | case 'TelemetryDescription': 178 | this.emit('TelemetryDescription', data); 179 | break; 180 | case 'Connected': 181 | this.emit('Connected'); 182 | break; 183 | case 'Disconnected': 184 | this.emit('Disconnected'); 185 | break; 186 | default: 187 | break; 188 | } 189 | }); 190 | } 191 | 192 | checkConnection() { 193 | if (this.IrSdkWrapper.isInitialized() && this.IrSdkWrapper.isConnected()) { 194 | if (!this.connected) { 195 | this.connected = true; 196 | /** 197 | iRacing, sim, is started 198 | @event iracing#Connected 199 | @example 200 | * iracing.on('Connected', function (evt) { 201 | * console.log(evt) 202 | * }) 203 | */ 204 | this.emit('update', { type: 'Connected', timestamp: new Date() }); 205 | } 206 | } else { 207 | if (this.connected) { 208 | this.connected = false; 209 | /** 210 | iRacing, sim, was closed 211 | @event iracing#Disconnected 212 | @example 213 | * iracing.on('Disconnected', function (evt) { 214 | * console.log(evt) 215 | * }) 216 | */ 217 | this.emit('update', { type: 'Disconnected', timestamp: new Date() }); 218 | 219 | this.IrSdkWrapper.shutdown(); 220 | this.telemetryDescription = null; 221 | } 222 | } 223 | } 224 | 225 | /** iRacing SDK related constants 226 | @type IrSdkConsts 227 | @instance 228 | */ 229 | Consts = IrSdkConsts; 230 | 231 | /** Camera controls 232 | @type {Object} 233 | */ 234 | camControls = { 235 | /** Change camera tool state 236 | @method 237 | @param {IrSdkConsts.CameraState} state new state 238 | @example 239 | * // hide UI and enable mouse aim 240 | * var States = iracing.Consts.CameraState 241 | * var state = States.CamToolActive | States.UIHidden | States.UseMouseAimMode 242 | * iracing.camControls.setState(state) 243 | */ 244 | setState: (state) => { 245 | this.execCmd(BroadcastMsg.CamSetState, state); 246 | }, 247 | /** Switch camera, focus on car 248 | @method 249 | @param {Integer|String|IrSdkConsts.CamFocusAt} carNum Car to focus on 250 | @param {Integer} [camGroupNum] Select camera group 251 | @param {Integer} [camNum] Select camera 252 | 253 | @example 254 | * // show car #2 255 | * iracing.camControls.switchToCar(2) 256 | @example 257 | * // show car #02 258 | * iracing.camControls.switchToCar('02') 259 | @example 260 | * // show leader 261 | * iracing.camControls.switchToCar('leader') 262 | @example 263 | * // show car #2 using cam group 3 264 | * iracing.camControls.switchToCar(2, 3) 265 | */ 266 | switchToCar: (carNum, camGroupNum, camNum) => { 267 | camGroupNum = camGroupNum | 0; 268 | camNum = camNum | 0; 269 | 270 | if (typeof carNum === 'string') { 271 | if (isNaN(parseInt(carNum))) { 272 | carNum = stringToEnum(carNum, IrSdkConsts.CamFocusAt); 273 | } else { 274 | carNum = padCarNum(carNum); 275 | } 276 | } 277 | if (Number.isInteger(carNum)) { 278 | this.execCmd(BroadcastMsg.CamSwitchNum, carNum, camGroupNum, camNum); 279 | } 280 | }, 281 | /** Switch camera, focus on position 282 | @method 283 | @param {Integer|IrSdkConsts.CamFocusAt} position Position to focus on 284 | @param {Integer} [camGroupNum] Select camera group 285 | @param {Integer} [camNum] Select camera 286 | 287 | @example iracing.camControls.switchToPos(2) // show P2 288 | */ 289 | switchToPos: (position, camGroupNum, camNum) => { 290 | camGroupNum = camGroupNum | 0; 291 | camNum = camNum | 0; 292 | 293 | if (typeof position === 'string') { 294 | position = stringToEnum(position, IrSdkConsts.CamFocusAt); 295 | } 296 | if (Number.isInteger(position)) { 297 | this.execCmd(BroadcastMsg.CamSwitchPos, position, camGroupNum, camNum); 298 | } 299 | }, 300 | }; 301 | 302 | /** Replay and playback controls 303 | @type {Object} 304 | */ 305 | playbackControls = { 306 | /** Play replay 307 | @method 308 | @example iracing.playbackControls.play() 309 | */ 310 | play: () => { 311 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, 1, 0); 312 | }, 313 | /** Pause replay 314 | @method 315 | @example iracing.playbackControls.pause() 316 | */ 317 | pause: () => { 318 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, 0, 0); 319 | }, 320 | /** fast-forward replay 321 | @method 322 | @param {Integer} [speed=2] FF speed, something between 2-16 works 323 | @example iracing.playbackControls.fastForward() // double speed FF 324 | */ 325 | fastForward: (speed) => { 326 | speed = speed || 2; 327 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, speed, 0); 328 | }, 329 | /** rewind replay 330 | @method 331 | @param {Integer} [speed=2] RW speed, something between 2-16 works 332 | @example iracing.playbackControls.rewind() // double speed RW 333 | */ 334 | rewind: (speed) => { 335 | speed = speed || 2; 336 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, -1 * speed, 0); 337 | }, 338 | /** slow-forward replay, slow motion 339 | @method 340 | @param {Integer} [divider=2] divider of speed, something between 2-17 works 341 | @example iracing.playbackControls.slowForward(2) // half speed 342 | */ 343 | slowForward: (divider) => { 344 | divider = divider || 2; 345 | divider -= 1; 346 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, divider, 1); 347 | }, 348 | /** slow-backward replay, reverse slow motion 349 | @method 350 | @param {Integer} [divider=2] divider of speed, something between 2-17 works 351 | @example iracing.playbackControls.slowBackward(2) // half speed RW 352 | */ 353 | slowBackward: (divider) => { 354 | divider = divider || 2; 355 | divider -= 1; 356 | this.execCmd(BroadcastMsg.ReplaySetPlaySpeed, -1 * divider, 1); 357 | }, 358 | /** Search things from replay 359 | @method 360 | @param {IrSdkConsts.RpySrchMode} searchMode what to search 361 | @example iracing.playbackControls.search('nextIncident') 362 | */ 363 | search: (searchMode) => { 364 | if (typeof searchMode === 'string') { 365 | searchMode = stringToEnum(searchMode, IrSdkConsts.RpySrchMode); 366 | } 367 | if (Number.isInteger(searchMode)) { 368 | this.execCmd(BroadcastMsg.ReplaySearch, searchMode); 369 | } 370 | }, 371 | /** Search timestamp 372 | @method 373 | @param {Integer} sessionNum Session number 374 | @param {Integer} sessionTimeMS Session time in milliseconds 375 | @example 376 | * // jump to 2nd minute of 3rd session 377 | * iracing.playbackControls.searchTs(2, 2*60*1000) 378 | */ 379 | searchTs: (sessionNum, sessionTimeMS) => { 380 | this.execCmd( 381 | BroadcastMsg.ReplaySearchSessionTime, 382 | sessionNum, 383 | sessionTimeMS 384 | ); 385 | }, 386 | /** Go to frame. Frame counting can be relative to begin, end or current. 387 | @method 388 | @param {Integer} frameNum Frame number 389 | @param {IrSdkConsts.RpyPosMode} rpyPosMode Is frame number relative to begin, end or current frame 390 | @example iracing.playbackControls.searchFrame(1, 'current') // go to 1 frame forward 391 | */ 392 | searchFrame: (frameNum, rpyPosMode) => { 393 | if (typeof rpyPosMode === 'string') { 394 | rpyPosMode = stringToEnum(rpyPosMode, IrSdkConsts.RpyPosMode); 395 | } 396 | if (Number.isInteger(rpyPosMode)) { 397 | this.execCmd(BroadcastMsg.ReplaySetPlayPosition, rpyPosMode, frameNum); 398 | } 399 | }, 400 | }; 401 | 402 | /** Reload all car textures 403 | @method 404 | @example iracing.reloadTextures() // reload all paints 405 | */ 406 | reloadTextures() { 407 | this.execCmd( 408 | BroadcastMsg.ReloadTextures, 409 | IrSdkConsts.ReloadTexturesMode.All 410 | ); 411 | } 412 | 413 | /** Reload car's texture 414 | @method 415 | @param {Integer} carIdx car to reload 416 | @example iracing.reloadTexture(1) // reload paint of carIdx=1 417 | */ 418 | reloadTexture(carIdx) { 419 | this.execCmd( 420 | BroadcastMsg.ReloadTextures, 421 | IrSdkConsts.ReloadTexturesMode.CarIdx, 422 | carIdx 423 | ); 424 | } 425 | 426 | /** Execute chat command 427 | @param {IrSdkConsts.ChatCommand} cmd 428 | @param {Integer} [arg] Command argument, if needed 429 | @example iracing.execChatCmd('cancel') // close chat window 430 | */ 431 | execChatCmd(cmd, arg) { 432 | arg = arg || 0; 433 | if (typeof cmd === 'string') { 434 | cmd = stringToEnum(cmd, IrSdkConsts.ChatCommand); 435 | } 436 | if (Number.isInteger(cmd)) { 437 | this.execCmd(BroadcastMsg.ChatComand, cmd, arg); 438 | } 439 | } 440 | 441 | /** Execute chat macro 442 | @param {Integer} num Macro's number (0-15) 443 | @example iracing.execChatMacro(1) // macro 1 444 | */ 445 | execChatMacro(num) { 446 | this.execChatCmd('macro', num); 447 | } 448 | 449 | /** Execute pit command 450 | @param {IrSdkConsts.PitCommand} cmd 451 | @param {Integer} [arg] Command argument, if needed 452 | @example 453 | * // full tank, no tires, no tear off 454 | * iracing.execPitCmd('clear') 455 | * iracing.execPitCmd('fuel', 999) // 999 liters 456 | * iracing.execPitCmd('lf') // new left front 457 | * iracing.execPitCmd('lr', 200) // new left rear, 200 kPa 458 | */ 459 | execPitCmd(cmd, arg) { 460 | arg = arg || 0; 461 | if (typeof cmd === 'string') { 462 | cmd = stringToEnum(cmd, IrSdkConsts.PitCommand); 463 | } 464 | if (Number.isInteger(cmd)) { 465 | this.execCmd(BroadcastMsg.PitCommand, cmd, arg); 466 | } 467 | } 468 | 469 | /** Control telemetry logging (ibt file) 470 | @param {IrSdkConsts.TelemCommand} cmd Command: start/stop/restart 471 | @example iracing.execTelemetryCmd('restart') 472 | */ 473 | execTelemetryCmd(cmd) { 474 | if (typeof cmd === 'string') { 475 | cmd = stringToEnum(cmd, IrSdkConsts.TelemCommand); 476 | } 477 | if (Number.isInteger(cmd)) { 478 | this.execCmd(BroadcastMsg.TelemCommand, cmd); 479 | } 480 | } 481 | 482 | /** 483 | Stops JsIrSdk, no new events are fired after calling this 484 | @method 485 | @private 486 | */ 487 | _stop() { 488 | clearInterval(this.telemetryIntervalId); 489 | clearInterval(this.sessionInfoIntervalId); 490 | clearInterval(this.startIntervalId); 491 | this.IrSdkWrapper.shutdown(); 492 | } 493 | } 494 | 495 | module.exports = JsIrSdk; 496 | -------------------------------------------------------------------------------- /src/cpp/irsdk/irsdk_defines.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2013, iRacing.com Motorsport Simulations, LLC. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of iRacing.com Motorsport Simulations nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR 20 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | */ 27 | 28 | #ifndef IRSDK_DEFINES_H 29 | #define IRSDK_DEFINES_H 30 | 31 | #include 32 | #include 33 | 34 | /* 35 | The IRSDK is a simple api that lets clients access telemetry data from the 36 | iRacing simulator. It is broken down into several parts: 37 | 38 | - Live data 39 | Live data is output from the sim into a shared memory mapped file. Any 40 | application can open this memory mapped file and read the telemetry data 41 | out. The format of this data was laid out in such a way that it should be 42 | possible to access from any language that can open a windows memory mapped 43 | file, without needing an external api. 44 | 45 | There are two different types of data that the telemetry outputs, 46 | sessionInfo and variables: 47 | 48 | Session info is for data that only needs to be updated every once in a 49 | while. This data is output as a YAML formatted string. 50 | 51 | Variables, on the other hand, are output at a rate of 60 times a second. 52 | The varHeader struct defines each variable that the sim will output, while 53 | the varData struct gives details about the current line buffer that the vars 54 | are being written into. Each variable is packed into a binary array with 55 | an offset and length stored in the varHeader. The number of variables 56 | available can change depending on the car or session loaded. But once the 57 | sim is running the variable list is locked down and will not change during a 58 | session. 59 | 60 | The sim writes a new line of variables every 16 ms, and then signals any 61 | listeners in order to wake them up to read the data. Because the sim has no 62 | way of knowing when a listener is done reading the data, we triple buffer 63 | it in order to give all the clients enough time to read the data out. This 64 | gives you a minimum of 16 ms to read the data out and process it. So it is 65 | best to copy the data out before processing it. You can use the function 66 | irsdk_waitForDataReady() to both wait for new data and copy the data to a 67 | local buffer. 68 | 69 | - Logged data 70 | Detailed information about the local drivers car can be logged to disk in 71 | the form of an ibt binary file. This logging is enabled in the sim by 72 | typing alt-L at any time. The ibt file format directly mirrors the format 73 | of the live data. 74 | 75 | It is stored as an irsdk_header followed immediately by an irsdk_diskSubHeader. 76 | After that the offsets in the irsdk_header point to the sessionInfo string, 77 | the varHeader, and the varBuffer. 78 | 79 | - Remote Conrol 80 | You can control the camera selections and playback of a replay tape, from 81 | any external application by sending a windows message with the 82 | irsdk_broadcastMsg() function. 83 | */ 84 | 85 | // Constant Definitions 86 | 87 | #ifdef _WIN32 88 | #include 89 | 90 | static const _TCHAR IRSDK_DATAVALIDEVENTNAME[] = _T("Local\\IRSDKDataValidEvent"); 91 | static const _TCHAR IRSDK_MEMMAPFILENAME[] = _T("Local\\IRSDKMemMapFileName"); 92 | static const _TCHAR IRSDK_BROADCASTMSGNAME[] = _T("IRSDK_BROADCASTMSG"); 93 | 94 | #else 95 | 96 | static const char *IRSDK_DATAVALIDEVENTNAME = "Local\\IRSDKDataValidEvent"; 97 | static const char *IRSDK_MEMMAPFILENAME = "Local\\IRSDKMemMapFileName"; 98 | static const char *IRSDK_BROADCASTMSGNAME = "IRSDK_BROADCASTMSG"; 99 | #endif 100 | 101 | static const int IRSDK_MAX_BUFS = 4; 102 | static const int IRSDK_MAX_STRING = 32; 103 | // descriptions can be longer than max_string! 104 | static const int IRSDK_MAX_DESC = 64; 105 | 106 | // define markers for unlimited session lap and time 107 | static const int IRSDK_UNLIMITED_LAPS = 32767; 108 | static const float IRSDK_UNLIMITED_TIME = 604800.0f; 109 | 110 | // latest version of our telemetry headers 111 | static const int IRSDK_VER = 2; 112 | 113 | enum irsdk_StatusField 114 | { 115 | irsdk_stConnected = 1 116 | }; 117 | 118 | enum irsdk_VarType 119 | { 120 | // 1 byte 121 | irsdk_char = 0, 122 | irsdk_bool, 123 | 124 | // 4 bytes 125 | irsdk_int, 126 | irsdk_bitField, 127 | irsdk_float, 128 | 129 | // 8 bytes 130 | irsdk_double, 131 | 132 | // index, don't use 133 | irsdk_ETCount 134 | }; 135 | 136 | static const int irsdk_VarTypeBytes[irsdk_ETCount] = 137 | { 138 | 1, // irsdk_char 139 | 1, // irsdk_bool 140 | 141 | 4, // irsdk_int 142 | 4, // irsdk_bitField 143 | 4, // irsdk_float 144 | 145 | 8 // irsdk_double 146 | }; 147 | 148 | //--- 149 | 150 | // status 151 | enum irsdk_TrkLoc 152 | { 153 | irsdk_NotInWorld = -1, 154 | irsdk_OffTrack = 0, 155 | irsdk_InPitStall, 156 | // This indicates the lead in to pit road, as well as the pit road itself (where speed limits are enforced) 157 | // if you just want to know that your on the pit road surface look at the live value 'OnPitRoad' 158 | irsdk_AproachingPits, 159 | irsdk_OnTrack 160 | }; 161 | 162 | enum irsdk_TrkSurf 163 | { 164 | irsdk_SurfaceNotInWorld = -1, 165 | irsdk_UndefinedMaterial = 0, 166 | 167 | irsdk_Asphalt1Material, 168 | irsdk_Asphalt2Material, 169 | irsdk_Asphalt3Material, 170 | irsdk_Asphalt4Material, 171 | irsdk_Concrete1Material, 172 | irsdk_Concrete2Material, 173 | irsdk_RacingDirt1Material, 174 | irsdk_RacingDirt2Material, 175 | irsdk_Paint1Material, 176 | irsdk_Paint2Material, 177 | irsdk_Rumble1Material, 178 | irsdk_Rumble2Material, 179 | irsdk_Rumble3Material, 180 | irsdk_Rumble4Material, 181 | 182 | irsdk_Grass1Material, 183 | irsdk_Grass2Material, 184 | irsdk_Grass3Material, 185 | irsdk_Grass4Material, 186 | irsdk_Dirt1Material, 187 | irsdk_Dirt2Material, 188 | irsdk_Dirt3Material, 189 | irsdk_Dirt4Material, 190 | irsdk_SandMaterial, 191 | irsdk_Gravel1Material, 192 | irsdk_Gravel2Material, 193 | irsdk_GrasscreteMaterial, 194 | irsdk_AstroturfMaterial, 195 | }; 196 | 197 | enum irsdk_SessionState 198 | { 199 | irsdk_StateInvalid = 0, 200 | irsdk_StateGetInCar, 201 | irsdk_StateWarmup, 202 | irsdk_StateParadeLaps, 203 | irsdk_StateRacing, 204 | irsdk_StateCheckered, 205 | irsdk_StateCoolDown 206 | }; 207 | 208 | enum irsdk_CarLeftRight 209 | { 210 | irsdk_LROff = 0, 211 | irsdk_LRClear, // no cars around us. 212 | irsdk_LRCarLeft, // there is a car to our left. 213 | irsdk_LRCarRight, // there is a car to our right. 214 | irsdk_LRCarLeftRight, // there are cars on each side. 215 | irsdk_LR2CarsLeft, // there are two cars to our left. 216 | irsdk_LR2CarsRight // there are two cars to our right. 217 | }; 218 | 219 | enum irsdk_PitSvStatus 220 | { 221 | // status 222 | irsdk_PitSvNone = 0, 223 | irsdk_PitSvInProgress, 224 | irsdk_PitSvComplete, 225 | 226 | // errors 227 | irsdk_PitSvTooFarLeft = 100, 228 | irsdk_PitSvTooFarRight, 229 | irsdk_PitSvTooFarForward, 230 | irsdk_PitSvTooFarBack, 231 | irsdk_PitSvBadAngle, 232 | irsdk_PitSvCantFixThat, 233 | }; 234 | 235 | enum irsdk_PaceMode 236 | { 237 | irsdk_PaceModeSingleFileStart = 0, 238 | irsdk_PaceModeDoubleFileStart, 239 | irsdk_PaceModeSingleFileRestart, 240 | irsdk_PaceModeDoubleFileRestart, 241 | irsdk_PaceModeNotPacing, 242 | }; 243 | 244 | enum irsdk_TrackWetness 245 | { 246 | irsdk_TrackWetness_UNKNOWN = 0, 247 | irsdk_TrackWetness_Dry, 248 | irsdk_TrackWetness_MostlyDry, 249 | irsdk_TrackWetness_VeryLightlyWet, 250 | irsdk_TrackWetness_LightlyWet, 251 | irsdk_TrackWetness_ModeratelyWet, 252 | irsdk_TrackWetness_VeryWet, 253 | irsdk_TrackWetness_ExtremelyWet 254 | }; 255 | 256 | enum irsdk_IncidentFlags 257 | { 258 | // first byte is incident report flag 259 | // only one of these will be used 260 | irsdk_Incident_RepNoReport = 0x0000, // no penalty 261 | irsdk_Incident_RepOutOfControl = 0x0001, // "Loss of Control (2x)" 262 | irsdk_Incident_RepOffTrack = 0x0002, // "Off Track (1x)" 263 | irsdk_Incident_RepOffTrackOngoing = 0x0003, // not currently sent 264 | irsdk_Incident_RepContactWithWorld = 0x0004, // "Contact (0x)" 265 | irsdk_Incident_RepCollisionWithWorld = 0x0005, // "Contact (2x)" 266 | irsdk_Incident_RepCollisionWithWorldOngoing = 0x0006, // not currently sent 267 | irsdk_Incident_RepContactWithCar = 0x0007, // "Car Contact (0x)" 268 | irsdk_Incident_RepCollisionWithCar = 0x0008, // "Car Contact (4x)" 269 | 270 | // second byte is incident penalty 271 | // only one of these will be used 272 | irsdk_Incident_PenNoReport = 0x0000, // no penalty 273 | irsdk_Incident_PenZeroX = 0x0100, // 0x 274 | irsdk_Incident_PenOneX = 0x0200, // 1x 275 | irsdk_Incident_PenTwoX = 0x0300, // 2x 276 | irsdk_Incident_PenFourX = 0x0400, // 4x 277 | 278 | // not enums, used to seperate the above incident report field 279 | // from the incident penalty field 280 | IRSDK_INCIDENT_REP_MASK = 0x000000FF, 281 | IRSDK_INCIDENT_PEN_MASK = 0x0000FF00, 282 | }; 283 | 284 | //--- 285 | 286 | // bit fields 287 | enum irsdk_EngineWarnings 288 | { 289 | irsdk_waterTempWarning = 0x0001, 290 | irsdk_fuelPressureWarning = 0x0002, 291 | irsdk_oilPressureWarning = 0x0004, 292 | irsdk_engineStalled = 0x0008, 293 | irsdk_pitSpeedLimiter = 0x0010, 294 | irsdk_revLimiterActive = 0x0020, 295 | irsdk_oilTempWarning = 0x0040, 296 | irsdk_mandRepNeeded = 0x0080, // car needs mandatory repairs 297 | irsdk_optRepNeeded = 0x0100, // car needs optional repairs 298 | }; 299 | 300 | // global flags 301 | enum irsdk_Flags 302 | { 303 | // global flags 304 | irsdk_checkered = 0x00000001, 305 | irsdk_white = 0x00000002, 306 | irsdk_green = 0x00000004, 307 | irsdk_yellow = 0x00000008, 308 | irsdk_red = 0x00000010, 309 | irsdk_blue = 0x00000020, 310 | irsdk_debris = 0x00000040, 311 | irsdk_crossed = 0x00000080, 312 | irsdk_yellowWaving = 0x00000100, 313 | irsdk_oneLapToGreen = 0x00000200, 314 | irsdk_greenHeld = 0x00000400, 315 | irsdk_tenToGo = 0x00000800, 316 | irsdk_fiveToGo = 0x00001000, 317 | irsdk_randomWaving = 0x00002000, 318 | irsdk_caution = 0x00004000, 319 | irsdk_cautionWaving = 0x00008000, 320 | 321 | // drivers black flags 322 | irsdk_black = 0x00010000, 323 | irsdk_disqualify = 0x00020000, 324 | irsdk_servicible = 0x00040000, // car is allowed service (not a flag) 325 | irsdk_furled = 0x00080000, 326 | irsdk_repair = 0x00100000, 327 | irsdk_dqScoringInvalid = 0x00200000, // car is disqualified and scoring is disabled 328 | 329 | // start lights 330 | irsdk_startHidden = 0x10000000, 331 | irsdk_startReady = 0x20000000, 332 | irsdk_startSet = 0x40000000, 333 | irsdk_startGo = 0x80000000, 334 | }; 335 | 336 | enum irsdk_CameraState 337 | { 338 | irsdk_IsSessionScreen = 0x0001, // the camera tool can only be activated if viewing the session screen (out of car) 339 | irsdk_IsScenicActive = 0x0002, // the scenic camera is active (no focus car) 340 | 341 | // these can be changed with a broadcast message 342 | irsdk_CamToolActive = 0x0004, 343 | irsdk_UIHidden = 0x0008, 344 | irsdk_UseAutoShotSelection = 0x0010, 345 | irsdk_UseTemporaryEdits = 0x0020, 346 | irsdk_UseKeyAcceleration = 0x0040, 347 | irsdk_UseKey10xAcceleration = 0x0080, 348 | irsdk_UseMouseAimMode = 0x0100 349 | }; 350 | 351 | enum irsdk_PitSvFlags 352 | { 353 | irsdk_LFTireChange = 0x0001, 354 | irsdk_RFTireChange = 0x0002, 355 | irsdk_LRTireChange = 0x0004, 356 | irsdk_RRTireChange = 0x0008, 357 | 358 | irsdk_FuelFill = 0x0010, 359 | irsdk_WindshieldTearoff = 0x0020, 360 | irsdk_FastRepair = 0x0040 361 | }; 362 | 363 | enum irsdk_PaceFlags 364 | { 365 | irsdk_PaceFlagsEndOfLine = 0x0001, 366 | irsdk_PaceFlagsFreePass = 0x0002, 367 | irsdk_PaceFlagsWavedAround = 0x0004, 368 | }; 369 | 370 | //---- 371 | // 372 | 373 | struct irsdk_varHeader 374 | { 375 | int type; // irsdk_VarType 376 | int offset; // offset fron start of buffer row 377 | int count; // number of entrys (array) 378 | // so length in bytes would be irsdk_VarTypeBytes[type] * count 379 | bool countAsTime; 380 | char pad[3]; // (16 byte align) 381 | 382 | char name[IRSDK_MAX_STRING]; 383 | char desc[IRSDK_MAX_DESC]; 384 | char unit[IRSDK_MAX_STRING]; // something like "kg/m^2" 385 | 386 | void clear() 387 | { 388 | type = 0; 389 | offset = 0; 390 | count = 0; 391 | countAsTime = false; 392 | memset(name, 0, sizeof(name)); 393 | memset(desc, 0, sizeof(name)); 394 | memset(unit, 0, sizeof(name)); 395 | } 396 | }; 397 | 398 | struct irsdk_varBuf 399 | { 400 | int tickCount; // used to detect changes in data 401 | int bufOffset; // offset from header 402 | int pad[2]; // (16 byte align) 403 | }; 404 | 405 | struct irsdk_header 406 | { 407 | int ver; // this api header version, see IRSDK_VER 408 | int status; // bitfield using irsdk_StatusField 409 | int tickRate; // ticks per second (60 or 360 etc) 410 | 411 | // session information, updated periodicaly 412 | int sessionInfoUpdate; // Incremented when session info changes 413 | int sessionInfoLen; // Length in bytes of session info string 414 | int sessionInfoOffset; // Session info, encoded in YAML format 415 | 416 | // State data, output at tickRate 417 | 418 | int numVars; // length of arra pointed to by varHeaderOffset 419 | int varHeaderOffset; // offset to irsdk_varHeader[numVars] array, Describes the variables received in varBuf 420 | 421 | int numBuf; // <= IRSDK_MAX_BUFS (3 for now) 422 | int bufLen; // length in bytes for one line 423 | //****ToDo, add these in 424 | // int curBufTickCount; // stashed copy of the current tickCount, can read this to see if new data is available 425 | // byte curBuf; // index of the most recently written buffer (0 to IRSDK_MAX_BUFS-1) 426 | // byte pad1[3]; // 16 byte align 427 | int pad1[2]; // (16 byte align) 428 | irsdk_varBuf varBuf[IRSDK_MAX_BUFS]; // buffers of data being written to 429 | }; 430 | 431 | // sub header used when writing telemetry to disk 432 | struct irsdk_diskSubHeader 433 | { 434 | time_t sessionStartDate; 435 | double sessionStartTime; 436 | double sessionEndTime; 437 | int sessionLapCount; 438 | int sessionRecordCount; 439 | }; 440 | 441 | //---- 442 | // Client function definitions 443 | 444 | bool irsdk_startup(); 445 | void irsdk_shutdown(); 446 | 447 | bool irsdk_getNewData(char *data); 448 | bool irsdk_waitForDataReady(int timeOut, char *data); 449 | bool irsdk_isConnected(); 450 | 451 | const irsdk_header *irsdk_getHeader(); 452 | const char *irsdk_getData(int index); 453 | const char *irsdk_getSessionInfoStr(); 454 | int irsdk_getSessionInfoStrUpdate(); // incrementing index that indicates new session info string 455 | 456 | const irsdk_varHeader *irsdk_getVarHeaderPtr(); 457 | const irsdk_varHeader *irsdk_getVarHeaderEntry(int index); 458 | 459 | int irsdk_varNameToIndex(const char *name); 460 | int irsdk_varNameToOffset(const char *name); 461 | 462 | //---- 463 | // Remote controll the sim by sending these windows messages 464 | // camera and replay commands only work when you are out of your car, 465 | // pit commands only work when in your car 466 | enum irsdk_BroadcastMsg 467 | { 468 | irsdk_BroadcastCamSwitchPos = 0, // car position, group, camera 469 | irsdk_BroadcastCamSwitchNum, // driver #, group, camera 470 | irsdk_BroadcastCamSetState, // irsdk_CameraState, unused, unused 471 | irsdk_BroadcastReplaySetPlaySpeed, // speed, slowMotion, unused 472 | irsdk_BroadcastReplaySetPlayPosition, // irsdk_RpyPosMode, Frame Number (high, low) 473 | irsdk_BroadcastReplaySearch, // irsdk_RpySrchMode, unused, unused 474 | irsdk_BroadcastReplaySetState, // irsdk_RpyStateMode, unused, unused 475 | irsdk_BroadcastReloadTextures, // irsdk_ReloadTexturesMode, carIdx, unused 476 | irsdk_BroadcastChatComand, // irsdk_ChatCommandMode, subCommand, unused 477 | irsdk_BroadcastPitCommand, // irsdk_PitCommandMode, parameter 478 | irsdk_BroadcastTelemCommand, // irsdk_TelemCommandMode, unused, unused 479 | irsdk_BroadcastFFBCommand, // irsdk_FFBCommandMode, value (float, high, low) 480 | irsdk_BroadcastReplaySearchSessionTime, // sessionNum, sessionTimeMS (high, low) 481 | irsdk_BroadcastVideoCapture, // irsdk_VideoCaptureMode, unused, unused 482 | irsdk_BroadcastLast // unused placeholder 483 | }; 484 | 485 | enum irsdk_ChatCommandMode 486 | { 487 | irsdk_ChatCommand_Macro = 0, // pass in a number from 1-15 representing the chat macro to launch 488 | irsdk_ChatCommand_BeginChat, // Open up a new chat window 489 | irsdk_ChatCommand_Reply, // Reply to last private chat 490 | irsdk_ChatCommand_Cancel // Close chat window 491 | }; 492 | 493 | enum irsdk_PitCommandMode // this only works when the driver is in the car 494 | { 495 | irsdk_PitCommand_Clear = 0, // Clear all pit checkboxes 496 | irsdk_PitCommand_WS, // Clean the winshield, using one tear off 497 | irsdk_PitCommand_Fuel, // Add fuel, optionally specify the amount to add in liters or pass '0' to use existing amount 498 | irsdk_PitCommand_LF, // Change the left front tire, optionally specifying the pressure in KPa or pass '0' to use existing pressure 499 | irsdk_PitCommand_RF, // right front 500 | irsdk_PitCommand_LR, // left rear 501 | irsdk_PitCommand_RR, // right rear 502 | irsdk_PitCommand_ClearTires, // Clear tire pit checkboxes 503 | irsdk_PitCommand_FR, // Request a fast repair 504 | irsdk_PitCommand_ClearWS, // Uncheck Clean the winshield checkbox 505 | irsdk_PitCommand_ClearFR, // Uncheck request a fast repair 506 | irsdk_PitCommand_ClearFuel, // Uncheck add fuel 507 | irsdk_PitCommand_TC, // Change tire compound 508 | }; 509 | 510 | enum irsdk_TelemCommandMode // You can call this any time, but telemtry only records when driver is in there car 511 | { 512 | irsdk_TelemCommand_Stop = 0, // Turn telemetry recording off 513 | irsdk_TelemCommand_Start, // Turn telemetry recording on 514 | irsdk_TelemCommand_Restart, // Write current file to disk and start a new one 515 | }; 516 | 517 | enum irsdk_RpyStateMode 518 | { 519 | irsdk_RpyState_EraseTape = 0, // clear any data in the replay tape 520 | irsdk_RpyState_Last // unused place holder 521 | }; 522 | 523 | enum irsdk_ReloadTexturesMode 524 | { 525 | irsdk_ReloadTextures_All = 0, // reload all textuers 526 | irsdk_ReloadTextures_CarIdx // reload only textures for the specific carIdx 527 | }; 528 | 529 | // Search replay tape for events 530 | enum irsdk_RpySrchMode 531 | { 532 | irsdk_RpySrch_ToStart = 0, 533 | irsdk_RpySrch_ToEnd, 534 | irsdk_RpySrch_PrevSession, 535 | irsdk_RpySrch_NextSession, 536 | irsdk_RpySrch_PrevLap, 537 | irsdk_RpySrch_NextLap, 538 | irsdk_RpySrch_PrevFrame, 539 | irsdk_RpySrch_NextFrame, 540 | irsdk_RpySrch_PrevIncident, 541 | irsdk_RpySrch_NextIncident, 542 | irsdk_RpySrch_Last // unused placeholder 543 | }; 544 | 545 | enum irsdk_RpyPosMode 546 | { 547 | irsdk_RpyPos_Begin = 0, 548 | irsdk_RpyPos_Current, 549 | irsdk_RpyPos_End, 550 | irsdk_RpyPos_Last // unused placeholder 551 | }; 552 | 553 | enum irsdk_FFBCommandMode // You can call this any time 554 | { 555 | irsdk_FFBCommand_MaxForce = 0, // Set the maximum force when mapping steering torque force to direct input units (float in Nm) 556 | irsdk_FFBCommand_Last // unused placeholder 557 | }; 558 | 559 | // irsdk_BroadcastCamSwitchPos or irsdk_BroadcastCamSwitchNum camera focus defines 560 | // pass these in for the first parameter to select the 'focus at' types in the camera system. 561 | enum irsdk_csMode 562 | { 563 | irsdk_csFocusAtIncident = -3, 564 | irsdk_csFocusAtLeader = -2, 565 | irsdk_csFocusAtExiting = -1, 566 | // ctFocusAtDriver + car number... 567 | irsdk_csFocusAtDriver = 0 568 | }; 569 | 570 | enum irsdk_VideoCaptureMode 571 | { 572 | irsdk_VideoCapture_TriggerScreenShot = 0, // save a screenshot to disk 573 | irsdk_VideoCaptuer_StartVideoCapture, // start capturing video 574 | irsdk_VideoCaptuer_EndVideoCapture, // stop capturing video 575 | irsdk_VideoCaptuer_ToggleVideoCapture, // toggle video capture on/off 576 | irsdk_VideoCaptuer_ShowVideoTimer, // show video timer in upper left corner of display 577 | irsdk_VideoCaptuer_HideVideoTimer, // hide video timer 578 | }; 579 | 580 | // send a remote controll message to the sim 581 | // var1, var2, and var3 are all 16 bits signed 582 | void irsdk_broadcastMsg(irsdk_BroadcastMsg msg, int var1, int var2, int var3); 583 | // var2 can be a full 32 bits 584 | void irsdk_broadcastMsg(irsdk_BroadcastMsg msg, int var1, int var2); 585 | // var2 can be a full 32 bit float 586 | void irsdk_broadcastMsg(irsdk_BroadcastMsg msg, int var1, float var2); 587 | 588 | // add a leading zero (or zeros) to a car number 589 | // to encode car #001 call padCarNum(1,2) 590 | int irsdk_padCarNum(int num, int zero); 591 | 592 | #endif // IRSDK_DEFINES_H 593 | -------------------------------------------------------------------------------- /test/JsIrSdk-spec.js: -------------------------------------------------------------------------------- 1 | const JsIrSdk = require('../src/JsIrSdk'); 2 | const IrSdkWrapper = require('./IrsdkNodeWrapper-stub'); 3 | const { describe, it, mock, beforeEach, afterEach } = require('node:test'); 4 | const assert = require('node:assert'); 5 | 6 | describe('JsIrSdk', function () { 7 | it('emits "Connected" when iRacing available', function (context) { 8 | context.mock.timers.enable(); 9 | 10 | const opts = { 11 | telemetryUpdateInterval: 1, 12 | sessionInfoUpdateInterval: 20000, 13 | }; 14 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 15 | const start = mock.method(irSdkWrapperMockObj, 'start', () => true); 16 | 17 | irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 18 | const isConnected = mock.method( 19 | irSdkWrapperMockObj, 20 | 'isConnected', 21 | () => false 22 | ); 23 | 24 | const spy = context.mock.fn(); 25 | 26 | irsdk.on('Connected', spy); 27 | context.mock.timers.tick(2); 28 | assert.strictEqual(spy.mock.callCount(), 0); 29 | 30 | isConnected.mock.mockImplementation(() => true); 31 | 32 | context.mock.timers.tick(2); 33 | 34 | assert.strictEqual(spy.mock.callCount(), 1); 35 | assert.strictEqual(start.mock.callCount(), 1); 36 | }); 37 | 38 | it('emits "Disconnected" when iRacing shut down', function (context) { 39 | context.mock.timers.enable(); 40 | const opts = { 41 | telemetryUpdateInterval: 1, 42 | sessionInfoUpdateInterval: 20000, 43 | }; 44 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 45 | const irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 46 | const isConnected = mock.method( 47 | irSdkWrapperMockObj, 48 | 'isConnected', 49 | () => true 50 | ); 51 | const spy = context.mock.fn(); 52 | irsdk.on('Disconnected', spy); 53 | context.mock.timers.tick(2); 54 | assert.strictEqual(spy.mock.callCount(), 0); 55 | isConnected.mock.mockImplementation(() => false); 56 | context.mock.timers.tick(2); 57 | assert.strictEqual(spy.mock.callCount(), 1); 58 | }); 59 | 60 | it('emits "Connected" again after reconnect', function (context) { 61 | context.mock.timers.enable(); 62 | const opts = { 63 | telemetryUpdateInterval: 2000, 64 | sessionInfoUpdateInterval: 20000, 65 | }; 66 | 67 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 68 | const start = mock.method(irSdkWrapperMockObj, 'start', () => true); 69 | const isConnected = mock.method( 70 | irSdkWrapperMockObj, 71 | 'isConnected', 72 | () => true 73 | ); 74 | 75 | const irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 76 | 77 | assert.strictEqual(start.mock.callCount(), 1); 78 | 79 | context.mock.timers.tick(2500); 80 | isConnected.mock.mockImplementation(() => false); 81 | const isInitialized = mock.method( 82 | irSdkWrapperMockObj, 83 | 'isInitialized', 84 | () => false 85 | ); 86 | context.mock.timers.tick(11000); 87 | 88 | assert.strictEqual(start.mock.callCount(), 2); 89 | 90 | isConnected.mock.mockImplementation(() => true); 91 | isInitialized.mock.mockImplementation(() => true); 92 | 93 | const spy = context.mock.fn(); 94 | irsdk.on('Connected', spy); 95 | context.mock.timers.tick(2500); 96 | 97 | assert.strictEqual(spy.mock.callCount(), 1); 98 | }); 99 | 100 | it('emits "TelemetryDescription" once after "Connected"', async function (context) { 101 | context.mock.timers.enable(); 102 | 103 | const opts = { 104 | telemetryUpdateInterval: 1, 105 | sessionInfoUpdateInterval: 20000, 106 | }; 107 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 108 | mock.method(irSdkWrapperMockObj, 'updateTelemetry', () => true); 109 | mock.method(irSdkWrapperMockObj, 'getTelemetryDescription', () => [ 110 | { RPM: 'engine revs per minute' }, 111 | ]); 112 | mock.method(irSdkWrapperMockObj, 'isConnected', () => true); 113 | const irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 114 | 115 | const spy = context.mock.fn(); 116 | irsdk.on('TelemetryDescription', spy); 117 | 118 | context.mock.timers.tick(5); 119 | assert.strictEqual(spy.mock.callCount(), 1); 120 | assert.strictEqual( 121 | spy.mock.calls[0].arguments[0][0].RPM, 122 | 'engine revs per minute' 123 | ); 124 | context.mock.timers.tick(5); 125 | assert.strictEqual(spy.mock.callCount(), 1); 126 | }); 127 | 128 | it('emits "Telemetry" when update available', function (context) { 129 | context.mock.timers.enable(); 130 | 131 | const opts = { 132 | telemetryUpdateInterval: 10, 133 | sessionInfoUpdateInterval: 20000, 134 | }; 135 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 136 | mock.method(irSdkWrapperMockObj, 'updateTelemetry', () => true); 137 | mock.method(irSdkWrapperMockObj, 'getTelemetry', () => ({ 138 | values: { RPM: 1100 }, 139 | })); 140 | mock.method(irSdkWrapperMockObj, 'isConnected', () => true); 141 | const irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 142 | 143 | const spy = context.mock.fn(); 144 | irsdk.on('Telemetry', spy); 145 | 146 | context.mock.timers.tick(12); 147 | assert.strictEqual(spy.mock.callCount(), 1); 148 | assert.strictEqual(spy.mock.calls[0].arguments[0].values.RPM, 1100); 149 | 150 | mock.method(irSdkWrapperMockObj, 'updateTelemetry', () => false); 151 | context.mock.timers.tick(12); 152 | 153 | assert.strictEqual(spy.mock.callCount(), 1); 154 | 155 | mock.method(irSdkWrapperMockObj, 'updateTelemetry', () => true); 156 | context.mock.timers.tick(12); 157 | 158 | assert.strictEqual(spy.mock.callCount(), 2); 159 | }); 160 | 161 | it('emits "SessionInfo" when update available', function (context) { 162 | context.mock.timers.enable(); 163 | const opts = { telemetryUpdateInterval: 10, sessionInfoUpdateInterval: 10 }; 164 | const irSdkWrapperMockObj = Object.create(IrSdkWrapper); 165 | mock.method(irSdkWrapperMockObj, 'updateSessionInfo', () => true); 166 | mock.method(irSdkWrapperMockObj, 'getSessionInfo', () => { 167 | return '---\ntype: race\n'; 168 | }); 169 | mock.method(irSdkWrapperMockObj, 'isConnected', () => true); 170 | const irsdk = new JsIrSdk(irSdkWrapperMockObj, opts); 171 | const spy = context.mock.fn(); 172 | irsdk.on('SessionInfo', spy); 173 | context.mock.timers.tick(12); 174 | assert.strictEqual(spy.mock.callCount(), 1); 175 | assert.strictEqual(spy.mock.calls[0].arguments[0].data.type, 'race'); 176 | mock.method(irSdkWrapperMockObj, 'updateSessionInfo', () => false); 177 | context.mock.timers.tick(12); 178 | assert.strictEqual(spy.mock.callCount(), 1); 179 | mock.method(irSdkWrapperMockObj, 'updateSessionInfo', () => true); 180 | context.mock.timers.tick(12); 181 | assert.strictEqual(spy.mock.callCount(), 2); 182 | }); 183 | 184 | describe('All commands', function () { 185 | let sendCmd, irsdk; 186 | beforeEach(function () { 187 | const irsdkWrapperMockObj = Object.create(IrSdkWrapper); 188 | sendCmd = mock.method(irsdkWrapperMockObj, 'sendCmd', () => true); 189 | irsdk = new JsIrSdk(irsdkWrapperMockObj); 190 | }); 191 | afterEach(function () { 192 | irsdk._stop(); 193 | }); 194 | 195 | describe('.execCmd(cmd, [arg1, arg2, arg3])', function () { 196 | it('sends arbitrary broadcast message', function () { 197 | irsdk.execCmd(12, 13, 14, 15); 198 | assert.strictEqual(sendCmd.mock.callCount(), 1); 199 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 12); 200 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 13); 201 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 14); 202 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 15); 203 | }); 204 | }); 205 | 206 | describe('.reloadTextures()', function () { 207 | it('sends reload all command', function () { 208 | irsdk.reloadTextures(); 209 | assert.strictEqual(sendCmd.mock.callCount(), 1); 210 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 7); 211 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 0); 212 | }); 213 | }); 214 | 215 | describe('.reloadTextures(carIdx)', function () { 216 | it('sends reload car command', function () { 217 | irsdk.reloadTexture(13); 218 | assert.strictEqual(sendCmd.mock.callCount(), 1); 219 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 7); 220 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 221 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 13); 222 | }); 223 | }); 224 | 225 | describe('.execChatCmd(cmd, [arg])', function () { 226 | it('sends chat command when cmd given as integer', function () { 227 | irsdk.execChatCmd(2); 228 | assert.strictEqual(sendCmd.mock.callCount(), 1); 229 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 8); 230 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2); 231 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 232 | }); 233 | it('sends chat command when cmd given as string', function () { 234 | irsdk.execChatCmd('cancel'); 235 | assert.strictEqual(sendCmd.mock.callCount(), 1); 236 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 8); 237 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 3); 238 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 239 | }); 240 | }); 241 | 242 | describe('.execChatMacro(num)', function () { 243 | it('sends chat macro command', function () { 244 | irsdk.execChatMacro(7); 245 | assert.strictEqual(sendCmd.mock.callCount(), 1); 246 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 8); 247 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 0); 248 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 7); 249 | }); 250 | }); 251 | 252 | describe('.execPitCmd(cmd, [arg])', function () { 253 | it('sends command when cmd given as integer', function () { 254 | irsdk.execPitCmd(1); 255 | assert.strictEqual(sendCmd.mock.callCount(), 1); 256 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 9); 257 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 258 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 259 | }); 260 | it('sends command when cmd given as string', function () { 261 | irsdk.execPitCmd('clearTires'); 262 | assert.strictEqual(sendCmd.mock.callCount(), 1); 263 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 9); 264 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 7); 265 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 266 | }); 267 | it('passes thru integer argument', function () { 268 | irsdk.execPitCmd('fuel', 60); 269 | assert.strictEqual(sendCmd.mock.callCount(), 1); 270 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 9); 271 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2); 272 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 60); 273 | }); 274 | }); 275 | 276 | describe('.execTelemetryCmd(cmd)', function () { 277 | it('sends command when cmd given as integer', function () { 278 | irsdk.execTelemetryCmd(1); 279 | assert.strictEqual(sendCmd.mock.callCount(), 1); 280 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 10); 281 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 282 | }); 283 | it('sends command when cmd given as string', function () { 284 | irsdk.execTelemetryCmd('restart'); 285 | assert.strictEqual(sendCmd.mock.callCount(), 1); 286 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 10); 287 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2); 288 | }); 289 | }); 290 | 291 | describe('.camControls', function () { 292 | describe('.setState(state)', function () { 293 | it('sends state cmd', function () { 294 | irsdk.camControls.setState(15); 295 | assert.strictEqual(sendCmd.mock.callCount(), 1); 296 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 2); 297 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 15); 298 | }); 299 | }); 300 | describe('.switchToCar(carNum, [camGroupNum], [camNum])', function () { 301 | it('sends switch cmd', function () { 302 | irsdk.camControls.switchToCar(12); 303 | assert.strictEqual(sendCmd.mock.callCount(), 1); 304 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 305 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 12); 306 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 307 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 308 | }); 309 | describe('leading zeros are padded if car num is given as string', function () { 310 | it('"1" -> 1', function () { 311 | irsdk.camControls.switchToCar('1'); 312 | assert.strictEqual(sendCmd.mock.callCount(), 1); 313 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 314 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 315 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 316 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 317 | }); 318 | it('"100" -> 100', function () { 319 | irsdk.camControls.switchToCar('100'); 320 | assert.strictEqual(sendCmd.mock.callCount(), 1); 321 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 322 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 100); 323 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 324 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 325 | }); 326 | it('"110" -> 110', function () { 327 | irsdk.camControls.switchToCar('100'); 328 | assert.strictEqual(sendCmd.mock.callCount(), 1); 329 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 330 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 100); 331 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 332 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 333 | }); 334 | it('"01" -> 2001', function () { 335 | irsdk.camControls.switchToCar('01'); 336 | assert.strictEqual(sendCmd.mock.callCount(), 1); 337 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 338 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2001); 339 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 340 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 341 | }); 342 | it('"001" -> 3001', function () { 343 | irsdk.camControls.switchToCar('001'); 344 | assert.strictEqual(sendCmd.mock.callCount(), 1); 345 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 346 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 3001); 347 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 348 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 349 | }); 350 | it('"011" -> 3011', function () { 351 | irsdk.camControls.switchToCar('011'); 352 | assert.strictEqual(sendCmd.mock.callCount(), 1); 353 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 354 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 3011); 355 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 356 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 357 | }); 358 | }); 359 | it('sends focus at cmd', function () { 360 | irsdk.camControls.switchToCar(-2); 361 | assert.strictEqual(sendCmd.mock.callCount(), 1); 362 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 363 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -2); 364 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 365 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 366 | }); 367 | it('switches cam group and cam', function () { 368 | irsdk.camControls.switchToCar(12, 2, 3); 369 | assert.strictEqual(sendCmd.mock.callCount(), 1); 370 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 1); 371 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 12); 372 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 2); 373 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 3); 374 | }); 375 | }); 376 | describe('.switchToPos(carNum, [camGroupNum], [camNum])', function () { 377 | it('sends switch cmd', function () { 378 | irsdk.camControls.switchToPos(12); 379 | assert.strictEqual(sendCmd.mock.callCount(), 1); 380 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 0); 381 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 12); 382 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 383 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 384 | }); 385 | it('sends focus at cmd"', function () { 386 | irsdk.camControls.switchToPos(-2); 387 | assert.strictEqual(sendCmd.mock.callCount(), 1); 388 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 0); 389 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -2); 390 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 391 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 0); 392 | }); 393 | it('switches cam group and cam', function () { 394 | irsdk.camControls.switchToPos(12, 2, 3); 395 | assert.strictEqual(sendCmd.mock.callCount(), 1); 396 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 0); 397 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 12); 398 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 2); 399 | assert.strictEqual(sendCmd.mock.calls[0].arguments[3], 3); 400 | }); 401 | }); 402 | 403 | describe('.playbackControls', function () { 404 | describe('.play()', function () { 405 | it('sends cmd', function () { 406 | irsdk.playbackControls.play(); 407 | assert.strictEqual(sendCmd.mock.callCount(), 1); 408 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 409 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 410 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 411 | }); 412 | }); 413 | describe('.pause()', function () { 414 | it('sends cmd', function () { 415 | irsdk.playbackControls.pause(); 416 | assert.strictEqual(sendCmd.mock.callCount(), 1); 417 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 418 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 0); 419 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 420 | }); 421 | }); 422 | describe('.fastForward([speed])', function () { 423 | it('sends cmd', function () { 424 | irsdk.playbackControls.fastForward(); 425 | assert.strictEqual(sendCmd.mock.callCount(), 1); 426 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 427 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2); 428 | }); 429 | it('passes optional argument', function () { 430 | irsdk.playbackControls.fastForward(16); 431 | assert.strictEqual(sendCmd.mock.callCount(), 1); 432 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 433 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 16); 434 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 435 | }); 436 | }); 437 | describe('.rewind([speed])', function () { 438 | it('sends cmd', function () { 439 | irsdk.playbackControls.rewind(); 440 | assert.strictEqual(sendCmd.mock.callCount(), 1); 441 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 442 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -2); 443 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 444 | }); 445 | it('passes optional argument', function () { 446 | irsdk.playbackControls.rewind(16); 447 | assert.strictEqual(sendCmd.mock.callCount(), 1); 448 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 449 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -16); 450 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 0); 451 | }); 452 | }); 453 | describe('.slowForward([divider])', function () { 454 | it('sends cmd', function () { 455 | irsdk.playbackControls.slowForward(); 456 | assert.strictEqual(sendCmd.mock.callCount(), 1); 457 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 458 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 459 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 1); 460 | }); 461 | it('passes optional argument', function () { 462 | irsdk.playbackControls.slowForward(16); 463 | assert.strictEqual(sendCmd.mock.callCount(), 1); 464 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 465 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 15); 466 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 1); 467 | }); 468 | }); 469 | describe('.slowBackward([divider])', function () { 470 | it('sends cmd', function () { 471 | irsdk.playbackControls.slowBackward(); 472 | assert.strictEqual(sendCmd.mock.callCount(), 1); 473 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 474 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -1); 475 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 1); 476 | }); 477 | it('passes optional argument', function () { 478 | irsdk.playbackControls.slowBackward(16); 479 | assert.strictEqual(sendCmd.mock.callCount(), 1); 480 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 3); 481 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], -15); 482 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 1); 483 | }); 484 | }); 485 | 486 | describe('.searchTs(sessionNum, sessionTimeMS)', function () { 487 | it('sends cmd with args', function () { 488 | irsdk.playbackControls.searchTs(1, 5000); 489 | assert.strictEqual(sendCmd.mock.callCount(), 1); 490 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 12); 491 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 492 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 5000); 493 | }); 494 | }); 495 | describe('.searchFrame(frameNum, rpyPosMode)', function () { 496 | it('sends cmd with args', function () { 497 | irsdk.playbackControls.searchFrame(5, 1); 498 | assert.strictEqual(sendCmd.mock.callCount(), 1); 499 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 4); 500 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 1); 501 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 5); 502 | }); 503 | it('rpyPosMode can be given as string', function () { 504 | irsdk.playbackControls.searchFrame(17, 'end'); 505 | assert.strictEqual(sendCmd.mock.callCount(), 1); 506 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 4); 507 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 2); 508 | assert.strictEqual(sendCmd.mock.calls[0].arguments[2], 17); 509 | }); 510 | }); 511 | describe('.search(searchMode)', function () { 512 | it('sends cmd with args', function () { 513 | irsdk.playbackControls.search(6); 514 | assert.strictEqual(sendCmd.mock.callCount(), 1); 515 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 5); 516 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 6); 517 | }); 518 | it('searchMode can be given as string', function () { 519 | irsdk.playbackControls.search('prevIncident'); 520 | assert.strictEqual(sendCmd.mock.callCount(), 1); 521 | assert.strictEqual(sendCmd.mock.calls[0].arguments[0], 5); 522 | assert.strictEqual(sendCmd.mock.calls[0].arguments[1], 8); 523 | }); 524 | }); 525 | }); 526 | }); 527 | }); 528 | }); 529 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iracing-sdk-js 2 | 3 | (Another) Unofficial [iRacing](http://www.iracing.com/) SDK implementation for Node.js. 4 | 5 | [![npm version](https://img.shields.io/npm/v/iracing-sdk-js.svg)](https://www.npmjs.com/package/iracing-sdk-js) 6 | 7 | **iracing-sdk-js** provides data access (live telemetry and session info) and most of the available commands. You can find some usage examples in the [examples](https://github.com/friss/iracing-sdk-js/tree/main/examples) directory, and there are some [data samples](https://github.com/friss/iracing-sdk-js/tree/main/sample-data) too. 8 | 9 | Latest tested iRacing version: *2025 Season 4* 10 | 11 | ## Installing 12 | 13 | Make sure you are running [Node.js](https://nodejs.org/) v21 x64 or later. (Currently requires Node 21 for latest node test runner implementation, but should work with Node 20 too.) 14 | 15 | `npm install --save iracing-sdk-js` 16 | 17 | `yarn add iracing-sdk-js --save` 18 | 19 | ## API documentation 20 | 21 | 22 | 23 | ### irsdk 24 | 25 | * [irsdk](#module_irsdk) 26 | * [.init([opts])](#module_irsdk.init) ⇒ [iracing](#iracing) 27 | * [.getInstance()](#module_irsdk.getInstance) ⇒ [iracing](#iracing) 28 | 29 | 30 | 31 | #### irsdk.init([opts]) ⇒ [iracing](#iracing) 32 | Initialize JsIrSdk, can be done once before using getInstance first time. 33 | 34 | **Kind**: static method of [irsdk](#module_irsdk) 35 | **Returns**: [iracing](#iracing) - Running instance of JsIrSdk 36 | 37 | | Param | Type | Default | Description | 38 | | --- | --- | --- | --- | 39 | | [opts] | Object | | Options | 40 | | [opts.telemetryUpdateInterval] | Integer | 0 | Telemetry update interval, milliseconds | 41 | | [opts.sessionInfoUpdateInterval] | Integer | 0 | SessionInfo update interval, milliseconds | 42 | | [opts.sessionInfoParser] | [sessionInfoParser](#iracing..sessionInfoParser) | | Custom parser for session info | 43 | 44 | 45 | ```js 46 | const irsdk = require('iracing-sdk-js') // look for telemetry updates only once per 100 ms const iracing = irsdk.init({telemetryUpdateInterval: 100}) 47 | ``` 48 | 49 | 50 | #### irsdk.getInstance() ⇒ [iracing](#iracing) 51 | Get initialized instance of JsIrSdk 52 | 53 | **Kind**: static method of [irsdk](#module_irsdk) 54 | **Returns**: [iracing](#iracing) - Running instance of JsIrSdk 55 | 56 | ```js 57 | const irsdk = require('iracing-sdk-js') const iracing = irsdk.getInstance() 58 | ``` 59 | 60 | 61 | ### iracing ⇐ events.EventEmitter 62 | JsIrSdk is javascript implementation of iRacing SDK. 63 | 64 | Don't use constructor directly, use [getInstance](#module_irsdk.getInstance). 65 | 66 | **Kind**: global class 67 | **Extends**: events.EventEmitter 68 | **Emits**: [Connected](#iracing+event_Connected), [Disconnected](#iracing+event_Disconnected), [Telemetry](#iracing+event_Telemetry), [TelemetryDescription](#iracing+event_TelemetryDescription), [SessionInfo](#iracing+event_SessionInfo) 69 | **See**: [EventEmitter API](https://nodejs.org/api/events.html#events_class_eventemitter) 70 | 71 | * [iracing](#iracing) ⇐ events.EventEmitter 72 | * [new JsIrSdk()](#new_iracing_new) 73 | * _instance_ 74 | * [.telemetry](#iracing+telemetry) 75 | * [.telemetryDescription](#iracing+telemetryDescription) 76 | * [.sessionInfo](#iracing+sessionInfo) 77 | * [.Consts](#iracing+Consts) : [IrSdkConsts](#IrSdkConsts) 78 | * [.camControls](#iracing+camControls) : Object 79 | * [.playbackControls](#iracing+playbackControls) : Object 80 | * [.execCmd(msgId, [arg1], [arg2], [arg3])](#iracing+execCmd) 81 | * [.reloadTextures()](#iracing+reloadTextures) 82 | * [.reloadTexture(carIdx)](#iracing+reloadTexture) 83 | * [.execChatCmd(cmd, [arg])](#iracing+execChatCmd) 84 | * [.execChatMacro(num)](#iracing+execChatMacro) 85 | * [.execPitCmd(cmd, [arg])](#iracing+execPitCmd) 86 | * [.execTelemetryCmd(cmd)](#iracing+execTelemetryCmd) 87 | * ["TelemetryDescription"](#iracing+event_TelemetryDescription) 88 | * ["Telemetry"](#iracing+event_Telemetry) 89 | * ["SessionInfo"](#iracing+event_SessionInfo) 90 | * ["update"](#iracing+event_update) 91 | * ["Connected"](#iracing+event_Connected) 92 | * ["Disconnected"](#iracing+event_Disconnected) 93 | * _inner_ 94 | * [~sessionInfoParser](#iracing..sessionInfoParser) ⇒ Object 95 | 96 | 97 | 98 | #### new JsIrSdk() 99 | 100 | ```js 101 | const iracing = require('iracing-sdk-js').getInstance() 102 | ``` 103 | 104 | 105 | #### iracing.telemetry 106 | Latest telemetry, may be null or undefined 107 | 108 | **Kind**: instance property of [iracing](#iracing) 109 | 110 | 111 | #### iracing.telemetryDescription 112 | Latest telemetry, may be null or undefined 113 | 114 | **Kind**: instance property of [iracing](#iracing) 115 | 116 | 117 | #### iracing.sessionInfo 118 | Latest telemetry, may be null or undefined 119 | 120 | **Kind**: instance property of [iracing](#iracing) 121 | 122 | 123 | #### iracing.Consts : [IrSdkConsts](#IrSdkConsts) 124 | iRacing SDK related constants 125 | 126 | **Kind**: instance property of [iracing](#iracing) 127 | 128 | 129 | #### iracing.camControls : Object 130 | Camera controls 131 | 132 | **Kind**: instance property of [iracing](#iracing) 133 | 134 | 135 | #### iracing.playbackControls : Object 136 | Replay and playback controls 137 | 138 | **Kind**: instance property of [iracing](#iracing) 139 | 140 | 141 | #### iracing.execCmd(msgId, [arg1], [arg2], [arg3]) 142 | Execute any of available commands, excl. FFB command 143 | 144 | **Kind**: instance method of [iracing](#iracing) 145 | 146 | | Param | Type | Description | 147 | | --- | --- | --- | 148 | | msgId | Integer | Message id | 149 | | [arg1] | Integer | 1st argument | 150 | | [arg2] | Integer | 2nd argument | 151 | | [arg3] | Integer | 3rd argument | 152 | 153 | 154 | 155 | #### iracing.reloadTextures() 156 | Reload all car textures 157 | 158 | **Kind**: instance method of [iracing](#iracing) 159 | 160 | ```js 161 | iracing.reloadTextures() // reload all paints 162 | ``` 163 | 164 | 165 | #### iracing.reloadTexture(carIdx) 166 | Reload car's texture 167 | 168 | **Kind**: instance method of [iracing](#iracing) 169 | 170 | | Param | Type | Description | 171 | | --- | --- | --- | 172 | | carIdx | Integer | car to reload | 173 | 174 | 175 | ```js 176 | iracing.reloadTexture(1) // reload paint of carIdx=1 177 | ``` 178 | 179 | 180 | #### iracing.execChatCmd(cmd, [arg]) 181 | Execute chat command 182 | 183 | **Kind**: instance method of [iracing](#iracing) 184 | 185 | | Param | Type | Description | 186 | | --- | --- | --- | 187 | | cmd | [ChatCommand](#IrSdkConsts.ChatCommand) | | 188 | | [arg] | Integer | Command argument, if needed | 189 | 190 | 191 | ```js 192 | iracing.execChatCmd('cancel') // close chat window 193 | ``` 194 | 195 | 196 | #### iracing.execChatMacro(num) 197 | Execute chat macro 198 | 199 | **Kind**: instance method of [iracing](#iracing) 200 | 201 | | Param | Type | Description | 202 | | --- | --- | --- | 203 | | num | Integer | Macro's number (0-15) | 204 | 205 | 206 | ```js 207 | iracing.execChatMacro(1) // macro 1 208 | ``` 209 | 210 | 211 | #### iracing.execPitCmd(cmd, [arg]) 212 | Execute pit command 213 | 214 | **Kind**: instance method of [iracing](#iracing) 215 | 216 | | Param | Type | Description | 217 | | --- | --- | --- | 218 | | cmd | [PitCommand](#IrSdkConsts.PitCommand) | | 219 | | [arg] | Integer | Command argument, if needed | 220 | 221 | 222 | ```js 223 | // full tank, no tires, no tear off iracing.execPitCmd('clear') iracing.execPitCmd('fuel', 999) // 999 liters iracing.execPitCmd('lf') // new left front iracing.execPitCmd('lr', 200) // new left rear, 200 kPa 224 | ``` 225 | 226 | 227 | #### iracing.execTelemetryCmd(cmd) 228 | Control telemetry logging (ibt file) 229 | 230 | **Kind**: instance method of [iracing](#iracing) 231 | 232 | | Param | Type | Description | 233 | | --- | --- | --- | 234 | | cmd | [TelemCommand](#IrSdkConsts.TelemCommand) | Command: start/stop/restart | 235 | 236 | 237 | ```js 238 | iracing.execTelemetryCmd('restart') 239 | ``` 240 | 241 | 242 | #### "TelemetryDescription" 243 | Telemetry description, contains description of available telemetry values 244 | 245 | **Kind**: event emitted by [iracing](#iracing) 246 | 247 | ```js 248 | iracing.on('TelemetryDescription', function (data) { console.log(evt) }) 249 | ``` 250 | 251 | 252 | #### "Telemetry" 253 | Telemetry update 254 | 255 | **Kind**: event emitted by [iracing](#iracing) 256 | 257 | ```js 258 | iracing.on('Telemetry', function (evt) { console.log(evt) }) 259 | ``` 260 | 261 | 262 | #### "SessionInfo" 263 | SessionInfo update 264 | 265 | **Kind**: event emitted by [iracing](#iracing) 266 | 267 | ```js 268 | iracing.on('SessionInfo', function (evt) { console.log(evt) }) 269 | ``` 270 | 271 | 272 | #### "update" 273 | any update event 274 | 275 | **Kind**: event emitted by [iracing](#iracing) 276 | 277 | ```js 278 | iracing.on('update', function (evt) { console.log(evt) }) 279 | ``` 280 | 281 | 282 | #### "Connected" 283 | iRacing, sim, is started 284 | 285 | **Kind**: event emitted by [iracing](#iracing) 286 | 287 | ```js 288 | iracing.on('Connected', function (evt) { console.log(evt) }) 289 | ``` 290 | 291 | 292 | #### "Disconnected" 293 | iRacing, sim, was closed 294 | 295 | **Kind**: event emitted by [iracing](#iracing) 296 | 297 | ```js 298 | iracing.on('Disconnected', function (evt) { console.log(evt) }) 299 | ``` 300 | 301 | 302 | #### iracing~sessionInfoParser ⇒ Object 303 | Parser for SessionInfo YAML 304 | 305 | **Kind**: inner typedef of [iracing](#iracing) 306 | **Returns**: Object - parsed session info 307 | 308 | | Param | Type | Description | 309 | | --- | --- | --- | 310 | | sessionInfo | String | SessionInfo YAML | 311 | 312 | 313 | 314 | ### IrSdkConsts 315 | IrSdkConsts, iRacing SDK constants/enums. 316 | 317 | **Kind**: global constant 318 | 319 | ```js 320 | var IrSdkConsts = require('node-irsdk').getInstance().Consts 321 | ``` 322 | 323 | * [IrSdkConsts](#IrSdkConsts) 324 | * [.BroadcastMsg](#IrSdkConsts.BroadcastMsg) 325 | * [.CameraState](#IrSdkConsts.CameraState) 326 | * [.RpyPosMode](#IrSdkConsts.RpyPosMode) 327 | * [.RpySrchMode](#IrSdkConsts.RpySrchMode) 328 | * [.RpyStateMode](#IrSdkConsts.RpyStateMode) 329 | * [.ReloadTexturesMode](#IrSdkConsts.ReloadTexturesMode) 330 | * [.ChatCommand](#IrSdkConsts.ChatCommand) 331 | * [.PitCommand](#IrSdkConsts.PitCommand) 332 | * [.TelemCommand](#IrSdkConsts.TelemCommand) 333 | * [.CamFocusAt](#IrSdkConsts.CamFocusAt) 334 | 335 | 336 | 337 | #### IrSdkConsts.BroadcastMsg 338 | Available command messages. 339 | 340 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 341 | **Properties** 342 | 343 | | Name | Default | Description | 344 | | --- | --- | --- | 345 | | CamSwitchPos | 0 | Switch cam, args: car position, group, camera | 346 | | CamSwitchNum | 1 | Switch cam, args, driver #, group, camera | 347 | | CamSetState | 2 | Set cam state, args: CameraState, unused, unused | 348 | | ReplaySetPlaySpeed | 3 | Set replay speed, args: speed, slowMotion, unused | 349 | | ReplaySetPlayPosition | 4 | Jump to frame, args: RpyPosMode, Frame Number (high, low) | 350 | | ReplaySearch | 5 | Search things from replay, args: RpySrchMode, unused, unused | 351 | | ReplaySetState | 6 | Set replay state, args: RpyStateMode, unused, unused | 352 | | ReloadTextures | 7 | Reload textures, args: ReloadTexturesMode, carIdx, unused | 353 | | ChatComand | 8 | Chat commands, args: ChatCommand, subCommand, unused | 354 | | PitCommand | 9 | Pit commands, args: PitCommand, parameter | 355 | | TelemCommand | 10 | Disk telemetry commands, args: TelemCommand, unused, unused | 356 | | FFBCommand | 11 | *not supported by node-irsdk**: Change FFB settings, args: FFBCommandMode, value (float, high, low) | 357 | | ReplaySearchSessionTime | 12 | Search by timestamp, args: sessionNum, sessionTimeMS (high, low) | 358 | 359 | 360 | 361 | #### IrSdkConsts.CameraState 362 | Camera state 363 | Camera state is bitfield; use these values to compose a new state. 364 | 365 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 366 | **Properties** 367 | 368 | | Name | Default | Description | 369 | | --- | --- | --- | 370 | | IsSessionScreen | 1 | Is driver out of the car | 371 | | IsScenicActive | 2 | The scenic camera is active (no focus car) | 372 | | CamToolActive | 4 | Activate camera tool | 373 | | UIHidden | 8 | Hide UI | 374 | | UseAutoShotSelection | 16 | Enable auto shot selection | 375 | | UseTemporaryEdits | 32 | Enable temporary edits | 376 | | UseKeyAcceleration | 64 | Enable key acceleration | 377 | | UseKey10xAcceleration | 128 | Enable 10x key acceleration | 378 | | UseMouseAimMode | 256 | Enable mouse aim | 379 | 380 | 381 | 382 | #### IrSdkConsts.RpyPosMode 383 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 384 | **Properties** 385 | 386 | | Name | Default | Description | 387 | | --- | --- | --- | 388 | | Begin | 0 | Frame number is relative to beginning | 389 | | Current | 1 | Frame number is relative to current frame | 390 | | End | 2 | Frame number is relative to end | 391 | 392 | 393 | 394 | #### IrSdkConsts.RpySrchMode 395 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 396 | **Properties** 397 | 398 | | Name | Default | 399 | | --- | --- | 400 | | ToStart | 0 | 401 | | ToEnd | 1 | 402 | | PrevSession | 2 | 403 | | NextSession | 3 | 404 | | PrevLap | 4 | 405 | | NextLap | 5 | 406 | | PrevFrame | 6 | 407 | | NextFrame | 7 | 408 | | PrevIncident | 8 | 409 | | NextIncident | 9 | 410 | 411 | 412 | 413 | #### IrSdkConsts.RpyStateMode 414 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 415 | **Properties** 416 | 417 | | Name | Default | Description | 418 | | --- | --- | --- | 419 | | EraseTape | 0 | Clear any data in the replay tape (works only if replay spooling disabled) | 420 | 421 | 422 | 423 | #### IrSdkConsts.ReloadTexturesMode 424 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 425 | **Properties** 426 | 427 | | Name | Default | 428 | | --- | --- | 429 | | All | 0 | 430 | | CarIdx | 1 | 431 | 432 | 433 | 434 | #### IrSdkConsts.ChatCommand 435 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 436 | **Properties** 437 | 438 | | Name | Default | Description | 439 | | --- | --- | --- | 440 | | Macro | 0 | Macro, give macro num (0-15) as argument | 441 | | BeginChat | 1 | Open up a new chat window | 442 | | Reply | 2 | Reply to last private chat | 443 | | Cancel | 3 | Close chat window | 444 | 445 | 446 | 447 | #### IrSdkConsts.PitCommand 448 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 449 | **Properties** 450 | 451 | | Name | Default | Description | 452 | | --- | --- | --- | 453 | | Clear | 0 | Clear all pit checkboxes | 454 | | WS | 1 | Clean the winshield, using one tear off | 455 | | Fuel | 2 | Request fuel, optional argument: liters | 456 | | LF | 3 | Request new left front, optional argument: pressure in kPa | 457 | | RF | 4 | Request new right front, optional argument: pressure in kPa | 458 | | LR | 5 | Request new left rear, optional argument: pressure in kPa | 459 | | RR | 6 | Request new right rear, optional argument: pressure in kPa | 460 | | ClearTires | 7 | Clear tire pit checkboxes | 461 | | FR | 8 | Request a fast repair | 462 | | ClearWS | 9 | Disable clear windshield | 463 | | ClearFR | 10 | Disable fast repair | 464 | | ClearFuel | 11 | Disable refueling | 465 | 466 | 467 | 468 | #### IrSdkConsts.TelemCommand 469 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 470 | **Properties** 471 | 472 | | Name | Default | Description | 473 | | --- | --- | --- | 474 | | Stop | 0 | Turn telemetry recording off | 475 | | Start | 1 | Turn telemetry recording on | 476 | | Restart | 2 | Write current file to disk and start a new one | 477 | 478 | 479 | 480 | #### IrSdkConsts.CamFocusAt 481 | When switching camera, these can be used instead of car number / position 482 | 483 | **Kind**: static enum of [IrSdkConsts](#IrSdkConsts) 484 | **Properties** 485 | 486 | | Name | Default | Description | 487 | | --- | --- | --- | 488 | | Incident | -3 | | 489 | | Leader | -2 | | 490 | | Exciting | -1 | | 491 | | Driver | 0 | Use car number / position instead of this | 492 | 493 | 494 | 495 | ### setState(state) 496 | Change camera tool state 497 | 498 | **Kind**: global function 499 | 500 | | Param | Type | Description | 501 | | --- | --- | --- | 502 | | state | [CameraState](#IrSdkConsts.CameraState) | new state | 503 | 504 | 505 | ```js 506 | // hide UI and enable mouse aim var States = iracing.Consts.CameraState var state = States.CamToolActive | States.UIHidden | States.UseMouseAimMode iracing.camControls.setState(state) 507 | ``` 508 | 509 | 510 | ### switchToCar(carNum, [camGroupNum], [camNum]) 511 | Switch camera, focus on car 512 | 513 | **Kind**: global function 514 | 515 | | Param | Type | Description | 516 | | --- | --- | --- | 517 | | carNum | Integer \| String \| [CamFocusAt](#IrSdkConsts.CamFocusAt) | Car to focus on | 518 | | [camGroupNum] | Integer | Select camera group | 519 | | [camNum] | Integer | Select camera | 520 | 521 | 522 | ```js 523 | // show car #2 iracing.camControls.switchToCar(2) 524 | 525 | ``` 526 | 527 | ```js 528 | // show car #02 iracing.camControls.switchToCar('02') 529 | 530 | ``` 531 | 532 | ```js 533 | // show leader iracing.camControls.switchToCar('leader') 534 | 535 | ``` 536 | 537 | ```js 538 | // show car #2 using cam group 3 iracing.camControls.switchToCar(2, 3) 539 | ``` 540 | 541 | 542 | ### switchToPos(position, [camGroupNum], [camNum]) 543 | Switch camera, focus on position 544 | 545 | **Kind**: global function 546 | 547 | | Param | Type | Description | 548 | | --- | --- | --- | 549 | | position | Integer \| [CamFocusAt](#IrSdkConsts.CamFocusAt) | Position to focus on | 550 | | [camGroupNum] | Integer | Select camera group | 551 | | [camNum] | Integer | Select camera | 552 | 553 | 554 | ```js 555 | iracing.camControls.switchToPos(2) // show P2 556 | ``` 557 | 558 | 559 | ### play() 560 | Play replay 561 | 562 | **Kind**: global function 563 | 564 | ```js 565 | iracing.playbackControls.play() 566 | ``` 567 | 568 | 569 | ### pause() 570 | Pause replay 571 | 572 | **Kind**: global function 573 | 574 | ```js 575 | iracing.playbackControls.pause() 576 | ``` 577 | 578 | 579 | ### fastForward([speed]) 580 | fast-forward replay 581 | 582 | **Kind**: global function 583 | 584 | | Param | Type | Default | Description | 585 | | --- | --- | --- | --- | 586 | | [speed] | Integer | 2 | FF speed, something between 2-16 works | 587 | 588 | 589 | ```js 590 | iracing.playbackControls.fastForward() // double speed FF 591 | ``` 592 | 593 | 594 | ### rewind([speed]) 595 | rewind replay 596 | 597 | **Kind**: global function 598 | 599 | | Param | Type | Default | Description | 600 | | --- | --- | --- | --- | 601 | | [speed] | Integer | 2 | RW speed, something between 2-16 works | 602 | 603 | 604 | ```js 605 | iracing.playbackControls.rewind() // double speed RW 606 | ``` 607 | 608 | 609 | ### slowForward([divider]) 610 | slow-forward replay, slow motion 611 | 612 | **Kind**: global function 613 | 614 | | Param | Type | Default | Description | 615 | | --- | --- | --- | --- | 616 | | [divider] | Integer | 2 | divider of speed, something between 2-17 works | 617 | 618 | 619 | ```js 620 | iracing.playbackControls.slowForward(2) // half speed 621 | ``` 622 | 623 | 624 | ### slowBackward([divider]) 625 | slow-backward replay, reverse slow motion 626 | 627 | **Kind**: global function 628 | 629 | | Param | Type | Default | Description | 630 | | --- | --- | --- | --- | 631 | | [divider] | Integer | 2 | divider of speed, something between 2-17 works | 632 | 633 | 634 | ```js 635 | iracing.playbackControls.slowBackward(2) // half speed RW 636 | ``` 637 | 638 | 639 | ### search(searchMode) 640 | Search things from replay 641 | 642 | **Kind**: global function 643 | 644 | | Param | Type | Description | 645 | | --- | --- | --- | 646 | | searchMode | [RpySrchMode](#IrSdkConsts.RpySrchMode) | what to search | 647 | 648 | 649 | ```js 650 | iracing.playbackControls.search('nextIncident') 651 | ``` 652 | 653 | 654 | ### searchTs(sessionNum, sessionTimeMS) 655 | Search timestamp 656 | 657 | **Kind**: global function 658 | 659 | | Param | Type | Description | 660 | | --- | --- | --- | 661 | | sessionNum | Integer | Session number | 662 | | sessionTimeMS | Integer | Session time in milliseconds | 663 | 664 | 665 | ```js 666 | // jump to 2nd minute of 3rd session iracing.playbackControls.searchTs(2, 2*60*1000) 667 | ``` 668 | 669 | 670 | ### searchFrame(frameNum, rpyPosMode) 671 | Go to frame. Frame counting can be relative to begin, end or current. 672 | 673 | **Kind**: global function 674 | 675 | | Param | Type | Description | 676 | | --- | --- | --- | 677 | | frameNum | Integer | Frame number | 678 | | rpyPosMode | [RpyPosMode](#IrSdkConsts.RpyPosMode) | Is frame number relative to begin, end or current frame | 679 | 680 | 681 | ```js 682 | iracing.playbackControls.searchFrame(1, 'current') // go to 1 frame forward 683 | ``` 684 | 685 | ## Development 686 | 687 | To develop `iracing-sdk-js` itself, you need working working installation of [node-gyp](https://github.com/nodejs/node-gyp#on-windows). 688 | 689 | Useful commands: 690 | 691 | * `yarn rebuild` rebuilds binary addon 692 | * `yarn test` runs mocked tests 693 | * `yarn smoke-tests` runs tests that requires iRacing to be running 694 | * `yarn ready` runs all tests and updates docs 695 | 696 | ## License 697 | 698 | Released under the [MIT License](https://github.com/friss/iracing-sdk-js/blob/main/LICENSE.md). 699 | 700 | ## Credits 701 | Originally based on [node-irsdk](https://github.com/apihlaja/node-irsdk) by [apihlaja](https://github.com/apihlaja). 702 | 703 | Parts of original irsdk used, license available here: https://github.com/friss/iracing-sdk-js/blob/main/src/cpp/irsdk/irsdk_defines.h (BSD-3-Clause) 704 | -------------------------------------------------------------------------------- /sample-data/sessioninfo.json: -------------------------------------------------------------------------------- 1 | { 2 | "timestamp": "2025-09-17T22:39:38.501Z", 3 | "data": { 4 | "WeekendInfo": { 5 | "TrackName": "virginia 2022 full", 6 | "TrackID": 465, 7 | "TrackLength": "5.2169 km", 8 | "TrackLengthOfficial": "5.26 km", 9 | "TrackDisplayName": "Virginia International Raceway", 10 | "TrackDisplayShortName": "VIR", 11 | "TrackConfigName": "Full Course", 12 | "TrackCity": "Alton", 13 | "TrackState": "VA", 14 | "TrackCountry": "USA", 15 | "TrackAltitude": "166.18 m", 16 | "TrackLatitude": "36.568814 m", 17 | "TrackLongitude": "-79.206660 m", 18 | "TrackNorthOffset": "4.2700 rad", 19 | "TrackNumTurns": 20, 20 | "TrackPitSpeedLimit": "60.00 kph", 21 | "TrackPaceSpeed": "23.61 kph", 22 | "TrackNumPitStalls": 27, 23 | "TrackType": "road course", 24 | "TrackDirection": "neutral", 25 | "TrackWeatherType": "Realistic", 26 | "TrackSkies": "Dynamic", 27 | "TrackSurfaceTemp": "23.49 C", 28 | "TrackSurfaceTempCrew": "22.22 C", 29 | "TrackAirTemp": "20.49 C", 30 | "TrackAirPressure": "29.14 Hg", 31 | "TrackAirDensity": "1.16 kg/m^3", 32 | "TrackWindVel": "4.80 m/s", 33 | "TrackWindDir": "3.26 rad", 34 | "TrackRelativeHumidity": "97 %", 35 | "TrackFogLevel": "0 %", 36 | "TrackPrecipitation": "58 %", 37 | "TrackCleanup": 0, 38 | "TrackDynamicTrack": 1, 39 | "TrackVersion": "2025.09.05.02", 40 | "SeriesID": 0, 41 | "SeasonID": 0, 42 | "SessionID": 0, 43 | "SubSessionID": 0, 44 | "LeagueID": 0, 45 | "Official": 0, 46 | "RaceWeek": 0, 47 | "EventType": "Test", 48 | "Category": "Road", 49 | "SimMode": "full", 50 | "TeamRacing": 0, 51 | "MinDrivers": 0, 52 | "MaxDrivers": 0, 53 | "DCRuleSet": "None", 54 | "QualifierMustStartRace": 0, 55 | "NumCarClasses": 1, 56 | "NumCarTypes": 1, 57 | "HeatRacing": 0, 58 | "BuildType": "Release", 59 | "BuildTarget": "Members", 60 | "BuildVersion": "2025.09.12.01", 61 | "RaceFarm": null, 62 | "WeekendOptions": { 63 | "NumStarters": 0, 64 | "StartingGrid": "single file", 65 | "QualifyScoring": "best lap", 66 | "CourseCautions": "off", 67 | "StandingStart": 0, 68 | "ShortParadeLap": 0, 69 | "Restarts": "single file", 70 | "WeatherType": "Realistic", 71 | "Skies": "Dynamic", 72 | "WindDirection": "N", 73 | "WindSpeed": "3.22 km/h", 74 | "WeatherTemp": "25.56 C", 75 | "RelativeHumidity": "45 %", 76 | "FogLevel": "0 %", 77 | "TimeOfDay": "3:16 pm", 78 | "Date": "2025-05-15T00:00:00.000Z", 79 | "EarthRotationSpeedupFactor": 1, 80 | "Unofficial": 1, 81 | "CommercialMode": "consumer", 82 | "NightMode": "variable", 83 | "IsFixedSetup": 0, 84 | "StrictLapsChecking": "default", 85 | "HasOpenRegistration": 0, 86 | "HardcoreLevel": 1, 87 | "NumJokerLaps": 0, 88 | "IncidentLimit": "unlimited", 89 | "FastRepairsLimit": "unlimited", 90 | "GreenWhiteCheckeredLimit": 0 91 | }, 92 | "TelemetryOptions": { 93 | "TelemetryDiskFile": "C /Users/friss/Documents/iRacing/telemetry/chevyvettez06rgt3_virginia 2022 full 2025-09-17 18-39-18.ibt" 94 | } 95 | }, 96 | "SessionInfo": { 97 | "CurrentSessionNum": 0, 98 | "Sessions": [ 99 | { 100 | "SessionNum": 0, 101 | "SessionLaps": "unlimited", 102 | "SessionTime": "unlimited", 103 | "SessionNumLapsToAvg": 0, 104 | "SessionType": "Offline Testing", 105 | "SessionTrackRubberState": "moderately low usage", 106 | "SessionName": "TESTING", 107 | "SessionSubType": null, 108 | "SessionSkipped": 0, 109 | "SessionRunGroupsUsed": 0, 110 | "SessionEnforceTireCompoundChange": 0, 111 | "ResultsPositions": null, 112 | "ResultsFastestLap": [ 113 | { 114 | "CarIdx": 255, 115 | "FastestLap": 0, 116 | "FastestTime": -1 117 | } 118 | ], 119 | "ResultsAverageLapTime": -1, 120 | "ResultsNumCautionFlags": 0, 121 | "ResultsNumCautionLaps": 0, 122 | "ResultsNumLeadChanges": 0, 123 | "ResultsLapsComplete": -1, 124 | "ResultsOfficial": 0 125 | } 126 | ] 127 | }, 128 | "CameraInfo": { 129 | "Groups": [ 130 | { 131 | "GroupNum": 1, 132 | "GroupName": "Nose", 133 | "Cameras": [ 134 | { 135 | "CameraNum": 1, 136 | "CameraName": "CamNose" 137 | } 138 | ] 139 | }, 140 | { 141 | "GroupNum": 2, 142 | "GroupName": "Gearbox", 143 | "Cameras": [ 144 | { 145 | "CameraNum": 1, 146 | "CameraName": "CamGearbox" 147 | } 148 | ] 149 | }, 150 | { 151 | "GroupNum": 3, 152 | "GroupName": "Roll Bar", 153 | "Cameras": [ 154 | { 155 | "CameraNum": 1, 156 | "CameraName": "CamRoll Bar" 157 | } 158 | ] 159 | }, 160 | { 161 | "GroupNum": 4, 162 | "GroupName": "LF Susp", 163 | "Cameras": [ 164 | { 165 | "CameraNum": 1, 166 | "CameraName": "CamLF Susp" 167 | } 168 | ] 169 | }, 170 | { 171 | "GroupNum": 5, 172 | "GroupName": "LR Susp", 173 | "Cameras": [ 174 | { 175 | "CameraNum": 1, 176 | "CameraName": "CamLR Susp" 177 | } 178 | ] 179 | }, 180 | { 181 | "GroupNum": 6, 182 | "GroupName": "Gyro", 183 | "Cameras": [ 184 | { 185 | "CameraNum": 1, 186 | "CameraName": "CamGyro" 187 | } 188 | ] 189 | }, 190 | { 191 | "GroupNum": 7, 192 | "GroupName": "RF Susp", 193 | "Cameras": [ 194 | { 195 | "CameraNum": 1, 196 | "CameraName": "CamRF Susp" 197 | } 198 | ] 199 | }, 200 | { 201 | "GroupNum": 8, 202 | "GroupName": "RR Susp", 203 | "Cameras": [ 204 | { 205 | "CameraNum": 1, 206 | "CameraName": "CamRR Susp" 207 | } 208 | ] 209 | }, 210 | { 211 | "GroupNum": 9, 212 | "GroupName": "Cockpit", 213 | "Cameras": [ 214 | { 215 | "CameraNum": 1, 216 | "CameraName": "CamCockpit" 217 | } 218 | ] 219 | }, 220 | { 221 | "GroupNum": 10, 222 | "GroupName": "Scenic", 223 | "IsScenic": true, 224 | "Cameras": [ 225 | { 226 | "CameraNum": 1, 227 | "CameraName": "Scenic_07" 228 | }, 229 | { 230 | "CameraNum": 2, 231 | "CameraName": "Scenic_08" 232 | }, 233 | { 234 | "CameraNum": 3, 235 | "CameraName": "Scenic_09" 236 | }, 237 | { 238 | "CameraNum": 4, 239 | "CameraName": "Scenic_10" 240 | }, 241 | { 242 | "CameraNum": 5, 243 | "CameraName": "Scenic_02" 244 | }, 245 | { 246 | "CameraNum": 6, 247 | "CameraName": "Scenic_03" 248 | }, 249 | { 250 | "CameraNum": 7, 251 | "CameraName": "Scenic_05" 252 | }, 253 | { 254 | "CameraNum": 8, 255 | "CameraName": "Scenic_04" 256 | }, 257 | { 258 | "CameraNum": 9, 259 | "CameraName": "Scenic_06" 260 | }, 261 | { 262 | "CameraNum": 10, 263 | "CameraName": "Scenic_01" 264 | } 265 | ] 266 | }, 267 | { 268 | "GroupNum": 11, 269 | "GroupName": "TV1", 270 | "Cameras": [ 271 | { 272 | "CameraNum": 1, 273 | "CameraName": "CamTV1_00" 274 | }, 275 | { 276 | "CameraNum": 2, 277 | "CameraName": "CamTV1_01" 278 | }, 279 | { 280 | "CameraNum": 3, 281 | "CameraName": "CamTV1_02" 282 | }, 283 | { 284 | "CameraNum": 4, 285 | "CameraName": "CamTV1_03" 286 | }, 287 | { 288 | "CameraNum": 5, 289 | "CameraName": "CamTV1_05" 290 | }, 291 | { 292 | "CameraNum": 6, 293 | "CameraName": "CamTV1_04" 294 | }, 295 | { 296 | "CameraNum": 7, 297 | "CameraName": "CamTV1_06" 298 | }, 299 | { 300 | "CameraNum": 8, 301 | "CameraName": "CamTV1_07" 302 | }, 303 | { 304 | "CameraNum": 9, 305 | "CameraName": "CamTV1_08" 306 | }, 307 | { 308 | "CameraNum": 10, 309 | "CameraName": "CamTV1_09" 310 | } 311 | ] 312 | }, 313 | { 314 | "GroupNum": 12, 315 | "GroupName": "TV2", 316 | "Cameras": [ 317 | { 318 | "CameraNum": 1, 319 | "CameraName": "CamTV2_04" 320 | }, 321 | { 322 | "CameraNum": 2, 323 | "CameraName": "CamTV2_01" 324 | }, 325 | { 326 | "CameraNum": 3, 327 | "CameraName": "CamTV2_02" 328 | }, 329 | { 330 | "CameraNum": 4, 331 | "CameraName": "CamTV2_03" 332 | }, 333 | { 334 | "CameraNum": 5, 335 | "CameraName": "CamTV2_05" 336 | }, 337 | { 338 | "CameraNum": 6, 339 | "CameraName": "CamTV2_07" 340 | }, 341 | { 342 | "CameraNum": 7, 343 | "CameraName": "CamTV2_08" 344 | }, 345 | { 346 | "CameraNum": 8, 347 | "CameraName": "CamTV2_09" 348 | }, 349 | { 350 | "CameraNum": 9, 351 | "CameraName": "CamTV2_10" 352 | }, 353 | { 354 | "CameraNum": 10, 355 | "CameraName": "CamTV2_15" 356 | }, 357 | { 358 | "CameraNum": 11, 359 | "CameraName": "CamTV2_12" 360 | }, 361 | { 362 | "CameraNum": 12, 363 | "CameraName": "CamTV2_13" 364 | }, 365 | { 366 | "CameraNum": 13, 367 | "CameraName": "CamTV2_14" 368 | }, 369 | { 370 | "CameraNum": 14, 371 | "CameraName": "CamTV2_11" 372 | }, 373 | { 374 | "CameraNum": 15, 375 | "CameraName": "CamTV2_00" 376 | } 377 | ] 378 | }, 379 | { 380 | "GroupNum": 13, 381 | "GroupName": "TV3", 382 | "Cameras": [ 383 | { 384 | "CameraNum": 1, 385 | "CameraName": "CamTV3_03" 386 | }, 387 | { 388 | "CameraNum": 2, 389 | "CameraName": "CamTV3_01" 390 | }, 391 | { 392 | "CameraNum": 3, 393 | "CameraName": "CamTV3_02" 394 | }, 395 | { 396 | "CameraNum": 4, 397 | "CameraName": "CamTV3_04" 398 | }, 399 | { 400 | "CameraNum": 5, 401 | "CameraName": "CamTV3_05" 402 | }, 403 | { 404 | "CameraNum": 6, 405 | "CameraName": "CamTV3_06" 406 | }, 407 | { 408 | "CameraNum": 7, 409 | "CameraName": "CamTV3_07" 410 | }, 411 | { 412 | "CameraNum": 8, 413 | "CameraName": "CamTV3_08" 414 | }, 415 | { 416 | "CameraNum": 9, 417 | "CameraName": "CamTV3_09" 418 | }, 419 | { 420 | "CameraNum": 10, 421 | "CameraName": "CamTV3_10" 422 | } 423 | ] 424 | }, 425 | { 426 | "GroupNum": 14, 427 | "GroupName": "TV Static", 428 | "Cameras": [ 429 | { 430 | "CameraNum": 1, 431 | "CameraName": "CamTV4_00" 432 | }, 433 | { 434 | "CameraNum": 2, 435 | "CameraName": "CamTV4_01" 436 | }, 437 | { 438 | "CameraNum": 3, 439 | "CameraName": "CamTV4_02" 440 | }, 441 | { 442 | "CameraNum": 4, 443 | "CameraName": "CamTV4_03" 444 | }, 445 | { 446 | "CameraNum": 5, 447 | "CameraName": "CamTV4_04" 448 | }, 449 | { 450 | "CameraNum": 6, 451 | "CameraName": "CamTV4_05" 452 | }, 453 | { 454 | "CameraNum": 7, 455 | "CameraName": "CamTV4_06" 456 | }, 457 | { 458 | "CameraNum": 8, 459 | "CameraName": "CamTV4_08" 460 | }, 461 | { 462 | "CameraNum": 9, 463 | "CameraName": "CamTV4_07" 464 | }, 465 | { 466 | "CameraNum": 10, 467 | "CameraName": "CamTV4_10" 468 | }, 469 | { 470 | "CameraNum": 11, 471 | "CameraName": "CamTV4_09" 472 | }, 473 | { 474 | "CameraNum": 12, 475 | "CameraName": "CamTV4_11" 476 | }, 477 | { 478 | "CameraNum": 13, 479 | "CameraName": "CamTV4_12" 480 | }, 481 | { 482 | "CameraNum": 14, 483 | "CameraName": "CamTV4_13" 484 | }, 485 | { 486 | "CameraNum": 15, 487 | "CameraName": "CamTV4_14" 488 | }, 489 | { 490 | "CameraNum": 16, 491 | "CameraName": "CamTV4_15" 492 | }, 493 | { 494 | "CameraNum": 17, 495 | "CameraName": "CamTV4_16" 496 | }, 497 | { 498 | "CameraNum": 18, 499 | "CameraName": "CamTV4_17" 500 | } 501 | ] 502 | }, 503 | { 504 | "GroupNum": 15, 505 | "GroupName": "TV Mixed", 506 | "Cameras": [ 507 | { 508 | "CameraNum": 1, 509 | "CameraName": "CamTV3_09b" 510 | }, 511 | { 512 | "CameraNum": 2, 513 | "CameraName": "CamTV1_00" 514 | }, 515 | { 516 | "CameraNum": 3, 517 | "CameraName": "CamTV1_01" 518 | }, 519 | { 520 | "CameraNum": 4, 521 | "CameraName": "CamTV1_02" 522 | }, 523 | { 524 | "CameraNum": 5, 525 | "CameraName": "CamTV1_04" 526 | }, 527 | { 528 | "CameraNum": 6, 529 | "CameraName": "CamTV1_05" 530 | }, 531 | { 532 | "CameraNum": 7, 533 | "CameraName": "CamTV1_06" 534 | }, 535 | { 536 | "CameraNum": 8, 537 | "CameraName": "CamTV1_07" 538 | }, 539 | { 540 | "CameraNum": 9, 541 | "CameraName": "CamTV1_09" 542 | }, 543 | { 544 | "CameraNum": 10, 545 | "CameraName": "CamTV2_00" 546 | }, 547 | { 548 | "CameraNum": 11, 549 | "CameraName": "CamTV2_01" 550 | }, 551 | { 552 | "CameraNum": 12, 553 | "CameraName": "CamTV2_02" 554 | }, 555 | { 556 | "CameraNum": 13, 557 | "CameraName": "CamTV2_03" 558 | }, 559 | { 560 | "CameraNum": 14, 561 | "CameraName": "CamTV2_04" 562 | }, 563 | { 564 | "CameraNum": 15, 565 | "CameraName": "CamTV2_05" 566 | }, 567 | { 568 | "CameraNum": 16, 569 | "CameraName": "CamTV2_06" 570 | }, 571 | { 572 | "CameraNum": 17, 573 | "CameraName": "CamTV2_07" 574 | }, 575 | { 576 | "CameraNum": 18, 577 | "CameraName": "CamTV2_08" 578 | }, 579 | { 580 | "CameraNum": 19, 581 | "CameraName": "CamTV2_09" 582 | }, 583 | { 584 | "CameraNum": 20, 585 | "CameraName": "CamTV2_10" 586 | }, 587 | { 588 | "CameraNum": 21, 589 | "CameraName": "CamTV2_11" 590 | }, 591 | { 592 | "CameraNum": 22, 593 | "CameraName": "CamTV2_12" 594 | }, 595 | { 596 | "CameraNum": 23, 597 | "CameraName": "CamTV2_14" 598 | }, 599 | { 600 | "CameraNum": 24, 601 | "CameraName": "CamTV2_15" 602 | }, 603 | { 604 | "CameraNum": 25, 605 | "CameraName": "CamTV3_01" 606 | }, 607 | { 608 | "CameraNum": 26, 609 | "CameraName": "CamTV3_02" 610 | }, 611 | { 612 | "CameraNum": 27, 613 | "CameraName": "CamTV3_03" 614 | }, 615 | { 616 | "CameraNum": 28, 617 | "CameraName": "CamTV3_04" 618 | }, 619 | { 620 | "CameraNum": 29, 621 | "CameraName": "CamTV3_05" 622 | }, 623 | { 624 | "CameraNum": 30, 625 | "CameraName": "CamTV3_05b" 626 | }, 627 | { 628 | "CameraNum": 31, 629 | "CameraName": "CamTV3_06" 630 | }, 631 | { 632 | "CameraNum": 32, 633 | "CameraName": "CamTV3_07" 634 | }, 635 | { 636 | "CameraNum": 33, 637 | "CameraName": "CamTV3_07b" 638 | }, 639 | { 640 | "CameraNum": 34, 641 | "CameraName": "CamTV3_08" 642 | }, 643 | { 644 | "CameraNum": 35, 645 | "CameraName": "CamTV3_09" 646 | }, 647 | { 648 | "CameraNum": 36, 649 | "CameraName": "CamTV3_10" 650 | }, 651 | { 652 | "CameraNum": 37, 653 | "CameraName": "CamTV4_00" 654 | } 655 | ] 656 | }, 657 | { 658 | "GroupNum": 16, 659 | "GroupName": "Pit Lane", 660 | "Cameras": [ 661 | { 662 | "CameraNum": 1, 663 | "CameraName": "CamPit Lane" 664 | } 665 | ] 666 | }, 667 | { 668 | "GroupNum": 17, 669 | "GroupName": "Pit Lane 2", 670 | "Cameras": [ 671 | { 672 | "CameraNum": 1, 673 | "CameraName": "CamPit Lane 2" 674 | } 675 | ] 676 | }, 677 | { 678 | "GroupNum": 18, 679 | "GroupName": "Blimp", 680 | "Cameras": [ 681 | { 682 | "CameraNum": 1, 683 | "CameraName": "CamBlimp" 684 | } 685 | ] 686 | }, 687 | { 688 | "GroupNum": 19, 689 | "GroupName": "Chopper", 690 | "Cameras": [ 691 | { 692 | "CameraNum": 1, 693 | "CameraName": "CamChopper" 694 | } 695 | ] 696 | }, 697 | { 698 | "GroupNum": 20, 699 | "GroupName": "Chase", 700 | "Cameras": [ 701 | { 702 | "CameraNum": 1, 703 | "CameraName": "CamChase" 704 | } 705 | ] 706 | }, 707 | { 708 | "GroupNum": 21, 709 | "GroupName": "Far Chase", 710 | "Cameras": [ 711 | { 712 | "CameraNum": 1, 713 | "CameraName": "CamFar Chase" 714 | } 715 | ] 716 | }, 717 | { 718 | "GroupNum": 22, 719 | "GroupName": "Rear Chase", 720 | "Cameras": [ 721 | { 722 | "CameraNum": 1, 723 | "CameraName": "CamRear Chase" 724 | } 725 | ] 726 | } 727 | ] 728 | }, 729 | "RadioInfo": { 730 | "SelectedRadioNum": 0, 731 | "Radios": [ 732 | { 733 | "RadioNum": 0, 734 | "HopCount": 2, 735 | "NumFrequencies": 6, 736 | "TunedToFrequencyNum": 0, 737 | "ScanningIsOn": 1, 738 | "Frequencies": [ 739 | { 740 | "FrequencyNum": 0, 741 | "FrequencyName": "@ALLTEAMS", 742 | "Priority": 12, 743 | "CarIdx": -1, 744 | "EntryIdx": -1, 745 | "ClubID": 0, 746 | "CanScan": 1, 747 | "CanSquawk": 1, 748 | "Muted": 0, 749 | "IsMutable": 1, 750 | "IsDeletable": 0 751 | }, 752 | { 753 | "FrequencyNum": 1, 754 | "FrequencyName": "@DRIVERS", 755 | "Priority": 15, 756 | "CarIdx": -1, 757 | "EntryIdx": -1, 758 | "ClubID": 0, 759 | "CanScan": 1, 760 | "CanSquawk": 1, 761 | "Muted": 0, 762 | "IsMutable": 1, 763 | "IsDeletable": 0 764 | }, 765 | { 766 | "FrequencyNum": 2, 767 | "FrequencyName": "@TEAM", 768 | "Priority": 60, 769 | "CarIdx": 0, 770 | "EntryIdx": -1, 771 | "ClubID": 0, 772 | "CanScan": 1, 773 | "CanSquawk": 1, 774 | "Muted": 0, 775 | "IsMutable": 0, 776 | "IsDeletable": 0 777 | }, 778 | { 779 | "FrequencyNum": 3, 780 | "FrequencyName": "@ADMIN", 781 | "Priority": 90, 782 | "CarIdx": -1, 783 | "EntryIdx": -1, 784 | "ClubID": 0, 785 | "CanScan": 1, 786 | "CanSquawk": 1, 787 | "Muted": 0, 788 | "IsMutable": 0, 789 | "IsDeletable": 0 790 | }, 791 | { 792 | "FrequencyNum": 4, 793 | "FrequencyName": "@RACECONTROL", 794 | "Priority": 80, 795 | "CarIdx": -1, 796 | "EntryIdx": -1, 797 | "ClubID": 0, 798 | "CanScan": 1, 799 | "CanSquawk": 1, 800 | "Muted": 0, 801 | "IsMutable": 0, 802 | "IsDeletable": 0 803 | }, 804 | { 805 | "FrequencyNum": 5, 806 | "FrequencyName": "@PRIVATE", 807 | "Priority": 70, 808 | "CarIdx": -1, 809 | "EntryIdx": 0, 810 | "ClubID": 0, 811 | "CanScan": 1, 812 | "CanSquawk": 1, 813 | "Muted": 0, 814 | "IsMutable": 0, 815 | "IsDeletable": 0 816 | } 817 | ] 818 | } 819 | ] 820 | }, 821 | "DriverInfo": { 822 | "DriverCarIdx": 0, 823 | "DriverUserID": 697587, 824 | "PaceCarIdx": -1, 825 | "DriverIsAdmin": 1, 826 | "DriverHeadPosX": -0.105, 827 | "DriverHeadPosY": 0.325, 828 | "DriverHeadPosZ": 0.535, 829 | "DriverCarIsElectric": 0, 830 | "DriverCarIdleRPM": 1950, 831 | "DriverCarRedLine": 8000, 832 | "DriverCarEngCylinderCount": 8, 833 | "DriverCarFuelKgPerLtr": 0.75, 834 | "DriverCarFuelMaxLtr": 104, 835 | "DriverCarMaxFuelPct": 1, 836 | "DriverCarGearNumForward": 6, 837 | "DriverCarGearNeutral": 1, 838 | "DriverCarGearReverse": 1, 839 | "DriverGearboxType": "Sequential", 840 | "DriverGearboxControlType": "Sequential", 841 | "DriverCarShiftAid": "Automatic", 842 | "DriverCarSLFirstRPM": 7000, 843 | "DriverCarSLShiftRPM": 7700, 844 | "DriverCarSLLastRPM": 7600, 845 | "DriverCarSLBlinkRPM": 7950, 846 | "DriverCarVersion": "2025.09.08.01", 847 | "DriverPitTrkPct": 0.990978, 848 | "DriverCarEstLapTime": 113.4305, 849 | "DriverSetupName": "baseline.sto", 850 | "DriverSetupIsModified": 0, 851 | "DriverSetupLoadTypeName": "baseline", 852 | "DriverSetupPassedTech": 1, 853 | "DriverIncidentCount": 0, 854 | "DriverBrakeCurvingFactor": 0.001, 855 | "DriverTires": [ 856 | { 857 | "TireIndex": 0, 858 | "TireCompoundType": "Hard" 859 | }, 860 | { 861 | "TireIndex": 1, 862 | "TireCompoundType": "Wet" 863 | } 864 | ], 865 | "Drivers": [ 866 | { 867 | "CarIdx": 0, 868 | "UserName": "Zachary Friss", 869 | "AbbrevName": null, 870 | "Initials": null, 871 | "UserID": 697587, 872 | "TeamID": 0, 873 | "TeamName": "Zachary Friss", 874 | "CarNumber": "64", 875 | "CarNumberRaw": 64, 876 | "CarPath": "chevyvettez06rgt3", 877 | "CarClassID": 0, 878 | "CarID": 184, 879 | "CarIsPaceCar": 0, 880 | "CarIsAI": 0, 881 | "CarIsElectric": 0, 882 | "CarScreenName": "Chevrolet Corvette Z06 GT3.R", 883 | "CarScreenNameShort": "Corvette GT3.R", 884 | "CarCfg": -1, 885 | "CarCfgName": null, 886 | "CarCfgCustomPaintExt": null, 887 | "CarClassShortName": null, 888 | "CarClassRelSpeed": 0, 889 | "CarClassLicenseLevel": 0, 890 | "CarClassMaxFuelPct": "1.000 %", 891 | "CarClassWeightPenalty": "0.000 kg", 892 | "CarClassPowerAdjust": "0.000 %", 893 | "CarClassDryTireSetLimit": "0 %", 894 | "CarClassColor": 16777215, 895 | "CarClassEstLapTime": 113.4305, 896 | "IRating": 1, 897 | "LicLevel": 1, 898 | "LicSubLevel": 1, 899 | "LicString": "R 0.01", 900 | "LicColor": "0xundefined", 901 | "IsSpectator": 0, 902 | "CarDesignStr": "10,FFFFFF,FF7A59,33475B", 903 | "HelmetDesignStr": "7,ffffff,ff7a59,33475b", 904 | "SuitDesignStr": "21,ff7a59,33475b,ffffff", 905 | "BodyType": 0, 906 | "FaceType": 4, 907 | "HelmetType": 0, 908 | "CarNumberDesignStr": "0,0,FFFFFF,777777,000000", 909 | "CarSponsor_1": 0, 910 | "CarSponsor_2": 0, 911 | "CurDriverIncidentCount": 0, 912 | "TeamIncidentCount": 0 913 | } 914 | ] 915 | }, 916 | "SplitTimeInfo": { 917 | "Sectors": [ 918 | { 919 | "SectorNum": 0, 920 | "SectorStartPct": 0 921 | }, 922 | { 923 | "SectorNum": 1, 924 | "SectorStartPct": 0.184456 925 | }, 926 | { 927 | "SectorNum": 2, 928 | "SectorStartPct": 0.337214 929 | }, 930 | { 931 | "SectorNum": 3, 932 | "SectorStartPct": 0.504637 933 | }, 934 | { 935 | "SectorNum": 4, 936 | "SectorStartPct": 0.734279 937 | }, 938 | { 939 | "SectorNum": 5, 940 | "SectorStartPct": 0.829332 941 | } 942 | ] 943 | }, 944 | "CarSetup": { 945 | "UpdateCount": 2, 946 | "TiresAero": { 947 | "TireType": { 948 | "TireType": "Dry" 949 | }, 950 | "LeftFront": { 951 | "StartingPressure": "159 kPa", 952 | "LastHotPressure": "159 kPa", 953 | "LastTempsOMI": "35C, 35C, 35C", 954 | "TreadRemaining": "100%, 100%, 100%" 955 | }, 956 | "LeftRear": { 957 | "StartingPressure": "159 kPa", 958 | "LastHotPressure": "159 kPa", 959 | "LastTempsOMI": "35C, 35C, 35C", 960 | "TreadRemaining": "100%, 100%, 100%" 961 | }, 962 | "RightFront": { 963 | "StartingPressure": "159 kPa", 964 | "LastHotPressure": "159 kPa", 965 | "LastTempsIMO": "35C, 35C, 35C", 966 | "TreadRemaining": "100%, 100%, 100%" 967 | }, 968 | "RightRear": { 969 | "StartingPressure": "159 kPa", 970 | "LastHotPressure": "159 kPa", 971 | "LastTempsIMO": "35C, 35C, 35C", 972 | "TreadRemaining": "100%, 100%, 100%" 973 | }, 974 | "AeroBalanceCalc": { 975 | "FrontRhAtSpeed": "41 mm", 976 | "RearRhAtSpeed": "47 mm", 977 | "RearWingAngle": "9.5 degrees", 978 | "FrontDownforce": "39.6%" 979 | } 980 | }, 981 | "Chassis": { 982 | "FrontBrakes": { 983 | "ArbBlades": 5, 984 | "TotalToeIn": "-3.0 mm", 985 | "BrakePedalRatio": 4.67, 986 | "BrakePads": "Medium friction", 987 | "FWtdist": "44.3%", 988 | "CenterFrontSplitterHeight": "52.3 mm" 989 | }, 990 | "LeftFront": { 991 | "CornerWeight": "3163 N", 992 | "RideHeight": "51.0 mm", 993 | "BumpRubberGap": "15 mm", 994 | "SpringRate": "120 N/mm", 995 | "Camber": "-4.0 deg" 996 | }, 997 | "LeftRear": { 998 | "CornerWeight": "3972 N", 999 | "RideHeight": "58.0 mm", 1000 | "BumpRubberGap": "40 mm", 1001 | "SpringRate": "325 N/mm", 1002 | "Camber": "-3.3 deg", 1003 | "ToeIn": "+1.5 mm" 1004 | }, 1005 | "Rear": { 1006 | "FuelLevel": "52.0 L", 1007 | "ArbBlades": 2, 1008 | "RearWingAngle": "9.5 degrees" 1009 | }, 1010 | "InCarAdjustments": { 1011 | "BrakePressureBias": "55.2%", 1012 | "AbsSetting": "3 (ABS)", 1013 | "TcSetting": "3 (T/C)", 1014 | "DashDisplayPage": "Race", 1015 | "CrossWeight": "50.0%" 1016 | }, 1017 | "RightFront": { 1018 | "CornerWeight": "3163 N", 1019 | "RideHeight": "51.0 mm", 1020 | "BumpRubberGap": "15 mm", 1021 | "SpringRate": "120 N/mm", 1022 | "Camber": "-4.0 deg" 1023 | }, 1024 | "RightRear": { 1025 | "CornerWeight": "3972 N", 1026 | "RideHeight": "58.0 mm", 1027 | "BumpRubberGap": "40 mm", 1028 | "SpringRate": "325 N/mm", 1029 | "Camber": "-3.3 deg", 1030 | "ToeIn": "+1.5 mm" 1031 | }, 1032 | "GearsDifferential": { 1033 | "GearStack": "FIA", 1034 | "FrictionFaces": 8, 1035 | "DiffPreload": "100 Nm" 1036 | } 1037 | }, 1038 | "Dampers": { 1039 | "FrontDampers": { 1040 | "LowSpeedCompressionDamping": "5 clicks", 1041 | "HighSpeedCompressionDamping": "0 clicks", 1042 | "LowSpeedReboundDamping": "5 clicks", 1043 | "HighSpeedReboundDamping": "5 clicks" 1044 | }, 1045 | "RearDampers": { 1046 | "LowSpeedCompressionDamping": "5 clicks", 1047 | "HighSpeedCompressionDamping": "0 clicks", 1048 | "LowSpeedReboundDamping": "5 clicks", 1049 | "HighSpeedReboundDamping": "7 clicks" 1050 | } 1051 | } 1052 | } 1053 | } 1054 | } -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { EventEmitter } from 'events'; 3 | 4 | declare module 'iracing-sdk-js' { 5 | export interface InitOptions { 6 | telemetryUpdateInterval?: number; 7 | sessionInfoUpdateInterval?: number; 8 | sessionInfoParser?: (sessionInfo: string) => SessionInfoData; 9 | } 10 | 11 | // String literal types for various enums 12 | export type TrackSurface = 13 | | 'NotInWorld' 14 | | 'OffTrack' 15 | | 'InPitStall' 16 | | 'AproachingPits' 17 | | 'OnTrack'; 18 | 19 | export type TrackSurfaceMaterial = 20 | | 'SurfaceNotInWorld' 21 | | 'UndefinedMaterial' 22 | | 'Asphalt1Material' 23 | | 'Asphalt2Material' 24 | | 'Asphalt3Material' 25 | | 'Asphalt4Material' 26 | | 'Concrete1Material' 27 | | 'Concrete2Material' 28 | | 'RacingDirt1Material' 29 | | 'RacingDirt2Material' 30 | | 'Paint1Material' 31 | | 'Paint2Material' 32 | | 'Rumble1Material' 33 | | 'Rumble2Material' 34 | | 'Rumble3Material' 35 | | 'Rumble4Material' 36 | | 'Grass1Material' 37 | | 'Grass2Material' 38 | | 'Grass3Material' 39 | | 'Grass4Material' 40 | | 'Dirt1Material' 41 | | 'Dirt2Material' 42 | | 'Dirt3Material' 43 | | 'Dirt4Material' 44 | | 'SandMaterial' 45 | | 'Gravel1Material' 46 | | 'Gravel2Material' 47 | | 'GrasscreteMaterial' 48 | | 'AstroturfMaterial'; 49 | 50 | export type SessionState = 51 | | 'Invalid' 52 | | 'GetInCar' 53 | | 'Warmup' 54 | | 'ParadeLaps' 55 | | 'Racing' 56 | | 'Checkered' 57 | | 'CoolDown'; 58 | 59 | export type SessionFlag = 60 | | 'OneLapToGreen' 61 | | 'Servicible' 62 | | 'StartHidden' 63 | | 'Checkered' 64 | | 'White' 65 | | 'Green' 66 | | 'Yellow' 67 | | 'Red' 68 | | 'Blue' 69 | | 'Debris' 70 | | 'Crossed' 71 | | 'YellowWaving' 72 | | 'GreenHeld' 73 | | 'TenToGo' 74 | | 'FiveToGo' 75 | | 'RandomWaving' 76 | | 'Caution' 77 | | 'CautionWaving' 78 | | 'Black' 79 | | 'Disqualify' 80 | | 'Furled' 81 | | 'DQScoringInvalid' 82 | | 'Repair' 83 | | 'StartReady' 84 | | 'StartSet' 85 | | 'StartGo'; 86 | 87 | export type PitServiceStatus = 88 | | 'PitSvNone' 89 | | 'PitSvInProgress' 90 | | 'PitSvComplete' 91 | | 'PitSvTooFarLeft' 92 | | 'PitSvTooFarRight' 93 | | 'PitSvTooFarForward' 94 | | 'PitSvTooFarBack' 95 | | 'PitSvBadAngle' 96 | | 'PitSvCantFixThat'; 97 | 98 | export type CarLeftRight = 99 | | 'LROff' 100 | | 'LRClear' 101 | | 'LRCarLeft' 102 | | 'LRCarRight' 103 | | 'LRCarLeftRight' 104 | | 'LR2CarsLeft' 105 | | 'LR2CarsRight'; 106 | 107 | export type TrackWetness = 108 | | 'UNKNOWN' 109 | | 'Dry' 110 | | 'MostlyDry' 111 | | 'VeryLightlyWet' 112 | | 'LightlyWet' 113 | | 'ModeratelyWet' 114 | | 'VeryWet' 115 | | 'ExtremelyWet'; 116 | 117 | export type CameraState = 118 | | 'IsSessionScreen' 119 | | 'IsScenicActive' 120 | | 'CamToolActive' 121 | | 'UIHidden' 122 | | 'UseAutoShotSelection' 123 | | 'UseTemporaryEdits' 124 | | 'UseKeyAcceleration' 125 | | 'UseKey10xAcceleration' 126 | | 'UseMouseAimMode'; 127 | 128 | export type EngineWarning = 129 | | 'WaterTempWarning' 130 | | 'FuelPressureWarning' 131 | | 'OilPressureWarning' 132 | | 'EngineStalled' 133 | | 'PitSpeedLimiter' 134 | | 'RevLimiterActive' 135 | | 'OilTempWarning' 136 | | 'MandRepNeeded' 137 | | 'OptRepNeeded'; 138 | 139 | export type PaceMode = 140 | | 'PaceModeSingleFileStart' 141 | | 'PaceModeDoubleFileStart' 142 | | 'PaceModeSingleFileRestart' 143 | | 'PaceModeDoubleFileRestart' 144 | | 'PaceModeNotPacing'; 145 | 146 | export type IncidentType = 147 | | 'NoPenalty(0x)' 148 | | 'OutOfControl(2x)' 149 | | 'OffTrack(1x)' 150 | | 'Contact(0x)' 151 | | 'Contact(2x)' 152 | | 'CarContact(0x)' 153 | | 'CarContact(4x)'; 154 | 155 | export interface TelemetryValues { 156 | // Session Data 157 | SessionTime: number; 158 | SessionTick: number; 159 | SessionNum: number; 160 | SessionState: SessionState; 161 | SessionUniqueID: number; 162 | SessionFlags: SessionFlag[]; 163 | 164 | // Player Data 165 | PlayerCarPosition: number; 166 | PlayerCarClassPosition: number; 167 | PlayerTrackSurface: TrackSurface; 168 | PlayerCarIdx: number; 169 | 170 | // Controls Data 171 | SteeringWheelAngle: number; 172 | Throttle: number; 173 | Brake: number; 174 | Clutch: number; 175 | Gear: number; 176 | RPM: number; 177 | 178 | // Physics Data 179 | Speed: number; 180 | Yaw: number; 181 | Pitch: number; 182 | Roll: number; 183 | VertAccel: number; 184 | LatAccel: number; 185 | LongAccel: number; 186 | 187 | // Environment Data 188 | TrackTemp: number; 189 | AirTemp: number; 190 | TrackWetness: TrackWetness; 191 | WindVel: number; 192 | WindDir: number; 193 | 194 | // Additional telemetry fields 195 | SessionTimeRemain: number; 196 | SessionLapsRemain: number; 197 | SessionLapsRemainEx: number; 198 | SessionTimeTotal: number; 199 | SessionLapsTotal: number; 200 | SessionJokerLapsRemain: number; 201 | SessionOnJokerLap: boolean; 202 | SessionTimeOfDay: number; 203 | RadioTransmitCarIdx: number; 204 | RadioTransmitRadioIdx: number; 205 | RadioTransmitFrequencyIdx: number; 206 | DisplayUnits: number; 207 | DriverMarker: boolean; 208 | PushToTalk: boolean; 209 | PushToPass: boolean; 210 | ManualBoost: boolean; 211 | ManualNoBoost: boolean; 212 | IsOnTrack: boolean; 213 | IsReplayPlaying: boolean; 214 | ReplayFrameNum: number; 215 | ReplayFrameNumEnd: number; 216 | IsDiskLoggingEnabled: boolean; 217 | IsDiskLoggingActive: boolean; 218 | FrameRate: number; 219 | CpuUsageFG: number; 220 | GpuUsage: number; 221 | ChanAvgLatency: number; 222 | ChanLatency: number; 223 | ChanQuality: number; 224 | ChanPartnerQuality: number; 225 | CpuUsageBG: number; 226 | ChanClockSkew: number; 227 | MemPageFaultSec: number; 228 | MemSoftPageFaultSec: number; 229 | PlayerCarClass: number; 230 | PlayerTrackSurfaceMaterial: TrackSurfaceMaterial; 231 | PlayerCarTeamIncidentCount: number; 232 | PlayerCarMyIncidentCount: number; 233 | PlayerCarDriverIncidentCount: number; 234 | PlayerCarWeightPenalty: number; 235 | PlayerCarPowerAdjust: number; 236 | PlayerCarDryTireSetLimit: number; 237 | PlayerCarTowTime: number; 238 | PlayerCarInPitStall: boolean; 239 | PlayerCarPitSvStatus: PitServiceStatus; 240 | PlayerTireCompound: number; 241 | PlayerFastRepairsUsed: number; 242 | CarIdxLap: number[]; 243 | CarIdxLapCompleted: number[]; 244 | CarIdxLapDistPct: number[]; 245 | CarIdxTrackSurface: TrackSurface[]; 246 | CarIdxTrackSurfaceMaterial: TrackSurfaceMaterial[]; 247 | CarIdxOnPitRoad: boolean[]; 248 | CarIdxPosition: number[]; 249 | CarIdxClassPosition: number[]; 250 | CarIdxClass: number[]; 251 | CarIdxF2Time: number[]; 252 | CarIdxEstTime: number[]; 253 | CarIdxLastLapTime: number[]; 254 | CarIdxBestLapTime: number[]; 255 | CarIdxBestLapNum: number[]; 256 | CarIdxTireCompound: number[]; 257 | CarIdxQualTireCompound: number[]; 258 | CarIdxQualTireCompoundLocked: boolean[]; 259 | CarIdxFastRepairsUsed: number[]; 260 | CarIdxSessionFlags: SessionFlag[][]; 261 | PaceMode: PaceMode; 262 | CarIdxPaceLine: number[]; 263 | CarIdxPaceRow: number[]; 264 | CarIdxPaceFlags: string[][]; 265 | OnPitRoad: boolean; 266 | CarIdxSteer: number[]; 267 | CarIdxRPM: number[]; 268 | CarIdxGear: number[]; 269 | PlayerCarSLFirstRPM: number; 270 | PlayerCarSLShiftRPM: number; 271 | PlayerCarSLLastRPM: number; 272 | PlayerCarSLBlinkRPM: number; 273 | Lap: number; 274 | LapCompleted: number; 275 | LapDist: number; 276 | LapDistPct: number; 277 | RaceLaps: number; 278 | CarDistAhead: number; 279 | CarDistBehind: number; 280 | LapBestLap: number; 281 | LapBestLapTime: number; 282 | LapLastLapTime: number; 283 | LapCurrentLapTime: number; 284 | LapLasNLapSeq: number; 285 | LapLastNLapTime: number; 286 | LapBestNLapLap: number; 287 | LapBestNLapTime: number; 288 | LapDeltaToBestLap: number; 289 | LapDeltaToBestLap_DD: number; 290 | LapDeltaToBestLap_OK: boolean; 291 | LapDeltaToOptimalLap: number; 292 | LapDeltaToOptimalLap_DD: number; 293 | LapDeltaToOptimalLap_OK: boolean; 294 | LapDeltaToSessionBestLap: number; 295 | LapDeltaToSessionBestLap_DD: number; 296 | LapDeltaToSessionBestLap_OK: boolean; 297 | LapDeltaToSessionOptimalLap: number; 298 | LapDeltaToSessionOptimalLap_DD: number; 299 | LapDeltaToSessionOptimalLap_OK: boolean; 300 | LapDeltaToSessionLastlLap: number; 301 | LapDeltaToSessionLastlLap_DD: number; 302 | LapDeltaToSessionLastlLap_OK: boolean; 303 | YawNorth: number; 304 | EnterExitReset: number; 305 | TrackTempCrew: number; 306 | Skies: number; 307 | AirDensity: number; 308 | AirPressure: number; 309 | RelativeHumidity: number; 310 | FogLevel: number; 311 | Precipitation: number; 312 | SolarAltitude: number; 313 | SolarAzimuth: number; 314 | WeatherDeclaredWet: boolean; 315 | SteeringFFBEnabled: boolean; 316 | DCLapStatus: number; 317 | DCDriversSoFar: number; 318 | OkToReloadTextures: boolean; 319 | LoadNumTextures: boolean; 320 | CarLeftRight: CarLeftRight; 321 | PitsOpen: boolean; 322 | VidCapEnabled: boolean; 323 | VidCapActive: boolean; 324 | PlayerIncidents: IncidentType; 325 | PitRepairLeft: number; 326 | PitOptRepairLeft: number; 327 | PitstopActive: boolean; 328 | FastRepairUsed: number; 329 | FastRepairAvailable: number; 330 | LFTiresUsed: number; 331 | RFTiresUsed: number; 332 | LRTiresUsed: number; 333 | RRTiresUsed: number; 334 | LeftTireSetsUsed: number; 335 | RightTireSetsUsed: number; 336 | FrontTireSetsUsed: number; 337 | RearTireSetsUsed: number; 338 | TireSetsUsed: number; 339 | LFTiresAvailable: number; 340 | RFTiresAvailable: number; 341 | LRTiresAvailable: number; 342 | RRTiresAvailable: number; 343 | LeftTireSetsAvailable: number; 344 | RightTireSetsAvailable: number; 345 | FrontTireSetsAvailable: number; 346 | RearTireSetsAvailable: number; 347 | TireSetsAvailable: number; 348 | CamCarIdx: number; 349 | CamCameraNumber: number; 350 | CamGroupNumber: number; 351 | CamCameraState: CameraState[]; 352 | IsOnTrackCar: boolean; 353 | IsInGarage: boolean; 354 | SteeringWheelAngleMax: number; 355 | ShiftPowerPct: number; 356 | ShiftGrindRPM: number; 357 | ThrottleRaw: number; 358 | BrakeRaw: number; 359 | ClutchRaw: number; 360 | HandbrakeRaw: number; 361 | BrakeABSactive: boolean; 362 | Shifter: number; 363 | EngineWarnings: EngineWarning[]; 364 | FuelLevelPct: number; 365 | PitSvFlags: string[]; 366 | PitSvLFP: number; 367 | PitSvRFP: number; 368 | PitSvLRP: number; 369 | PitSvRRP: number; 370 | PitSvFuel: number; 371 | PitSvTireCompound: number; 372 | CarIdxP2P_Status: boolean[]; 373 | CarIdxP2P_Count: number[]; 374 | P2P_Status: boolean; 375 | P2P_Count: number; 376 | SteeringWheelPctTorque: number; 377 | SteeringWheelPctTorqueSign: number; 378 | SteeringWheelPctTorqueSignStops: number; 379 | SteeringWheelPctIntensity: number; 380 | SteeringWheelPctSmoothing: number; 381 | SteeringWheelPctDamper: number; 382 | SteeringWheelLimiter: number; 383 | SteeringWheelMaxForceNm: number; 384 | SteeringWheelPeakForceNm: number; 385 | SteeringWheelUseLinear: boolean; 386 | ShiftIndicatorPct: number; 387 | ReplayPlaySpeed: number; 388 | ReplayPlaySlowMotion: boolean; 389 | ReplaySessionTime: number; 390 | ReplaySessionNum: number; 391 | TireLF_RumblePitch: number; 392 | TireRF_RumblePitch: number; 393 | TireLR_RumblePitch: number; 394 | TireRR_RumblePitch: number; 395 | IsGarageVisible: boolean; 396 | SteeringWheelTorque_ST: number[]; 397 | SteeringWheelTorque: number; 398 | VelocityZ_ST: number[]; 399 | VelocityY_ST: number[]; 400 | VelocityX_ST: number[]; 401 | VelocityZ: number; 402 | VelocityY: number; 403 | VelocityX: number; 404 | YawRate_ST: number[]; 405 | PitchRate_ST: number[]; 406 | RollRate_ST: number[]; 407 | YawRate: number; 408 | PitchRate: number; 409 | RollRate: number; 410 | VertAccel_ST: number[]; 411 | LatAccel_ST: number[]; 412 | LongAccel_ST: number[]; 413 | dcStarter: boolean; 414 | dcPitSpeedLimiterToggle: boolean; 415 | dcTractionControlToggle: boolean; 416 | dcHeadlightFlash: boolean; 417 | dcLowFuelAccept: boolean; 418 | dpRFTireChange: number; 419 | dpLFTireChange: number; 420 | dpRRTireChange: number; 421 | dpLRTireChange: number; 422 | dpFuelFill: number; 423 | dpFuelAutoFillEnabled: number; 424 | dpFuelAutoFillActive: number; 425 | dpWindshieldTearoff: number; 426 | dpFuelAddKg: number; 427 | dpFastRepair: number; 428 | dcDashPage: number; 429 | dcBrakeBias: number; 430 | dpLFTireColdPress: number; 431 | dpRFTireColdPress: number; 432 | dpLRTireColdPress: number; 433 | dpRRTireColdPress: number; 434 | dcTractionControl: number; 435 | dcABS: number; 436 | dcToggleWindshieldWipers: boolean; 437 | dcTriggerWindshieldWipers: boolean; 438 | RFbrakeLinePress: number; 439 | RFcoldPressure: number; 440 | RFodometer: number; 441 | RFtempCL: number; 442 | RFtempCM: number; 443 | RFtempCR: number; 444 | RFwearL: number; 445 | RFwearM: number; 446 | RFwearR: number; 447 | LFbrakeLinePress: number; 448 | LFcoldPressure: number; 449 | LFodometer: number; 450 | LFtempCL: number; 451 | LFtempCM: number; 452 | LFtempCR: number; 453 | LFwearL: number; 454 | LFwearM: number; 455 | LFwearR: number; 456 | FuelUsePerHour: number; 457 | Voltage: number; 458 | WaterTemp: number; 459 | WaterLevel: number; 460 | FuelPress: number; 461 | OilTemp: number; 462 | OilPress: number; 463 | OilLevel: number; 464 | ManifoldPress: number; 465 | FuelLevel: number; 466 | Engine0_RPM: number; 467 | RRbrakeLinePress: number; 468 | RRcoldPressure: number; 469 | RRodometer: number; 470 | RRtempCL: number; 471 | RRtempCM: number; 472 | RRtempCR: number; 473 | RRwearL: number; 474 | RRwearM: number; 475 | RRwearR: number; 476 | LRbrakeLinePress: number; 477 | LRcoldPressure: number; 478 | LRodometer: number; 479 | LRtempCL: number; 480 | LRtempCM: number; 481 | LRtempCR: number; 482 | LRwearL: number; 483 | LRwearM: number; 484 | LRwearR: number; 485 | LRshockDefl: number; 486 | LRshockDefl_ST: number[]; 487 | LRshockVel: number; 488 | LRshockVel_ST: number[]; 489 | RRshockDefl: number; 490 | RRshockDefl_ST: number[]; 491 | RRshockVel: number; 492 | RRshockVel_ST: number[]; 493 | LFshockDefl: number; 494 | LFshockDefl_ST: number[]; 495 | LFshockVel: number; 496 | LFshockVel_ST: number[]; 497 | RFshockDefl: number; 498 | RFshockDefl_ST: number[]; 499 | RFshockVel: number; 500 | RFshockVel_ST: number[]; 501 | // Additional fields that may exist 502 | [key: string]: any; 503 | } 504 | 505 | export interface SessionInfoData { 506 | WeekendInfo: SessionInfoDataWeekendInfo; 507 | SessionInfo: SessionInfoDataSessionInfo; 508 | CameraInfo: SessionInfoDataCameraInfo; 509 | RadioInfo: SessionInfoDataRadioInfo; 510 | DriverInfo: SessionInfoDataDriverInfo; 511 | SplitTimeInfo: SessionInfoDataSplitTimeInfo; 512 | CarSetup: SessionInfoDataCarSetup; 513 | } 514 | export interface SessionInfoDataCarSetup { 515 | UpdateCount: number; 516 | TiresAero: SessionInfoDataCarSetupTiresAero; 517 | Chassis: SessionInfoDataCarSetupChassis; 518 | Dampers: SessionInfoDataCarSetupDampers; 519 | } 520 | export interface SessionInfoDataCarSetupDampers { 521 | FrontDampers: SessionInfoDataCarSetupDampersFrontDampers; 522 | RearDampers: SessionInfoDataCarSetupDampersRearDampers; 523 | } 524 | export interface SessionInfoDataCarSetupDampersRearDampers { 525 | LowSpeedCompressionDamping: string; 526 | HighSpeedCompressionDamping: string; 527 | LowSpeedReboundDamping: string; 528 | HighSpeedReboundDamping: string; 529 | } 530 | export interface SessionInfoDataCarSetupDampersFrontDampers { 531 | LowSpeedCompressionDamping: string; 532 | HighSpeedCompressionDamping: string; 533 | LowSpeedReboundDamping: string; 534 | HighSpeedReboundDamping: string; 535 | } 536 | export interface SessionInfoDataCarSetupChassis { 537 | FrontBrakes: SessionInfoDataCarSetupChassisFrontBrakes; 538 | LeftFront: SessionInfoDataCarSetupChassisLeftFront; 539 | LeftRear: SessionInfoDataCarSetupChassisLeftRear; 540 | Rear: SessionInfoDataCarSetupChassisRear; 541 | InCarAdjustments: SessionInfoDataCarSetupChassisInCarAdjustments; 542 | RightFront: SessionInfoDataCarSetupChassisRightFront; 543 | RightRear: SessionInfoDataCarSetupChassisRightRear; 544 | GearsDifferential: SessionInfoDataCarSetupChassisGearsDifferential; 545 | } 546 | export interface SessionInfoDataCarSetupChassisGearsDifferential { 547 | GearStack: string; 548 | FrictionFaces: number; 549 | DiffPreload: string; 550 | } 551 | export interface SessionInfoDataCarSetupChassisRightRear { 552 | CornerWeight: string; 553 | RideHeight: string; 554 | BumpRubberGap: string; 555 | SpringRate: string; 556 | Camber: string; 557 | ToeIn: string; 558 | } 559 | export interface SessionInfoDataCarSetupChassisRightFront { 560 | CornerWeight: string; 561 | RideHeight: string; 562 | BumpRubberGap: string; 563 | SpringRate: string; 564 | Camber: string; 565 | } 566 | export interface SessionInfoDataCarSetupChassisInCarAdjustments { 567 | BrakePressureBias: string; 568 | AbsSetting: string; 569 | TcSetting: string; 570 | DashDisplayPage: string; 571 | CrossWeight: string; 572 | } 573 | export interface SessionInfoDataCarSetupChassisRear { 574 | FuelLevel: string; 575 | ArbBlades: number; 576 | RearWingAngle: string; 577 | } 578 | export interface SessionInfoDataCarSetupChassisLeftRear { 579 | CornerWeight: string; 580 | RideHeight: string; 581 | BumpRubberGap: string; 582 | SpringRate: string; 583 | Camber: string; 584 | ToeIn: string; 585 | } 586 | export interface SessionInfoDataCarSetupChassisLeftFront { 587 | CornerWeight: string; 588 | RideHeight: string; 589 | BumpRubberGap: string; 590 | SpringRate: string; 591 | Camber: string; 592 | } 593 | export interface SessionInfoDataCarSetupChassisFrontBrakes { 594 | ArbBlades: number; 595 | TotalToeIn: string; 596 | BrakePedalRatio: number; 597 | BrakePads: string; 598 | FWtdist: string; 599 | CenterFrontSplitterHeight: string; 600 | } 601 | export interface SessionInfoDataCarSetupTiresAero { 602 | TireType: SessionInfoDataCarSetupTiresAeroTireType; 603 | LeftFront: SessionInfoDataCarSetupTiresAeroLeftFront; 604 | LeftRear: SessionInfoDataCarSetupTiresAeroLeftRear; 605 | RightFront: SessionInfoDataCarSetupTiresAeroRightFront; 606 | RightRear: SessionInfoDataCarSetupTiresAeroRightRear; 607 | AeroBalanceCalc: SessionInfoDataCarSetupTiresAeroAeroBalanceCalc; 608 | } 609 | export interface SessionInfoDataCarSetupTiresAeroAeroBalanceCalc { 610 | FrontRhAtSpeed: string; 611 | RearRhAtSpeed: string; 612 | RearWingAngle: string; 613 | FrontDownforce: string; 614 | } 615 | export interface SessionInfoDataCarSetupTiresAeroRightRear { 616 | StartingPressure: string; 617 | LastHotPressure: string; 618 | LastTempsIMO: string; 619 | TreadRemaining: string; 620 | } 621 | export interface SessionInfoDataCarSetupTiresAeroRightFront { 622 | StartingPressure: string; 623 | LastHotPressure: string; 624 | LastTempsIMO: string; 625 | TreadRemaining: string; 626 | } 627 | export interface SessionInfoDataCarSetupTiresAeroLeftRear { 628 | StartingPressure: string; 629 | LastHotPressure: string; 630 | LastTempsOMI: string; 631 | TreadRemaining: string; 632 | } 633 | export interface SessionInfoDataCarSetupTiresAeroLeftFront { 634 | StartingPressure: string; 635 | LastHotPressure: string; 636 | LastTempsOMI: string; 637 | TreadRemaining: string; 638 | } 639 | export interface SessionInfoDataCarSetupTiresAeroTireType { 640 | TireType: TrackWetness; 641 | } 642 | export interface SessionInfoDataSplitTimeInfo { 643 | Sectors: SessionInfoDataSplitTimeInfoSectorsItem[]; 644 | } 645 | export interface SessionInfoDataSplitTimeInfoSectorsItem { 646 | SectorNum: number; 647 | SectorStartPct: number; 648 | } 649 | export interface SessionInfoDataDriverInfo { 650 | DriverCarIdx: number; 651 | DriverUserID: number; 652 | PaceCarIdx: number; 653 | DriverIsAdmin?: number; 654 | DriverHeadPosX: number; 655 | DriverHeadPosY: number; 656 | DriverHeadPosZ: number; 657 | DriverCarIsElectric: number; 658 | DriverCarIdleRPM: number; 659 | DriverCarRedLine: number; 660 | DriverCarEngCylinderCount: number; 661 | DriverCarFuelKgPerLtr: number; 662 | DriverCarFuelMaxLtr: number; 663 | DriverCarMaxFuelPct: number; 664 | DriverCarGearNumForward: number; 665 | DriverCarGearNeutral: number; 666 | DriverCarGearReverse: number; 667 | DriverGearboxType: string; 668 | DriverGearboxControlType: string; 669 | DriverCarShiftAid: string; 670 | DriverCarSLFirstRPM: number; 671 | DriverCarSLShiftRPM: number; 672 | DriverCarSLLastRPM: number; 673 | DriverCarSLBlinkRPM: number; 674 | DriverCarVersion: string; 675 | DriverPitTrkPct: number; 676 | DriverCarEstLapTime: number; 677 | DriverSetupName: string; 678 | DriverSetupIsModified: number; 679 | DriverSetupLoadTypeName: string; 680 | DriverSetupPassedTech: number; 681 | DriverIncidentCount: number; 682 | DriverBrakeCurvingFactor: number; 683 | DriverTires: SessionInfoDataDriverInfoDriverTiresItem[]; 684 | Drivers: SessionInfoDataDriverInfoDriversItem[]; 685 | } 686 | export interface SessionInfoDataDriverInfoDriversItem { 687 | CarIdx: number; 688 | UserName: string; 689 | AbbrevName: any; 690 | Initials: any; 691 | UserID: number; 692 | TeamID: number; 693 | TeamName: string; 694 | CarNumber: string; 695 | CarNumberRaw: number; 696 | CarPath: string; 697 | CarClassID: number; 698 | CarID: number; 699 | CarIsPaceCar: number; 700 | CarIsAI: number; 701 | CarIsElectric: number; 702 | CarScreenName: string; 703 | CarScreenNameShort: string; 704 | CarCfg: number; 705 | CarCfgName: any; 706 | CarCfgCustomPaintExt: any; 707 | CarClassShortName: any; 708 | CarClassRelSpeed: number; 709 | CarClassLicenseLevel: number; 710 | CarClassMaxFuelPct: string; 711 | CarClassWeightPenalty: string; 712 | CarClassPowerAdjust: string; 713 | CarClassDryTireSetLimit: string; 714 | CarClassColor: number; 715 | CarClassEstLapTime: number; 716 | IRating: number; 717 | LicLevel: number; 718 | LicSubLevel: number; 719 | LicString: string; 720 | LicColor: string; 721 | IsSpectator: number; 722 | CarDesignStr: string; 723 | HelmetDesignStr: string; 724 | SuitDesignStr: string; 725 | BodyType: number; 726 | FaceType: number; 727 | HelmetType: number; 728 | CarNumberDesignStr: string; 729 | CarSponsor_1: number; 730 | CarSponsor_2: number; 731 | CurDriverIncidentCount: number; 732 | TeamIncidentCount: number; 733 | } 734 | export interface SessionInfoDataDriverInfoDriverTiresItem { 735 | TireIndex: number; 736 | TireCompoundType: string; 737 | } 738 | export interface SessionInfoDataRadioInfo { 739 | SelectedRadioNum: number; 740 | Radios: SessionInfoDataRadioInfoRadiosItem[]; 741 | } 742 | export interface SessionInfoDataRadioInfoRadiosItem { 743 | RadioNum: number; 744 | HopCount: number; 745 | NumFrequencies: number; 746 | TunedToFrequencyNum: number; 747 | ScanningIsOn: number; 748 | Frequencies: SessionInfoDataRadioInfoRadiosItemFrequenciesItem[]; 749 | } 750 | export interface SessionInfoDataRadioInfoRadiosItemFrequenciesItem { 751 | FrequencyNum: number; 752 | FrequencyName: string; 753 | Priority: number; 754 | CarIdx: number; 755 | EntryIdx: number; 756 | ClubID: number; 757 | CanScan: number; 758 | CanSquawk: number; 759 | Muted: number; 760 | IsMutable: number; 761 | IsDeletable: number; 762 | } 763 | export interface SessionInfoDataCameraInfo { 764 | Groups: SessionInfoDataCameraInfoGroupsItem[]; 765 | } 766 | export interface SessionInfoDataCameraInfoGroupsItem { 767 | GroupNum: number; 768 | GroupName: string; 769 | IsScenic?: boolean; 770 | Cameras: SessionInfoDataCameraInfoGroupsItemCamerasItem[]; 771 | } 772 | export interface SessionInfoDataCameraInfoGroupsItemCamerasItem { 773 | CameraNum: number; 774 | CameraName: string; 775 | } 776 | export interface SessionInfoDataSessionInfo { 777 | CurrentSessionNum: number; 778 | Sessions: SessionInfoDataSessionInfoSessionsItem[]; 779 | } 780 | export interface SessionInfoDataSessionInfoSessionsItem { 781 | SessionNum: number; 782 | SessionLaps: string; 783 | SessionTime: string; 784 | SessionNumLapsToAvg: number; 785 | SessionType: string; 786 | SessionTrackRubberState: string; 787 | SessionName: string; 788 | SessionSubType: any; 789 | SessionSkipped: number; 790 | SessionRunGroupsUsed: number; 791 | SessionEnforceTireCompoundChange: number; 792 | ResultsPositions: any; 793 | ResultsFastestLap: SessionInfoDataSessionInfoSessionsItemResultsFastestLapItem[]; 794 | ResultsAverageLapTime: number; 795 | ResultsNumCautionFlags: number; 796 | ResultsNumCautionLaps: number; 797 | ResultsNumLeadChanges: number; 798 | ResultsLapsComplete: number; 799 | ResultsOfficial: number; 800 | } 801 | export interface SessionInfoDataSessionInfoSessionsItemResultsFastestLapItem { 802 | CarIdx: number; 803 | FastestLap: number; 804 | FastestTime: number; 805 | } 806 | export interface SessionInfoDataWeekendInfo { 807 | TrackName: string; 808 | TrackID: number; 809 | TrackLength: string; 810 | TrackLengthOfficial: string; 811 | TrackDisplayName: string; 812 | TrackDisplayShortName: string; 813 | TrackConfigName: string; 814 | TrackCity: string; 815 | TrackState: string; 816 | TrackCountry: string; 817 | TrackAltitude: string; 818 | TrackLatitude: string; 819 | TrackLongitude: string; 820 | TrackNorthOffset: string; 821 | TrackNumTurns: number; 822 | TrackPitSpeedLimit: string; 823 | TrackPaceSpeed: string; 824 | TrackNumPitStalls: number; 825 | TrackType: string; 826 | TrackDirection: string; 827 | TrackWeatherType: string; 828 | TrackSkies: string; 829 | TrackSurfaceTemp: string; 830 | TrackSurfaceTempCrew: string; 831 | TrackAirTemp: string; 832 | TrackAirPressure: string; 833 | TrackAirDensity: string; 834 | TrackWindVel: string; 835 | TrackWindDir: string; 836 | TrackRelativeHumidity: string; 837 | TrackFogLevel: string; 838 | TrackPrecipitation: string; 839 | TrackCleanup: number; 840 | TrackDynamicTrack: number; 841 | TrackVersion: string; 842 | SeriesID: number; 843 | SeasonID: number; 844 | SessionID: number; 845 | SubSessionID: number; 846 | LeagueID: number; 847 | Official: number; 848 | RaceWeek: number; 849 | EventType: string; 850 | Category: string; 851 | SimMode: string; 852 | TeamRacing: number; 853 | MinDrivers: number; 854 | MaxDrivers: number; 855 | DCRuleSet: string; 856 | QualifierMustStartRace: number; 857 | NumCarClasses: number; 858 | NumCarTypes: number; 859 | HeatRacing: number; 860 | BuildType: string; 861 | BuildTarget: string; 862 | BuildVersion: string; 863 | RaceFarm: any; 864 | WeekendOptions: SessionInfoDataWeekendInfoWeekendOptions; 865 | TelemetryOptions: SessionInfoDataWeekendInfoTelemetryOptions; 866 | } 867 | export interface SessionInfoDataWeekendInfoTelemetryOptions { 868 | TelemetryDiskFile: string; 869 | } 870 | export interface SessionInfoDataWeekendInfoWeekendOptions { 871 | NumStarters: number; 872 | StartingGrid: string; 873 | QualifyScoring: string; 874 | CourseCautions: string; 875 | StandingStart: number; 876 | ShortParadeLap: number; 877 | Restarts: string; 878 | WeatherType: string; 879 | Skies: string; 880 | WindDirection: string; 881 | WindSpeed: string; 882 | WeatherTemp: string; 883 | RelativeHumidity: string; 884 | FogLevel: string; 885 | TimeOfDay: string; 886 | Date: string; 887 | EarthRotationSpeedupFactor: number; 888 | Unofficial: number; 889 | CommercialMode: string; 890 | NightMode: string; 891 | IsFixedSetup: number; 892 | StrictLapsChecking: string; 893 | HasOpenRegistration: number; 894 | HardcoreLevel: number; 895 | NumJokerLaps: number; 896 | IncidentLimit: string; 897 | FastRepairsLimit: string; 898 | GreenWhiteCheckeredLimit: number; 899 | } 900 | 901 | export interface TelemetryEvent { 902 | timestamp: Date; 903 | values: TelemetryValues; 904 | } 905 | 906 | export interface SessionInfoEvent { 907 | timestamp: Date; 908 | data: SessionInfoData; 909 | } 910 | 911 | export interface UpdateEvent { 912 | type: 913 | | 'Connected' 914 | | 'Disconnected' 915 | | 'Telemetry' 916 | | 'TelemetryDescription' 917 | | 'SessionInfo'; 918 | data?: any; 919 | timestamp: Date; 920 | } 921 | 922 | export interface CameraControls { 923 | setState(state: number): void; 924 | switchToCar( 925 | carNum: number | string, 926 | camGroupNum?: number, 927 | camNum?: number 928 | ): void; 929 | switchToPos( 930 | position: number | string, 931 | camGroupNum?: number, 932 | camNum?: number 933 | ): void; 934 | } 935 | 936 | export interface PlaybackControls { 937 | play(): void; 938 | pause(): void; 939 | fastForward(speed?: number): void; 940 | rewind(speed?: number): void; 941 | slowForward(divider?: number): void; 942 | slowBackward(divider?: number): void; 943 | search(searchMode: number | string): void; 944 | searchTs(sessionNum: number, sessionTimeMS: number): void; 945 | searchFrame(frameNum: number, rpyPosMode: number | string): void; 946 | } 947 | 948 | export interface IrSdkConsts { 949 | BroadcastMsg: { 950 | CamSwitchPos: 0; 951 | CamSwitchNum: 1; 952 | CamSetState: 2; 953 | ReplaySetPlaySpeed: 3; 954 | ReplaySetPlayPosition: 4; 955 | ReplaySearch: 5; 956 | ReplaySetState: 6; 957 | ReloadTextures: 7; 958 | ChatComand: 8; 959 | PitCommand: 9; 960 | TelemCommand: 10; 961 | FFBCommand: 11; 962 | ReplaySearchSessionTime: 12; 963 | }; 964 | CameraState: { 965 | IsSessionScreen: 0x0001; 966 | IsScenicActive: 0x0002; 967 | CamToolActive: 0x0004; 968 | UIHidden: 0x0008; 969 | UseAutoShotSelection: 0x0010; 970 | UseTemporaryEdits: 0x0020; 971 | UseKeyAcceleration: 0x0040; 972 | UseKey10xAcceleration: 0x0080; 973 | UseMouseAimMode: 0x0100; 974 | }; 975 | RpyPosMode: { 976 | Begin: 0; 977 | Current: 1; 978 | End: 2; 979 | }; 980 | RpySrchMode: { 981 | ToStart: 0; 982 | ToEnd: 1; 983 | PrevSession: 2; 984 | NextSession: 3; 985 | PrevLap: 4; 986 | NextLap: 5; 987 | PrevFrame: 6; 988 | NextFrame: 7; 989 | PrevIncident: 8; 990 | NextIncident: 9; 991 | }; 992 | RpyStateMode: { 993 | EraseTape: 0; 994 | }; 995 | ReloadTexturesMode: { 996 | All: 0; 997 | CarIdx: 1; 998 | }; 999 | ChatCommand: { 1000 | Macro: 0; 1001 | BeginChat: 1; 1002 | Reply: 2; 1003 | Cancel: 3; 1004 | }; 1005 | PitCommand: { 1006 | Clear: 0; 1007 | WS: 1; 1008 | Fuel: 2; 1009 | LF: 3; 1010 | RF: 4; 1011 | LR: 5; 1012 | RR: 6; 1013 | ClearTires: 7; 1014 | FR: 8; 1015 | ClearWS: 9; 1016 | ClearFR: 10; 1017 | ClearFuel: 11; 1018 | }; 1019 | TelemCommand: { 1020 | Stop: 0; 1021 | Start: 1; 1022 | Restart: 2; 1023 | }; 1024 | CamFocusAt: { 1025 | Incident: -3; 1026 | Leader: -2; 1027 | Exciting: -1; 1028 | Driver: 0; 1029 | }; 1030 | } 1031 | 1032 | export interface JsIrSdk extends EventEmitter { 1033 | connected: boolean; 1034 | telemetry: { timestamp: Date; values: TelemetryValues } | null; 1035 | telemetryDescription: any | null; 1036 | sessionInfo: { timestamp: Date; data: SessionInfoData } | null; 1037 | Consts: IrSdkConsts; 1038 | camControls: CameraControls; 1039 | playbackControls: PlaybackControls; 1040 | 1041 | execCmd(msgId: number, arg1?: number, arg2?: number, arg3?: number): void; 1042 | reloadTextures(): void; 1043 | reloadTexture(carIdx: number): void; 1044 | execChatCmd(cmd: number | string, arg?: number): void; 1045 | execChatMacro(num: number): void; 1046 | execPitCmd(cmd: number | string, arg?: number): void; 1047 | execTelemetryCmd(cmd: number | string): void; 1048 | 1049 | on(event: 'Connected', listener: () => void): this; 1050 | on(event: 'Disconnected', listener: () => void): this; 1051 | on(event: 'Telemetry', listener: (evt: TelemetryEvent) => void): this; 1052 | on(event: 'TelemetryDescription', listener: (data: any) => void): this; 1053 | on(event: 'SessionInfo', listener: (evt: SessionInfoEvent) => void): this; 1054 | on(event: 'update', listener: (evt: UpdateEvent) => void): this; 1055 | on(event: string | symbol, listener: (...args: any[]) => void): this; 1056 | 1057 | once(event: 'Connected', listener: () => void): this; 1058 | once(event: 'Disconnected', listener: () => void): this; 1059 | once(event: 'Telemetry', listener: (evt: TelemetryEvent) => void): this; 1060 | once(event: 'TelemetryDescription', listener: (data: any) => void): this; 1061 | once(event: 'SessionInfo', listener: (evt: SessionInfoEvent) => void): this; 1062 | once(event: 'update', listener: (evt: UpdateEvent) => void): this; 1063 | once(event: string | symbol, listener: (...args: any[]) => void): this; 1064 | 1065 | emit(event: 'Connected'): boolean; 1066 | emit(event: 'Disconnected'): boolean; 1067 | emit(event: 'Telemetry', evt: TelemetryEvent): boolean; 1068 | emit(event: 'TelemetryDescription', data: any): boolean; 1069 | emit(event: 'SessionInfo', evt: SessionInfoEvent): boolean; 1070 | emit(event: 'update', evt: UpdateEvent): boolean; 1071 | emit(event: string | symbol, ...args: any[]): boolean; 1072 | } 1073 | 1074 | export function init(opts?: InitOptions): JsIrSdk; 1075 | export function getInstance(): JsIrSdk; 1076 | } 1077 | --------------------------------------------------------------------------------