1
0
Fork 0

Update NAV/COMM frequencies from properties

- Add new Emesary notification type for NAV/COM data
- Create Update/Publish interfaces using Emesary from properties
- Use interfaces to drive updates to EIS and NAV/COM frequencies
- Change the PageGroupController to a "proper" MFD page
This commit is contained in:
Stuart Buchanan 2018-01-05 16:37:39 +00:00
parent ad77dc2f9c
commit 73424c1791
18 changed files with 1327 additions and 2635 deletions

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 81 KiB

File diff suppressed because it is too large Load diff

Before

Width:  |  Height:  |  Size: 91 KiB

View file

@ -1,68 +0,0 @@
# FG1000 Engine Information Display Default Driver
#
# Driver values in the EIS.
var PropMap =
{
new : func(name, property)
{
var obj = { parents : [ PropMap ] };
obj._name = name;
obj._val = 0;
obj._prop = globals.props.getNode(property, 1);
obj._val = obj._prop.getValue();
return obj;
},
update : func()
{
me._val = me._prop.getValue();
},
getValue : func()
{
return me._val;
}
};
var EISDriver =
{
new : func()
{
var obj = { parents : [ EISDriver ] };
# Hack to handle most aircraft not having proper engine hours
setprop("/engines/engine[0]/hours", 157.0);
obj._propmap = {
"RPM" : PropMap.new("RPM", "/engines/engine[0]/rpm"),
"MBusVolts" : PropMap.new("MBusVolts", "/systems/electrical/volts"),
"EngineHours" : PropMap.new("EngineHours", "/engines/engine[0]/hours"),
"FuelFlowGPH" : PropMap.new("FuelFlowGPH", "/engines/engine[0]/fuel-flow-gph"),
"OilPressurePSI" : PropMap.new("OilPressurePSI", "/engines/engine[0]/oil-pressure-psi"),
"OilTemperatureF" : PropMap.new("OilTemperatureF", "/engines/engine[0]/oil-temperature-degf"),
"EGTNorm" : PropMap.new("EGTNorm", "/engines/engine[0]/egt-norm"),
"VacuumSuctionInHG" : PropMap.new("VacuumSuctionInHG", "/systems/vacuum/suction-inhg"),
"LeftFuelUSGal" : PropMap.new("LeftFuelUSGal", "/consumables/fuel/tank[0]/indicated-level-gal_us"),
"RightFuelUSGal" : PropMap.new("RightFuelUSGal", "/consumables/fuel/tank[1]/indicated-level-gal_us"),
};
return obj;
},
getValue : func(name)
{
if (me._propmap[name] == nil) print("Unknown Driver Key in EISDriver.nas " ~ name);
return me._propmap[name].getValue();
},
update: func () {
foreach (var tp; keys(me._propmap)) {
me._propmap[tp].update();
}
}
};

View file

@ -30,21 +30,21 @@ var EIS =
},
updateData : func(engineData) {
obj.setTextElement("RPMDisplay", sprintf("%i", engineData.RPM));
obj.setTextElement("MBusVolts", sprintf("%.01f", engineData.MBusVolts));
obj.setTextElement("EBusVolts", sprintf("%.01f", engineData.MBusVolts)); # TODO: Include Emergency Bus
obj.setTextElement("EngineHours", sprintf("%.01f", engineData.EngineHours));
me.setTextElement("RPMDisplay", sprintf("%i", engineData.RPM));
me.setTextElement("MBusVolts", sprintf("%.01f", engineData.MBusVolts));
me.setTextElement("EBusVolts", sprintf("%.01f", engineData.MBusVolts)); # TODO: Include Emergency Bus
me.setTextElement("EngineHours", sprintf("%.01f", engineData.EngineHours));
obj._fuelFlowPointer.setValue(engineData.FuelFlowGPH);
obj._oilPressurePointer.setValue(engineData.OilPressurePSI);
obj._oilTempPointer.setValue(engineData.OilTemperatureF);
obj._EGTPointer.setValue(engineData.EGTNorm);
obj._EGTCylinder.setValue(engineData.EGTNorm);
obj._vacPointer.setValue(engineData.VacuumSuctionInHG);
obj._leftFuelPointer.setValue(engineData.LeftFuelUSGal);
obj._rightFuelPointer.setValue(engineData.RightFuelUSGal);
me._fuelFlowPointer.setValue(engineData.FuelFlowGPH);
me._oilPressurePointer.setValue(engineData.OilPressurePSI);
me._oilTempPointer.setValue(engineData.OilTemperatureF);
me._EGTPointer.setValue(engineData.EGTNorm);
me._EGTCylinder.setValue(engineData.EGTNorm);
me._vacPointer.setValue(engineData.VacuumSuctionInHG);
me._leftFuelPointer.setValue(engineData.LeftFuelUSGal);
me._rightFuelPointer.setValue(engineData.RightFuelUSGal);
obj._RPMPointer.setValue(engineData.RPM);
me._RPMPointer.setValue(engineData.RPM);
},
# Menu tree . engineMenu is referenced from most pages as softkey 0:

View file

@ -38,7 +38,8 @@ var EISController =
}
# Display it
me.page.upateData(data);
me._page.updateData(data);
return emesary.Transmitter.ReceiptStatus_OK;
},
RegisterWithEmesary : func(transmitter = nil){

View file

@ -0,0 +1,41 @@
# EIS Driver using Emesary for a single engined aircraft, e.g. Cessna 172.
var GenericEISPublisher =
{
new : func (frequency=0.25, transmitter = nil) {
var obj = {
parents : [ GenericEISPublisher, PropertyPublisher.new(frequency, transmitter) ],
};
# Hack to handle most aircraft not having proper engine hours
if (getprop("/engines/engine[0]/hours") == nil) setprop("/engines/engine[0]/hours", 157.0);
obj.addPropMap("RPM", "/engines/engine[0]/rpm");
obj.addPropMap("MBusVolts", "/systems/electrical/volts");
obj.addPropMap("EngineHours", "/engines/engine[0]/hours");
obj.addPropMap("FuelFlowGPH", "/engines/engine[0]/fuel-flow-gph");
obj.addPropMap("OilPressurePSI", "/engines/engine[0]/oil-pressure-psi");
obj.addPropMap("OilTemperatureF", "/engines/engine[0]/oil-temperature-degf");
obj.addPropMap("EGTNorm", "/engines/engine[0]/egt-norm");
obj.addPropMap("VacuumSuctionInHG", "/systems/vacuum/suction-inhg");
obj.addPropMap("LeftFuelUSGal", "/consumables/fuel/tank[0]/indicated-level-gal_us");
obj.addPropMap("RightFuelUSGal", "/consumables/fuel/tank[1]/indicated-level-gal_us");
return obj;
},
publish : func() {
var engineData0 = {};
foreach (var propmap; me._propmaps) {
var name = propmap.getName();
engineData0[name] = propmap.getValue();
}
var engineData = [];
append(engineData, engineData0);
me.notify(notifications.PFDEventNotification.EngineData, engineData);
},
};

View file

@ -0,0 +1,57 @@
# NavCom Interface using Emesary for a simple dual Nav/Com system using standard properties
var GenericNavComPublisher =
{
new : func (frequency=0.25, transmitter = nil) {
var obj = {
parents : [ GenericNavComPublisher, PropertyPublisher.new(frequency, transmitter) ],
};
# Hack to handle cases where there is no selected COMM or NAV frequency
if (getprop("/instrumentation/com-selected") == nil) setprop("/instrumentation/com-selected", 1);
if (getprop("/instrumentation/nav-selected") == nil) setprop("/instrumentation/nav-selected", 1);
obj.addPropMap("Comm1SelectedFreq", "/instrumentation/comm/frequencies/selected-mhz");
obj.addPropMap("Comm1StandbyFreq", "/instrumentation/comm/frequencies/selected-mhz");
obj.addPropMap("Comm1AirportID", "/instrumentation/comm/airport-id");
obj.addPropMap("Comm1StationName", "/instrumentation/comm/station-name");
obj.addPropMap("Comm1StationType", "/instrumentation/comm/station-type");
obj.addPropMap("Comm1Volume", "/instrumentation/comm/volume");
obj.addPropMap("Comm1Serviceable", "/instrumentation/comm/serviceable");
obj.addPropMap("Comm2SelectedFreq", "/instrumentation/comm[1]/frequencies/selected-mhz");
obj.addPropMap("Comm2StandbyFreq", "/instrumentation/comm[1]/frequencies/selected-mhz");
obj.addPropMap("Comm2AirportID", "/instrumentation/comm[1]/airport-id");
obj.addPropMap("Comm2StationName", "/instrumentation/comm[1]/station-name");
obj.addPropMap("Comm2StationType", "/instrumentation/comm[1]/station-type");
obj.addPropMap("Comm2Volume", "/instrumentation/comm[1]/volume");
obj.addPropMap("Comm2Serviceable", "/instrumentation/comm[1]/serviceable");
obj.addPropMap("CommSelected", "/instrumentation/com-selected");
obj.addPropMap("Nav1SelectedFreq", "/instrumentation/nav/frequencies/selected-mhz");
obj.addPropMap("Nav1StandbyFreq", "/instrumentation/nav/frequencies/selected-mhz");
obj.addPropMap("Nav1ID", "/instrumentation/nav/nav-id");
obj.addPropMap("Nav1Serviceable", "/instrumentation/nav/serviceable");
obj.addPropMap("Nav2SelectedFreq", "/instrumentation/nav[1]/frequencies/selected-mhz");
obj.addPropMap("Nav2StandbyFreq", "/instrumentation/nav[1]/frequencies/selected-mhz");
obj.addPropMap("Nav2ID", "/instrumentation/nav[1]/nav-id");
obj.addPropMap("Nav2Serviceable", "/instrumentation/nav[1]/serviceable");
obj.addPropMap("NavSelected", "/instrumentation/nav-selected");
return obj;
},
publish : func() {
var data = {};
foreach (var propmap; me._propmaps) {
var name = propmap.getName();
data[name] = propmap.getValue();
}
me.notify(notifications.PFDEventNotification.NavComData, data);
},
};

View file

@ -0,0 +1,60 @@
# Generic PropertyPublisher class for the FG1000 MFD using Emesary
# Publishes property values to Emesary for consumption by the MFD
var PropertyPublisher =
{
PropMap : {
new : func(name, property)
{
var obj = { parents : [ PropertyPublisher.PropMap ] };
obj._name = name;
obj._prop = globals.props.getNode(property, 1);
return obj;
},
getName : func() { return me._name; },
getValue : func() { return me._prop.getValue(); },
},
new : func (frequency=0.25, transmitter = nil) {
var obj = {
parents : [ PropertyPublisher ],
_transmitter : transmitter,
_frequency : frequency,
_propmaps : [],
};
if (obj._transmitter == nil) obj._transmitter = emesary.GlobalTransmitter;
obj._publishTimer = nil;
return obj;
},
addPropMap : func(name, prop) {
append(me._propmaps, PropertyPublisher.PropMap.new(name, prop));
},
publish : func() {
},
notify : func(notification, data) {
var notification = notifications.PFDEventNotification.new(
"MFD",
1,
notification,
data);
me._transmitter.NotifyAll(notification);
},
start : func() {
me._timer = maketimer(me._frequency, me, me.publish);
me._timer.start();
},
stop : func() {
if(me._timer != nil) me._timer.stop();
me._timer = nil;
},
};

View file

@ -0,0 +1,93 @@
# Generic class to update properties from Emesary for the MFD
#
# In the simplest cases where the Emesary EventParameter for the specified
# Event ID is a hash whose values can be mapped directly to property values,
# it can be used directly. For more complex cases, the handleNotificationEvent
# method should be over-ridden, and should return
# emesary.Transmitter.ReceiptStatus_OK or equivalent ReceiptStatus value.
var PropertyUpdater =
{
PropMap : {
new : func(name, property)
{
var obj = { parents : [ PropertyUpdater.PropMap ] };
obj._name = name;
obj._prop = globals.props.getNode(property, 1);
return obj;
},
getName : func() { return me._name; },
getValue : func() { return me._prop.getValue(); },
},
new : func (device, notificationType, eventID, transmitter = nil) {
var obj = {
parents : [ Interface ],
_device : device,
_notificationType : notificationType,
_transmitter : transmitter,
_recipient : recipient,
_propmaps : {},
};
if (obj._transmitter == nil) obj._transmitter = emesary.GlobalTransmitter;
return obj;
},
addPropMap : func(name, prop) {
me._propmaps[name] = PropertyPublisher.PropMap.new(name, prop);
},
handleNotificationEvent : func(eventParameter) {
var retval = emesary.Transmitter.ReceiptStatus_NotProcessed;
foreach(var name; keys(eventParameters)) {
if (me._propmaps[name] != nil) {
me._propmaps[name].setValue(eventParameters[name])
retval = emesary.Transmitter.ReceiptStatus_OK;
}
}
return retval;
},
RegisterWithEmesary : func(){
if (me._recipient == nil){
me._recipient = emesary.Recipient.new("AirportInfoController_" ~ me._page.device.designation);
var pfd_obj = me._device;
var controller = me;
me._recipient.Receive = func(notification)
{
if (notification.Device_Id == pfd_obj.device_id
and notification.NotificationType == me._notificationType) {
if (notification.Event_Id == me._eventID
and notification.EventParameter != nil)
{
return me.handleNotificationEvent(notification.EventParameter);
}
}
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;
},
start : func() {
me.RegisterWithEmesary();
},
stop : func() {
me.DeRegisterWithEmesary();
},
};

View file

@ -11,6 +11,7 @@ var nasal_dir = getprop("/sim/fg-root") ~ "/Aircraft/Instruments-3d/FG1000/Nasal
io.load_nasal(nasal_dir ~ '/MFDPage.nas', "fg1000");
var MFDPages = [
"Surround",
"NavigationMap",
"EIS",
"TrafficMap",
@ -58,8 +59,6 @@ foreach (var page; MFDPages) {
io.load_nasal(nasal_dir ~ page ~ '/' ~ page ~ 'Controller.nas', "fg1000");
}
io.load_nasal(nasal_dir ~ 'PageGroupController.nas', "fg1000");
var MFD =
{
new : func (myCanvas)
@ -68,8 +67,7 @@ var MFD =
parents : [ MFD ],
EIS : nil,
NavigationMap: nil,
Surround : nil,
};
obj._svg = myCanvas.createGroup("softkeys");
@ -101,26 +99,28 @@ var MFD =
# Surround dynamic elements
obj._pageTitle = obj._svg.getElementById("PageTitle");
# Controller for the display on the bottom left which allows selection
# Controller for the header and display on the bottom left which allows selection
# of page groups and individual pages using the FMS controller.
obj._pageGroupController = fg1000.PageGroupController.new(myCanvas, obj._svg, obj._MFDDevice);
obj.Surround = fg1000.Surround.new(obj, myCanvas, obj._MFDDevice, obj._svg);
obj._pageGroupController = obj.Surround.controller;
# Engine Information System. A special case as it's always displayed on the MFD.
obj.EIS = obj._pageGroupController.addPage("EIS", fg1000.EIS.new(obj, myCanvas, obj._MFDDevice, obj._svg));
obj.EIS = obj.Surround.addPage("EIS", fg1000.EIS.new(obj, myCanvas, obj._MFDDevice, obj._svg));
# The NavigationMap page is a special case, as it is displayed with the Nearest... pages as an overlay
obj.NavigationMap = obj._pageGroupController.addPage("NavigationMap", fg1000.NavigationMap.new(obj, myCanvas, obj._MFDDevice, obj._svg));
obj.NavigationMap = obj.Surround.addPage("NavigationMap", fg1000.NavigationMap.new(obj, myCanvas, obj._MFDDevice, obj._svg));
obj.NavigationMap.topMenu(obj._MFDDevice, obj.NavigationMap, nil);
foreach (var page; MFDPages) {
if (page != "NavigationMap") {
var code = "obj._pageGroupController.addPage(\"" ~ page ~ "\", fg1000." ~ page ~ ".new(obj, myCanvas, obj._MFDDevice, obj._svg));";
if ((page != "NavigationMap") and (page != "EIS")) {
var code = "obj.Surround.addPage(\"" ~ page ~ "\", fg1000." ~ page ~ ".new(obj, myCanvas, obj._MFDDevice, obj._svg));";
var addPageFn = compile(code);
addPageFn();
}
}
# Display the EIS and NavMap and the appropriate top level on startup.
# Display the Surround, EIS and NavMap and the appropriate top level on startup.
obj.Surround.setVisible(1);
obj.EIS.setVisible(1);
obj.EIS.ondisplay();
obj._MFDDevice.selectPage(obj.NavigationMap);
@ -142,6 +142,7 @@ var MFD =
{
me._MFDDevice.current_page.offdisplay();
me._MFDDevice.DeRegisterWithEmesary();
me._pageGroupController.del();
},
setPageTitle: func(title)

View file

@ -72,6 +72,7 @@ var NearestAirportsController =
handleCRSR : func() {
me._crsrToggle = (! me._crsrToggle);
if (me._crsrToggle) {
me.page.topMenu(me.page.device, me.page, nil);
me.page.selectAirports();
me.selectAirports();
} else {

View file

@ -1,169 +0,0 @@
# Controller for the PageGroup navigation, displayed in the bottom right of the
# FMS, and controlled by the FMS knob
# Set of pages, references by SVG ID
var PAGE_GROUPS = [
{ label: "MapPageGroupLabel",
group: "MapPageGroup",
pages: [ "NavigationMap", "TrafficMap", "Stormscope", "WeatherDataLink", "TAWSB"],
},
{ label: "WPTGroupLabel",
group: "WPTPageGroup",
pages: [ "AirportInfo", "IntersectionInfo", "NDBInfo", "VORInfo", "UserWPTInfo"],
},
{ label: "AuxGroupLabel",
group: "AuxPageGroup",
pages: [ "TripPlanning", "Utility", "GPSStatus", "XMRadio", "SystemStatus"],
},
{ label: "FPLGroupLabel",
group: "FPLPageGroup",
pages: [ "ActiveFlightPlanWide", "FlightPlanCatalog", "StoredFlightPlan"],
},
{ label: "LstGroupLabel",
group: "LstPageGroup",
pages: [ "Checklist1", "Checklist2", "Checklist3", "Checklist4", "Checklist5"],
},
{ label: "NrstGroupLabel",
group: "NrstPageGroup",
pages: [ "NearestAirports", "NearestIntersections", "NearestNDB", "NearestVOR", "NearestUserWaypoints", "NearestFrequencies", "NearestAirspaces"],
}
];
var PageGroupController =
{
new : func (myCanvas, svg, device)
{
var obj = { parents : [ PageGroupController ] };
obj._canvas = myCanvas;
obj._svg = svg;
obj._device = device;
obj._menuVisible = 0;
obj._selectedPageGroup = 0;
obj._selectedPage = 0;
# List of pages to be controllers. Keys are the pages in PAGE_GROUPS;
obj._pageList = {};
obj._elements = {};
foreach (var pageGroup; PAGE_GROUPS) {
var group = svg.getElementById(pageGroup.group);
var label = svg.getElementById(pageGroup.label);
assert(group != nil, "Unable to find element " ~ pageGroup.group);
assert(label != nil, "Unable to find element " ~ pageGroup.label);
obj._elements[pageGroup.group] = group;
obj._elements[pageGroup.label] = label;
foreach(var pg; pageGroup.pages) {
var page = svg.getElementById(pg);
assert(page != nil, "Unable to find element " ~ pg);
obj._elements[pg] = page;
}
}
# Timers to control when to hide the menu after inactivity, and when to load
# a new page.
obj._hideMenuTimer = maketimer(3, obj, obj.hideMenu);
obj._hideMenuTimer.singleShot = 1;
obj._loadPageTimer = maketimer(0.5, obj, obj.loadPage);
obj._loadPageTimer.singleShot = 1;
obj.hideMenu();
return obj;
},
addPage : func(name, page)
{
me._pageList[name] = page;
},
getPage : func(name)
{
return me._pageList[name];
},
hideMenu : func()
{
foreach(var pageGroup; PAGE_GROUPS)
{
me._elements[pageGroup.group].setVisible(0);
me._elements[pageGroup.label].setVisible(0);
}
me._menuVisible = 0;
},
# Function to change a page based on the selection
loadPage : func()
{
var pageToLoad = PAGE_GROUPS[me._selectedPageGroup].pages[me._selectedPage];
var page = me._pageList[pageToLoad];
assert(page != nil, "Unable to find page " ~ pageToLoad);
me._device.selectPage(page);
},
showMenu : func()
{
foreach(var pageGroup; PAGE_GROUPS)
{
if (PAGE_GROUPS[me._selectedPageGroup].label == pageGroup.label)
{
# Display the page group and highlight the label
me._elements[pageGroup.group].setVisible(1);
me._elements[pageGroup.label].setVisible(1);
me._elements[pageGroup.label].setColor(0.7,0.7,1.0);
foreach (var page; pageGroup.pages)
{
# Highlight the current page.
if (pageGroup.pages[me._selectedPage] == page) {
me._elements[page].setColor(0.7,0.7,1.0);
} else {
me._elements[page].setColor(0.7,0.7,0.7);
}
}
}
else
{
# Hide the pagegroup and unhighlight the label on the bottom
me._elements[pageGroup.group].setVisible(0);
me._elements[pageGroup.label].setVisible(1);
me._elements[pageGroup.label].setColor(0.7,0.7,0.7);
}
}
me._menuVisible = 1;
me._hideMenuTimer.stop();
me._hideMenuTimer.restart(3);
me._loadPageTimer.stop();
me._loadPageTimer.restart(0.5);
},
handleFMSOuter : func(val)
{
if (me._menuVisible == 1) {
# Change page group
var incr_or_decr = (val > 0) ? 1 : -1;
me._selectedPageGroup = math.mod(me._selectedPageGroup + incr_or_decr, size(PAGE_GROUPS));
me._selectedPage = 0;
}
me.showMenu();
return emesary.Transmitter.ReceiptStatus_Finished;
},
handleFMSInner : func(val)
{
if (me._menuVisible == 1) {
# Change page group
var incr_or_decr = (val > 0) ? 1 : -1;
me._selectedPage = math.mod(me._selectedPage + incr_or_decr, size(PAGE_GROUPS[me._selectedPageGroup].pages));
}
me.showMenu();
return emesary.Transmitter.ReceiptStatus_Finished;
},
};

View file

@ -0,0 +1,254 @@
# MFD Surround
#
# Header fields at the top of the page
#
# PageGroup navigation, displayed in the bottom right of the
# FMS, and controlled by the FMS knob
# Set of pages, references by SVG ID
var PAGE_GROUPS = [
{ label: "MapPageGroupLabel",
group: "MapPageGroup",
pages: [ "NavigationMap", "TrafficMap", "Stormscope", "WeatherDataLink", "TAWSB"],
},
{ label: "WPTGroupLabel",
group: "WPTPageGroup",
pages: [ "AirportInfo", "IntersectionInfo", "NDBInfo", "VORInfo", "UserWPTInfo"],
},
{ label: "AuxGroupLabel",
group: "AuxPageGroup",
pages: [ "TripPlanning", "Utility", "GPSStatus", "XMRadio", "SystemStatus"],
},
{ label: "FPLGroupLabel",
group: "FPLPageGroup",
pages: [ "ActiveFlightPlanWide", "FlightPlanCatalog", "StoredFlightPlan"],
},
{ label: "LstGroupLabel",
group: "LstPageGroup",
pages: [ "Checklist1", "Checklist2", "Checklist3", "Checklist4", "Checklist5"],
},
{ label: "NrstGroupLabel",
group: "NrstPageGroup",
pages: [ "NearestAirports", "NearestIntersections", "NearestNDB", "NearestVOR", "NearestUserWaypoints", "NearestFrequencies", "NearestAirspaces"],
}
];
var Surround =
{
new : func (mfd, myCanvas, device, svg)
{
var obj = { parents : [
Surround,
MFDPage.new(mfd, myCanvas, device, svg, "Surround", ""),
] };
var textElements = [
"Comm1StandbyFreq", "Comm1SelectedFreq",
"Comm2StandbyFreq", "Comm2SelectedFreq",
"Nav1StandbyFreq", "Nav1SelectedFreq",
"Nav2StandbyFreq", "Nav2SelectedFreq",
];
obj.addTextElements(textElements);
obj._comm1selected = PFD.HighlightElement.new(obj.pageName, svg, "Comm1Selected", "Comm1");
obj._comm2selected = PFD.HighlightElement.new(obj.pageName, svg, "Comm2Selected", "Comm2");
obj._nav1selected = PFD.HighlightElement.new(obj.pageName, svg, "Nav1Selected", "Nav1");
obj._nav2selected = PFD.HighlightElement.new(obj.pageName, svg, "Nav2Selected", "Nav2");
obj._comm1failed = PFD.HighlightElement.new(obj.pageName, svg, "Comm1Failed", "Comm1");
obj._comm2failed = PFD.HighlightElement.new(obj.pageName, svg, "Comm2Failed", "Comm2");
obj._nav1failed = PFD.HighlightElement.new(obj.pageName, svg, "Nav1Failed", "Nav1");
obj._nav2failed = PFD.HighlightElement.new(obj.pageName, svg, "Nav2Failed", "Nav2");
obj._canvas = myCanvas;
obj._menuVisible = 0;
obj._selectedPageGroup = 0;
obj._selectedPage = 0;
# List of pages to be controllers. Keys are the pages in PAGE_GROUPS;
obj._pageList = {};
obj._elements = {};
foreach (var pageGroup; PAGE_GROUPS) {
var group = svg.getElementById(pageGroup.group);
var label = svg.getElementById(pageGroup.label);
assert(group != nil, "Unable to find element " ~ pageGroup.group);
assert(label != nil, "Unable to find element " ~ pageGroup.label);
obj._elements[pageGroup.group] = group;
obj._elements[pageGroup.label] = label;
foreach(var pg; pageGroup.pages) {
var page = svg.getElementById(pg);
assert(page != nil, "Unable to find element " ~ pg);
obj._elements[pg] = page;
}
}
# Timers to control when to hide the menu after inactivity, and when to load
# a new page.
obj._hideMenuTimer = maketimer(3, obj, obj.hideMenu);
obj._hideMenuTimer.singleShot = 1;
obj._loadPageTimer = maketimer(0.5, obj, obj.loadPage);
obj._loadPageTimer.singleShot = 1;
obj.hideMenu();
obj.controller = fg1000.SurroundController.new(obj, svg);
return obj;
},
handleNavComData : func(data) {
foreach(var name; keys(data)) {
var val = data[name];
if (name == "Comm1SelectedFreq") me.setTextElement("Comm1SelectedFreq", sprintf("%0.03f", val));
if (name == "Comm1StandbyFreq") me.setTextElement("Comm1StandbyFreq", sprintf("%0.03f", val));
if (name == "Comm1Serviceable") {
if (val == 1) {
me._comm1failed.unhighlightElement()
} else {
me._comm1failed.highlightElement();
}
}
if (name == "Comm2SelectedFreq") me.setTextElement("Comm2SelectedFreq", sprintf("%0.03f", val));
if (name == "Comm2StandbyFreq") me.setTextElement("Comm2StandbyFreq", sprintf("%0.03f", val));
if (name == "Comm2Serviceable") {
if (val == 1) {
me._comm2failed.unhighlightElement();
} else {
me._comm2failed.highlightElement();
}
}
if (name == "CommSelected") {
if (val == 1) {
me._comm1selected.highlightElement();
me._comm2selected.unhighlightElement();
} else {
me._comm1selected.unhighlightElement();
me._comm2selected.highlightElement();
}
}
if (name == "Nav1SelectedFreq") me.setTextElement("Nav1SelectedFreq", sprintf("%0.03f", val));
if (name == "Nav1StandbyFreq") me.setTextElement("Nav1StandbyFreq", sprintf("%0.03f", val));
if (name == "Nav1Serviceable") {
if (val == 1) {
me._nav1failed.unhighlightElement();
} else {
me._nav1failed.highlightElement();
}
}
if (name == "Nav2SelectedFreq") me.setTextElement("Nav2SelectedFreq", sprintf("%0.03f", val));
if (name == "Nav2StandbyFreq") me.setTextElement("Nav2StandbyFreq", sprintf("%0.03f", val));
if (name == "Nav2Serviceable") {
if (val == 1) {
me._nav2failed.unhighlightElement();
} else {
me._nav2failed.highlightElement();
}
}
if (name == "NavSelected") {
if (val == 1) {
me._nav1selected.highlightElement();
me._nav2selected.unhighlightElement();
} else {
me._nav1selected.unhighlightElement();
me._nav2selected.highlightElement();
}
}
}
},
addPage : func(name, page)
{
me._pageList[name] = page;
},
getPage : func(name)
{
return me._pageList[name];
},
# Function to change a page based on the selection
loadPage : func()
{
var pageToLoad = PAGE_GROUPS[me._selectedPageGroup].pages[me._selectedPage];
var page = me._pageList[pageToLoad];
assert(page != nil, "Unable to find page " ~ pageToLoad);
me.device.selectPage(page);
},
incrPageGroup : func(val) {
var incr_or_decr = (val > 0) ? 1 : -1;
me._selectedPageGroup = math.mod(me._selectedPageGroup + incr_or_decr, size(PAGE_GROUPS));
me._selectedPage = 0;
},
incrPage : func(val) {
var incr_or_decr = (val > 0) ? 1 : -1;
me._selectedPage = math.mod(me._selectedPage + incr_or_decr, size(PAGE_GROUPS[me._selectedPageGroup].pages));
},
showMenu : func()
{
foreach(var pageGroup; PAGE_GROUPS)
{
if (PAGE_GROUPS[me._selectedPageGroup].label == pageGroup.label)
{
# Display the page group and highlight the label
me._elements[pageGroup.group].setVisible(1);
me._elements[pageGroup.label].setVisible(1);
me._elements[pageGroup.label].setColor(0.7,0.7,1.0);
foreach (var page; pageGroup.pages)
{
# Highlight the current page.
if (pageGroup.pages[me._selectedPage] == page) {
me._elements[page].setColor(0.7,0.7,1.0);
} else {
me._elements[page].setColor(0.7,0.7,0.7);
}
}
}
else
{
# Hide the pagegroup and unhighlight the label on the bottom
me._elements[pageGroup.group].setVisible(0);
me._elements[pageGroup.label].setVisible(1);
me._elements[pageGroup.label].setColor(0.7,0.7,0.7);
}
}
me._menuVisible = 1;
me._hideMenuTimer.stop();
me._hideMenuTimer.restart(3);
me._loadPageTimer.stop();
me._loadPageTimer.restart(0.5);
},
hideMenu : func()
{
foreach(var pageGroup; PAGE_GROUPS)
{
me._elements[pageGroup.group].setVisible(0);
me._elements[pageGroup.label].setVisible(0);
}
me._menuVisible = 0;
},
isMenuVisible : func()
{
return me._menuVisible;
},
};

View file

@ -0,0 +1,81 @@
# Surround Controller
var SurroundController =
{
new : func (page, svg)
{
var obj = {
parents : [ SurroundController ],
_recipient : nil,
_page : page,
};
obj.RegisterWithEmesary();
return obj;
},
del : func() {
me.DeRegisterWithEmesary();
},
handleNavComData : func(values) {
# pass straight through the to page - we have nothing to do.
me._page.handleNavComData(values);
return emesary.Transmitter.ReceiptStatus_OK;
},
# These methods are slightly unusual in that they are called by other
# controllers when the CRSR is not active. Hence they aren't referenced
# in the RegisterWithEmesary call below.
#
handleFMSOuter : func(val)
{
if (me._page.isMenuVisible()) {
# Change page group
me._page.incrPageGroup(val);
}
me._page.showMenu();
return emesary.Transmitter.ReceiptStatus_Finished;
},
handleFMSInner : func(val)
{
if (me._page.isMenuVisible()) {
# Change page group
me.incrPage(val);
}
me.showMenu();
return emesary.Transmitter.ReceiptStatus_Finished;
},
RegisterWithEmesary : func(transmitter = nil){
if (transmitter == nil)
transmitter = emesary.GlobalTransmitter;
if (me._recipient == nil){
me._recipient = emesary.Recipient.new("PageGroupController_" ~ me._page.device.designation);
var pfd_obj = me._page.device;
var controller = me;
me._recipient.Receive = func(notification)
{
if (notification.Device_Id == pfd_obj.device_id
and notification.NotificationType == notifications.PFDEventNotification.DefaultType) {
if (notification.Event_Id == notifications.PFDEventNotification.NavComData
and notification.EventParameter != nil)
{
return controller.handleNavComData(notification.EventParameter);
}
}
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;
},
};

View file

@ -0,0 +1,28 @@
# Surround Options
var SurroundOptions =
{
new : func() {
var obj = { parents : [SurroundOptions] };
obj.Options= {};
obj.loadOptions();
return obj;
},
getOption : func(type) {
return me.Options[type];
},
setOption : func(type, name, value) {
me.Options[type][name] = value;
},
loadOptions : func() {
me.clearOptions();
me.Options.APS = {};
},
clearOptions : func() {
me.Options = {};
},
};

View file

@ -0,0 +1,28 @@
# Surround Styles
var SurroundStyles =
{
new : func() {
var obj = { parents : [ SurroundStyles ]};
obj.Styles = {};
obj.loadStyles();
return obj;
},
getStyle : func(type) {
return me.Styles[type];
},
setStyle : func(type, name, value) {
me.Styles[type][name] = value;
},
loadStyles : func() {
me. clearStyles();
me.Styles.XXX = {};
},
clearStyles : func() {
me.Styles = {};
},
};

View file

@ -545,6 +545,7 @@ var PFDEventNotification =
# 3 Change softkey button text
# 4 hardkey pushed - i.e. non-soft keys that don't change function based on context.
# 5 Engine data - e.g. RPM, EGTs, CHTs for display purposes
# 6 NavCom data - e.g. frequencies, volume, TX, RX for each of the COM and NAV radios.
# _event_param - param related to the event ID. implementation specific.
##
SoftKeyPushed : 1,
@ -552,6 +553,7 @@ var PFDEventNotification =
ChangeMenuText : 3, #event parameter contains array of { Id: , Text: } tuples
HardKeyPushed : 4, #event parameter contains single { Id: , Value: } tuple
EngineData : 5, #event parameter contains an array of hashes, each containing information about a given engine.
NavComData : 6, #event paramterr contains a hash of updated Nav/Com settings
DefaultType : "PFDEventNotification",

View file

@ -15,6 +15,8 @@
var self = cmdarg();
var listeners = [];
var mfd = nil;
var eisPublisher = nil;
var navcomPublisher = nil;
]]></open>
<close><![CDATA[
@ -22,6 +24,11 @@
removelistener(l);
setsize(listeners, 0);
mfd.del();
eisPublisher.stop();
eisPublisher = nil;
navcomPublisher.stop();
navcomPublisher = nil;
]]></close>
</nasal>
@ -362,8 +369,17 @@
var nasal_dir = getprop("/sim/fg-root") ~ "/Aircraft/Instruments-3d/FG1000/Nasal/";
io.load_nasal(nasal_dir ~ 'MFD.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/PropertyPublisher.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/GenericEISPublisher.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/GenericNavComPublisher.nas', "fg1000");
mfd = fg1000.MFD.new(myCanvas);
eisPublisher = fg1000.GenericEISPublisher.new();
eisPublisher.start();
navcomPublisher = fg1000.GenericNavComPublisher.new();
navcomPublisher.start();
softkeypushed = 0;
# Connect the buttons - using the provided model index to get the right ones from the model binding
var softkey_listener = setlistener("/sim/gui/dialogs/fg1000/softkey-pressed", func(v)