Emesary MFD bridge

From FlightGear wiki
Jump to navigation Jump to search
This article is a stub. You can help the wiki by expanding it.


Document ongoing talks and ideas about using the Emesary framework to provide a sane interfacing mechanism for Canvas based avionics (and lower-level MapStructure building blocks) using Richard's Canvas MFD Framework.


This is an adapted version of the Garmin GPSMap 196 originally developed by F-JJTH. Here, the whole instrument is entirely set up in XML space without using any Nasal, including buttons/event handling, but also the embedded canvas region that serves as the 'screen'. The idea is to allow arbitrary MFDs to be specified in an aircraft-agnostic fashion, including displays like a PFD, NavDisplay or EFB. For details, please see Canvas Glass Cockpit Efforts.

Over time, the NavDisplay code has become a huge mess - it is far from being easily maintainable, yet it is the most widely used Canvas-based MFD in use today, mainly because it is generic and can be easily reused, which is due to it having been designed using just a standalone GUI dialog.

So there are some lessons to be learnt from the whole experience.

Nasal modules

Use cases

Design goals

Screenshot showing a PUI dialog with two NavDisplay instances, featuring support for customizable resolution/size of the ND as well as selectable number of NDs to be shown.

The NavDisplay code is structured to support:

  • GUI-based prototyping and design/testing of layers
  • truly indenpendent instances (an arbitrary number of them)
  • styling of ND layers/components according to aircraft specifics (think Boeing vs. Airbus ND)
  • encapsulate aircraft/system specifics (think differences between fdm, autopilot and route manager)
  • encourage a modular and reusable design of additions, so that all aircraft developers using the framework can benefit automatically


We will be hooking up the Canvas MFD framework to a conventional Canvas GUI dialog, so that a MFD instrument can be shown in a standalone GUI dialog. The next step will involve using the Emesary framework to create custom events and actions.

Instantiating a MFD dialog

1rightarrow.png See Howto:Creating a Canvas GUI dialog file for the main article about this subject.

var (width,height) = (640,480);
var title = 'Canvas MFD/Emesary Bridge';

var window = canvas.Window.new([width,height],"dialog").set('title',title);

# the del() function is the destructor of the Window
# which will be called upon termination (dialog closing)
# you can use this to do resource management (clean up timers, listeners or background threads)
#window.del = func()
#  print("Cleaning up window:",title,"\n");
# explanation for the call() technique at: http://wiki.flightgear.org/Object_oriented_programming_in_Nasal#Making_safer_base-class_calls
#  call(canvas.Window.del, [], me);

# adding a canvas to the new window and setting up background colors/transparency
var myCanvas = window.createCanvas().set("background", canvas.style.getColor("bg_color"));

# creating the top-level/root group which will contain all other elements/group
var root = myCanvas.createGroup();

var svgURL = "https://sourceforge.net/p/flightgear/fgaddon/HEAD/tree/trunk/Aircraft/F-15/Nasal/MPCD/MPCD_0_0.svg?format=raw";
var svgFile = http.load(svgURL);

svgFile.done( func(request) {
	debug.dump( request.response );

var MPCD_Device =
# create new MFD device. This is the main interface (from our code) to the MFD device
# Each MFD device will contain the underlying PFD device object, the SVG, and the canvas
# Parameters
# - designation - Flightdeck Legend for this
# - model_element - name of the 3d model element that is to be used for drawing
# - model_index - index of the device
    new : func(designation, model_element, model_index=0)
        var obj = {parents : [MPCD_Device] };
        obj.designation = designation;
        obj.model_element = model_element;
        var dev_canvas= canvas.new({
                "name": designation,
                           "size": [1024,1024], 
                           "view": [740,680],                       
                    "mipmapping": 1     

        dev_canvas.addPlacement({"node": model_element});
        dev_canvas.setColorBackground(0.003921,0.1764,0, 0);
# Create a group for the parsed elements
        obj.PFDsvg = dev_canvas.createGroup();
        var pres = canvas.parsesvg(obj.PFDsvg, "Nasal/MPCD/MPCD_0_0.svg");
# Parse an SVG file and add the parsed elements to the given group
        printf("MPCD : %s Load SVG %s",designation,pres);
        obj.PFDsvg.setTranslation (270.0, 197.0);
# create the object that will control all of this
        obj.num_menu_buttons = 20;
        obj.PFD = PFD_Device.new(obj.PFDsvg, obj.num_menu_buttons, "MI_", dev_canvas);
        obj.PFD._canvas = dev_canvas;
        obj.PFD.designation = designation;
        obj.mfd_device_status = 1;
        obj.model_index = model_index; # numeric index (1 to 9, left to right) used to connect the buttons in the cockpit to the display

        return obj;

    addPages : func
        me.p1_1 = me.PFD.addPage("Aircraft Menu", "p1_1");

        me.p1_1.update = func
            var sec = getprop("instrumentation/clock/indicated-sec");
            var cdt = getprop("sim/time/gmt");

            if (cdt != nil)

        me.pjitds_1 =  PFD_NavDisplay.new(me.PFD,"Situation", "mpcd-sit", "pjitds_1", "jtids_main");
        # use the radar range as the ND range.

        # Page 1 is the time display
        me.p1_1.update = func
            var sec = getprop("instrumentation/clock/indicated-sec");
            var cdt = getprop("sim/time/gmt");

            if (cdt != nil)

        # armament page gun rounds is implemented a little differently as the menu item (1) changes to show
        # the contents of the magazine.
        me.p1_3.gun_rounds = me.p1_3.addMenuItem(1, sprintf("HIGH\n%dM",getprop("sim/model/f15/systems/gun/rounds")), me.p1_3);

        setlistener("sim/model/f15/systems/gun/rounds", func(v)
                        if (v != nil) {
                            me.p1_3.gun_rounds.title = sprintf("HIGH\n%dM",v.getValue());
        me.mpcd_button_pushed = 0;
        # Connect the buttons - using the provided model index to get the right ones from the model binding
        setlistener("sim/model/f15/controls/MPCD/button-pressed", func(v)
                        if (v != nil) {
                            if (v.getValue())
                                me.mpcd_button_pushed = v.getValue();
                            else {
                                printf("%s: Button %d",me.designation, me.mpcd_button_pushed);
                                me.mpcd_button_pushed = 0;

        # Set listener on the PFD mode button; this could be an on off switch or by convention
        # it will also act as brightness; so 0 is off and anything greater is brightness.
        # ranges are not pre-defined; it is probably sensible to use 0..10 as an brightness rather
        # than 0..1 as a floating value; but that's just my view.
        setlistener("sim/model/f15/controls/PFD/mode"~me.model_index, func(v)
                        if (v != nil) {
                            me.mfd_device_status = v.getValue();
                            print("MFD Mode ",me.designation," ",me.mfd_device_status);
                            if (!me.mfd_device_status)

        me.mpcd_button_pushed = 0;

    # Add the menus to each page. 
    setupMenus : func


    update : func
        if (me.mfd_device_status)
    },# update

# Create the MPCD device 
var MPCD =  MPCD_Device.new("F15-MPCD", "MPCDImage",0);

var updateMPCD = func ()

Related content