Skip to main content

SimVars

Introduction

One of the primary ways by which addons can communicate with MSFS is via Simulation Variables, or SimVars. These variable contain data and information about the state of the simulator itself, your airplane, and various other components of MSFS.

From Javascript, you can utilize several of the types of SimVars available in MSFS.

Types of SimVars

A Vars

A vars are normal MSFS simulation variables which can be queried. Historically, they have been called A vars due to needing to prefix those SimVars with the text A:, but this is not required from the Javascript framework. To query a SimVar, supply the SimVar name and the units you would like to return the value in:

const indicatedAirspeed = SimVar.GetSimVarValue('INDICATED AIRSPEED', 'knots');

Some A vars in the sim can also take an index, such as with engine SimVars. This index can be appended to the end of the SimVar to specify which in a collection of items the value should be retrieved for:

const engineN1 = SimVar.GetSimVarValue('TURB ENG N1:1', 'percent');
info

While you can read the value of any A var, only certain A vars allow you to directly set their values. Attempting to set the value of a read-only A var will fail silently.

L Vars

L vars are user-settable values that can have any name, and are prefixed with the text L: in their name. These variables are used to store addon specific information that is to be shared with other parts of the addon or with the outside world.

const isInMenuMode = SimVar.GetSimVarValue('L:IS_MENU_ACTIVE', 'bool');

L vars must be properly prefixed and their name must be a contiguous string with no spaces. Setting an L var for the first time creates the variable; it is not necessary to define them anywhere ahead of time. All L vars can hold only numeric data, and not arbitrary string or binary data.

B Vars

B vars are values associated with Model Behaviors Input Events and are prefixed with B:. Each Input Event defined by Model Behaviors has a corresponding B var with the name B:[Input Event Name] that stores the value for that Input Event. Input Events are typically used to capture and manage the states of various user-interactable cockpit elements, such as switches, knobs, levers, etc. Therefore, B vars are a convenient method to access the states of these cockpit elements.

const taxiLightSwitchState = SimVar.GetSimVarValue('B:LIGHTING_Taxi_Switch', 'number');

B vars can hold only numeric data. B vars are user-settable, but require a special syntax to do so.

Setting SimVars in JS

Setting a SimVar is very straightforward:

SimVar.SetSimVarValue('GENERAL ENG THROTTLE LEVER POSITION:1', 'percent', 100);

However, do note that setting SimVars via JS is an asynchronous operation that is not guaranteed to finish by the time the next line of code is run. SetSimVarValue() returns a Promise<void>, which allows you to wait until the command has been accepted to run additional code:

//Using .then
SimVar.SetSimVarValue('LIGHT NAV', 'bool', true)
.then(() => console.log('Nav light is on!'));

//Using async/await
await SimVar.SetSimVarValue('LIGHT NAV', 'bool', true);
console.log('Nav light is on!');
caution

For some SimVars, even awaiting the Promise will not guarantee the update is complete. Calls into the sim are cached and run at frame end for performance reasons and not all sim systems are synchronous. Nonetheless, we still highly recommend using the Promise form, which yeilds much more consistent results for these cases.

Most usages of setting SimVars will not fall into this case, where explicit post-set timings are required.

Setting B Vars

Setting the value of a B Var is a bit different from setting A Vars and L Vars. The usual syntax does not work for B Vars:

// Doesn't work!
SimVar.SetSimVarValue('B:LIGHTING_Taxi_Switch', 'number', 1);

Instead, you must "set" or invoke special B Var events (also known as B events) using SetSimVarValue(), similar to how key events can be invoked using the same API. All B Vars have three associated B events:

  • Set: sets the value of a B Var.
  • Inc: increments the value of a B Var.
  • Dec: decrements the value of a B Var.

The exact behavior of each of the above events depends on its associated Input Event/B Var. For example, Set may clamp values to a certain range, and Inc may increment values with or without wrapping.

To invoke a B event, call SetSimVarValue() with the name of the B var suffixed with the event name:

// Invokes the 'Set' event on the 'B:LIGHTING_Taxi_Switch' B Var with a parameter of 1.
SimVar.SetSimVarValue('B:LIGHTING_Taxi_Switch_Set', 'number', 1);

Some B Vars have additional custom events defined for them (called bindings). These events can be invoked in the same manner as the default events. For example, if the On binding is defined as an alias for invoking Set with a parameter of 1, then the following is equivalent to the previous example:

SimVar.SetSimVarValue('B:LIGHTING_Taxi_Switch_On', 'number', 0);

Bridging SimVars to the Event Bus

SimVars are the primary means by which you will retrieve information from the simulator. However, calls to SimVar.GetSimVarValue() incur a non-negligible performance cost. Because of this, we want to minimize the number of times we call SimVar.GetSimVarValue(). Additionally, there are many scenarios when we would like to use event-driven logic with SimVars by watching the value of a SimVar and executing code only when it changes. We can solve both of these issues by bridging SimVars to the event bus.

Using SimVarPublisher

We can code the bridge manually as described in the section linked above, but doing this for many SimVars will quickly become tedious. Instead, we can use the SimVarPublisher class to simplify the process. To use SimVarPublisher, we must specify a set of SimVars to read and then take care of some initialization and housekeeping tasks:

import { EventBus, SimVarPublisher } from '@microsoft/msfs-sdk';

interface SpeedEvents {
indicated_airspeed: number;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

private readonly airspeedPublisher = new SimVarPublisher<SpeedEvents>(new Map([
['indicated_airspeed', { name: 'AIRSPEED INDICATED', type: 'knots' }]
]), this.bus);

// ...

public connectedCallback(): void {
super.connectedCallback();

this.airspeedPublisher.startPublish();
}

protected Update(): void {
super.Update();

this.airspeedPublisher.onUpdate();
}
}

In the above example, we've created a SimVarPublisher that reads the AIRSPEED INDICATED SimVar with units knots and publishes the value to the indicated_airspeed topic on the event bus. The call to startPublish() tells the publisher to start reading and publishing SimVar values the next time it is updated. Finally, we call onUpdate() in the instrument's update loop to update the publisher once on every instrument update cycle.

Publishing just one SimVar doesn't seem so impressive, but we can quite easily add additional SimVars:

interface AirDataEvents {
indicated_airspeed: number;
true_airspeed: number;
mach: number;
pressure_altitude: number;
vertical_speed: number;
total_air_temperature: number;
static_air_temperature: number;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

private readonly airspeedPublisher = new SimVarPublisher<AirDataEvents>(new Map([
['indicated_airspeed', { name: 'AIRSPEED INDICATED', type: 'knots' }],
['true_airspeed', { name: 'AIRSPEED TRUE', type: 'knots' }],
['mach', { name: 'AIRSPEED MACH', type: 'number' }],
['pressure_altitude', { name: 'PRESSURE ALTITUDE', type: 'feet' }],
['vertical_speed', { name: 'VERTICAL SPEED', type: 'feet per minute' }],
['total_air_temperature', { name: 'TOTAL AIR TEMPERATURE', type: 'celsius' }],
['static_air_temperature', { name: 'AMBIENT TEMPERATURE', type: 'celsius' }]
]), this.bus);

// ...
}
tip

SimVarPublisher will only read and publish the SimVars tied to topics that have actually been subscribed to in order to avoid making useless calls to SimVar.GetSimVarValue().

Publishing Boolean Values

SimVar.GetSimVarValue() does not return values of type boolean, even if you pass in 'bool' or 'boolean' as the units argument. Instead, it will return the numeric values 0 and 1 to represent false and true, respectively. In order to avoid type confusion and to allow SimVar values of type boolean to be published on the bus where a boolean type is expected, SimVarPublisher automatically converts numeric SimVar values to their boolean equivalents when 'bool' or 'boolean' is specified as the SimVar unit type.

interface OnGroundEvents {
is_on_ground: boolean;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

private readonly airspeedPublisher = new SimVarPublisher<OnGroundEvents>(new Map([
// A boolean (true/false) will be published to 'is_on_ground' instead of 0/1.
['is_on_ground', { name: 'SIM ON GROUND', type: 'bool' }]
]), this.bus);

// ...
}

Publishing Indexed SimVars

SimVarPublisher supports publishing SimVars with arbitrary indexes:

interface EngineEvents {
n1: number;

[n1: `n1_${number}`]: number;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

private readonly airspeedPublisher = new SimVarPublisher<EngineEvents>(new Map([
['n1', { name: 'TURB ENG N1:#index#', type: 'percent', indexed: true }]
]), this.bus);

// ...
}

In the above example, the n1 topic is flagged as an indexed SimVar topic by setting indexed to true in its publisher entry. When an entry is flagged as indexed, the publisher will automatically expand the entry to a series of indexed topics and SimVar names, with indexes beginning at 0. The indexed topics are generated using the pattern `${topic}_${index}`, and the indexed SimVar names are generated by replacing the #index# macro with the numeric index. So, in the above example the SimVars TURB ENG N1:0, TURB ENG N1:1, TURB ENG N1:2, ... will be published to the topics n1_0, n1_1, n1_2, ...

Additionally, the publisher will publish the SimVar at a default index (equal to 1 unless otherwise specified) to the unsuffixed form of the topic. So, in the above example the TURB ENG N1:1 SimVar will also be published to the topic n1. A different default index may be specified through the defaultIndex property in the publisher entry. Publishing of the default index can be omitted altogether by specifying null for defaultIndex:

interface EngineEventsRoot {
n1: number;
}

interface EngineEvents {
[n1: `n1_${number}`]: number;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

// The second type parameter on SimVarPublisher lets it know it should accept entries with topics defined in the
// EngineEventsRoot interface in addition to the ones defined in EngineEvents interface. This is needed because the
// unsuffixed 'n1' topic is not actually published and so doesn't appear in the EngineEvents interface.
private readonly airspeedPublisher = new SimVarPublisher<EngineEvents, EngineEventsRoot>(new Map([
['n1', { name: 'TURB ENG N1:#index#', type: 'percent', indexed: true, defaultIndex: null }]
]), this.bus);

// ...
}

If you want only certain indexes of a SimVar to be published, pass an iterable of indexes to the entry's indexed property instead of true:

interface EngineEventsRoot {
n1: number;
}

interface EngineEvents {
n1_1: number;
n1_2: number;
}

class MyInstrument extends BaseInstrument {
private readonly bus = new EventBus();

private readonly airspeedPublisher = new SimVarPublisher<EngineEvents, EngineEventsRoot>(new Map([
// Only publish SimVar for engines 1 and 2.
['n1', { name: 'TURB ENG N1:#index#', type: 'percent', indexed: [1, 2], defaultIndex: null }]
]), this.bus);

// ...
}

More Information

For more information about the various SimVars available in MSFS as well as a full list and description of each A var, please see the MSFS SDK Documentation.