Skip to main content

Setting up an FMC Screen

We will now see how we can configure a simple FMC screen, to a point where it is rendering and can have pages added to it.

Creating an FMC Screen Class

An FMC screen for a specific CDU starts with creating a class extending FmcScreen. This class has two type parameters; the first one indicates which class the pages on this screen will extend, and the second one defines which events can be sent to the screen.

You will first need to create those two types.

Defining an event type

The event type on the FMC screen is used to register certain events as performing specific actions on the FMC screen. Examples of events that are commonly found include:

  • Line select keys (LSKs);
  • Scrolling keys (left/previous, right/next, up, down);
  • Page/mode keys (FPLN, LEGS, PERF, INDEX etc.);
  • Scratchpad keys (alphanumeric keys, +/-, /, ., etc.).

You can create an event type with a plain TypeScript interface:

interface MyFmcEvents {
// Each line select key must be a distinct event
lsk_1_l: void;

lsk_1_r: void;

prev_page: void;

next_page: void;

// It is recommended to use a singular event for text being input into the scratchpad, to avoid having to create 36 separate events
// for all alphanumeric values
scratchpad_type: string;

// "Special" keys should however have their own events
scratchpad_plus_mins: void;

example_page: void;

example_page_2: void;
}
tip

As some events are nullary (meaning they take no arguments), we can use void to not have to specify a value to pass into the event. When firing those events, use undefined as the payload value.

Creating a page class

Pages for a specific FMC screen must all extend the same base class, which can contain functionality specific to this CDU.

/**
* A test FMC page class
*/
export class MyFmcPage extends AbstractFmcPage {
/**
* Ctor
* @param bus the event bus
* @param screen the FMC screen
* @param renderCallback the FMC render callback
*/
constructor(
bus: EventBus,
screen: FmcScreen<MyFmcPage, MyFmcEvents>, // This can be replaced with your avionics-specific FMC screen class later on
public readonly renderCallback: FmcRenderCallback
) {
super(bus, screen);
}

/** @inheritDoc */
render(): FmcRenderTemplate[] {
return [];
}
}

Creating a page factory

Since the FMC screen creates pages automatically, a page factory is necessary.

export class MyFmcPageFactory extends FmcPageFactory<MyFmcPage> {
createPage(
pageCtor: typeof MyFmcPage,
bus: EventBus,
screen: FmcScreen<MyFmcPage, any>,
renderCallback: FmcRenderCallback
): MyFmcPage {
return new pageCtor(bus, screen, renderCallback);
}
}
tip

If your page class requires more constructor arguments (a common pattern is passing in an FMS or flight planner instance), add those as constructor properties to your MyFmcPageFactory class:

export class MyFmcPageFactory extends FmcPageFactory<MyFmcPage> {
constructor(private readonly fms: MyFms) {
super();
}

/* ... */
}

Creating the actual FMC screen class

Using MyFmcPage, MyFmcEvents and MyFmcPageFactory, we can create our class extending FmcScreen:

const MY_CDU_CELL_WIDTH = 24;
const MY_CDU_CELL_HEIGHT = 14;

export class MyFmcScreen extends FmcScreen<MyFmcPage, MyFmcEvents> {
constructor(bus: EventBus, renderTarget: HTMLDivElement) {
super(
bus,
new MyFmcPageFactory(),
{ screenDimensions: { cellWidth: MY_CDU_CELL_WIDTH, cellHeight: MY_CDU_CELL_HEIGHT }, enableScratchpad: false },
new SimpleFmcRenderer(bus, renderTarget, {
screenCellWidth: MY_CDU_CELL_WIDTH,
screenCellHeight: MY_CDU_CELL_HEIGHT,
screenPXWidth: 800,
screenPXHeight: 600,
}),
new FmcScratchpad(bus, { cellWidth: UNS_CDU_CELL_WIDTH }, () => {})
);
}
}

Configuring the FMC Screen

When creating the FmcScreen class, you will need to pass a partial FmcScreenOptions interface which provides multiple options regarding screen size, etc.

Additionally, you may wish to override some of the default behaviours of the FmcScreen class, such as to display an overlay. If you wish to override the default page output, such as for displaying an overlay, you can do so by modifying the rendered output in the acceptPageOutput function of MyFmcScreen.

Creating a Stylesheet

The FMC framework uses simple CSS for styling, allowing developers to simply include and import a css stylesheet. For more information regarding styling, see styling

Instantiating and Rendering the FMC Screen

It is recommended to contain the initialization of the FMC screen into its own component where pages and events are configured:

/**
* Props for {@link MyCduDisplay}
*/
export interface MyCduDisplayProps extends ComponentProps {
/** The event bus */
bus: EventBus;
}

/**
* My CDU display component
*/
export class MyCduDisplay extends DisplayComponent<MyCduDisplayProps> {
private readonly fmcScreenRef = FSComponent.createRef<HTMLDivElement>();

private myFmcScreen: MyFmcScreen | undefined;

/** @inheritDoc */
public override onAfterRender(node: VNode): void {
super.onAfterRender(node);

// We instantiate our FMC screen, passsing in the bus and the element we want it to render its content grid to
this.myFmcScreen = new MyFmcScreen(
this.props.bus,
this.fmcScreenRef.instance
);

this.fmcScreen && this.initialiseFmcScreen(this.fmcScreen);
}

/**
* Initialises the FMC screen
* @param fmcScreen The UnsFmcScreen instance
*/
private initialiseFmcScreen(fmcScreen: MyFmcScreen): void {
// ...we configure this later
}

/** @inheritDoc */
public override render(): VNode | null {
return <div ref={this.fmcScreenRef} />;
}
}

Setting up events

In order for our FMC screen to respond appropriately to different events, we must tell it which events correspond to what actions. (see Defining an event type).

This is done by calling a couple of utility methods on FmcScreen:

class MyCduDisplay /* ... */ {
/* ... */
private initialiseFmcScreen(fmcScreen: MyFmcScreen): void {
// Here, we configure which events (keys of the event interface we specify as the second type parameter
// of `FmcScreen` in `MyFmcScreen` - in this case, `MyFmcEvents`) correspond to which line select keys, associating them
// with row indices and column indices (referring to the left/right automatic layout columns) they correspond to.
// Any components present at that location will receive those events.
fmcScreen.addLskEvents([
["lsk_1_l", 2, 0],
["lsk_1_r", 2, 1],
]);

// We do the same for scrolling events. It is not nevessary to specify every possible scrolling event, as few CDUs have
// dedicated up/down scrolling keys.
fmcScreen.addPagingEvents({
pageLeft: "prev_page",
pageRight: "next_page",
});

// For events the FMC screen does not handle automatically, you can easily get a consumer to events, prefixed with
// the event prefix configured on the FMC screen (by default, none). The prefix is configured when you need tow distinct
// CDUs which respond to separate events. This is automatically taken care of by `addLskEvents`, `addPagingEvents` and `addPageRoute`.
//
// The prefix can be configured using the `eventPeefix` property of the `FmcScreenOptions` object we pass to the
// parent class in the `MyFmcScreen` constructor.
fmcScreen.onPrefixedEvent("scratchpad_plus_mins").handle(() => {
/* ... */
});
}
/* ... */
}
note

The FMC Framework does not publish events automatically, only consumes them. The most common way of publishing events is via Receiving H Events, and re-publishing those (conforming to the created event interface) on the event bus.

It is good practice to use H Events even if your CDU buttons are virtual. This is because users employing external software or hardware controls can easily emit H Events, but not EventBus events.