1
0
Fork 0
A320-family/Nasal/FMGC/flightplan-delegates.nas
Jonathan Redpath 04177e434c Make the FMGC respect speed constraints in flightplan (while still meeting speed limit)
Also fix bug with incorrect groundspeed node, and prevent an error in
replay mode
2022-07-03 18:40:52 +01:00

313 lines
10 KiB
Text

# SPDX-License-Identifier: GPL-2.0-or-later
#
# NOTE! This copyright does *not* cover user models that use these Nasal
# services by normal function calls - this is merely considered normal use
# of the code, and does *not* fall under the heading of "derived work."
#
# Copyright (C) 2012-202 by James Turner
# route_manager.nas - FlightPlan delegate(s) corresponding to the built-
# in route-manager dialog and GPS. Intended to provide a sensible default behaviour,
# but can be disabled by an aircraft-specific FMS / GPS system.
# This delegate corresponds to functionality of the built-in route-manager dialog.
# if you disable it, the built-in route-manager dialog may not work as expected.
# Especially, this dialog is responsible for building departure, approach and
# arrival waypoints corresponding to the requested SID/STAR/approach,
# and replacing them when the inputs change (eg, user seelcted a different
# destination or STAR while enroute)
#
# You can disable the default GPS behaviour *without* touching this delegate : they are
# kept seperate since this first one is less likely to need changes
var A320RouteManagerDelegate = {
new: func(fp) {
var m = { parents: [A320RouteManagerDelegate] };
logprint(LOG_INFO, 'creating A320 Route Manager FPDelegate');
m.flightplan = fp;
return m;
},
departureChanged: func
{
logprint(LOG_INFO, 'saw departure changed');
me.flightplan.clearWPType('sid');
if (me.flightplan.departure == nil)
return;
if (me.flightplan.departure_runway == nil) {
# no runway, only an airport, use that
var wp = createWPFrom(me.flightplan.departure);
wp.wp_role = 'sid';
me.flightplan.insertWP(wp, 0);
return;
}
# first, insert the runway itself
var wp = createWPFrom(me.flightplan.departure_runway);
wp.wp_role = 'sid';
me.flightplan.insertWP(wp, 0);
if (me.flightplan.sid == nil)
return;
# and we have a SID
var sid = me.flightplan.sid;
logprint(LOG_INFO, 'routing via SID ' ~ sid.id);
var wps = sid.route(me.flightplan.departure_runway, me.flightplan.sid_trans);
var lastWP = wps[-1];
var foundIdx = -999;
for (var wptIdx = 0; wptIdx < me.flightplan.getPlanSize(); wptIdx = wptIdx + 1) {
if (me.flightplan.getWP(wptIdx).id == lastWP.id) {
foundIdx = wptIdx;
break;
}
}
if (foundIdx != -999) {
while (foundIdx > 0) {
me.flightplan.deleteWP(1);
foundIdx -= 1;
}
}
me.flightplan.insertWaypoints(wps, 1);
},
arrivalChanged: func
{
me.flightplan.clearWPType('star');
me.flightplan.clearWPType('approach');
if (me.flightplan.destination == nil)
return;
if (me.flightplan.destination_runway == nil) {
# no runway, only an airport, use that
var wp = createWPFrom(me.flightplan.destination);
wp.wp_role = 'approach';
me.flightplan.appendWP(wp);
return;
}
var initialApproachFix = nil;
if (me.flightplan.star != nil) {
logprint(LOG_INFO, 'routing via STAR ' ~ me.flightplan.star.id);
var wps = me.flightplan.star.route(me.flightplan.destination_runway, me.flightplan.star_trans);
if (wps != nil) {
me.flightplan.insertWaypoints(wps, -1);
initialApproachFix = wps[-1]; # final waypoint of STAR
}
}
if (me.flightplan.approach != nil) {
var wps = nil;
var approachIdent = me.flightplan.approach.id;
if (me.flightplan.approach_trans != nil) {
# if an approach transition was specified, let's use it explicitly
wps = me.flightplan.approach.route(me.flightplan.destination_runway, me.flightplan.approach_trans);
if (wps == nil) {
logprint(LOG_WARN, "couldn't route approach " ~ approachIdent ~ " based on specified transition:" ~ me.flightplan.approach_trans);
}
} else if (initialApproachFix != nil) {
# no explicit approach transition, let's use the IAF to guess one
wps = me.flightplan.approach.route(me.flightplan.destination_runway, initialApproachFix);
if (wps == nil) {
logprint(LOG_INFO, "couldn't route approach " ~ approachIdent ~ " based on IAF:" ~ initialApproachFix.wp_name);
}
}
# depending on the order the user selects the approach or STAR, we might get into
# a mess here. If we failed to route so far, just try a direct to the approach
if (wps == nil) {
# route direct
wps = me.flightplan.approach.route(me.flightplan.destination_runway);
}
if (wps == nil) {
logprint(LOG_WARN, 'routing via approach ' ~ approachIdent ~ ' failed entirely.');
} else {
me.flightplan.insertWaypoints(wps, -1);
}
} else {
# no approach, just use the runway waypoint
var wp = createWPFrom(me.flightplan.destination_runway);
wp.wp_role = 'approach';
me.flightplan.appendWP(wp);
}
},
cleared: func
{
logprint(LOG_INFO, "saw active flightplan cleared, deactivating");
# see http://https://code.google.com/p/flightgear-bugs/issues/detail?id=885
fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
},
endOfFlightPlan: func
{
logprint(LOG_INFO, "end of flight-plan, deactivating");
fgcommand("activate-flightplan", props.Node.new({"activate": 0}));
}
};
var GPSPath = "/instrumentation/gps";
# this delegate corresponds to the default behaviour of the built-in GPS.
# depending on the real GPS/FMS you are modelling, you probably need to
# replace this with your own.
#
# To do that, just set /autopilot/route-manager/disable-fms to true, which
# will block creation of this delegate.
#
# Of course you are then responsible for many basic FMS functions, such as
# route sequencing and activation
#
var A320GPSDelegate = {
new: func(fp) {
var m = { parents: [A320GPSDelegate], flightplan:fp, landingCheck:nil };
logprint(LOG_INFO, 'creating A320 GPS FPDelegate');
# tell the GPS C++ code we will do sequencing ourselves, so it can disable
# its legacy logic for this
setprop(GPSPath ~ '/config/delegate-sequencing', 1);
# disable turn anticipation
setprop(GPSPath ~ '/config/enable-fly-by', 0);
# flyOver maximum distance
setprop(GPSPath ~ '/config/over-flight-arm-distance', 5);
fp.followLegTrackToFix = 1;
fp.aircraftCategory = 'C';
m._modeProp = props.globals.getNode(GPSPath ~ '/mode');
return m;
},
_landingCheckTimeout: func
{
if (pts.Gear.wow[0].getValue() and pts.Velocities.groundspeedKt.getValue() < 25) {
logprint(LOG_INFO, 'GPS saw speed < 25kts on destination runway, end of route.');
me.landingCheck.stop();
# record touch-down time?
me.flightplan.finish();
}
},
_captureCurrentCourse: func
{
setprop(GPSPath ~ "/selected-course-deg", getprop(GPSPath ~ "/desired-course-deg"));
},
_selectOBSMode: func
{
setprop(GPSPath ~ "/command", "obs");
},
waypointsChanged: func
{
},
activated: func
{
if (!me.flightplan.active)
return;
logprint(LOG_INFO,'flightplan activated, default GPS to LEG mode');
setprop(GPSPath ~ "/command", "leg");
},
deactivated: func
{
if (me._modeProp.getValue() == 'leg') {
logprint(LOG_INFO, 'flightplan deactivated, default GPS to OBS mode');
me._captureCurrentCourse();
me._selectOBSMode();
}
},
endOfFlightPlan: func
{
if (me._modeProp.getValue() == 'leg') {
logprint(LOG_INFO, 'end of flight-plan, switching GPS to OBS mode');
me._captureCurrentCourse();
me._selectOBSMode();
}
},
cleared: func
{
if (!me.flightplan.active)
return;
if (me._modeProp.getValue() == 'leg') {
logprint(LOG_INFO, 'flight-plan cleared, switching GPS to OBS mode');
me._captureCurrentCourse();
me._selectOBSMode();
}
},
sequence: func
{
if (!me.flightplan.active)
return;
if (me._modeProp.getValue() == 'leg') {
if (me.flightplan.current + 1 >= me.flightplan.numWaypoints()) {
logprint(LOG_INFO, "default GPS sequencing, finishing flightplan");
me.flightplan.finish();
} elsif (me.flightplan.nextWP().wp_type != 'discontinuity' and me.flightplan.nextWP().wp_type != 'vectors') {
logprint(LOG_INFO, "default GPS sequencing to next WP");
me.flightplan.current = me.flightplan.current + 1;
} else {
logprint(LOG_INFO, "default GPS sequencing to next WP (special)");
if (fmgc.Output.lat.getValue() == 1) {
fmgc.Input.lat.setValue(3);
}
if (me.flightplan.nextWP().wp_type == 'vectors') {
me.flightplan.current = me.flightplan.current + 2;
}
}
} else {
# OBS, do nothing
}
},
currentWaypointChanged: func
{
if (!me.flightplan.active)
return;
if (me.landingCheck != nil) {
me.landingCheck.stop();
me.landingCheck = nil; # delete timer
}
var active = me.flightplan.currentWP();
if (active == nil) return;
var activeRunway = active.runway();
# this check is needed to avoid problems with circular routes; when
# activating the FP we end up here, and without this check, immediately
# detect that we've 'landed' and finish the FP again.
if (!pts.Gear.wow[0].getValue() and
(activeRunway != nil) and (me.flightplan.destination_runway != nil) and
(activeRunway.id == me.flightplan.destination_runway.id))
{
me.landingCheck = maketimer(2.0, me, A320GPSDelegate._landingCheckTimeout);
me.landingCheck.start();
}
}
};
registerFlightPlanDelegate(A320GPSDelegate.new);
registerFlightPlanDelegate(A320RouteManagerDelegate.new);