e69c77aa8e
Transponder can now be set using PFD softkeys. (Should also be settable using the FMS knob, but this is not implemented yet)
875 lines
31 KiB
Text
875 lines
31 KiB
Text
# 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/>.
|
|
#
|
|
# PFDInstruments
|
|
var PFDInstruments =
|
|
{
|
|
|
|
COLORS : {
|
|
green : [0, 1, 0],
|
|
white : [1, 1, 1],
|
|
black : [0, 0, 0],
|
|
lightblue : [0, 1, 1],
|
|
darkblue : [0, 0, 1],
|
|
red : [1, 0, 0],
|
|
magenta : [1, 0, 1],
|
|
},
|
|
|
|
new : func (mfd, myCanvas, device, svg)
|
|
{
|
|
var obj = {
|
|
parents : [
|
|
PFDInstruments,
|
|
MFDPage.new(mfd, myCanvas, device, svg, "PFDInstruments", "PFD Instruments")
|
|
],
|
|
|
|
_ias_already_exceeded : 0,
|
|
_windDataDisplay : 0,
|
|
_BRG1 : "OFF",
|
|
_BRG2 : "OFF",
|
|
_DME : 0,
|
|
_OMI : "",
|
|
_Multiline : 0,
|
|
_annunciation : 0,
|
|
};
|
|
|
|
# Hide various elements for the moment. TODO - implement
|
|
obj.device.svg.getElementById("PFDInstrumentsFailures").setVisible(0);
|
|
obj.device.svg.getElementById("PFDInstrumentsGSPD").setVisible(0);
|
|
|
|
obj.addTextElements([
|
|
"Speed110",
|
|
"VSIText",
|
|
"TAS-text", "GSPD-text",
|
|
"Alt11100",
|
|
"AltBigC", "AltSmallC",
|
|
"BARO-text", "OAT-text",
|
|
"HDG-text",
|
|
"SelectedHDG-text",
|
|
"SelectedALT-text",
|
|
"XPDR-DIGIT-3-text", "XPDR-DIGIT-2-text", "XPDR-DIGIT-1-text", "XPDR-DIGIT-0-text",
|
|
"XPDR-MODE-text",
|
|
]);
|
|
|
|
# Set clipping for the various tapes
|
|
var clips = {
|
|
PitchScale : "rect(70,664,370,256)",
|
|
SpeedLint1 : "rect(252,226,318,204)",
|
|
SpeedTape : "rect(115,239,455,156)",
|
|
LintAlt : "rect(115,808,455,704)",
|
|
AltLint00011 : "rect(252,804,318,771)",
|
|
};
|
|
|
|
foreach(var id; keys(clips)) {
|
|
var clip = clips[id];
|
|
obj.device.svg.getElementById("PFDInstruments" ~ id).set("clip", clip);
|
|
}
|
|
|
|
obj._SVGGroup.setInt("z-index", 10);
|
|
obj.insetMap = fg1000.NavMap.new(obj, obj.getElement("PFD-Map-Display"), [119,601], "rect(-109px, 109px, 109px, -109px)", 0, 2);
|
|
|
|
# Flight Plan Window group
|
|
obj.flightplanList = PFD.GroupElement.new(
|
|
obj.pageName,
|
|
svg,
|
|
[ "FlightPlanArrow", "FlightPlanID", "FlightPlanType", "FlightPlanDTK", "FlightPlanDIS"],
|
|
5,
|
|
"FlightPlanArrow",
|
|
1,
|
|
"FlightPlanScrollTrough",
|
|
"FlightPlanScrollThumb",
|
|
120
|
|
);
|
|
|
|
obj.setController(fg1000.PFDInstrumentsController.new(obj, svg));
|
|
|
|
obj.setWindDisplay(0);
|
|
obj.setCDISource("GPS");
|
|
obj.setBRG1("OFF");
|
|
obj.setBRG2("OFF");
|
|
obj.setDME(0);
|
|
obj.setMultiline(0);
|
|
obj.setAnnunciation(0);
|
|
obj.setOMI("");
|
|
obj.setInsetMapVisible(0);
|
|
obj.updateHDG(0);
|
|
obj.updateSelectedALT(0);
|
|
obj.updateCRS(0);
|
|
obj.setFlightPlanVisible(0);
|
|
|
|
return obj;
|
|
},
|
|
|
|
topMenu : func(device, pg, menuitem) {
|
|
pg.clearMenu();
|
|
pg.resetMenuColors();
|
|
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, func(dev, pg, mi) { pg.getController().incrCDISource(); } );
|
|
#pg.addMenuItem(6, "DME", pg, func(dev, pg, mi) { pg.toggleDME(); } ); # TODO
|
|
pg.addMenuItem(7, "XPDR", pg, pg.mfd.PFDInstruments.transponderMenu);
|
|
pg.addMenuItem(8, "IDENT", pg, pg.mfd.PFDInstruments.setIdent); # TODO
|
|
pg.addMenuItem(9, "TMR/REF", pg); # TODO
|
|
pg.addMenuItem(10, "NRST", pg); # TODO
|
|
pg.addMenuItem(11, "ALERTS", pg); # TODO
|
|
device.updateMenus();
|
|
},
|
|
|
|
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 = me.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, func(dev, pg, mi) { pg.getController().incrBRG1(); });
|
|
pg.addMenuItem(5, "HSI FRMT", pg); # TODO
|
|
pg.addMenuItem(6, "BRG2", pg, func(dev, pg, mi) { pg.getController().incrBRG2(); });
|
|
pg.addMenuItem(8, "ALT UNIT ", 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();
|
|
},
|
|
|
|
windMenu : func(device, pg, menuitem) {
|
|
pg.clearMenu();
|
|
pg.resetMenuColors();
|
|
pg.addMenuItem(2, "OPTN1", pg,
|
|
func(dev, pg, mi) { pg.mfd.PFDInstruments.setWindDisplay(1); device.updateMenus(); }, # Action callback
|
|
func(svg, mi) { pg.mfd.PFDInstruments.toggleWindDisplay(device, svg, mi, 1); } # Display callback
|
|
);
|
|
pg.addMenuItem(3, "OPTN2", pg,
|
|
func(dev, pg, mi) { pg.mfd.PFDInstruments.setWindDisplay(2); device.updateMenus(); }, # Action callback
|
|
func(svg, mi) { pg.mfd.PFDInstruments.toggleWindDisplay(device, svg, mi, 2); } # Display callback
|
|
);
|
|
pg.addMenuItem(4, "OPTN3", pg,
|
|
func(dev, pg, mi) { pg.mfd.PFDInstruments.setWindDisplay(3); device.updateMenus(); }, # Action callback
|
|
func(svg, mi) { pg.mfd.PFDInstruments.toggleWindDisplay(device, svg, mi, 3); } # Display callback
|
|
);
|
|
pg.addMenuItem(5, "OFF", pg,
|
|
func(dev, pg, mi) { pg.mfd.PFDInstruments.setWindDisplay(0); device.updateMenus(); }, # Action callback
|
|
func(svg, mi) { pg.mfd.PFDInstruments.toggleWindDisplay(device, svg, mi, 0); } # Display callback
|
|
);
|
|
pg.addMenuItem(10, "BACK", pg, pg.mfd.PFDInstruments.topMenu);
|
|
pg.addMenuItem(11, "ALERTS", pg); # TODO
|
|
device.updateMenus();
|
|
},
|
|
|
|
toggleWindDisplay : func(device, svg, mi, wind_value) {
|
|
var bg_name = sprintf("SoftKey%d-bg",mi.menu_id);
|
|
if (me._windDataDisplay == wind_value) {
|
|
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
|
|
},
|
|
|
|
transponderMenu : func(device, pg, menuitem) {
|
|
pg.clearMenu();
|
|
pg.resetMenuColors();
|
|
pg.addMenuItem(2, "STBY", pg, pg.mfd.PFDInstruments.setTransponderMode);
|
|
pg.addMenuItem(3, "ON", pg, pg.mfd.PFDInstruments.setTransponderMode);
|
|
pg.addMenuItem(4, "ALT", pg, pg.mfd.PFDInstruments.setTransponderMode);
|
|
pg.addMenuItem(5, "GND", pg, pg.mfd.PFDInstruments.setTransponderMode);
|
|
pg.addMenuItem(6, "VFR", pg, pg.mfd.PFDInstruments.setVFR);
|
|
pg.addMenuItem(7, "CODE", pg, pg.mfd.PFDInstruments.codeMenu);
|
|
pg.addMenuItem(8, "IDENT", pg, pg.mfd.PFDInstruments.setIdent);
|
|
pg.addMenuItem(10, "BACK", pg, pg.mfd.PFDInstruments.topMenu);
|
|
pg.addMenuItem(11, "ALERTS", pg); # TODO
|
|
device.updateMenus();
|
|
},
|
|
|
|
codeMenu : func(device, pg, menuitem) {
|
|
pg.clearMenu();
|
|
pg.resetMenuColors();
|
|
pg.addMenuItem(0, "0", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(1, "1", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(2, "2", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(3, "3", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(4, "4", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(5, "5", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(6, "6", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(7, "7 ", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(8, "IDENT", pg, pg.mfd.PFDInstruments.setIdent);
|
|
pg.addMenuItem(9, "BKSP", pg, pg.mfd.PFDInstruments.setTransponderDigit);
|
|
pg.addMenuItem(10, "BACK", pg, pg.mfd.PFDInstruments.transponderMenu);
|
|
pg.addMenuItem(11, "ALERTS", pg); # TODO
|
|
device.updateMenus();
|
|
},
|
|
|
|
setTransponderMode : func(device, pg, menuitem) {
|
|
# Get the transponder mode from the menuitem itself.
|
|
pg.getController().setTransponderMode(menuitem.title);
|
|
},
|
|
|
|
setVFR : func(device, pg, menuitem) {
|
|
# Set VFR Mode - 1200
|
|
pg.getController().setTransponderCode(1200);
|
|
},
|
|
|
|
setIdent : func(device, pg, menuitem) {
|
|
# Ident the transponder
|
|
pg.getController().setTransponderIdent(1);
|
|
},
|
|
|
|
setTransponderDigit: func(device, pg, menuitem) {
|
|
# Set a transponder digit. Get digit from menu item.
|
|
pg.getController().setTransponderDigit(menuitem.title);
|
|
},
|
|
|
|
offdisplay : func() {
|
|
me._group.setVisible(0);
|
|
|
|
# Reset the menu colours. Shouldn't have to do this here, but
|
|
# there's not currently an obvious other location to do so.
|
|
for(var i = 0; i < 12; i +=1) {
|
|
var name = sprintf("SoftKey%d",i);
|
|
me.device.svg.getElementById(name ~ "-bg").setColorFill(0.0,0.0,0.0);
|
|
me.device.svg.getElementById(name).setColor(1.0,1.0,1.0);
|
|
}
|
|
me.getController().offdisplay();
|
|
},
|
|
ondisplay : func() {
|
|
me._group.setVisible(1);
|
|
me.getController().ondisplay();
|
|
},
|
|
|
|
updateAI: func(pitch, roll, slip) {
|
|
if (pitch > 80)
|
|
pitch = 80;
|
|
elsif (pitch < -80)
|
|
pitch = -80;
|
|
me.getElement("Horizon")
|
|
.setCenter(459, 282.8 - 6.849 * pitch)
|
|
.setRotation(-roll * D2R)
|
|
.setTranslation(0, pitch * 6.849);
|
|
me.getElement("bankPointer")
|
|
.setRotation(-roll * D2R);
|
|
me.getElement("SlipSkid")
|
|
.setTranslation(slip * 10, 0);
|
|
},
|
|
|
|
updateIAS: func (ias, ias_trend) {
|
|
if (ias >= 10) {
|
|
me.setTextElement("Speed110", sprintf("% 2u",num(math.floor(ias/10))));
|
|
} else {
|
|
me.setTextElement("Speed110", "");
|
|
}
|
|
|
|
me.getElement("SpeedLint1").setTranslation(0,(math.mod(ias,10) + (ias >= 10)*10) * 36);
|
|
me.getElement("SpeedTape").setTranslation(0,ias * 5.711);
|
|
|
|
ias_trend = math.clamp(ias_trend, -30, 30);
|
|
me.getElement("Airspeed-Trend-Indicator")
|
|
.setScale(1,ias_trend)
|
|
.setTranslation(0, -284.5 * (ias_trend - 1));
|
|
|
|
var vne = me.mfd.ConfigStore.get("Vne");
|
|
|
|
if (ias > vne and ! me._ias_already_exceeded) {
|
|
me._ias_already_exceeded = 1;
|
|
me.getElement("IAS-bg").setColorFill(1,0,0);
|
|
} elsif (ias < vne and me._ias_already_exceeded) {
|
|
me._ias_already_exceeded = 0;
|
|
me.getElement("IAS-bg").setColorFill(0,0,0);
|
|
}
|
|
|
|
foreach (var v; ["Vx", "Vy", "Vr", "Vglide"]) {
|
|
var spd = me.mfd.ConfigStore.get(v);
|
|
var visible = me.mfd.ConfigStore.get(v ~ "-visible");
|
|
if (visible and abs(spd - ias) < 30) {
|
|
me.getElement("IAS-" ~ v)
|
|
.setTranslation(0, (ias - spd) * 5.711)
|
|
.show();
|
|
} else {
|
|
me.getElement("IAS-" ~ v).hide();
|
|
}
|
|
}
|
|
},
|
|
|
|
updateVSI: func (vsi) {
|
|
me.getElement("VSI").setTranslation(0, math.clamp(vsi, -4500, 4500) * -0.03465);
|
|
me.setTextElement("VSIText", num(math.round(vsi, 10)));
|
|
},
|
|
|
|
updateTAS: func (tas) {
|
|
me.setTextElement("TAS-text", sprintf("%iKT", tas));
|
|
#me.getElement("GSPD-text").setText(sprintf("%iKT", tas));
|
|
},
|
|
|
|
updateALT: func (alt, alt_trend, selected_alt) {
|
|
if (alt < 0) {
|
|
me.setTextElement("Alt11100", sprintf("-% 3i",abs(math.ceil(alt/100))));
|
|
} elsif (alt < 100) {
|
|
me.setTextElement("Alt11100", "");
|
|
} else {
|
|
me.setTextElement("Alt11100", sprintf("% 3i",math.floor(alt/100)));
|
|
}
|
|
|
|
me.getElement("AltLint00011").setTranslation(0,math.fmod(alt,100) * 1.24);
|
|
|
|
if (alt> -1000 and alt< 1000000) {
|
|
var Offset10 = 0;
|
|
var Offset100 = 0;
|
|
var Offset1000 = 0;
|
|
var Ne = 0;
|
|
var Alt10 = math.mod(alt,100);
|
|
var Alt100 = int(math.mod(alt/100,10));
|
|
var Alt1000 = int(math.mod(alt/1000,10));
|
|
var Alt10000 = int(math.mod(alt/10000,10));
|
|
var Alt20 = math.mod(Alt10,20)/20;
|
|
|
|
|
|
if (alt< 0) {
|
|
var Ne = 1;
|
|
var alt= -alt;
|
|
}
|
|
|
|
if (Alt10 >= 80) Alt100 += Alt20;
|
|
if (Alt10 >= 80 and Alt100 >= 9) Alt1000 += Alt20;
|
|
if (Alt10 >= 80 and Alt100 >= 9 and Alt1000 >= 9) Alt10000 += Alt20;
|
|
if (alt> 100) Offset10 = 100;
|
|
if (alt> 1000) Offset100 = 10;
|
|
if (alt> 10000) Offset1000 = 10;
|
|
|
|
if (Ne) {
|
|
me.getElement("LintAlt").setTranslation(0,(math.mod(alt,100))*-0.57375);
|
|
var altCentral = -(int(alt/100)*100);
|
|
} else {
|
|
me.getElement("LintAlt").setTranslation(0,(math.mod(alt,100))*0.57375);
|
|
var altCentral = (int(alt/100)*100);
|
|
}
|
|
|
|
me.setTextElement("AltBigC", "");
|
|
me.setTextElement("AltSmallC", "");
|
|
for (var place = 1; place <= 6; place += 1) {
|
|
var altUP = altCentral + (place*100);
|
|
var altDOWN = altCentral - (place*100);
|
|
var offset = -30.078;
|
|
var prefix = "";
|
|
|
|
if (altUP < 0) {
|
|
altUP = -altUP;
|
|
prefix = "-";
|
|
offset += 15.039;
|
|
}
|
|
var AltBigUP = "";
|
|
var AltSmallUP = "0";
|
|
|
|
if (altUP == 0) {
|
|
AltBigUP = "";
|
|
AltSmallUP = "0";
|
|
} elsif (math.mod(altUP,500) == 0 and altUP != 0) {
|
|
AltBigUP = sprintf(prefix~"%1d", altUP);
|
|
AltSmallUP = "";
|
|
} elsif (altUP < 1000 and (math.mod(altUP,500))) {
|
|
AltBigUP = "";
|
|
AltSmallUP = sprintf(prefix~"%1d", int(math.mod(altUP,1000)));
|
|
offset = -30.078;
|
|
} elsif ((altUP < 10000) and (altUP >= 1000) and (math.mod(altUP,500))) {
|
|
AltBigUP = sprintf(prefix~"%1d", int(altUP/1000));
|
|
AltSmallUP = sprintf("%1d", int(math.mod(altUP,1000)));
|
|
offset += 15.039;
|
|
} else {
|
|
AltBigUP = sprintf(prefix~"%1d", int(altUP/1000));
|
|
mod = int(math.mod(altUP,1000));
|
|
AltSmallUP = sprintf("%1d", mod);
|
|
offset += 30.078;
|
|
}
|
|
|
|
me.getElement("AltBigU"~place).setText(AltBigUP);
|
|
me.getElement("AltSmallU"~place).setText(AltSmallUP);
|
|
me.getElement("AltSmallU"~place).setTranslation(offset,0);
|
|
|
|
offset = -30.078;
|
|
prefix = "";
|
|
if (altDOWN < 0) {
|
|
altDOWN = -altDOWN;
|
|
prefix = "-";
|
|
offset += 15.039;
|
|
}
|
|
|
|
if (altDOWN == 0) {
|
|
AltBigDOWN = "";
|
|
AltSmallDOWN = "0";
|
|
} elsif (math.mod(altDOWN,500) == 0 and altDOWN != 0) {
|
|
AltBigDOWN = sprintf(prefix~"%1d", altDOWN);
|
|
AltSmallDOWN = "";
|
|
} elsif (altDOWN < 1000 and (math.mod(altDOWN,500))) {
|
|
AltBigDOWN = "";
|
|
AltSmallDOWN = sprintf(prefix~"%1d", int(math.mod(altDOWN,1000)));
|
|
offset = -30.078;
|
|
} elsif ((altDOWN < 10000) and (altDOWN >= 1000) and (math.mod(altDOWN,500))) {
|
|
AltBigDOWN = sprintf(prefix~"%1d", int(altDOWN/1000));
|
|
AltSmallDOWN = sprintf("%1d", int(math.mod(altDOWN,1000)));
|
|
offset += 15.039;
|
|
} else {
|
|
AltBigDOWN = sprintf(prefix~"%1d", int(altDOWN/1000));
|
|
AltSmallDOWN = sprintf("%1d", int(math.mod(altDOWN,1000)));
|
|
offset += 30.078;
|
|
}
|
|
|
|
me.getElement("AltBigD"~place).setText(AltBigDOWN);
|
|
me.getElement("AltSmallD"~place).setText(AltSmallDOWN);
|
|
me.getElement("AltSmallD"~place).setTranslation(offset,0);
|
|
}
|
|
}
|
|
|
|
alt_trend = math.clamp(alt_trend, -15, 15);
|
|
me.getElement("Altitude-Trend-Indicator")
|
|
.setScale(1,alt_trend)
|
|
.setTranslation(0, -284.5 * (alt_trend - 1));
|
|
|
|
var delta_alt = alt - selected_alt;
|
|
delta_alt = math.clamp(delta_alt, -300, 300);
|
|
me.getElement("SelectedALT-bug").setTranslation(0, delta_alt * 0.567); # 170/300 = 0.567
|
|
},
|
|
|
|
updateBARO : func (baro) {
|
|
# TODO: Support hPa and inhg
|
|
#var fmt = me._baro_unit == "inhg" ? "%.2fin" : "%i%shPa";
|
|
var fmt = "%.2fIN";
|
|
me.setTextElement("BARO-text", sprintf(fmt, baro));
|
|
},
|
|
|
|
updateOAT : func (oat) {
|
|
# TODO: Support FAHRENHEIT
|
|
me.setTextElement("OAT-text", sprintf((abs(oat) < 10) ? "%.1f %s" : "%i %s", oat, "°C"));
|
|
},
|
|
|
|
updateHSI : func (hdg) {
|
|
me.getElement("Rose").setRotation(-hdg * D2R);
|
|
me.setTextElement("HDG-text", sprintf("%03u°", hdg));
|
|
},
|
|
|
|
updateHDG : func (hdg) {
|
|
me.getElement("Heading-bug").setRotation(hdg * D2R);
|
|
me.setTextElement("SelectedHDG-text", sprintf("%03d°%s", hdg, ""));
|
|
},
|
|
|
|
# Indicate the selected course, from a given source (OFF, NAV, GPS)
|
|
updateCRS : func (crs) {
|
|
me.getElement("SelectedCRS-text")
|
|
.setText(sprintf("%03d°%s", crs, ""))
|
|
.setColor(me.getController().getCDISource() == "GPS" ? me.COLORS.magenta : me.COLORS.green);
|
|
},
|
|
|
|
updateSelectedALT : func (selected_alt) {
|
|
me.setTextElement("SelectedALT-text", sprintf("%i", selected_alt));
|
|
},
|
|
|
|
setBRG1 : func(option) { me._setBRG("BRG1",option); },
|
|
setBRG2 : func(option) { me._setBRG("BRG2",option); },
|
|
|
|
_setBRG : func (brg, option) {
|
|
if (option == "OFF") {
|
|
me.getElement(brg).hide();
|
|
me.getElement(brg ~ "-pointer").hide();
|
|
if ((me.getController().getBRG1() == "OFF") and (me.getController().getBRG2() == "OFF")) {
|
|
# Hide the circle if there are now BRGs selected
|
|
me.getElement("BRG-circle").hide();
|
|
}
|
|
} else {
|
|
me.getElement(brg).show();
|
|
me.getElement(brg ~ "-pointer").show();
|
|
me.getElement("BRG-circle").show();
|
|
|
|
me.getElement(brg ~ "-SRC-text").setText(option);
|
|
me.getElement(brg ~ "-WPID-text").setText("----");
|
|
|
|
if (option == "ADF") {
|
|
# Special case. We won't have a distance and the "ID" will be the ADF
|
|
# frequency
|
|
me.getElement(brg ~ "-DST-text").setText("");
|
|
} else {
|
|
me.getElement(brg ~ "-DST-text").setText("--nm");
|
|
}
|
|
}
|
|
},
|
|
|
|
# Update BRG information
|
|
updateBRG1 : func(valid, id, dst, current_heading, brg_heading) {
|
|
me._updateBRG("BRG1", me.getController().getBRG1(), valid, id, dst, current_heading, brg_heading);
|
|
},
|
|
updateBRG2 : func(valid, id, dst, current_heading, brg_heading) {
|
|
me._updateBRG("BRG2", me.getController().getBRG2(), valid, id, dst, current_heading, brg_heading);
|
|
},
|
|
_updateBRG : func (brg, source, valid, id, dst, current_heading, brg_heading) {
|
|
if (source == "OFF") return;
|
|
|
|
if (valid) {
|
|
me.getElement(brg ~ "-SRC-text").setText(source);
|
|
me.getElement(brg ~ "-WPID-text").setText(id);
|
|
|
|
if (source == "ADF") {
|
|
# Special case. We won't have a distance and the "ID" will be the ADF
|
|
# frequency
|
|
me.getElement(brg ~ "-DST-text").setText("");
|
|
} else {
|
|
me.getElement(brg ~ "-DST-text").setText(sprintf("%.1fNM", dst));
|
|
}
|
|
|
|
var rot = (brg_heading - current_heading) * D2R;
|
|
me.getElement(brg ~ "-pointer").setRotation(rot).show();
|
|
} else {
|
|
# Data is not valid - hide the pointer and display NO DATA
|
|
me.getElement(brg ~ "-SRC-text").setText(source);
|
|
me.getElement(brg ~ "-WPID-text").setText("NO DATA");
|
|
me.getElement(brg ~ "-DST-text").setText("");
|
|
me.getElement(brg ~ "-pointer").hide();
|
|
}
|
|
},
|
|
|
|
toggleDME : func() {
|
|
me.setDME(! me._DME);
|
|
},
|
|
|
|
setDME : func (enabled) {
|
|
me._DME = enabled;
|
|
me.getElement("DME1").setVisible(enabled);
|
|
},
|
|
|
|
updateDME : func (mode, freq, dst) {
|
|
if (me._DME == 0) return;
|
|
me.getElement("DME1-SRC-text").setText(mode);
|
|
me.getElement("DME1-FREQ-text").setText(sprintf("%.2f", freq));
|
|
me.getElement("DME1-DST-text").setText(sprintf("%.2fNM", dst));
|
|
},
|
|
|
|
setCDISource : func(source) {
|
|
foreach (var s; ["GPS", "NAV1", "NAV2"]) {
|
|
me.getElement(s ~ "-pointer").setVisible(source == s);
|
|
me.getElement(s ~ "-CDI").setVisible(source == s);
|
|
me.getElement(s ~ "-FROM").setVisible(source == s);
|
|
me.getElement(s ~ "-TO").setVisible(source == s);
|
|
}
|
|
|
|
me.getElement("CDI-SRC-text")
|
|
.setText(source)
|
|
.setColor(source == "GPS" ? me.COLORS.magenta : me.COLORS.green)
|
|
.setVisible(source != "OFF");
|
|
},
|
|
|
|
updateCDI : func (heading, course, waypoint_valid, course_deviation_deg, deflection_dots, xtrk_nm, from, annun, loc) {
|
|
|
|
var source = me.getController().getCDISource();
|
|
if (source == "OFF") return;
|
|
|
|
# While the user selects between GPS, NAV1, NAV2, we display localizers as LOC1 and LOC2
|
|
if ((source == "NAV1") and (loc == 1)) me.getElement("CDI-SRC-text").setText("LOC1");
|
|
if ((source == "NAV2") and (loc == 1)) me.getElement("CDI-SRC-text").setText("LOC2");
|
|
|
|
if (waypoint_valid == 0) {
|
|
me.getElement(source ~ "-CDI").hide();
|
|
me.getElement(source ~ "-FROM").hide();
|
|
me.getElement(source ~ "-TO").hide();
|
|
me.getElement(source ~ "-pointer").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").setText("NO DATA").show();
|
|
} else {
|
|
me.getElement(source ~ "-CDI").show();
|
|
|
|
var rot = (course - heading) * D2R;
|
|
me.getElement("CDI").setRotation(rot).show();
|
|
me.getElement("GPS-CTI-diamond").setRotation(course_deviation_deg * D2R).setVisible(source == "GPS");
|
|
|
|
if ((source == "GPS") and (abs(deflection_dots) > 2.0)) {
|
|
# 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 %.2fNM", abs(xtrk_nm)))
|
|
.show();
|
|
} else {
|
|
me.getElement("CDI-GPS-XTK-text").hide();
|
|
}
|
|
|
|
if (source == "GPS") {
|
|
me.getElement("CDI-GPS-ANN-text").setText(annun).show();
|
|
} else {
|
|
me.getElement("CDI-GPS-ANN-text").hide();
|
|
}
|
|
|
|
var scale = math.clamp(deflection_dots, -2.4, 2.4);
|
|
me.getElement(source ~ "-CDI").setTranslation(80 * scale / 2.4, 0);
|
|
|
|
# Display the appropriate TO/FROM indication for the selected source,
|
|
# switching all others off.
|
|
me.getElement(source ~ "-TO").setVisible(from == 0);
|
|
me.getElement(source ~ "-FROM").setVisible(from);
|
|
}
|
|
},
|
|
|
|
# Update the wind display. There are three options:
|
|
# 0 - No wind data displayed
|
|
# 1 - Numeric headwind and crosswind components
|
|
# 2 - Direction arrow and numeric speed
|
|
# 3 - Direction arrow, and numeric True direction and speet
|
|
setWindDisplay : func(option) {
|
|
me.getElement("WindData").setVisible(option != 0);
|
|
me.getElement("WindData-OPTN1").setVisible(option == 1);
|
|
me.getElement("WindData-OPTN2").setVisible(option == 2);
|
|
me.getElement("WindData-OPTN3").setVisible(option == 3);
|
|
|
|
me._windDataDisplay = option;
|
|
},
|
|
|
|
updateWindData : func (hdg, wind_hdg, wind_spd, no_data) {
|
|
if (no_data and (me._windDataDisplay > 0)) {
|
|
me.getElement("WindData-NODATA").show();
|
|
me.getElement("WindData-NODATA-bg").show();
|
|
return;
|
|
} else {
|
|
me.getElement("WindData-NODATA").hide();
|
|
me.getElement("WindData-NODATA-bg").hide();
|
|
}
|
|
|
|
var alpha = (wind_hdg - hdg);
|
|
|
|
# Stop the wind arrows oscillating
|
|
if (wind_spd < 1) {
|
|
alpha = 0;
|
|
wind_hdg = 0;
|
|
}
|
|
|
|
if (me._windDataDisplay == 0) {
|
|
me.getElement("WindData").hide();
|
|
} elsif (me._windDataDisplay == 1) {
|
|
# Headwind/Crosswind numeric display
|
|
var Vt = wind_spd * math.sin(alpha * D2R);
|
|
var Ve = wind_spd * math.cos(alpha * D2R);
|
|
me.getElement("WindData-OPTN1-crosswind-text").setText(sprintf("%i", abs(Vt)));
|
|
me.getElement("WindData-OPTN1-crosswind").setRotation(Vt > 0.1 ? 180*D2R : 0);
|
|
me.getElement("WindData-OPTN1-headwind-text").setText(sprintf("%i", abs(Ve)));
|
|
me.getElement("WindData-OPTN1-headwind").setRotation(Ve > 0.1 ? 180*D2R : 0);
|
|
} elsif (me._windDataDisplay == 2) {
|
|
# Direction arrow and numeric speed
|
|
me.getElement("WindData-OPTN2-HDG").setRotation((alpha + 180) * D2R);
|
|
me.getElement("WindData-OPTN2-SPD-text").setText(sprintf("%i", wind_spd));
|
|
} elsif (me._windDataDisplay == 3) {
|
|
# Direction arrow with numeric true direction and speed
|
|
me.getElement("WindData-OPTN3-HDG").setRotation((alpha + 180) * D2R);
|
|
me.getElement("WindData-OPTN3-HDG-text").setText(sprintf("%03i°T", wind_hdg));
|
|
me.getElement("WindData-OPTN3-SPD-text").setText(sprintf("%iKT", wind_spd));
|
|
} else {
|
|
print("Unknown wind data option " ~ me._windDataDisplay);
|
|
}
|
|
},
|
|
|
|
# Enable/disable the multiline display on the right hand side of the PFD
|
|
setMultiline : func(enabled) {
|
|
me._Multiline = enabled;
|
|
me.getElement("PFD-Multilines").setVisible(enabled);
|
|
},
|
|
|
|
# Enable/disable the warning annunication window.
|
|
setAnnunciation : func(enabled) {
|
|
me._annunciation = enabled;
|
|
me.getElement("Annunciation").setVisible(enabled);
|
|
},
|
|
|
|
# set the Outer, Middle, Inner indicator
|
|
setOMI : func(omi) {
|
|
if (omi == "") {
|
|
me.getElement("OMI").hide();
|
|
} else {
|
|
me.getElement("OMI").show();
|
|
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);
|
|
},
|
|
|
|
setFlightPlanVisible : func(enabled) {
|
|
me.getElement("FlightPlanGroup").setVisible(enabled);
|
|
},
|
|
|
|
# Update the FlightPlan display with an updated flightplan.
|
|
setFlightPlan : func(fp) {
|
|
var elements = [];
|
|
|
|
if (fp == nil) return;
|
|
|
|
var current_wp = fp.current;
|
|
|
|
for (var i = 0; i < fp.getPlanSize(); i = i + 1) {
|
|
var wp = fp.getWP(i);
|
|
|
|
var element = {
|
|
FlightPlanArrow : 0,
|
|
FlightPlanID : "",
|
|
FlightPlanType : "",
|
|
FlightPlanDTK : 0,
|
|
FlightPlanDIS : 0,
|
|
};
|
|
|
|
if (wp.wp_name != nil) element.FlightPlanID = substr(wp.wp_name, 0, 7);
|
|
if (wp.wp_role != nil) element.FlightPlanType = substr(wp.wp_role, 0, 4);
|
|
|
|
if (i < current_wp) {
|
|
# Passed waypoints are blanked out on the display
|
|
element.FlightPlanDIS = "___nm";
|
|
element.FlightPlanDTK = "___°";
|
|
} else {
|
|
if (wp.leg_distance != nil) element.FlightPlanDIS = sprintf("%.1fnm", wp.leg_distance);
|
|
if (wp.leg_bearing != nil) element.FlightPlanDTK = sprintf("%03d°", wp.leg_bearing);
|
|
}
|
|
append(elements, element);
|
|
}
|
|
|
|
me.flightplanList.setValues(elements);
|
|
me.flightplanList.setCRSR(current_wp);
|
|
|
|
# Determine a suitable name to display, using the flightplan name if there is one,
|
|
# but falling back to the flightplan departure / destination airports, or failing
|
|
# that the IDs of the first and last waypoints.
|
|
if ((fp.id == nil) or (fp.id == "default-flightplan")) {
|
|
var from = "????";
|
|
var dest = "????";
|
|
|
|
if ((fp.getWP(0) != nil) and (fp.getWP(0).wp_name != nil)) {
|
|
from = fp.getWP(0).wp_name;
|
|
}
|
|
|
|
if ((fp.getWP(fp.getPlanSize() -1) != nil) and (fp.getWP(fp.getPlanSize() -1).wp_name != nil)) {
|
|
dest = fp.getWP(fp.getPlanSize() -1).wp_name;
|
|
}
|
|
|
|
if (fp.departure != nil) from = fp.departure.id;
|
|
if (fp.destination != nil) dest = fp.destination.id;
|
|
me.getElement("FlightPlanName").setText(from ~ " / " ~ dest);
|
|
} else {
|
|
me.getElement("FlightPlanName").setText(fp.id);
|
|
}
|
|
},
|
|
|
|
# Update the FlightPlan display to indicate the current waypoint
|
|
updateFlightPlan : func(current_wp) {
|
|
if (current_wp == -1) return;
|
|
me.flightplanList.setCRSR(current_wp);
|
|
me.flightplanList.displayGroup();
|
|
},
|
|
|
|
# Update the Transponder display
|
|
updateTransponder : func(mode, code, ident, edit=0) {
|
|
# Data validation on the mode
|
|
if (mode < 0) mode = 0;
|
|
if (mode > 5) mode = 5;
|
|
|
|
# Ensure the code is a 4 digit string representation of a number.
|
|
# Normally this means padding with 0's at the left e.g. 42 becomes 0042.
|
|
# In editing mode we enter values from the left, so we want " " padding on the
|
|
# right, so 42 becomes "42 ". Note also that the code is a string value
|
|
# so we can have "004 "
|
|
if (edit) {
|
|
code = sprintf("%-4s", code);
|
|
} else {
|
|
code = sprintf("%04i", code);
|
|
}
|
|
|
|
# Colour of display. White in OFF, STDBY, TEST modes, green in GND, ON, ALT
|
|
var r = 1.0;
|
|
var g = 1.0;
|
|
var b = 1.0;
|
|
if (mode > 2) {
|
|
r = 0.2;
|
|
g = 1.0;
|
|
b = 0.2;
|
|
}
|
|
|
|
if (ident) {
|
|
me.getElement("XPDR-MODE-text").setText("IDNT").setColor(r,g,b);
|
|
} else {
|
|
me.getElement("XPDR-MODE-text").setText(TRANSPONDER_MODES[mode]).setColor(r,g,b);
|
|
}
|
|
|
|
var codes = split("", code);
|
|
me.getElement("XPDR-DIGIT-3-text").setText(chr(code[0])).setColor(r,g,b);
|
|
me.getElement("XPDR-DIGIT-2-text").setText(chr(code[1])).setColor(r,g,b);
|
|
me.getElement("XPDR-DIGIT-1-text").setText(chr(code[2])).setColor(r,g,b);
|
|
me.getElement("XPDR-DIGIT-0-text").setText(chr(code[3])).setColor(r,g,b);
|
|
},
|
|
};
|