Skip to main content

G3X Touch External Navigators

Introduction

The G3X Touch can interface with other navigation sources, or external navigators, in the same aircraft. The G3X Touch allows the user to view and interact with the information provided by external navigators through the G3X interface and can also route external navigation guidance to an autopilot. This allows the G3X Touch to function as a central "hub" that presents all pertinent navigation information to the user and allows control of key navigation and autopilot functions through a convenient large-screen touch interface.

External navigators can provide one or both of the following types of data to the G3X Touch:

  • NAV radio data
  • GPS navigation data (includes flight planning, LNAV, VNAV, and glidepath data)

Up to two external navigators can be configured for the G3X Touch. If no external navigators are configured, then the G3X Touch is limited to navigating using its internal VFR-only GPS navigation source.

info

The G3X Touch can only interface with NAV radios through external navigators. The G3X Touch's NAV1 radio is the NAV radio from external navigator 1 and NAV2 is the NAV radio from external navigator 2. Because of this, even standalone NAV radio units without a GPS component (e.g. GNC-series radios) connected to the G3X Touch take up an external navigator slot.

Configuring the G3X Touch to work with External Navigators

The G3X Touch must be explicitly configured to accept data from external navigators. All configuration is done through panel.xml.

The Nav Radio Configuration section applies when configuring external navigators that provide NAV radio data, and the FMS Configuration and External Flight Plan Source Configuration sections apply when configuring external navigators that provide GPS navigation data.

NAV radio sources from external navigators are configured through the <Radios> tag. One child <Nav> tag should be added to <Radios> for each external navigator that provides NAV radio data. The index attribute of each <Nav> tag should match the index of its corresponding external navigator. If an external navigator does not provide NAV radio data, then the corresponding <Nav> tag should be omitted.

info

External navigator 1 is assumed to use the sim's NAV1 radio and external navigator 2 is assumed to use the sim's NAV2 radio.

FMS Configuration

The presence of external navigators that provide GPS navigation data necessarily means that independent instances of certain flight planning and navigation code are running simultaneously in the same aircraft. One set will be running on the G3X Touch, and one set on each GPS-capable external navigator. Care must be taken to ensure these instances do not conflict with one another.

To that end, the following options should be explicitly configured in the <Fms> tag:

  • LNAV Index: The index of the LNAV instance used by the G3X Touch internally. Must be distinct from any external navigators.
  • Use Sim OBS State: Whether the G3X Touch should use the sim's native OBS state for its internal LNAV instance. This should be disabled if any external navigators are using the sim's native OBS state.
  • VNAV Index: The index of the VNAV instance used by the G3X Touch internally. Must be distinct from any external navigators.
  • Sync to Sim: Whether the G3X Touch should sync its internal flight plan back to the sim's native flight planning system. This should be disabled if any external navigators are syncing their flight plans to the sim.

External Flight Plan Source Configuration

External navigators that provide GPS navigation data are considered external flight plan sources. These are declared using the <ExternalSources> tag. One <Source> tag should be created as a child of <ExternalSources> for each external flight plan source.

The following options must be defined for each external flight plan source:

  • Index: The external navigator index of the source.
  • Flight Planner ID: The ID of the flight planner used by the source.
  • Flight Path Calculator ID: The ID of the flight path calculator used by the source.
  • LNAV Index: The index of the LNAV instance used by the source.
  • Use Sim OBS State: Whether the source uses the sim's native OBS state for its internal LNAV instance.
  • VNAV Index: The index of the VNAV instance used by the source.
  • Autopilot Guidance Index: The index of the autopilot guidance published by the source.
  • CDI ID: The ID of the CDI used by the source.

Example

The following is an example excerpt from a panel.xml file that configures a G3X Touch with two external navigators. External navigator 1 provides both NAV radio and GPS navigation data, while external navigator 2 provides only NAV radio data.

<Radios>
<Nav index="1" />
<Nav index="2" />
</Radios>

<Fms
lnav-index="0"
use-sim-obs="false"
vnav-index="0"
sync-to-sim="false"
>
<ExternalSources>
<Source
index="1"
fpl-id="gns-1"
flight-path-calc-id="gns-1"
lnav-index="1"
use-sim-obs="true"
vnav-index="1"
ap-guidance-index="1"
cdi-id="gns1"
/>
</ExternalSources>
</Fms>

Configuring External Navigators to work with the G3X Touch

External navigators must be configured to correctly send data to the G3X Touch.

If an external navigator only provides NAV radio data, then the only configuration required is to set up a sim NAV radio with the same index as the external navigator's index. Sim radios are configured using the airplane's systems.cfg file.

If an external navigator provides GPS navigation data, then it must be configured to send CDI source, flight planning, LNAV, and optionally VNAV and glidepath data to the G3X Touch. Please refer to the following sections for more details.

CDI Source

External navigators that provide GPS navigation data must inform the G3X Touch of their current CDI source. CDI source can either be NAV or GPS. Even if an external navigator only provides GPS navigation data and not NAV radio data, it still must send CDI source data (in this case the source would always be GPS).

CDI source state should be sent to the G3X Touch using the CDI source API defined in the MSFS SDK. To use this API, an ID for the external navigator's CDI must be chosen. The ID can be any string, including the empty string, that is not equal to 'g3x' (which is the ID used by the G3X's CDI).

info

CDI IDs should be unique for each independent CDI.

Once a CDI ID is chosen, the next step is to publish the CDI source to the event bus topic cdi_select[suffix] (defined by the CdiEvents interface), where [suffix] is determined by the CDI ID: the empty string when the ID is also the empty string, and _[ID] otherwise, where [ID] is the ID string. When publishing the source to the event bus, it should be synced across instruments and cached:

import {
CdiEvents, CdiUtils, EventBus, NavSourceId, NavSourceType
} from '@microsoft/msfs-sdk';

const bus = new EventBus();
const publisher = bus.getPublisher<CdiEvents>();

const cdiId = 'gns1'; // Must be different from the G3X Touch's CDI ID (g3x).
const topic = `cdi_select${CdiUtils.getEventBusTopicSuffix(cdiId)}` as const;

// Publish CDI source as NAV. The index is not relevant for the G3X Touch.
publisher.pub(topic, { type: NavSourceType.Nav, index: 1 }, true, true);

// Publish CDI source as GPS. The index is not relevant for the G3X Touch.
publisher.pub(topic, { type: NavSourceType.Gps, index: 1 }, true, true);

If you wish to allow the G3X Touch to control the external navigator's CDI source (only applicable for certain configurations of CDI auto-switch), then the external navigator must also be configured to respond to the cdi_src_set[suffix] event bus topic (defined by the CdiControlEvents interface).

caution

When the G3X Touch is configured with an internal autopilot, it intercepts the AP_NAV_SELECT_SET and TOGGLE_GPS_DRIVES_NAV1 key events to change its CDI source. Therefore, under these circumstances external navigators should not use the same key events to control their own CDI source state.

Flight Planning

In order to send valid flight plan data to the G3X Touch, the external navigator must use the flight planning system provided by the MSFS SDK. An instance of FlightPlanner should be created with an ID string that is not 'g3x'. The flight planner should use a FlightPathCalculator instance that is created with ID string that is also not 'g3x'. The calculator should have an initialization sync role of primary (see the example below). If the external navigator shares its flight path calculator ID with another instrument, then only one instance among all the calculators of that ID need to have a sync role of primary.

Here is some example code that creates a FlightPlanner instance compatible with the G3X Touch:

import {
EventBus, FacilityLoader, FacilityRepository, FlightPathCalculator, FlightPlanner
} from '@microsoft/msfs-sdk';

const bus = new EventBus();
const facLoader = new FacilityLoader(FacilityRepository.getRepository(bus));

const flightPathCalculator = new FlightPathCalculator(
facLoader,
{
id: 'gns',
initSyncRole: 'primary',
// Other options as desired.
},
bus
);

const flightPlanner = FlightPlanner.getPlanner('gns', bus, { calculator: flightPathCalculator });

The simplest way for the external navigator to generate LNAV data compatible with the G3X Touch is to create an instance of LNavComputer with the GarminObsLNavModule and an instance of NavdataComputer. Certain event bus publishers also need to be created for these classes to work correctly (see the example code).

Here is some example code that creates a set of LNAV classes compatible with the G3X Touch:

import {
InstrumentBackplane, LNavComputer, LNavObsSimVarPublisher, LNavSimVarPublisher
} from '@microsoft/msfs-sdk';
import {
GarminAPUtils, GarminObsLNavModule, NavdataComputer
} from '@microsoft/msfs-garminsdk';

// These instruments/publishers must be used to publish topics to the event bus in order for the LNAV classes to
// function properly.
const backplane = new InstrumentBackplane();
const clock = new Clock(bus);
const lNavPublisher = new LNavSimVarPublisher(bus);
const lNavObsPublisher = new LNavObsSimVarPublisher(bus);
// The names of the instruments/publishers used to add them to the backplane must be unique,
// but are otherwise arbitrary.
backplane.addInstrument('clock', clock);
backplane.addPublisher('lnav', lNavPublisher);
backplane.addPublisher('lnavObs', lNavObsPublisher);

const lnavIndex = 0; // Must be different from the G3X Touch's internal LNAV index.
const useSimObsState = true; // As desired.
const maxBankAngle = 25; // Should be the same value as the autopilot's configured maximum bank angle for FMS/GPS mode (not a strict requirement).
const intercept = GarminAPUtils.lnavIntercept; // As desired.

const lnavComputer = new LNavComputer(
lnavIndex,
bus,
flightPlanner,
new GarminObsLNavModule(lnavIndex, bus, flightPlanner, {
maxBankAngle,
intercept,
useSimObsState
}),
{
maxBankAngle,
intercept,
isPositionDataValid: true, // As desired.
hasVectorAnticipation: true
}
);

const navdataComputer = new NavdataComputer(bus, flightPlanner, facLoader, {
lnavIndex,
useSimObsState,
vnavIndex: 0,
useVfrCdiScaling: false // As desired.
});

Once created, LNavComputer must be updated periodically by calling its update() method. It is recommended to update LNavComputer every frame using a setInterval() loop or subscribing to the event bus's simTimeHiFreq topic. These strategies bypass throttling of the Javascript rendering loop that occurs under certain situations (e.g. with a "Low" cockpit refresh rate setting or when the user is in External View) that might lead to unacceptably long intervals between updates. NavdataComputer does not need to be explicitly updated and will automatically perform its functions after creation. Finally, the InstrumentBackplane (or the individual event bus publishers if not using InstrumentBackplane) must also be initialized and updated in a loop; it is recommended to use BaseInstrument's Update() method for this.

Here is some example code showing how to update LNAV and supporting classes:

import { ClockEvents } from '@microsoft/msfs-sdk';

class MyInstrument extends BaseInstrument {
// ...

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

// Assuming all LNAV classes and publishers have been created by now.

this.backplane.init();

this.bus.getSubscriber<ClockEvents>().on('simTimeHiFreq').handle(() => {
this.lnavComputer.update();
});
}

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

this.backplane.onUpdate();
}
}

You may also choose to use your own code to generate LNAV data from the external navigator. Any custom implementation must conform to the LNAV API defined in the MSFS SDK and Garmin SDK. Namely, values must be published to all of the SimVars and event bus topics (with the correct LNAV index) defined by the following:

SimVars

Event Bus Topics

VNAV

If the external navigator supports VNAV, then it must generate VNAV data. First, a vertical path calculator should be created in the the form of an instance of SmoothingPathCalculator. The calculator must be created using a specific set of configuration options (see the example below).

Here is some example code that creates a vertical path calculator compatible with the G3X Touch:

import { SmoothingPathCalculator } from '@microsoft/msfs-sdk';
import {
FmsUtils, GarminVNavUtils
} from '@microsoft/msfs-garminsdk';

const vnavIndex = 0; // Must be different from the G3X Touch's internal VNAV index.

const verticalPathCalc = new SmoothingPathCalculator(bus, flightPlanner, FmsUtils.PRIMARY_PLAN_INDEX, {
index: vnavIndex,
defaultFpa: 3,
maxFpa: 6,
isLegEligible: GarminVNavUtils.isLegVNavEligible,
shouldUseConstraint: GarminVNavUtils.shouldUseConstraint,
invalidateClimbConstraint: GarminVNavUtils.invalidateClimbConstraint,
invalidateDescentConstraint: GarminVNavUtils.invalidateDescentConstraint
});

Once a vertical path calculator is created, VNAV data can be generated by using the GarminVNavComputer class.

GarminVNavComputer requires feedback from the autopilot to function correctly. Specifically, it needs access to the autopilot's selected altitude, selected vertical speed, active lateral mode, active vertical mode, and armed vertical mode. It also needs to respond to when the autopilot attempts to activate and deactivate VNAV. If the aircraft is configured to use the G3X Touch's internal autopilot, then these autopilot data can all be retrieved from the event bus (see the example below). If the aircraft is configured with an autopilot that is not managed by the G3X Touch, then you will need to ensure the appropriate data is sent to and captured by the external navigator.

Here is some example code showing how to create a GarminVNavComputer that receives feedback from an autopilot managed by the G3X Touch:

import {
APLateralModes, APVerticalModes, AutopilotInstrument, ConsumerValue, InstrumentBackplane, MappedValue
} from '@microsoft/msfs-sdk';
import {
FmaDataEvents, FmsUtils, GarminVNavComputer, GarminVNavComputerAPValues, GarminVNavManagerEvents
} from '@microsoft/msfs-garminsdk';

// AutopilotInstrument is required to retrieve selected altitude and selected VS data from the bus.
const backplane = new InstrumentBackplane();
const apInstrument = new AutopilotInstrument(bus)
// The names of the instrument used to add it to the backplane must be unique, but is otherwise arbitrary.
backplane.addInstrument('ap', apInstrument);

const vnavIndex = 0; // Must be different from the G3X Touch's internal VNAV index.

const fmaData = ConsumerValue.create<Readonly<FmaData> | undefined>(
bus.getSubscriber<FmaDataEvents>().on('fma_data'), undefined
);

// Retrieve autopilot data from the event bus.
const apValues: GarminVNavComputerAPValues = {
selectedAltitude: ConsumerValue.create(bus.getSubscriber<APEvents>().on('ap_altitude_selected'), 0),
selectedVerticalSpeed: ConsumerValue.create(bus.getSubscriber<APEvents>().on('ap_vs_selected'), 0),
lateralActive: MappedValue.create(([fmaData]) => fmaData?.lateralActive ?? APLateralModes.NONE),
verticalActive: MappedValue.create(([fmaData]) => fmaData?.verticalActive ?? APVerticalModes.NONE),
verticalArmed: MappedValue.create(([fmaData]) => fmaData?.verticalArmed ?? APVerticalModes.NONE)
};

const vnavComputer = new GarminVNavComputer(
vnavIndex,
bus,
flightPlanner,
verticalPathCalc,
apValues,
{
primaryPlanIndex: FmsUtils.PRIMARY_PLAN_INDEX
// Other options as desired.
}
);

// Respond to attempts by the autopilot to activate/deactivate VNAV.
bus.getSubscriber<GarminVNavManagerEvents>()
.on('vnav_manager_activated').handle(() => { vnavComputer.tryActivate(); });
bus.getSubscriber<GarminVNavManagerEvents>()
.on('vnav_manager_deactivated').handle(() => { vnavComputer.tryDeactivate(); });

Once created, GarminVNavComputer must be updated periodically by calling its update() method. Like with the LNAV computer, it is recommended to update GarminVNavComputer every frame using a setInterval() loop or subscribing to the event bus's simTimeHiFreq topic. And as always, the InstrumentBackplane (or the individual event bus publishers if not using InstrumentBackplane) must also be initialized and updated in a loop; it is recommended to use BaseInstrument's Update() method for this.

Here is some example code showing how to update VNAV and supporting classes:

import { ClockEvents } from '@microsoft/msfs-sdk';

class MyInstrument extends BaseInstrument {
// ...

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

// Assuming all VNAV classes and publishers have been created by now.

this.backplane.init();

this.bus.getSubscriber<ClockEvents>().on('simTimeHiFreq').handle(() => {
this.vnavComputer.update();
});
}

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

this.backplane.onUpdate();
}
}

Glidepath

If the external navigator supports RNAV approaches, then it must generate glidepath data. Glidepath data can be generated using an instance of GarminGlidepathComputer.

Here is some example code that creates a glidepath computer compatible with the G3X Touch:

import {
FmsUtils, GarminGlidepathComputer
} from '@microsoft/msfs-garminsdk';

const vnavIndex = 0; // Must be different from the G3X Touch's internal VNAV index.

const glidepathComputer = new GarminGlidepathComputer(
vnavIndex,
bus,
flightPlanner,
{
primaryPlanIndex: FmsUtils.PRIMARY_PLAN_INDEX
// Other options as desired.
}
);

FMS

The external navigator should use an instance of Fms to manipulate flight plans. Fms ensures that the internal structure of flight plans are formatted correctly, appropriate metadata is created and maintained for flight plans, and important data is published to the event bus.

Here is some example code that creates an FMS compatible with the G3X Touch:

import { Fms } from '@microsoft/msfs-garminsdk';

const isPrimary = true; // There must be exactly one primary Fms instance per unique flight planner ID.
const lnavIndex = 0; // Must be different from the G3X Touch's internal LNAV index.
const useSimObsState = true; // As desired.
const vnavIndex = 0; // Must be different from the G3X Touch's internal VNAV index.

const fms = new Fms(
isPrimary,
bus,
flightPlanner,
verticalPathCalculator, // Optional; only required if the external navigator supports VNAV.
{
lnavIndex,
useSimObsState
// Other options as desired.
}
);

Once created, Fms should be used to carry out all modifications to the external navigator's flight plan. Direct manipulation of flight plans using FlightPlanner or FlightPlan is highly discouraged and may lead to undesired behavior.

Autopilot Guidance

The external navigator should provide LNAV, VNAV, and glidepath guidance to the G3X Touch's managed autopilot if the G3X Touch is configured with one. These guidance data should be written to the SimVars listed in the following enums:

The SimVars should be indexed (using the suffix :[index]) with a chosen autopilot guidance index. The index can be any non-negative integer, but it must be unique for each external navigator providing independent guidance and it must match the index provided to the G3X Touch when declaring external navigators in panel.xml.

If you used the LNAV, VNAV, and/or glidepath classes suggested above, then guidance data can be retrieved directly from those classes:

const lnavComputer = new LNavComputer(/* ... */);
const vnavComputer = new GarminVNavComputer(/* ... */);
const glidepathComputer = new GarminGlidepathComputer(/* ... */);

const gpsSteerCommand = lnavComputer.steerCommand.get();
const vnavGuidance = vnavComputer.guidance.get();
const vnavPathGuidance = vnavComputer.pathGuidance.get();
const glidepathGuidance = glidepathComputer.glidepathGuidance.get();
info

If the G3X Touch is not configured with an autopilot, then the external navigator can skip sending autopilot guidance in the form of the above SimVars. However, LNAV, VNAV, and glidepath data (the latter two only if supported) must still be generated even without an autopilot. This is because the G3X Touch uses those data to drive various indications that are not dependent on an autopilot, such as the CDI and VDI.

CDI Auto-Switch Guidance

If the external navigator provides both GPS navigation and NAV radio data and supports automatically switching CDI source from GPS to NAV during the final approach segment of certain approaches (e.g. ILS/LOC approaches) and the G3X Touch is configured with an autopilot, then the external navigator must provide the appropriate guidance to the G3X Touch. If this guidance is not provided and the automatic switch happens when the G3X's flight director is in GPS mode, then the flight director will revert to ROL mode due to the change in CDI source.

CDI auto-switch guidance should be provided to the G3X Touch by publishing the appropriate data to the following event bus topics. Note that all topics should be suffixed with the external navigator's index (e.g. g3x_external_nav_to_nav_armable_nav_radio_index_1 or g3x_external_nav_to_nav_armable_nav_radio_index_2). Published data should be synced to other instruments and cached.

TopicDescription
g3x_external_nav_to_nav_armable_nav_radio_indexThe index of the NAV radio that can be armed for an automatic CDI source switch. When CDI auto-switch can be armed, this should be equal to the external navigator's NAV radio index. When CDI auto-switch cannot be armed, this should be equal to -1.
g3x_external_nav_to_nav_armable_lateral_modeThe autopilot lateral mode that can be armed while automatic CDI source switch can be armed (usually APLateralModes.LOC). When CDI auto-switch cannot be armed, this should be equal to APLateralModes.NONE.
g3x_external_nav_to_nav_armable_vertical_modeThe autopilot vertical mode that can be armed while automatic CDI source switch can be armed (usually APVerticalModes.GS). When CDI auto-switch cannot be armed, this should be equal to APVerticalModes.NONE.
g3x_external_nav_to_nav_can_switchWhether the G3X Touch's autopilot is allowed to initiate an automatic CDI source switch at the current point in time. If the external navigator initiates its own automatic CDI source switches, then this should always be set to false. If set to true, the G3X Touch's autopilot will attempt to initiate a CDI source switch as soon as the armable NAV radio index is valid, the flight director's LOC mode is armed, and the autopilot's LOC director can capture the localizer course.
g3x_external_nav_to_nav_external_switch_in_progressWhether an automatic CDI source switch initiated by the external navigator is currently in progress. This should be set to true immediately before the automatic CDI source switch occurs, and set to false immediately after the switch is complete.

External navigators have the option (but are not required) to generate CDI auto-switch guidance using GarminNavToNavComputer. GarminNavToNavComputer will generate guidance based on logic that is common to all Garmin avionics units.

info

GarminNavToNavComputer requires LNAV data and an instance of Fms to function properly.

Here is some example code that uses GarminNavToNavComputer to generate CDI auto-switch guidance and publishes the guidance for the G3X Touch:

import { GarminNavToNavComputer } from '@microsoft/msfs-garminsdk';

const cdiId = 'gns1'; // The external navigator's CDI ID.
const navRadioIndexes = [1]; // An iterable of the indexes of the NAV radios to which the external navigator's CDI can
// be automatically switched. This should contain a single index: that of the external
// navigator's NAV radio.

const computer = new GarminNavToNavComputer(
bus,
fms, // An instance of Fms
{
cdiId,
navRadioIndexes
// Other options as desired
}
);

// Once the computer is created, it will automatically generate guidance. There is no need to explicitly update it.

computer.armableNavRadioIndex.sub(index => {
publisher.pub('g3x_external_nav_to_nav_armable_nav_radio_index_1', index, true, true);
}, true);

computer.armableLateralMode.sub(mode => {
publisher.pub('g3x_external_nav_to_nav_armable_lateral_mode_1', mode, true, true);
}, true);

// Etc.
info

If the G3X Touch is not configured with an autopilot, then the external navigator can skip sending CDI auto-switch guidance.

Availability

The G3X Touch needs to know when external navigators are available and when they are not so that it can revert to the backup external navigator or to its internal navigation source when necessary. Generally speaking, an external navigator should be considered available when it is powered and initialized and not available otherwise. However, external navigators are free to choose their own logic as long as they provide the G3X Touch with coherent data while they are considered to be available.

External navigators should communicate their availability to the G3X Touch in two ways. The first is to write the availability state as a boolean (0/1) to an LVar: L:WT_G3X_Fpl_Source_External_Available:[index], where [index] is the external navigator's assigned index (i.e. 1 for external navigator 1 and 2 for external navigator 2). The second is to publish the availability state as a boolean value to the event bus topic g3x_fpl_source_external_available_[index], where [index] again is the external navigator's assigned index. When publishing this event bus topic, ensure that the published data is synced to other instruments (so that it reaches the G3X Touch) and is cached:

bus.pub(
'g3x_fpl_source_external_available_1',
true,
true, // sync to other instruments
true // cache data
);