Emesary MFD bridge

From FlightGear wiki
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.
This article is a stub. You can help the wiki by expanding it.

Objective

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.

Background

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

Implementation

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

        obj.addPages();
        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");
            me.page1_1.time.setText(getprop("sim/time/gmt-string")~"Z");
            var cdt = getprop("sim/time/gmt");

            if (cdt != nil)
                me.page1_1.date.setText(substr(cdt,5,2)~"/"~substr(cdt,8,2)~"/"~substr(cdt,2,2)~"Z");
        };


        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");
            me.time.setText(getprop("sim/time/gmt-string")~"Z");
            var cdt = getprop("sim/time/gmt");

            if (cdt != nil)
                me.date.setText(substr(cdt,5,2)~"/"~substr(cdt,8,2)~"/"~substr(cdt,2,2)~"Z");
        };

        #
        # 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.PFD.updateMenus();
                        }
                    }
            );
        me.PFD.selectPage(me.p1_1);
        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.PFD.notifyButton(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.PFDsvg.setVisible(0);
                            else
                                me.PFDsvg.setVisible(1);
                        }
                    }
            );

        me.mpcd_button_pushed = 0;
        me.setupMenus();
        me.PFD.selectPage(me.p1_1);
    },

    # Add the menus to each page. 
    setupMenus : func
    {

    },

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

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

var updateMPCD = func ()
{  
    MPCD.update();
}

Related

References