|
| 1 | +# vmsACARS Plugin Development Kit (PDK) |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The plugins and scripts for vmsACARS are written in Typescript, and then transpiled to JS. |
| 6 | +Typescript ensures that the interfaces required are following, and that the proper things |
| 7 | +are returned so ACARS can run them. While Typescript isn't required, it's best to use it to |
| 8 | +ensure proper values are passed - especially around enums. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +# Setup |
| 13 | + |
| 14 | +## Required: |
| 15 | + |
| 16 | +- nodejs/npm |
| 17 | +- Typescript |
| 18 | +- Gulp |
| 19 | + |
| 20 | +Run: |
| 21 | + |
| 22 | +```shell |
| 23 | +npm install |
| 24 | +``` |
| 25 | + |
| 26 | +### Customizing using the `.env` file: |
| 27 | + |
| 28 | +Next, copy the `.env.default` to `.env`. Then edit this file to change the profile name. |
| 29 | + |
| 30 | +The available options: |
| 31 | + |
| 32 | +- `ACARS_PROFILE_NAME` - The default profile to use for testing |
| 33 | +- `ACARS_CONFIG_PATH` - The default usually works, but you can change this to the path where you put ACARS, if you did a |
| 34 | + local install |
| 35 | +- `ACARS_SCRIPTS_PATH` - Uses the `ACARS_PROFILE_NAME` to build the path to where the scripts should be sent after a |
| 36 | + build |
| 37 | +- `ACARS_DIST_ZIP` - The distribution filename |
| 38 | + |
| 39 | +--- |
| 40 | + |
| 41 | +### Commands |
| 42 | + |
| 43 | +Then there are multiple commands you can use: |
| 44 | + |
| 45 | +#### To run a build: |
| 46 | + |
| 47 | +This creates a `dist` directory, with all of the JS files in it |
| 48 | + |
| 49 | +```shell |
| 50 | +npm run build |
| 51 | +``` |
| 52 | + |
| 53 | +This doesn't copy it anywhere, just runs a compile and build |
| 54 | + |
| 55 | +### Create a distribution file |
| 56 | + |
| 57 | +This creates a `dist.zip` (you can rename it in the `.env` file) after running a compile. |
| 58 | +You can modify the `gulpfile.mjs` to include other files in the `dist/` directory - this |
| 59 | +directory is simply zipped and placed into the `dist/` directory. You can then configure |
| 60 | +Github Actions to then upload this zip somewhere for ACARS to download. |
| 61 | + |
| 62 | +#### Automatically build and copy to ACARS |
| 63 | + |
| 64 | +This will setup a watch, and then automatically transpile and then copy the contents of the `dist` folder |
| 65 | +into the `ACARS_PROFILE_PATH` directory that's defined in the `.env` file. |
| 66 | + |
| 67 | +```shell |
| 68 | +npm run dev |
| 69 | +``` |
| 70 | + |
| 71 | +It's recommended to run this *after* you've started ACARS, or, in the ACARS configuration, disable the |
| 72 | +remote-download of configs: |
| 73 | + |
| 74 | +> TODO: Guide on how to disable remote config downloading |
| 75 | +
|
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +# Development Documentation |
| 80 | + |
| 81 | +There are several core files/interfaces that are included: |
| 82 | + |
| 83 | +### `src/global.d.ts` |
| 84 | + |
| 85 | +This describes the globally available functions, including the logging methods available through `console` and |
| 86 | +`Acars`. |
| 87 | + |
| 88 | +### `src/types.d.ts` |
| 89 | + |
| 90 | +This contains all of the base types: |
| 91 | + |
| 92 | +- `Pirep` - data that's available about a PIREP, and it's associated interfaces (`Airport`, `Runway`, etc) |
| 93 | +- `Telemetry` - telemetry information that's come out of the simulator |
| 94 | +- `User` - information about the current user |
| 95 | + |
| 96 | +It also includes other detailed type information, for example `Length`, so you can retrieve that type of information. |
| 97 | + |
| 98 | +## Aircraft Rules: |
| 99 | + |
| 100 | +Aircraft rules are required to inherit the `AircraftConfig` abstract class. An example class would look like: |
| 101 | + |
| 102 | +```typescript |
| 103 | +import { AircraftConfigSimType, AircraftFeature, FeatureType } from '../defs' |
| 104 | +// Additional mports are left out for now |
| 105 | + |
| 106 | +export default class FenixA320 extends AircraftConfig { |
| 107 | + meta: Meta = { |
| 108 | + id: 'fenix_a320', |
| 109 | + name: 'Fenix A320', |
| 110 | + sim: AircraftConfigSimType.MsFs, |
| 111 | + enabled: true, |
| 112 | + priority: 2, |
| 113 | + } |
| 114 | + |
| 115 | + features: FeatureAddresses = { |
| 116 | + // Aircraft feature |
| 117 | + [AircraftFeature.BeaconLights]: { |
| 118 | + 'lvar name': FeatureType.Int, |
| 119 | + }, |
| 120 | + } |
| 121 | + |
| 122 | + flapNames: FlapNames = { |
| 123 | + 0: 'UP', |
| 124 | + 1: 'CONF 1', |
| 125 | + 2: 'CONF 1+F', |
| 126 | + 3: 'CONF 2', |
| 127 | + 4: 'CONF 3', |
| 128 | + 5: 'FULL', |
| 129 | + } |
| 130 | + |
| 131 | + match(title: string, icao: string, config_path: string): boolean { |
| 132 | + // Check the aircraft title and return true/false if this matches |
| 133 | + } |
| 134 | + |
| 135 | + beaconLights(lvar_value: number): FeatureState { |
| 136 | + // Check the lvar_value if the |
| 137 | + } |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +The configuration is a class which has a few different components. |
| 142 | + |
| 143 | +1. `meta`, which gives some general information about the configuration: |
| 144 | + - `name` - a name for this script |
| 145 | + - `sim` - The simulator it's for |
| 146 | + - `AircraftConfigSimType.XPlane` |
| 147 | + - `AircraftConfigSimType.Fsuipc` |
| 148 | + - `AircraftConfigSimType.MsFs` |
| 149 | + - `enabled` |
| 150 | + - `priority` - from 1 (lowest) to 10 (highest). If there are multiple rules which match this, then which one takes |
| 151 | + priority. All the built-in rules are at a priority 1, and aircraft specifics rules are priority 2. I recommend |
| 152 | + using a priority of 3 or higher. More on this below |
| 153 | + |
| 154 | +2. `features` - this is the type `FeatureAddresses` - see `defs.ts` for the definitions |
| 155 | + - MSFS - the lookups you enter are LVars |
| 156 | + - X-Plane - the looks ups are via datarefs |
| 157 | + - FSUIPC - the lookups are offsets |
| 158 | +3. `match()` |
| 159 | + - This needs to return a boolean |
| 160 | + - A method (`match()`) which passes some information about the starting aircraft |
| 161 | + - For MSFS, it's the aircraft ICAO |
| 162 | + - For FSX/P3d, the value looked at is the aircraft title field, offset `0x3D00` |
| 163 | + - For X-Plane, the value looked at is `sim/aircraft/view/acf_descrip` |
| 164 | + - This method can be used to determine if this rule should match |
| 165 | +4. Methods for the different features (see below) |
| 166 | + - The maps - a group of datarefs or offsets which constitute that feature being "on" or "enabled" |
| 167 | + |
| 168 | +In the above example, for the Fenix A320, the landing lights are controlled by two datarefs, both of which the |
| 169 | +values need to be 1 or 2 for the landing lights to be considered "on". |
| 170 | + |
| 171 | +### Features |
| 172 | + |
| 173 | +Features are essentially stored in a dictionary of dictionaries, of type `FeatureAddresses`: |
| 174 | + |
| 175 | +```typescript |
| 176 | +features: FeatureAddresses = { |
| 177 | + // Aircraft feature |
| 178 | + [AircraftFeature.BeaconLights]: { |
| 179 | + 'lvar name': FeatureType.Int, |
| 180 | + }, |
| 181 | +} |
| 182 | +``` |
| 183 | + |
| 184 | +In the above example: |
| 185 | + |
| 186 | +- `AircraftFeature.BeaconLights` is an enum value of the feature type. It's put in `[]` because it's a variable name |
| 187 | +- It's set to an object, where the keys are the lookup address or lvar. |
| 188 | +- `FeatureType.Int` - is the type of value that's returned. |
| 189 | + |
| 190 | +The different features available are: |
| 191 | + |
| 192 | +- beaconLights |
| 193 | +- landingLights |
| 194 | +- logoLights |
| 195 | +- navigationLights |
| 196 | +- strobeLights |
| 197 | +- taxiLights |
| 198 | +- wingLights |
| 199 | +- flaps |
| 200 | + |
| 201 | +The different features contain how to look up the value, and the type. You can have multiple variables to be |
| 202 | +read and looked at for a feature. Each feature then corresponds to a method which is called to return if |
| 203 | +that feature is on or off. That method will have the equivalent number of arguments for each data reference |
| 204 | + |
| 205 | +Example: |
| 206 | + |
| 207 | +```typescript |
| 208 | + |
| 209 | +export default class Example extends AircraftConfig { |
| 210 | + features: FeatureAddresses = { |
| 211 | + // Aircraft feature |
| 212 | + [AircraftFeature.BeaconLights]: { |
| 213 | + 'sample/dataref/1': FeatureType.Bool, |
| 214 | + 'sample/dataref/2': FeatureType.Bool, |
| 215 | + }, |
| 216 | + } |
| 217 | + |
| 218 | + beaconLights(dataref_1: boolean, dataref_2: boolean): FeatureState { |
| 219 | + if (dataref_1 && dataref_2) { |
| 220 | + return true; |
| 221 | + } |
| 222 | + |
| 223 | + return false; |
| 224 | + } |
| 225 | +} |
| 226 | +``` |
| 227 | + |
| 228 | +### Ignoring Features |
| 229 | + |
| 230 | +To ignore a feature in the rules (for example, if a feature doesn't work properly), set the feature to false: |
| 231 | + |
| 232 | +```typescript |
| 233 | +import { AircraftFeature } from './defs' |
| 234 | + |
| 235 | +features: FeatureAddresses = { |
| 236 | + // Aircraft feature |
| 237 | + [AircraftFeature.BeaconLights]: { |
| 238 | + 'lvar name': FeatureType.Int, |
| 239 | + }, |
| 240 | + [AircraftFeature.LandingLights]: false, |
| 241 | +} |
| 242 | +``` |
| 243 | + |
| 244 | +### Mixed priorities |
| 245 | + |
| 246 | +If there are two scripts which match a particular aircraft, and a feature is omitted, it will use the lower priority |
| 247 | +one in place. For example: |
| 248 | + |
| 249 | +```typescript |
| 250 | +import { FeatureAddresses } from './aircraft' |
| 251 | + |
| 252 | +export default class Example extends AircraftConfig { |
| 253 | + meta: Meta = { |
| 254 | + // ... |
| 255 | + priority: 1 |
| 256 | + } |
| 257 | + |
| 258 | + features: FeatureAddresses = { |
| 259 | + [AircraftFeature.BeaconLights]: { |
| 260 | + 'sample/dataref/1': FeatureType.Bool, |
| 261 | + 'sample/dataref/2': FeatureType.Bool, |
| 262 | + }, |
| 263 | + [AircraftFeature.LandingLights]: { |
| 264 | + 'sample/landing/light/1': FeatureType.Bool, |
| 265 | + 'sample/landing/light/2': FeatureType.Bool, |
| 266 | + }, |
| 267 | + } |
| 268 | +} |
| 269 | + |
| 270 | +export default class ExampleOverride { |
| 271 | + meta: Meta = { |
| 272 | + // ... |
| 273 | + priority: 10 |
| 274 | + } |
| 275 | + |
| 276 | + features: FeatureAddresses = { |
| 277 | + [AircraftFeature.LandingLights]: { |
| 278 | + 'override/landing/light/1': FeatureType.Bool, |
| 279 | + 'override/landing/light/2': FeatureType.Bool, |
| 280 | + }, |
| 281 | + } |
| 282 | +} |
| 283 | +``` |
| 284 | + |
| 285 | +In this case, the lookups used for the rules will be: |
| 286 | + |
| 287 | +- beaconLights - `sample/dataref/1|2` |
| 288 | +- landingLights - `override/landing/light/1|2` |
0 commit comments