Howto:Control the route manager in Nasal
This tutorial will document all the steps involved in creating a new Nasal module that constantly updates itself to control the Route manager, Nasal is FlightGear's scripting language.
This tutorial is based on [[Howto: ]], you should fully understand that one first.
A system that constantly updates itself to control the route manager could for example be used to create an entirely scripted flight using Nasal.
When you take a look at the wiki, you'll see that the route manager can be controlled by setting properties: Route manager. The "input" or "control" property is /autopilot/route-manager/input - as you can see by referring to $FG_ROOT/gui/dialogs/route-manager.xml.
So the route manager dialog only provides a convenient interface, you could just as well use the property browser or Nasal directly.
Properties can be set from Nasal using the setprop() command: Nasal library#setprop()
In order to set a property to a certain value/string, you just use a piece of code like the following:
setprop("/my/property", "value");
Obviously, this needs to be loaded/run or otherwise invoked by fgfs, for example by using the built-in Nasal console.
So you need to set the next/active waypoint for the route manager using setprop:
var control="/autopilot/route-manager/input";
setprop(control, next);
Now you need to make sure that the undefined "next" variable actually contains sensible and comprehensible input for the route manager system (see the RM wiki page).
To work with a list of pre-defined coordinates, you could use a Nasal vector: Nasal Variables
The following piece of code declares and initializes an empty vector in Nasal:
var coordinates = [];
Now you need to fill in some valid waypoint data (comma separated values):
var coordinates = ["SAU","OAK","SFO"];
Note that these are not GPS coordinates, but rather identifiers of VORs located near KSFO. But obviously, you could just as well use any other "fixes" supported by the route manager.
If you were to add these continuously to the RM, you would basically be flying a triangle between SAUSALITO VOR, OAKLAND VOR and SAN FRANCISCO VOR
Once you have your vector populated with some data, you can access individual elements by using a numeric INDEX into the vector using square brackets:
VECTOR[INDEX];
For example:
var coordinates = ["SAU","OAK","SFO"];
print ( coordinates[0] ); # will print SAU
print ( coordinates[1] ); # will print OAK
print ( coordinates[2] ); # will print SFO
Note that indexing starts at 0 and not at 1 !
Next, you would need to register a function that gets called during startup, so that it can keep setting the route manager, this is done using a _setlistener() call: Nasal library#setlistener()
Note that the underscore (_) is important, because the low level function (not he wrapper) must be used here:
_setlistener("/sim/signals/nasal-dir-initialized", myCode);
To ensure that your code gets called repeatedly, at a fixed rate - you need to use a settimer() call: Nasal library#settimer()
As you can see at Route manager internals there is even direct support for re-sequencing a route by setting the current-waypoint index to a different waypoint number in the route.
# rm_control.nas (save in $FG_ROOT/Nasal)
var mycode = func {
print("running my route manager controller ");
setprop("/autopilot/route-manager/input", "SFO");
settimer(mycode, 5); # register a timer, invoke the "mycode" function again after 5 second
}
_setlistener("/sim/signals/nasal-dir-initialized", mycode);
When added to $FG_ROOT/Nasal/rm_control.nas, this piece of code will get called at 5 second intervals and keeps adding yet another "SFO" waypoint to the RM route.
Obviously, this is not yet very useful - but when you take another look at Route manager internals#Signals you'll see that the RM system also emits so called "signals" by setting a certain property when specific events (such as route re-sequencing) occur. So you could now register a listener to this property and keep setting the property to the next "position" in the vector:
# rm_control.nas (save in $FG_ROOT/Nasal)
var pos = ['SAU','OAK','SFO']; # makes sense to start at KSFO with these defaults
var index=0;
var re_sequence = func {
print("running my route manager controller ");
setprop("/autopilot/route-manager/input", pos[index] );
index=index+1;
if (index > 2) index = 0; # reset index to 0 (wrap around)
}
_setlistener("/sim/signals/nasal-dir-initialized", re_sequence );
_setlistener("/autopilot/route-manager/signals/sequenced", re_sequence);
To actually fly the "route", you obviously need to activate the route manager (from code, or by using the RM GUI dialog).
As you'll see, you could even create a small dialog file to repeatedly set the waypoint automatically to a set of specified coordinates.
Just have a look at the implementation of the route manager dialog in $FG_ROOT/gui/dialogs/route-manager.xml.
If you are interested, this could be used to create a realistic holding-pattern autoflight system, that even uses proper entry and exit procedures.