Howto:Implement a Flight Management Computer in Nasal

From FlightGear wiki
Jump to navigation Jump to search
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 This is a link to the FlightGear forum.. 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.

Canvasready.png

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