1
0
Fork 0
fgdata/Nasal/canvas/MFD_Generic.nas
Stuart Buchanan 76cfa2c077 FG1000 and MFD handle non-MFD Emesary notifications
Previously the FG1000 and MFD assumed that all notifications received
via Emesary had a Device_Id field.  This is true for
notifications.PFDEventNotification.DefaultType but not for other
messages - e.g. those generated by the AN SPN 46  system on the
carrier.

The effect of this was that when Nimitz was loaded, the FG1000 stopped
working with various errors.

This fix checks the notification type before the Device_Id.
2020-03-12 17:25:34 +00:00

451 lines
16 KiB
Text

# Generic Page switching cockpit display device.
# ---------------------------
# Richard Harrison: 2015-10-17 : rjh@zaretto.com
# ---------------------------
# I'm calling this a PFD as in Programmable Function Display.
# ---------------------------
# documentation: see http://wiki.flightgear.org/Canvas_MFD_Framework
# See FGAddon/Aircraft/F-15/Nasal/MPCD/MPCD_main.nas for an example usage
# ---------------------------
# This is but a straightforwards wrapper to provide the core logic that page switching displays require.
# Examples of Page switching displays
# * MFD
# * PFD
# * FMCS
# * F-15 MPCD
#
# Menu Item. There is a list of these for each page changing button per display page
# Parameters:
# menu_id : page change event id for this menu item. e.g. button number
# title : Title Text (for display on the device)
# page : Instance of page usually returned from PFD.addPage
# callbackfn : Function to call when menu item is selected
# displayfn : Function to call when the menu item is displayed. Used to enable
# highlighting of menu items, for example.
var PFD_MenuItem =
{
new : func (menu_id, title, page, callbackfn=nil, displayfn=nil)
{
var obj = {parents : [PFD_MenuItem] };
obj.page = page;
obj.menu_id = menu_id;
obj.callbackfn = callbackfn;
obj.displayfn = displayfn;
obj.title = title;
return obj;
},
};
#
#
# Create a new PFD Page
# - related svg
# - Title: Page title
# - SVG element for the page
# - Device to attach the page to
var PFD_Page =
{
new : func (svg, title, layer_id, device)
{
var obj = {parents : [PFD_Page] };
obj.title = title;
obj.device = device;
obj.layer_id = layer_id;
obj.menus = [];
obj.svg = svg.getElementById(layer_id);
if(obj.svg == nil)
printf("PFD_Device: Error loading %s: svg layer %s ",title, layer_id);
return obj;
},
#
# Makes a page visible.
# It is the responsibility of the caller to manage the visibility of pages - i.e. to
# make a page that is currenty visible not visible before making a new page visible,
# however more than one page could be visible - but only one set of menu buttons can be active
# so if two pages are visible (e.g. an overlay) then when the overlay removed it would be necessary
# to call setVisible on the base page to ensure that the menus are setup
setVisible : func(vis)
{
if(me.svg != nil)
me.svg.setVisible(vis);
if (vis)
me.ondisplay();
else
me.offdisplay();
},
# Standard callback for buttons, causing the appropriate page to be displayed
std_callbackfn : func (device, me, mi)
{
device.selectPage(mi.page);
},
# Standard display function for buttons, displaying the text and making visible
std_displayfn : func(svg_element, menuitem)
{
svg_element.setText(menuitem.title);
svg_element.setVisible(1);
#me.buttons[mi.menu_id].setText(mi.title);
#me.buttons[mi.menu_id].setVisible(1);
},
#
# Perform action when button is pushed
notifyButton : func(button_id)
{ foreach(var mi; me.menus)
{
if (mi.menu_id == button_id)
{
if (mi.callbackfn != nil) mi.callbackfn(me.device, me, mi);
break;
}
}
},
#
# Add an item to a menu
# Params:
# menu button id (that is set in controls/PFD/button-pressed by the model)
# title of the menu for the label
# page that will be selected when pressed
#
# The corresponding menu for the selected page will automatically be loaded
addMenuItem : func(menu_id, title, page, callbackfn=nil, displayfn=nil)
{
if (callbackfn == nil) callbackfn = me.std_callbackfn;
if (displayfn == nil) displayfn = me.std_displayfn;
var nm = PFD_MenuItem.new(menu_id, title, page, callbackfn, displayfn);
append(me.menus, nm);
return nm;
},
#
# Clear all items from the menu. Use-case is where they may be a hierarchy
# of menus within the same page.
#
clearMenu : func()
{
me.menus = [];
},
# base method for update; this can be overridden per page instance to provide update of the
# elements on display (e.g. to display updated properties)
update : func(notification=nil)
{
},
#
# notify the page that it is being displayed. use to load any static framework or perform one
# time initialisation
ondisplay : func
{
},
#
# notify the page that it is going off display; use to clean up any created elements or perform
# any other required functions
offdisplay : func
{
},
};
#
# Container device for pages.
var PFD_Device =
{
# - svg is the page elements from the svg.
# - num_menu_buttons is the Number of menu buttons; starting from the bottom left then right, then top, then left.
# - button prefix (e.g MI_) is the prefix of the labels in the SVG for the menu boxes.
# - _canvas is the canvas group.
# - designation (optional) is used for Emesary designation
#NOTE:
# This does not actually create the canvas elements, or parse the SVG, that would typically be done in
# a higher level class that contains an instance of this class.
# see: http://wiki.flightgear.org/Canvas_MFD_Framework
new : func(svg, num_menu_buttons, button_prefix, _canvas, designation="MFD")
{
var obj = {parents : [PFD_Device] };
obj.svg = svg;
obj.canvas = _canvas;
obj.current_page = nil;
obj.pages = [];
obj.page_index = {};
obj.buttons = setsize([], num_menu_buttons);
obj.transmitter = nil;
# change after creation if required
obj.device_id = 1;
obj.designation = designation;
for(var idx = 0; idx < num_menu_buttons; idx += 1)
{
var label_name = sprintf(button_prefix~"%d",idx);
var msvg = obj.svg.getElementById(label_name);
if (msvg == nil)
printf("PFD_Device: Failed to load %s",label_name);
else
{
obj.buttons[idx] = msvg;
obj.buttons[idx].setText(sprintf("M%d",idx));
}
}
obj.Recipient = nil;
return obj;
},
#
# instead of using the direct call method this allows the use of Emesary (via a specified or default global transmitter)
# example to notify that a softkey has been used. The "1" in the line below is the device ID
# var notification = notifications.PFDEventNotification.new(me.designation, me.DeviceId, notifications.PFDEventNotification.SoftKeyPushed, me.mpcd_button_pushed);
# emesary.GlobalTransmitter.NotifyAll(notification);
# - currently supported is
# 1. setting menu text directly (after page has been loaded)
# notifications.PFDEventNotification.new(me.designation, 1, notifications.PFDEventNotification.ChangeMenuText, [{ Id: 1, Text: "NNN"}]);
# 2. SoftKey selection.
#
# the device ID must match this device ID (to allow for multiple devices).
RegisterWithEmesary : func(transmitter = nil){
if (transmitter == nil)
transmitter = emesary.GlobalTransmitter;
if (me.Recipient == nil){
me.Recipient = emesary.Recipient.new("PFD_"~me.designation);
var pfd_obj = me;
me.Recipient.Receive = func(notification)
{
if (notification.NotificationType == notifications.PFDEventNotification.DefaultType and
notification.Device_Id == pfd_obj.device_id) {
if (notification.Event_Id == notifications.PFDEventNotification.SoftKeyPushed
and notification.EventParameter != nil)
{
#printf("Button pressed " ~ notification.EventParameter);
pfd_obj.notifyButton(notification.EventParameter);
}
else if (notification.Event_Id == notifications.PFDEventNotification.ChangeMenuText
and notification.EventParameter != nil)
{
foreach(var eventMenu; notification.EventParameter) {
#printf("Menu Text changed : " ~ eventMenu.Text);
foreach (var mi ; pfd_obj.current_page.menus) {
if (pfd_obj.buttons[eventMenu.Id] != nil) {
pfd_obj.buttons[eventMenu.Id].setText(eventMenu.Text);
}
else
printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
}
}
}
return emesary.Transmitter.ReceiptStatus_OK;
}
return emesary.Transmitter.ReceiptStatus_NotProcessed;
};
transmitter.Register(me.Recipient);
me.transmitter = transmitter;
}
},
DeRegisterWithEmesary : func(transmitter = nil){
# remove registration from transmitter; but keep the recipient once it is created.
if (me.transmitter != nil)
me.transmitter.DeRegister(me.Recipient);
me.transmitter = nil;
},
#
# called when a button is pushed - connecting the property to this method is implemented in the outer class
notifyButton : func(button_id)
{
#
# by convention the buttons we have are 0 based; however externally 0 is used
# to indicate no button pushed.
if (button_id > 0)
{
button_id = button_id - 1;
if (me.current_page != nil)
{
me.current_page.notifyButton(button_id);
}
else
printf("PFD_Device: Could not locate page for button ",button_id);
}
},
#
#
# add a page to the device.
# - page title.
# - svg element id
addPage : func(title, layer_id)
{
var np = PFD_Page.new(me.svg, title, layer_id, me);
append(me.pages, np);
me.page_index[layer_id] = np;
np.setVisible(0);
return np;
},
#
# Get a named page
#
getPage : func(title)
{
foreach(var p; me.pages) {
if (p.title == title) return p;
}
return nil;
},
#
# manage the update of the currently selected page
update : func(notification=nil)
{
if (me.current_page != nil)
me.current_page.update(notification);
},
#
# Change to display the selected page.
# - the page object method controls the visibility
selectPage : func(p)
{
if (me.current_page == p) return;
if (me.current_page != nil)
me.current_page.setVisible(0);
if (me.buttons != nil)
{
foreach(var mb ; me.buttons)
if (mb != nil)
mb.setVisible(0);
foreach(var mi ; p.menus)
{
if (me.buttons[mi.menu_id] != nil)
{
mi.displayfn(me.buttons[mi.menu_id], mi);
}
else
printf("PFD_device: Menu for button not found. Menu ID '%s'",mi.menu_id);
}
}
p.setVisible(1);
me.current_page = p;
},
# Return the current selected page.
getCurrentPage : func()
{
return me.current_page;
},
#
# ensure that the menus are display correctly for the current page.
updateMenus : func
{
foreach(var mb ; me.buttons)
if (mb != nil)
mb.setVisible(0);
if (me.current_page == nil) return;
foreach(var mi ; me.current_page.menus)
{
if (me.buttons[mi.menu_id] != nil)
{
mi.displayfn(me.buttons[mi.menu_id], mi);
}
else
printf("No corresponding item '%s'",mi.menu_id);
}
},
};
var PFD_NavDisplay =
{
#
# Instantiate parameters:
# 1. pfd_device (instance of PFD_Device)
# 2. instrument display ident (e.g. mfd-map, or mfd-map-left mfd-map-right for multiple displays)
# (this is used to map to the property tree)
# 3. layer_id: main layer in the SVG
# 4. nd_group_ident : group (usually within the main layer) to place the NavDisplay
# 5. switches - used to connect the property tree to the nav display. see the canvas nav display
# documentation
new : func (pfd_device, title, instrument_ident, layer_id, nd_group_ident, switches=nil, map_style="Boeing")
{
var obj = pfd_device.addPage(title, layer_id);
# if no switches given then use a default set.
if (switches != nil)
obj.switches = switches;
else
obj.switches = {
'toggle_range': { path: '/inputs/range-nm', value: 50, type: 'INT' },
'toggle_weather': { path: '/inputs/wxr', value: 0, type: 'BOOL' },
'toggle_airports': { path: '/inputs/arpt', value: 1, type: 'BOOL' },
'toggle_stations': { path: '/inputs/sta', value: 1, type: 'BOOL' },
'toggle_waypoints': { path: '/inputs/wpt', value: 1, type: 'BOOL' },
'toggle_position': { path: '/inputs/pos', value: 0, type: 'BOOL' },
'toggle_data': { path: '/inputs/data', value: 0, type: 'BOOL' },
'toggle_terrain': { path: '/inputs/terr', value: 0, type: 'BOOL' },
'toggle_traffic': { path: '/inputs/tfc', value: 1, type: 'BOOL' },
'toggle_centered': { path: '/inputs/nd-centered', value: 1, type: 'BOOL' },
'toggle_lh_vor_adf': { path: '/inputs/lh-vor-adf', value: 1, type: 'INT' },
'toggle_rh_vor_adf': { path: '/inputs/rh-vor-adf', value: 1, type: 'INT' },
'toggle_display_mode': { path: '/mfd/display-mode', value: 'MAP', type: 'STRING' },
'toggle_display_type': { path: '/mfd/display-type', value: 'LCD', type: 'STRING' },
'toggle_true_north': { path: '/mfd/true-north', value: 0, type: 'BOOL' },
'toggle_rangearc': { path: '/mfd/rangearc', value: 0, type: 'BOOL' },
'toggle_track_heading': { path: '/hdg-trk-selected', value: 1, type: 'BOOL' },
};
obj.nd_initialised = 0;
obj.nd_placeholder_ident = nd_group_ident;
obj.nd_ident = instrument_ident;
obj.pfd_device = pfd_device;
obj.nd_init = func
{
me.ND = canvas.NavDisplay;
if (!me.nd_initialised)
{
me.nd_initialised = 1;
me.NDCpt = me.ND.new("instrumentation/"~me.nd_ident, me.switches,map_style);
me.group = me.pfd_device.svg.getElementById(me.nd_placeholder_ident);
me.group.setScale(0.39,0.45);
me.group.setTranslation(45,0);
me.NDCpt.newMFD(me.group, pfd_device.canvas);
}
me.NDCpt.update();
};
#
# Method overrides
#-----------------------------------------------
# Called when the page goes on display - need to delay initialization of the NavDisplay until later (it fails
# if done too early).
# NOTE: This causes a display "wobble" the first time on display as resizing happens. I've seen similar things
# happen on real avionics (when switched on) so it's not necessarily unrealistic -)
obj.ondisplay = func
{
if (!me.nd_initialised)
me.nd_init();
#2018.2 - manage the timer so that the nav display is only updated when visibile
me.NDCpt.onDisplay();
};
obj.offdisplay = func
{
#2018.2 - manage the timer so that the nav display is only updated when visibile
if (me.nd_initialised)
me.NDCpt.offDisplay();
};
#
# most updates performed by the canvas nav display directly.
obj.update = func
{
};
return obj;
},
};