List of Nasal extension functions

From FlightGear wiki
Jump to navigation Jump to search


FlightGear extension functions

cmdarg()

cmdarg() is a mechanism to pass arguments to a nasal script (wrapped in properties) instead of "normal" function parameters. Note that cmdarg() should be primarily used in Nasal code embedded in XML files and should be considered depreciated otherwise (see [1] and [2]).

cmdarg() will keep working in (joystick) XML-bindings and on the top-level of embedded Nasal scripts (i.e. dialog and animation XML files).

As such, the cmdarg() function is primarily used for listener callbacks declared in XML markup, cmdarg() returns the listened-to property as props.Node object, so you can use it with all its methods (see $FG_ROOT/Nasal/props.nas) for example:

 print(cmdarg().getPath(), " has been changed to ", cmdarg().getValue())

The cmdarg() function avoids that you have to type the exact same path twice (once here and once in the setlistener() command) and it makes clear that this is the listened to property. Also, you can use all the nice props.Node methods on cmdarg() directly:

setlistener("/gear/launchbar/state", func {
     if (cmdarg().getValue() == "Engaged")
         setprop("/sim/messages/copilot", "Engaged!");
 }, 1, 0);

Use of cmdarg() outside of XML-bindings won't cause an error, but (still) return the last cmdarg() property. This just won't be the listened-to property anymore, but whatever the last legitimate cmdarg() user set. Most of the time it will be the property root of a joystick binding.

Don't make any assumptions and use cmdarg() only in one of these cases:

  • binding: returns root of this binding's property branch. Needed for accessing an axis' value: cmdarg().getNode("setting").getValue()
  • dialog xml files: returns root of that file's property branch in memory. This can be used to let embedded Nasal change the dialog (e.g. clear and build lists) before the final layout is decided
  • animation xml files: returns root of this model's place in /ai/models/ when used as AI/MP model. Examples: /ai/models/multiplayer[3], /ai/models/tanker[1], etc.[1]
  • AI aircraft XML files
  • remotely invoking Nasal code by setting properties using the built-in telnet daemon (RPC)[2][3].

In all cases, the cmdarg() call must not be delayed until later using settimer() or setlistener(). Because later it'll again return some unrelated property!

fgcommand()

Runs an internal "fgcommand", see Bindings for a full list of available items (since they are most often used in bindings for input devices). The first argument is the name of the command (e.g. "property-interpolate") and the second is a props.Node object or its ghost which is the "argument" to the fgcommand. (This ensures that fgcommands are universal since props.Node/SGPropertyNode objects can really be accessed from anywhere in FlightGear.) Each fgcommand returns 1 if it succeeded or 0 if it failed.

The profiling related fgcommands profiler-start and profiler-stop are documented at Built-in Profiler.

addcommand()

Add a fgcommand which can be used like a regular fgcommand (global scope, etc.). First argument is the name, second is the function to run. Note that this fgcommand will always return true!

print()

Concatenates an arbitrary number of arguments to one string, appends a new-line, and prints it to the terminal. Returns the number of printed characters.

print("Just", " a ", "test");


getprop()

Returns the node value for a given path, or nil if the node doesn't exist or hasn't been initialized yet.

getprop(<path>);

Example:

print("The frame rate is ", getprop("/sim/frame-rate"), " FPS");


setprop()

Sets a property value for a given node path string. Always returns nil.

setprop(<path> [, <path>, [...]], <value>);

All arguments but the last are concatenated to a path string, with a slash (/) inserted between each element. The last value is written to the respective node. If the node isn't writable, then an error message is printed to the console.

Note: setprop() concatenates a list of input arguments by means of inserting a "/" in between. That is nice for properties, as this slash is part of the tree. However, when one wants to make use of indices, like [0], one has to concatenate by hand (using "~") inside one part of the string argument list. An example is:

 var i = 4;
 setprop("instrumentation","cdu","page["~i~"]","title","MENU");

This results in instrumentation/cdu/page[4]/title = 'MENU' (string)

Examples:

setprop("/sim/current-view/view-number", 2);
setprop("/controls", "engines/engine[0]", "reverser", 1);


Erasing a property from the property tree: a property that has been created, for example through setprop() can be erased via

 props.globals.getNode("foo/bar").remove(); 		# take out the complete node
 props.globals.getNode("/foo").removeChild("bar"); 	# take out a certain child node

interpolate()

Give the value from a value or a source node to a destination node in given time.

 interpolate(<path>, <value>, 

Examples:

 interpolate("controls/switches/nav-lights-pos", 1, 0.25); # After 25ms, nav-lights-pos = 1
 interpolate("controls/gear/brake-left-pos", getprop("controls/gear/brake-left"), 1); # After 1s, brake-left-pos = brake-left

settimer()

Runs a function after a given simulation time (default) or real time in seconds.

settimer(<function>, 

The first object is a function object (ie, "func { ... }"). Note that this is different from a function call (ie, "func ( ... )"). If you don't understand what this means, just remember to always enclose the first argument in any call to settimer with the word "func" and braces "{ }", and it will always work. For instance, if you want print the words "My result" in five seconds, use this code:

settimer ( func { print ( "My result"); }, 5);

Inside the braces of the func object you can put any valid Nasal code, including a function call. In fact, if you want to call a function with certain values as arguments, the way to do it is to turn it into a function object by enclosing it with a func{}, for example:

myarg1="My favorite string";
myarg2=432;
settimer ( func { myfunction ( myarg1, myarg2); }, 25);

The third argument is optional and defaults to 0, which lets the time argument be interpreted as "seconds simulation time". In this case the timer doesn't run when FlightGear is paused. For user interaction purposes (measuring key press time, displaying popups, etc.) one usually prefers real time.

# simulation time example
var copilot_annoyed = func { setprop("/sim/messages/copilot", "Stop it! Immediately!") }
settimer(copilot_annoyed, 10);
# real time example
var popdown = func ( tipArg ) { 
 fgcommand("dialog-close", tipArg); 
}

var selfStatusPopupTip = func (label, delay = 10, override = nil) {	
   var tmpl = props.Node.new({
           name : "PopTipSelf", modal : 0, layout : "hbox",
           y: 140,
           text : { label : label, padding : 6 }
   });
   if (override != nil) tmpl.setValues(override);
   
   popdown(tipArgSelf);
   fgcommand("dialog-new", tmpl);
   fgcommand("dialog-show", tipArgSelf);

   currTimerSelf += 1;
   var thisTimerSelf = currTimerSelf;

   # Final argument 1 is a flag to use "real" time, not simulated time
   settimer(func { if(currTimerSelf == thisTimerSelf) { popdown(tipArgSelf) } }, delay, 1);
}

More information about best practices for using the settimer function to create loops in Nasal is elsewhere on this page.

maketimer() (v. 2.11+)

var timer = maketimer(<interval>, <function>)
var timer = maketimer(<interval>, <self>, <function>)
timer.start()
timer.stop()
timer.restart(<interval>)
timer.singleShot [read/write]
timer.isRunning [read-only]
# create timer with 1 second interval
var timer = maketimer(1.0, func { print('timer called'); });
# start the timer (with 1 second inverval)
timer.start();
# restart timer with 4 second interval
timer.restart(4);

# fire one single time in 6 seconds
timer.singleShot = 1;
timer.restart(6);
var Tooltip = {
  new: func
  {
    var m = {
      parents: [Tooltip]
    }
    m._hideTimer = maketimer(1.0, m, Tooltip._hideTimeout);
    m._hideTimer.singleShot = 1;

    return m;
  },
  run: func
  {
    if( !me._hideTimer.isRunning )
      me._hideTimer.start();
  }
  _hideTimeout: func
  {
    print('_hideTimeout');
  }
};

systime()

Returns epoch time (time since 1972/01/01 00:00) in seconds as a floating point number with high resolution. This is useful for benchmarking purposes.

 #benchmarking example:
 var start = systime();
 how_fast_am_I(123);
 var end = systime();
 print("took ", end - start, " seconds");

carttogeod()

Converts cartesian coordinates x/y/z to geodetic coordinates lat/lon/alt, which are returned as a vector. Units are degree and meter.

var geod = carttogeod(-2737504, -4264101, 3862172);
print("lat=", geod[0], " lon=", geod[1], " alt=", geod[2]);

# outputs
lat=37.49999782141546 lon=-122.6999914632327 alt=998.6042055172776


geodtocart()

Converts geodetic coordinates lat/lon/alt to cartesian coordinates x/y/z. Units are degree and meter.

var cart = geodtocart(37.5, -122.7, 1000); # lat/lon/alt(m)
print("x=", cart[0], " y=", cart[1], " z=", cart[2]);

# outputs
x=-2737504.667684828 y=-4264101.900993474 z=3862172.834656495


geodinfo()

Returns information about geodetic coordinates. Takes two arguments: lat, lon (in degree) and returns a vector with two entries, or nil if no information could be obtained because the terrain tile wasn't loaded. The first entry is the elevation (in meters) for the given point, and the second is a hash with information about the assigned material, or nil if there was no material information available, because there is, for instance, an untextured building at that spot or the scenery tile is not loaded.

var lat = getprop("/position/latitude-deg");
var lon = getprop("/position/longitude-deg");
var info = geodinfo(lat, lon);

if (info != nil) {
    print("the terrain under the aircraft is at elevation ", info[0], " m");
    if (info[1] != nil)
        print("and it is ", info[1].solid ? "solid ground" : "covered by water");
}

A full data set looks like this:

debug.dump(geodinfo(lat, lon));

# outputs
[ 106.9892101062052, { light_coverage : 0, bumpiness : 0.5999999999999999, load_resistance : 1e+30,
solid : 0,  names : [ "Lake", "Pond", "Reservoir", "Stream", "Canal" ], friction_factor : 1, 
rolling_friction : 1.5 } ]

Note that geodinfo is a *very* CPU intensive operation, particularly in FG 2.4.0 and earlier, so use sparingly (forum discussion here).

parsexml()

This function is an interface to the built-in Expat XML parser. It takes up to five arguments. The first is a mandatory absolute path to an XML file, the remaining four are optional callback functions, each of which can be nil (which is also the default value).

var ret = parsexml(<path> [, <start-elem> [, <end-elem> [,  [, <pi> ]]]]);

<start-elem>  ... called for every starting tag with two arguments: the tag name, and an attribute hash
<end-elem>    ... called for every ending tag with one argument: the tag name
        ... called for every piece of data with one argument: the data string
<pi>          ... called for every "processing information" with two args: target and data string

<ret>         ... the return value is nil on error, and the <path> otherwise

Example:

var start = func(name, attr) {
    print("starting tag ", name);
    foreach (var a; keys(attr))
        print("\twith attribute ", a, "=", attr[a]);
}
var end = func(name) { print("ending tag ", name) }
var data = func(data) { print("data=", data) }
var pi = func(target, data) { print("processing instruction: target=", target, " data=", data) }
parsexml("/tmp/foo.xml", start, end, data, pi);

airportinfo()

Function for retrieval of airport/runway information. Usage:

 var apt = airportinfo("KHAF");   # get info about KHAF
 var apt = airportinfo(lat, lon); # get info about apt closest to lat/lon
 var apt = airportinfo();         # get info about apt closest to aircraft  

The command debug.dump(airportinfo("KHAF")) outputs this:

 { lon : -122.4962626410256, lat : 37.51343502564102, has_metar : 0,
 runways : { 12 : { stopway2 : 0, threshold1 : 232.5624,
 lon : -122.5010889999999, lat : 37.513831, stopway1 : 0, width : 45.72,
 threshold2 : 232.5624, heading : 138.1199999999999, length : 1523.0856 } },
 elevation : 20.42159999999999, id : "KHAF", name : "Half Moon Bay" }

That is: a hash with elements lat/lon/elev/id/name/has_metar for the airport, and a hash with runways, each of which consists of lat/lon/length/width/heading/threshold[12]/stopway[12]. Only one side of each runway is listed -- the other can easily be deduced.

Positioned Object Queries

Several functions exist to query the navigation database of 'positioned' objects, i.e items with a defined location in the simulation world. (These objects all inherit from FGPositioned internally). The functions return either one, or a vector, of wrapped Nasal objects. These are efficient, but unlike a hash they cannot simply be debug.dump() to view all their members and methods.

When the query functions take a position, the default value is the current aircraft location. An alternative location can be supplied by passing two number (lat, lon), a Geo.Coord object, or any positioned object or waypoint retrieved from another query.

findAirportsWithinRange()
Find all airports within a specified range (in NM) of the current position or explicit position
findAirportsByICAO()
Find airports matching a complete or partial ICAO code. In particular this can search for all airports starting with a two or three letter prefix.
navinfo()
Return a list of navaids near a location by type and ident. Type should be 'fix', 'vor', 'ndb', 'dme'
findNavaidsWithinRange()
Search for navaids within a particular range (in NM), optionally limited by type. This provides a 'find the closest ILS' function
findNavaidByFrequency()
find the closest navaid (of a particular type) matching an exact frequency
findNavaidsByFrequency()
find all the navaids matching a particular frequency and optional type, sorted by distance from the search location.

All positioned objects returned by the above methods have the following members:

id
Identifier - ICAO code for airports, published ident for navaids and fixes
lon
degrees longitude
lat
degrees latitude

Depending on type, the following members are also available:

name
the full name of the airport or navaid if one is defined
elevation
the ASL elevation of the object in feet

For navaids, the following members are available:

frequency
the navaid frequency in kHz
type
the navaid type as a string: vor, dme, loc, ils, ndb
course
the degrees course associated with the navaid, for localiser and ILS stations

flightplan()

Function to retrieve the active flight-plan object, or load a flight plan from a file path.

Usage:

var fp = flightplan();
var fp = flightplan('/some/path/to/a/flightplan.xml');

In advance of converting the Map and NavDisplay to use the Canvas, James has improved the "flightplan()" extension function of the Nasal scripting interpreter to expose the full route-path vector for each flight plan leg, as a vector on the leg .


var fp = flightplan();
for (var i=0; i<fp.getPlanSize(); i += 1)
{
  var leg = fp.getWP(i);
  debug.dump(leg.path());
}


  1. Melchior Franz (8 December 2007). multiplayer generic properties.
  2. Melchior Franz (2 January 2006). Calling FG functions via network interface.
  3. Melchior Franz (7 January 2006). Calling FG functions via network interface.