├── .github
└── workflows
│ ├── build-test.yml
│ └── npm-publish.yml
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── binding.gyp
├── doc
├── README.hbs
└── examples.hbs
├── examples
├── cam-example.js
├── print-names.js
├── recorder.js
└── sample-writer.js
├── package.json
├── prettier.config.js
├── sample-data
├── sessioninfo.json
├── telemetry-desc.json
└── telemetry.json
├── src
├── JsIrSdk.js
├── consts
│ └── IrSdkConsts.js
├── cpp
│ ├── IRSDKWrapper.cpp
│ ├── IRSDKWrapper.h
│ ├── IrSdkBindingHelpers.cpp
│ ├── IrSdkBindingHelpers.h
│ ├── IrSdkCommand.cpp
│ ├── IrSdkCommand.h
│ ├── IrSdkNodeBindings.cpp
│ ├── IrSdkNodeBindings.h
│ ├── irsdk
│ │ └── irsdk_defines.h
│ └── platform
│ │ ├── platform.cpp
│ │ └── platform.h
├── iracing-sdk-js.js
└── utils
│ ├── createSessionInfoParser.js
│ ├── padCarNum.js
│ └── stringToEnum.js
├── test
├── IrsdkNodeWrapper-stub.js
├── JsIrSdk-spec.js
├── iracing-sdk-js-spec.js
├── smoke-test.js
└── utils
│ └── stringToEnum-spec.js
└── yarn.lock
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iracing-sdk-js
2 |
3 | (Another) Unofficial [iRacing](http://www.iracing.com/) SDK implementation for Node.js.
4 |
5 | [](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 | ## Installing
10 |
11 | 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.)
12 |
13 | `npm install --save iracing-sdk-js`
14 |
15 | `yarn add iracing-sdk-js --save`
16 |
17 |
18 | ## API documentation
19 |
20 |
21 |
22 | ### irsdk
23 |
24 | * [irsdk](#module_irsdk)
25 | * [.init([opts])](#module_irsdk.init) ⇒ [iracing
](#iracing)
26 | * [.getInstance()](#module_irsdk.getInstance) ⇒ [iracing
](#iracing)
27 |
28 |
29 |
30 | #### irsdk.init([opts]) ⇒ [iracing
](#iracing)
31 | Initialize JsIrSdk, can be done once before using getInstance first time.
32 |
33 | **Kind**: static method of [irsdk
](#module_irsdk)
34 | **Returns**: [iracing
](#iracing) - Running instance of JsIrSdk
35 |
36 | | Param | Type | Default | Description |
37 | | --- | --- | --- | --- |
38 | | [opts] | Object
| | Options |
39 | | [opts.telemetryUpdateInterval] | Integer
| 0
| Telemetry update interval, milliseconds |
40 | | [opts.sessionInfoUpdateInterval] | Integer
| 0
| SessionInfo update interval, milliseconds |
41 | | [opts.sessionInfoParser] | [sessionInfoParser
](#iracing..sessionInfoParser) | | Custom parser for session info |
42 |
43 |
44 | ```js
45 | const irsdk = require('iracing-sdk-js')
// look for telemetry updates only once per 100 ms
const iracing = irsdk.init({telemetryUpdateInterval: 100})
46 | ```
47 |
48 |
49 | #### irsdk.getInstance() ⇒ [iracing
](#iracing)
50 | Get initialized instance of JsIrSdk
51 |
52 | **Kind**: static method of [irsdk
](#module_irsdk)
53 | **Returns**: [iracing
](#iracing) - Running instance of JsIrSdk
54 |
55 | ```js
56 | const irsdk = require('iracing-sdk-js')
const iracing = irsdk.getInstance()
57 | ```
58 |
59 |
60 | ### iracing ⇐ events.EventEmitter
61 | JsIrSdk is javascript implementation of iRacing SDK.
62 |
63 | Don't use constructor directly, use [getInstance](#module_irsdk.getInstance).
64 |
65 | **Kind**: global class
66 | **Extends**: events.EventEmitter
67 | **Emits**: [Connected
](#iracing+event_Connected), [Disconnected
](#iracing+event_Disconnected), [Telemetry
](#iracing+event_Telemetry), [TelemetryDescription
](#iracing+event_TelemetryDescription), [SessionInfo
](#iracing+event_SessionInfo)
68 | **See**: [EventEmitter API](https://nodejs.org/api/events.html#events_class_eventemitter)
69 |
70 | * [iracing](#iracing) ⇐ events.EventEmitter
71 | * [new JsIrSdk()](#new_iracing_new)
72 | * _instance_
73 | * [.telemetry](#iracing+telemetry)
74 | * [.telemetryDescription](#iracing+telemetryDescription)
75 | * [.sessionInfo](#iracing+sessionInfo)
76 | * [.Consts](#iracing+Consts) : [IrSdkConsts
](#IrSdkConsts)
77 | * [.camControls](#iracing+camControls) : Object
78 | * [.playbackControls](#iracing+playbackControls) : Object
79 | * [.execCmd(msgId, [arg1], [arg2], [arg3])](#iracing+execCmd)
80 | * [.reloadTextures()](#iracing+reloadTextures)
81 | * [.reloadTexture(carIdx)](#iracing+reloadTexture)
82 | * [.execChatCmd(cmd, [arg])](#iracing+execChatCmd)
83 | * [.execChatMacro(num)](#iracing+execChatMacro)
84 | * [.execPitCmd(cmd, [arg])](#iracing+execPitCmd)
85 | * [.execTelemetryCmd(cmd)](#iracing+execTelemetryCmd)
86 | * ["TelemetryDescription"](#iracing+event_TelemetryDescription)
87 | * ["Telemetry"](#iracing+event_Telemetry)
88 | * ["SessionInfo"](#iracing+event_SessionInfo)
89 | * ["update"](#iracing+event_update)
90 | * ["Connected"](#iracing+event_Connected)
91 | * ["Disconnected"](#iracing+event_Disconnected)
92 | * _inner_
93 | * [~sessionInfoParser](#iracing..sessionInfoParser) ⇒ Object
94 |
95 |
96 |
97 | #### new JsIrSdk()
98 |
99 | ```js
100 | const iracing = require('iracing-sdk-js').getInstance()
101 | ```
102 |
103 |
104 | #### iracing.telemetry
105 | Latest telemetry, may be null or undefined
106 |
107 | **Kind**: instance property of [iracing
](#iracing)
108 |
109 |
110 | #### iracing.telemetryDescription
111 | Latest telemetry, may be null or undefined
112 |
113 | **Kind**: instance property of [iracing
](#iracing)
114 |
115 |
116 | #### iracing.sessionInfo
117 | Latest telemetry, may be null or undefined
118 |
119 | **Kind**: instance property of [iracing
](#iracing)
120 |
121 |
122 | #### iracing.Consts : [IrSdkConsts
](#IrSdkConsts)
123 | iRacing SDK related constants
124 |
125 | **Kind**: instance property of [iracing
](#iracing)
126 |
127 |
128 | #### iracing.camControls : Object
129 | Camera controls
130 |
131 | **Kind**: instance property of [iracing
](#iracing)
132 |
133 |
134 | #### iracing.playbackControls : Object
135 | Replay and playback controls
136 |
137 | **Kind**: instance property of [iracing
](#iracing)
138 |
139 |
140 | #### iracing.execCmd(msgId, [arg1], [arg2], [arg3])
141 | Execute any of available commands, excl. FFB command
142 |
143 | **Kind**: instance method of [iracing
](#iracing)
144 |
145 | | Param | Type | Description |
146 | | --- | --- | --- |
147 | | msgId | Integer
| Message id |
148 | | [arg1] | Integer
| 1st argument |
149 | | [arg2] | Integer
| 2nd argument |
150 | | [arg3] | Integer
| 3rd argument |
151 |
152 |
153 |
154 | #### iracing.reloadTextures()
155 | Reload all car textures
156 |
157 | **Kind**: instance method of [iracing
](#iracing)
158 |
159 | ```js
160 | iracing.reloadTextures() // reload all paints
161 | ```
162 |
163 |
164 | #### iracing.reloadTexture(carIdx)
165 | Reload car's texture
166 |
167 | **Kind**: instance method of [iracing
](#iracing)
168 |
169 | | Param | Type | Description |
170 | | --- | --- | --- |
171 | | carIdx | Integer
| car to reload |
172 |
173 |
174 | ```js
175 | iracing.reloadTexture(1) // reload paint of carIdx=1
176 | ```
177 |
178 |
179 | #### iracing.execChatCmd(cmd, [arg])
180 | Execute chat command
181 |
182 | **Kind**: instance method of [iracing
](#iracing)
183 |
184 | | Param | Type | Description |
185 | | --- | --- | --- |
186 | | cmd | [ChatCommand
](#IrSdkConsts.ChatCommand) | |
187 | | [arg] | Integer
| Command argument, if needed |
188 |
189 |
190 | ```js
191 | iracing.execChatCmd('cancel') // close chat window
192 | ```
193 |
194 |
195 | #### iracing.execChatMacro(num)
196 | Execute chat macro
197 |
198 | **Kind**: instance method of [iracing
](#iracing)
199 |
200 | | Param | Type | Description |
201 | | --- | --- | --- |
202 | | num | Integer
| Macro's number (0-15) |
203 |
204 |
205 | ```js
206 | iracing.execChatMacro(1) // macro 1
207 | ```
208 |
209 |
210 | #### iracing.execPitCmd(cmd, [arg])
211 | Execute pit command
212 |
213 | **Kind**: instance method of [iracing
](#iracing)
214 |
215 | | Param | Type | Description |
216 | | --- | --- | --- |
217 | | cmd | [PitCommand
](#IrSdkConsts.PitCommand) | |
218 | | [arg] | Integer
| Command argument, if needed |
219 |
220 |
221 | ```js
222 | // 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
223 | ```
224 |
225 |
226 | #### iracing.execTelemetryCmd(cmd)
227 | Control telemetry logging (ibt file)
228 |
229 | **Kind**: instance method of [iracing
](#iracing)
230 |
231 | | Param | Type | Description |
232 | | --- | --- | --- |
233 | | cmd | [TelemCommand
](#IrSdkConsts.TelemCommand) | Command: start/stop/restart |
234 |
235 |
236 | ```js
237 | iracing.execTelemetryCmd('restart')
238 | ```
239 |
240 |
241 | #### "TelemetryDescription"
242 | Telemetry description, contains description of available telemetry values
243 |
244 | **Kind**: event emitted by [iracing
](#iracing)
245 |
246 | ```js
247 | iracing.on('TelemetryDescription', function (data) {
console.log(evt)
})
248 | ```
249 |
250 |
251 | #### "Telemetry"
252 | Telemetry update
253 |
254 | **Kind**: event emitted by [iracing
](#iracing)
255 |
256 | ```js
257 | iracing.on('Telemetry', function (evt) {
console.log(evt)
})
258 | ```
259 |
260 |
261 | #### "SessionInfo"
262 | SessionInfo update
263 |
264 | **Kind**: event emitted by [iracing
](#iracing)
265 |
266 | ```js
267 | iracing.on('SessionInfo', function (evt) {
console.log(evt)
})
268 | ```
269 |
270 |
271 | #### "update"
272 | any update event
273 |
274 | **Kind**: event emitted by [iracing
](#iracing)
275 |
276 | ```js
277 | iracing.on('update', function (evt) {
console.log(evt)
})
278 | ```
279 |
280 |
281 | #### "Connected"
282 | iRacing, sim, is started
283 |
284 | **Kind**: event emitted by [iracing
](#iracing)
285 |
286 | ```js
287 | iracing.on('Connected', function (evt) {
console.log(evt)
})
288 | ```
289 |
290 |
291 | #### "Disconnected"
292 | iRacing, sim, was closed
293 |
294 | **Kind**: event emitted by [iracing
](#iracing)
295 |
296 | ```js
297 | iracing.on('Disconnected', function (evt) {
console.log(evt)
})
298 | ```
299 |
300 |
301 | #### iracing~sessionInfoParser ⇒ Object
302 | Parser for SessionInfo YAML
303 |
304 | **Kind**: inner typedef of [iracing
](#iracing)
305 | **Returns**: Object
- parsed session info
306 |
307 | | Param | Type | Description |
308 | | --- | --- | --- |
309 | | sessionInfo | String
| SessionInfo YAML |
310 |
311 |
312 |
313 | ### IrSdkConsts
314 | IrSdkConsts, iRacing SDK constants/enums.
315 |
316 | **Kind**: global constant
317 |
318 | ```js
319 | var IrSdkConsts = require('node-irsdk').getInstance().Consts
320 | ```
321 |
322 | * [IrSdkConsts](#IrSdkConsts)
323 | * [.BroadcastMsg](#IrSdkConsts.BroadcastMsg)
324 | * [.CameraState](#IrSdkConsts.CameraState)
325 | * [.RpyPosMode](#IrSdkConsts.RpyPosMode)
326 | * [.RpySrchMode](#IrSdkConsts.RpySrchMode)
327 | * [.RpyStateMode](#IrSdkConsts.RpyStateMode)
328 | * [.ReloadTexturesMode](#IrSdkConsts.ReloadTexturesMode)
329 | * [.ChatCommand](#IrSdkConsts.ChatCommand)
330 | * [.PitCommand](#IrSdkConsts.PitCommand)
331 | * [.TelemCommand](#IrSdkConsts.TelemCommand)
332 | * [.CamFocusAt](#IrSdkConsts.CamFocusAt)
333 |
334 |
335 |
336 | #### IrSdkConsts.BroadcastMsg
337 | Available command messages.
338 |
339 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
340 | **Properties**
341 |
342 | | Name | Default | Description |
343 | | --- | --- | --- |
344 | | CamSwitchPos | 0
| Switch cam, args: car position, group, camera |
345 | | CamSwitchNum | 1
| Switch cam, args, driver #, group, camera |
346 | | CamSetState | 2
| Set cam state, args: CameraState, unused, unused |
347 | | ReplaySetPlaySpeed | 3
| Set replay speed, args: speed, slowMotion, unused |
348 | | ReplaySetPlayPosition | 4
| Jump to frame, args: RpyPosMode, Frame Number (high, low) |
349 | | ReplaySearch | 5
| Search things from replay, args: RpySrchMode, unused, unused |
350 | | ReplaySetState | 6
| Set replay state, args: RpyStateMode, unused, unused |
351 | | ReloadTextures | 7
| Reload textures, args: ReloadTexturesMode, carIdx, unused |
352 | | ChatComand | 8
| Chat commands, args: ChatCommand, subCommand, unused |
353 | | PitCommand | 9
| Pit commands, args: PitCommand, parameter |
354 | | TelemCommand | 10
| Disk telemetry commands, args: TelemCommand, unused, unused |
355 | | FFBCommand | 11
| *not supported by node-irsdk**: Change FFB settings, args: FFBCommandMode, value (float, high, low) |
356 | | ReplaySearchSessionTime | 12
| Search by timestamp, args: sessionNum, sessionTimeMS (high, low) |
357 |
358 |
359 |
360 | #### IrSdkConsts.CameraState
361 | Camera state
362 | Camera state is bitfield; use these values to compose a new state.
363 |
364 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
365 | **Properties**
366 |
367 | | Name | Default | Description |
368 | | --- | --- | --- |
369 | | IsSessionScreen | 1
| Is driver out of the car |
370 | | IsScenicActive | 2
| The scenic camera is active (no focus car) |
371 | | CamToolActive | 4
| Activate camera tool |
372 | | UIHidden | 8
| Hide UI |
373 | | UseAutoShotSelection | 16
| Enable auto shot selection |
374 | | UseTemporaryEdits | 32
| Enable temporary edits |
375 | | UseKeyAcceleration | 64
| Enable key acceleration |
376 | | UseKey10xAcceleration | 128
| Enable 10x key acceleration |
377 | | UseMouseAimMode | 256
| Enable mouse aim |
378 |
379 |
380 |
381 | #### IrSdkConsts.RpyPosMode
382 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
383 | **Properties**
384 |
385 | | Name | Default | Description |
386 | | --- | --- | --- |
387 | | Begin | 0
| Frame number is relative to beginning |
388 | | Current | 1
| Frame number is relative to current frame |
389 | | End | 2
| Frame number is relative to end |
390 |
391 |
392 |
393 | #### IrSdkConsts.RpySrchMode
394 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
395 | **Properties**
396 |
397 | | Name | Default |
398 | | --- | --- |
399 | | ToStart | 0
|
400 | | ToEnd | 1
|
401 | | PrevSession | 2
|
402 | | NextSession | 3
|
403 | | PrevLap | 4
|
404 | | NextLap | 5
|
405 | | PrevFrame | 6
|
406 | | NextFrame | 7
|
407 | | PrevIncident | 8
|
408 | | NextIncident | 9
|
409 |
410 |
411 |
412 | #### IrSdkConsts.RpyStateMode
413 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
414 | **Properties**
415 |
416 | | Name | Default | Description |
417 | | --- | --- | --- |
418 | | EraseTape | 0
| Clear any data in the replay tape (works only if replay spooling disabled) |
419 |
420 |
421 |
422 | #### IrSdkConsts.ReloadTexturesMode
423 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
424 | **Properties**
425 |
426 | | Name | Default |
427 | | --- | --- |
428 | | All | 0
|
429 | | CarIdx | 1
|
430 |
431 |
432 |
433 | #### IrSdkConsts.ChatCommand
434 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
435 | **Properties**
436 |
437 | | Name | Default | Description |
438 | | --- | --- | --- |
439 | | Macro | 0
| Macro, give macro num (0-15) as argument |
440 | | BeginChat | 1
| Open up a new chat window |
441 | | Reply | 2
| Reply to last private chat |
442 | | Cancel | 3
| Close chat window |
443 |
444 |
445 |
446 | #### IrSdkConsts.PitCommand
447 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
448 | **Properties**
449 |
450 | | Name | Default | Description |
451 | | --- | --- | --- |
452 | | Clear | 0
| Clear all pit checkboxes |
453 | | WS | 1
| Clean the winshield, using one tear off |
454 | | Fuel | 2
| Request fuel, optional argument: liters |
455 | | LF | 3
| Request new left front, optional argument: pressure in kPa |
456 | | RF | 4
| Request new right front, optional argument: pressure in kPa |
457 | | LR | 5
| Request new left rear, optional argument: pressure in kPa |
458 | | RR | 6
| Request new right rear, optional argument: pressure in kPa |
459 | | ClearTires | 7
| Clear tire pit checkboxes |
460 | | FR | 8
| Request a fast repair |
461 | | ClearWS | 9
| Disable clear windshield |
462 | | ClearFR | 10
| Disable fast repair |
463 | | ClearFuel | 11
| Disable refueling |
464 |
465 |
466 |
467 | #### IrSdkConsts.TelemCommand
468 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
469 | **Properties**
470 |
471 | | Name | Default | Description |
472 | | --- | --- | --- |
473 | | Stop | 0
| Turn telemetry recording off |
474 | | Start | 1
| Turn telemetry recording on |
475 | | Restart | 2
| Write current file to disk and start a new one |
476 |
477 |
478 |
479 | #### IrSdkConsts.CamFocusAt
480 | When switching camera, these can be used instead of car number / position
481 |
482 | **Kind**: static enum of [IrSdkConsts
](#IrSdkConsts)
483 | **Properties**
484 |
485 | | Name | Default | Description |
486 | | --- | --- | --- |
487 | | Incident | -3
| |
488 | | Leader | -2
| |
489 | | Exciting | -1
| |
490 | | Driver | 0
| Use car number / position instead of this |
491 |
492 |
493 |
494 | ### setState(state)
495 | Change camera tool state
496 |
497 | **Kind**: global function
498 |
499 | | Param | Type | Description |
500 | | --- | --- | --- |
501 | | state | [CameraState
](#IrSdkConsts.CameraState) | new state |
502 |
503 |
504 | ```js
505 | // hide UI and enable mouse aim
var States = iracing.Consts.CameraState
var state = States.CamToolActive | States.UIHidden | States.UseMouseAimMode
iracing.camControls.setState(state)
506 | ```
507 |
508 |
509 | ### switchToCar(carNum, [camGroupNum], [camNum])
510 | Switch camera, focus on car
511 |
512 | **Kind**: global function
513 |
514 | | Param | Type | Description |
515 | | --- | --- | --- |
516 | | carNum | Integer
\| String
\| [CamFocusAt
](#IrSdkConsts.CamFocusAt) | Car to focus on |
517 | | [camGroupNum] | Integer
| Select camera group |
518 | | [camNum] | Integer
| Select camera |
519 |
520 |
521 | ```js
522 | // show car #2
iracing.camControls.switchToCar(2)
523 |
524 | ```
525 |
526 | ```js
527 | // show car #02
iracing.camControls.switchToCar('02')
528 |
529 | ```
530 |
531 | ```js
532 | // show leader
iracing.camControls.switchToCar('leader')
533 |
534 | ```
535 |
536 | ```js
537 | // show car #2 using cam group 3
iracing.camControls.switchToCar(2, 3)
538 | ```
539 |
540 |
541 | ### switchToPos(position, [camGroupNum], [camNum])
542 | Switch camera, focus on position
543 |
544 | **Kind**: global function
545 |
546 | | Param | Type | Description |
547 | | --- | --- | --- |
548 | | position | Integer
\| [CamFocusAt
](#IrSdkConsts.CamFocusAt) | Position to focus on |
549 | | [camGroupNum] | Integer
| Select camera group |
550 | | [camNum] | Integer
| Select camera |
551 |
552 |
553 | ```js
554 | iracing.camControls.switchToPos(2) // show P2
555 | ```
556 |
557 |
558 | ### play()
559 | Play replay
560 |
561 | **Kind**: global function
562 |
563 | ```js
564 | iracing.playbackControls.play()
565 | ```
566 |
567 |
568 | ### pause()
569 | Pause replay
570 |
571 | **Kind**: global function
572 |
573 | ```js
574 | iracing.playbackControls.pause()
575 | ```
576 |
577 |
578 | ### fastForward([speed])
579 | fast-forward replay
580 |
581 | **Kind**: global function
582 |
583 | | Param | Type | Default | Description |
584 | | --- | --- | --- | --- |
585 | | [speed] | Integer
| 2
| FF speed, something between 2-16 works |
586 |
587 |
588 | ```js
589 | iracing.playbackControls.fastForward() // double speed FF
590 | ```
591 |
592 |
593 | ### rewind([speed])
594 | rewind replay
595 |
596 | **Kind**: global function
597 |
598 | | Param | Type | Default | Description |
599 | | --- | --- | --- | --- |
600 | | [speed] | Integer
| 2
| RW speed, something between 2-16 works |
601 |
602 |
603 | ```js
604 | iracing.playbackControls.rewind() // double speed RW
605 | ```
606 |
607 |
608 | ### slowForward([divider])
609 | slow-forward replay, slow motion
610 |
611 | **Kind**: global function
612 |
613 | | Param | Type | Default | Description |
614 | | --- | --- | --- | --- |
615 | | [divider] | Integer
| 2
| divider of speed, something between 2-17 works |
616 |
617 |
618 | ```js
619 | iracing.playbackControls.slowForward(2) // half speed
620 | ```
621 |
622 |
623 | ### slowBackward([divider])
624 | slow-backward replay, reverse slow motion
625 |
626 | **Kind**: global function
627 |
628 | | Param | Type | Default | Description |
629 | | --- | --- | --- | --- |
630 | | [divider] | Integer
| 2
| divider of speed, something between 2-17 works |
631 |
632 |
633 | ```js
634 | iracing.playbackControls.slowBackward(2) // half speed RW
635 | ```
636 |
637 |
638 | ### search(searchMode)
639 | Search things from replay
640 |
641 | **Kind**: global function
642 |
643 | | Param | Type | Description |
644 | | --- | --- | --- |
645 | | searchMode | [RpySrchMode
](#IrSdkConsts.RpySrchMode) | what to search |
646 |
647 |
648 | ```js
649 | iracing.playbackControls.search('nextIncident')
650 | ```
651 |
652 |
653 | ### searchTs(sessionNum, sessionTimeMS)
654 | Search timestamp
655 |
656 | **Kind**: global function
657 |
658 | | Param | Type | Description |
659 | | --- | --- | --- |
660 | | sessionNum | Integer
| Session number |
661 | | sessionTimeMS | Integer
| Session time in milliseconds |
662 |
663 |
664 | ```js
665 | // jump to 2nd minute of 3rd session
iracing.playbackControls.searchTs(2, 2*60*1000)
666 | ```
667 |
668 |
669 | ### searchFrame(frameNum, rpyPosMode)
670 | Go to frame. Frame counting can be relative to begin, end or current.
671 |
672 | **Kind**: global function
673 |
674 | | Param | Type | Description |
675 | | --- | --- | --- |
676 | | frameNum | Integer
| Frame number |
677 | | rpyPosMode | [RpyPosMode
](#IrSdkConsts.RpyPosMode) | Is frame number relative to begin, end or current frame |
678 |
679 |
680 | ```js
681 | iracing.playbackControls.searchFrame(1, 'current') // go to 1 frame forward
682 | ```
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 |
701 | ## Credits
702 | Originally based on [node-irsdk](https://github.com/apihlaja/node-irsdk) by [apihlaja](https://github.com/apihlaja).
703 |
704 | 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)
705 |
--------------------------------------------------------------------------------
/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 | "main}}
21 |
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 |
39 | ## Credits
40 | Originally based on [node-irsdk](https://github.com/apihlaja/node-irsdk) by [apihlaja](https://github.com/apihlaja).
41 |
42 | 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)
43 |
--------------------------------------------------------------------------------
/doc/examples.hbs:
--------------------------------------------------------------------------------
1 | {{#examples}}
2 | {{#if caption}} **Example:** {{caption}} {{else}} {{/if}}
3 | {{{inlineLinks example}}}
4 | {{/examples}}
5 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/examples/recorder.js:
--------------------------------------------------------------------------------
1 | const irsdk = require('../');
2 | const fs = require('fs');
3 |
4 | irsdk.init({
5 | telemetryUpdateInterval: 100,
6 | sessionInfoUpdateInterval: 2000,
7 | });
8 |
9 | const iracing = irsdk.getInstance();
10 |
11 | function saveSample(type, time, data) {
12 | const fileName = './sample-data/rec/' + time + '-' + type + '.json';
13 | fs.writeFile(fileName, JSON.stringify(data), function (err) {
14 | if (err) throw err;
15 | });
16 | }
17 |
18 | console.log('waiting for iRacing...');
19 |
20 | iracing.on('Connected', function () {
21 | console.log('connected to iRacing..');
22 | });
23 |
24 | iracing.on('Disconnected', function () {
25 | console.log('iRacing shut down, exiting.\n');
26 | process.exit();
27 | });
28 |
29 | iracing.on('TelemetryDescription', function (data) {
30 | console.log('got TelemetryDescription');
31 |
32 | saveSample('TelemetryDescription', Date.now(), data);
33 | });
34 |
35 | iracing.on('Telemetry', function (data) {
36 | console.log('got Telemetry');
37 | saveSample('Telemetry', Date.now(), data);
38 | });
39 |
40 | iracing.on('SessionInfo', function (data) {
41 | console.log('got SessionInfo');
42 |
43 | saveSample('SessionInfo', Date.now(), data);
44 | });
45 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iracing-sdk-js",
3 | "version": "1.4.0",
4 | "description": "iRacing SDK implementation for Node.js",
5 | "main": "src/iracing-sdk-js.js",
6 | "scripts": {
7 | "install": "yarn rebuild",
8 | "rebuild": "node-gyp rebuild",
9 | "smoke-test": "node test/smoke-test.js",
10 | "write-samples": "node examples/sample-writer.js",
11 | "test": "node --test test/**/*-spec.js",
12 | "doc": "jsdoc2md -d 3 -t doc/README.hbs --partial doc/examples.hbs -m none -g none src/iracing-sdk-js.js src/JsIrSdk.js src/consts/IrSdkConsts.js > README.md",
13 | "ready": "yarn rebuild && yarn test && yarn smoke-test && yarn write-samples && yarn doc"
14 | },
15 | "keywords": [
16 | "iracing"
17 | ],
18 | "author": "Zachary Friss",
19 | "license": "MIT",
20 | "repository": {
21 | "type": "git",
22 | "url": "https://github.com/Friss/iracing-sdk-js"
23 | },
24 | "engines": {
25 | "node": ">=21"
26 | },
27 | "dependencies": {
28 | "js-yaml": "4.1.0",
29 | "nan": "2.22.0"
30 | },
31 | "devDependencies": {
32 | "jsdoc-to-markdown": "9.1.1",
33 | "node-gyp": "11.0.0",
34 | "prettier": "3.4.2",
35 | "sandboxed-module": "2.0.4"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sample-data/sessioninfo.json:
--------------------------------------------------------------------------------
1 | {
2 | "timestamp": "2024-12-13T01:47:04.527Z",
3 | "data": {
4 | "WeekendInfo": {
5 | "TrackName": "virginia 2022 full",
6 | "TrackID": 465,
7 | "TrackLength": "5.22 km",
8 | "TrackLengthOfficial": "5.26 km",
9 | "TrackDisplayName": "Virginia International Raceway",
10 | "TrackDisplayShortName": "VIR",
11 | "TrackConfigName": "Full Course",
12 | "TrackCity": "Alton",
13 | "TrackCountry": "USA",
14 | "TrackAltitude": "166.18 m",
15 | "TrackLatitude": "36.568814 m",
16 | "TrackLongitude": "-79.206660 m",
17 | "TrackNorthOffset": "4.2700 rad",
18 | "TrackNumTurns": 20,
19 | "TrackPitSpeedLimit": "60.00 kph",
20 | "TrackType": "road course",
21 | "TrackDirection": "neutral",
22 | "TrackWeatherType": "Static",
23 | "TrackSkies": "Partly Cloudy",
24 | "TrackSurfaceTemp": "39.81 C",
25 | "TrackAirTemp": "25.56 C",
26 | "TrackAirPressure": "29.36 Hg",
27 | "TrackWindVel": "0.89 m/s",
28 | "TrackWindDir": "0.00 rad",
29 | "TrackRelativeHumidity": "45 %",
30 | "TrackFogLevel": "0 %",
31 | "TrackPrecipitation": "0 %",
32 | "TrackCleanup": 0,
33 | "TrackDynamicTrack": 1,
34 | "TrackVersion": "2024.11.26.01",
35 | "SeriesID": 0,
36 | "SeasonID": 0,
37 | "SessionID": 0,
38 | "SubSessionID": 0,
39 | "LeagueID": 0,
40 | "Official": 0,
41 | "RaceWeek": 0,
42 | "EventType": "Test",
43 | "Category": "Road",
44 | "SimMode": "full",
45 | "TeamRacing": 0,
46 | "MinDrivers": 0,
47 | "MaxDrivers": 0,
48 | "DCRuleSet": "None",
49 | "QualifierMustStartRace": 0,
50 | "NumCarClasses": 1,
51 | "NumCarTypes": 1,
52 | "HeatRacing": 0,
53 | "BuildType": "Release",
54 | "BuildTarget": "Members",
55 | "BuildVersion": "2024.12.11.01",
56 | "RaceFarm": null,
57 | "WeekendOptions": {
58 | "NumStarters": 0,
59 | "StartingGrid": "single file",
60 | "QualifyScoring": "best lap",
61 | "CourseCautions": "off",
62 | "StandingStart": 0,
63 | "ShortParadeLap": 0,
64 | "Restarts": "single file",
65 | "WeatherType": "Static",
66 | "Skies": "Partly Cloudy",
67 | "WindDirection": "N",
68 | "WindSpeed": "3.22 km/h",
69 | "WeatherTemp": "25.56 C",
70 | "RelativeHumidity": "45 %",
71 | "FogLevel": "0 %",
72 | "TimeOfDay": "12:00 pm",
73 | "Date": "2024-05-15T00:00:00.000Z",
74 | "EarthRotationSpeedupFactor": 1,
75 | "Unofficial": 1,
76 | "CommercialMode": "consumer",
77 | "NightMode": "variable",
78 | "IsFixedSetup": 0,
79 | "StrictLapsChecking": "default",
80 | "HasOpenRegistration": 0,
81 | "HardcoreLevel": 1,
82 | "NumJokerLaps": 0,
83 | "IncidentLimit": "unlimited",
84 | "FastRepairsLimit": "unlimited",
85 | "GreenWhiteCheckeredLimit": 0
86 | },
87 | "TelemetryOptions": {
88 | "TelemetryDiskFile": ""
89 | }
90 | },
91 | "SessionInfo": {
92 | "Sessions": [
93 | {
94 | "SessionNum": 0,
95 | "SessionLaps": "unlimited",
96 | "SessionTime": "unlimited",
97 | "SessionNumLapsToAvg": 0,
98 | "SessionType": "Offline Testing",
99 | "SessionTrackRubberState": "moderately low usage",
100 | "SessionName": "TESTING",
101 | "SessionSubType": null,
102 | "SessionSkipped": 0,
103 | "SessionRunGroupsUsed": 0,
104 | "SessionEnforceTireCompoundChange": 0,
105 | "ResultsPositions": null,
106 | "ResultsFastestLap": [
107 | {
108 | "CarIdx": 255,
109 | "FastestLap": 0,
110 | "FastestTime": -1
111 | }
112 | ],
113 | "ResultsAverageLapTime": -1,
114 | "ResultsNumCautionFlags": 0,
115 | "ResultsNumCautionLaps": 0,
116 | "ResultsNumLeadChanges": 0,
117 | "ResultsLapsComplete": -1,
118 | "ResultsOfficial": 0
119 | }
120 | ]
121 | },
122 | "CameraInfo": {
123 | "Groups": [
124 | {
125 | "GroupNum": 1,
126 | "GroupName": "Nose",
127 | "Cameras": [
128 | {
129 | "CameraNum": 1,
130 | "CameraName": "CamNose"
131 | }
132 | ]
133 | },
134 | {
135 | "GroupNum": 2,
136 | "GroupName": "Gearbox",
137 | "Cameras": [
138 | {
139 | "CameraNum": 1,
140 | "CameraName": "CamGearbox"
141 | }
142 | ]
143 | },
144 | {
145 | "GroupNum": 3,
146 | "GroupName": "Roll Bar",
147 | "Cameras": [
148 | {
149 | "CameraNum": 1,
150 | "CameraName": "CamRoll Bar"
151 | }
152 | ]
153 | },
154 | {
155 | "GroupNum": 4,
156 | "GroupName": "LF Susp",
157 | "Cameras": [
158 | {
159 | "CameraNum": 1,
160 | "CameraName": "CamLF Susp"
161 | }
162 | ]
163 | },
164 | {
165 | "GroupNum": 5,
166 | "GroupName": "LR Susp",
167 | "Cameras": [
168 | {
169 | "CameraNum": 1,
170 | "CameraName": "CamLR Susp"
171 | }
172 | ]
173 | },
174 | {
175 | "GroupNum": 6,
176 | "GroupName": "Gyro",
177 | "Cameras": [
178 | {
179 | "CameraNum": 1,
180 | "CameraName": "CamGyro"
181 | }
182 | ]
183 | },
184 | {
185 | "GroupNum": 7,
186 | "GroupName": "RF Susp",
187 | "Cameras": [
188 | {
189 | "CameraNum": 1,
190 | "CameraName": "CamRF Susp"
191 | }
192 | ]
193 | },
194 | {
195 | "GroupNum": 8,
196 | "GroupName": "RR Susp",
197 | "Cameras": [
198 | {
199 | "CameraNum": 1,
200 | "CameraName": "CamRR Susp"
201 | }
202 | ]
203 | },
204 | {
205 | "GroupNum": 9,
206 | "GroupName": "Cockpit",
207 | "Cameras": [
208 | {
209 | "CameraNum": 1,
210 | "CameraName": "CamCockpit"
211 | }
212 | ]
213 | },
214 | {
215 | "GroupNum": 10,
216 | "GroupName": "Scenic",
217 | "IsScenic": true,
218 | "Cameras": [
219 | {
220 | "CameraNum": 1,
221 | "CameraName": "Scenic_07"
222 | },
223 | {
224 | "CameraNum": 2,
225 | "CameraName": "Scenic_08"
226 | },
227 | {
228 | "CameraNum": 3,
229 | "CameraName": "Scenic_09"
230 | },
231 | {
232 | "CameraNum": 4,
233 | "CameraName": "Scenic_10"
234 | },
235 | {
236 | "CameraNum": 5,
237 | "CameraName": "Scenic_02"
238 | },
239 | {
240 | "CameraNum": 6,
241 | "CameraName": "Scenic_03"
242 | },
243 | {
244 | "CameraNum": 7,
245 | "CameraName": "Scenic_05"
246 | },
247 | {
248 | "CameraNum": 8,
249 | "CameraName": "Scenic_04"
250 | },
251 | {
252 | "CameraNum": 9,
253 | "CameraName": "Scenic_06"
254 | },
255 | {
256 | "CameraNum": 10,
257 | "CameraName": "Scenic_01"
258 | }
259 | ]
260 | },
261 | {
262 | "GroupNum": 11,
263 | "GroupName": "TV1",
264 | "Cameras": [
265 | {
266 | "CameraNum": 1,
267 | "CameraName": "CamTV1_00"
268 | },
269 | {
270 | "CameraNum": 2,
271 | "CameraName": "CamTV1_01"
272 | },
273 | {
274 | "CameraNum": 3,
275 | "CameraName": "CamTV1_02"
276 | },
277 | {
278 | "CameraNum": 4,
279 | "CameraName": "CamTV1_03"
280 | },
281 | {
282 | "CameraNum": 5,
283 | "CameraName": "CamTV1_05"
284 | },
285 | {
286 | "CameraNum": 6,
287 | "CameraName": "CamTV1_04"
288 | },
289 | {
290 | "CameraNum": 7,
291 | "CameraName": "CamTV1_06"
292 | },
293 | {
294 | "CameraNum": 8,
295 | "CameraName": "CamTV1_07"
296 | },
297 | {
298 | "CameraNum": 9,
299 | "CameraName": "CamTV1_08"
300 | },
301 | {
302 | "CameraNum": 10,
303 | "CameraName": "CamTV1_09"
304 | }
305 | ]
306 | },
307 | {
308 | "GroupNum": 12,
309 | "GroupName": "TV2",
310 | "Cameras": [
311 | {
312 | "CameraNum": 1,
313 | "CameraName": "CamTV2_04"
314 | },
315 | {
316 | "CameraNum": 2,
317 | "CameraName": "CamTV2_01"
318 | },
319 | {
320 | "CameraNum": 3,
321 | "CameraName": "CamTV2_02"
322 | },
323 | {
324 | "CameraNum": 4,
325 | "CameraName": "CamTV2_03"
326 | },
327 | {
328 | "CameraNum": 5,
329 | "CameraName": "CamTV2_05"
330 | },
331 | {
332 | "CameraNum": 6,
333 | "CameraName": "CamTV2_07"
334 | },
335 | {
336 | "CameraNum": 7,
337 | "CameraName": "CamTV2_08"
338 | },
339 | {
340 | "CameraNum": 8,
341 | "CameraName": "CamTV2_09"
342 | },
343 | {
344 | "CameraNum": 9,
345 | "CameraName": "CamTV2_10"
346 | },
347 | {
348 | "CameraNum": 10,
349 | "CameraName": "CamTV2_15"
350 | },
351 | {
352 | "CameraNum": 11,
353 | "CameraName": "CamTV2_12"
354 | },
355 | {
356 | "CameraNum": 12,
357 | "CameraName": "CamTV2_13"
358 | },
359 | {
360 | "CameraNum": 13,
361 | "CameraName": "CamTV2_14"
362 | },
363 | {
364 | "CameraNum": 14,
365 | "CameraName": "CamTV2_11"
366 | },
367 | {
368 | "CameraNum": 15,
369 | "CameraName": "CamTV2_00"
370 | }
371 | ]
372 | },
373 | {
374 | "GroupNum": 13,
375 | "GroupName": "TV3",
376 | "Cameras": [
377 | {
378 | "CameraNum": 1,
379 | "CameraName": "CamTV3_03"
380 | },
381 | {
382 | "CameraNum": 2,
383 | "CameraName": "CamTV3_01"
384 | },
385 | {
386 | "CameraNum": 3,
387 | "CameraName": "CamTV3_02"
388 | },
389 | {
390 | "CameraNum": 4,
391 | "CameraName": "CamTV3_04"
392 | },
393 | {
394 | "CameraNum": 5,
395 | "CameraName": "CamTV3_05"
396 | },
397 | {
398 | "CameraNum": 6,
399 | "CameraName": "CamTV3_06"
400 | },
401 | {
402 | "CameraNum": 7,
403 | "CameraName": "CamTV3_07"
404 | },
405 | {
406 | "CameraNum": 8,
407 | "CameraName": "CamTV3_08"
408 | },
409 | {
410 | "CameraNum": 9,
411 | "CameraName": "CamTV3_09"
412 | },
413 | {
414 | "CameraNum": 10,
415 | "CameraName": "CamTV3_10"
416 | }
417 | ]
418 | },
419 | {
420 | "GroupNum": 14,
421 | "GroupName": "TV Static",
422 | "Cameras": [
423 | {
424 | "CameraNum": 1,
425 | "CameraName": "CamTV4_00"
426 | },
427 | {
428 | "CameraNum": 2,
429 | "CameraName": "CamTV4_01"
430 | },
431 | {
432 | "CameraNum": 3,
433 | "CameraName": "CamTV4_02"
434 | },
435 | {
436 | "CameraNum": 4,
437 | "CameraName": "CamTV4_03"
438 | },
439 | {
440 | "CameraNum": 5,
441 | "CameraName": "CamTV4_04"
442 | },
443 | {
444 | "CameraNum": 6,
445 | "CameraName": "CamTV4_05"
446 | },
447 | {
448 | "CameraNum": 7,
449 | "CameraName": "CamTV4_06"
450 | },
451 | {
452 | "CameraNum": 8,
453 | "CameraName": "CamTV4_08"
454 | },
455 | {
456 | "CameraNum": 9,
457 | "CameraName": "CamTV4_07"
458 | },
459 | {
460 | "CameraNum": 10,
461 | "CameraName": "CamTV4_10"
462 | },
463 | {
464 | "CameraNum": 11,
465 | "CameraName": "CamTV4_09"
466 | },
467 | {
468 | "CameraNum": 12,
469 | "CameraName": "CamTV4_11"
470 | },
471 | {
472 | "CameraNum": 13,
473 | "CameraName": "CamTV4_12"
474 | },
475 | {
476 | "CameraNum": 14,
477 | "CameraName": "CamTV4_13"
478 | },
479 | {
480 | "CameraNum": 15,
481 | "CameraName": "CamTV4_14"
482 | },
483 | {
484 | "CameraNum": 16,
485 | "CameraName": "CamTV4_15"
486 | },
487 | {
488 | "CameraNum": 17,
489 | "CameraName": "CamTV4_16"
490 | },
491 | {
492 | "CameraNum": 18,
493 | "CameraName": "CamTV4_17"
494 | }
495 | ]
496 | },
497 | {
498 | "GroupNum": 15,
499 | "GroupName": "TV Mixed",
500 | "Cameras": [
501 | {
502 | "CameraNum": 1,
503 | "CameraName": "CamTV3_09b"
504 | },
505 | {
506 | "CameraNum": 2,
507 | "CameraName": "CamTV1_00"
508 | },
509 | {
510 | "CameraNum": 3,
511 | "CameraName": "CamTV1_01"
512 | },
513 | {
514 | "CameraNum": 4,
515 | "CameraName": "CamTV1_02"
516 | },
517 | {
518 | "CameraNum": 5,
519 | "CameraName": "CamTV1_04"
520 | },
521 | {
522 | "CameraNum": 6,
523 | "CameraName": "CamTV1_05"
524 | },
525 | {
526 | "CameraNum": 7,
527 | "CameraName": "CamTV1_06"
528 | },
529 | {
530 | "CameraNum": 8,
531 | "CameraName": "CamTV1_07"
532 | },
533 | {
534 | "CameraNum": 9,
535 | "CameraName": "CamTV1_09"
536 | },
537 | {
538 | "CameraNum": 10,
539 | "CameraName": "CamTV2_00"
540 | },
541 | {
542 | "CameraNum": 11,
543 | "CameraName": "CamTV2_01"
544 | },
545 | {
546 | "CameraNum": 12,
547 | "CameraName": "CamTV2_02"
548 | },
549 | {
550 | "CameraNum": 13,
551 | "CameraName": "CamTV2_03"
552 | },
553 | {
554 | "CameraNum": 14,
555 | "CameraName": "CamTV2_04"
556 | },
557 | {
558 | "CameraNum": 15,
559 | "CameraName": "CamTV2_05"
560 | },
561 | {
562 | "CameraNum": 16,
563 | "CameraName": "CamTV2_06"
564 | },
565 | {
566 | "CameraNum": 17,
567 | "CameraName": "CamTV2_07"
568 | },
569 | {
570 | "CameraNum": 18,
571 | "CameraName": "CamTV2_08"
572 | },
573 | {
574 | "CameraNum": 19,
575 | "CameraName": "CamTV2_09"
576 | },
577 | {
578 | "CameraNum": 20,
579 | "CameraName": "CamTV2_10"
580 | },
581 | {
582 | "CameraNum": 21,
583 | "CameraName": "CamTV2_11"
584 | },
585 | {
586 | "CameraNum": 22,
587 | "CameraName": "CamTV2_12"
588 | },
589 | {
590 | "CameraNum": 23,
591 | "CameraName": "CamTV2_14"
592 | },
593 | {
594 | "CameraNum": 24,
595 | "CameraName": "CamTV2_15"
596 | },
597 | {
598 | "CameraNum": 25,
599 | "CameraName": "CamTV3_01"
600 | },
601 | {
602 | "CameraNum": 26,
603 | "CameraName": "CamTV3_02"
604 | },
605 | {
606 | "CameraNum": 27,
607 | "CameraName": "CamTV3_03"
608 | },
609 | {
610 | "CameraNum": 28,
611 | "CameraName": "CamTV3_04"
612 | },
613 | {
614 | "CameraNum": 29,
615 | "CameraName": "CamTV3_05"
616 | },
617 | {
618 | "CameraNum": 30,
619 | "CameraName": "CamTV3_05b"
620 | },
621 | {
622 | "CameraNum": 31,
623 | "CameraName": "CamTV3_06"
624 | },
625 | {
626 | "CameraNum": 32,
627 | "CameraName": "CamTV3_07"
628 | },
629 | {
630 | "CameraNum": 33,
631 | "CameraName": "CamTV3_07b"
632 | },
633 | {
634 | "CameraNum": 34,
635 | "CameraName": "CamTV3_08"
636 | },
637 | {
638 | "CameraNum": 35,
639 | "CameraName": "CamTV3_09"
640 | },
641 | {
642 | "CameraNum": 36,
643 | "CameraName": "CamTV3_10"
644 | },
645 | {
646 | "CameraNum": 37,
647 | "CameraName": "CamTV4_00"
648 | }
649 | ]
650 | },
651 | {
652 | "GroupNum": 16,
653 | "GroupName": "Pit Lane",
654 | "Cameras": [
655 | {
656 | "CameraNum": 1,
657 | "CameraName": "CamPit Lane"
658 | }
659 | ]
660 | },
661 | {
662 | "GroupNum": 17,
663 | "GroupName": "Pit Lane 2",
664 | "Cameras": [
665 | {
666 | "CameraNum": 1,
667 | "CameraName": "CamPit Lane 2"
668 | }
669 | ]
670 | },
671 | {
672 | "GroupNum": 18,
673 | "GroupName": "Blimp",
674 | "Cameras": [
675 | {
676 | "CameraNum": 1,
677 | "CameraName": "CamBlimp"
678 | }
679 | ]
680 | },
681 | {
682 | "GroupNum": 19,
683 | "GroupName": "Chopper",
684 | "Cameras": [
685 | {
686 | "CameraNum": 1,
687 | "CameraName": "CamChopper"
688 | }
689 | ]
690 | },
691 | {
692 | "GroupNum": 20,
693 | "GroupName": "Chase",
694 | "Cameras": [
695 | {
696 | "CameraNum": 1,
697 | "CameraName": "CamChase"
698 | }
699 | ]
700 | },
701 | {
702 | "GroupNum": 21,
703 | "GroupName": "Far Chase",
704 | "Cameras": [
705 | {
706 | "CameraNum": 1,
707 | "CameraName": "CamFar Chase"
708 | }
709 | ]
710 | },
711 | {
712 | "GroupNum": 22,
713 | "GroupName": "Rear Chase",
714 | "Cameras": [
715 | {
716 | "CameraNum": 1,
717 | "CameraName": "CamRear Chase"
718 | }
719 | ]
720 | }
721 | ]
722 | },
723 | "RadioInfo": {
724 | "SelectedRadioNum": 0,
725 | "Radios": [
726 | {
727 | "RadioNum": 0,
728 | "HopCount": 2,
729 | "NumFrequencies": 7,
730 | "TunedToFrequencyNum": 0,
731 | "ScanningIsOn": 1,
732 | "Frequencies": [
733 | {
734 | "FrequencyNum": 0,
735 | "FrequencyName": "@ALLTEAMS",
736 | "Priority": 12,
737 | "CarIdx": -1,
738 | "EntryIdx": -1,
739 | "ClubID": 0,
740 | "CanScan": 1,
741 | "CanSquawk": 1,
742 | "Muted": 0,
743 | "IsMutable": 1,
744 | "IsDeletable": 0
745 | },
746 | {
747 | "FrequencyNum": 1,
748 | "FrequencyName": "@DRIVERS",
749 | "Priority": 15,
750 | "CarIdx": -1,
751 | "EntryIdx": -1,
752 | "ClubID": 0,
753 | "CanScan": 1,
754 | "CanSquawk": 1,
755 | "Muted": 0,
756 | "IsMutable": 1,
757 | "IsDeletable": 0
758 | },
759 | {
760 | "FrequencyNum": 2,
761 | "FrequencyName": "@TEAM",
762 | "Priority": 60,
763 | "CarIdx": 0,
764 | "EntryIdx": -1,
765 | "ClubID": 0,
766 | "CanScan": 1,
767 | "CanSquawk": 1,
768 | "Muted": 0,
769 | "IsMutable": 0,
770 | "IsDeletable": 0
771 | },
772 | {
773 | "FrequencyNum": 3,
774 | "FrequencyName": "@CLUB",
775 | "Priority": 20,
776 | "CarIdx": -1,
777 | "EntryIdx": -1,
778 | "ClubID": 25,
779 | "CanScan": 1,
780 | "CanSquawk": 1,
781 | "Muted": 0,
782 | "IsMutable": 1,
783 | "IsDeletable": 0
784 | },
785 | {
786 | "FrequencyNum": 4,
787 | "FrequencyName": "@ADMIN",
788 | "Priority": 90,
789 | "CarIdx": -1,
790 | "EntryIdx": -1,
791 | "ClubID": 0,
792 | "CanScan": 1,
793 | "CanSquawk": 1,
794 | "Muted": 0,
795 | "IsMutable": 0,
796 | "IsDeletable": 0
797 | },
798 | {
799 | "FrequencyNum": 5,
800 | "FrequencyName": "@RACECONTROL",
801 | "Priority": 80,
802 | "CarIdx": -1,
803 | "EntryIdx": -1,
804 | "ClubID": 0,
805 | "CanScan": 1,
806 | "CanSquawk": 1,
807 | "Muted": 0,
808 | "IsMutable": 0,
809 | "IsDeletable": 0
810 | },
811 | {
812 | "FrequencyNum": 6,
813 | "FrequencyName": "@PRIVATE",
814 | "Priority": 70,
815 | "CarIdx": -1,
816 | "EntryIdx": 0,
817 | "ClubID": 0,
818 | "CanScan": 1,
819 | "CanSquawk": 1,
820 | "Muted": 0,
821 | "IsMutable": 0,
822 | "IsDeletable": 0
823 | }
824 | ]
825 | }
826 | ]
827 | },
828 | "DriverInfo": {
829 | "DriverCarIdx": 0,
830 | "DriverUserID": 697587,
831 | "PaceCarIdx": -1,
832 | "DriverHeadPosX": -0.105,
833 | "DriverHeadPosY": 0.325,
834 | "DriverHeadPosZ": 0.535,
835 | "DriverCarIsElectric": 0,
836 | "DriverCarIdleRPM": 1950,
837 | "DriverCarRedLine": 8000,
838 | "DriverCarEngCylinderCount": 8,
839 | "DriverCarFuelKgPerLtr": 0.75,
840 | "DriverCarFuelMaxLtr": 104,
841 | "DriverCarMaxFuelPct": 1,
842 | "DriverCarGearNumForward": 6,
843 | "DriverCarGearNeutral": 1,
844 | "DriverCarGearReverse": 1,
845 | "DriverCarSLFirstRPM": 7000,
846 | "DriverCarSLShiftRPM": 7700,
847 | "DriverCarSLLastRPM": 7600,
848 | "DriverCarSLBlinkRPM": 7950,
849 | "DriverCarVersion": "2024.11.26.01",
850 | "DriverPitTrkPct": 0.990978,
851 | "DriverCarEstLapTime": 105.0762,
852 | "DriverSetupName": "baseline.sto",
853 | "DriverSetupIsModified": 0,
854 | "DriverSetupLoadTypeName": "baseline",
855 | "DriverSetupPassedTech": 1,
856 | "DriverIncidentCount": 0,
857 | "Drivers": [
858 | {
859 | "CarIdx": 0,
860 | "UserName": "Zachary Friss",
861 | "AbbrevName": null,
862 | "Initials": null,
863 | "UserID": 697587,
864 | "TeamID": 0,
865 | "TeamName": "Zachary Friss",
866 | "CarNumber": "64",
867 | "CarNumberRaw": 64,
868 | "CarPath": "chevyvettez06rgt3",
869 | "CarClassID": 0,
870 | "CarID": 184,
871 | "CarIsPaceCar": 0,
872 | "CarIsAI": 0,
873 | "CarIsElectric": 0,
874 | "CarScreenName": "Chevrolet Corvette Z06 GT3.R",
875 | "CarScreenNameShort": "Corvette GT3.R",
876 | "CarClassShortName": null,
877 | "CarClassRelSpeed": 0,
878 | "CarClassLicenseLevel": 0,
879 | "CarClassMaxFuelPct": "1.000 %",
880 | "CarClassWeightPenalty": "0.000 kg",
881 | "CarClassPowerAdjust": "0.000 %",
882 | "CarClassDryTireSetLimit": "0 %",
883 | "CarClassColor": 16777215,
884 | "CarClassEstLapTime": 105.0762,
885 | "IRating": 1,
886 | "LicLevel": 1,
887 | "LicSubLevel": 1,
888 | "LicString": "R 0.01",
889 | "LicColor": "0xundefined",
890 | "IsSpectator": 0,
891 | "CarDesignStr": "10,FFFFFF,FF7A59,33475B",
892 | "HelmetDesignStr": "7,ffffff,ff7a59,33475b",
893 | "SuitDesignStr": "21,ff7a59,33475b,ffffff",
894 | "BodyType": 0,
895 | "FaceType": 4,
896 | "HelmetType": 0,
897 | "CarNumberDesignStr": "0,0,FFFFFF,777777,000000",
898 | "CarSponsor_1": 0,
899 | "CarSponsor_2": 0,
900 | "CurDriverIncidentCount": 0,
901 | "TeamIncidentCount": 0
902 | }
903 | ]
904 | },
905 | "SplitTimeInfo": {
906 | "Sectors": [
907 | {
908 | "SectorNum": 0,
909 | "SectorStartPct": 0
910 | },
911 | {
912 | "SectorNum": 1,
913 | "SectorStartPct": 0.184456
914 | },
915 | {
916 | "SectorNum": 2,
917 | "SectorStartPct": 0.337214
918 | },
919 | {
920 | "SectorNum": 3,
921 | "SectorStartPct": 0.504637
922 | },
923 | {
924 | "SectorNum": 4,
925 | "SectorStartPct": 0.734279
926 | },
927 | {
928 | "SectorNum": 5,
929 | "SectorStartPct": 0.829332
930 | }
931 | ]
932 | },
933 | "CarSetup": {
934 | "UpdateCount": 1,
935 | "TiresAero": {
936 | "TireType": {
937 | "TireType": "Dry"
938 | },
939 | "LeftFront": {
940 | "StartingPressure": "165 kPa",
941 | "LastHotPressure": "165 kPa",
942 | "LastTempsOMI": "44C, 44C, 44C",
943 | "TreadRemaining": "100%, 100%, 100%"
944 | },
945 | "LeftRear": {
946 | "StartingPressure": "165 kPa",
947 | "LastHotPressure": "165 kPa",
948 | "LastTempsOMI": "44C, 44C, 44C",
949 | "TreadRemaining": "100%, 100%, 100%"
950 | },
951 | "RightFront": {
952 | "StartingPressure": "165 kPa",
953 | "LastHotPressure": "165 kPa",
954 | "LastTempsIMO": "44C, 44C, 44C",
955 | "TreadRemaining": "100%, 100%, 100%"
956 | },
957 | "RightRear": {
958 | "StartingPressure": "165 kPa",
959 | "LastHotPressure": "165 kPa",
960 | "LastTempsIMO": "44C, 44C, 44C",
961 | "TreadRemaining": "100%, 100%, 100%"
962 | },
963 | "AeroBalanceCalc": {
964 | "FrontRhAtSpeed": "43 mm",
965 | "RearRhAtSpeed": "53 mm",
966 | "RearWingAngle": "9.5 degrees",
967 | "FrontDownforce": "38.1%"
968 | }
969 | },
970 | "Chassis": {
971 | "FrontBrakes": {
972 | "ArbBlades": 3,
973 | "TotalToeIn": "-3.1 mm",
974 | "BrakePedalRatio": 4.67,
975 | "BrakePads": "Medium friction"
976 | },
977 | "LeftFront": {
978 | "CornerWeight": "3162 N",
979 | "RideHeight": "51.0 mm",
980 | "BumpRubberGap": "12 mm",
981 | "SpringRate": "105 N/mm",
982 | "Camber": "-3.9 deg"
983 | },
984 | "LeftRear": {
985 | "CornerWeight": "3973 N",
986 | "RideHeight": "71.3 mm",
987 | "BumpRubberGap": "53 mm",
988 | "SpringRate": "200 N/mm",
989 | "Camber": "-3.5 deg",
990 | "ToeIn": "+1.6 mm"
991 | },
992 | "Rear": {
993 | "FuelLevel": "52.0 L",
994 | "ArbBlades": 0,
995 | "RearWingAngle": "9.5 degrees"
996 | },
997 | "InCarAdjustments": {
998 | "BrakePressureBias": "55.0%",
999 | "AbsSetting": "3 (ABS)",
1000 | "TractionControlSetting": "3 (T/C)",
1001 | "DisplayPage": "Race",
1002 | "CrossWeight": "50.0%"
1003 | },
1004 | "RightFront": {
1005 | "CornerWeight": "3162 N",
1006 | "RideHeight": "51.0 mm",
1007 | "BumpRubberGap": "12 mm",
1008 | "SpringRate": "105 N/mm",
1009 | "Camber": "-3.9 deg"
1010 | },
1011 | "RightRear": {
1012 | "CornerWeight": "3973 N",
1013 | "RideHeight": "71.3 mm",
1014 | "BumpRubberGap": "53 mm",
1015 | "SpringRate": "200 N/mm",
1016 | "Camber": "-3.5 deg",
1017 | "ToeIn": "+1.6 mm"
1018 | },
1019 | "GearsDifferential": {
1020 | "GearStack": "FIA",
1021 | "FrictionFaces": 8,
1022 | "DiffPreload": "100 Nm"
1023 | }
1024 | },
1025 | "Dampers": {
1026 | "FrontDampers": {
1027 | "LowSpeedCompressionDamping": "5 clicks",
1028 | "HighSpeedCompressionDamping": "0 clicks",
1029 | "LowSpeedReboundDamping": "5 clicks",
1030 | "HighSpeedReboundDamping": "5 clicks"
1031 | },
1032 | "RearDampers": {
1033 | "LowSpeedCompressionDamping": "5 clicks",
1034 | "HighSpeedCompressionDamping": "0 clicks",
1035 | "LowSpeedReboundDamping": "5 clicks",
1036 | "HighSpeedReboundDamping": "7 clicks"
1037 | }
1038 | }
1039 | }
1040 | }
1041 | }
--------------------------------------------------------------------------------
/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/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/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/IRSDKWrapper.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "irsdk/irsdk_defines.h"
4 | #include "platform/platform.h"
5 | #include
6 | #include