FG1000 HSI BRG/CDI controls
- BRG1/BRG2 now correctly show direction and distance to NAV1/NAV2/GPS/ADF - CDI displays correctly against course, switchable between GPS/NAV1/NAV2
This commit is contained in:
parent
74d1257f5d
commit
7b526adb69
9 changed files with 274 additions and 179 deletions
|
@ -46,7 +46,8 @@ var GenericADCPublisher =
|
|||
obj.addPropMap("ADCSlipSkid", "/instrumentation/slip-skid-ball/indicated-slip-skid");
|
||||
|
||||
# Assume an accurate solid-state compass
|
||||
obj.addPropMap("ADCHeadingDeg", "/orientation/heading-magnetic-deg");
|
||||
obj.addPropMap("ADCHeadingMagneticDeg", "/orientation/heading-magnetic-deg");
|
||||
obj.addPropMap("ADCMagneticVariationDeg", "/environment/magnetic-variation-deg");
|
||||
|
||||
obj.addPropMap("ADCAltitudeFT", "/instrumentation/altimeter/indicated-altitude-ft");
|
||||
obj.addPropMap("ADCPressureSettingInHG", "/instrumentation/altimeter/setting-inhg");
|
||||
|
|
|
@ -31,7 +31,7 @@ var GenericFMSPublisher =
|
|||
|
||||
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("FMSLegBearingMagDeg", "/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("FMSLegDesiredTrack", "/instrumentation/gps/wp/wp[1]/desired-course-deg");
|
||||
|
@ -57,7 +57,7 @@ var GenericFMSPublisher =
|
|||
|
||||
# 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["FMSLegBearingMagDeg"] == -9999) gpsdata["FMSLegBearingMagDeg"] = nil;
|
||||
if (gpsdata["FMSLegDistanceNM"] == -1) gpsdata["FMSLegDistanceNM"] = nil;
|
||||
|
||||
# A couple of calculated values used by the MFD Header display
|
||||
|
|
|
@ -68,6 +68,13 @@ var GenericNavComPublisher =
|
|||
obj._periodicPublisher.addPropMap("Nav1HeadingDeg", "/instrumentation/nav/heading-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav1RadialDeg", "/instrumentation/nav/radials/selected-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav1DistanceMeters", "/instrumentation/nav/nav-distance");
|
||||
obj._periodicPublisher.addPropMap("Nav1CourseDeviationDeg", "/instrumentation/nav/crosstrack-heading-error-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav1CrosstrackErrorM", "/instrumentation/nav/crosstrack-error-m");
|
||||
obj._periodicPublisher.addPropMap("Nav1Localizer", "/instrumentation/nav/nav-loc");
|
||||
obj._periodicPublisher.addPropMap("Nav1Deflection", "/instrumentation/nav/heading-needle-deflection-norm");
|
||||
obj._periodicPublisher.addPropMap("Nav1From", "/instrumentation/nav/from-flag");
|
||||
|
||||
|
||||
obj._triggeredPublisher.addPropMap("Nav1Volume", "/instrumentation/nav/nav-volume");
|
||||
obj._triggeredPublisher.addPropMap("Nav1AudioID", "/instrumentation/nav/audio-btn");
|
||||
obj._triggeredPublisher.addPropMap("Nav1Serviceable", "/instrumentation/nav/operable");
|
||||
|
@ -79,6 +86,12 @@ var GenericNavComPublisher =
|
|||
obj._periodicPublisher.addPropMap("Nav2HeadingDeg", "/instrumentation/nav[1]/heading-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav2RadialDeg", "/instrumentation/nav[1]/radials/selected-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav2DistanceMeters", "/instrumentation/nav[1]/nav-distance");
|
||||
obj._periodicPublisher.addPropMap("Nav2CourseDeviationDeg", "/instrumentation/nav[1]/crosstrack-heading-error-deg");
|
||||
obj._periodicPublisher.addPropMap("Nav2CrosstrackErrorM", "/instrumentation/nav[1]/crosstrack-error-m");
|
||||
obj._periodicPublisher.addPropMap("Nav2Localizer", "/instrumentation/nav[1]/nav-loc");
|
||||
obj._periodicPublisher.addPropMap("Nav2Deflection", "/instrumentation/nav[1]/heading-needle-deflection-norm");
|
||||
obj._periodicPublisher.addPropMap("Nav2From", "/instrumentation/nav/from-flag");
|
||||
|
||||
obj._triggeredPublisher.addPropMap("Nav2Volume", "/instrumentation/nav[1]/nav-volume");
|
||||
obj._triggeredPublisher.addPropMap("Nav2AudioID", "/instrumentation/nav[1]/audio-btn");
|
||||
obj._triggeredPublisher.addPropMap("Nav2Serviceable", "/instrumentation/nav[1]/operable");
|
||||
|
|
|
@ -55,6 +55,7 @@ var GenericNavComUpdater =
|
|||
obj.addPropMap("Nav1SelectedFreq", "/instrumentation/nav/frequencies/selected-mhz");
|
||||
obj.addPropMap("Nav1StandbyFreq", "/instrumentation/nav/frequencies/standby-mhz");
|
||||
obj.addPropMap("Nav1ID", "/instrumentation/nav/nav-id");
|
||||
obj.addPropMap("Nav1RadialDeg", "/instrumentation/nav/radials/selected-deg");
|
||||
obj.addPropMap("Nav1Volume", "/instrumentation/nav/nav-volume");
|
||||
obj.addPropMap("Nav1AudioID", "/instrumentation/nav/audio-btn");
|
||||
obj.addPropMap("Nav1Serviceable", "/instrumentation/nav/operable");
|
||||
|
@ -62,6 +63,7 @@ var GenericNavComUpdater =
|
|||
obj.addPropMap("Nav2SelectedFreq", "/instrumentation/nav[1]/frequencies/selected-mhz");
|
||||
obj.addPropMap("Nav2StandbyFreq", "/instrumentation/nav[1]/frequencies/standby-mhz");
|
||||
obj.addPropMap("Nav2ID", "/instrumentation/nav[1]/nav-id");
|
||||
obj.addPropMap("Nav2RadialDeg", "/instrumentation/nav[1]/radials/selected-deg");
|
||||
obj.addPropMap("Nav2Volume", "/instrumentation/nav[1]/nav-volume");
|
||||
obj.addPropMap("Nav2AudioID", "/instrumentation/nav[1]/audio-btn");
|
||||
obj.addPropMap("Nav2Serviceable", "/instrumentation/nav[1]/operable");
|
||||
|
|
|
@ -45,7 +45,7 @@ handleNavOuter : func (value) { return me.page.mfd.SurroundController.han
|
|||
handleNavInner : func (value) { return me.page.mfd.SurroundController.handleNavInner(value); },
|
||||
handleNavToggle : func (value) { return me.page.mfd.SurroundController.handleNavToggle(value); },
|
||||
handleHeading : func (value) { return me.page.mfd.SurroundController.handleHeading(value); },
|
||||
handleHeadingPress : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
|
||||
handleHeadingPress : func (value) { return me.page.mfd.SurroundController.handleHeadingPress(value); },
|
||||
|
||||
# Joystick
|
||||
handleRange : func (value) { return emesary.Transmitter.ReceiptStatus_NotProcessed; },
|
||||
|
@ -54,8 +54,8 @@ handleJoystickHorizontal : func (value) { return emesary.Transmitter.ReceiptStat
|
|||
|
||||
#CRS/BARO
|
||||
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; },
|
||||
handleCRS : func (value) { return me.page.mfd.SurroundController.handleCRS(value); },
|
||||
handleCRSCenter : func (value) { return me.page.mfd.SurroundController.handleCRSCenter(value); },
|
||||
|
||||
handleComOuter : func (value) { return me.page.mfd.SurroundController.handleComOuter(value); },
|
||||
handleComInner : func (value) { return me.page.mfd.SurroundController.handleComInner(value); },
|
||||
|
|
|
@ -28,10 +28,6 @@ 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 = {
|
||||
|
@ -42,7 +38,6 @@ var PFDInstruments =
|
|||
|
||||
_ias_already_exceeded : 0,
|
||||
_windDataDisplay : 0,
|
||||
_CDISource : "GPS",
|
||||
_BRG1 : "OFF",
|
||||
_BRG2 : "OFF",
|
||||
_DME : 0,
|
||||
|
@ -116,7 +111,7 @@ var PFDInstruments =
|
|||
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, pg.incrCDI);
|
||||
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); # TODO
|
||||
pg.addMenuItem(8, "IDENT", pg); # TODO
|
||||
|
@ -126,23 +121,7 @@ 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);
|
||||
|
||||
|
@ -207,9 +186,9 @@ var PFDInstruments =
|
|||
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, pg.mfd.PFDInstruments.incrBRG1); # 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, pg.mfd.PFDInstruments.incrBRG2); # TODO
|
||||
pg.addMenuItem(6, "BRG2", pg, func(dev, pg, mi) { pg.getController().incrBRG2(); });
|
||||
#pg.addMenuItem(8, "IDENT", pg); # TODO
|
||||
pg.addMenuItem(8, "ALT UNIT ", pg); # TODO
|
||||
pg.addMenuItem(9, "STD BARO", pg, func(dev, pg, mi) { pg.getController().setStdBaro(); } );
|
||||
|
@ -218,29 +197,6 @@ var PFDInstruments =
|
|||
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();
|
||||
|
@ -513,31 +469,22 @@ var PFDInstruments =
|
|||
updateCRS : func (crs) {
|
||||
me.getElement("SelectedCRS-text")
|
||||
.setText(sprintf("%03d°%s", crs, ""))
|
||||
.setColor(me._CDISource == "GPS" ? me.COLORS.magenta : me.COLORS.green);
|
||||
.setColor(me.getController().getCDISource() == "GPS" ? me.COLORS.magenta : me.COLORS.green);
|
||||
},
|
||||
|
||||
updateSelectedALT : func (selected_alt) {
|
||||
me.setTextElement("SelectedALT-text", sprintf("%i", selected_alt));
|
||||
},
|
||||
|
||||
# Bearing (BRG) settings
|
||||
# "OFF", "NAV1", "NAV2", "GPS", "ADF"
|
||||
getBRG1 : func() { return me._BRG1; },
|
||||
getBRG2 : func() { return me._BRG2; },
|
||||
setBRG1 : func(option) { me._setBRG("BRG1",option); },
|
||||
setBRG2 : func(option) { me._setBRG("BRG2",option); },
|
||||
|
||||
setBRG1 : func(option) {
|
||||
me._BRG1 = option;
|
||||
me._setBRG("BRG1",option);
|
||||
},
|
||||
setBRG2 : func(option) {
|
||||
me._BRG2 = option;
|
||||
me._setBRG("BRG2",option);
|
||||
},
|
||||
_setBRG : func (brg, option) {
|
||||
if (option == "OFF") {
|
||||
me.getElement(brg).hide();
|
||||
me.getElement(brg ~ "-pointer").hide();
|
||||
if ((me._BRG1 == "OFF") and (me._BRG2 == "OFF")) {
|
||||
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 {
|
||||
|
@ -560,10 +507,10 @@ var PFDInstruments =
|
|||
|
||||
# Update BRG information
|
||||
updateBRG1 : func(valid, id, dst, current_heading, brg_heading) {
|
||||
me._updateBRG("BRG1", me._BRG1, 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._BRG2, 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;
|
||||
|
@ -608,74 +555,45 @@ var PFDInstruments =
|
|||
},
|
||||
|
||||
setCDISource : func(source) {
|
||||
if (source == "OFF") {
|
||||
foreach (var s; ["GPS", "NAV1", "NAV2"]) {
|
||||
foreach (var t; ["pointer", "CDI", "FROM", "TO"]) {
|
||||
me.getElement(s ~ "-" ~ t).hide();
|
||||
}
|
||||
}
|
||||
me.getElement("CDI-GPS-ANN-text").hide();
|
||||
me.getElement("CDI-GPS-XTK-text").hide();
|
||||
me.getElement("CDI-SRC-text").hide();
|
||||
me.getElement("CDI").hide();
|
||||
me.getElement("GPS-CTI-diamond").hide();
|
||||
me.getElement("SelectedCRS").hide();
|
||||
|
||||
} else {
|
||||
me.getElement("CDI").show();
|
||||
me.getElement("SelectedCRS").show();
|
||||
|
||||
if (source == "GPS") {
|
||||
me.getElement("CDI-GPS-ANN-text").show();
|
||||
me.getElement("GPS-CTI-diamond").show();
|
||||
} else {
|
||||
me.getElement("CDI-GPS-ANN-text").hide();
|
||||
me.getElement("GPS-CTI-diamond").hide();
|
||||
}
|
||||
|
||||
# Localizers are mapped to the NAV1/2 elements, but have reduced deflection
|
||||
# and a different label.
|
||||
me.getElement("CDI-SRC-text")
|
||||
.setText(source)
|
||||
.setColor(source == "GPS" ? me.COLORS.magenta : me.COLORS.green)
|
||||
.show();
|
||||
|
||||
if (source == "LOC1") source = "NAV1";
|
||||
if (source == "LOC2") source = "NAV2";
|
||||
|
||||
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);
|
||||
}
|
||||
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._CDISource = source;
|
||||
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) {
|
||||
if (me._CDISource == "OFF") return;
|
||||
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(me._CDISource ~ "-CDI").hide();
|
||||
me.getElement(me._CDISource ~ "-FROM").hide();
|
||||
me.getElement(me._CDISource ~ "-TO").hide();
|
||||
me.getElement(me._CDISource ~ "-pointer").hide();
|
||||
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").hide();
|
||||
me.getElement("CDI-GPS-ANN-text").setText("NO DATA").show();
|
||||
} else {
|
||||
me.getElement(me._CDISource ~ "-CDI").show();
|
||||
me.getElement(source ~ "-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);
|
||||
me.getElement("GPS-CTI-diamond").setRotation(course_deviation_deg * D2R).setVisible(source == "GPS");
|
||||
|
||||
if ((me._CDISource == "GPS") and (abs(deflection_dots) > 2.0)) {
|
||||
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")
|
||||
|
@ -685,15 +603,19 @@ var PFDInstruments =
|
|||
me.getElement("CDI-GPS-XTK-text").hide();
|
||||
}
|
||||
|
||||
if (me._CDISource == "GPS") me.getElement("CDI-GPS-ANN-text").setText(annun).show();
|
||||
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(me._CDISource ~ "-CDI").setTranslation(80 * scale / 2.4, 0);
|
||||
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(me._CDISource ~ "-TO").setVisible(from == 0);
|
||||
me.getElement(me._CDISource ~ "-FROM").setVisible(from);
|
||||
me.getElement(source ~ "-TO").setVisible(from == 0);
|
||||
me.getElement(source ~ "-FROM").setVisible(from);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
|
||||
# Copyright 2018 Stuart Buchanan
|
||||
# This file is part of FlightGear.
|
||||
#
|
||||
|
@ -17,6 +18,9 @@
|
|||
# PFDInstruments Controller
|
||||
var PFDInstrumentsController =
|
||||
{
|
||||
CDI_SOURCE : [ "GPS", "NAV1", "NAV2" ],
|
||||
BRG_SOURCE : ["OFF", "NAV1", "NAV2", "GPS", "ADF"],
|
||||
|
||||
new : func (page, svg)
|
||||
{
|
||||
var obj = {
|
||||
|
@ -24,13 +28,19 @@ var PFDInstrumentsController =
|
|||
_crsrToggle : 0,
|
||||
_pfdrecipient : nil,
|
||||
page : page,
|
||||
|
||||
_CDISource : 0,
|
||||
_BRG1Source : 0,
|
||||
_BRG2Source : 0,
|
||||
|
||||
_last_ias_kt : 0,
|
||||
_last_alt_ft : 0,
|
||||
_last_trend : systime(),
|
||||
_selected_alt_ft : 0,
|
||||
_heading : 0,
|
||||
_source : "GPS",
|
||||
_from :0,
|
||||
_mag_var : 0,
|
||||
|
||||
_leg_from :0,
|
||||
_leg_id : "",
|
||||
_leg_bearing : 0,
|
||||
_leg_distance_nm : 0,
|
||||
|
@ -45,6 +55,11 @@ var PFDInstrumentsController =
|
|||
_nav1_heading_deg :0.0,
|
||||
_nav1_in_range : 0,
|
||||
_nav1_distance_m :0,
|
||||
_nav1_radial_deg : 0,
|
||||
_nav1_in_range : 0,
|
||||
_nav1_distance_m : 0,
|
||||
_nav1_deviation_deg : 0,
|
||||
_nav1_loc : 0,
|
||||
|
||||
_nav2_id : "",
|
||||
_nav2_freq : 0.0,
|
||||
|
@ -52,6 +67,11 @@ var PFDInstrumentsController =
|
|||
_nav2_heading_deg : 0.0,
|
||||
_nav2_in_range : 0,
|
||||
_nav2_distance_m :0,
|
||||
_nav2_radial_deg : 0,
|
||||
_nav2_in_range : 0,
|
||||
_nav2_distance_m : 0,
|
||||
_nav2_deviation_deg : 0,
|
||||
_nav2_loc : 0,
|
||||
|
||||
_adf_freq : 0.0,
|
||||
_adf_in_range : 0,
|
||||
|
@ -114,6 +134,43 @@ var PFDInstrumentsController =
|
|||
me.transmitter.NotifyAll(notification);
|
||||
},
|
||||
|
||||
incrCDISource : func() {
|
||||
me._CDISource = math.mod(me._CDISource + 1, size(PFDInstrumentsController.CDI_SOURCE));
|
||||
var src = PFDInstrumentsController.CDI_SOURCE[me._CDISource];
|
||||
|
||||
# If we're changing to NAV1 or NAV2, we also change the selected NAV.
|
||||
if ((src == "NAV1") or (src == "NAV2")) {
|
||||
var data = {};
|
||||
data["NavSelected"] = (src == "NAV1") ? 1 : 2;
|
||||
var notification = notifications.PFDEventNotification.new(
|
||||
"MFD",
|
||||
me._page.mfd.getDeviceID(),
|
||||
notifications.PFDEventNotification.NavComData,
|
||||
data);
|
||||
|
||||
me.transmitter.NotifyAll(notification);
|
||||
}
|
||||
|
||||
me.page.setCDISource(src);
|
||||
},
|
||||
|
||||
getCDISource : func() {
|
||||
return PFDInstrumentsController.CDI_SOURCE[me._CDISource];
|
||||
},
|
||||
|
||||
incrBRG1 : func() {
|
||||
me._BRG1Source = math.mod(me._BRG1Source + 1, size(PFDInstrumentsController.BRG_SOURCE));
|
||||
me.page.setBRG1(PFDInstrumentsController.BRG_SOURCE[me._BRG1Source]);
|
||||
},
|
||||
|
||||
incrBRG2 : func() {
|
||||
me._BRG2Source = math.mod(me._BRG2Source + 1, size(PFDInstrumentsController.BRG_SOURCE));
|
||||
me.page.setBRG2(PFDInstrumentsController.BRG_SOURCE[me._BRG2Source]);
|
||||
},
|
||||
|
||||
getBRG1 : func() { return PFDInstrumentsController.BRG_SOURCE[me._BRG1Source]; },
|
||||
getBRG2 : func() { return PFDInstrumentsController.BRG_SOURCE[me._BRG2Source]; },
|
||||
|
||||
# Handle update of the airdata information.
|
||||
# ADC data is produced periodically as an entire set
|
||||
handleADCData : func(data) {
|
||||
|
@ -139,13 +196,14 @@ var PFDInstrumentsController =
|
|||
me.page.updateBARO(data["ADCPressureSettingInHG"]);
|
||||
|
||||
me.page.updateOAT(data["ADCOutsideAirTemperatureC"]);
|
||||
me.page.updateHSI(data["ADCHeadingDeg"]);
|
||||
me._heading = data["ADCHeadingDeg"];
|
||||
me.page.updateHSI(data["ADCHeadingMagneticDeg"]);
|
||||
me._heading_magnetic_deg = data["ADCHeadingMagneticDeg"];
|
||||
me._mag_var = data["ADCMagneticVariationDeg"];
|
||||
|
||||
# If we're "flying" at < 10kts, then we won't have sufficient delta between
|
||||
# airspeed and groundspeed to determine wind
|
||||
me.page.updateWindData(
|
||||
hdg : data["ADCHeadingDeg"],
|
||||
hdg : data["ADCHeadingMagneticDeg"],
|
||||
wind_hdg : data["ADCWindHeadingDeg"],
|
||||
wind_spd : data ["ADCWindSpeedKt"],
|
||||
no_data: (data["ADCIndicatedAirspeed"] < 1.0)
|
||||
|
@ -166,53 +224,56 @@ var PFDInstrumentsController =
|
|||
|
||||
if (data["FMSLegValid"] != nil) me._leg_valid = data["FMSLegValid"];
|
||||
|
||||
if (me._leg_valid == 0) {
|
||||
# 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.0,
|
||||
xtrk_nm : 0,
|
||||
from: 0,
|
||||
annun: "NO DATA",
|
||||
);
|
||||
if (me._navSelected == 1) {
|
||||
if (data["FMSNav1From"] != nil) me._leg_from = data["FMSNav1From"];
|
||||
} else {
|
||||
if (data["FMSNav2From"] != nil) me._leg_from = data["FMSNav2From"];
|
||||
}
|
||||
|
||||
if (data["FMSLegBearing"] != nil) me.page.updateCRS(data["FMSLegBearing"]);
|
||||
if (data["FMSLegID"] != nil) me._leg_id = data["FMSLegID"];
|
||||
if (data["FMSLegBearingMagDeg"] != nil) me._leg_bearing = data["FMSLegBearingMagDeg"];
|
||||
if (data["FMSLegDistanceNM"] != nil) me._leg_distance_nm = data["FMSLegDistanceNM"];
|
||||
if (data["FMSLegTrackErrorAngle"] != nil) me._leg_deviation_deg = data["FMSLegTrackErrorAngle"];
|
||||
|
||||
if (me._navSelected == 1) {
|
||||
if (data["FMSNav1From"] != nil) me._from = data["FMSNav1From"];
|
||||
# TODO: Proper cross-track error based on source and flight phase.
|
||||
if (data["FMSLegCourseError"] != nil) me._deflection_dots = data["FMSLegCourseError"] /2.0;
|
||||
if (data["FMSLegCourseError"] != nil) me._leg_xtrk_nm = data["FMSLegCourseError"];
|
||||
|
||||
if (me.getCDISource() == "GPS") {
|
||||
if (me._leg_valid == 0) {
|
||||
# No valid leg data, likely because there's no GPS course set
|
||||
me.page.updateCRS(0);
|
||||
me.page.updateCDI(
|
||||
heading: me._heading_magnetic_deg,
|
||||
course: 0,
|
||||
waypoint_valid: 0,
|
||||
course_deviation_deg : 0,
|
||||
deflection_dots : 0.0,
|
||||
xtrk_nm : 0,
|
||||
from: 0,
|
||||
annun: "NO DATA",
|
||||
loc : 0,
|
||||
);
|
||||
} else {
|
||||
if (data["FMSNav2From"] != nil) me._from = data["FMSNav2From"];
|
||||
me.page.updateCRS(me._leg_bearing);
|
||||
|
||||
me.page.updateCDI(
|
||||
heading: me._heading_magnetic_deg,
|
||||
course: me._leg_bearing,
|
||||
waypoint_valid: me._leg_valid,
|
||||
course_deviation_deg : me._leg_deviation_deg,
|
||||
deflection_dots : me._deflection_dots,
|
||||
xtrk_nm : me._leg_xtrk_nm,
|
||||
from: me._leg_from,
|
||||
annun: "ENR",
|
||||
loc: 0,
|
||||
);
|
||||
}
|
||||
|
||||
if (data["FMSLegID"] != nil) me._leg_id = data["FMSLegID"];
|
||||
if (data["FMSLegBearing"] != nil) me._leg_bearing = data["FMSLegBearing"];
|
||||
if (data["FMSLegDistanceNM"] != nil) me._leg_distance_nm = data["FMSLegDistanceNM"];
|
||||
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"] /2.0;
|
||||
if (data["FMSLegCourseError"] != nil) me._leg_xtrk_nm = data["FMSLegCourseError"];
|
||||
|
||||
me.page.updateCDI(
|
||||
heading: me._heading,
|
||||
course: me._leg_bearing,
|
||||
waypoint_valid: me._leg_valid,
|
||||
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.updateBRG1(me._leg_valid, me._leg_id, me._leg_distance_nm, me._heading, me._leg_bearing);
|
||||
if (me.page.getBRG2() == "GPS") me.page.updateBRG2(me._leg_valid, me._leg_id, me._leg_distance_nm, me._heading, me._leg_bearing);
|
||||
if (me.getBRG1() == "GPS") me.page.updateBRG1(me._leg_valid, me._leg_id, me._leg_distance_nm, me._heading_magnetic_deg, me._leg_bearing);
|
||||
if (me.getBRG2() == "GPS") me.page.updateBRG2(me._leg_valid, me._leg_id, me._leg_distance_nm, me._heading_magnetic_deg, me._leg_bearing);
|
||||
|
||||
return emesary.Transmitter.ReceiptStatus_OK;
|
||||
},
|
||||
|
@ -229,7 +290,13 @@ var PFDInstrumentsController =
|
|||
if (data["Nav1RadialDeg"] != nil) me._nav1_radial_deg = data["Nav1RadialDeg"];
|
||||
if (data["Nav1InRange"] != nil) me._nav1_in_range = data["Nav1InRange"];
|
||||
if (data["Nav1DistanceMeters"] != nil) me._nav1_distance_m = data["Nav1DistanceMeters"];
|
||||
if (data["Nav1CourseDeviationDeg"] != nil) me._nav1_deviation_deg = data["Nav1CourseDeviationDeg"];
|
||||
|
||||
# Deflection range is [-10,10], while deflection_dots is [-2.4, 2.4];
|
||||
if (data["Nav1Deflection"] != nil) me._nav1_deflection = data["Nav1Deflection"] * 2.4;
|
||||
if (data["Nav1CrosstrackErrorM"] != nil) me._nav1_crosstrack_m = data["Nav1CrosstrackErrorM"];
|
||||
if (data["Nav1From"] != nil) me._nav1_from = data["Nav1From"];
|
||||
if (data["Nav1Localizer"] != nil) me._nav1_loc = data["Nav1Localizer"];
|
||||
|
||||
if (data["Nav2SelectedFreq"] != nil) me._nav2_freq = data["Nav2SelectedFreq"];
|
||||
if (data["Nav2ID"] != nil) me._nav2_id = data["Nav2ID"];
|
||||
|
@ -237,18 +304,57 @@ var PFDInstrumentsController =
|
|||
if (data["Nav2RadialDeg"] != nil) me._nav2_radial_deg = data["Nav2RadialDeg"];
|
||||
if (data["Nav2InRange"] != nil) me._nav2_in_range = data["Nav2InRange"];
|
||||
if (data["Nav2DistanceMeters"] != nil) me._nav2_distance_m = data["Nav2DistanceMeters"];
|
||||
if (data["Nav2CourseDeviationDeg"] != nil) me._nav2_deviation_deg = data["Nav1CourseDeviationDeg"];
|
||||
|
||||
# Deflection range is [-1,1], while deflection_dots is [-2.4, 2.4];
|
||||
if (data["Nav2Deflection"] != nil) me._nav2_deflection = data["Nav2Deflection"] * 2.4;
|
||||
if (data["Nav2CrosstrackErrorM"] != nil) me._nav2_crosstrack_m = data["Nav2CrosstrackErrorM"];
|
||||
if (data["Nav2From"] != nil) me._nav2_from = data["Nav2From"];
|
||||
if (data["Nav2Localizer"] != nil) me._nav2_loc = data["Nav2Localizer"];
|
||||
|
||||
if (data["ADFSelectedFreq"] != nil) me._adf_freq = data["ADFSelectedFreq"];
|
||||
if (data["ADFInRange"] != nil) me._adf_in_range = data["ADFInRange"];
|
||||
if (data["ADFHeadingDeg"] !=nil) me._adf_heading_deg = data["ADFInRange"];
|
||||
|
||||
if (me.page.getBRG1() == "NAV1") me.page.updateBRG1(me._nav1_in_range, me._nav1_id, me._nav1_distance_m * M2NM, me._heading, me._nav1_heading_deg);
|
||||
if (me.page.getBRG1() == "NAV2") me.page.updateBRG1(me._nav2_in_range, me._nav2_id, me._nav2_distance_m * M2NM, me._heading, me._nav2_heading_deg);
|
||||
if (me.page.getBRG1() == "ADF") me.page.updateBRG1(me._adf_in_range, sprintf("%.1f", me._adf_freq), 0, me._heading, me._adf_heading_deg);
|
||||
if (me.getBRG1() == "NAV1") me.page.updateBRG1(me._nav1_in_range, me._nav1_id, me._nav1_distance_m * M2NM, me._heading_magnetic_deg, me._nav1_heading_deg);
|
||||
if (me.getBRG1() == "NAV2") me.page.updateBRG1(me._nav2_in_range, me._nav2_id, me._nav2_distance_m * M2NM, me._heading_magnetic_deg, me._nav2_heading_deg);
|
||||
if (me.getBRG1() == "ADF") me.page.updateBRG1(me._adf_in_range, sprintf("%.1f", me._adf_freq), 0, me._heading_magnetic_deg, me._adf_heading_deg);
|
||||
|
||||
if (me.page.getBRG2() == "NAV1") me.page.updateBRG2(me._nav1_in_range, me._nav1_id, me._nav1_distance_m * M2NM, me._heading, me._nav1_heading_deg);
|
||||
if (me.page.getBRG2() == "NAV2") me.page.updateBRG2(me._nav2_in_range, me._nav2_id, me._nav2_distance_m * M2NM, me._heading, me._nav2_heading_deg);
|
||||
if (me.page.getBRG2() == "ADF") me.page.updateBRG2(me._adf_in_range, sprintf("%.1f", me._adf_freq), 0, me._heading, me._adf_heading_deg);
|
||||
if (me.getBRG2() == "NAV1") me.page.updateBRG2(me._nav1_in_range, me._nav1_id, me._nav1_distance_m * M2NM, me._heading_magnetic_deg, me._nav1_heading_deg);
|
||||
if (me.getBRG2() == "NAV2") me.page.updateBRG2(me._nav2_in_range, me._nav2_id, me._nav2_distance_m * M2NM, me._heading_magnetic_deg, me._nav2_heading_deg);
|
||||
if (me.getBRG2() == "ADF") me.page.updateBRG2(me._adf_in_range, sprintf("%.1f", me._adf_freq), 0, me._heading_magnetic_deg, me._adf_heading_deg);
|
||||
|
||||
if (me.getCDISource() == "NAV1") {
|
||||
me.page.updateCRS(me._nav1_radial_deg);
|
||||
me.page.updateCDI(
|
||||
heading: me._heading_magnetic_deg,
|
||||
course: me._nav1_radial_deg,
|
||||
waypoint_valid: me._nav1_in_range,
|
||||
course_deviation_deg : me._nav1_deviation_deg,
|
||||
deflection_dots : me._nav1_deflection,
|
||||
xtrk_nm : me._nav1_crosstrack_m * M2NM,
|
||||
from: me._nav1_from,
|
||||
annun: "",
|
||||
loc : me._nav1_loc,
|
||||
);
|
||||
}
|
||||
|
||||
if (me.getCDISource() == "NAV2") {
|
||||
me.page.updateCRS(me._nav2_radial_deg);
|
||||
me.page.updateCDI(
|
||||
heading: me._heading_magnetic_deg,
|
||||
course: me._nav2_radial_deg,
|
||||
waypoint_valid: me._nav2_in_range,
|
||||
course_deviation_deg : me._nav2_deviation_deg,
|
||||
deflection_dots : me._nav2_deflection,
|
||||
xtrk_nm : me._nav2_crosstrack_m * M2NM,
|
||||
from: me._nav2_from,
|
||||
annun: "",
|
||||
loc : me._nav2_loc,
|
||||
);
|
||||
}
|
||||
|
||||
return emesary.Transmitter.ReceiptStatus_OK;
|
||||
},
|
||||
|
||||
PFDRegisterWithEmesary : func(transmitter = nil){
|
||||
|
|
|
@ -57,7 +57,7 @@ var PAGE_GROUPS = [
|
|||
# Mapping for header labels to specific FMS or ADC messages, and sprintf formatting
|
||||
# to use
|
||||
var HEADER_MAPPING = {
|
||||
"BRG" : { message : "FMSLegBearing", format : "%d"},
|
||||
"BRG" : { message : "FMSLegBearingMagDeg", format : "%d"},
|
||||
"XTK" : { message : "FMSLegCourseError", format : "%.1fnm"},
|
||||
"DIS" : { message : "FMSDistance", format : "%.1fnm"},
|
||||
"DTK" : { message : "FMSLegDesiredTrack", format : "%d"},
|
||||
|
|
|
@ -31,11 +31,16 @@ var SurroundController =
|
|||
_com2standby : 0.0,
|
||||
_nav1active : 0.0,
|
||||
_nav1standby : 0.0,
|
||||
_nav1radial : 0.0,
|
||||
_nav1_heading_deg : 0.0,
|
||||
_nav2active : 0.0,
|
||||
_nav2standby : 0.0,
|
||||
_nav2radial : 0.0,
|
||||
_nav2_heading_deg : 0.0,
|
||||
_pressure_settings_inhg : 0.0,
|
||||
_selected_alt_ft : 0.0,
|
||||
_heading_bug_deg : 0.0,
|
||||
_heading_deg : 0.0,
|
||||
};
|
||||
|
||||
obj.RegisterWithEmesary();
|
||||
|
@ -81,8 +86,13 @@ var SurroundController =
|
|||
|
||||
if (data["Nav1SelectedFreq"] != nil) me._nav1active = data["Nav1SelectedFreq"];
|
||||
if (data["Nav1StandbyFreq"] != nil) me._nav1standby = data["Nav1StandbyFreq"];
|
||||
if (data["Nav1RadialDeg"] != nil) me._nav1radial = data["Nav1RadialDeg"];
|
||||
if (data["Nav1HeadingDeg"] != nil) me._nav1_heading_deg = data["Nav1HeadingDeg"];
|
||||
|
||||
if (data["Nav2SelectedFreq"] != nil) me._nav2active = data["Nav2SelectedFreq"];
|
||||
if (data["Nav2StandbyFreq"] != nil) me._nav2standby = data["Nav2StandbyFreq"];
|
||||
if (data["Nav2RadialDeg"] != nil) me._nav2radial = data["Nav2RadialDeg"];
|
||||
if (data["Nav2HeadingDeg"] != nil) me._nav2_heading_deg = data["Nav2HeadingDeg"];
|
||||
|
||||
# pass through to the page
|
||||
me._page.handleNavComData(data);
|
||||
|
@ -93,6 +103,7 @@ var SurroundController =
|
|||
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"];
|
||||
if (data["ADCHeadingMagneticDeg"] != nil) me._heading_deg = data["ADCHeadingMagneticDeg"];
|
||||
|
||||
# Pass FMS and ADC data straight to the page to display in the header fields
|
||||
me._page.updateHeaderData(data);
|
||||
|
@ -211,7 +222,7 @@ var SurroundController =
|
|||
},
|
||||
|
||||
# Switch between Nav1 and Nav2.
|
||||
handleNavToggle : func (value)
|
||||
handleNavToggle : func ()
|
||||
{
|
||||
var data={};
|
||||
|
||||
|
@ -225,6 +236,13 @@ var SurroundController =
|
|||
return emesary.Transmitter.ReceiptStatus_Finished;
|
||||
},
|
||||
|
||||
setNav : func(value) {
|
||||
var data={};
|
||||
data["NavSelected"] = value;
|
||||
me.sendNavComDataNotification(data);
|
||||
return emesary.Transmitter.ReceiptStatus_Finished;
|
||||
},
|
||||
|
||||
# Outer COM dial changes the integer value of the standby selected COM
|
||||
# frequency, wrapping on limits. Leaves the fractional part unchanged
|
||||
handleComOuter : func (value) {
|
||||
|
@ -366,6 +384,32 @@ var SurroundController =
|
|||
return emesary.Transmitter.ReceiptStatus_Finished;
|
||||
},
|
||||
|
||||
handleCRS : func(value) {
|
||||
var incr_or_decr = (value > 0) ? 1 : -1;
|
||||
var data={};
|
||||
|
||||
if (me._navselected == 1) {
|
||||
data["Nav1RadialDeg"] = math.mod(me._nav1radial + incr_or_decr, 360);
|
||||
} else {
|
||||
data["Nav2RadialDeg"] = math.mod(me._nav2radial + incr_or_decr, 360);
|
||||
}
|
||||
|
||||
me.sendNavComDataNotification(data);
|
||||
return emesary.Transmitter.ReceiptStatus_Finished;
|
||||
},
|
||||
|
||||
handleCRSCenter : func(value) {
|
||||
var data = {};
|
||||
if (me._navselected == 1) {
|
||||
data["Nav1RadialDeg"] = me._nav1_heading_deg;
|
||||
} else {
|
||||
data["Nav2RadialDeg"] = me._nav2_heading_deg;
|
||||
}
|
||||
|
||||
me.sendNavComDataNotification(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);
|
||||
|
@ -396,6 +440,13 @@ var SurroundController =
|
|||
return emesary.Transmitter.ReceiptStatus_Finished;
|
||||
},
|
||||
|
||||
handleHeadingPress : func(value) {
|
||||
var data = {};
|
||||
data["FMSHeadingBug"] = me._heading_deg;
|
||||
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.
|
||||
|
|
Loading…
Reference in a new issue