From c37f1ff7bbc83a127e4c2f0dca7870d9a6d39972 Mon Sep 17 00:00:00 2001 From: Stuart Buchanan Date: Sun, 4 Mar 2018 20:02:16 +0000 Subject: [PATCH] FG1000 PFD Inset Map, HDG, BARO, ALT controls - Add inset map for PFD - Add bindings to set the HDG, BARO, Altitude --- .../FG1000/MFDPages/PFDInstruments.svg | 78 +++++- .../FG1000/Nasal/ConfigStore.nas | 59 +++++ .../Instruments-3d/FG1000/Nasal/Constants.nas | 40 +++ .../Instruments-3d/FG1000/Nasal/FG1000.nas | 1 + .../Nasal/Interfaces/GenericFMSPublisher.nas | 14 +- .../Nasal/Interfaces/GenericFMSUpdater.nas | 37 +++ .../Interfaces/GenericInterfaceController.nas | 4 + .../Instruments-3d/FG1000/Nasal/MFDPage.nas | 9 + .../FG1000/Nasal/MFDPageController.nas | 8 +- .../NavigationMap/NavigationMapController.nas | 98 ++----- .../PFDInstruments/PFDInstruments.nas | 206 ++++++++++++--- .../PFDInstrumentsController.nas | 108 ++++++-- .../MFDPages/Surround/SurroundController.nas | 66 ++++- .../Instruments-3d/FG1000/Nasal/NavMap.nas | 239 ++++++++++++++++++ 14 files changed, 801 insertions(+), 166 deletions(-) create mode 100644 Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericFMSUpdater.nas create mode 100644 Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas diff --git a/Aircraft/Instruments-3d/FG1000/MFDPages/PFDInstruments.svg b/Aircraft/Instruments-3d/FG1000/MFDPages/PFDInstruments.svg index a3c61f79d..866a2e904 100644 --- a/Aircraft/Instruments-3d/FG1000/MFDPages/PFDInstruments.svg +++ b/Aircraft/Instruments-3d/FG1000/MFDPages/PFDInstruments.svg @@ -24,11 +24,11 @@ borderopacity="1" inkscape:pageopacity="1" inkscape:pageshadow="2" - inkscape:zoom="7.4375004" - inkscape:cx="279.11962" - inkscape:cy="261.78273" + inkscape:zoom="3.7187502" + inkscape:cx="99.463593" + inkscape:cy="150.41314" inkscape:document-units="px" - inkscape:current-layer="layer6" + inkscape:current-layer="svg3140" showgrid="false" showguides="false" inkscape:guide-bbox="true" @@ -75,7 +75,7 @@ image/svg+xml - + @@ -6998,13 +6998,67 @@ id="PFDInstrumentsPFD-Map" inkscape:label="PFD-Map"> + style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:1.005;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" + id="PFDInstrumentsPFD-Map-bg-1" + width="218.99536" + height="218.99538" + x="9.3594561" + y="491.24179" + inkscape:label="PFD-Map-bg" /> + + + + + NORTH UP + + 15nm . +# +# FMS Interface using Emesary to update FMS properties from Emesary messages. + +var GenericFMSUpdater = +{ + new : func () { + var obj = { + parents : [ + GenericFMSUpdater, + PropertyUpdater.new( + notifications.PFDEventNotification.DefaultType, + notifications.PFDEventNotification.FMSData, + ) + ], + }; + + obj.addPropMap("FMSHeadingBug", "/autopilot/settings/heading-bug-deg"); + obj.addPropMap("FMSSelectedAlt", "/autopilot/settings/target-alt-ft"); + obj.addPropMap("FMSPressureSettingInHG", "/instrumentation/altimeter/setting-inhg"); + return obj; + }, +}; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas index 2ac874175..d19cbb82c 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/Interfaces/GenericInterfaceController.nas @@ -24,6 +24,7 @@ io.load_nasal(nasal_dir ~ 'Interfaces/GenericNavComPublisher.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericNavComUpdater.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/NavDataInterface.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericFMSPublisher.nas', "fg1000"); +io.load_nasal(nasal_dir ~ 'Interfaces/GenericFMSUpdater.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericADCPublisher.nas', "fg1000"); @@ -51,6 +52,7 @@ var GenericInterfaceController = { obj.navcomUpdater = fg1000.GenericNavComUpdater.new(); obj.navdataInterface = fg1000.NavDataInterface.new(); obj.gpsPublisher = fg1000.GenericFMSPublisher.new(); + obj.gpsUpdater = fg1000.GenericFMSUpdater.new(); obj.adcPublisher = fg1000.GenericADCPublisher.new(); return obj; }, @@ -62,6 +64,7 @@ var GenericInterfaceController = { me.navcomUpdater.start(); me.navdataInterface.start(); me.gpsPublisher.start(); + me.gpsUpdater.start(); me.adcPublisher.start(); }, @@ -72,6 +75,7 @@ var GenericInterfaceController = { me.navcomUpdater.stop(); me.navdataInterface.stop(); me.gpsPublisher.stop(); + me.gpsUpdater.stop(); me.adcPublisher.stop(); }, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas index 7b3cf932b..207f9fa61 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPage.nas @@ -146,5 +146,14 @@ getDevice : func() { getMFD : func() { return me.mfd; }, +getPageName : func () { + return me.pageName; +}, +getSVG : func() { + return me._SVGGroup; +}, +getGroup : func() { + return me._group; +} }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas index d1218a639..554740766 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPageController.nas @@ -44,7 +44,7 @@ handleNavFreqTransfer : func (value) { return me.page.mfd.SurroundController.han handleNavOuter : func (value) { return me.page.mfd.SurroundController.handleNavOuter(value); }, handleNavInner : func (value) { return me.page.mfd.SurroundController.handleNavInner(value); }, handleNavToggle : func (value) { return me.page.mfd.SurroundController.handleNavToggle(value); }, -handleHeading : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, +handleHeading : func (value) { return me.page.mfd.SurroundController.handleHeading(value); }, handleHeadingPress : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, # Joystick @@ -53,7 +53,7 @@ handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStat handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, #CRS/BARO -handleBaro : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, +handleBaro : func (value) { return me.page.mfd.SurroundController.handleBaro(value); }, handleCRS : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleCRSCenter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, @@ -94,8 +94,8 @@ handleMenu : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcess handleProc : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleEnter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, -handleAltOuter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, -handleAltInner : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, +handleAltOuter : func (value) { return me.page.mfd.SurroundController.handleAltOuter(value); }, +handleAltInner : func (value) { return me.page.mfd.SurroundController.handleAltInner(value); }, RegisterWithEmesary : func() { diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas index ffe88d45e..4a9e34493 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/NavigationMap/NavigationMapController.nas @@ -17,75 +17,7 @@ # Navigation Map Controller var NavigationMapController = { - # Vertical ranges, and labels. - # 28 ranges from 500ft to 2000nm, measuring the vertical map distance. - # Vertical size of the map (once the nav box and softkey area is removed) is 689px. - # 2000nm = 12,152,000ft. - RANGES : [{range: 500/6076.12, label: "500ft"}, - {range: 750/6076.12, label: "750ft"}, - {range: 1000/6076.12, label: "1000ft"}, - {range: 1500/6076.12, label: "1500ft"}, - {range: 2000/6076.12, label: "2000ft"}, - {range: 0.5, label: "0.5nm"}, - {range: 0.75, label: "0.75nm"}, - {range: 1, label: "1nm"}, - {range: 2, label: "2nm"}, - {range: 3, label: "3nm"}, - {range: 4, label: "4nm"}, - {range: 6, label: "6nm"}, - {range: 8, label: "8nm"}, - {range: 10, label: "10nm"}, - {range: 12, label: "12nm"}, - {range: 15, label: "15nm"}, - {range: 20, label: "20nm"}, - {range: 25, label: "25nm"}, - {range: 30, label: "30nm"}, - {range: 40, label: "40nm"}, - {range: 50, label: "50nm"}, - {range: 75, label: "75nm"}, - {range: 100, label: "100nm"}, - {range: 200, label: "200nm"}, - {range: 500, label: "500nm"}, - {range: 1000, label: "1000nm"}, - {range: 1500, label: "1500nm"}, - {range: 2000, label: "2000nm"}, ], - ORIENTATIONS : [ - { label: "NORTH UP" }, - { label: "TRK UP" }, - { label: "DTK UP" }, - { label: "HDG UP" }, - ], - - # Layer display configuration: - # enabled - whether this layer has been enabled by the user - # declutter - the maximum declutter level (0-3) that this layer is visible in - # range - the maximum range this layer is visible (configured by user) - # max_range - the maximum range value that a user can configure for this layer. - LAYER_RANGES : { - DTO : { enabled: 0, declutter: 3, range: 2000, max_range: 2000 }, - - GRID : { enabled: 0, declutter: 1, range: 20, max_range: 2000 }, - DME : { enabled: 1, declutter: 1, range: 150, max_range: 300 }, - VOR_FG1000 : { enabled: 1, declutter: 1, range: 150, max_range: 300 }, - NDB : { enabled: 1, declutter: 1, range: 15, max_range: 30 }, - FIX : { enabled: 1, declutter: 1, range: 15, max_range: 30 }, - RTE : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 }, - WPT : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 }, - - APS : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 }, - FLT : { enabled: 1, declutter: 3, range: 2000, max_range: 2000 }, - - WXR : { enabled: 1, declutter: 2, range: 2000, max_range: 2000 }, - - APT : { enabled: 1, declutter: 2, range: 150, max_range: 300 }, - - TFC : { enabled: 0, declutter: 3, range: 150, max_range: 2000}, - - OpenAIP : { enabled: 1, declutter: 1, range: 150, max_range: 300 }, - STAMEN : { enabled: 1, declutter: 3, range: 500, max_range: 2000 }, - STAMEN_terrain : { enabled: 1, declutter: 3, range: 500, max_range: 2000 }, - }, # TODO: Add STAMEN topo layer, which is visible on all levels as opposed to # roads, railways, boundaries, cities which are only visible on declutter 0. @@ -96,7 +28,6 @@ var NavigationMapController = # Airways levels. AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"], - new : func (page, svg) { var obj = { parents : [ NavigationMapController, MFDPageController.new(page) ] }; @@ -105,7 +36,7 @@ var NavigationMapController = obj.airways = 0; obj.page = page; obj.setZoom(obj.current_zoom); - obj.setOrientation(obj.ORIENTATIONS[0]); + obj.setOrientation(fg1000.ORIENTATIONS[0]); return obj; }, @@ -116,12 +47,12 @@ var NavigationMapController = me.setZoom(me.current_zoom +1); }, setZoom : func(zoom) { - if ((zoom < 0) or (zoom > (size(me.RANGES) - 1))) return; + if ((zoom < 0) or (zoom > (size(fg1000.RANGES) - 1))) return; me.current_zoom = zoom; # Ranges above represent vertical ranges, but the display is a rectangle, so # we need to use the diagonal range of the 1024 x 689, which is 617px. # 617px is 1.8 x 689/2, so we need to increase the range values by x1.8 - me.page.setRange(me.RANGES[zoom].range, me.RANGES[zoom].label); + me.page.setRange(fg1000.RANGES[zoom].range, fg1000.RANGES[zoom].label); me.updateVisibility(); }, setOrientation : func(orientation) { @@ -129,10 +60,10 @@ var NavigationMapController = }, updateVisibility : func() { # Determine which layers should be visible. - foreach (var layer_name; keys(me.LAYER_RANGES)) { - var layer = me.LAYER_RANGES[layer_name]; + foreach (var layer_name; me.page.mfd.ConfigStore.getLayerNames()) { + var layer = me.page.mfd.ConfigStore.getLayer(layer_name); if (layer.enabled and - (me.RANGES[me.current_zoom].range <= layer.range) and + (fg1000.RANGES[me.current_zoom].range <= layer.range) and (me.declutter <= layer.declutter) ) { me.page.MFDMap.getLayer(layer_name).setVisible(1); @@ -141,15 +72,11 @@ var NavigationMapController = } } }, - configureLayer : func(layer, enabled, range) { - me.LAYER_RANGES[layer].enabled = enabled; - me.LAYER_RANGES[layer].range = math.min(range, me.LAYER_RANGES[layer].max_range); - }, isEnabled : func(layer) { - return me.LAYER_RANGES[layer].enabled; + return me.page.mfd.ConfigStore.isLayerEnabled(layer); }, toggleLayer : func(layer) { - me.LAYER_RANGES[layer].enabled = ! me.LAYER_RANGES[layer].enabled; + me.page.mfd.ConfigStore.toggleLayerEnabled(layer); me.updateVisibility(); }, @@ -162,6 +89,10 @@ var NavigationMapController = me.updateVisibility(); }, + getDCLTRTitle : func() { + return me.DCLTR[me.declutter]; + }, + # Increment through the AIRWAYS levels. At present this doesn't do anything # except change the label. It should enable/disable different airways # information. @@ -171,13 +102,16 @@ var NavigationMapController = device.updateMenus(); me.updateVisibility(); }, + getAIRWAYSTitle : func() { + return me.AIRWAYS[me.airways]; + }, # Set the DTO line target setDTOLineTarget : func(lat, lon) { me.page.MFDMap.getLayer("DTO").controller.setTarget(lat,lon); }, enableDTO : func(enable) { - me.LAYER_RANGES["DTO"].enabled = enable; + me.page.mfd.ConfigStore.setLayerEnabled("DTO", enable); me.updateVisibility(); }, diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas index 15785673a..f930916cd 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstruments.nas @@ -28,6 +28,10 @@ var PFDInstruments = magenta : [1, 0, 1], }, + CDI_SOURCE : [ "GPS", "NAV1", "NAV2" ], + + BRG_SOURCE : ["OFF", "NAV1", "NAV2", "GPS", "ADF"], + new : func (mfd, myCanvas, device, svg) { var obj = { @@ -38,7 +42,7 @@ var PFDInstruments = _ias_already_exceeded : 0, _windDataDisplay : 0, - _CDISource : "OFF", + _CDISource : "GPS", _BRG1 : "OFF", _BRG2 : "OFF", _DME : 0, @@ -46,6 +50,7 @@ var PFDInstruments = _Map : 0, _Multiline : 0, _annunciation : 0, + }; # Hide various elements for the moment. TODO - implement @@ -78,12 +83,17 @@ var PFDInstruments = obj.device.svg.getElementById("PFDInstruments" ~ id).set("clip", clip); } + #obj.insetMap = fg1000.NavMap.new(obj, [130,170], "rect(-160px, 160px, 160px, -160px)", -100, 2); + obj._SVGGroup.setInt("z-index", 10); + #obj._SVGGroup.setVisible(0); + #obj.insetMap = fg1000.NavMap.new(obj, [119,601], "rect(-109px, 109px, 109px, -109px)", 50, 2); + obj.insetMap = fg1000.NavMap.new(obj, obj.getElement("PFD-Map-Display"), [119,601], "rect(-109px, 109px, 109px, -109px)", 0, 2); #obj.topMenu(device, obj, nil); obj.setController(fg1000.PFDInstrumentsController.new(obj, svg)); obj.setWindDisplay(0); - obj.setCDISource("OFF"); + obj.setCDISource("GPS"); obj.setBRG1("OFF"); obj.setBRG2("OFF"); obj.setDME(0); @@ -91,6 +101,10 @@ var PFDInstruments = obj.setMultiline(0); obj.setAnnunciation(0); obj.setOMI(""); + obj.setInsetMapVisible(0); + obj.updateHDG(0); + obj.updateSelectedALT(0); + obj.updateCRS(0); return obj; }, @@ -99,11 +113,11 @@ var PFDInstruments = topMenu : func(device, pg, menuitem) { pg.clearMenu(); pg.resetMenuColors(); - pg.addMenuItem(1, "INSET", pg); # TODO + pg.addMenuItem(1, "INSET", pg, pg.mfd.PFDInstruments.insetMenu); pg.addMenuItem(3, "PFD", pg, pg.mfd.PFDInstruments.PFDMenu); pg.addMenuItem(4, "OBS", pg); # TODO - pg.addMenuItem(5, "CDI", pg); # TODO - pg.addMenuItem(6, "DME", pg); # TODO + pg.addMenuItem(5, "CDI", pg, pg.incrCDI); + #pg.addMenuItem(6, "DME", pg, func(dev, pg, mi) { pg.toggleDME(); } ); # TODO pg.addMenuItem(7, "XPDR", pg); # TODO pg.addMenuItem(8, "IDENT", pg); # TODO pg.addMenuItem(9, "TMR/REF", pg); # TODO @@ -112,24 +126,121 @@ var PFDInstruments = device.updateMenus(); }, + incrCDI : func(dev, pg, mi) { + var idx = -1; + for (var i = 0; i < size(PFDInstruments.CDI_SOURCE); i = i + 1) { + if (PFDInstruments.CDI_SOURCE[i] == pg._CDISource) { + idx = i; + break; + } + } + + if (idx == -1) die("Unabled to increment CDI. _CDISource:" ~ me._CDISource); + + idx = math.mod(idx + 1, size(PFDInstruments.CDI_SOURCE)); + pg.setCDISource(PFDInstruments.CDI_SOURCE[idx]); + }, + + insetMenu : func(device, pg, menuitem) { + + # Switch on the inset Map + pg.setInsetMapVisible(1); + + pg.clearMenu(); + pg.resetMenuColors(); + pg.addMenuItem(0, "OFF", pg, func(dev, pg, mi) { pg.setInsetMapVisible(0); }); # TODO + pg.addMenuItem(1, "DCLTR", pg, + func(dev, pg, mi) { pg.insetMap.incrDCLTR(dev, mi); device.updateMenus(); }, + func(svg, mi) { pg.displayDCLTR(svg, mi); }, + ); + #pg.addMenuItem(2, "WXLGND", pg); # Optional + + # TODO: Support TRFC-1 to add traffic layer, TRFC-2 to just display a traffic map + pg.addMenuItem(3, "TRAFFIC", pg, + func(dev, pg, mi) { pg.insetMap.toggleLayer("TFC"); device.updateMenus(); }, # callback + func(svg, mi) { pg.display_toggle(device, svg, mi, "TFC"); } + ); + + pg.addMenuItem(4, "TOPO", pg, + func(dev, pg, mi) { pg.insetMap.toggleLayer("STAMEN"); device.updateMenus(); }, # callback + func(svg, mi) { pg.display_toggle(device, svg, mi, "STAMEN"); } + ); + + pg.addMenuItem(5, "TERRAIN", pg, + func(dev, pg, mi) { pg.insetMap.toggleLayer("STAMEN_terrain"); device.updateMenus(); }, # callback + func(svg, mi) { pg.display_toggle(device, svg, mi, "STAMEN_terrain"); } + ); + #pg.addMenuItem(6, "STRMSCP", pg); # TODO + #pg.addMenuItem(7, "NEXRAD", pg); # TODO + #pg.addMenuItem(8, "XM LTNG", pg); # TODO + #pg.addMenuItem(9, "METAR", pg); # TODO + pg.addMenuItem(10, "BACK", pg, pg.mfd.PFDInstruments.topMenu); + pg.addMenuItem(11, "ALERTS", pg); # TODO + device.updateMenus(); + }, + + displayDCLTR : func(svg, mi) { + mi.title = pg.mfd.PFDInstruments.insetMap.getDCLTRTitle(); + svg.setText(mi.title); + svg.setVisible(1); + }, + + # Display map toggle softkeys which change color depending + # on whether a particular layer is enabled or not. + display_toggle : func(device, svg, mi, layer) { + var bg_name = sprintf("SoftKey%d-bg",mi.menu_id); + if (me.insetMap.isEnabled(layer)) { + device.svg.getElementById(bg_name).setColorFill(0.5,0.5,0.5); + svg.setColor(0.0,0.0,0.0); + } else { + device.svg.getElementById(bg_name).setColorFill(0.0,0.0,0.0); + svg.setColor(1.0,1.0,1.0); + } + svg.setText(mi.title); + svg.setVisible(1); # display function + }, + PFDMenu : func(device, pg, menuitem) { pg.clearMenu(); pg.resetMenuColors(); pg.addMenuItem(0, "SYN VIS", pg); # TODO pg.addMenuItem(1, "DFLTS", pg); pg.addMenuItem(2, "WIND", pg, pg.mfd.PFDInstruments.windMenu); - pg.addMenuItem(3, "DME", pg); # TODO - pg.addMenuItem(4, "BRG1", pg); # TODO + #pg.addMenuItem(3, "DME", pg); # TODO + pg.addMenuItem(4, "BRG1", pg, pg.mfd.PFDInstruments.incrBRG1); # TODO pg.addMenuItem(5, "HSI FRMT", pg); # TODO - pg.addMenuItem(6, "BRG2", pg); # TODO + pg.addMenuItem(6, "BRG2", pg, pg.mfd.PFDInstruments.incrBRG2); # TODO #pg.addMenuItem(8, "IDENT", pg); # TODO pg.addMenuItem(8, "ALT UNIT ", pg); # TODO - pg.addMenuItem(9, "STD BARO", pg); # TODO + pg.addMenuItem(9, "STD BARO", pg, func(dev, pg, mi) { pg.getController().setStdBaro(); } ); pg.addMenuItem(10, "BACK", pg, pg.mfd.PFDInstruments.topMenu); pg.addMenuItem(11, "ALERTS", pg); # TODO device.updateMenus(); }, + incrBRG1 : func(dev, pg, mi) { pg.mfd.PFDInstruments.incrBRG("BRG1"); }, + incrBRG2 : func(dev, pg, mi) { pg.mfd.PFDInstruments.incrBRG("BRG2"); }, + + incrBRG : func(brg) { + var curr = (brg == "BRG1" ? me.getBRG1() : me.getBRG2()); + var idx = -1; + for (var i = 0; i < size(PFDInstruments.BRG_SOURCE); i = i + 1) { + if (PFDInstruments.BRG_SOURCE[i] == curr) { + idx = i; + break; + } + } + + if (idx == -1) die("Unabled to increment BRG. curr:" ~ curr); + + idx = math.mod(idx + 1, size(PFDInstruments.BRG_SOURCE)); + if (brg == "BRG1") { + me.setBRG1(PFDInstruments.BRG_SOURCE[idx]); + } else { + me.setBRG2(PFDInstruments.BRG_SOURCE[idx]); + } + }, + windMenu : func(device, pg, menuitem) { pg.clearMenu(); pg.resetMenuColors(); @@ -379,7 +490,7 @@ var PFDInstruments = updateBARO : func (baro) { # TODO: Support hPa and inhg #var fmt = me._baro_unit == "inhg" ? "%.2fin" : "%i%shPa"; - var fmt = "%.2fin"; + var fmt = "%.2fIN"; me.setTextElement("BARO-text", sprintf(fmt, baro)); }, @@ -394,7 +505,6 @@ var PFDInstruments = }, updateHDG : func (hdg) { - if (hdg == nil) me.getElement("Heading-bug").setRotation(hdg * D2R); me.setTextElement("SelectedHDG-text", sprintf("%03d°%s", hdg, "")); }, @@ -459,6 +569,10 @@ var PFDInstruments = } }, + toggleDME : func() { + me.setDME(! me._DME); + }, + setDME : func (enabled) { me._DME = enabled; me.getElement("DME1").setVisible(enabled); @@ -518,34 +632,48 @@ var PFDInstruments = me._CDISource = source; }, - updateCDI : func (heading, course, waypoint_valid, course_deviation_deg, deflection_dots, xtrk_nm, from) { + updateCDI : func (heading, course, waypoint_valid, course_deviation_deg, deflection_dots, xtrk_nm, from, annun) { if (me._CDISource == "OFF") return; - var rot = (course - heading) * D2R; - me.getElement("CDI") - .setRotation(rot) - .show(); - me.getElement("GPS-CTI-diamond") - .setVisible(waypoint_valid) - .setRotation(course_deviation_deg * D2R); - - if ((me._CDISource == "GPS") and (deflection_dots > 2)) { - # Only display the cross-track error if the error exceeds the maximum - # deflection of two dots. - me.getElement("CDI-GPS-XTK-text") - .setText(sprintf("XTK %iNM", abs(xtrk_nm))) - .show(); - } else { + if (waypoint_valid == 0) { + me.getElement(me._CDISource ~ "-CDI").hide(); + me.getElement(me._CDISource ~ "-FROM").hide(); + me.getElement(me._CDISource ~ "-TO").hide(); + me.getElement("CDI").setRotation(0); + me.getElement("GPS-CTI-diamond").hide(); me.getElement("CDI-GPS-XTK-text").hide(); + me.getElement("CDI-GPS-ANN-text").hide(); + } else { + me.getElement(me._CDISource ~ "-CDI").show(); + + var rot = (course - heading) * D2R; + me.getElement("CDI") + .setRotation(rot) + .show(); + me.getElement("GPS-CTI-diamond") + .setVisible(waypoint_valid) + .setRotation(course_deviation_deg * D2R); + + if ((me._CDISource == "GPS") and (deflection_dots > 2)) { + # Only display the cross-track error if the error exceeds the maximum + # deflection of two dots. + me.getElement("CDI-GPS-XTK-text") + .setText(sprintf("XTK %iNM", abs(xtrk_nm))) + .show(); + } else { + me.getElement("CDI-GPS-XTK-text").hide(); + } + + if (me._CDISource == "GPS") me.getElement("CDI-GPS-ANN-text").setText(annun).show(); + + var scale = math.clamp(deflection_dots, -2.4, 2.4); + me.getElement(me._CDISource ~ "-CDI").setTranslation(65 * scale, 0); + + # Display the appropriate TO/FROM indication for the selected source, + # switching all others off. + me.getElement(me._CDISource ~ "-TO").setVisible(from == 0); + me.getElement(me._CDISource ~ "-FROM").setVisible(from); } - - var scale = math.clamp(deflection_dots, -2.4, 2.4); - me.getElement(me._CDISource ~ "-CDI").setTranslation(65 * scale, 0); - - # Display the appropriate TO/FROM indication for the selected source, - # switching all others off. - me.getElement(me._CDISource ~ "-TO").setVisible(from == 0); - me.getElement(me._CDISource ~ "-FROM").setVisible(from); }, # Update the wind display. There are three options: @@ -631,5 +759,11 @@ var PFDInstruments = me.getElement("MarkerText").setText(omi); } me._OMI = omi; - } + }, + + setInsetMapVisible :func(enabled ) { + me.getElement("PFD-Map").setVisible(enabled); + me.getElement("PFD-Map-bg").setVisible(enabled); + me.insetMap.setVisible(enabled); + }, }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas index 3237229f3..eb3391d02 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/PFDInstruments/PFDInstrumentsController.nas @@ -17,11 +17,6 @@ # PFDInstruments Controller var PFDInstrumentsController = { - - - # Declutter levels. - WIND : [ "DCLTR", "DCLTR-1", "DCLTR-2", "DCLTR-3"], - new : func (page, svg) { var obj = { @@ -35,6 +30,13 @@ var PFDInstrumentsController = _selected_alt_ft : 0, _heading : 0, _source : "GPS", + _from :0, + _leg_id : "", + _leg_bearing : 0, + _leg_distance_nm : 0, + _leg_deviation_deg : 0, + _deflection_dots :0, + _leg_xtrk_nm : 0, }; return obj; @@ -74,6 +76,25 @@ var PFDInstrumentsController = } }, + handleRange : func(val) + { + var incr_or_decr = (val > 0) ? me.page.insetMap.zoomIn() : me.page.insetMap.zoomOut(); + }, + + # Set the STD BARO to 29.92 in Hg + setStdBaro : func() { + var data = {}; + data["FMSPressureSettingInHG"] = 29.92; + + var notification = notifications.PFDEventNotification.new( + "MFD", + me._page.mfd.getDeviceID(), + notifications.PFDEventNotification.FMSData, + data); + + me.transmitter.NotifyAll(notification); + }, + # Handle update of the airdata information. # ADC data is produced periodically as an entire set handleADCData : func(data) { @@ -97,6 +118,7 @@ var PFDInstrumentsController = me.page.updateVSI(data["ADCVerticalSpeedFPM"]); me.page.updateTAS(data["ADCTrueAirspeed"]); me.page.updateBARO(data["ADCPressureSettingInHG"]); + me.page.updateOAT(data["ADCOutsideAirTemperatureC"]); me.page.updateHSI(data["ADCHeadingDeg"]); me._heading = data["ADCHeadingDeg"]; @@ -113,31 +135,65 @@ var PFDInstrumentsController = return emesary.Transmitter.ReceiptStatus_OK; }, - # Handle update to the FMS information. + # Handle update to the FMS information. Note that there is no guarantee + # that the entire set of FMS data will be available. handleFMSData : func(data) { - me.page.updateHDG(data["FMSHeadingBug"]); - me.page.updateSelectedALT(data["FMSSelectedAlt"]); - me._selected_alt_ft = data["FMSSelectedAlt"]; - - me.page.updateCRS(data["FMSLegBearing"]); - - var from = 0; - if (me._navSelected == 1) { - from = data["FMSNav1From"]; - } else { - from = data["FMSNav2From"]; + if (data["FMSHeadingBug"] != nil) me.page.updateHDG(data["FMSHeadingBug"]); + if (data["FMSSelectedAlt"] != nil) { + me.page.updateSelectedALT(data["FMSSelectedAlt"]); + me._selected_alt_ft = data["FMSSelectedAlt"]; } - me.page.updateCDI( - heading: me._heading, - course: data["FMSLegBearing"], - waypoint_valid: 1, - course_deviation_deg : data["FMSLegTrackErrorAngle"], - deflection_dots : data["FMSLegCourseError"], # TODO: proper conversion depending on source, environment - xtrk_nm : data["FMSLegCourseError"], - from: from, - ); + if (data["FMSLegValid"] == "false") { + # No valid leg data, likely because there's no GPS course set + me.page.updateCRS(0); + me.page.updateCDI( + heading: me._heading, + course: 0, + waypoint_valid: 0, + course_deviation_deg : 0, + deflection_dots : 0, + xtrk_nm : 0, + from: 0, + ); + + # Update the bearing indicators with GPS data if that's what we're displaying. + if (me.page.getBRG1() == "GPS") me.page.setBRG1("NONE", 0); + if (me.page.getBRG2() == "GPS") me.page.setBRG2("NONE", 0); + } else { + + if (data["FMSLegBearing"] != nil) me.page.updateCRS(data["FMSLegBearing"]); + + if (me._navSelected == 1) { + if (data["FMSNav1From"] != nil) me._from = data["FMSNav1From"]; + } else { + if (data["FMSNav2From"] != nil) me._from = data["FMSNav2From"]; + } + + if (data["FMSLegID"] != nil) me._leg_id = data["FMSLegID"]; + if (data["FMSLegBearing"] != nil) me._leg_bearing = data["FMSLegBearing"]; + if (data["FMSLegTrackErrorAngle"] != nil) me._leg_deviation_deg = data["FMSLegTrackErrorAngle"]; + + # TODO: Proper cross-track error based on source and flight phase. + if (data["FMSLegCourseError"] != nil) me.deflection_dots = data["FMSLegCourseError"]; + if (data["FMSLegCourseError"] != nil) me._leg_xtrk_nm = data["FMSLegCourseError"]; + + me.page.updateCDI( + heading: me._heading, + course: me._leg_bearing, + waypoint_valid: (data["FMSLegValid"] == "true"), + course_deviation_deg : me._leg_deviation_deg, + deflection_dots : me._deflection_dots, + xtrk_nm : me._leg_xtrk_nm, + from: me._from, + annun: "ENR" + ); + + # Update the bearing indicators with GPS data if that's what we're displaying. + if (me.page.getBRG1() == "GPS") me.page.setBRG1(me._leg_id, me._leg_bearing); + if (me.page.getBRG2() == "GPS") me.page.setBRG2(me._leg_id, me._leg_bearing); + } return emesary.Transmitter.ReceiptStatus_OK; }, diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/SurroundController.nas b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/SurroundController.nas index 2c8efdd6f..e8f21e3fd 100644 --- a/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/SurroundController.nas +++ b/Aircraft/Instruments-3d/FG1000/Nasal/MFDPages/Surround/SurroundController.nas @@ -33,6 +33,9 @@ var SurroundController = _nav1standby : 0.0, _nav2active : 0.0, _nav2standby : 0.0, + _pressure_settings_inhg : 0.0, + _selected_alt_ft : 0.0, + _heading_bug_deg : 0.0, }; obj.RegisterWithEmesary(); @@ -54,6 +57,17 @@ var SurroundController = me.transmitter.NotifyAll(notification); }, + # Helper function to notify the Emesary bridge of a FMSData update. + sendFMSDataNotification : func(data) { + var notification = notifications.PFDEventNotification.new( + "MFD", + me._page.mfd.getDeviceID(), + notifications.PFDEventNotification.FMSData, + data); + + me.transmitter.NotifyAll(notification); + }, + handleNavComData : func(data) { # Store off particularly important data for control. @@ -75,6 +89,15 @@ var SurroundController = return emesary.Transmitter.ReceiptStatus_OK; }, + handleFMSADCData : func(data) { + if (data["ADCPressureSettingInHG"] != nil) me._pressure_settings_inhg = data["ADCPressureSettingInHG"]; + if (data["FMSSelectedAlt"] != nil) me._selected_alt_ft = data["FMSSelectedAlt"]; + if (data["FMSHeadingBug"] != nil) me._heading_bug_deg = data["FMSHeadingBug"]; + + # Pass FMS and ADC data straight to the page to display in the header fields + me._page.updateHeaderData(data); + return emesary.Transmitter.ReceiptStatus_OK; + }, # # Handle the various COM and NAV controls at the top left and top right of the Fascia @@ -334,6 +357,45 @@ var SurroundController = handleComVolToggle : func (value) { }, + handleBaro : func(value) { + var incr_or_decr = (value > 0) ? 1 : -1; + var press = me._pressure_settings_inhg + (incr_or_decr * 0.01); + var data = {}; + data["FMSPressureSettingInHG"] = sprintf("%.2f", press); + me.sendFMSDataNotification(data); + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleAltInner : func(value) { + var incr_or_decr = (value > 0) ? 1 : -1; + var alt = int(me._selected_alt_ft + incr_or_decr * 100); + if (alt < 0) alt = 0; + var data = {}; + data["FMSSelectedAlt"] = alt; + me.sendFMSDataNotification(data); + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleAltOuter : func(value) { + var incr_or_decr = (value > 0) ? 1 : -1; + var alt = int(me._selected_alt_ft + incr_or_decr * 1000); + if (alt < 0) alt = 0; + var data = {}; + data["FMSSelectedAlt"] = alt; + me.sendFMSDataNotification(data); + return emesary.Transmitter.ReceiptStatus_Finished; + }, + + handleHeading : func(value) { + var incr_or_decr = (value > 0) ? 1 : -1; + var hdg = me._heading_bug_deg + incr_or_decr; + hdg = math.mod(hdg, 360); + var data = {}; + data["FMSHeadingBug"] = sprintf("%i", hdg); + me.sendFMSDataNotification(data); + return emesary.Transmitter.ReceiptStatus_Finished; + }, + # 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. @@ -381,10 +443,8 @@ var SurroundController = (notification.Event_Id == notifications.PFDEventNotification.ADCData) ) and notification.EventParameter != nil) { - # Pass FMS and ADC data straight to the page to display in the header fields - return controller._page.updateHeaderData(notification.EventParameter); + return controller.handleFMSADCData(notification.EventParameter); } - } return emesary.Transmitter.ReceiptStatus_NotProcessed; }; diff --git a/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas new file mode 100644 index 000000000..db515c61a --- /dev/null +++ b/Aircraft/Instruments-3d/FG1000/Nasal/NavMap.nas @@ -0,0 +1,239 @@ +# Copyright 2018 Stuart Buchanan +# This file is part of FlightGear. +# +# FlightGear is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# FlightGear is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with FlightGear. If not, see . +# +# Common Navigation map functions +var NavMap = { + + # Declutter levels. + DCLTR : [ "DCLTR", "DCLTR-1", "DCLTR-2", "DCLTR-3"], + + # Airways levels. + AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"], + + new : func(page, element, center, clip="", zindex=0, vis_shift=0 ) + { + var obj = { + parents : [ NavMap ], + _group : page.getGroup(), + _svg : page.getSVG(), + _page : page, + _pageName : page.getPageName(), + current_zoom : 13, + declutter : 0, + airways : 0, + vis_shift : vis_shift, + }; + + element.setTranslation(center[0], center[1]); + + obj.Styles = fg1000.NavigationMapStyles.new(); + obj.Options = fg1000.NavigationMapOptions.new(); + + obj.Map = element.createChild("map"); + obj.Map.setScreenRange(689/2.0); + + obj._rangeDisplay = obj._svg.getElementById(obj._pageName ~ "RangeDisplay"); + if (obj._rangeDisplay == nil) die("Unable to find element " ~ obj._pageName ~ "RangeDisplay"); + + obj._orientationDisplay = obj._svg.getElementById(obj._pageName ~ "OrientationDisplay"); + if (obj._orientationDisplay == nil) die("Unable to find element " ~ obj._pageName ~ "OrientationDisplay"); + + # Initialize the controllers: + var ctrl_ns = canvas.Map.Controller.get("Aircraft position"); + var source = ctrl_ns.SOURCES["current-pos"]; + if (source == nil) { + # TODO: amend + var source = ctrl_ns.SOURCES["current-pos"] = { + getPosition: func subvec(geo.aircraft_position().latlon(), 0, 2), + getAltitude: func getprop('/position/altitude-ft'), + getHeading: func { + if (me.aircraft_heading) + getprop('/orientation/heading-deg') + else 0 + }, + aircraft_heading: 1, + }; + } + setlistener("/sim/gui/dialogs/map-canvas/aircraft-heading-up", func(n) { + source.aircraft_heading = n.getBoolValue(); + }, 1); + # Make it move with our aircraft: + obj.Map.setController("Aircraft position", "current-pos"); # from aircraftpos.controller + + if (clip != "") { + obj.Map.set("clip-frame", canvas.Element.LOCAL); + obj.Map.set("clip", clip); + } + + if (zindex != 0) { + element.setInt("z-index", zindex); + } + + var r = func(name,vis=1,zindex=nil) return caller(0)[0]; + # TODO: we'll need some z-indexing here, right now it's just random + foreach(var type; [r('GRID'),r('DTO',0),r('TFC',0),r('APT'),r('DME'),r('VOR_FG1000'),r('NDB'),r('FIX',0),r('GPS'),r('RTE'),r('WPT'),r('FLT'),r('WXR',0),r('APS')] ) { + obj.Map.addLayer( + factory: canvas.SymbolLayer, + type_arg: type.name, + priority: 4, + style: obj.Styles.getStyle(type.name), + options: obj.Options.getOption(type.name), + visible: type.vis); + } + + foreach(var type; [ r('STAMEN_terrain'),r('STAMEN'), r('OpenAIP') ]) { + obj.Map.addLayer( + factory: canvas.OverlayLayer, + type_arg: type.name, + priority: 1, + style: obj.Styles.getStyle(type.name), + options: obj.Options.getOption(type.name), + visible: 0); + } + + obj.setZoom(obj.current_zoom); + obj.setOrientation(0); + obj.Map.setVisible(0); + return obj; + }, + + toggleLayerVisible : func(name) { + (var l = me.Map.getLayer(name)).setVisible(l.getVisible()); + }, + + setLayerVisible : func(name,n=1) { + me.Map.getLayer(name).setVisible(n); + }, + + setRange : func(range, label) { + me.Map.setRange(range); + me._rangeDisplay.setText(label); + }, + + setOrientation : func(orientation) { + # TODO - implment this + me._orientationDisplay.setText(fg1000.ORIENTATIONS[orientation].label); + }, + + setScreenRange : func(range) { + me.Map.setScreenRange(range); + }, + + zoomIn : func() { + me.setZoom(me.current_zoom -1); + }, + + zoomOut : func() { + me.setZoom(me.current_zoom +1); + }, + + setZoom : func(zoom) { + if ((zoom < 0) or (zoom > (size(fg1000.RANGES) - 1))) return; + me.current_zoom = zoom; + # Ranges above represent vertical ranges, but the display is a rectangle, so + # we need to use the diagonal range of the 1024 x 689, which is 617px. + # 617px is 1.8 x 689/2, so we need to increase the range values by x1.8 + me.setRange(fg1000.RANGES[zoom].range, fg1000.RANGES[zoom].label); + me.updateVisibility(); + }, + + updateVisibility : func() { + # Determine which layers should be visible. + foreach (var layer_name; me._page.mfd.ConfigStore.getLayerNames()) { + var layer = me._page.mfd.ConfigStore.getLayer(layer_name); + + # Layers are only displayed if: + # 1) the user has enabled them. + # 2) The current zoom level is _less_ than the maximum range for the layer + # (i.e. as the range gets larger, we remove layers). Note that for + # inset maps, the range that items are removed is lower. + # 3) They haven't been removed due to the declutter level. + var effective_zoom = math.clamp(me.current_zoom + me.vis_shift, 0, size(fg1000.RANGES)); + var effective_range = fg1000.RANGES[effective_zoom].range; + if (layer.enabled and + (effective_range <= layer.range) and + (me.declutter <= layer.declutter) ) + { + me.Map.getLayer(layer_name).setVisible(1); + } else { + me.Map.getLayer(layer_name).setVisible(0); + } + } + }, + + isEnabled : func(layer) { + return me._page.mfd.ConfigStore.isLayerEnabled(layer); + }, + + toggleLayer : func(layer) { + me._page.mfd.ConfigStore.toggleLayerEnabled(layer); + me.updateVisibility(); + }, + + # Increment through the de-clutter levels, which impact what layers are + # displayed. We also need to update the declutter menu item. + incrDCLTR : func(device, menuItem) { + me.declutter = math.mod(me.declutter +1, 4); + me.updateVisibility(); + return me.DCLTR[me.declutter]; + }, + + getDCLTRTitle : func() { + return me.DCLTR[me.declutter]; + }, + + # Increment through the AIRWAYS levels. At present this doesn't do anything + # except change the label. It should enable/disable different airways + # information. + incrAIRWAYS : func(device, menuItem) { + me.airways = math.mod(me.airways +1, 4); + me.updateVisibility(); + return me.AIRWAYS[me.airways]; + }, + + getAIRWAYSTitle : func() { + return me.AIRWAYS[me.airways]; + }, + + # Set the DTO line target + setDTOLineTarget : func(lat, lon) { + me.Map.getLayer("DTO").controller.setTarget(lat,lon); + }, + enableDTO : func(enable) { + me._page.mfd.ConfigStore.setLayerEnabled("DTO", enable); + me.updateVisibility(); + }, + + handleRange : func(val) + { + var incr_or_decr = (val > 0) ? 1 : -1; + me.setZoom(me.current_zoom + incr_or_decr); + }, + + getMap : func() { + return me.Map; + }, + show : func() { + me.Map.show(); + }, + hide : func() { + me.Map.hide(); + }, + setVisible : func(visible) { + me.Map.setVisible(visible); + } + +};