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');
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!');
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);
// ...
}
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.