1
0
Fork 0

FG1000 PFD Inset Map, HDG, BARO, ALT controls

- Add inset map for PFD
- Add bindings to set the HDG, BARO, Altitude
This commit is contained in:
Stuart Buchanan 2018-03-04 20:02:16 +00:00
parent e95dfc5b02
commit c37f1ff7bb
14 changed files with 801 additions and 166 deletions

View file

@ -24,11 +24,11 @@
borderopacity="1" borderopacity="1"
inkscape:pageopacity="1" inkscape:pageopacity="1"
inkscape:pageshadow="2" inkscape:pageshadow="2"
inkscape:zoom="7.4375004" inkscape:zoom="3.7187502"
inkscape:cx="279.11962" inkscape:cx="99.463593"
inkscape:cy="261.78273" inkscape:cy="150.41314"
inkscape:document-units="px" inkscape:document-units="px"
inkscape:current-layer="layer6" inkscape:current-layer="svg3140"
showgrid="false" showgrid="false"
showguides="false" showguides="false"
inkscape:guide-bbox="true" inkscape:guide-bbox="true"
@ -75,7 +75,7 @@
<dc:format>image/svg+xml</dc:format> <dc:format>image/svg+xml</dc:format>
<dc:type <dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title> <dc:title />
</cc:Work> </cc:Work>
</rdf:RDF> </rdf:RDF>
</metadata> </metadata>
@ -6998,13 +6998,67 @@
id="PFDInstrumentsPFD-Map" id="PFDInstrumentsPFD-Map"
inkscape:label="PFD-Map"> inkscape:label="PFD-Map">
<rect <rect
inkscape:label="PFD-Map-bg" 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"
y="492" id="PFDInstrumentsPFD-Map-bg-1"
x="13" width="218.99536"
height="218" height="218.99538"
width="215" x="9.3594561"
id="PFD-Map-bg" y="491.24179"
style="opacity:1;fill:#000000;fill-opacity:1;stroke:#ffffff;stroke-width:2;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" /> inkscape:label="PFD-Map-bg" />
<g
id="PFDInstrumentsPFD-Map-Display"
inkscape:label="#g14879">
<rect
style="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"
width="218.99536"
height="218.99538"
x="9.5023146"
y="491.50232"
inkscape:label="PFD-Map-bg" />
</g>
<rect
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.97517461;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="PFDInstrumentsOrientation-bg"
width="79.886292"
height="21.989275"
x="146.78056"
y="492.74817"
inkscape:label="#rect14859" />
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:center;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
x="186.54517"
y="508.86316"
id="PFDInstrumentsOrientationDisplay"
sodipodi:linespacing="125%"
inkscape:label="#text14869"><tspan
sodipodi:role="line"
id="tspan14871"
x="186.54517"
y="508.86316"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:center;writing-mode:lr-tb;text-anchor:middle;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none">NORTH UP</tspan></text>
<rect
inkscape:label="#rect14859"
y="688.24396"
x="147.18393"
height="21.989275"
width="79.886292"
id="rect14873"
style="opacity:1;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:0.97517461;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
<text
sodipodi:linespacing="125%"
id="PFDInstrumentsRangeDisplay"
y="704.09003"
x="223.3855"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:end;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:end;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"
inkscape:label="#text14875"><tspan
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:15px;line-height:125%;font-family:Sans;-inkscape-font-specification:'Sans, Normal';text-align:end;writing-mode:lr-tb;text-anchor:end"
y="704.09003"
x="223.3855"
id="tspan14877"
sodipodi:role="line">15nm</tspan></text>
</g> </g>
<g <g
id="PFDInstrumentsBRG2" id="PFDInstrumentsBRG2"

Before

Width:  |  Height:  |  Size: 464 KiB

After

Width:  |  Height:  |  Size: 468 KiB

View file

@ -22,6 +22,36 @@
var ConfigStore = { var ConfigStore = {
# 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.
layerRanges : {
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 },
},
configValues : { configValues : {
"DisplayUnitsNavAngle": ["MAGNETIC", "TRUE"] , "DisplayUnitsNavAngle": ["MAGNETIC", "TRUE"] ,
"DisplayUnitsDistanceAndSpeed": ["NAUTICAL", "METRIC"] , "DisplayUnitsDistanceAndSpeed": ["NAUTICAL", "METRIC"] ,
@ -91,6 +121,7 @@ var ConfigStore = {
var obj ={ var obj ={
parents : [ ConfigStore ], parents : [ ConfigStore ],
_values : {}, _values : {},
_layerRanges : {},
}; };
foreach (var i; keys(ConfigStore.configValues)) { foreach (var i; keys(ConfigStore.configValues)) {
@ -107,6 +138,10 @@ var ConfigStore = {
#obj.set("MFDHeader4", "ESA"); #obj.set("MFDHeader4", "ESA");
obj.set("MFDHeader4", "FOD"); obj.set("MFDHeader4", "FOD");
foreach (var i; keys(ConfigStore.layerRanges)) {
obj._layerRanges[i] = ConfigStore.layerRanges[i];
}
return obj; return obj;
}, },
@ -140,4 +175,28 @@ var ConfigStore = {
get : func(name) { get : func(name) {
return me._values[name]; return me._values[name];
}, },
getLayer : func(name) {
return me._layerRanges[name];
},
getLayerNames : func() {
return keys(me._layerRanges);
},
isLayerEnabled : func(name) {
return me._layerRanges[name].enabled;
},
setLayerEnabled : func(name, enabled) {
me._layerRanges[name].enabled = enabled;
},
toggleLayerEnabled : func(name) {
me._layerRanges[name].enabled = ! me._layerRanges[name].enabled;
},
configureLayer : func(layer, enabled, range) {
me._layerRanges[layer].enabled = enabled;
me._layerRanges[layer].range = math.min(range, me._layerRanges[layer].max_range);
},
}; };

View file

@ -167,3 +167,43 @@ var SURFACE_TYPES = {
9 : "DIRT", 9 : "DIRT",
0 : "GRAVEL", 0 : "GRAVEL",
}; };
# 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.
var 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"}, ];
var ORIENTATIONS = [
{ label: "NORTH UP" },
{ label: "TRK UP" },
{ label: "DTK UP" },
{ label: "HDG UP" },
];

View file

@ -31,6 +31,7 @@ io.load_nasal(nasal_dir ~ '/MFDPageController.nas', "fg1000");
io.load_nasal(nasal_dir ~ '/PFD.nas', "fg1000"); io.load_nasal(nasal_dir ~ '/PFD.nas', "fg1000");
io.load_nasal(nasal_dir ~ '/MFD.nas', "fg1000"); io.load_nasal(nasal_dir ~ '/MFD.nas', "fg1000");
io.load_nasal(nasal_dir ~ '/GUI.nas', "fg1000"); io.load_nasal(nasal_dir ~ '/GUI.nas', "fg1000");
io.load_nasal(nasal_dir ~ '/NavMap.nas', "fg1000");
var FG1000 = { var FG1000 = {

View file

@ -29,14 +29,17 @@ var GenericFMSPublisher =
obj.addPropMap("FMSHeadingBug", "/autopilot/settings/heading-bug-deg"); obj.addPropMap("FMSHeadingBug", "/autopilot/settings/heading-bug-deg");
obj.addPropMap("FMSSelectedAlt", "/autopilot/settings/target-alt-ft"); obj.addPropMap("FMSSelectedAlt", "/autopilot/settings/target-alt-ft");
obj.addPropMap("FMSLegValid", "/instrumentation/gps/wp/wp[1]/valid");
obj.addPropMap("FMSLegID", "/instrumentation/gps/wp/wp[1]/ID");
obj.addPropMap("FMSLegBearing", "/instrumentation/gps/wp/wp[1]/bearing-mag-deg"); obj.addPropMap("FMSLegBearing", "/instrumentation/gps/wp/wp[1]/bearing-mag-deg");
obj.addPropMap("FMSLegDistanceNM", "/instrumentation/gps/wp/wp[1]/distance-nm");
obj.addPropMap("FMSLegCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); obj.addPropMap("FMSLegCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm");
obj.addPropMap("FMSLegDesiredTrack", "/instrumentation/gps/indicated-track-magnetic-deg"); obj.addPropMap("FMSLegDesiredTrack", "/instrumentation/gps/wp/wp[1]/desired-course-deg");
obj.addPropMap("FMSLegTrackErrorAngle", "/instrumentation/gps/wp/wp[1]/course-deviation-deg"); obj.addPropMap("FMSLegTrackErrorAngle", "/instrumentation/gps/wp/wp[1]/course-deviation-deg");
obj.addPropMap("FMSLegTrack", "/instrumentation/gps/indicated-track-magnetic-deg");
obj.addPropMap("FMSGroundspeed", "/instrumentation/gps/indicated-ground-speed-kt");
obj.addPropMap("FMSWayPointCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm"); obj.addPropMap("FMSWayPointCourseError", "/instrumentation/gps/wp/wp[1]/course-error-nm");
obj.addPropMap("FMSGroundspeed", "/instrumentation/gps/indicated-ground-speed-kt");
obj.addPropMap("FMSNav1From", "/instrumentation/nav/from-flag"); obj.addPropMap("FMSNav1From", "/instrumentation/nav/from-flag");
obj.addPropMap("FMSNav2From", "/instrumentation/nav[1]/from-flag"); obj.addPropMap("FMSNav2From", "/instrumentation/nav[1]/from-flag");
@ -52,6 +55,11 @@ var GenericFMSPublisher =
gpsdata[name] = propmap.getValue(); gpsdata[name] = propmap.getValue();
} }
# Some GPS properties have odd values to indicate that nothing is set, so
# remove them from the data set.
if (gpsdata["FMSLegBearing"] == -9999) gpsdata["FMSLegBearing"] = nil;
if (gpsdata["FMSLegDistanceNM"] == -1) gpsdata["FMSLegDistanceNM"] = nil;
# A couple of calculated values used by the MFD Header display # A couple of calculated values used by the MFD Header display
var total_fuel = getprop("/consumables/fuel/tank[0]/indicated-level-gal_us") or 0.0; var total_fuel = getprop("/consumables/fuel/tank[0]/indicated-level-gal_us") or 0.0;
total_fuel = total_fuel + (getprop("/consumables/fuel/tank[1]/indicated-level-gal_us") or 0.0); total_fuel = total_fuel + (getprop("/consumables/fuel/tank[1]/indicated-level-gal_us") or 0.0);

View file

@ -0,0 +1,37 @@
# Copyright 2018 Stuart Buchanan
# This file is part of FlightGear.
#
# Foobar 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 <http://www.gnu.org/licenses/>.
#
# 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;
},
};

View file

@ -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/GenericNavComUpdater.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/NavDataInterface.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/GenericFMSPublisher.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/GenericFMSUpdater.nas', "fg1000");
io.load_nasal(nasal_dir ~ 'Interfaces/GenericADCPublisher.nas', "fg1000"); io.load_nasal(nasal_dir ~ 'Interfaces/GenericADCPublisher.nas', "fg1000");
@ -51,6 +52,7 @@ var GenericInterfaceController = {
obj.navcomUpdater = fg1000.GenericNavComUpdater.new(); obj.navcomUpdater = fg1000.GenericNavComUpdater.new();
obj.navdataInterface = fg1000.NavDataInterface.new(); obj.navdataInterface = fg1000.NavDataInterface.new();
obj.gpsPublisher = fg1000.GenericFMSPublisher.new(); obj.gpsPublisher = fg1000.GenericFMSPublisher.new();
obj.gpsUpdater = fg1000.GenericFMSUpdater.new();
obj.adcPublisher = fg1000.GenericADCPublisher.new(); obj.adcPublisher = fg1000.GenericADCPublisher.new();
return obj; return obj;
}, },
@ -62,6 +64,7 @@ var GenericInterfaceController = {
me.navcomUpdater.start(); me.navcomUpdater.start();
me.navdataInterface.start(); me.navdataInterface.start();
me.gpsPublisher.start(); me.gpsPublisher.start();
me.gpsUpdater.start();
me.adcPublisher.start(); me.adcPublisher.start();
}, },
@ -72,6 +75,7 @@ var GenericInterfaceController = {
me.navcomUpdater.stop(); me.navcomUpdater.stop();
me.navdataInterface.stop(); me.navdataInterface.stop();
me.gpsPublisher.stop(); me.gpsPublisher.stop();
me.gpsUpdater.stop();
me.adcPublisher.stop(); me.adcPublisher.stop();
}, },
}; };

View file

@ -146,5 +146,14 @@ getDevice : func() {
getMFD : func() { getMFD : func() {
return me.mfd; return me.mfd;
}, },
getPageName : func () {
return me.pageName;
},
getSVG : func() {
return me._SVGGroup;
},
getGroup : func() {
return me._group;
}
}; };

View file

@ -44,7 +44,7 @@ handleNavFreqTransfer : func (value) { return me.page.mfd.SurroundController.han
handleNavOuter : func (value) { return me.page.mfd.SurroundController.handleNavOuter(value); }, handleNavOuter : func (value) { return me.page.mfd.SurroundController.handleNavOuter(value); },
handleNavInner : func (value) { return me.page.mfd.SurroundController.handleNavInner(value); }, handleNavInner : func (value) { return me.page.mfd.SurroundController.handleNavInner(value); },
handleNavToggle : func (value) { return me.page.mfd.SurroundController.handleNavToggle(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; }, handleHeadingPress : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
# Joystick # Joystick
@ -53,7 +53,7 @@ handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStat
handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
#CRS/BARO #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; }, handleCRS : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
handleCRSCenter : 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; }, handleProc : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
handleEnter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleEnter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
handleAltOuter : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleAltOuter : func (value) { return me.page.mfd.SurroundController.handleAltOuter(value); },
handleAltInner : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; }, handleAltInner : func (value) { return me.page.mfd.SurroundController.handleAltInner(value); },
RegisterWithEmesary : func() RegisterWithEmesary : func()
{ {

View file

@ -17,75 +17,7 @@
# Navigation Map Controller # Navigation Map Controller
var NavigationMapController = 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 # 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. # roads, railways, boundaries, cities which are only visible on declutter 0.
@ -96,7 +28,6 @@ var NavigationMapController =
# Airways levels. # Airways levels.
AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"], AIRWAYS : [ "AIRWAYS", "AIRWY ON", "AIRWY LO", "AIRWY HI"],
new : func (page, svg) new : func (page, svg)
{ {
var obj = { parents : [ NavigationMapController, MFDPageController.new(page) ] }; var obj = { parents : [ NavigationMapController, MFDPageController.new(page) ] };
@ -105,7 +36,7 @@ var NavigationMapController =
obj.airways = 0; obj.airways = 0;
obj.page = page; obj.page = page;
obj.setZoom(obj.current_zoom); obj.setZoom(obj.current_zoom);
obj.setOrientation(obj.ORIENTATIONS[0]); obj.setOrientation(fg1000.ORIENTATIONS[0]);
return obj; return obj;
}, },
@ -116,12 +47,12 @@ var NavigationMapController =
me.setZoom(me.current_zoom +1); me.setZoom(me.current_zoom +1);
}, },
setZoom : func(zoom) { 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; me.current_zoom = zoom;
# Ranges above represent vertical ranges, but the display is a rectangle, so # 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. # 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 # 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(); me.updateVisibility();
}, },
setOrientation : func(orientation) { setOrientation : func(orientation) {
@ -129,10 +60,10 @@ var NavigationMapController =
}, },
updateVisibility : func() { updateVisibility : func() {
# Determine which layers should be visible. # Determine which layers should be visible.
foreach (var layer_name; keys(me.LAYER_RANGES)) { foreach (var layer_name; me.page.mfd.ConfigStore.getLayerNames()) {
var layer = me.LAYER_RANGES[layer_name]; var layer = me.page.mfd.ConfigStore.getLayer(layer_name);
if (layer.enabled and 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.declutter <= layer.declutter) )
{ {
me.page.MFDMap.getLayer(layer_name).setVisible(1); 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) { isEnabled : func(layer) {
return me.LAYER_RANGES[layer].enabled; return me.page.mfd.ConfigStore.isLayerEnabled(layer);
}, },
toggleLayer : func(layer) { toggleLayer : func(layer) {
me.LAYER_RANGES[layer].enabled = ! me.LAYER_RANGES[layer].enabled; me.page.mfd.ConfigStore.toggleLayerEnabled(layer);
me.updateVisibility(); me.updateVisibility();
}, },
@ -162,6 +89,10 @@ var NavigationMapController =
me.updateVisibility(); me.updateVisibility();
}, },
getDCLTRTitle : func() {
return me.DCLTR[me.declutter];
},
# Increment through the AIRWAYS levels. At present this doesn't do anything # Increment through the AIRWAYS levels. At present this doesn't do anything
# except change the label. It should enable/disable different airways # except change the label. It should enable/disable different airways
# information. # information.
@ -171,13 +102,16 @@ var NavigationMapController =
device.updateMenus(); device.updateMenus();
me.updateVisibility(); me.updateVisibility();
}, },
getAIRWAYSTitle : func() {
return me.AIRWAYS[me.airways];
},
# Set the DTO line target # Set the DTO line target
setDTOLineTarget : func(lat, lon) { setDTOLineTarget : func(lat, lon) {
me.page.MFDMap.getLayer("DTO").controller.setTarget(lat,lon); me.page.MFDMap.getLayer("DTO").controller.setTarget(lat,lon);
}, },
enableDTO : func(enable) { enableDTO : func(enable) {
me.LAYER_RANGES["DTO"].enabled = enable; me.page.mfd.ConfigStore.setLayerEnabled("DTO", enable);
me.updateVisibility(); me.updateVisibility();
}, },

View file

@ -28,6 +28,10 @@ var PFDInstruments =
magenta : [1, 0, 1], magenta : [1, 0, 1],
}, },
CDI_SOURCE : [ "GPS", "NAV1", "NAV2" ],
BRG_SOURCE : ["OFF", "NAV1", "NAV2", "GPS", "ADF"],
new : func (mfd, myCanvas, device, svg) new : func (mfd, myCanvas, device, svg)
{ {
var obj = { var obj = {
@ -38,7 +42,7 @@ var PFDInstruments =
_ias_already_exceeded : 0, _ias_already_exceeded : 0,
_windDataDisplay : 0, _windDataDisplay : 0,
_CDISource : "OFF", _CDISource : "GPS",
_BRG1 : "OFF", _BRG1 : "OFF",
_BRG2 : "OFF", _BRG2 : "OFF",
_DME : 0, _DME : 0,
@ -46,6 +50,7 @@ var PFDInstruments =
_Map : 0, _Map : 0,
_Multiline : 0, _Multiline : 0,
_annunciation : 0, _annunciation : 0,
}; };
# Hide various elements for the moment. TODO - implement # Hide various elements for the moment. TODO - implement
@ -78,12 +83,17 @@ var PFDInstruments =
obj.device.svg.getElementById("PFDInstruments" ~ id).set("clip", clip); 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.topMenu(device, obj, nil);
obj.setController(fg1000.PFDInstrumentsController.new(obj, svg)); obj.setController(fg1000.PFDInstrumentsController.new(obj, svg));
obj.setWindDisplay(0); obj.setWindDisplay(0);
obj.setCDISource("OFF"); obj.setCDISource("GPS");
obj.setBRG1("OFF"); obj.setBRG1("OFF");
obj.setBRG2("OFF"); obj.setBRG2("OFF");
obj.setDME(0); obj.setDME(0);
@ -91,6 +101,10 @@ var PFDInstruments =
obj.setMultiline(0); obj.setMultiline(0);
obj.setAnnunciation(0); obj.setAnnunciation(0);
obj.setOMI(""); obj.setOMI("");
obj.setInsetMapVisible(0);
obj.updateHDG(0);
obj.updateSelectedALT(0);
obj.updateCRS(0);
return obj; return obj;
}, },
@ -99,11 +113,11 @@ var PFDInstruments =
topMenu : func(device, pg, menuitem) { topMenu : func(device, pg, menuitem) {
pg.clearMenu(); pg.clearMenu();
pg.resetMenuColors(); 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(3, "PFD", pg, pg.mfd.PFDInstruments.PFDMenu);
pg.addMenuItem(4, "OBS", pg); # TODO pg.addMenuItem(4, "OBS", pg); # TODO
pg.addMenuItem(5, "CDI", pg); # TODO pg.addMenuItem(5, "CDI", pg, pg.incrCDI);
pg.addMenuItem(6, "DME", pg); # TODO #pg.addMenuItem(6, "DME", pg, func(dev, pg, mi) { pg.toggleDME(); } ); # TODO
pg.addMenuItem(7, "XPDR", pg); # TODO pg.addMenuItem(7, "XPDR", pg); # TODO
pg.addMenuItem(8, "IDENT", pg); # TODO pg.addMenuItem(8, "IDENT", pg); # TODO
pg.addMenuItem(9, "TMR/REF", pg); # TODO pg.addMenuItem(9, "TMR/REF", pg); # TODO
@ -112,24 +126,121 @@ var PFDInstruments =
device.updateMenus(); 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) { PFDMenu : func(device, pg, menuitem) {
pg.clearMenu(); pg.clearMenu();
pg.resetMenuColors(); pg.resetMenuColors();
pg.addMenuItem(0, "SYN VIS", pg); # TODO pg.addMenuItem(0, "SYN VIS", pg); # TODO
pg.addMenuItem(1, "DFLTS", pg); pg.addMenuItem(1, "DFLTS", pg);
pg.addMenuItem(2, "WIND", pg, pg.mfd.PFDInstruments.windMenu); pg.addMenuItem(2, "WIND", pg, pg.mfd.PFDInstruments.windMenu);
pg.addMenuItem(3, "DME", pg); # TODO #pg.addMenuItem(3, "DME", pg); # TODO
pg.addMenuItem(4, "BRG1", pg); # TODO pg.addMenuItem(4, "BRG1", pg, pg.mfd.PFDInstruments.incrBRG1); # TODO
pg.addMenuItem(5, "HSI FRMT", pg); # 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, "IDENT", pg); # TODO
pg.addMenuItem(8, "ALT UNIT ", 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(10, "BACK", pg, pg.mfd.PFDInstruments.topMenu);
pg.addMenuItem(11, "ALERTS", pg); # TODO pg.addMenuItem(11, "ALERTS", pg); # TODO
device.updateMenus(); 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) { windMenu : func(device, pg, menuitem) {
pg.clearMenu(); pg.clearMenu();
pg.resetMenuColors(); pg.resetMenuColors();
@ -379,7 +490,7 @@ var PFDInstruments =
updateBARO : func (baro) { updateBARO : func (baro) {
# TODO: Support hPa and inhg # TODO: Support hPa and inhg
#var fmt = me._baro_unit == "inhg" ? "%.2fin" : "%i%shPa"; #var fmt = me._baro_unit == "inhg" ? "%.2fin" : "%i%shPa";
var fmt = "%.2fin"; var fmt = "%.2fIN";
me.setTextElement("BARO-text", sprintf(fmt, baro)); me.setTextElement("BARO-text", sprintf(fmt, baro));
}, },
@ -394,7 +505,6 @@ var PFDInstruments =
}, },
updateHDG : func (hdg) { updateHDG : func (hdg) {
if (hdg == nil)
me.getElement("Heading-bug").setRotation(hdg * D2R); me.getElement("Heading-bug").setRotation(hdg * D2R);
me.setTextElement("SelectedHDG-text", sprintf("%03d°%s", hdg, "")); me.setTextElement("SelectedHDG-text", sprintf("%03d°%s", hdg, ""));
}, },
@ -459,6 +569,10 @@ var PFDInstruments =
} }
}, },
toggleDME : func() {
me.setDME(! me._DME);
},
setDME : func (enabled) { setDME : func (enabled) {
me._DME = enabled; me._DME = enabled;
me.getElement("DME1").setVisible(enabled); me.getElement("DME1").setVisible(enabled);
@ -518,34 +632,48 @@ var PFDInstruments =
me._CDISource = source; 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; if (me._CDISource == "OFF") return;
var rot = (course - heading) * D2R; if (waypoint_valid == 0) {
me.getElement("CDI") me.getElement(me._CDISource ~ "-CDI").hide();
.setRotation(rot) me.getElement(me._CDISource ~ "-FROM").hide();
.show(); me.getElement(me._CDISource ~ "-TO").hide();
me.getElement("GPS-CTI-diamond") me.getElement("CDI").setRotation(0);
.setVisible(waypoint_valid) me.getElement("GPS-CTI-diamond").hide();
.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(); 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: # Update the wind display. There are three options:
@ -631,5 +759,11 @@ var PFDInstruments =
me.getElement("MarkerText").setText(omi); me.getElement("MarkerText").setText(omi);
} }
me._OMI = omi; me._OMI = omi;
} },
setInsetMapVisible :func(enabled ) {
me.getElement("PFD-Map").setVisible(enabled);
me.getElement("PFD-Map-bg").setVisible(enabled);
me.insetMap.setVisible(enabled);
},
}; };

View file

@ -17,11 +17,6 @@
# PFDInstruments Controller # PFDInstruments Controller
var PFDInstrumentsController = var PFDInstrumentsController =
{ {
# Declutter levels.
WIND : [ "DCLTR", "DCLTR-1", "DCLTR-2", "DCLTR-3"],
new : func (page, svg) new : func (page, svg)
{ {
var obj = { var obj = {
@ -35,6 +30,13 @@ var PFDInstrumentsController =
_selected_alt_ft : 0, _selected_alt_ft : 0,
_heading : 0, _heading : 0,
_source : "GPS", _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; 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. # Handle update of the airdata information.
# ADC data is produced periodically as an entire set # ADC data is produced periodically as an entire set
handleADCData : func(data) { handleADCData : func(data) {
@ -97,6 +118,7 @@ var PFDInstrumentsController =
me.page.updateVSI(data["ADCVerticalSpeedFPM"]); me.page.updateVSI(data["ADCVerticalSpeedFPM"]);
me.page.updateTAS(data["ADCTrueAirspeed"]); me.page.updateTAS(data["ADCTrueAirspeed"]);
me.page.updateBARO(data["ADCPressureSettingInHG"]); me.page.updateBARO(data["ADCPressureSettingInHG"]);
me.page.updateOAT(data["ADCOutsideAirTemperatureC"]); me.page.updateOAT(data["ADCOutsideAirTemperatureC"]);
me.page.updateHSI(data["ADCHeadingDeg"]); me.page.updateHSI(data["ADCHeadingDeg"]);
me._heading = data["ADCHeadingDeg"]; me._heading = data["ADCHeadingDeg"];
@ -113,31 +135,65 @@ var PFDInstrumentsController =
return emesary.Transmitter.ReceiptStatus_OK; 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) { handleFMSData : func(data) {
me.page.updateHDG(data["FMSHeadingBug"]); if (data["FMSHeadingBug"] != nil) me.page.updateHDG(data["FMSHeadingBug"]);
me.page.updateSelectedALT(data["FMSSelectedAlt"]); if (data["FMSSelectedAlt"] != nil) {
me._selected_alt_ft = data["FMSSelectedAlt"]; 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"];
} }
me.page.updateCDI( if (data["FMSLegValid"] == "false") {
heading: me._heading, # No valid leg data, likely because there's no GPS course set
course: data["FMSLegBearing"], me.page.updateCRS(0);
waypoint_valid: 1, me.page.updateCDI(
course_deviation_deg : data["FMSLegTrackErrorAngle"], heading: me._heading,
deflection_dots : data["FMSLegCourseError"], # TODO: proper conversion depending on source, environment course: 0,
xtrk_nm : data["FMSLegCourseError"], waypoint_valid: 0,
from: from, 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; return emesary.Transmitter.ReceiptStatus_OK;
}, },

View file

@ -33,6 +33,9 @@ var SurroundController =
_nav1standby : 0.0, _nav1standby : 0.0,
_nav2active : 0.0, _nav2active : 0.0,
_nav2standby : 0.0, _nav2standby : 0.0,
_pressure_settings_inhg : 0.0,
_selected_alt_ft : 0.0,
_heading_bug_deg : 0.0,
}; };
obj.RegisterWithEmesary(); obj.RegisterWithEmesary();
@ -54,6 +57,17 @@ var SurroundController =
me.transmitter.NotifyAll(notification); 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) { handleNavComData : func(data) {
# Store off particularly important data for control. # Store off particularly important data for control.
@ -75,6 +89,15 @@ var SurroundController =
return emesary.Transmitter.ReceiptStatus_OK; 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 # 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) { 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 # These methods are slightly unusual in that they are called by other
# controllers when the CRSR is not active. Hence they aren't referenced # controllers when the CRSR is not active. Hence they aren't referenced
# in the RegisterWithEmesary call below. # in the RegisterWithEmesary call below.
@ -381,10 +443,8 @@ var SurroundController =
(notification.Event_Id == notifications.PFDEventNotification.ADCData) ) (notification.Event_Id == notifications.PFDEventNotification.ADCData) )
and notification.EventParameter != nil) and notification.EventParameter != nil)
{ {
# Pass FMS and ADC data straight to the page to display in the header fields return controller.handleFMSADCData(notification.EventParameter);
return controller._page.updateHeaderData(notification.EventParameter);
} }
} }
return emesary.Transmitter.ReceiptStatus_NotProcessed; return emesary.Transmitter.ReceiptStatus_NotProcessed;
}; };

View file

@ -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 <http://www.gnu.org/licenses/>.
#
# 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);
}
};