├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── funding.yml
└── workflows
│ ├── api-docs.yml
│ ├── codeql-analysis.yml
│ ├── npm-build.yml
│ ├── publish-package.yml
│ └── release.yml
├── .gitignore
├── .husky
└── pre-commit
├── .prettierrc.js
├── LICENSE
├── README.md
├── jest.config.js
├── lint-staged.config.js
├── package-lock.json
├── package.json
├── samples
├── README.md
├── js
│ └── aircraftPosition.mjs
└── typescript
│ ├── aircraftInformation.ts
│ ├── clientEvents.ts
│ ├── facilities.ts
│ ├── facilitiesMenu.ts
│ ├── inputEvents.ts
│ ├── pmdgCduScreen.ts
│ ├── reconnect.ts
│ ├── showMetar.ts
│ ├── simulationVariablesRead.ts
│ ├── simulationVariablesReadTagged.ts
│ ├── simulationVariablesWrite.ts
│ ├── systemEvents.ts
│ ├── trafficRadar
│ ├── index.html
│ ├── trafficRadar.ts
│ └── websocketServer.ts
│ └── wxradar
│ ├── index.html
│ └── index.ts
├── src
├── RawBuffer.ts
├── SimConnectConnection.ts
├── SimConnectConstants.ts
├── SimConnectPacketBuilder.ts
├── SimConnectSocket.ts
├── Types.ts
├── connectionParameters.ts
├── datastructures
│ ├── ControllerItem.ts
│ ├── FacilityAirport.ts
│ ├── FacilityMinimal.ts
│ ├── FacilityNDB.ts
│ ├── FacilityVOR.ts
│ ├── FacilityWaypoint.ts
│ ├── InputEventDescriptor.ts
│ ├── JetwayData.ts
│ ├── VersionBaseType.ts
│ └── index.ts
├── dto
│ ├── InitPosition.ts
│ ├── LatLonAlt.ts
│ ├── MarkerState.ts
│ ├── PBH.ts
│ ├── SimConnectData.ts
│ ├── Waypoint.ts
│ ├── XYZ.ts
│ ├── bufferHelpers.ts
│ ├── icao.ts
│ └── index.ts
├── enums
│ ├── ClientDataPeriod.ts
│ ├── FacilityDataType.ts
│ ├── FacilityListType.ts
│ ├── InputEventType.ts
│ ├── JetwayStatus.ts
│ ├── NotificationPriority.ts
│ ├── Protocol.ts
│ ├── SimConnectDataType.ts
│ ├── SimConnectException.ts
│ ├── SimConnectPeriod.ts
│ ├── SimObjectType.ts
│ ├── TextResult.ts
│ ├── TextType.ts
│ ├── WeatherMode.ts
│ └── index.ts
├── flags
│ ├── ClientDataRequestFlag.ts
│ ├── DataRequestFlag.ts
│ ├── DataSetFlag.ts
│ ├── EventFlag.ts
│ └── index.ts
├── index.ts
├── recv
│ ├── RecvActionCallback.ts
│ ├── RecvAirportList.ts
│ ├── RecvAssignedObjectID.ts
│ ├── RecvCloudState.ts
│ ├── RecvControllersList.ts
│ ├── RecvCustomAction.ts
│ ├── RecvEnumerateInputEventParams.ts
│ ├── RecvEnumerateInputEvents.ts
│ ├── RecvEvent.ts
│ ├── RecvEventAddRemove.ts
│ ├── RecvEventEx1.ts
│ ├── RecvEventFilename.ts
│ ├── RecvEventFrame.ts
│ ├── RecvEventRaceEnd.ts
│ ├── RecvEventRaceLap.ts
│ ├── RecvEventWeatherMode.ts
│ ├── RecvException.ts
│ ├── RecvFacilitiesList.ts
│ ├── RecvFacilityData.ts
│ ├── RecvFacilityDataEnd.ts
│ ├── RecvFacilityMinimalList.ts
│ ├── RecvGetInputEvent.ts
│ ├── RecvJetwayData.ts
│ ├── RecvListTemplate.ts
│ ├── RecvNDBList.ts
│ ├── RecvOpen.ts
│ ├── RecvReservedKey.ts
│ ├── RecvSimObjectData.ts
│ ├── RecvSubscribeInputEvent.ts
│ ├── RecvSystemState.ts
│ ├── RecvVORList.ts
│ ├── RecvWaypointList.ts
│ ├── RecvWeatherObservation.ts
│ └── index.ts
└── utils
│ ├── ini.ts
│ ├── network.ts
│ └── registry.ts
├── tests
├── flightLoad.test.ts
└── utils.ts
├── tsconfig.build.json
├── tsconfig.json
└── utils
└── packetInspectorProxy.ts
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | end_of_line = lf
3 | insert_final_newline = true
4 | indent_style = space
5 | indent_size = 4
6 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | dist
2 | samples
3 | .eslintrc.js
4 | jest.config.js
5 | lint-staged.config.js
6 | node_modules
7 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | parserOptions: {
4 | ecmaVersion: 2020,
5 | sourceType: 'module',
6 | project: './tsconfig.json',
7 | },
8 | env: {
9 | browser: false,
10 | node: true,
11 | es6: true,
12 | },
13 | settings: {
14 | 'import/resolver': {
15 | node: {
16 | extensions: ['.ts'],
17 | },
18 | },
19 | },
20 | plugins: ['@typescript-eslint', 'eslint-plugin-tsdoc'],
21 | extends: [
22 | 'plugin:@typescript-eslint/recommended',
23 | 'airbnb-base',
24 | 'airbnb-typescript/base',
25 | 'prettier',
26 | 'plugin:prettier/recommended',
27 | ],
28 | rules: {
29 | '@typescript-eslint/switch-exhaustiveness-check': 'error',
30 | '@typescript-eslint/no-unused-vars': 'error',
31 | '@typescript-eslint/no-explicit-any': 'error',
32 | 'import/extensions': [
33 | 'error',
34 | 'ignorePackages',
35 | {
36 | js: 'never',
37 | ts: 'never',
38 | },
39 | ],
40 | 'import/prefer-default-export': 'off',
41 | 'no-plusplus': 'off',
42 | 'import/no-import-module-exports': 'off',
43 | '@typescript-eslint/no-use-before-define': 'off',
44 | 'no-bitwise': 'off',
45 | 'no-underscore-dangle': 'off',
46 | '@typescript-eslint/ban-ts-comment': 'off',
47 | 'no-inline-comments': 'off',
48 | 'default-case': 'warn',
49 | },
50 | };
51 |
--------------------------------------------------------------------------------
/.github/funding.yml:
--------------------------------------------------------------------------------
1 | github: EvenAR
2 | buy_me_a_coffee: evenar
3 |
--------------------------------------------------------------------------------
/.github/workflows/api-docs.yml:
--------------------------------------------------------------------------------
1 | # Simple workflow for deploying static content to GitHub Pages
2 | name: Deploy API docs to pages
3 |
4 | on:
5 | # Runs on pushes targeting the default branch
6 | push:
7 | branches: ["master"]
8 |
9 | # Allows you to run this workflow manually from the Actions tab
10 | workflow_dispatch:
11 |
12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
13 | permissions:
14 | contents: read
15 | pages: write
16 | id-token: write
17 |
18 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
19 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
20 | concurrency:
21 | group: "pages"
22 | cancel-in-progress: false
23 |
24 | jobs:
25 | # Single deploy job since we're just deploying
26 | deploy:
27 | environment:
28 | name: github-pages
29 | url: ${{ steps.deployment.outputs.page_url }}
30 | runs-on: ubuntu-latest
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | - name: Setup Pages
35 | uses: actions/configure-pages@v4
36 | - name: Build docs
37 | run: npm install && npm run generate-api-reference
38 | - name: Upload artifact
39 | uses: actions/upload-pages-artifact@v2
40 | with:
41 | path: './docs' # Upload entire repository
42 | - name: Deploy to GitHub Pages
43 | id: deployment
44 | uses: actions/deploy-pages@v3
45 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # For most projects, this workflow file will not need changing; you simply need
2 | # to commit it to your repository.
3 | #
4 | # You may wish to alter this file to override the set of languages analyzed,
5 | # or to provide custom queries or build logic.
6 | #
7 | # ******** NOTE ********
8 | # We have attempted to detect the languages in your repository. Please check
9 | # the `language` matrix defined below to confirm you have the correct set of
10 | # supported CodeQL languages.
11 | #
12 | name: "CodeQL"
13 |
14 | on:
15 | push:
16 | branches: [ "master" ]
17 | pull_request:
18 | # The branches below must be a subset of the branches above
19 | branches: [ "master" ]
20 | schedule:
21 | - cron: '19 15 * * 4'
22 |
23 | jobs:
24 | analyze:
25 | name: Analyze
26 | runs-on: ubuntu-latest
27 | permissions:
28 | actions: read
29 | contents: read
30 | security-events: write
31 |
32 | strategy:
33 | fail-fast: false
34 | matrix:
35 | language: [ 'javascript' ]
36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
38 |
39 | steps:
40 | - name: Checkout repository
41 | uses: actions/checkout@v3
42 |
43 | # Initializes the CodeQL tools for scanning.
44 | - name: Initialize CodeQL
45 | uses: github/codeql-action/init@v2
46 | with:
47 | languages: ${{ matrix.language }}
48 | # If you wish to specify custom queries, you can do so here or in a config file.
49 | # By default, queries listed here will override any specified in a config file.
50 | # Prefix the list here with "+" to use these queries and those in the config file.
51 |
52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
53 | # queries: security-extended,security-and-quality
54 |
55 |
56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
57 | # If this step fails, then you should remove it and run the build manually (see below)
58 | - name: Autobuild
59 | uses: github/codeql-action/autobuild@v2
60 |
61 | # ℹ️ Command-line programs to run using the OS shell.
62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
63 |
64 | # If the Autobuild fails above, remove it and uncomment the following three lines.
65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
66 |
67 | # - run: |
68 | # echo "Run, Build Application using script"
69 | # ./location_of_script_within_repo/buildscript.sh
70 |
71 | - name: Perform CodeQL Analysis
72 | uses: github/codeql-action/analyze@v2
73 | with:
74 | category: "/language:${{matrix.language}}"
75 |
--------------------------------------------------------------------------------
/.github/workflows/npm-build.yml:
--------------------------------------------------------------------------------
1 | name: Node.js CI
2 |
3 | on:
4 | push:
5 | branches: [master]
6 | pull_request:
7 | branches: [master]
8 |
9 | jobs:
10 | build:
11 | strategy:
12 | matrix:
13 | node-version: [latest]
14 | os: [windows-latest, macos-latest, ubuntu-latest]
15 | runs-on: ${{ matrix.os }}
16 | steps:
17 | - uses: actions/checkout@v3
18 | - name: Use Node.js ${{ matrix.node-version }}
19 | uses: actions/setup-node@v3
20 | with:
21 | node-version: ${{ matrix.node-version }}
22 | - run: npm ci
23 | - run: npm run build --if-present
24 | - run: npm test
25 |
--------------------------------------------------------------------------------
/.github/workflows/publish-package.yml:
--------------------------------------------------------------------------------
1 | name: Publish new release to npm
2 |
3 | on:
4 | release:
5 | types: [created]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v4
12 | - uses: actions/setup-node@v4
13 | with:
14 | node-version: 20.x
15 | - run: npm ci
16 | - run: npm test
17 |
18 | publish-gpr:
19 | needs: build
20 | runs-on: ubuntu-latest
21 | permissions:
22 | contents: read
23 | packages: write
24 | steps:
25 | - uses: actions/checkout@v4
26 | - uses: actions/setup-node@v4
27 | with:
28 | node-version: '20.x'
29 | registry-url: 'https://npm.pkg.github.com'
30 | - run: npm ci
31 | - run: npm publish
32 | env:
33 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
34 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Create a release
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | version:
7 | type: choice
8 | description: 'Version to bump'
9 | required: true
10 | options:
11 | - 'patch'
12 | - 'minor'
13 | - 'major'
14 | tag:
15 | type: choice
16 | description: 'Tag'
17 | required: true
18 | options:
19 | - 'alpha'
20 | - 'beta'
21 | - 'latest'
22 |
23 | jobs:
24 | release:
25 | name: Release new version
26 | runs-on: ubuntu-latest
27 | outputs:
28 | new_tag: ${{ steps.bump_version.outputs.NEW_TAG }}
29 | env:
30 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
31 | steps:
32 | - uses: actions/checkout@v4
33 | - uses: actions/setup-node@v4
34 | with:
35 | node-version: '20.x'
36 | registry-url: 'https://registry.npmjs.org'
37 | - run: npm ci
38 | - run: npm run build
39 | - run: |
40 | git config user.name "github-actions[bot]"
41 | git config user.email "41898282+github-actions[bot]@users.noreply.github"
42 | - id: bump_version
43 | run: echo "NEW_TAG=$(npm version ${{ inputs.version }})" >> $GITHUB_OUTPUT
44 | - run: git push --follow-tags
45 | github_release:
46 | name: Create a release
47 | runs-on: ubuntu-latest
48 | needs: release
49 | env:
50 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
51 | steps:
52 | - uses: actions/checkout@v4
53 | - run: gh release create ${{ needs.release.outputs.new_tag }} --draft --verify-tag
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .idea
3 | dist
4 | .DS_Store
5 | temp
6 | doc
7 | docs
8 | build
9 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 | . "$(dirname -- "$0")/_/husky.sh"
3 |
4 | exit_with_warning_message(){
5 | echo "\e[0;33m$1\e[m"
6 | exit 1
7 | }
8 |
9 | echo "Formatting and linting staged files ✨"
10 | ./node_modules/.bin/lint-staged || exit_with_warning_message "\nCode contains linting errors.\n"
11 | echo "Successfully formatted and linted staged files ✨"
12 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tabWidth: 4,
3 | printWidth: 100,
4 | endOfLine: 'lf',
5 | arrowParens: 'avoid',
6 | trailingComma: 'es5',
7 | semi: true,
8 | useTabs: false,
9 | singleQuote: true,
10 | bracketSpacing: true,
11 | overrides: [
12 | {
13 | files: '*.json',
14 | options: {
15 | tabWidth: 2,
16 | },
17 | },
18 | ],
19 | };
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # node-simconnect
2 |
3 | [](https://badge.fury.io/js/node-simconnect)
4 | [](https://www.typescriptlang.org)
5 |
6 | A non-official SimConnect client library written in TypeScript. Lets you write Node.js applications that communicates directly with Microsoft Flight Simulator, FSX and Prepar3D without need for additional SDK files. Runs on Windows, Linux and Mac.
7 |
8 | This project is a port of the Java client library
9 | [jsimconnect](https://github.com/mharj/jsimconnect), originally written by
10 | [lc0277](https://www.fsdeveloper.com/forum/members/lc0277.1581). Details about the protocol can be found on [lc0277's old website](http://web.archive.org/web/20090620063532/http://lc0277.nerim.net/jsimconnect/doc/flightsim/simconnect/package-summary.html#package_description). A huge thanks to everyone involved in that project! :pray:
11 |
12 | ## Installation and use
13 |
14 | > :bulb: Tip: check out the [msfs-simconnect-api-wrapper](https://www.npmjs.com/package/msfs-simconnect-api-wrapper) which provides a more user-friendly wrapper around some of the `node-simconnect` APIs.
15 |
16 | 1. `npm install node-simconnect`
17 | 2. Check out the [/samples](https://github.com/EvenAR/node-simconnect/tree/master/samples) folder for example scripts.
18 | 3. Refer to the [official SimConnect documentation](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm) for comprehensive details on SimConnect APIs and usage.
19 |
20 | There are also [auto generated API-docs](https://evenar.github.io/node-simconnect/).
21 |
22 | ### Getting started
23 |
24 | You always start by calling [`open(...)`](https://evenar.github.io/node-simconnect/functions/open.html) which will attempt to open a connection with the SimConnect server (your flight simulator). If this succeeds you will get access to:
25 |
26 | - [`recvOpen`](https://evenar.github.io/node-simconnect/classes/RecvOpen.html): contains simulator information
27 | - [`handle`](https://evenar.github.io/node-simconnect/classes/SimConnectConnection.html): used for accessing the SimConnect APIs
28 |
29 | Example:
30 |
31 | ```js
32 | import { open, Protocol } from 'node-simconnect';
33 |
34 | const EVENT_ID_PAUSE = 1;
35 |
36 | open('My SimConnect client', Protocol.FSX_SP2)
37 | .then(function ({ recvOpen, handle }) {
38 | console.log('Connected to', recvOpen.applicationName);
39 |
40 | handle.on('event', function (recvEvent) {
41 | switch (recvEvent.clientEventId) {
42 | case EVENT_ID_PAUSE:
43 | console.log(recvEvent.data === 1 ? 'Sim paused' : 'Sim unpaused');
44 | break;
45 | }
46 | });
47 | handle.on('exception', function (recvException) {
48 | console.log(recvException);
49 | });
50 | handle.on('quit', function () {
51 | console.log('Simulator quit');
52 | });
53 | handle.on('close', function () {
54 | console.log('Connection closed unexpectedly (simulator CTD?)');
55 | });
56 |
57 | handle.subscribeToSystemEvent(EVENT_ID_PAUSE, 'Pause');
58 | })
59 | .catch(function (error) {
60 | console.log('Connection failed:', error);
61 | });
62 | ```
63 |
64 | ## node-simconnect vs the official API
65 |
66 | ### Supported APIs
67 |
68 | Most of the APIs described in the [official SimConnect documentation](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_API_Reference.htm) are implemented in `node-simconnect`. For information on how each feature works, please refer to the official documentation.
69 |
70 | Several new features have been added to the SimConnect API after the new Microsoft Flight Simulator was released, and more features are likely to come. Most of these will only be implemented on request. If you are missing any features in `node-simconnect` feel free to [open a new issue](https://github.com/EvenAR/node-simconnect/issues) or create a pull request.
71 |
72 | Prepar3D support and Prepar3D-only-features will not be prioritized.
73 |
74 | For a complete list of available API methods, please refer to the [`SimConnectConnection`](https://evenar.github.io/node-simconnect/classes/SimConnectConnection.html) class.
75 |
76 | ### Data unwrapping
77 |
78 | A major feature used by C/C++/C# implementation of SimConnect client libraries is the ability to directly cast a memory block to a user-defined structure. This is technically impossible to do in JavaScript or TypeScript since memory layout of classes and types are not accessible. Consequently, the wrapping/unwrapping steps must be done by the user.
79 |
80 | Example using the official SimConnect SDK (C++):
81 |
82 | ```C++
83 | // C++ code ////////////////////
84 |
85 | struct Struct1 {
86 | double kohlsmann;
87 | double altitude;
88 | double latitude;
89 | double longitude;
90 | int verticalSpeed;
91 | };
92 |
93 | // ....
94 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Kohlsman setting hg", "inHg");
95 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Indicated Altitude", "feet");
96 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Plane Latitude", "degrees");
97 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "Plane Longitude", "degrees");
98 | hr = SimConnect_AddToDataDefinition(hSimConnect, DEFINITION_1, "VERTICAL SPEED", "Feet per second", SimConnectDataType.INT32);
99 |
100 | SimConnect_RequestDataOnSimObject(hSimConnect, REQUEST_1, DEFINITION_1, SIMCONNECT_OBJECT_ID_USER, SIMCONNECT_PERIOD_SECOND);
101 | // ....
102 |
103 | void CALLBACK MyDispatchProc(SIMCONNECT_RECV* pData, DWORD cbData) {
104 | switch(pData->dwID) {
105 | case SIMCONNECT_RECV_ID_SIMOBJECT_DATA: {
106 | SIMCONNECT_RECV_SIMOBJECT_DATA *pObjData = (SIMCONNECT_RECV_SIMOBJECT_DATA*) pData;
107 | switch(pObjData->dwRequestID) {
108 | case REQUEST_1:
109 | Struct1 *pS = (Struct1*)&pObjData->dwData;
110 | break;
111 | }
112 | break;
113 | }
114 | }
115 | }
116 | ```
117 |
118 | The code below demonstrates how the same is achieved with `node-simconnect`:
119 |
120 | ```ts
121 | // TypeScript code ////////////////////
122 |
123 | const REQUEST_1 = 0;
124 | const DEFINITION_1 = 0;
125 | // ....
126 | handle.addToDataDefinition(DEFINITION_1, 'Kohlsman setting hg', 'inHg', SimConnectDataType.FLOAT64);
127 | handle.addToDataDefinition(DEFINITION_1, 'Indicated Altitude', 'feet', SimConnectDataType.FLOAT64);
128 | handle.addToDataDefinition(DEFINITION_1, 'Plane Latitude', 'degrees', SimConnectDataType.FLOAT64);
129 | handle.addToDataDefinition(DEFINITION_1, 'Plane Longitude', 'degrees', SimConnectDataType.FLOAT64);
130 | handle.addToDataDefinition(DEFINITION_1, 'VERTICAL SPEED', 'Feet per second', SimConnectDataType.INT32);
131 |
132 | handle.requestDataOnSimObject(REQUEST_1, DEFINITION_1, SimConnectConstants.OBJECT_ID_USER, SimConnectPeriod.SIM_FRAME);
133 |
134 | // ....
135 | handle.on('simObjectData', recvSimObjectData => {
136 | switch (recvSimObjectData.requestID) {
137 | case REQUEST_1: {
138 | const receivedData = {
139 | // Read order is important!
140 | kohlsmann: recvSimObjectData.data.readFloat64(),
141 | altitude: recvSimObjectData.data.readFloat64(),
142 | latitude: recvSimObjectData.data.readFloat64(),
143 | longitude: recvSimObjectData.data.readFloat64(),
144 | verticalSpeed: recvSimObjectData.data.readInt32(),
145 | }
146 | break;
147 | }
148 | }
149 | });
150 | ```
151 |
152 | When the `simObjectData` callback is triggered, the `recvSimObjectData.data` is used to extract the requested simulation variables. These values are stored as a single continuous binary data chunk/buffer, maintaining the order in which the simulation variables were added to the data definition. In this case, the buffer is 288 bits long (64 + 64 + 64 + 64 + 32), or 36 bytes.
153 |
154 | The `read...()` functions are used to extract each value individually. When the correct function is used, the reading "cursor" (offset) automatically moves after each read, positioning it at the beginning of the next value in the buffer. Consequently, it is crucial to ensure that the values are read in the same order and using the same data type as initially requested.
155 |
156 | ## Running over network?
157 |
158 | If the Node.js application runs on the same computer as the flight simulator you don't need to worry about this part.
159 |
160 | To connect from an external computer you must configure SimConnect to accept connections from other hosts. This procedure is also described in the official docs, but here is the short version:
161 |
162 | 1. Open `SimConnect.xml`.
163 |
164 | - FSX: `X:\Users\\AppData\Roaming\Microsoft\FSX`
165 | - MSFS: `X:\Users\\AppData\Local\Packages\Microsoft.FlightSimulator_**********\LocalCache`.
166 |
167 | 1. Set property `0.0.0.0`. Example of a working SimConnect.xml file:
168 |
169 | ```xml
170 |
171 |
172 | SimConnect.xml
173 |
174 | IPv4
175 | local
176 | 5111
177 | 64
178 | 41088
179 | 0.0.0.0
180 |
181 |
182 | ```
183 |
184 | Connecting from a remote script can be done by providing the IP address of the flight simulator PC and the port number when calling `open`:
185 |
186 | ```js
187 | const options = { remote: { host: 'localhost', port: 5111 } };
188 |
189 | open('My SimConnect client', Protocol.FSX_SP2, options).then(/* ... */).catch(/* try again? */);
190 | ```
191 |
192 | Note that if no connection options are specified, `node-simconnect` will auto-discover connection details in the following order:
193 |
194 | 1. Look for a [`SimConnect.cfg`](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_CFG_Definition.htm) in the folder where Node.js is located. If the script is running in Electron, this will be the folder where the Electron executable is installed.
195 | 1. Look for a [`SimConnect.cfg`](https://docs.flightsimulator.com/html/Programming_Tools/SimConnect/SimConnect_CFG_Definition.htm) in the user's home directory (`%USERPROFILE%`, eg. `C:\Users\`)
196 | 1. Look for a named pipe in the Windows registry, automatically set by the simulator
197 | 1. Look for a port number in the Windows registry, automatically set by the simulator. node-simconnect will then connect to `localhost:`.
198 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | };
5 |
--------------------------------------------------------------------------------
/lint-staged.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | '**/*.ts': () => 'tsc --noEmit',
3 | '**/*.(ts|js)': (filenames) => [
4 | `eslint --fix ${filenames.join(' ')}`,
5 | `prettier --write ${filenames.join(' ')}`,
6 | ],
7 | '**/*.(md|json)': (filenames) => `prettier --write ${filenames.join(' ')}`,
8 | };
9 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "node-simconnect",
3 | "version": "4.0.0",
4 | "description": "A SimConnect client library for Node.JS.",
5 | "main": "dist/index.js",
6 | "types": "dist/index.d.ts",
7 | "type": "commonjs",
8 | "scripts": {
9 | "build": "npx rimraf dist && tsc -p tsconfig.build.json",
10 | "watch": "tsc --watch",
11 | "test": "jest",
12 | "link": "npm run build && cd dist && npm link",
13 | "precommit": "pretty-quick --staged",
14 | "prepublishOnly": "npm run build",
15 | "prepare": "husky install",
16 | "generate-api-reference": "typedoc src/index.ts"
17 | },
18 | "author": "Even Arneberg Rognlien",
19 | "license": "LGPL-3.0-or-later",
20 | "repository": {
21 | "type": "git",
22 | "url": "git+https://github.com/EvenAR/node-simconnect.git"
23 | },
24 | "keywords": [
25 | "FSX",
26 | "P3D",
27 | "SDK",
28 | "SimConnect",
29 | "Prepar3D",
30 | "FlightSimulator",
31 | "Simulator"
32 | ],
33 | "bugs": {
34 | "url": "https://github.com/EvenAR/node-simconnect/issues"
35 | },
36 | "homepage": "https://github.com/EvenAR/node-simconnect#readme",
37 | "devDependencies": {
38 | "@types/bytebuffer": "^5.0.44",
39 | "@types/debug": "^4.1.7",
40 | "@types/ini": "^1.3.30",
41 | "@types/jest": "^29.2.4",
42 | "@typescript-eslint/eslint-plugin": "^5.26.0",
43 | "@typescript-eslint/parser": "^5.26.0",
44 | "eslint": "^8.16.0",
45 | "eslint-config-airbnb-base": "^15.0.0",
46 | "eslint-config-airbnb-typescript": "^17.0.0",
47 | "eslint-config-prettier": "^8.5.0",
48 | "eslint-plugin-import": "^2.26.0",
49 | "eslint-plugin-prettier": "^4.0.0",
50 | "eslint-plugin-simple-import-sort": "^7.0.0",
51 | "eslint-plugin-tsdoc": "^0.2.17",
52 | "husky": "^8.0.1",
53 | "jest": "^29.3.1",
54 | "lint-staged": "^12.4.2",
55 | "prettier": "^2.6.2",
56 | "pretty-quick": "^3.1.0",
57 | "ts-jest": "^29.0.3",
58 | "typedoc": "^0.25.4",
59 | "typescript": "^4.9.4"
60 | },
61 | "dependencies": {
62 | "@types/node": "*",
63 | "bytebuffer": "^5.0.1",
64 | "debug": "^4.3.4",
65 | "ini": "^2.0.0",
66 | "regedit": "^5.1.1"
67 | },
68 | "husky": {
69 | "hooks": {
70 | "pre-commit": "npm run precommit"
71 | }
72 | },
73 | "files": [
74 | "/dist",
75 | "/samples",
76 | "./package.json",
77 | "./package-lock.json",
78 | "./README.md",
79 | "./LICENCE"
80 | ]
81 | }
82 |
--------------------------------------------------------------------------------
/samples/README.md:
--------------------------------------------------------------------------------
1 | # node-simconnect samples
2 |
3 | In order to run the samples you first need to build the node-simconnect project (from the root folder):
4 |
5 | ```
6 | npm install
7 | npm run build
8 | ```
9 |
10 | Then (still from the node-simconnect root folder):
11 |
12 | - TypeScript: `npx ts-node .\\samples\\typescript\\.ts`
13 | - JavaScript: `node .\\samples\\js\\.mjs`
14 |
--------------------------------------------------------------------------------
/samples/js/aircraftPosition.mjs:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | SimConnectDataType,
5 | SimConnectPeriod,
6 | SimConnectConstants,
7 | } from '../../dist/index.js';
8 |
9 | const POSITION_DATA = 1;
10 | const REQUEST_ID_POSITION_DATA = 1;
11 | const EVENT_ID_PAUSE = 1;
12 |
13 | open('My app', Protocol.FSX_SP2)
14 | .then(function ({ recvOpen, handle }) {
15 | console.log('Connected to', recvOpen.applicationName);
16 |
17 | handle.addToDataDefinition(
18 | POSITION_DATA,
19 | 'Plane Latitude',
20 | 'degrees',
21 | SimConnectDataType.FLOAT64,
22 | 0.0,
23 | SimConnectConstants.UNUSED
24 | );
25 | handle.addToDataDefinition(
26 | POSITION_DATA,
27 | 'Plane Longitude',
28 | 'degrees',
29 | SimConnectDataType.FLOAT64,
30 | 0.0,
31 | SimConnectConstants.UNUSED
32 | );
33 | handle.requestDataOnSimObject(
34 | REQUEST_ID_POSITION_DATA,
35 | POSITION_DATA,
36 | SimConnectConstants.OBJECT_ID_USER,
37 | SimConnectPeriod.SECOND,
38 | 0,
39 | 0,
40 | 0,
41 | 0
42 | );
43 | handle.subscribeToSystemEvent(EVENT_ID_PAUSE, 'Pause');
44 |
45 | handle.on('event', function (recvEvent) {
46 | switch (recvEvent.eventID) {
47 | case EVENT_ID_PAUSE:
48 | console.log(
49 | recvEvent.data === 1 ? 'Sim paused' : 'Sim unpaused'
50 | );
51 | break;
52 | }
53 | });
54 |
55 | handle.on('simObjectData', function (recvSimObjectData) {
56 | switch (recvSimObjectData.requestID) {
57 | case POSITION_DATA:
58 | console.log({
59 | latitude: recvSimObjectData.data.readFloat64(),
60 | longitude: recvSimObjectData.data.readFloat64(),
61 | });
62 | break;
63 | }
64 | });
65 | handle.on('quit', function () {
66 | console.log('Quit');
67 | });
68 | })
69 | .catch(function (error) {
70 | console.log('Connection failed:', error);
71 | });
72 |
--------------------------------------------------------------------------------
/samples/typescript/aircraftInformation.ts:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | SimConnectConstants,
5 | SimConnectDataType,
6 | SimConnectPeriod,
7 | } from '../../dist';
8 |
9 | const AIRCRAFT_DATA_REQUEST = 0;
10 | const AIRCRAFT_DATA_DEFINITION = 0;
11 |
12 | open('My app', Protocol.FSX_SP2).then(recv => {
13 | console.log('Connected to', recv.recvOpen.applicationName);
14 |
15 | recv.handle.addToDataDefinition(
16 | AIRCRAFT_DATA_DEFINITION,
17 | 'Plane Latitude',
18 | 'degrees',
19 | SimConnectDataType.FLOAT64,
20 | 0,
21 | SimConnectConstants.UNUSED
22 | );
23 | recv.handle.addToDataDefinition(
24 | AIRCRAFT_DATA_DEFINITION,
25 | 'Plane Longitude',
26 | 'degrees',
27 | SimConnectDataType.FLOAT64,
28 | 0,
29 | SimConnectConstants.UNUSED
30 | );
31 | recv.handle.addToDataDefinition(
32 | AIRCRAFT_DATA_DEFINITION,
33 | 'CATEGORY',
34 | null,
35 | SimConnectDataType.STRING32,
36 | 0,
37 | SimConnectConstants.UNUSED
38 | );
39 | recv.handle.addToDataDefinition(
40 | AIRCRAFT_DATA_DEFINITION,
41 | 'TITLE',
42 | null,
43 | SimConnectDataType.STRING128,
44 | 0,
45 | SimConnectConstants.UNUSED
46 | );
47 | recv.handle.addToDataDefinition(
48 | AIRCRAFT_DATA_DEFINITION,
49 | 'ATC ID',
50 | null,
51 | SimConnectDataType.STRING32,
52 | 0,
53 | SimConnectConstants.UNUSED
54 | );
55 |
56 | recv.handle.requestDataOnSimObject(
57 | AIRCRAFT_DATA_REQUEST,
58 | AIRCRAFT_DATA_DEFINITION,
59 | SimConnectConstants.OBJECT_ID_USER,
60 | SimConnectPeriod.ONCE,
61 | 0,
62 | 0,
63 | 0,
64 | 0
65 | );
66 |
67 | recv.handle.on('simObjectData', recvSimObjectData => {
68 | console.log(`Lat: ${recvSimObjectData.data.readFloat64()}`);
69 | console.log(`Lng: ${recvSimObjectData.data.readFloat64()}`);
70 | console.log(`Type: "${recvSimObjectData.data.readString32()}"`);
71 | console.log(`Title: "${recvSimObjectData.data.readString128()}"`);
72 | console.log(`ATC ID: "${recvSimObjectData.data.readString32()}"`);
73 | });
74 | });
75 |
--------------------------------------------------------------------------------
/samples/typescript/clientEvents.ts:
--------------------------------------------------------------------------------
1 | import { EventFlag, open, Protocol, SimConnectConstants } from '../../dist';
2 |
3 | /**
4 | * Demonstrates how to use client events to interact with the simulator
5 | */
6 |
7 | enum ClientEventID {
8 | INCREMENT,
9 | DECREMENT,
10 | }
11 |
12 | open('My app', Protocol.FSX_SP2)
13 | .then(({ recvOpen, handle }) => {
14 | console.log('Connected: ', recvOpen);
15 |
16 | handle.mapClientEventToSimEvent(ClientEventID.INCREMENT, 'AP_ALT_VAR_INC');
17 | handle.mapClientEventToSimEvent(ClientEventID.DECREMENT, 'AP_ALT_VAR_DEC');
18 |
19 | let step = 0;
20 | let rise = true;
21 |
22 | // Increment and decrement the altitude every 50ms
23 | setInterval(() => {
24 | step++;
25 | handle.transmitClientEvent(
26 | SimConnectConstants.OBJECT_ID_USER,
27 | rise ? ClientEventID.INCREMENT : ClientEventID.DECREMENT,
28 | 0,
29 | 1,
30 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY
31 | );
32 |
33 | if (step === 100) {
34 | rise = !rise; // Change direction
35 | step = 0;
36 | }
37 | }, 50);
38 |
39 | handle.on('error', error => {
40 | console.log('Error:', error);
41 | });
42 |
43 | handle.on('exception', recvException => {
44 | console.log('recvException:', recvException);
45 | });
46 | })
47 | .catch(error => {
48 | console.log('Failed to connect', error);
49 | });
50 |
--------------------------------------------------------------------------------
/samples/typescript/facilities.ts:
--------------------------------------------------------------------------------
1 | import { Protocol, open, FacilityListType } from '../../dist';
2 | import { FacilityDataType } from '../../dist/enums/FacilityDataType';
3 |
4 | /**
5 | * Reading navigational data from an airport (KittyHawk only)
6 | */
7 |
8 | const AIRPORT_ICAO = 'ENGM';
9 |
10 | const enum DefinitionID {
11 | FACILITY_AIRPORT = 1000,
12 | FACILITY_VOR = 2000,
13 | }
14 |
15 | const enum REQUEST_ID {
16 | NEW_AIRPORTS = 42,
17 | OLD_AIRPORTS = 43,
18 | }
19 |
20 | let requestId = 100;
21 |
22 | const DESIGNATOR_VALUE = ['', 'L', 'R', 'C', 'WATER', 'A', 'B', 'LAST'];
23 |
24 | open('SimConnect sample client', Protocol.KittyHawk)
25 | .then(({ recvOpen, handle }) => {
26 | console.log('Connected to sim!');
27 |
28 | // Airport
29 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN AIRPORT'); // Open
30 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LATITUDE');
31 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LONGITUDE');
32 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'ALTITUDE');
33 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'MAGVAR');
34 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'NAME');
35 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'N_RUNWAYS');
36 | // Runway
37 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN RUNWAY'); // Open
38 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_NUMBER');
39 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_DESIGNATOR');
40 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_ILS_ICAO');
41 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'PRIMARY_ILS_REGION');
42 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_NUMBER');
43 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_DESIGNATOR');
44 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_ILS_ICAO');
45 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SECONDARY_ILS_REGION');
46 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'HEADING');
47 | handle.addToFacilityDefinition(
48 | DefinitionID.FACILITY_AIRPORT,
49 | 'OPEN PRIMARY_APPROACH_LIGHTS'
50 | );
51 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'SYSTEM');
52 | handle.addToFacilityDefinition(
53 | DefinitionID.FACILITY_AIRPORT,
54 | 'CLOSE PRIMARY_APPROACH_LIGHTS'
55 | );
56 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_LEFT_VASI');
57 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'TYPE');
58 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_LEFT_VASI');
59 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_RIGHT_VASI');
60 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'TYPE');
61 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_RIGHT_VASI');
62 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'OPEN PRIMARY_OVERRUN');
63 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'LENGTH');
64 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE PRIMARY_OVERRUN');
65 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE RUNWAY'); // Close
66 | // VOR
67 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'OPEN VOR'); // Open
68 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'NAME');
69 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'FREQUENCY');
70 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'LOCALIZER');
71 | handle.addToFacilityDefinition(DefinitionID.FACILITY_VOR, 'CLOSE VOR'); // Close
72 |
73 | handle.addToFacilityDefinition(DefinitionID.FACILITY_AIRPORT, 'CLOSE AIRPORT'); // Close
74 |
75 | // Request data
76 | handle.requestFacilityData(DefinitionID.FACILITY_AIRPORT, requestId++, AIRPORT_ICAO);
77 | handle.subscribeToFacilitiesEx1(
78 | FacilityListType.AIRPORT,
79 | REQUEST_ID.NEW_AIRPORTS,
80 | REQUEST_ID.OLD_AIRPORTS
81 | );
82 |
83 | handle.on('facilityData', recvFacilityData => {
84 | switch (recvFacilityData.type) {
85 | case FacilityDataType.AIRPORT:
86 | // Reading order is important!
87 | const lat = recvFacilityData.data.readFloat64();
88 | const lng = recvFacilityData.data.readFloat64();
89 | const alt = recvFacilityData.data.readFloat64();
90 | const magvar = recvFacilityData.data.readFloat32();
91 | const name = recvFacilityData.data.readString32();
92 | const nRunways = recvFacilityData.data.readInt32();
93 | console.log({ lat, lng, alt, magvar, name, nRunways });
94 | break;
95 | case FacilityDataType.RUNWAY:
96 | // Reading order is important!
97 | const number1 = recvFacilityData.data.readInt32();
98 | const designator1 = recvFacilityData.data.readInt32();
99 | const ilsIcao1 = recvFacilityData.data.readString8();
100 | const ilsRegion1 = recvFacilityData.data.readString8();
101 | const number2 = recvFacilityData.data.readInt32();
102 | const designator2 = recvFacilityData.data.readInt32();
103 | const ilsIcao2 = recvFacilityData.data.readString8();
104 | const ilsRegion2 = recvFacilityData.data.readString8();
105 |
106 | const heading = recvFacilityData.data.readFloat32();
107 |
108 | console.log({
109 | runway:
110 | number1 +
111 | DESIGNATOR_VALUE[designator1] +
112 | '/' +
113 | number2 +
114 | DESIGNATOR_VALUE[designator2],
115 | heading,
116 | ilsIcao1,
117 | ilsIcao2,
118 | ilsRegion1,
119 | ilsRegion2,
120 | });
121 |
122 | handle.requestFacilityData(
123 | DefinitionID.FACILITY_VOR,
124 | requestId++,
125 | ilsIcao1,
126 | ilsRegion1,
127 | 'V'
128 | );
129 | handle.requestFacilityData(
130 | DefinitionID.FACILITY_VOR,
131 | requestId++,
132 | ilsIcao2,
133 | ilsRegion2,
134 | 'V'
135 | );
136 | break;
137 | case FacilityDataType.VOR:
138 | const vorName = recvFacilityData.data.readString64();
139 | const frequency = recvFacilityData.data.readInt32() / 1_000_000;
140 | const localizer = recvFacilityData.data.readFloat32();
141 | console.log({
142 | vorName,
143 | frequency,
144 | localizer,
145 | });
146 | break;
147 | // Note that you'll need to keep track of the order of these as they will arrive
148 | // in the same order as they were requested
149 | case FacilityDataType.VASI:
150 | console.log('VASI', recvFacilityData.data.readInt32());
151 | break;
152 | case FacilityDataType.APPROACHLIGHTS:
153 | console.log('APPROACHLIGHTS', recvFacilityData.data.readInt32());
154 | break;
155 | case FacilityDataType.PAVEMENT:
156 | console.log('PAVEMENT', recvFacilityData.data.readFloat32());
157 | break;
158 | }
159 | });
160 |
161 | handle.on('facilityMinimalList', recvFacilityMinimalList => {
162 | console.log(recvFacilityMinimalList);
163 | });
164 |
165 | handle.on('facilityDataEnd', recvFacilityDataEnd => {
166 | console.log(
167 | `Finished reading data for request ID: ` + recvFacilityDataEnd.userRequestId
168 | );
169 | });
170 |
171 | handle.on('airportList', recvWaypointList => {
172 | switch (recvWaypointList.requestID) {
173 | case REQUEST_ID.NEW_AIRPORTS:
174 | console.log('These airports appeared', recvWaypointList.airports);
175 | break;
176 | case REQUEST_ID.OLD_AIRPORTS:
177 | console.log('These airports disappeared', recvWaypointList.airports);
178 | break;
179 | }
180 | });
181 |
182 | handle.on('exception', exception => {
183 | console.log(exception);
184 | });
185 | })
186 | .catch(error => {
187 | console.log('Failed to connect', error);
188 | });
189 |
--------------------------------------------------------------------------------
/samples/typescript/facilitiesMenu.ts:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | RecvEvent,
5 | TextType,
6 | TextResult,
7 | FacilityListType,
8 | NotificationPriority,
9 | } from '../../dist';
10 |
11 | /**
12 | * SimConnect FaciliitesData sample
13 | *
14 | * Description:
15 | * Ctrl F1 displays the Get Facilities menu on the screen
16 | * Ctrl F2 displays the Subscribe to Faciliites menu on the screen
17 | *
18 | */
19 |
20 | enum NotificationGroupID {
21 | GROUP0,
22 | }
23 |
24 | enum EventID {
25 | OPEN_MENU_1,
26 | OPEN_MENU_2,
27 | EVENT_MENU_1,
28 | EVENT_MENU_2,
29 | }
30 |
31 | enum InputGroupID {
32 | INPUT0,
33 | }
34 |
35 | enum RequestID {
36 | REQUEST_0,
37 | REQUEST_1,
38 | }
39 |
40 | const GET_FACILITIES_MENU_OPTIONS: string[] = [
41 | 'Get airport facilities',
42 | 'Get waypoints',
43 | 'Get NDB',
44 | 'Get VOR',
45 | 'Close menu',
46 | ];
47 |
48 | const SUBSCRIBE_FACILITIES_MENU_OPTIONS: string[] = [
49 | 'Subscribe to airports',
50 | 'Subscribe to waypoints',
51 | 'Subscribe to NDB',
52 | 'Subscribe to VOR',
53 | 'Unsubscribe to airports',
54 | 'Unsubscribe to waypoints',
55 | 'Unsubscribe to NDB',
56 | 'Unsubscribe to VOR',
57 | 'Close menu',
58 | ];
59 |
60 | open('My app', Protocol.FSX_SP2)
61 | .then(({ recvOpen, handle }) => {
62 | console.log('Connected: ', recvOpen);
63 |
64 | handle.mapClientEventToSimEvent(EventID.OPEN_MENU_1);
65 | handle.mapClientEventToSimEvent(EventID.OPEN_MENU_2);
66 | handle.addClientEventToNotificationGroup(
67 | NotificationGroupID.GROUP0,
68 | EventID.OPEN_MENU_1,
69 | true
70 | );
71 | handle.addClientEventToNotificationGroup(
72 | NotificationGroupID.GROUP0,
73 | EventID.OPEN_MENU_2,
74 | true
75 | );
76 | handle.setNotificationGroupPriority(
77 | NotificationGroupID.GROUP0,
78 | NotificationPriority.HIGHEST
79 | );
80 | handle.mapInputEventToClientEvent(
81 | InputGroupID.INPUT0,
82 | 'Ctrl+F1',
83 | EventID.OPEN_MENU_1
84 | );
85 | handle.mapInputEventToClientEvent(
86 | InputGroupID.INPUT0,
87 | 'Ctrl+F2',
88 | EventID.OPEN_MENU_2
89 | );
90 | handle.setInputGroupState(InputGroupID.INPUT0, true);
91 |
92 | handle.text(TextType.PRINT_RED, 15, 3, 'Facilities Data');
93 | handle.text(
94 | TextType.PRINT_RED,
95 | 15,
96 | 3,
97 | 'Press Ctrl-F1 for Get Facilities, Ctrl-F2 for Subscribe to Facilities'
98 | );
99 |
100 | handle.on('event', ({ clientEventId, data }: RecvEvent) => {
101 | switch (clientEventId) {
102 | case EventID.OPEN_MENU_1:
103 | openMenu(GET_FACILITIES_MENU_OPTIONS);
104 | break;
105 | case EventID.OPEN_MENU_2:
106 | openMenu(SUBSCRIBE_FACILITIES_MENU_OPTIONS);
107 | break;
108 | case EventID.EVENT_MENU_1:
109 | {
110 | if (data > TextResult.MENU_SELECT_10) return;
111 | if (data < FacilityListType.COUNT) {
112 | handle.requestFacilitiesList(
113 | data as FacilityListType,
114 | RequestID.REQUEST_0
115 | );
116 | }
117 | }
118 | break;
119 | case EventID.EVENT_MENU_2:
120 | {
121 | if (data > TextResult.MENU_SELECT_10) return;
122 | if (data < FacilityListType.COUNT) {
123 | handle.subscribeToFacilities(
124 | data as FacilityListType,
125 | RequestID.REQUEST_1
126 | );
127 | } else if (data < 2 * FacilityListType.COUNT) {
128 | handle.unSubscribeToFacilities(
129 | (data -
130 | FacilityListType.COUNT) as FacilityListType
131 | );
132 | }
133 | }
134 | break;
135 | }
136 | });
137 |
138 | function openMenu(items: string[]) {
139 | handle.menu(
140 | 0.0,
141 | EventID.EVENT_MENU_1,
142 | 'SimConnect Facilities Test',
143 | 'Choose which item:',
144 | ...items
145 | );
146 | }
147 |
148 | handle.on('airportList', (recvAirportList) => {
149 | console.log(recvAirportList.airports.map((ap) => ap.icao).join(','));
150 | });
151 |
152 | handle.on('ndbList', (recvNDBList) => {
153 | console.log(recvNDBList.ndbs.map((ndb) => ndb.icao).join(','));
154 | });
155 |
156 | handle.on('vorList', (recvVORList) => {
157 | console.log(recvVORList.vors.map((vor) => vor.icao).join(','));
158 | });
159 |
160 | handle.on('waypointList', (recvWaypointList) => {
161 | console.log(
162 | recvWaypointList.waypoints.map((wp) => wp.icao).join(',')
163 | );
164 | });
165 | })
166 | .catch((error) => {
167 | console.log('Failed to connect', error);
168 | });
169 |
--------------------------------------------------------------------------------
/samples/typescript/inputEvents.ts:
--------------------------------------------------------------------------------
1 | import { open, Protocol } from '../../dist';
2 |
3 | /**
4 | * Collects information about all available input events for the aircraft and prints them
5 | */
6 | open('My app', Protocol.KittyHawk)
7 | .then(({ recvOpen, handle }) => {
8 | console.log('Connected: ', recvOpen);
9 |
10 | let allInputEvents: {
11 | inputEventName: string;
12 | inputEventIdHash: bigint;
13 | params?: string;
14 | }[] = [];
15 |
16 | handle.on('inputEventsList', recvEnumerateInputEvents => {
17 | recvEnumerateInputEvents.inputEventDescriptors.forEach(e => {
18 | allInputEvents.push({
19 | inputEventName: e.name,
20 | inputEventIdHash: e.inputEventIdHash,
21 | });
22 | handle.enumerateInputEventParams(e.inputEventIdHash);
23 | });
24 | });
25 |
26 | handle.on('enumerateInputEventParams', recvEnumerateInputEventParams => {
27 | // Update the list with the received value
28 | allInputEvents = allInputEvents.map(inputEvent => {
29 | if (
30 | inputEvent.inputEventIdHash === recvEnumerateInputEventParams.inputEventIdHash
31 | ) {
32 | return { ...inputEvent, params: recvEnumerateInputEventParams.value };
33 | } else {
34 | return inputEvent;
35 | }
36 | });
37 |
38 | // Finally, when no input events are missing the params value, print the list
39 | if (allInputEvents.filter(ev => ev.params === undefined).length === 0) {
40 | console.log(allInputEvents);
41 | }
42 | });
43 |
44 | handle.on('exception', ex => console.log('Exception', ex));
45 |
46 | handle.enumerateInputEvents(0 /* request-id */);
47 | })
48 | .catch(error => {
49 | console.log('Failed to connect', error);
50 | });
51 |
--------------------------------------------------------------------------------
/samples/typescript/pmdgCduScreen.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Prints the text that is currently displayed on the CDU of the PMDG 737
3 | * (captain side), and types "ABC" into the scratchpad using direct event
4 | * triggering.
5 | *
6 | * Assumes that CDU data broadcast is enabled in the PMDG 737 config.
7 | */
8 |
9 | import {
10 | ClientDataPeriod,
11 | ClientDataRequestFlag,
12 | EventFlag,
13 | open,
14 | Protocol,
15 | RawBuffer,
16 | SimConnectConstants,
17 | } from '../../dist';
18 |
19 | // These consts were found in PMDG_NG3_SDK.h
20 | const PMDG_NG3_CDU_0_NAME = 'PMDG_NG3_CDU_0';
21 | const PMDG_NG3_CDU_0_ID = 0x4e473335;
22 | const PMDG_NG3_CDU_0_DEFINITION = 0x4e473338;
23 | const CDU_COLUMNS = 24;
24 | const CDU_ROWS = 14;
25 | const THIRD_PARTY_EVENT_ID_MIN = 0x00011000;
26 |
27 | // Based on the PMDG_NG3_CDU_Screen struct found in PMDG_NG3_SDK.h. There are 3 bytes per character + 1 byte which tells if the CDU is powered.
28 | const SCREEN_STATE_SIZE = CDU_COLUMNS * CDU_ROWS * (1 + 1 + 1) + 1;
29 |
30 | // These event IDs are found in PMDG_NG3_SDK.h
31 | enum PmdgEventID {
32 | CDU_A = THIRD_PARTY_EVENT_ID_MIN + 573,
33 | CDU_B = THIRD_PARTY_EVENT_ID_MIN + 574,
34 | CDU_C = THIRD_PARTY_EVENT_ID_MIN + 575,
35 | }
36 |
37 | enum MyEventID {
38 | CDU_A,
39 | CDU_B,
40 | CDU_C,
41 | }
42 |
43 | const enum RequestID {
44 | CDU_SCREEN_DATA_REQUEST,
45 | }
46 |
47 | (async function readCduScreen() {
48 | const { handle } = await open('My app', Protocol.KittyHawk);
49 | console.log('Connected!');
50 |
51 | handle.mapClientDataNameToID(PMDG_NG3_CDU_0_NAME, PMDG_NG3_CDU_0_ID);
52 | handle.addToClientDataDefinition(PMDG_NG3_CDU_0_DEFINITION, 0, SCREEN_STATE_SIZE);
53 | handle.requestClientData(
54 | PMDG_NG3_CDU_0_ID,
55 | RequestID.CDU_SCREEN_DATA_REQUEST,
56 | PMDG_NG3_CDU_0_DEFINITION,
57 | ClientDataPeriod.ON_SET,
58 | ClientDataRequestFlag.CLIENT_DATA_REQUEST_FLAG_CHANGED
59 | );
60 |
61 | handle.on('exception', ex => console.log(ex));
62 |
63 | handle.on('clientData', recvSimObjectData => {
64 | if (recvSimObjectData.requestID === RequestID.CDU_SCREEN_DATA_REQUEST) {
65 | const { powered, lines } = extractCduScreenState(recvSimObjectData.data);
66 | if (powered) {
67 | console.log(lines.join('\r\n'));
68 | } else {
69 | console.log('Not powered');
70 | }
71 | }
72 | });
73 |
74 | // Write "ABC" to the scratchpad
75 | handle.mapClientEventToSimEvent(MyEventID.CDU_A, '#' + PmdgEventID.CDU_A);
76 | handle.mapClientEventToSimEvent(MyEventID.CDU_B, '#' + PmdgEventID.CDU_B);
77 | handle.mapClientEventToSimEvent(MyEventID.CDU_C, '#' + PmdgEventID.CDU_C);
78 | handle.transmitClientEvent(
79 | SimConnectConstants.OBJECT_ID_USER,
80 | MyEventID.CDU_A,
81 | 1,
82 | 0,
83 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY
84 | );
85 | handle.transmitClientEvent(
86 | SimConnectConstants.OBJECT_ID_USER,
87 | MyEventID.CDU_B,
88 | 1,
89 | 0,
90 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY
91 | );
92 | handle.transmitClientEvent(
93 | SimConnectConstants.OBJECT_ID_USER,
94 | MyEventID.CDU_C,
95 | 1,
96 | 0,
97 | EventFlag.EVENT_FLAG_GROUPID_IS_PRIORITY
98 | );
99 | })();
100 |
101 | function extractCduScreenState(rawBuffer: RawBuffer): { lines: string[]; powered: boolean } {
102 | const screenText: string[] = Array(CDU_ROWS).fill('');
103 |
104 | for (let col = 0; col < CDU_COLUMNS; col++) {
105 | for (let row = 0; row < CDU_ROWS; row++) {
106 | // I tried readString(1) but that only works with zero-terminated strings, which doesn't seem to be used here
107 | const symbol = rawBuffer.readBytes(1).toString('utf-8');
108 | // These values are not used in this example
109 | const color = rawBuffer.readBytes(1)[0];
110 | const flags = rawBuffer.readBytes(1)[0];
111 |
112 | screenText[row] += symbol;
113 | }
114 | }
115 | const cduIsPowered = rawBuffer.readBytes(1)[0] === 1;
116 |
117 | return { lines: screenText, powered: cduIsPowered };
118 | }
119 |
--------------------------------------------------------------------------------
/samples/typescript/reconnect.ts:
--------------------------------------------------------------------------------
1 | import { open, Protocol } from '../../dist';
2 |
3 | connectToSim();
4 |
5 | function connectToSim() {
6 | open('My app', Protocol.FSX_SP2)
7 | .then(({ recvOpen, handle }) => {
8 | console.log('Connected to', recvOpen.applicationName);
9 |
10 | let simIsQuitting = false;
11 |
12 | handle.on('quit', () => {
13 | console.log('The simulator quit. Will try to reconnect.');
14 | handle.close();
15 | connectToSim();
16 | simIsQuitting = true;
17 | });
18 | handle.on('close', () => {
19 | if (!simIsQuitting) {
20 | // if we are not trying to reconnect already
21 | console.log('Connection closed unexpectedly. Will try to reconnect.');
22 | handle.close();
23 | connectToSim();
24 | }
25 | });
26 | })
27 | .catch(error => {
28 | console.log('Failed to connect. Will try again in 5 seconds. Details:', error.message);
29 | setTimeout(connectToSim, 5000);
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/samples/typescript/showMetar.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Protocol,
3 | SimConnectDataType,
4 | SimConnectConstants,
5 | SimConnectPeriod,
6 | RecvSimObjectData,
7 | SimConnectException,
8 | RecvException,
9 | RecvWeatherObservation,
10 | open,
11 | } from '../../dist';
12 |
13 | /**
14 | * A little app to show METAR at current position
15 | */
16 |
17 | const DEF_ID_POSITION = 0;
18 | const REQ_ID_POSITION = 0;
19 | const REQ_ID_METAR = 1;
20 |
21 | open('My app', Protocol.FSX_SP2)
22 | .then(({ recvOpen, handle }) => {
23 | console.log('Connected: ', recvOpen);
24 | handle.addToDataDefinition(
25 | DEF_ID_POSITION,
26 | 'Plane longitude',
27 | 'degrees',
28 | SimConnectDataType.FLOAT32
29 | );
30 | handle.addToDataDefinition(
31 | DEF_ID_POSITION,
32 | 'Plane latitude',
33 | 'degrees',
34 | SimConnectDataType.FLOAT32
35 | );
36 | handle.requestDataOnSimObject(
37 | REQ_ID_POSITION,
38 | DEF_ID_POSITION,
39 | SimConnectConstants.OBJECT_ID_USER,
40 | SimConnectPeriod.ONCE
41 | );
42 |
43 | handle.on('simObjectData', (recvSimObjectData: RecvSimObjectData) => {
44 | const lon = recvSimObjectData.data.readFloat();
45 | const lat = recvSimObjectData.data.readFloat();
46 | console.log(`Requesting METAR at ${lat}, ${lon}`);
47 | handle.weatherRequestObservationAtNearestStation(REQ_ID_METAR, lat, lon);
48 | });
49 |
50 | handle.on('weatherObservation', (recvWeatherObservation: RecvWeatherObservation) => {
51 | console.log(recvWeatherObservation.metar);
52 | });
53 |
54 | handle.on('exception', (recvException: RecvException) => {
55 | if (recvException.exception === SimConnectException.WEATHER_UNABLE_TO_GET_OBSERVATION) {
56 | console.log('Unable to get weather observation');
57 | } else {
58 | console.log(`Unexpected exception: ${recvException.exception}`);
59 | }
60 | });
61 | })
62 | .catch(error => {
63 | console.log('Failed to connect', error);
64 | });
65 |
--------------------------------------------------------------------------------
/samples/typescript/simulationVariablesRead.ts:
--------------------------------------------------------------------------------
1 | import {
2 | Protocol,
3 | SimConnectConstants,
4 | SimConnectDataType,
5 | SimConnectPeriod,
6 | open,
7 | readLatLonAlt,
8 | } from '../../dist';
9 |
10 | /**
11 | * Demonstrates a few system events
12 | */
13 |
14 | const enum EventID {
15 | PAUSE,
16 | }
17 |
18 | const enum DefinitionID {
19 | LIVE_DATA,
20 | }
21 |
22 | const enum RequestID {
23 | LIVE_DATA,
24 | }
25 |
26 | open('My app', Protocol.FSX_SP2)
27 | .then(({ recvOpen, handle }) => {
28 | console.log('Connected:', recvOpen);
29 |
30 | handle.subscribeToSystemEvent(EventID.PAUSE, 'Pause');
31 |
32 | handle.addToDataDefinition(
33 | DefinitionID.LIVE_DATA,
34 | 'STRUCT LATLONALT',
35 | null,
36 | SimConnectDataType.LATLONALT
37 | );
38 |
39 | handle.addToDataDefinition(
40 | DefinitionID.LIVE_DATA,
41 | 'AIRSPEED INDICATED',
42 | 'knots',
43 | SimConnectDataType.INT32
44 | );
45 |
46 | handle.addToDataDefinition(
47 | DefinitionID.LIVE_DATA,
48 | 'VERTICAL SPEED',
49 | 'Feet per second',
50 | SimConnectDataType.INT32
51 | );
52 |
53 | handle.addToDataDefinition(
54 | DefinitionID.LIVE_DATA,
55 | 'PLANE HEADING DEGREES TRUE',
56 | 'Degrees',
57 | SimConnectDataType.INT32
58 | );
59 |
60 | handle.addToDataDefinition(
61 | DefinitionID.LIVE_DATA,
62 | 'LIGHT LANDING',
63 | 'bool',
64 | SimConnectDataType.INT32
65 | );
66 |
67 | handle.requestDataOnSimObject(
68 | RequestID.LIVE_DATA,
69 | DefinitionID.LIVE_DATA,
70 | SimConnectConstants.OBJECT_ID_USER,
71 | SimConnectPeriod.SIM_FRAME
72 | );
73 |
74 | handle.on('simObjectData', recvSimObjectData => {
75 | if (recvSimObjectData.requestID === RequestID.LIVE_DATA) {
76 | console.log({
77 | // Read order is important
78 | position: readLatLonAlt(recvSimObjectData.data),
79 | airspeed: recvSimObjectData.data.readInt32(),
80 | verticalSpeed: recvSimObjectData.data.readInt32(),
81 | heading: recvSimObjectData.data.readInt32(),
82 | landingLight: recvSimObjectData.data.readInt32() === 1,
83 | });
84 | }
85 | });
86 |
87 | handle.on('event', recvEvent => {
88 | if (recvEvent.clientEventId === EventID.PAUSE) {
89 | console.log(recvEvent.data === 1 ? 'Paused' : 'Unpaused');
90 | }
91 | });
92 | })
93 | .catch(error => {
94 | console.log('Failed to connect', error);
95 | });
96 |
--------------------------------------------------------------------------------
/samples/typescript/simulationVariablesReadTagged.ts:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | readLatLonAlt,
5 | SimConnectConstants,
6 | SimConnectDataType,
7 | SimConnectPeriod,
8 | } from '../../dist';
9 | import { DataRequestFlag } from '../../dist';
10 |
11 | const enum DefinitionID {
12 | LIVE_DATA,
13 | }
14 |
15 | const enum RequestID {
16 | LIVE_DATA,
17 | }
18 |
19 | const enum Tag {
20 | LAT_LON_ALT,
21 | AIRSPEED,
22 | VERTICAL_SPEED,
23 | HEADING,
24 | LANDING_LIGHT,
25 | }
26 |
27 | open('My app', Protocol.FSX_SP2)
28 | .then(({ recvOpen, handle }) => {
29 | console.log('Connected:', recvOpen);
30 |
31 | handle.addToDataDefinition(
32 | DefinitionID.LIVE_DATA,
33 | 'STRUCT LATLONALT',
34 | null,
35 | SimConnectDataType.LATLONALT,
36 | 0,
37 | Tag.LAT_LON_ALT
38 | );
39 |
40 | handle.addToDataDefinition(
41 | DefinitionID.LIVE_DATA,
42 | 'AIRSPEED INDICATED',
43 | 'knots',
44 | SimConnectDataType.INT32,
45 | 0,
46 | Tag.AIRSPEED
47 | );
48 |
49 | handle.addToDataDefinition(
50 | DefinitionID.LIVE_DATA,
51 | 'VERTICAL SPEED',
52 | 'Feet per second',
53 | SimConnectDataType.INT32,
54 | 0,
55 | Tag.VERTICAL_SPEED
56 | );
57 |
58 | handle.addToDataDefinition(
59 | DefinitionID.LIVE_DATA,
60 | 'PLANE HEADING DEGREES TRUE',
61 | 'Degrees',
62 | SimConnectDataType.INT32,
63 | 0,
64 | Tag.HEADING
65 | );
66 |
67 | handle.addToDataDefinition(
68 | DefinitionID.LIVE_DATA,
69 | 'LIGHT LANDING',
70 | 'bool',
71 | SimConnectDataType.INT32,
72 | 0,
73 | Tag.LANDING_LIGHT
74 | );
75 |
76 | handle.requestDataOnSimObject(
77 | RequestID.LIVE_DATA,
78 | DefinitionID.LIVE_DATA,
79 | SimConnectConstants.OBJECT_ID_USER,
80 | SimConnectPeriod.SIM_FRAME,
81 | DataRequestFlag.DATA_REQUEST_FLAG_CHANGED | DataRequestFlag.DATA_REQUEST_FLAG_TAGGED
82 | );
83 |
84 | handle.on('simObjectData', recvSimObjectData => {
85 | if (recvSimObjectData.requestID === RequestID.LIVE_DATA) {
86 | let counter = 0;
87 |
88 | while (counter < recvSimObjectData.defineCount) {
89 | /**
90 | * Read all datums in the message. The values are separated by their tag number.
91 | * Important: if not all data are read you might end up with corrupt data.
92 | */
93 | const datumId = recvSimObjectData.data.readInt32();
94 |
95 | switch (datumId) {
96 | case Tag.LAT_LON_ALT:
97 | {
98 | const value = readLatLonAlt(recvSimObjectData.data);
99 | console.log('Position', value);
100 | }
101 | break;
102 | case Tag.AIRSPEED:
103 | {
104 | const value = recvSimObjectData.data.readInt32();
105 | console.log('Airspeed', value);
106 | }
107 | break;
108 | case Tag.HEADING:
109 | {
110 | const value = recvSimObjectData.data.readInt32();
111 | console.log('Heading', value);
112 | }
113 | break;
114 | case Tag.VERTICAL_SPEED:
115 | {
116 | const value = recvSimObjectData.data.readInt32();
117 | console.log('Vertical speed', value);
118 | }
119 | break;
120 | case Tag.LANDING_LIGHT:
121 | {
122 | const value = recvSimObjectData.data.readInt32();
123 | console.log('Landing light on', value);
124 | }
125 | break;
126 | default: {
127 | throw Error(`Unknown datum ID: $datumId`);
128 | }
129 | }
130 | counter++;
131 | }
132 | }
133 | });
134 | })
135 | .catch(error => {
136 | console.log('Failed to connect', error);
137 | });
138 |
--------------------------------------------------------------------------------
/samples/typescript/simulationVariablesWrite.ts:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | SimConnectConstants,
5 | SimConnectDataType,
6 | SimConnectPeriod,
7 | RawBuffer,
8 | } from '../../dist';
9 |
10 | const enum DefinitionID {
11 | LIGHTS,
12 | }
13 |
14 | open('Flick lights', Protocol.FSX_SP2)
15 | .then(({ recvOpen, handle }) => {
16 | console.log('Connected:', recvOpen);
17 |
18 | const lights = ['LIGHT LANDING', 'LIGHT LOGO', 'LIGHT TAXI', 'LIGHT WING', 'LIGHT NAV'];
19 |
20 | lights.forEach(lightName => {
21 | handle.addToDataDefinition(
22 | DefinitionID.LIGHTS,
23 | lightName,
24 | 'Bool',
25 | SimConnectDataType.INT32
26 | );
27 | });
28 |
29 | let lightsOn = false;
30 | const dataToSet = new RawBuffer(100);
31 |
32 | // Toggle all lights on/off every second
33 | setInterval(() => {
34 | lightsOn = !lightsOn;
35 |
36 | dataToSet.clear();
37 | lights.forEach(() => {
38 | dataToSet.writeInt32(lightsOn ? 1 : 0);
39 | });
40 |
41 | handle.setDataOnSimObject(DefinitionID.LIGHTS, SimConnectConstants.OBJECT_ID_USER, {
42 | buffer: dataToSet,
43 | arrayCount: 0,
44 | tagged: false,
45 | });
46 | }, 1000);
47 |
48 | handle.on('exception', recvException => {
49 | console.log(recvException);
50 | });
51 | })
52 | .catch(error => {
53 | console.log('Failed to connect', error);
54 | });
55 |
--------------------------------------------------------------------------------
/samples/typescript/systemEvents.ts:
--------------------------------------------------------------------------------
1 | import { open, Protocol } from '../../dist';
2 | /**
3 | * Demonstrates a few system events
4 | */
5 |
6 | const enum EVENT_ID {
7 | PAUSE = 0,
8 | AIRCRAFT_LOADED = 1,
9 | FRAME = 2,
10 | NEW_WEATHER_MODE = 3,
11 | }
12 |
13 | open('My app', Protocol.FSX_SP2)
14 | .then(({ recvOpen, handle }) => {
15 | console.log('Connected: ', recvOpen);
16 |
17 | handle.subscribeToSystemEvent(EVENT_ID.PAUSE, 'Pause');
18 | handle.subscribeToSystemEvent(
19 | EVENT_ID.AIRCRAFT_LOADED,
20 | 'AircraftLoaded'
21 | );
22 | handle.subscribeToSystemEvent(EVENT_ID.FRAME, 'Frame');
23 | handle.subscribeToSystemEvent(
24 | EVENT_ID.NEW_WEATHER_MODE,
25 | 'WeatherModeChanged'
26 | );
27 |
28 | handle.on('event', (recvEvent) => {
29 | switch (recvEvent.clientEventId) {
30 | case EVENT_ID.PAUSE:
31 | console.log(recvEvent.data === 1 ? 'Paused' : 'Unpaused');
32 | break;
33 | case EVENT_ID.AIRCRAFT_LOADED:
34 | break;
35 | }
36 | });
37 |
38 | handle.on('eventFilename', (recvEventFilename) => {
39 | console.log('New aircraft:', recvEventFilename.fileName);
40 | });
41 |
42 | handle.on('eventWeatherMode', (recvWeatherMode) => {
43 | console.log('New weather mode:', recvWeatherMode.mode);
44 | });
45 |
46 | handle.on('eventFrame', (recvEventFrame) => {
47 | // console.log('Framerate:', recvEventFrame.frameRate);
48 | });
49 | })
50 | .catch((error) => {
51 | console.log('Failed to connect', error);
52 | });
53 |
--------------------------------------------------------------------------------
/samples/typescript/trafficRadar/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Clouds
8 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/samples/typescript/trafficRadar/trafficRadar.ts:
--------------------------------------------------------------------------------
1 | import {
2 | ConnectionHandle,
3 | open,
4 | Protocol,
5 | RawBuffer,
6 | SimConnectConstants,
7 | SimConnectDataType,
8 | SimConnectPeriod,
9 | SimObjectType,
10 | } from '../../../dist';
11 | import { client } from './websocketServer';
12 |
13 | /**
14 | * Lists all aircraft within a 10 km radius
15 | */
16 |
17 | const enum DefinitionID {
18 | AIRCRAFT_DETAILS,
19 | }
20 |
21 | const enum RequestID {
22 | NEARBY_AIRCRAFT,
23 | MY_POSITION,
24 | }
25 |
26 | open('My app', Protocol.FSX_SP2)
27 | .then(({ recvOpen, handle }) => {
28 | console.log('Connected:', recvOpen);
29 |
30 | registerAircraftDetailsDefinition(handle);
31 | handle.requestDataOnSimObject(
32 | RequestID.MY_POSITION,
33 | DefinitionID.AIRCRAFT_DETAILS,
34 | SimConnectConstants.OBJECT_ID_USER,
35 | SimConnectPeriod.SECOND
36 | );
37 |
38 | handle.on('simObjectData', recvSimObjectData => {
39 | if (recvSimObjectData.requestID === RequestID.MY_POSITION) {
40 | const aircraftDetails = readAircraftPosition(recvSimObjectData.data);
41 | client?.send(
42 | JSON.stringify({
43 | type: 'myPosition',
44 | data: aircraftDetails,
45 | })
46 | );
47 |
48 | // Request info about all aircraft in a 10 NM radius
49 | handle.requestDataOnSimObjectType(
50 | RequestID.NEARBY_AIRCRAFT,
51 | DefinitionID.AIRCRAFT_DETAILS,
52 | 10000,
53 | SimObjectType.AIRCRAFT
54 | );
55 | }
56 | });
57 |
58 | handle.on('simObjectDataByType', recvSimObjectData => {
59 | if (recvSimObjectData.requestID === RequestID.NEARBY_AIRCRAFT) {
60 | if (recvSimObjectData.outOf === 0) return; // There are no sim objects nearby
61 |
62 | const aircraftDetails = readAircraftPosition(recvSimObjectData.data);
63 | client?.send(
64 | JSON.stringify({
65 | type: 'traffic',
66 | data: aircraftDetails,
67 | })
68 | );
69 | }
70 | });
71 | })
72 | .catch(error => {
73 | console.log('Failed to connect', error);
74 | });
75 |
76 | function registerAircraftDetailsDefinition(handle: ConnectionHandle) {
77 | handle.addToDataDefinition(
78 | DefinitionID.AIRCRAFT_DETAILS,
79 | 'Plane Latitude',
80 | 'degrees',
81 | SimConnectDataType.FLOAT64
82 | );
83 | handle.addToDataDefinition(
84 | DefinitionID.AIRCRAFT_DETAILS,
85 | 'Plane Longitude',
86 | 'degrees',
87 | SimConnectDataType.FLOAT64
88 | );
89 | handle.addToDataDefinition(
90 | DefinitionID.AIRCRAFT_DETAILS,
91 | 'Indicated Altitude',
92 | 'feet',
93 | SimConnectDataType.INT32
94 | );
95 | handle.addToDataDefinition(
96 | DefinitionID.AIRCRAFT_DETAILS,
97 | 'TITLE',
98 | null,
99 | SimConnectDataType.STRING128
100 | );
101 | }
102 |
103 | function readAircraftPosition(rawBuffer: RawBuffer) {
104 | return {
105 | // Read order is important
106 | lat: rawBuffer.readFloat64(),
107 | lng: rawBuffer.readFloat64(),
108 | altitude: rawBuffer.readInt32(),
109 | title: rawBuffer.readString(128),
110 | };
111 | }
112 |
--------------------------------------------------------------------------------
/samples/typescript/trafficRadar/websocketServer.ts:
--------------------------------------------------------------------------------
1 | import childProcess = require('child_process');
2 |
3 | import WebSocketServer = require('ws');
4 | /**
5 | * Starts a websocket server and opens the default browser
6 | */
7 |
8 | const wss = new WebSocketServer({
9 | port: 8080,
10 | });
11 |
12 | let client: WebSocket | undefined;
13 |
14 | wss.on('connection', (ws: WebSocket) => {
15 | console.log('WebSocket client connected');
16 | client = ws;
17 | });
18 |
19 | const command =
20 | process.platform === 'darwin'
21 | ? 'open'
22 | : process.platform === 'win32'
23 | ? 'start'
24 | : 'xdg-open';
25 |
26 | childProcess.exec(`${command} file:///${__dirname}/index.html`);
27 |
28 | export { client };
29 |
--------------------------------------------------------------------------------
/samples/typescript/wxradar/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Clouds
8 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/samples/typescript/wxradar/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | open,
3 | Protocol,
4 | readLatLonAlt,
5 | RecvCloudState,
6 | SimConnectConstants,
7 | SimConnectDataType,
8 | SimConnectPeriod,
9 | } from '../../../dist';
10 |
11 | const WebSocketServer = require('ws').Server;
12 |
13 | /**
14 | * Starts a websocket server, opens a browser and renders
15 | * surrounding clouds on a canvas.
16 | */
17 |
18 | const wss = new WebSocketServer({
19 | port: 8080,
20 | });
21 |
22 | let client: WebSocket | undefined;
23 |
24 | wss.on('connection', (ws: WebSocket) => {
25 | console.log('WebSocket client connected');
26 | client = ws;
27 | });
28 |
29 | const startCommand =
30 | process.platform === 'darwin'
31 | ? 'open'
32 | : process.platform === 'win32'
33 | ? 'start'
34 | : 'xdg-open';
35 |
36 | require('child_process').exec(
37 | `${startCommand} file:///${__dirname}/index.html`
38 | );
39 |
40 | // SimConnect client ////////////////////
41 |
42 | enum DefinitionID {
43 | POSITION,
44 | }
45 |
46 | enum RequestID {
47 | POSITION,
48 | CLOUD_STATE,
49 | }
50 |
51 | const RANGE = 0.5; // Degrees of lat/lng
52 | const ALT_RANGE = 1000; // Feet
53 |
54 | open('Clouds example', Protocol.FSX_SP2)
55 | .then(({ recvOpen, handle }) => {
56 | console.log(recvOpen);
57 | handle.addToDataDefinition(
58 | DefinitionID.POSITION,
59 | 'STRUCT LATLONALT',
60 | null,
61 | SimConnectDataType.LATLONALT
62 | );
63 | handle.requestDataOnSimObject(
64 | RequestID.POSITION,
65 | DefinitionID.POSITION,
66 | SimConnectConstants.OBJECT_ID_USER,
67 | SimConnectPeriod.SECOND
68 | );
69 |
70 | handle.on('cloudState', (cloudState: RecvCloudState) => {
71 | client?.send(JSON.stringify(cloudState.data));
72 | });
73 |
74 | handle.on('simObjectData', (data) => {
75 | if (data.requestID === RequestID.POSITION) {
76 | const pos = readLatLonAlt(data.data);
77 | console.log(pos);
78 | const altFt = pos.altitude * 3.2808;
79 |
80 | handle.weatherRequestCloudState(
81 | RequestID.CLOUD_STATE,
82 | pos.latitude - RANGE,
83 | pos.longitude - RANGE,
84 | altFt - ALT_RANGE,
85 | pos.latitude + RANGE,
86 | pos.longitude + RANGE,
87 | altFt + ALT_RANGE
88 | );
89 | }
90 | });
91 | })
92 | .catch((error) => {
93 | console.log('Failed to connect', error);
94 | });
95 |
--------------------------------------------------------------------------------
/src/RawBuffer.ts:
--------------------------------------------------------------------------------
1 | import ByteBuffer = require('bytebuffer');
2 |
3 | class RawBuffer {
4 | private readonly buffer: ByteBuffer;
5 |
6 | constructor(b: Buffer | number) {
7 | if (typeof b === 'number') {
8 | this.buffer = ByteBuffer.allocate(b).LE(true);
9 | } else {
10 | this.buffer = ByteBuffer.wrap(b).LE(true);
11 | }
12 | }
13 |
14 | clear() {
15 | this.buffer.clear();
16 | }
17 |
18 | setOffset(offset: number) {
19 | this.buffer.offset = offset;
20 | }
21 |
22 | getOffset(): number {
23 | return this.buffer.offset;
24 | }
25 |
26 | getBuffer(): Buffer {
27 | this.buffer.flip();
28 | return this.buffer.toBuffer(true);
29 | }
30 |
31 | write(bytes: Buffer) {
32 | this.buffer.append(bytes);
33 | }
34 |
35 | writeByte(byte: number) {
36 | this.buffer.writeByte(byte);
37 | }
38 |
39 | readBytes(length: number): Buffer {
40 | const bytes = this.buffer.readBytes(length).copy();
41 | return bytes.toBuffer();
42 | }
43 |
44 | readInt16(): number {
45 | return this.buffer.readInt16();
46 | }
47 |
48 | readInt32(): number {
49 | return this.buffer.readInt32();
50 | }
51 |
52 | readUint32(): number {
53 | return this.buffer.readUint32();
54 | }
55 |
56 | writeInt16(value: number, offset?: number) {
57 | this.buffer.writeInt16(value, offset);
58 | }
59 |
60 | /** @deprecated use readInt32() instead */
61 | readInt = this.readInt32;
62 |
63 | writeInt32(value: number, offset?: number) {
64 | this.buffer.writeInt32(value, offset);
65 | }
66 |
67 | /** @deprecated use writeInt32() instead */
68 | writeInt = this.writeInt32;
69 |
70 | readInt64(): number {
71 | return this.buffer.readInt64().toNumber();
72 | }
73 |
74 | readUint64(): bigint {
75 | return BigInt(this.buffer.readUint64().toString(10));
76 | }
77 |
78 | /** @deprecated use readInt64() instead */
79 | readLong = this.readInt64;
80 |
81 | writeInt64(value: number) {
82 | this.buffer.writeInt64(value);
83 | }
84 |
85 | writeUint32(value: number, offset?: number) {
86 | this.buffer.writeUint64(value, offset);
87 | }
88 |
89 | writeUint64(value: bigint, offset?: number) {
90 | const buffer = Buffer.alloc(8);
91 | buffer.writeBigUint64LE(value);
92 | this.buffer.append(buffer, undefined, offset);
93 | }
94 |
95 | /** @deprecated use writeInt64() instead */
96 | writeLong = this.writeInt64;
97 |
98 | readFloat32(): number {
99 | return this.buffer.readFloat32();
100 | }
101 |
102 | /** @deprecated use readFloat32() instead */
103 | readFloat = this.readFloat32;
104 |
105 | writeFloat32(value: number) {
106 | this.buffer.writeFloat32(value);
107 | }
108 |
109 | /** @deprecated use writeFloat32() instead */
110 | writeFloat = this.writeFloat32;
111 |
112 | readFloat64() {
113 | return this.buffer.readFloat64();
114 | }
115 |
116 | /** @deprecated use readFloat64() instead */
117 | readDouble = this.readFloat64;
118 |
119 | writeFloat64(value: number) {
120 | this.buffer.writeFloat64(value);
121 | }
122 |
123 | /** @deprecated use writeFloat64() instead */
124 | writeDouble = this.writeFloat64;
125 |
126 | writeString(value: string, fixedLength?: number) {
127 | putString(this.buffer, value, fixedLength || value.length);
128 | }
129 |
130 | readString8() {
131 | return makeString(this.buffer, 8);
132 | }
133 |
134 | writeString8(value: string) {
135 | putString(this.buffer, value, 8);
136 | }
137 |
138 | writeString30(value: string) {
139 | putString(this.buffer, value, 30);
140 | }
141 |
142 | readString32() {
143 | return makeString(this.buffer, 32);
144 | }
145 |
146 | writeString32(value: string) {
147 | putString(this.buffer, value, 32);
148 | }
149 |
150 | readString64() {
151 | return makeString(this.buffer, 64);
152 | }
153 |
154 | writeString64(value: string) {
155 | putString(this.buffer, value, 64);
156 | }
157 |
158 | readString128() {
159 | return makeString(this.buffer, 128);
160 | }
161 |
162 | writeString128(value: string) {
163 | putString(this.buffer, value, 128);
164 | }
165 |
166 | readString256() {
167 | return makeString(this.buffer, 256);
168 | }
169 |
170 | writeString256(value: string | null) {
171 | putString(this.buffer, value, 256);
172 | }
173 |
174 | readString260() {
175 | return makeString(this.buffer, 260);
176 | }
177 |
178 | writeString260(value: string) {
179 | putString(this.buffer, value, 260);
180 | }
181 |
182 | readStringV() {
183 | let bytesRead = 0;
184 | let strLen = 0;
185 | let endFound = false;
186 | while (this.buffer.offset < this.buffer.limit) {
187 | const currentByte = this.buffer.readByte();
188 | bytesRead++;
189 | if (endFound && currentByte !== 0) {
190 | break; // Reached beginning of new value
191 | } else if (currentByte === 0) {
192 | endFound = true;
193 | }
194 | strLen++;
195 | }
196 | // Reset offset so we can read the same bytes later
197 | this.buffer.offset -= bytesRead;
198 | return makeString(this.buffer, strLen);
199 | }
200 |
201 | readString(length: number) {
202 | return makeString(this.buffer, length);
203 | }
204 |
205 | remaining() {
206 | return this.buffer.remaining();
207 | }
208 | }
209 |
210 | function makeString(bf: ByteBuffer, expectedLength: number) {
211 | const content = bf.readCString(bf.offset);
212 | bf.skip(expectedLength);
213 | return content.string;
214 | }
215 |
216 | function putString(bf: ByteBuffer, s: string | null, fixed: number) {
217 | const value = s === null ? '' : s;
218 | const bytes = Buffer.from(value, 'utf-8');
219 | bf.append(bytes);
220 | if (bytes.length < fixed) {
221 | for (let i = 0; i < fixed - bytes.length; i++) {
222 | bf.writeByte(0x00);
223 | }
224 | }
225 | }
226 |
227 | export { RawBuffer };
228 |
--------------------------------------------------------------------------------
/src/SimConnectConstants.ts:
--------------------------------------------------------------------------------
1 | // Import the necessary type
2 | import { ObjectId } from './Types';
3 |
4 | // eslint-disable-next-line @typescript-eslint/no-namespace
5 | export namespace SimConnectConstants {
6 | /** Specify the user aircraft in {@link SimConnectRecvEvents#simObjectDataByType} and {@link SimConnectConnection.requestDataOnSimObject} */
7 | export const OBJECT_ID_USER: ObjectId = 0 as ObjectId;
8 |
9 | export const UNUSED = 0xffffffff;
10 |
11 | /** current (and max) protocol version supported by this implementation of jsimconnect */
12 | export const PROTO_VERSION = 4;
13 |
14 | export const RECEIVE_SIZE = 65536;
15 |
16 | /** indicates that the value for the camera should be taken unmodified from the reference point. */
17 | // TODO: CAMERA_IGNORE_FIELD : Float.MAX_VALUE, //Used to tell the Camera API to NOT modify the value in this part of the argument.
18 |
19 | // Weather observations Metar strings
20 | export const MAX_METAR_LENGTH = 2000;
21 |
22 | // Maximum thermal size is 100 km.
23 | export const MAX_THERMAL_SIZE = 100000;
24 | export const MAX_THERMAL_RATE = 1000;
25 |
26 | // SIMCONNECT_DATA_INITPOSITION.Airspeed
27 | /** The aircraft's design cruising speed. */
28 | export const INITPOSITION_AIRSPEED_CRUISE = -1;
29 | /** Maintain the current airspeed. */
30 | export const INITPOSITION_AIRSPEED_KEEP = -2;
31 |
32 | /** a MS Windows constant */
33 | export const MAX_PATH = 260;
34 |
35 | /** Specifies requested speed is valid. */
36 | export const WAYPOINT_SPEED_REQUESTED = 0x04;
37 | /** Specifies requested throttle percentage is valid. */
38 | export const WAYPOINT_THROTTLE_REQUESTED = 0x08;
39 | /** Specifies that the vertical should be calculated to reach the required speed when crossing the waypoint. */
40 | export const WAYPOINT_COMPUTE_VERTICAL_SPEED = 0x10;
41 | /** Specifies the altitude specified is AGL (above ground level). */
42 | export const WAYPOINT_ALTITUDE_IS_AGL = 0x20;
43 | /** Specifies the waypoint should be on the ground. Make sure this flag is set if the aircraft is to taxi to this point. */
44 | export const WAYPOINT_ON_GROUND = 0x100000;
45 | /** Specifies that the aircraft should back up to this waypoint. This is only valid on the first waypoint. */
46 | export const WAYPOINT_REVERSE = 0x200000;
47 | /** Specifies that the next waypoint is the first waypoint. This is only valid on the last waypoint. */
48 | export const WAYPOINT_WRAP_TO_FIRST = 0x400000;
49 |
50 | /** When subscribed to event MissionCompleted
*/
51 | export const MISSION_FAILED = 0;
52 | /** When subscribed to event MissionCompleted
*/
53 | export const MISSION_CRASHED = 1;
54 | /** When subscribed to event MissionCompleted
*/
55 | export const MISSION_SUCCEEDED = 2;
56 |
57 | /** When subscribed to event View
, 2D Panels in cockpit view */
58 | export const VIEW_SYSTEM_EVENT_DATA_COCKPIT_2D = 0x00000001;
59 | /** When subscribed to event View
, Virtual (3D) panels in cockpit view */
60 | export const VIEW_SYSTEM_EVENT_DATA_COCKPIT_VIRTUAL = 0x00000002;
61 | /** When subscribed to event View
, Orthogonal (Map) view */
62 | export const VIEW_SYSTEM_EVENT_DATA_ORTHOGONAL = 0x00000004;
63 |
64 | /** When subsribed to event Sound */
65 | export const SOUND_SYSTEM_EVENT_DATA_MASTER = 1;
66 |
67 | /** unknow group received */
68 | export const UNKNOWN_GROUP = 0xffffffff;
69 |
70 | /** automatically compute offset of the ClientData variable */
71 | export const CLIENTDATAOFFSET_AUTO = -1;
72 |
73 | /** Specifies that the user has selected the menu item. */
74 | export const TEXT_RESULT_MENU_SELECT_1 = 0;
75 | /** Specifies that the user has selected the menu item. */
76 | export const TEXT_RESULT_MENU_SELECT_2 = 1;
77 | /** Specifies that the user has selected the menu item. */
78 | export const TEXT_RESULT_MENU_SELECT_3 = 2;
79 | /** Specifies that the user has selected the menu item. */
80 | export const TEXT_RESULT_MENU_SELECT_4 = 3;
81 | /** Specifies that the user has selected the menu item. */
82 | export const TEXT_RESULT_MENU_SELECT_5 = 4;
83 | /** Specifies that the user has selected the menu item. */
84 | export const TEXT_RESULT_MENU_SELECT_6 = 5;
85 | /** Specifies that the user has selected the menu item. */
86 | export const TEXT_RESULT_MENU_SELECT_7 = 6;
87 | /** Specifies that the user has selected the menu item. */
88 | export const TEXT_RESULT_MENU_SELECT_8 = 7;
89 | /** Specifies that the user has selected the menu item. */
90 | export const TEXT_RESULT_MENU_SELECT_9 = 8;
91 | /** Specifies that the user has selected the menu item. */
92 | export const TEXT_RESULT_MENU_SELECT_10 = 9;
93 | /** Specifies that the menu or text identified by the EventID is now on display. */
94 | export const TEXT_RESULT_DISPLAYED = 0x00010000;
95 | /** Specifies that the menu or text identified by the EventID is waiting in a queue. */
96 | export const TEXT_RESULT_QUEUED = 0x00010001;
97 | /** Specifies that the menu or text identified by the EventID has been removed from the queue. */
98 | export const TEXT_RESULT_REMOVED = 0x00010002;
99 | /** Specifies that the menu or text identified by the EventID has been replaced in the queue. */
100 | export const TEXT_RESULT_REPLACED = 0x00010003;
101 | /** Specifies that the menu or text identified by the EventID has timed-out and is no longer on display. */
102 | export const TEXT_RESULT_TIMEOUT = 0x00010004;
103 |
104 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
105 | export const CLIENT_DATA_TYPE_INT8 = -1;
106 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
107 | export const CLIENT_DATA_TYPE_INT16 = -2;
108 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
109 | export const CLIENT_DATA_TYPE_INT32 = -3;
110 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
111 | export const CLIENT_DATA_TYPE_INT64 = -4;
112 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
113 | export const CLIENT_DATA_TYPE_FLOAT32 = -5;
114 | /** @see {@link SimConnectConnection.addToClientDataDefinition} */
115 | export const CLIENT_DATA_TYPE_FLOAT64 = -6;
116 | }
117 |
118 | module.exports = {
119 | SimConnectConstants,
120 | };
121 |
--------------------------------------------------------------------------------
/src/SimConnectPacketBuilder.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from './RawBuffer';
2 | import { Protocol } from './enums/Protocol';
3 |
4 | export class SimConnectPacketBuilder {
5 | private readonly packetContent: RawBuffer;
6 |
7 | constructor(packetTypeId: number, protocol: Protocol, packetDataBuffer?: RawBuffer) {
8 | packetDataBuffer?.clear(); // Prepare for new message
9 | /**
10 | * Packet header content (16 bytes):
11 | * 0-3 packet size (set later)
12 | * 4-7 protocol
13 | * 8-11 packet type / SimConnect function
14 | * 12-15 packet id (set later)
15 | */
16 | this.packetContent = packetDataBuffer || new RawBuffer(256);
17 | this.packetContent.writeInt32(protocol, 4);
18 | this.packetContent.writeInt32(0xf0000000 | packetTypeId, 8);
19 | this.packetContent.setOffset(16);
20 | }
21 |
22 | putFloat32(value: number) {
23 | this.packetContent.writeFloat32(value);
24 | return this;
25 | }
26 |
27 | putFloat64(value: number) {
28 | this.packetContent.writeFloat64(value);
29 | return this;
30 | }
31 |
32 | putString(value: string, fixedLength?: number) {
33 | this.packetContent.writeString(value, fixedLength);
34 | return this;
35 | }
36 |
37 | putString256(value: string | null) {
38 | this.packetContent.writeString256(value);
39 | return this;
40 | }
41 |
42 | putInt16(value: number, offset?: number) {
43 | this.packetContent.writeInt16(value, offset);
44 | return this;
45 | }
46 |
47 | putInt32(value: number, offset?: number) {
48 | this.packetContent.writeInt32(value, offset);
49 | return this;
50 | }
51 |
52 | putUint32(value: number, offset?: number) {
53 | this.packetContent.writeUint32(value, offset);
54 | return this;
55 | }
56 |
57 | putUint64(value: bigint, offset?: number) {
58 | this.packetContent.writeUint64(value, offset);
59 | return this;
60 | }
61 |
62 | putByte(value: number) {
63 | this.packetContent.writeByte(value);
64 | return this;
65 | }
66 |
67 | putBytes(value: Buffer) {
68 | this.packetContent.write(value);
69 | return this;
70 | }
71 |
72 | getRawBuffer(): RawBuffer {
73 | return this.packetContent;
74 | }
75 |
76 | /**
77 | * Finalize
78 | * @param sendId - of packet (can be used to identify packet when exception event occurs)
79 | */
80 | build(sendId: number): Buffer {
81 | const packetSize = this.packetContent.getOffset();
82 |
83 | // Finish packet header
84 | this.packetContent.writeInt32(packetSize, 0);
85 | this.packetContent.writeInt32(sendId, 12);
86 |
87 | return this.packetContent.getBuffer();
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/SimConnectSocket.ts:
--------------------------------------------------------------------------------
1 | import { Socket } from 'net';
2 | import { Duplex } from 'stream';
3 | import { RawBuffer } from './RawBuffer';
4 | import { ConnectionParameters } from './connectionParameters';
5 |
6 | const HEADER_LENGTH = 4;
7 |
8 | enum RecvID {
9 | ID_NULL,
10 | ID_EXCEPTION,
11 | ID_OPEN,
12 | ID_QUIT,
13 | ID_EVENT,
14 | ID_EVENT_OBJECT_ADDREMOVE,
15 | ID_EVENT_FILENAME,
16 | ID_EVENT_FRAME,
17 | ID_SIMOBJECT_DATA,
18 | ID_SIMOBJECT_DATA_BYTYPE,
19 | ID_WEATHER_OBSERVATION,
20 | ID_CLOUD_STATE,
21 | ID_ASSIGNED_OBJECT_ID,
22 | ID_RESERVED_KEY,
23 | ID_CUSTOM_ACTION,
24 | ID_SYSTEM_STATE,
25 | ID_CLIENT_DATA,
26 | ID_EVENT_WEATHER_MODE,
27 | ID_AIRPORT_LIST,
28 | ID_VOR_LIST,
29 | ID_NDB_LIST,
30 | ID_WAYPOINT_LIST,
31 | ID_EVENT_MULTIPLAYER_SERVER_STARTED,
32 | ID_EVENT_MULTIPLAYER_CLIENT_STARTED,
33 | ID_EVENT_MULTIPLAYER_SESSION_ENDED,
34 | ID_EVENT_RACE_END,
35 | ID_EVENT_RACE_LAP,
36 | // KittyHawk:
37 | ID_EVENT_EX1,
38 | ID_FACILITY_DATA,
39 | ID_FACILITY_DATA_END,
40 | ID_FACILITY_MINIMAL_LIST,
41 | ID_JETWAY_DATA,
42 | ID_CONTROLLERS_LIST,
43 | ID_ACTION_CALLBACK,
44 | ID_ENUMERATE_INPUT_EVENTS,
45 | ID_GET_INPUT_EVENT,
46 | ID_SUBSCRIBE_INPUT_EVENT,
47 | ID_ENUMERATE_INPUT_EVENT_PARAMS,
48 | }
49 |
50 | interface SimConnectMessage {
51 | protocolVersion: number;
52 | packetTypeId: RecvID;
53 | data: RawBuffer;
54 | }
55 |
56 | /**
57 | * For connecting, reading and writing to the SimConnect server.
58 | * The emitted "data"-event contains a SimConnectMessage-object.
59 | * Inspired by https://www.derpturkey.com/extending-tcp-socket-in-node-js/
60 | */
61 | class SimConnectSocket extends Duplex {
62 | private readonly _socket: Socket;
63 |
64 | private _dataBuffer: Buffer;
65 |
66 | private _readingPaused: boolean;
67 |
68 | constructor() {
69 | super({ objectMode: true });
70 | this._dataBuffer = Buffer.from([]);
71 | this._readingPaused = false;
72 | this._socket = new Socket();
73 | this._socket.setNoDelay(false);
74 | this._wrapSocket();
75 | }
76 |
77 | connect(address: ConnectionParameters) {
78 | switch (address.type) {
79 | case 'pipe':
80 | this._socket.connect(address.address);
81 | break;
82 | case 'ipv4':
83 | this._socket.connect(address.port, address.host);
84 | break;
85 | default:
86 | throw Error('Unsupported address type. Must be "ipv4" or "pipe"');
87 | }
88 | }
89 |
90 | close() {
91 | this._socket.destroy();
92 | }
93 |
94 | private _wrapSocket() {
95 | this._socket.on('close', hadError => this.emit('close', hadError));
96 | this._socket.on('connect', () => this.emit('connect'));
97 | this._socket.on('drain', () => this.emit('drain'));
98 | this._socket.on('end', () => this.emit('end'));
99 | this._socket.on('error', err => this.emit('error', err));
100 | this._socket.on('lookup', (err, address, family, host) => this.emit('lookup', err, address, family, host)); // prettier-ignore
101 | this._socket.on('ready', () => this.emit('ready'));
102 | this._socket.on('timeout', () => this.emit('timeout'));
103 |
104 | this._socket.on('readable', this._onReadable.bind(this));
105 | }
106 |
107 | private _onReadable() {
108 | while (!this._readingPaused) {
109 | const chunk: Buffer | null = this._socket.read();
110 | if (chunk === null) break;
111 |
112 | this._dataBuffer = Buffer.concat([this._dataBuffer, chunk]);
113 |
114 | while (this._dataBuffer.length >= HEADER_LENGTH) {
115 | const totalMessageSize: number = this._dataBuffer.readInt32LE(0);
116 |
117 | if (this._dataBuffer.length >= totalMessageSize) {
118 | const messageBody: Buffer = this._dataBuffer.slice(
119 | HEADER_LENGTH,
120 | totalMessageSize
121 | );
122 |
123 | const simConnectMessage: SimConnectMessage = {
124 | protocolVersion: messageBody.readInt32LE(0),
125 | packetTypeId: messageBody.readInt32LE(4),
126 | data: new RawBuffer(messageBody.slice(8)),
127 | };
128 |
129 | const pushOk = this.push(simConnectMessage);
130 |
131 | if (!pushOk) {
132 | this._readingPaused = true;
133 | break; // Pause reading if consumer is slow
134 | }
135 |
136 | this._dataBuffer = this._dataBuffer.slice(totalMessageSize); // Remove processed message from the buffer
137 | } else {
138 | break; // Not enough data for a complete SimConnect message, break out of the loop
139 | }
140 | }
141 | }
142 | }
143 |
144 | _read() {
145 | this._readingPaused = false;
146 | setImmediate(this._onReadable.bind(this));
147 | }
148 |
149 | _write(data: Buffer, encoding: BufferEncoding, cb: (error?: Error | null) => void) {
150 | this._socket.write(data, encoding, cb);
151 | }
152 | }
153 |
154 | export { SimConnectSocket, RecvID, SimConnectMessage, ConnectionParameters };
155 |
--------------------------------------------------------------------------------
/src/Types.ts:
--------------------------------------------------------------------------------
1 | export type DataRequestId = number;
2 | export type DataDefinitionId = number;
3 | export type ClientDataDefinitionId = number;
4 | export type ObjectId = number;
5 | export type ClientEventId = number;
6 | export type NotificationGroupId = number;
7 | export type InputGroupId = number;
8 | export type ClientDataId = number;
9 |
--------------------------------------------------------------------------------
/src/connectionParameters.ts:
--------------------------------------------------------------------------------
1 | import * as Path from 'path';
2 | import * as os from 'os';
3 | import debug from 'debug';
4 | import { readIniFile } from './utils/ini';
5 | import { readRegistryValue } from './utils/registry';
6 | import { checkIfNamedPipeExist } from './utils/network';
7 |
8 | const logger = debug('node-simconnect');
9 |
10 | async function findSimConnectPortIPv4(): Promise {
11 | try {
12 | const port = await readRegistryValue(
13 | 'HKCU\\Software\\Microsoft\\Microsoft Games\\Flight Simulator',
14 | 'SimConnect_Port_IPv4'
15 | );
16 | if (!port) {
17 | throw new Error('Could not find SimConnect_Port_IPv4 in the Windows registry');
18 | }
19 | return parseInt(port, 10);
20 | } catch {
21 | return 2048;
22 | }
23 | }
24 |
25 | export type ConnectionParameters =
26 | | { type: 'pipe'; address: string }
27 | | { type: 'ipv4'; host: string; port: number };
28 |
29 | async function readNetworkConfigFromSimConnectCfg(
30 | folderPath: string,
31 | index?: number
32 | ): Promise {
33 | const filePath = Path.join(folderPath, 'SimConnect.cfg');
34 |
35 | let fullCfg;
36 | try {
37 | // SimConnect.cfg uses the INI fileformat
38 | fullCfg = await readIniFile(filePath);
39 | } catch (e) {
40 | logger('Could not read SimConnect.cfg due to to the following error:', e);
41 | return undefined;
42 | }
43 |
44 | if (fullCfg.SimConnect === undefined) {
45 | throw new Error(`Invalid SimConnect.cfg file: ${filePath}`);
46 | }
47 |
48 | const indexStr = index !== undefined ? index.toString(10) : '0';
49 | const cfg = fullCfg.SimConnect[indexStr] ? fullCfg.SimConnect[indexStr] : fullCfg.SimConnect;
50 |
51 | if (cfg.Protocol === undefined || cfg.Address === undefined || cfg.Port === undefined) {
52 | throw new Error(`The loaded SimConnect.cfg (${filePath}) is missing required parameters.`);
53 | } else if (cfg.Protocol && cfg.Protocol.toUpperCase() !== 'IPV4') {
54 | throw new Error('Only the Ipv4 protocol is supported at the moment');
55 | }
56 |
57 | return {
58 | type: 'ipv4',
59 | host: cfg.Address,
60 | port: parseInt(cfg.Port, 10),
61 | };
62 | }
63 |
64 | async function autodetectServerAddress(cfgIndex?: number): Promise {
65 | // Check for SimConnect.cfg in current dir
66 | const localConfig = await readNetworkConfigFromSimConnectCfg(process.cwd(), cfgIndex);
67 | if (localConfig) return localConfig;
68 |
69 | const homeConfig = await readNetworkConfigFromSimConnectCfg(os.homedir(), cfgIndex);
70 | if (homeConfig) return homeConfig;
71 |
72 | if (cfgIndex !== undefined) {
73 | throw new Error(
74 | `No SimConnect.cfg file containing the given config index ${cfgIndex} was found`
75 | );
76 | }
77 |
78 | // Check if named pipe exist
79 | const PIPE = '\\\\.\\pipe\\Microsoft Flight Simulator\\SimConnect';
80 | const msfsSimconnectPipeOk = await checkIfNamedPipeExist(PIPE);
81 | if (msfsSimconnectPipeOk) {
82 | return { type: 'pipe', address: PIPE };
83 | }
84 |
85 | // Read port number from Windows registry
86 | const ipv4port = await findSimConnectPortIPv4();
87 | return { type: 'ipv4', host: 'localhost', port: ipv4port };
88 | }
89 |
90 | export { autodetectServerAddress };
91 |
--------------------------------------------------------------------------------
/src/datastructures/ControllerItem.ts:
--------------------------------------------------------------------------------
1 | import { VersionBaseType } from '../dto';
2 | import { RawBuffer } from '../RawBuffer';
3 |
4 | export class ControllerItem {
5 | deviceName: string;
6 |
7 | deviceId: number;
8 |
9 | productId: number;
10 |
11 | compositeId: number;
12 |
13 | hardwareVersion: VersionBaseType;
14 |
15 | constructor(data: RawBuffer) {
16 | this.deviceName = data.readString256();
17 | this.deviceId = data.readInt32();
18 | this.productId = data.readInt32();
19 | this.compositeId = data.readInt32();
20 | this.hardwareVersion = new VersionBaseType(data);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/datastructures/FacilityAirport.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class FacilityAirport {
4 | icao: string;
5 |
6 | region: string;
7 |
8 | latitude: number;
9 |
10 | longitude: number;
11 |
12 | altitude: number;
13 |
14 | constructor(data: RawBuffer) {
15 | this.icao = data.readString(6);
16 | this.region = data.readString(3);
17 | this.latitude = data.readFloat64();
18 | this.longitude = data.readFloat64();
19 | this.altitude = data.readFloat64();
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/datastructures/FacilityMinimal.ts:
--------------------------------------------------------------------------------
1 | import { Icao, LatLonAlt, readLatLonAlt } from '../dto';
2 | import { RawBuffer } from '../RawBuffer';
3 |
4 | export class FacilityMinimal {
5 | icao: Icao;
6 |
7 | latLonAlt: LatLonAlt;
8 |
9 | constructor(data: RawBuffer) {
10 | this.icao = new Icao(data);
11 | this.latLonAlt = readLatLonAlt(data);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/datastructures/FacilityNDB.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityWaypoint } from './FacilityWaypoint';
3 |
4 | export class FacilityNDB extends FacilityWaypoint {
5 | frequency: number;
6 |
7 | constructor(data: RawBuffer) {
8 | super(data);
9 | this.frequency = data.readInt32();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastructures/FacilityVOR.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityNDB } from './FacilityNDB';
3 |
4 | export class FacilityVOR extends FacilityNDB {
5 | public static HAS_NAV_SIGNAL = 0x00000001;
6 |
7 | public static HAS_LOCALIZER = 0x00000002;
8 |
9 | public static HAS_GLIDE_SLOPE = 0x00000004;
10 |
11 | public static HAS_DME = 0x00000008;
12 |
13 | flags: number;
14 |
15 | localizer: number;
16 |
17 | glideLat: number;
18 |
19 | glideLon: number;
20 |
21 | glideAlt: number;
22 |
23 | glideSlipeAngle: number;
24 |
25 | constructor(data: RawBuffer) {
26 | super(data);
27 | this.flags = data.readInt32();
28 | this.localizer = data.readFloat32();
29 | this.glideLat = data.readFloat64();
30 | this.glideLon = data.readFloat64();
31 | this.glideAlt = data.readFloat64();
32 | this.glideSlipeAngle = data.readFloat32();
33 | }
34 |
35 | hasFlag(flag: number): boolean {
36 | return (this.flags & flag) !== 0;
37 | }
38 |
39 | hasNavSignal(): boolean {
40 | return this.hasFlag(FacilityVOR.HAS_NAV_SIGNAL);
41 | }
42 |
43 | hasLocalizer(): boolean {
44 | return this.hasFlag(FacilityVOR.HAS_LOCALIZER);
45 | }
46 |
47 | hasGlideSlope(): boolean {
48 | return this.hasFlag(FacilityVOR.HAS_GLIDE_SLOPE);
49 | }
50 |
51 | hasDME(): boolean {
52 | return this.hasFlag(FacilityVOR.HAS_DME);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/datastructures/FacilityWaypoint.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityAirport } from './FacilityAirport';
3 |
4 | export class FacilityWaypoint extends FacilityAirport {
5 | magVar: number;
6 |
7 | constructor(data: RawBuffer) {
8 | super(data);
9 | this.magVar = data.readFloat32();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastructures/InputEventDescriptor.ts:
--------------------------------------------------------------------------------
1 | import { InputEventType } from '../enums/InputEventType';
2 | import { RawBuffer } from '../RawBuffer';
3 |
4 | export class InputEventDescriptor {
5 | name: string;
6 |
7 | inputEventIdHash: bigint;
8 |
9 | type: InputEventType;
10 |
11 | constructor(data: RawBuffer) {
12 | this.name = data.readString64();
13 | this.inputEventIdHash = data.readUint64();
14 | this.type = data.readUint32() as InputEventType;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/datastructures/JetwayData.ts:
--------------------------------------------------------------------------------
1 | import { LatLonAlt } from '../dto/LatLonAlt';
2 | import { PBH } from '../dto/PBH';
3 | import { JetwayStatus } from '../enums/JetwayStatus';
4 | import { XYZ } from '../dto/XYZ';
5 | import { ObjectId } from '../Types';
6 | import { RawBuffer } from '../RawBuffer';
7 | import { readLatLonAlt, readPBH, readXYZ } from '../dto/bufferHelpers';
8 |
9 | export class JetwayData {
10 | airportIcao: string;
11 |
12 | parkingIndex: number;
13 |
14 | latLngAlt: LatLonAlt;
15 |
16 | pbh: PBH;
17 |
18 | status: JetwayStatus;
19 |
20 | door: number;
21 |
22 | exitDoorRelativePos: XYZ;
23 |
24 | mainHandlePos: XYZ;
25 |
26 | secondaryHandle: XYZ;
27 |
28 | wheelGroundLock: XYZ;
29 |
30 | jetwayObjectId: ObjectId;
31 |
32 | attachedObjectId: ObjectId;
33 |
34 | constructor(data: RawBuffer) {
35 | this.airportIcao = data.readString(8);
36 | this.parkingIndex = data.readInt32();
37 | this.latLngAlt = readLatLonAlt(data);
38 | this.pbh = readPBH(data);
39 | this.status = data.readInt32();
40 | this.door = data.readInt32();
41 | this.exitDoorRelativePos = readXYZ(data);
42 | this.mainHandlePos = readXYZ(data);
43 | this.secondaryHandle = readXYZ(data);
44 | this.wheelGroundLock = readXYZ(data);
45 | this.jetwayObjectId = data.readUint32() as ObjectId;
46 | this.attachedObjectId = data.readUint32() as ObjectId;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/datastructures/VersionBaseType.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class VersionBaseType {
4 | major: number;
5 |
6 | minor: number;
7 |
8 | revision: number;
9 |
10 | build: number;
11 |
12 | constructor(data: RawBuffer) {
13 | this.major = data.readInt16();
14 | this.minor = data.readInt16();
15 | this.revision = data.readInt16();
16 | this.build = data.readInt16();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/datastructures/index.ts:
--------------------------------------------------------------------------------
1 | export * from './FacilityAirport';
2 | export * from './FacilityNDB';
3 | export * from './FacilityMinimal';
4 | export * from './FacilityVOR';
5 | export * from './FacilityWaypoint';
6 | export * from './ControllerItem';
7 | export * from './InputEventDescriptor';
8 | export * from './JetwayData';
9 |
--------------------------------------------------------------------------------
/src/dto/InitPosition.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | enum Airspeed {
6 | Keep = -2,
7 | Cruise = -1,
8 | }
9 |
10 | class InitPosition implements SimConnectData {
11 | latitude = 0;
12 |
13 | longitude = 0;
14 |
15 | altitude = 0;
16 |
17 | pitch = 0;
18 |
19 | bank = 0;
20 |
21 | heading = 0;
22 |
23 | onGround = false;
24 |
25 | airspeed: Airspeed | number = 0;
26 |
27 | readFrom(buffer: RawBuffer) {
28 | this.latitude = buffer.readFloat64();
29 | this.longitude = buffer.readFloat64();
30 | this.altitude = buffer.readFloat64();
31 | this.pitch = buffer.readFloat64();
32 | this.bank = buffer.readFloat64();
33 | this.heading = buffer.readFloat64();
34 | this.onGround = buffer.readInt32() !== 0;
35 | this.airspeed = buffer.readInt32();
36 | }
37 |
38 | writeTo(packetBuilder: SimConnectPacketBuilder) {
39 | packetBuilder
40 | .putFloat64(this.latitude)
41 | .putFloat64(this.longitude)
42 | .putFloat64(this.altitude)
43 | .putFloat64(this.pitch)
44 | .putFloat64(this.bank)
45 | .putFloat64(this.heading)
46 | .putInt32(this.onGround ? 1 : 0)
47 | .putInt32(this.airspeed);
48 | }
49 | }
50 |
51 | export { InitPosition, Airspeed };
52 |
--------------------------------------------------------------------------------
/src/dto/LatLonAlt.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | class LatLonAlt implements SimConnectData {
6 | latitude = 0;
7 |
8 | longitude = 0;
9 |
10 | altitude = 0;
11 |
12 | readFrom(buffer: RawBuffer) {
13 | this.latitude = buffer.readFloat64();
14 | this.longitude = buffer.readFloat64();
15 | this.altitude = buffer.readFloat64();
16 | }
17 |
18 | writeTo(packetBuilder: SimConnectPacketBuilder) {
19 | packetBuilder
20 | .putFloat64(this.latitude)
21 | .putFloat64(this.longitude)
22 | .putFloat64(this.altitude);
23 | }
24 | }
25 |
26 | export { LatLonAlt };
27 |
--------------------------------------------------------------------------------
/src/dto/MarkerState.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | class MarkerState implements SimConnectData {
6 | markerName = '';
7 |
8 | markerState = false;
9 |
10 | readFrom(buffer: RawBuffer) {
11 | this.markerName = buffer.readString64();
12 | this.markerState = buffer.readInt32() !== 0;
13 | }
14 |
15 | writeTo(packetBuilder: SimConnectPacketBuilder) {
16 | packetBuilder //
17 | .putString(this.markerName, 64)
18 | .putInt32(this.markerState ? 1 : 0);
19 | }
20 | }
21 |
22 | export { MarkerState };
23 |
--------------------------------------------------------------------------------
/src/dto/PBH.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | class PBH implements SimConnectData {
6 | pitch = 0;
7 |
8 | bank = 0;
9 |
10 | heading = 0;
11 |
12 | readFrom(buffer: RawBuffer) {
13 | this.pitch = buffer.readFloat32();
14 | this.bank = buffer.readFloat32();
15 | this.heading = buffer.readFloat32();
16 | }
17 |
18 | writeTo(packetBuilder: SimConnectPacketBuilder) {
19 | packetBuilder //
20 | .putFloat32(this.pitch)
21 | .putFloat32(this.bank)
22 | .putFloat32(this.heading);
23 | }
24 | }
25 |
26 | export { PBH };
27 |
--------------------------------------------------------------------------------
/src/dto/SimConnectData.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
3 |
4 | interface SimConnectData {
5 | readonly readFrom: (buffer: RawBuffer) => void;
6 | readonly writeTo: (packetBuilder: SimConnectPacketBuilder) => void;
7 | }
8 |
9 | export { SimConnectData };
10 |
--------------------------------------------------------------------------------
/src/dto/Waypoint.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | class Waypoint implements SimConnectData {
6 | /** Latitude of waypoint, in degrees */
7 | latitude = 0;
8 |
9 | /** Longitude of waypoint, in degrees */
10 | longitude = 0;
11 |
12 | /** Altitude of waypoint, in feet */
13 | altitude = 0;
14 |
15 | /** flags of waypoints
16 | * @see
17 | * - {@link SimConnectConstants.WAYPOINT_ON_GROUND}
18 | * - {@link SimConnectConstants.WAYPOINT_REVERSE}
19 | * - {@link SimConnectConstants.WAYPOINT_ALTITUDE_IS_AGL}
20 | * - {@link SimConnectConstants.WAYPOINT_COMPUTE_VERTICAL_SPEED}
21 | * - {@link SimConnectConstants.WAYPOINT_SPEED_REQUESTED}
22 | * - {@link SimConnectConstants.WAYPOINT_THROTTLE_REQUESTED}
23 | */
24 | flags = 0;
25 |
26 | /** Speed, in kots. {@link SimConnectConstants.WAYPOINT_SPEED_REQUESTED} must be on */
27 | speed = 0;
28 |
29 | /** Throttle, in percent {@link SimConnectConstants.WAYPOINT_THROTTLE_REQUESTED} must be on */
30 | throttle = 0;
31 |
32 | readFrom(buffer: RawBuffer) {
33 | this.latitude = buffer.readFloat64();
34 | this.longitude = buffer.readFloat64();
35 | this.altitude = buffer.readFloat64();
36 | this.flags = buffer.readInt32();
37 | this.speed = buffer.readFloat64();
38 | this.throttle = buffer.readFloat64();
39 | }
40 |
41 | writeTo(packetBuilder: SimConnectPacketBuilder) {
42 | packetBuilder
43 | .putFloat64(this.latitude)
44 | .putFloat64(this.longitude)
45 | .putFloat64(this.altitude)
46 | .putInt32(this.flags)
47 | .putFloat64(this.speed)
48 | .putFloat64(this.throttle);
49 | }
50 | }
51 |
52 | export { Waypoint };
53 |
--------------------------------------------------------------------------------
/src/dto/XYZ.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectData } from './SimConnectData';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { SimConnectPacketBuilder } from '../SimConnectPacketBuilder';
4 |
5 | class XYZ implements SimConnectData {
6 | x = 0;
7 |
8 | y = 0;
9 |
10 | z = 0;
11 |
12 | readFrom(buffer: RawBuffer) {
13 | this.x = buffer.readFloat64();
14 | this.y = buffer.readFloat64();
15 | this.z = buffer.readFloat64();
16 | }
17 |
18 | writeTo(packetBuilder: SimConnectPacketBuilder) {
19 | packetBuilder //
20 | .putFloat64(this.x)
21 | .putFloat64(this.y)
22 | .putFloat64(this.z);
23 | }
24 | }
25 |
26 | export { XYZ };
27 |
--------------------------------------------------------------------------------
/src/dto/bufferHelpers.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { InitPosition } from './InitPosition';
3 | import { MarkerState } from './MarkerState';
4 | import { Waypoint } from './Waypoint';
5 | import { LatLonAlt } from './LatLonAlt';
6 | import { XYZ } from './XYZ';
7 | import { SimConnectData } from './SimConnectData';
8 | import { PBH } from './PBH';
9 |
10 | function readInitPosition(dataWrapper: RawBuffer): InitPosition {
11 | return readData(dataWrapper, new InitPosition());
12 | }
13 |
14 | function readMarkerState(dataWrapper: RawBuffer): MarkerState {
15 | return readData(dataWrapper, new MarkerState());
16 | }
17 |
18 | function readWaypoint(dataWrapper: RawBuffer): Waypoint {
19 | return readData(dataWrapper, new Waypoint());
20 | }
21 |
22 | function readLatLonAlt(dataWrapper: RawBuffer): LatLonAlt {
23 | return readData(dataWrapper, new LatLonAlt());
24 | }
25 |
26 | function readXYZ(dataWrapper: RawBuffer): XYZ {
27 | return readData(dataWrapper, new XYZ());
28 | }
29 |
30 | function readData(dataWrapper: RawBuffer, obj: T): T {
31 | obj.readFrom(dataWrapper);
32 | return obj;
33 | }
34 |
35 | function readPBH(dataWrapper: RawBuffer): PBH {
36 | return readData(dataWrapper, new PBH());
37 | }
38 |
39 | export { readInitPosition, readMarkerState, readWaypoint, readLatLonAlt, readXYZ, readPBH };
40 |
--------------------------------------------------------------------------------
/src/dto/icao.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export type IcaoType = 'V' | 'N' | 'W';
4 |
5 | export class Icao {
6 | type: IcaoType;
7 |
8 | ident: string;
9 |
10 | region: string;
11 |
12 | airport: string;
13 |
14 | constructor(data: RawBuffer) {
15 | this.type = data.readString(1)[0] as IcaoType;
16 | this.ident = data.readString(6);
17 | this.region = data.readString(3);
18 | this.airport = data.readString(5);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/dto/index.ts:
--------------------------------------------------------------------------------
1 | export * from './InitPosition';
2 | export * from './LatLonAlt';
3 | export * from './MarkerState';
4 | export * from './SimConnectData';
5 | export * from './Waypoint';
6 | export * from './XYZ';
7 | export * from './bufferHelpers';
8 | export * from './icao';
9 | export * from './PBH';
10 | export * from '../datastructures/VersionBaseType';
11 |
--------------------------------------------------------------------------------
/src/enums/ClientDataPeriod.ts:
--------------------------------------------------------------------------------
1 | export enum ClientDataPeriod {
2 | /** Specifies that the data is not to be sent. */
3 | NEVER,
4 | /** Specifies that the data should be sent once only. Note that this is not an efficient way of receiving
5 | * data frequently, use one of the other periods if there is a regular frequency to the data request. */
6 | ONCE,
7 | /** Specifies that the data should be sent every visual (rendered) frame. */
8 | VISUAL_FRAME,
9 | /** Specifies that the data should be sent whenever it is set. */
10 | ON_SET,
11 | /** Specifies that the data should be sent once every second. */
12 | SECOND,
13 | }
14 |
--------------------------------------------------------------------------------
/src/enums/FacilityDataType.ts:
--------------------------------------------------------------------------------
1 | export enum FacilityDataType {
2 | AIRPORT,
3 | RUNWAY,
4 | START,
5 | FREQUENCY,
6 | HELIPAD,
7 | APPROACH,
8 | APPROACH_TRANSITION,
9 | APPROACH_LEG,
10 | FINAL_APPROACH_LEG,
11 | MISSED_APPROACH_LEG,
12 | DEPARTURE,
13 | ARRIVAL,
14 | RUNWAY_TRANSITION,
15 | ENROUTE_TRANSITION,
16 | TAXI_POINT,
17 | TAXI_PARKING,
18 | TAXI_PATH,
19 | TAXI_NAME,
20 | JETWAY,
21 | VOR,
22 | NDB,
23 | WAYPOINT,
24 | ROUTE,
25 | PAVEMENT,
26 | APPROACHLIGHTS,
27 | VASI,
28 | }
29 |
30 | module.exports = {
31 | FacilityDataType,
32 | };
33 |
--------------------------------------------------------------------------------
/src/enums/FacilityListType.ts:
--------------------------------------------------------------------------------
1 | export enum FacilityListType {
2 | /** Specifies that the type of information is for an airport */
3 | AIRPORT,
4 | /** Specifies that the type of information is for a waypoint */
5 | WAYPOINT,
6 | /** Specifies that the type of information is for an NDB */
7 | NDB,
8 | /** Specifies that the type of information is for a VOR */
9 | VOR,
10 | /** Not valid as a list type, but simply the number of list types. */
11 | COUNT,
12 | }
13 |
14 | module.exports = {
15 | FacilityListType,
16 | };
17 |
--------------------------------------------------------------------------------
/src/enums/InputEventType.ts:
--------------------------------------------------------------------------------
1 | export const enum InputEventType {
2 | DOUBLE,
3 | STRING,
4 | }
5 |
--------------------------------------------------------------------------------
/src/enums/JetwayStatus.ts:
--------------------------------------------------------------------------------
1 | export const enum JetwayStatus {
2 | REST,
3 | APPROACH_OUTSIDE,
4 | APPROACH_DOOR,
5 | HOOD_CONNECT,
6 | HOOD_DISCONNECT,
7 | RETRACT_OUTSIDE,
8 | RETRACT_HOME,
9 | FULLY_ATTACHED,
10 | }
11 |
--------------------------------------------------------------------------------
/src/enums/NotificationPriority.ts:
--------------------------------------------------------------------------------
1 | export const enum NotificationPriority {
2 | /** The highest priority. */
3 | HIGHEST = 0x00000001,
4 | /** The hightest priority that allows events to be masked. */
5 | HIGHEST_MASKABLE = 0x00989680,
6 | /** The standard priority. */
7 | STANDARD = 0x713fb300,
8 | /** The default priority. */
9 | DEFAULT = 0x77359400,
10 | /** Priorities lower than this will be ignored. */
11 | LOWEST = 0xee6b2800,
12 | }
13 |
--------------------------------------------------------------------------------
/src/enums/Protocol.ts:
--------------------------------------------------------------------------------
1 | export enum Protocol {
2 | /**
3 | * FSX original release
4 | */
5 | FSX_RTM = 0x2,
6 | /**
7 | * FSX SP1, supports enhanced client data, facilites, and modeless ui
8 | */
9 | FSX_SP1 = 0x3,
10 | /**
11 | * FSX SP2/Acceleration, racing and another flight save
12 | */
13 | FSX_SP2 = 0x4,
14 | /**
15 | * MSFS / Asobo
16 | */
17 | KittyHawk = 0x5,
18 | }
19 |
20 | module.exports = {
21 | Protocol,
22 | };
23 |
--------------------------------------------------------------------------------
/src/enums/SimConnectDataType.ts:
--------------------------------------------------------------------------------
1 | export enum SimConnectDataType {
2 | /** Invalid data type */
3 | INVALID,
4 | /** 32-bit integer number */
5 | INT32,
6 | /** 64-bit integer number */
7 | INT64,
8 | /** 32-bit floating-point number (float) */
9 | FLOAT32,
10 | /** 64-bit floating-point number (double) */
11 | FLOAT64,
12 | /** Fixed-length string, 8-byte */
13 | STRING8,
14 | /** Fixed-length string, 32-byte */
15 | STRING32,
16 | /** Fixed-length string, 64-byte */
17 | STRING64,
18 | /** Fixed-length string, 128-byte */
19 | STRING128,
20 | /** Fixed-length string, 256-byte */
21 | STRING256,
22 | /** Fixed-length string, 260-byte */
23 | STRING260,
24 | /** Variable-length string */
25 | STRINGV,
26 |
27 | /** {@link InitPosition} data structure */
28 | INITPOSITION,
29 | /** {@link MarkerState} data structure */
30 | MARKERSTATE,
31 | /** {@link Waypoint} data structure */
32 | WAYPOINT,
33 | /** {@link LatLonAlt} data structure */
34 | LATLONALT,
35 | /** {@link XYZ} data structure */
36 | XYZ,
37 | /** enum limit */
38 | MAX,
39 | }
40 |
41 | module.exports = {
42 | SimConnectDataType,
43 | };
44 |
--------------------------------------------------------------------------------
/src/enums/SimConnectException.ts:
--------------------------------------------------------------------------------
1 | export enum SimConnectException {
2 | NONE,
3 | ERROR,
4 | SIZE_MISMATCH,
5 | UNRECOGNIZED_ID,
6 | UNOPENED,
7 | VERSION_MISMATCH,
8 | TOO_MANY_GROUPS,
9 | NAME_UNRECOGNIZED,
10 | TOO_MANY_EVENT_NAMES,
11 | EVENT_ID_DUPLICATE,
12 | TOO_MANY_MAPS,
13 | TOO_MANY_OBJECTS,
14 | TOO_MANY_REQUESTS,
15 | WEATHER_INVALID_PORT,
16 | WEATHER_INVALID_METAR,
17 | WEATHER_UNABLE_TO_GET_OBSERVATION,
18 | WEATHER_UNABLE_TO_CREATE_STATION,
19 | WEATHER_UNABLE_TO_REMOVE_STATION,
20 | INVALID_DATA_TYPE,
21 | INVALID_DATA_SIZE,
22 | DATA_ERROR,
23 | INVALID_ARRAY,
24 | CREATE_OBJECT_FAILED,
25 | LOAD_FLIGHTPLAN_FAILED,
26 | OPERATION_INVALID_FOR_OJBECT_TYPE,
27 | ILLEGAL_OPERATION,
28 | ALREADY_SUBSCRIBED,
29 | INVALID_ENUM,
30 | DEFINITION_ERROR,
31 | DUPLICATE_ID,
32 | DATUM_ID,
33 | OUT_OF_BOUNDS,
34 | ALREADY_CREATED,
35 | OBJECT_OUTSIDE_REALITY_BUBBLE,
36 | OBJECT_CONTAINER,
37 | OBJECT_AI,
38 | OBJECT_ATC,
39 | OBJECT_SCHEDULE,
40 | JETWAY_DATA,
41 | ACTION_NOT_FOUND,
42 | NOT_AN_ACTION,
43 | INCORRECT_ACTION_PARAMS,
44 | GET_INPUT_EVENT_FAILED,
45 | SET_INPUT_EVENT_FAILED,
46 | }
47 |
48 | module.exports = {
49 | SimConnectException,
50 | };
51 |
--------------------------------------------------------------------------------
/src/enums/SimConnectPeriod.ts:
--------------------------------------------------------------------------------
1 | export enum SimConnectPeriod {
2 | /** Specifies that the data is not to be sent. */
3 | NEVER,
4 | /** Specifies that the data should be sent once only. Note that this is not an efficient way of receiving
5 | * data frequently, use one of the other periods if there is a regular frequency to the data request. */
6 | ONCE,
7 | /** Specifies that the data should be sent every visual (rendered) frame. */
8 | VISUAL_FRAME,
9 | /** Specifies that the data should be sent every simulated frame, whether that frame is rendered or not. */
10 | SIM_FRAME,
11 | /** Specifies that the data should be sent once every second. */
12 | SECOND,
13 | }
14 |
15 | module.exports = {
16 | SimConnectPeriod,
17 | };
18 |
--------------------------------------------------------------------------------
/src/enums/SimObjectType.ts:
--------------------------------------------------------------------------------
1 | export enum SimObjectType {
2 | /** Specifies the user's aircraft. */
3 | USER,
4 | /** Specifies all AI controlled objects. */
5 | ALL,
6 | /** Specifies all aircraft. */
7 | AIRCRAFT,
8 | /** Specifies all helicopters. */
9 | HELICOPTER,
10 | /** Specifies all AI controlled boats. */
11 | BOAT,
12 | /** Specifies all AI controlled ground vehicles */
13 | GROUND,
14 |
15 | /** for invalid values */
16 | INVALID,
17 | }
18 |
19 | module.exports = {
20 | SimObjectType,
21 | };
22 |
--------------------------------------------------------------------------------
/src/enums/TextResult.ts:
--------------------------------------------------------------------------------
1 | export enum TextResult {
2 | /** Specifies that the user has selected the menu item. */
3 | MENU_SELECT_1 = 0,
4 | /** Specifies that the user has selected the menu item. */
5 | MENU_SELECT_2 = 1,
6 | /** Specifies that the user has selected the menu item. */
7 | MENU_SELECT_3 = 2,
8 | /** Specifies that the user has selected the menu item. */
9 | MENU_SELECT_4 = 3,
10 | /** Specifies that the user has selected the menu item. */
11 | MENU_SELECT_5 = 4,
12 | /** Specifies that the user has selected the menu item. */
13 | MENU_SELECT_6 = 5,
14 | /** Specifies that the user has selected the menu item. */
15 | MENU_SELECT_7 = 6,
16 | /** Specifies that the user has selected the menu item. */
17 | MENU_SELECT_8 = 7,
18 | /** Specifies that the user has selected the menu item. */
19 | MENU_SELECT_9 = 8,
20 | /** Specifies that the user has selected the menu item. */
21 | MENU_SELECT_10 = 9,
22 | /** Specifies that the menu or text identified by the EventID is now on display. */
23 | DISPLAYED = 0x00010000,
24 | /** Specifies that the menu or text identified by the EventID is waiting in a queue. */
25 | QUEUED = 0x00010001,
26 | /** Specifies that the menu or text identified by the EventID has been removed from the queue. */
27 | REMOVED = 0x00010002,
28 | /** Specifies that the menu or text identified by the EventID has been replaced in the queue. */
29 | REPLACED = 0x00010003,
30 | /** Specifies that the menu or text identified by the EventID has timed-out and is no longer on display. */
31 | TIMEOUT = 0x00010004,
32 | }
33 |
34 | module.exports = {
35 | TextResult,
36 | };
37 |
--------------------------------------------------------------------------------
/src/enums/TextType.ts:
--------------------------------------------------------------------------------
1 | export enum TextType {
2 | /** specify scrolling text in the named color */
3 | SCROLL_BLACK = 0,
4 | /** specify scrolling text in the named color */
5 | SCROLL_WHITE = 1,
6 | /** specify scrolling text in the named color */
7 | SCROLL_RED = 2,
8 | /** specify scrolling text in the named color */
9 | SCROLL_GREEN = 3,
10 | /** specify scrolling text in the named color */
11 | SCROLL_BLUE = 4,
12 | /** specify scrolling text in the named color */
13 | SCROLL_YELLOW = 5,
14 | /** specify scrolling text in the named color */
15 | SCROLL_MAGENTA = 6,
16 | /** specify scrolling text in the named color */
17 | SCROLL_CYAN = 7,
18 | /** specify static text in the named color */
19 | PRINT_BLACK = 0x0100,
20 | /** specify static text in the named color */
21 | PRINT_WHITE = 0x0101,
22 | /** specify static text in the named color */
23 | PRINT_RED = 0x0102,
24 | /** specify static text in the named color */
25 | PRINT_GREEN = 0x0103,
26 | /** specify static text in the named color */
27 | PRINT_BLUE = 0x0104,
28 | /** specify static text in the named color */
29 | PRINT_YELLOW = 0x0105,
30 | /** specify static text in the named color */
31 | PRINT_MAGENTA = 0x0106,
32 | /** specify static text in the named color */
33 | PRINT_CYAN = 0x0107,
34 | /** specify that the text is for a menu */
35 | MENU = 0x0200,
36 | }
37 |
--------------------------------------------------------------------------------
/src/enums/WeatherMode.ts:
--------------------------------------------------------------------------------
1 | export enum WeatherMode {
2 | /** Specifies that the weather has been set to a theme. */
3 | THEME,
4 | /** Specifies that real-world weather has been set. */
5 | RWW,
6 | /** Specifies that custom weather has been set. */
7 | CUSTOM,
8 | /** Specifies that the global weather mode has been set. */
9 | GLOBAL,
10 | }
11 |
--------------------------------------------------------------------------------
/src/enums/index.ts:
--------------------------------------------------------------------------------
1 | export * from './ClientDataPeriod';
2 | export * from './SimConnectDataType';
3 | export * from './TextType';
4 | export * from './TextResult';
5 | export * from './SimConnectPeriod';
6 | export * from './SimConnectException';
7 | export * from './SimObjectType';
8 | export * from './FacilityListType';
9 | export * from './NotificationPriority';
10 | export * from './Protocol';
11 | export * from './WeatherMode';
12 | export * from './InputEventType';
13 | export * from './FacilityDataType';
14 | export * from './JetwayStatus';
15 |
--------------------------------------------------------------------------------
/src/flags/ClientDataRequestFlag.ts:
--------------------------------------------------------------------------------
1 | export enum ClientDataRequestFlag {
2 | /** The default, data will be sent strictly according to the defined period.
3 | */
4 | CLIENT_DATA_REQUEST_FLAG_DEFAULT = 0x00000000,
5 | /** Data will only be sent to the client when one or more values have changed. If
6 | * this is the only flag set, then all the variables in a data definition will be returned
7 | * if just one of the values changes.
8 | */
9 | CLIENT_DATA_REQUEST_FLAG_CHANGED = 0x00000001, // send requested data when value(s) change
10 | /** Requested data will be sent in tagged format (datum ID/value pairs). Tagged
11 | * format requires that a datum reference ID is returned along with the data value,
12 | * in order that the client code is able to identify the variable. This flag is
13 | * usually set in conjunction with the previous flag, but it can be used on its own to
14 | * return all the values in a data definition in datum ID/value pairs.
15 | */
16 | CLIENT_DATA_REQUEST_FLAG_TAGGED = 0x00000002, // send requested data in tagged format
17 | }
18 |
--------------------------------------------------------------------------------
/src/flags/DataRequestFlag.ts:
--------------------------------------------------------------------------------
1 | export enum DataRequestFlag {
2 | /** The default, data will be sent strictly according to the defined period. */
3 | DATA_REQUEST_FLAG_DEFAULT = 0x00000000,
4 | /** Data will only be sent to the client when one or more values have changed. If
5 | * this is the only flag set, then all the variables in a data definition will be returned
6 | * if just one of the values changes. */
7 | DATA_REQUEST_FLAG_CHANGED = 0x00000001,
8 | /** Requested data will be sent in tagged format (datum ID/value pairs). Tagged
9 | * format requires that a datum reference ID is returned along with the data value,
10 | * in order that the client code is able to identify the variable. This flag is
11 | * usually set in conjunction with the previous flag, but it can be used on its own to
12 | * return all the values in a data definition in datum ID/value pairs */
13 | DATA_REQUEST_FLAG_TAGGED = 0x00000002,
14 | }
15 |
--------------------------------------------------------------------------------
/src/flags/DataSetFlag.ts:
--------------------------------------------------------------------------------
1 | export enum DataSetFlag {
2 | /** The data to be set is not in tagged format
3 | */
4 | DEFAULT = 0x00000000,
5 | /** The data to be set is being sent in tagged format. Refer to {@link SimConnectConnection.requestDataOnSimObject} for more details on the tagged format. */
6 | TAGGED = 0x00000001,
7 | }
8 |
--------------------------------------------------------------------------------
/src/flags/EventFlag.ts:
--------------------------------------------------------------------------------
1 | export enum EventFlag {
2 | /** Do nothing. */
3 | EVENT_FLAG_DEFAULT = 0x00000000,
4 | /** The flag will effectively reset the repeat timer to simulate slow repeat. Use this flag to
5 | * reset the repeat timer that works with various keyboard events and mouse clicks. */
6 | EVENT_FLAG_FAST_REPEAT_TIMER = 0x00000001, // set event repeat timer to simulate fast repeat
7 | /** The flag will effectively reset the repeat timer to simulate slow repeat.
8 | Use this flag to reset the repeat timer that works with various keyboard events and mouse clicks. */
9 | EVENT_FLAG_SLOW_REPEAT_TIMER = 0x00000002, // set event repeat timer to simulate slow repeat
10 | /** Indicates to the SimConnect server to treat the GroupID as a priority value.
11 | If this parameter is set to {@link NotificationPriority.HIGHEST} then all
12 | client notification groups that have subscribed to the event will receive
13 | the notification (unless one of them masks it). The event will be transmitted
14 | to clients starting at the given priority, though this can result in the
15 | client that transmitted the event, receiving it again. */
16 | EVENT_FLAG_GROUPID_IS_PRIORITY = 0x00000010, // interpret GroupID parameter as priority value
17 | }
18 |
--------------------------------------------------------------------------------
/src/flags/index.ts:
--------------------------------------------------------------------------------
1 | export * from './EventFlag';
2 | export * from './DataRequestFlag';
3 | export * from './DataSetFlag';
4 | export * from './ClientDataRequestFlag';
5 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectConnection, ConnectionOptions } from './SimConnectConnection';
2 | import { Protocol } from './enums/Protocol';
3 | import { RecvOpen } from './recv';
4 |
5 | export * from './SimConnectConstants';
6 | export * from './SimConnectPacketBuilder';
7 | export * from './enums';
8 | export * from './SimConnectConnection';
9 | export * from './SimConnectSocket';
10 | export * from './flags';
11 | export * from './datastructures';
12 | export * from './Types';
13 | export * from './recv';
14 | export * from './dto';
15 | export { RawBuffer } from './RawBuffer';
16 |
17 | /**
18 | * @member recvOpen - Information about the flight simulator
19 | * @member handle - The object used to interact with SimConnect
20 | */
21 | export interface OpenEvent {
22 | recvOpen: RecvOpen;
23 | handle: SimConnectConnection;
24 | }
25 |
26 | /**
27 | * Try opening a connection to SimConnect
28 | *
29 | * @param appName - An appropriate name for the client program. Your app will appear with this name in the SimConnect inspector in MSFS.
30 | * @param protocolVersion - Lowest protocol version
31 | * @param options - Used for connecting to a remote instance of SimConnect. If omitted it will attempt to read connection parameters from the following sources:
32 | *
33 | * - IP + port number from SimConnect.cfg in the node.js installation directory (or the installation directory of the Electron app)
34 | *
35 | * - IP + port number from SimConnect.cfg in the user's home directory
36 | *
37 | * - Named pipe found in the Windows registry (available when the sim has started)
38 | *
39 | * - Port number, for use with localhost, found in the Windows registry (available when the sim has started)
40 | *
41 | */
42 | export function open(
43 | appName: string,
44 | protocolVersion: Protocol,
45 | options?: ConnectionOptions
46 | ): Promise {
47 | const simConnectConnection = new SimConnectConnection(appName, protocolVersion);
48 | return new Promise((resolve, reject) => {
49 | simConnectConnection.on('open', data => {
50 | resolve({ recvOpen: data, handle: simConnectConnection });
51 | });
52 | simConnectConnection.on('error', error => {
53 | reject(error);
54 | });
55 | simConnectConnection.connect(options);
56 | });
57 | }
58 |
59 | export type ConnectionHandle = InstanceType;
60 |
--------------------------------------------------------------------------------
/src/recv/RecvActionCallback.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { RecvEvent } from './RecvEvent';
3 | import { DataRequestId } from '../Types';
4 |
5 | export class RecvActionCallback extends RecvEvent {
6 | requestID: DataRequestId;
7 |
8 | actionID: string;
9 |
10 | constructor(data: RawBuffer) {
11 | super(data);
12 | this.actionID = data.readString260();
13 | this.requestID = data.readInt32();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvAirportList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityAirport } from '../datastructures/FacilityAirport';
3 | import { RecvFacilitiesList } from './RecvFacilitiesList';
4 |
5 | export class RecvAirportList extends RecvFacilitiesList {
6 | airports: FacilityAirport[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.airports = [];
11 | for (let i = 0; i < this.arraySize; i++) {
12 | this.airports.push(new FacilityAirport(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvAssignedObjectID.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId, ObjectId } from '../Types';
3 |
4 | export class RecvAssignedObjectID {
5 | requestID: DataRequestId;
6 |
7 | objectID: ObjectId;
8 |
9 | constructor(data: RawBuffer) {
10 | this.requestID = data.readInt32() as DataRequestId;
11 | this.objectID = data.readInt32() as ObjectId;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/recv/RecvCloudState.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 |
4 | export class RecvCloudState {
5 | requestID: DataRequestId;
6 |
7 | arraySize: number;
8 |
9 | data: number[][];
10 |
11 | constructor(data: RawBuffer) {
12 | this.requestID = data.readInt32() as DataRequestId;
13 | this.arraySize = data.readInt32();
14 | this.data = [];
15 | // Read 2D-array of 64x64 bytes
16 | for (let i = 0; i < 64; i++) {
17 | this.data[i] = [...data.readBytes(64)];
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/recv/RecvControllersList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { RecvListTemplate } from './RecvListTemplate';
3 | import { ControllerItem } from '../datastructures/ControllerItem';
4 |
5 | export class RecvControllersList extends RecvListTemplate {
6 | controllers: ControllerItem[] = [];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 |
11 | this.controllers = [];
12 | for (let i = 0; i < this.arraySize; i++) {
13 | this.controllers.push(new ControllerItem(data));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/recv/RecvCustomAction.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class RecvCustomAction {
4 | guid: Buffer;
5 |
6 | waitForCompletion: number;
7 |
8 | payload: string;
9 |
10 | constructor(data: RawBuffer) {
11 | this.guid = data.readBytes(16);
12 | this.waitForCompletion = data.readInt32();
13 | this.payload = data.readString(data.remaining());
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvEnumerateInputEventParams.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class RecvEnumerateInputEventParams {
4 | inputEventIdHash: bigint;
5 |
6 | value: string;
7 |
8 | constructor(data: RawBuffer) {
9 | this.inputEventIdHash = data.readUint64();
10 | this.value = data.readStringV();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/recv/RecvEnumerateInputEvents.ts:
--------------------------------------------------------------------------------
1 | import { RecvListTemplate } from './RecvListTemplate';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { InputEventDescriptor } from '../datastructures/InputEventDescriptor';
4 |
5 | export class RecvEnumerateInputEvents extends RecvListTemplate {
6 | inputEventDescriptors: InputEventDescriptor[] = [];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 |
11 | this.inputEventDescriptors = [];
12 | for (let i = 0; i < this.arraySize; i++) {
13 | this.inputEventDescriptors.push(new InputEventDescriptor(data));
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/recv/RecvEvent.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { ClientEventId, InputGroupId, NotificationGroupId } from '../Types';
3 |
4 | export class RecvEvent {
5 | groupID: NotificationGroupId | InputGroupId;
6 |
7 | clientEventId: ClientEventId;
8 |
9 | data: number;
10 |
11 | constructor(data: RawBuffer) {
12 | this.groupID = data.readInt32() as NotificationGroupId;
13 | this.clientEventId = data.readInt32() as ClientEventId;
14 | this.data = data.readInt32();
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/recv/RecvEventAddRemove.ts:
--------------------------------------------------------------------------------
1 | import { SimObjectType } from '../enums/SimObjectType';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { RecvEvent } from './RecvEvent';
4 |
5 | export class RecvEventAddRemove extends RecvEvent {
6 | type: SimObjectType;
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.type = data.readInt32();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/recv/RecvEventEx1.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { ClientEventId, InputGroupId, NotificationGroupId } from '../Types';
3 |
4 | export class RecvEventEx1 {
5 | groupID: NotificationGroupId | InputGroupId;
6 |
7 | clientEventId: ClientEventId;
8 |
9 | /** Contains max 5 bytes of data */
10 | data: [number, number, number, number, number];
11 |
12 | constructor(data: RawBuffer) {
13 | this.groupID = data.readInt32() as NotificationGroupId;
14 | this.clientEventId = data.readInt32() as ClientEventId;
15 | this.data = [
16 | data.readInt32(),
17 | data.readInt32(),
18 | data.readInt32(),
19 | data.readInt32(),
20 | data.readInt32(),
21 | ];
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/recv/RecvEventFilename.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectConstants } from '../SimConnectConstants';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { RecvEvent } from './RecvEvent';
4 |
5 | export class RecvEventFilename extends RecvEvent {
6 | fileName: string;
7 |
8 | flags: number;
9 |
10 | constructor(data: RawBuffer) {
11 | super(data);
12 | this.fileName = data.readString(SimConnectConstants.MAX_PATH);
13 | this.flags = data.readInt32();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvEventFrame.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { RecvEvent } from './RecvEvent';
3 |
4 | export class RecvEventFrame extends RecvEvent {
5 | frameRate: number;
6 |
7 | simSpeed: number;
8 |
9 | constructor(data: RawBuffer) {
10 | super(data);
11 | this.frameRate = data.readFloat32();
12 | this.simSpeed = data.readFloat32();
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/recv/RecvEventRaceEnd.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { RecvEvent } from './RecvEvent';
3 |
4 | export class RecvEventRaceEnd extends RecvEvent {
5 | /** The index of the racer the results are for */
6 | racerNumber: number;
7 |
8 | /** The total number of racers */
9 | numberRacers: number;
10 |
11 | /** The name of the mission to execute, NULL if no mission */
12 | missionGUID: Buffer;
13 |
14 | /** The name of the player */
15 | playerName: string;
16 |
17 | /** The type of the multiplayer session: "LAN", "GAMESPY") */
18 | sessionType: string;
19 |
20 | /** The aircraft type */
21 | aircraft: string;
22 |
23 | /** The player role in the mission */
24 | playerRole: string;
25 |
26 | /** Total time in seconds, 0 means DNF */
27 | totalTime: number;
28 |
29 | /** Total penalty time in seconds */
30 | penaltyTime: number;
31 |
32 | /** non 0 - disqualified, 0 - not disqualified */
33 | disqualified: boolean;
34 |
35 | constructor(data: RawBuffer) {
36 | super(data);
37 | this.racerNumber = data.readInt32();
38 | this.numberRacers = data.readInt32();
39 | this.missionGUID = data.readBytes(16);
40 | this.playerName = data.readString(260);
41 | this.sessionType = data.readString(260);
42 | this.aircraft = data.readString(260);
43 | this.playerRole = data.readString(260);
44 | this.totalTime = data.readFloat64();
45 | this.penaltyTime = data.readFloat64();
46 | this.disqualified = data.readInt32() === 1;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/recv/RecvEventRaceLap.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { RecvEvent } from './RecvEvent';
3 |
4 | export class RecvEventRaceLap extends RecvEvent {
5 | /** The index of the racer the results are for */
6 | lapIndex: number;
7 |
8 | /** The total number of racers */
9 | numberRacers: number;
10 |
11 | /** The name of the mission to execute, NULL if no mission */
12 | missionGUID: Buffer;
13 |
14 | /** The name of the player */
15 | playerName: string;
16 |
17 | /** The type of the multiplayer session: "LAN", "GAMESPY") */
18 | sessionType: string;
19 |
20 | /** The aircraft type */
21 | aircraft: string;
22 |
23 | /** The player role in the mission */
24 | playerRole: string;
25 |
26 | /** Total time in seconds, 0 means DNF */
27 | totalTime: number;
28 |
29 | /** Total penalty time in seconds */
30 | penaltyTime: number;
31 |
32 | /** non 0 - disqualified, 0 - not disqualified */
33 | disqualified: boolean;
34 |
35 | constructor(data: RawBuffer) {
36 | super(data);
37 | this.lapIndex = data.readInt32();
38 | this.numberRacers = data.readInt32();
39 | this.missionGUID = data.readBytes(16);
40 | this.playerName = data.readString(260);
41 | this.sessionType = data.readString(260);
42 | this.aircraft = data.readString(260);
43 | this.playerRole = data.readString(260);
44 | this.totalTime = data.readFloat64();
45 | this.penaltyTime = data.readFloat64();
46 | this.disqualified = data.readInt32() === 1;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/recv/RecvEventWeatherMode.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { WeatherMode } from '../enums/WeatherMode';
3 | import { RecvEvent } from './RecvEvent';
4 |
5 | export class RecvEventWeatherMode extends RecvEvent {
6 | mode: WeatherMode;
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.mode =
11 | this.data < 0 || this.data > WeatherMode.GLOBAL
12 | ? WeatherMode.THEME
13 | : this.data;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvException.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { SimConnectException } from '../enums/SimConnectException';
3 |
4 | export class RecvException {
5 | exception: number;
6 |
7 | sendId: number;
8 |
9 | index: number;
10 |
11 | exceptionName: string;
12 |
13 | constructor(data: RawBuffer) {
14 | this.exception = data.readInt32();
15 | this.sendId = data.readInt32();
16 | this.index = data.readInt32();
17 |
18 | this.exceptionName = SimConnectException[this.exception];
19 | }
20 | }
21 |
22 | module.exports = { RecvException };
23 |
--------------------------------------------------------------------------------
/src/recv/RecvFacilitiesList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 |
4 | export class RecvFacilitiesList {
5 | requestID: DataRequestId;
6 |
7 | arraySize: number;
8 |
9 | entryNumber: number;
10 |
11 | outOf: number;
12 |
13 | constructor(data: RawBuffer) {
14 | this.requestID = data.readInt32() as DataRequestId;
15 | this.arraySize = data.readInt32();
16 | this.entryNumber = data.readInt32();
17 | this.outOf = data.readInt32();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/recv/RecvFacilityData.ts:
--------------------------------------------------------------------------------
1 | import { FacilityDataType } from '../enums/FacilityDataType';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { DataDefinitionId, DataRequestId } from '../Types';
4 |
5 | export class RecvFacilityData {
6 | userRequestId: DataRequestId;
7 |
8 | uniqueRequestId: DataDefinitionId;
9 |
10 | parentUniqueRequestId: DataDefinitionId;
11 |
12 | type: FacilityDataType;
13 |
14 | isListItem: boolean;
15 |
16 | itemIndex: number;
17 |
18 | listSize: number;
19 |
20 | data: RawBuffer;
21 |
22 | constructor(data: RawBuffer) {
23 | this.userRequestId = data.readInt32();
24 | this.uniqueRequestId = data.readInt32();
25 | this.parentUniqueRequestId = data.readInt32();
26 | this.type = data.readInt32();
27 | this.isListItem = data.readInt32() === 1;
28 | this.itemIndex = data.readInt32();
29 | this.listSize = data.readInt32();
30 | this.data = data;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/recv/RecvFacilityDataEnd.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 |
4 | export class RecvFacilityDataEnd {
5 | // extends RecvEvent
6 | userRequestId: DataRequestId;
7 |
8 | constructor(data: RawBuffer) {
9 | this.userRequestId = data.readInt32();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/recv/RecvFacilityMinimalList.ts:
--------------------------------------------------------------------------------
1 | import { FacilityMinimal } from '../datastructures/FacilityMinimal';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { RecvFacilitiesList } from './RecvFacilitiesList';
4 |
5 | export class RecvFacilityMinimalList extends RecvFacilitiesList {
6 | data: FacilityMinimal[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.data = [];
11 | for (let i = 0; i < this.arraySize; i++) {
12 | this.data.push(new FacilityMinimal(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvGetInputEvent.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 | import { InputEventType } from '../enums/InputEventType';
4 |
5 | export class RecvGetInputEvent {
6 | requestID: DataRequestId;
7 |
8 | type: InputEventType;
9 |
10 | value: number | string;
11 |
12 | constructor(data: RawBuffer) {
13 | this.requestID = data.readInt32();
14 | this.type = data.readUint32();
15 |
16 | switch (this.type) {
17 | case InputEventType.STRING:
18 | this.value = data.readString256();
19 | break;
20 | case InputEventType.DOUBLE:
21 | this.value = data.readFloat64();
22 | break;
23 | default:
24 | throw Error(`Unknown input event type: ${this.type}`);
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/recv/RecvJetwayData.ts:
--------------------------------------------------------------------------------
1 | import { RecvListTemplate } from './RecvListTemplate';
2 | import { JetwayData } from '../datastructures/JetwayData';
3 | import { RawBuffer } from '../RawBuffer';
4 |
5 | export class RecvJetwayData extends RecvListTemplate {
6 | jetways: JetwayData[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 |
11 | this.jetways = [];
12 |
13 | for (let i = 0; i < this.arraySize; i++) {
14 | this.jetways.push(new JetwayData(data));
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/recv/RecvListTemplate.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 |
4 | export class RecvListTemplate {
5 | requestID: DataRequestId;
6 |
7 | arraySize: number;
8 |
9 | entryNumber: number;
10 |
11 | outOf: number;
12 |
13 | constructor(data: RawBuffer) {
14 | this.requestID = data.readInt32() as DataRequestId;
15 | this.arraySize = data.readInt32();
16 | this.entryNumber = data.readInt32();
17 | this.outOf = data.readInt32();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/recv/RecvNDBList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityNDB } from '../datastructures/FacilityNDB';
3 | import { RecvFacilitiesList } from './RecvFacilitiesList';
4 |
5 | export class RecvNDBList extends RecvFacilitiesList {
6 | ndbs: FacilityNDB[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.ndbs = [];
11 | for (let i = 0; i < this.arraySize; i++) {
12 | this.ndbs.push(new FacilityNDB(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvOpen.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class RecvOpen {
4 | applicationName: string;
5 |
6 | applicationVersionMajor: number;
7 |
8 | applicationVersionMinor: number;
9 |
10 | applicationBuildMajor: number;
11 |
12 | applicationBuildMinor: number;
13 |
14 | simConnectVersionMajor: number;
15 |
16 | simConnectVersionMinor: number;
17 |
18 | simConnectBuildMajor: number;
19 |
20 | simConnectBuildMinor: number;
21 |
22 | reserved1: number;
23 |
24 | reserved2: number;
25 |
26 | constructor(data: RawBuffer) {
27 | this.applicationName = data.readString256();
28 | this.applicationVersionMajor = data.readInt32();
29 | this.applicationVersionMinor = data.readInt32();
30 | this.applicationBuildMajor = data.readInt32();
31 | this.applicationBuildMinor = data.readInt32();
32 | this.simConnectVersionMajor = data.readInt32();
33 | this.simConnectVersionMinor = data.readInt32();
34 | this.simConnectBuildMajor = data.readInt32();
35 | this.simConnectBuildMinor = data.readInt32();
36 | this.reserved1 = data.readInt32();
37 | this.reserved2 = data.readInt32();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/recv/RecvReservedKey.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 |
3 | export class RecvReservedKey {
4 | choiceReserved: string;
5 |
6 | reservedKey: string;
7 |
8 | constructor(data: RawBuffer) {
9 | this.choiceReserved = data.readString(50);
10 | this.reservedKey = data.readString(30);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/recv/RecvSimObjectData.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestFlag } from '../flags/DataRequestFlag';
3 | import { DataDefinitionId, DataRequestId, ObjectId } from '../Types';
4 |
5 | export class RecvSimObjectData {
6 | requestID: DataRequestId;
7 |
8 | objectID: ObjectId;
9 |
10 | defineID: DataDefinitionId;
11 |
12 | flags: DataRequestFlag;
13 |
14 | entryNumber: number;
15 |
16 | outOf: number;
17 |
18 | defineCount: number;
19 |
20 | data: RawBuffer;
21 |
22 | constructor(data: RawBuffer) {
23 | // data.skip(8)
24 | this.requestID = data.readInt32() as DataRequestId;
25 | this.objectID = data.readInt32() as ObjectId;
26 | this.defineID = data.readInt32() as DataDefinitionId;
27 | this.flags = data.readInt32() as DataRequestFlag;
28 | this.entryNumber = data.readInt32();
29 | this.outOf = data.readInt32();
30 | this.defineCount = data.readInt32();
31 | this.data = data;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/recv/RecvSubscribeInputEvent.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { InputEventType } from '../enums/InputEventType';
3 |
4 | export class RecvSubscribeInputEvent {
5 | inputEventIdHash: bigint;
6 |
7 | type: InputEventType;
8 |
9 | value: number | string;
10 |
11 | constructor(data: RawBuffer) {
12 | this.inputEventIdHash = data.readUint64();
13 | this.type = data.readUint32();
14 |
15 | switch (this.type) {
16 | case InputEventType.STRING:
17 | this.value = data.readString256();
18 | break;
19 | case InputEventType.DOUBLE:
20 | this.value = data.readFloat64();
21 | break;
22 | default:
23 | throw Error(`Unknown input event type: ${this.type}`);
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/recv/RecvSystemState.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectConstants } from '../SimConnectConstants';
2 | import { RawBuffer } from '../RawBuffer';
3 | import { DataRequestId } from '../Types';
4 |
5 | export class RecvSystemState {
6 | requestID: DataRequestId;
7 |
8 | dataInteger: number;
9 |
10 | dataFloat: number;
11 |
12 | dataString: string;
13 |
14 | constructor(data: RawBuffer) {
15 | this.requestID = data.readInt32() as DataRequestId;
16 | this.dataInteger = data.readInt32();
17 | this.dataFloat = data.readFloat32();
18 | this.dataString = data.readString(SimConnectConstants.MAX_PATH);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/recv/RecvVORList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityVOR } from '../datastructures/FacilityVOR';
3 | import { RecvFacilitiesList } from './RecvFacilitiesList';
4 |
5 | export class RecvVORList extends RecvFacilitiesList {
6 | vors: FacilityVOR[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.vors = [];
11 | for (let i = 0; i < this.arraySize; i++) {
12 | this.vors.push(new FacilityVOR(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvWaypointList.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { FacilityWaypoint } from '../datastructures/FacilityWaypoint';
3 | import { RecvFacilitiesList } from './RecvFacilitiesList';
4 |
5 | export class RecvWaypointList extends RecvFacilitiesList {
6 | waypoints: FacilityWaypoint[];
7 |
8 | constructor(data: RawBuffer) {
9 | super(data);
10 | this.waypoints = [];
11 | for (let i = 0; i < this.arraySize; i++) {
12 | this.waypoints.push(new FacilityWaypoint(data));
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/recv/RecvWeatherObservation.ts:
--------------------------------------------------------------------------------
1 | import { RawBuffer } from '../RawBuffer';
2 | import { DataRequestId } from '../Types';
3 |
4 | export class RecvWeatherObservation {
5 | requestID: DataRequestId;
6 |
7 | metar: string;
8 |
9 | constructor(data: RawBuffer) {
10 | this.requestID = data.readInt32() as DataRequestId;
11 | this.metar = data.readStringV();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/recv/index.ts:
--------------------------------------------------------------------------------
1 | export * from './RecvAirportList';
2 | export * from './RecvAssignedObjectID';
3 | export * from './RecvCloudState';
4 | export * from './RecvCustomAction';
5 | export * from './RecvEvent';
6 | export * from './RecvEventEx1';
7 | export * from './RecvEventAddRemove';
8 | export * from './RecvEventFilename';
9 | export * from './RecvEventFrame';
10 | export * from './RecvEventRaceEnd';
11 | export * from './RecvEventRaceLap';
12 | export * from './RecvEventWeatherMode';
13 | export * from './RecvException';
14 | export * from './RecvFacilitiesList';
15 | export * from './RecvFacilityData';
16 | export * from './RecvFacilityDataEnd';
17 | export * from './RecvFacilityMinimalList';
18 | export * from './RecvNDBList';
19 | export * from './RecvOpen';
20 | export * from './RecvReservedKey';
21 | export * from './RecvSimObjectData';
22 | export * from './RecvSystemState';
23 | export * from './RecvVORList';
24 | export * from './RecvWaypointList';
25 | export * from './RecvWeatherObservation';
26 | export * from './RecvJetwayData';
27 | export * from './RecvControllersList';
28 | export * from './RecvEnumerateInputEvents';
29 | export * from './RecvGetInputEvent';
30 | export * from './RecvSubscribeInputEvent';
31 | export * from './RecvEnumerateInputEventParams';
32 | export * from './RecvActionCallback';
33 | export * from './RecvListTemplate';
34 |
--------------------------------------------------------------------------------
/src/utils/ini.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 | import * as ini from 'ini';
3 |
4 | // eslint-disable-next-line
5 | export type IniStructure = { [key: string]: any };
6 |
7 | export function readIniFile(filePath: string): Promise {
8 | return new Promise((resolve, reject) => {
9 | fs.readFile(filePath, { encoding: 'utf-8' }, (err, data) => {
10 | if (err) {
11 | reject(new Error(`Failed to load INI-file: err ${err}`));
12 | } else {
13 | resolve(ini.parse(data));
14 | }
15 | });
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/utils/network.ts:
--------------------------------------------------------------------------------
1 | import * as fs from 'fs';
2 |
3 | export async function checkIfNamedPipeExist(pipeName: string): Promise {
4 | return new Promise(resolve => {
5 | fs.access(pipeName, fs.constants.F_OK, err => {
6 | if (err) resolve(false);
7 | else resolve(true);
8 | });
9 | });
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/registry.ts:
--------------------------------------------------------------------------------
1 | import regedit from 'regedit';
2 |
3 | /**
4 | * Returns `undefined` if the key does not exist.
5 | */
6 | export function readRegistryValue(key: string, subKey: string): Promise {
7 | return new Promise((resolve, reject) => {
8 | regedit.list([key], (err, result) => {
9 | if (err) {
10 | reject(new Error(`Failed to read registry value ${key} (${err})`));
11 | } else {
12 | const values = result[key]?.values;
13 | if (subKey in values) {
14 | resolve(values[subKey].value as string);
15 | } else {
16 | resolve(undefined);
17 | }
18 | }
19 | });
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/tests/flightLoad.test.ts:
--------------------------------------------------------------------------------
1 | import { SimConnectConnection } from '../src/SimConnectConnection';
2 | import { Protocol } from '../src/enums/Protocol';
3 | import { expectBufferContent } from './utils';
4 |
5 | test('The packet content when loading a flight is correctly formatted', () => {
6 | const MockSocket = jest.fn();
7 | const scc = new SimConnectConnection('', Protocol.KittyHawk);
8 | // @ts-ignore
9 | scc._clientSocket = new MockSocket();
10 | scc._clientSocket.write = MockSocket.bind(null);
11 |
12 | scc.flightLoad('C:\\Users\\Øystein\\flightplan');
13 |
14 | expectBufferContent(MockSocket.mock.calls[1][0], expectedMessageContent);
15 | });
16 |
17 | /** *
18 | * This sample was found by inspecting the data sent from an application that uses
19 | * the official SDK. This tool was used: https://github.com/Dragonlaird/SimConnect_Proxy
20 | */
21 | const expectedMessageContent = `
22 | 14 01 00 00 05 00 00 00
23 | 3D 00 00 F0 02 00 00 00
24 | 43 3A 5C 55 73 65 72 73
25 | 5C C3 98 79 73 74 65 69
26 | 6E 5C 66 6C 69 67 68 74
27 | 70 6C 61 6E 00 00 00 00
28 | 00 00 00 00 00 00 00 00
29 | 00 00 00 00 00 00 00 00
30 | 00 00 00 00 00 00 00 00
31 | 00 00 00 00 00 00 00 00
32 | 00 00 00 00 00 00 00 00
33 | 00 00 00 00 00 00 00 00
34 | 00 00 00 00 00 00 00 00
35 | 00 00 00 00 00 00 00 00
36 | 00 00 00 00 00 00 00 00
37 | 00 00 00 00 00 00 00 00
38 | 00 00 00 00 00 00 00 00
39 | 00 00 00 00 00 00 00 00
40 | 00 00 00 00 00 00 00 00
41 | 00 00 00 00 00 00 00 00
42 | 00 00 00 00 00 00 00 00
43 | 00 00 00 00 00 00 00 00
44 | 00 00 00 00 00 00 00 00
45 | 00 00 00 00 00 00 00 00
46 | 00 00 00 00 00 00 00 00
47 | 00 00 00 00 00 00 00 00
48 | 00 00 00 00 00 00 00 00
49 | 00 00 00 00 00 00 00 00
50 | 00 00 00 00 00 00 00 00
51 | 00 00 00 00 00 00 00 00
52 | 00 00 00 00 00 00 00 00
53 | 00 00 00 00 00 00 00 00
54 | 00 00 00 00 00 00 00 00
55 | 00 00 00 00 00 00 00 00
56 | 00 00 00 00
57 | `;
58 |
--------------------------------------------------------------------------------
/tests/utils.ts:
--------------------------------------------------------------------------------
1 | /**
2 | *
3 | * @param actual
4 | * @param expected Expected message content encoded as a string of hex-values (eg: 14 01 00 00 05 00 00 00 ...)
5 | */
6 | export function expectBufferContent(actual: Buffer, expected: string) {
7 | const expectedBuffer = toBuffer(expected);
8 | expect(formattedBufferContent(actual)).toBe(formattedBufferContent(expectedBuffer));
9 | }
10 |
11 | function toBuffer(hexData: string): Buffer {
12 | const buffer = Buffer.from(hexData.replace(/\W/g, ''), 'hex');
13 | resetPacketId(buffer);
14 | return buffer;
15 | }
16 |
17 | function resetPacketId(buffer: Buffer) {
18 | // Byte at index 12-16 is the packet ID which will vay
19 | buffer.set([0, 0, 0, 0], 12);
20 | }
21 |
22 | function formattedBufferContent(data: Buffer): string {
23 | const decodedString = data.toString();
24 | const rawBytes = data.toString('hex').replace(/(.{2})/g, ' 0x$1');
25 | return `String:\r\n${decodedString}\r\nBytes:\r\n${rawBytes}`;
26 | }
27 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": ["tests/**/*", "utils/**/*"]
4 | }
5 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "target": "es2015",
5 | "outDir": "./dist",
6 | "strict": true,
7 | "moduleResolution": "node",
8 | "allowSyntheticDefaultImports": true,
9 | "declaration": true,
10 | "declarationMap": true,
11 | "inlineSourceMap": true,
12 | "inlineSources": true
13 | },
14 | "include": ["src/**/*", "tests/**/*", "utils/**/*"]
15 | }
16 |
--------------------------------------------------------------------------------
/utils/packetInspectorProxy.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * To run this file: "npx ts-node .\utils\packetInspectorProxy.ts"
3 | *
4 | * Starts a proxy server that can be used to inspect packages sent between
5 | * the SimConnect server and a client application. Inspired by
6 | * https://github.com/Dragonlaird/SimConnect_Proxy
7 | *
8 | * Requires SimConnect network setup. Create a SimConnect.xml in the following directory:
9 | * X:\Users\\AppData\Local\Packages\Microsoft.FlightSimulator_**********\LocalCache
10 | *
11 | *
12 | *
13 | * SimConnect.xml
14 | *
15 | * IPv4
16 | * local
17 | * 500
18 | * 64
19 | * 41088
20 | * 0.0.0.0
21 | *
22 | *
23 | *
24 | *
25 | * Create a SimConnect.cfg file next to the client application .exe:
26 | *
27 | * [SimConnect]
28 | * Protocol=Ipv4
29 | * Port=1337 # Must match the proxy server's port number
30 | * Address=127.0.0.1
31 | */
32 |
33 | import * as net from 'net';
34 |
35 | const proxyHost = '127.0.0.1';
36 | const proxyPort = 1337;
37 |
38 | const simConnectHost = '127.0.0.1';
39 | const simConnectPort = 500; // Must match the port number in SimConnect.xml
40 |
41 | const server = net.createServer(clientSocket => {
42 | const targetSocket = net.connect(simConnectPort, simConnectHost, () => {
43 | console.log(`Proxy connected to target server: ${simConnectHost}:${simConnectPort}`);
44 |
45 | clientSocket.on('data', data => {
46 | console.log(
47 | `[Application -> SimConnect] ${data.length} bytes ::::::::::::::::::::::::\n`
48 | );
49 | const hexString = formatAndPrint(data);
50 |
51 | targetSocket.write(data);
52 | targetSocket.write(Buffer.from(hexString, 'hex')); // Forwarding the data to the target server
53 | });
54 |
55 | targetSocket.on('data', data => {
56 | console.log(
57 | `[SimConnect -> Application] ${data.length} bytes ::::::::::::::::::::::::\n`
58 | );
59 | const hexString = formatAndPrint(data);
60 |
61 | clientSocket.write(data);
62 | clientSocket.write(Buffer.from(hexString, 'hex')); // Forwarding the data to the client
63 | });
64 |
65 | clientSocket.on('end', () => {
66 | console.log('Client disconnected');
67 | targetSocket.end();
68 | });
69 |
70 | targetSocket.on('end', () => {
71 | console.log('Target server disconnected');
72 | clientSocket.end();
73 | });
74 |
75 | clientSocket.on('error', err => console.error(`Client socket error: ${err.message}`));
76 | targetSocket.on('error', err => console.error(`Target socket error: ${err.message}`));
77 | });
78 |
79 | clientSocket.on('end', () => {
80 | console.log('Client disconnected');
81 | targetSocket.end();
82 | });
83 |
84 | clientSocket.on('error', err => console.error(`Client socket error: ${err.message}`));
85 | });
86 |
87 | server.listen(proxyPort, proxyHost, () => {
88 | console.log(`Proxy server listening on ${proxyHost}:${proxyPort}`);
89 | });
90 |
91 | function formatAndPrint(data: Buffer): string {
92 | const hexString = data.toString('hex');
93 | for (let i = 0; i < hexString.length; i += 32) {
94 | const slice = hexString.slice(i, i + 32);
95 | const utf8String = Buffer.from(slice, 'hex').toString('utf-8');
96 | console.log(`${slice.match(/.{1,8}/g)?.join(' ')}\t\t${utf8String}`);
97 | }
98 | console.log('\n\n');
99 | return hexString; // Return the original hex string for forwarding
100 | }
101 |
--------------------------------------------------------------------------------