Howto:Implement a Flight Management Computer in Nasal
This article is a stub. You can help the wiki by expanding it. |
IMPORTANT: Some, and possibly most, of the features/ideas discussed here are likely to be affected, and possibly even deprecated, by the ongoing work on providing a property tree-based 2D drawing API accessible from Nasal using the new Canvas system available since FlightGear 2.80 (08/2012). Please see: Canvas for further information
You are advised not to start working on anything directly related to this without first discussing/coordinating your ideas with other FlightGear contributors using the FlightGear developers mailing list or the Canvas subforum . Anything related to Canvas Core Development should be discussed first of all with TheTom and Zakalawe. Nasal-space frameworks are being maintained by Philosopher and Hooray currently. talk page. |
This article explains how to implement a Boeing Flight Management Computer for any airliner.
Requirements
- Basic:
- Simple Nasal scripting
- Basic FlightGear XML knowledge
- Other:
- The aircraft must have a basic CDU/mCDU display to show the calculations / call the functions in the fmc and fmcFP classes.
Nasal scripts
1. Create nasal files with the following content.
################################################################################
#
# Boeing Flightplan Management
#-------------------------------------------------------------------------------
#
# The crew can enter 2 flightplans, a primary one and an alternate one. The
# crew also has a choice for an alternate airport. The 2 flightplans are stored
# in separate trees in the property list, and the crew can choose which one to
# use. The one chosen will be put into the route manager. When the pilot chooses
# to divert, the active route is cleared, and the aircraft is put on a direct
# flight to the alternate airport.
#-------------------------------------------------------------------------------
#
# This file (flightplan.nas) is standalone and only contains the functions
# that need to be executed for the flightplan manager's functions. These need to
# be called from the aircraft's CDU (mCDU for Airbus, but this is meant for BOE)
#-------------------------------------------------------------------------------
#
# This file permits the usage of how many ever flightplans are requierd. Just
# add the flightplan too be initialized if you want more than 2. 2 flightplans
# are there by default as Boeing's Flight Management Computer uses 2.
#-------------------------------------------------------------------------------
#
# Copyright (c) 2012 Narendran Muraleedharan
#
# Licensed under GNU General Public License v2.
# <http://www.gnu.org/licenses/>
#
################################################################################
var fmcFPtree = "/instrumentation/fmcFP/";
var route = "/autopilot/route-manager/";
# Flight Management Computer: Flightplan Management Class
var fmcFP = {
# Initialize Flight Plan Management
init: func() {
setprop(fmcFPtree~ "flightplan[0]/num", 0);
setprop(fmcFPtree~ "flightplan[1]/num", 0);
setprop(fmcFPtree~ "flightplan[0]/status", "EMPTY");
setprop(fmcFPtree~ "flightplan[1]/status", "EMPTY");
setprop(fmcFPtree~ "alternate/icao", "");
setprop(fmcFPtree~ "FPpage[0]/first", 0);
setprop(fmcFPtree~ "FPpage[1]/first", 0);
},
# Basic Route Manager Functions (CLEAR, INSERT, ADD, REMOVE)
clear: func(plan) {
var n = 0;
while(getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/id") != nil) {
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/id", "");
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/alt", 0);
n += 1;
}
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num", 0);
},
insert: func(plan, index, wp, alt) {
var n = getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num");
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num") + 1);
while(n >= index) {
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (n + 1) ~ "]/id", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/id"));
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (n + 1) ~ "]/alt", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/alt"));
n = n - 1;
}
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ index ~ "]/id", wp);
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ index ~ "]/alt", alt);
},
add: func(plan, wp, alt) {
var index = getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num");
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num") + 1);
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ index ~ "]/id", wp);
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ index ~ "]/alt", alt);
},
remove: func(plan, index) {
var n = index;
var last = getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num");
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num") - 1);
while(n < getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num")) {
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/id", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (n + 1) ~ "]/id"));
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/alt", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (n + 1) ~ "]/alt"));
n += 1;
}
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ last ~ "]/id", "");
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ last ~ "]/alt", 0);
},
# Copy a flight plan to Route Manager or Update the Route
copy: func(plan) {
## Clear the Route Manager
setprop(route~ "input", "@CLEAR");
## Put in all the Waypoints
var n = 0;
while (n < getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num")) {
setprop(route~ "input", "@INSERT999:" ~ getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/id") ~ "@" ~ getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ n ~ "]/alt"));
n += 1;
}
},
# Function to set altitude for a WP
setalt: func(plan, index, alt) {
setprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ index ~ "]/alt", alt);
},
# Clear the active route and divert to the given airport
divert: func(icao) {
## Clear the Route Manager
setprop(route~ "input", "@CLEAR");
## Set Diversion Airport as the only WP on the Route Manager
setprop(route~ "departure/airport", "");
setprop(route~ "destination/airport", "");
setprop(route~ "departure/runway", "");
setprop(route~ "destination/runway", "");
setprop(route~ "input", "@INSERT999:" ~ icao ~ "@0");
setprop(route~ "active", 1);
}
};
################################################################################
# Function to display the Flightplan page (part of the CDU, only for 787)
var FPpage = func(cdu, plan) {
## Field Types
setprop("/controls/cdu[" ~ cdu ~ "]/l1-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l2-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l3-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l4-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l5-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l6-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/l7-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r1-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r2-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r3-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r4-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r5-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r6-type", "click");
setprop("/controls/cdu[" ~ cdu ~ "]/r7-type", "click");
## Field Values
setprop("/controls/cdu[" ~ cdu ~ "]/display/l1-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l2-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l3-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l4-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l5-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l6-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l7-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r1-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r2-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r3-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r4-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r5-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r6-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r7-label", "");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l5", "< REMOVE WP");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l6", "< CLEAR FP");
setprop("/controls/cdu[" ~ cdu ~ "]/display/l7", "< INDEX");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r7", "FLIGHTPLANS >");
if (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") != 0)
setprop("/controls/cdu[" ~ cdu ~ "]/display/r5", "SCROLL UP >");
else
setprop("/controls/cdu[" ~ cdu ~ "]/display/r5", "");
if (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 4 < getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/num"))
setprop("/controls/cdu[" ~ cdu ~ "]/display/r6", "SCROLL DOWN >");
else
setprop("/controls/cdu[" ~ cdu ~ "]/display/r6", "");
## Display WP IDs and ALTs
if ((getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") ~ "]/id") != nil) and (getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first")) ~ "]/id") != "")) {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l1", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first")) ~ "]/id"));
setprop("/controls/cdu[" ~ cdu ~ "]/display/r1", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first")) ~ "]/alt"));
} else {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l1", "-");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r1", "-");
}
if ((getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 1) ~ "]/id") != nil) and (getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 1) ~ "]/id") != "")) {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l2", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 1) ~ "]/id"));
setprop("/controls/cdu[" ~ cdu ~ "]/display/r2", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 1) ~ "]/alt"));
} else {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l2", "-");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r2", "-");
}
if ((getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 2) ~ "]/id") != nil) and (getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 2) ~ "]/id") != "")) {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l3", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 2) ~ "]/id"));
setprop("/controls/cdu[" ~ cdu ~ "]/display/r3", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 2) ~ "]/alt"));
} else {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l3", "-");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r3", "-");
}
if ((getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 3) ~ "]/id") != nil) and (getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 3) ~ "]/id") != "")) {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l4", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 3) ~ "]/id"));
setprop("/controls/cdu[" ~ cdu ~ "]/display/r4", getprop(fmcFPtree~ "flightplan[" ~ plan ~ "]/wp[" ~ (getprop(fmcFPtree~ "FPpage[" ~ plan ~ "]/first") + 3) ~ "]/alt"));
} else {
setprop("/controls/cdu[" ~ cdu ~ "]/display/l4", "-");
setprop("/controls/cdu[" ~ cdu ~ "]/display/r4", "-");
}
}
var fmc = {
calc_speeds: func {
# Take-off and Approach Flap Extension Speeds
var total_weight = getprop("/fdm/jsbsim/inertia/weight-lbs");
## FLAPS: 1 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps1", (2.8303320214779 * 0.0001 * total_weight) + 118.28007981769);
## FLAPS: 5 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps5", (2.6844176662278 * 0.0001 * total_weight) + 119.04376832572);
## FLAPS: 10 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps10", (2.4794813791967 * 0.0001 * total_weight) + 101.66786689933);
## FLAPS: 15 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps15", (2.3086744918531 * 0.0001 * total_weight) + 81.372486004066);
## FLAPS: 25 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps25", (2.5112138808426 * 0.0001 * total_weight) + 61.051881021611);
## FLAPS: 35 DEG
setprop("/instrumentation/b787-fmc/speeds/flaps35", (1.7977818210994 * 0.0001 * total_weight) + 72.319797126439);
# Appraoch Speed
setprop("/instrumentation/b787-fmc/speeds/ap", (1.0044642857143 * 0.0001 * total_weight) + 101.84654017857);
# Touchdown Speed
setprop("/instrumentation/b787-fmc/speeds/td", (1.1160714285714 * 0.0001 * total_weight) + 85.385044642858);
},
parse_flightsDB: func {
io.read_properties(getprop("/sim/aircraft-dir") ~ "/FMC-DB/FMC_Flights.xml", "/instrumentation/b787-fmc");
},
search_flight: func(flightnum) {
var flightsDB = "/instrumentation/b787-fmc/flightsDB/" ~ flightnum ~ "/";
# Check if the flight exists
if (getprop(flightsDB ~ "depicao") != nil) {
# Display Flight Data in the CDU
setprop("/controls/cdu/display/l1", flightnum);
setprop("/controls/cdu/display/l2", getprop(flightsDB ~ "depicao"));
setprop("/controls/cdu/display/l3", getprop(flightsDB ~ "arricao"));
setprop("/controls/cdu/display/l4", getprop(flightsDB ~ "reg"));
setprop("/controls/cdu/display/l5", getprop(flightsDB ~ "flight-time"));
# Whether route is available
if (getprop(flightsDB ~ "route/pre") == 1) {
setprop("/controls/cdu/display/l6", "Available");
} else {
setprop("/controls/cdu/display/l6", "Unavailable");
}
} else {
setprop("/controls/cdu/display/page", "DEP/ARR");
}
},
confirm_flight: func(flightnum) {
var flightsDB = "/instrumentation/b787-fmc/flightsDB/" ~ flightnum ~ "/";
# Used to clear the current route entered
setprop("/autopilot/route-manager/input", "@CLEAR");
if (getprop(flightsDB ~ "route/pre") == 1) {
# Enter Route from the Database
var n = 0;
while(getprop(flightsDB ~ "route/wp[" ~ n ~ "]/id") != nil) {
# If VNAV is available, enter VNAV altitudes too
if (getprop(flightsDB ~ "route/vnav") == 1) {
setprop("/autopilot/route-manager/input", "@INSERT999:" ~ getprop(flightsDB ~ "route/wp[" ~ n ~ "]/id") ~ "@" ~ getprop(flightsDB ~ "route/wp[" ~ n ~ "]/alt"));
} else { # If not, just put in the waypoints
setprop("/autopilot/route-manager/input", "@INSERT999:" ~ getprop(flightsDB ~ "route/wp[" ~ n ~ "]/id"));
}
n += 1;
}
}
# If VNAV is enabled, enter crz altitude and crz wps
if (getprop(flightsDB ~ "route/vnav") == 1) {
setprop("/controls/cdu/vnav/crz-altitude-ft", getprop(flightsDB ~ "route/crz-altitude-ft"));
setprop("/controls/cdu/vnav/start-crz", getprop(flightsDB ~ "route/start-crz"));
setprop("/controls/cdu/vnav/end-crz", getprop(flightsDB ~ "route/end-crz"));
}
# Set Departure and Arrival Airport
setprop("/autopilot/route-manager/departure/airport", getprop(flightsDB ~ "depicao"));
setprop("/autopilot/route-manager/destination/airport", getprop(flightsDB ~ "arricao"));
# If a preset route doesn't exist, generate a route
if (getprop(flightsDB ~ "route/pre") != 1)
setprop("/autopilot/route-manager/input", "@ROUTE1")
}
};
Changes to CDU
To show reference speeds, simple display the values from the property tree. Also, call the fmc.calc_speeds() function everytime you want to calculate the reference speeds.
Call the following functions to work with the flight plans.
- init() - Initializes the FMC-FP Manager
- clear(plan) - Clears the defined flight plan
- remove(plan, index) - Removes the WP and ALT from the defined flight plan in that index
- insert(plan, index, wp, alt) - Inserts a WP and ALT at the given index in the defined flight plan
- add(plan, wp, alt) - Adds a WP and ALT to hte end of the defined flight plan
- copy(plan) - Moves the selected flight plan into the Route Manager
- divert(icao) - Diverts the aircraft to the given airport