FreeFlyer script allows users to define subroutines, called Procedures, to execute generic sets of FreeFlyer script. This functionality allows users to more easily generate and maintain complex Mission Plans.
There are a variety of Sample Mission Plans (included with your FreeFlyer installation) that demonstrate various applications of these topics. Continue to one of the Mission Plans listed below to view descriptions and images of these examples.
|
Uses
•Place common blocks of script in Procedures
•Streamlines a Mission Plan by replacing reoccurring code snippets with a Call to a Procedure
•Code snippets are located in one location for easy maintenance such as debugging or functionality enhancements |
Syntax
•Procedures may be defined in the Mission Plan or in an external .FFProcedure file. See the Include command for more information.
•Procedures are accessed using a Call command.
•Procedures are defined using a Define Procedure command.
•Arguments must be passed into a Procedure in order to retrieve calculations and store their results.
•Structs (user-defined collections of related FreeFlyer objects) can be passed as arguments into Procedures, effectively passing several objects together as one argument.
•Argument types for corresponding Call and Define statements must match and must be in the same sequence.
•Argument names for corresponding Call and Define statements are NOT required to match. |
The Include Command
•The Include command is used to allow Mission Plan access to the Procedure.
•Not necessary if the Procedure is added to the Externals list. The Externals tab is discussed on the Control Screen section of the Tour of FreeFlyer.
•See the Parsing Arbitrary String Data page for information on including environment variables in an Include statement. |
Where "filename" is the valid OS described path and file name, including the extension, of your Procedure.
The Call Command
The Call command is used with the following syntax from within the Mission Sequence. This example calls a Procedure named "Procedure1" while passing both a Spacecraft and its tank into the Procedure:
Call Procedure1(Spacecraft1, Spacecraft1.Tanks[0]);
|
The argument types that are used in the Call command MUST match the types used in the Define Procedure line, otherwise a syntax error will appear.
The Define Procedure Command
•The Define Procedure command is coupled with an EndProcedure statement.
•Must be present in the following format as the first line in a Procedure.
•The example below defines a Procedure named "Procedure1" while receiving both a Spacecraft and its tank from the Call statement: |
Define Procedure Procedure1(Spacecraft Spacecraft1, SphericalTank Tank1);
EndProcedure;
|
At this point, the properties of the Spacecraft and Tank are transferred from the Procedure back to the Mission Plan.
The ExitProcedure Command
The ExitProcedure command can be used within a Procedure to cause the Procedure to exit without completing the rest of the commands contained in the Procedure. This can be useful if a certain value is outside the expected range. In the example below, a Procedure is defined with a Spacecraft as an argument. If the eccentricity of the Spacecraft's orbit is greater than 0.2, the Procedure will exit; otherwise it will continue through the Procedure.
Define Procedure ExitProcTest(Spacecraft sc);
If (sc.E > 0.2);
ExitProcedure;
Else;
// Enter the desired script here...
End;
EndProcedure;
|
Details on Procedure Implementation
A few additional details of FreeFlyer's implementation of the Procedure functionality are worth noting:
•FreeFlyer only supports the typical Procedure functionality. This means that the algorithm within the Procedure cannot return a value. Although this might seem limiting at first, the approach for having a Procedure calculate or evaluate a return status is to simply pass an Argument to hold that data.
•All Arguments are passed into the Procedure using the "pass-by-reference" method. This means that the reference to an object in the Argument list is passed along to the Procedure, as opposed to having the Procedure instantiate a new copy of the object, and using that cloned object for the operations it performs. The effect of this is that any operation that a Procedure may execute on the list of Arguments will be seen after having completed execution of the Procedure. A simple example would be to change the color of the spacecraft based on the True Anomaly of its state.
•The local variables within a Procedure do not maintain their values between subsequent calls to the Procedure unless the Procedure is called inside of a For or While loop. In the example below, a Procedure is defined that creates a Variable a, reports a, and then sets its value to 5. When the Persistence Procedure is called within the For loop for the first time, the Report will show a value of 0 for a. Subsequent calls to the Persistence Procedure within the For loop will Report a value of 5 because a has persisted from the previous call (Arrow #1). The Call to Persistence outside of the For loop will create the Variable a and set its value to 0 (Arrow #2); the value will not persist from the previous instance of Persistence called inside of the For loop.
Define Procedure Persistence();
Variable a;
Report a;
a = 5;
EndProcedure;
Variable i;
For i = 0 to 2;
Call Persistence();
End;
Call Persistence();
|
Report output for Persistence procedure called within a For loop.
Report output for Persistence procedure called outside of a For loop.
•Each Call command has it's own copy of variables. Although variables in a Procedure are persistent across multiple iterations of a loop, as shown in the example above, they are not persistent between calls within the same iteration of the loop. If a Procedure is called two times within one iteration of a loop, then values will not persist between the two calls.
•There is no mechanism to prototype a Procedure. Prototyping provides a mechanism to specify the calling interface to a Procedure without having to define the executive behavior of the Procedure. The limitation of the lack of prototyping has two key consequences; one, a Procedure must be fully defined before it is referenced in FreeFlyer Script; two, since a Procedure must be fully defined before it is used, there is no way to support a recursive Procedure.
•FreeFlyer supports global Variables, Arrays, Strings, and StringArrays. See the Constant and Global Keywords article for more information.
Global Variable v;
Global Array a[0];
Global String Status;
Global StringArray State[6];
|
•When you pass an argument to a Procedure that evaluates to a string, number, array, or string array but is not a String object, Variable object, Array object, or StringArray object, FreeFlyer will create temporary objects with the same values you passed. However, if you do this, any changed values are NOT copied on the way back out. Using this method to provide input data is fine, but if you’re trying to get new data out of the Procedure, you’ll need to pass the actual object. The following example shows why it works this way:
Define Procedure example(Variable x);
x = 5;
EndProcedure;
|
Now, suppose this Procedure was called using an expression that evaluates to a variable, instead of an actual Variable object:
There is nowhere to assign the value, which is a problem. We allow users to pass compound expressions as inputs for convenience, but since FreeFlyer doesn’t determine if a particular argument to a procedure will be used as an input or an output, the user is responsible for passing an actual object for output arguments, and not any kind of expression. Another example of this behavior is shown in the ChangeName Procedure below.
Define Procedure ChangeName(String name);
name = "newName";
EndProcedure;
|
The Procedure takes in String as an argument. Calling the ChangeName Procedure with Spacecraft1.Name as the argument will not assign "newName" to Spacecraft1.Name outside the Procedure. Instead, a String object would need to be passed to the Procedure and Spacecraft1.Name would need to be set to that String after the Procedure call as in the second example below.
Call ChangeName(Spacecraft1.Name);
|
String SpacecraftName;
Call ChangeName(SpacecraftName);
Spacecraft1.Name = SpacecraftName;
|
•The @ symbol can be used to scan a literal string in an Include statement for environment variables and escape sequences. The $(EnvironmentVariableName) sequence inside a literal string indicates that the specified environment variable will be evaluated at parse time. While the Include command will always be evaluated at parse time, if you are using an environment variable for another purpose, you can also retrieve it at runtime using the FF_Preferences.GetEnvironmentVariable("EnvironmentVariableName") method. See the Parsing Arbitrary String Data page for more information.
Include @"$(myEnvironmentVariableToProcedureDirectory)\\myProcedure.FFProcedure";
|
|
More Examples
Example: Changing the color of a Spacecraft
The color of Spacecraft1 will change following the execution of the following Procedure:
Define Procedure sc_color_changer(Spacecraft spacecraft1);
If (spacecraft1.TA > 0 and spacecraft1.TA < 180);
spacecraft1.Color = ColorTools.Red;
Else;
spacecraft1.Color = ColorTools.Blue;
End;
EndProcedure;
|
Execute the Procedure in the Mission Sequence with a Call command:
Call sc_color_changer(Spacecraft1);
|
|
Example: Setting Spacecraft Orientation
This example illustrates how Procedures can be used to organize FreeFlyer Script into independent functional areas. This makes it easier to navigate when adding functionality or tracking down bugs that might arise.
Suppose you want to orient a spacecraft so that its z-axis is always pointing toward the sun if it is in sunlight and aligned with the LVLH frame if it is in shadow. The FreeFlyer Script might look like this:
Spacecraft Spacecraft1;
Variable in_shadow;
Array sc_to_sun_lvlh[3];
Define Procedure calc_sun_lvlh_vect(Spacecraft sc1, Array sc_to_sun_lvlh);
Variable ICRF = 0;
Variable LVLH = 2;
Vector sat_to_sun_pos;
sat_to_sun_pos.BuildVector(9, sc1, Sun);
sc_to_sun_lvlh = AttitudeConvert(ICRF, LVLH, sc1, sat_to_sun_pos.Element);
sc_to_sun_lvlh = sc_to_sun_lvlh.Normalized();
EndProcedure; // End Procedure;
Define Procedure orient_spacecraft(Spacecraft sc1, Variable in_shadow);
sc1.AttitudeRefFrame = "LVLH";
sc1.EulerSequence = {3, 1, 2};
Array sc_to_sun_lvlh[3];
If (in_shadow = 1);
sc1.EulerAngles = {0, 0, 0};
Else;
Call calc_sun_lvlh_vect(sc1,sc_to_sun_lvlh);
sc1.EulerAngles[0] = deg(atan2(sc_to_sun_lvlh[1], sc_to_sun_lvlh[0]));
sc1.EulerAngles[1] = 0;
sc1.EulerAngles[2] = deg(acos(sc_to_sun_lvlh[2]));
End;
EndProcedure; // End Procedure;
While (Spacecraft1.ElapsedTime < TIMESPAN(1 days));
Step Spacecraft1;
Call orient_spacecraft(Spacecraft1, Spacecraft1.EarthShadow);
View Spacecraft1;
End;
|
|
Example: Creating a Report
Suppose that you are to generate a data file reporting longitude and mean local times, along with a lot of formatted header information, at both ascending and descending nodes.
Spacecraft Spacecraft1;
ReportInterface ReportInterface1 using "node_xing.Report";
Define Procedure report_data(Spacecraft sc1, ReportInterface node_data);
Report '**************************************' to node_data without headers;
Report ' Longitude = ', sc1.Longitude,
' MLT = ', sc1.Mlt to node_data without headers;
Report '**************************************' to node_data without headers;
EndProcedure; // End Procedure;
While (Spacecraft1.ElapsedTime < TIMESPAN(1 days));
Step Spacecraft1 to (Spacecraft1.AscendingNode);
Call report_data(Spacecraft1, ReportInterface1);
End;
|
|
Example: Generating Ephemeris
You need to generate an ephemeris for n number of days for several operational spacecraft. The Mission Sequence for generating that ephemeris is identical for any given spacecraft, and the only difference is the initial spacecraft state.
Spacecraft EO1;
Spacecraft LS7;
Ephemeris EO1Ephemeris;
Ephemeris LS7Ephemeris;
Define Procedure sc_stepper(Spacecraft sc1, Ephemeris ephem1, TimeSpan duration);
While (sc1.ElapsedTime < duration);
Step sc1;
Map sc1;
Put sc1 to ephem1;
End;
EndProcedure; // End Procedure;
Call sc_stepper(EO1, EO1Ephemeris, TIMESPAN(7 days));
Put EO1Ephemeris to FFephem "EO1Ephemeris.ephem";
Call sc_stepper(LS7, LS7Ephemeris, TIMESPAN(7 days));
Put LS7Ephemeris to FFephem "LS7Ephemeris.ephem";
|
|
See Also
•Define Procedure Command
•Include Command
•Call Command
•Structs Script Reference
|