Using the Runtime API

Top  Previous  Next

A regular use-case for FreeFlyer is as a component within a larger ground system or set of tools built in support of end-to-end space-based mission design and operations. The runtime API, available only in the Mission tier of FreeFlyer, allows FreeFlyer instances to be closely integrated into these larger systems built using other programming languages, which ultimately enables developers to build their overarching driver applications around commonly used and familiar software engineering libraries. Four programming language interfaces are provided with the runtime API, and they are listed below.

 

C/C++ Interface

C# Interface

Java Interface

Python Interface

 

Note: The Java API interface (freeflyer-runtimeapi-3.0.0.jar) can be used directly or can be retrieved from Maven Central.

 

Note: The FreeFlyer runtime API can be used in MATLAB through use of the C# or Java interfaces. When used in this fashion, you will want to ensure that you call close in MATLAB to end the ff.exe process generated by the engine. When encountering an error in their MATLAB scripts, oftentimes users will forget this call and may unintentionally leave behind unhandled ff.exe processes.

 

Similar applications can be built with FreeFlyer using its command line interface, but whereas that interface was specifically designed for convenience while being run from the command line, the runtime API is specifically designed for convenience when being a part of a bigger application. Some challenges to overcome if you were to attempt to build a ground system using the command line interface follow.

 

Mission Plan input must be handled through file inputs or user-info arguments. For users running multiple simultaneous instances of FreeFlyer, some combination of the two must be used.

 

Long command lines are not well supported, which can create difficulties for arbitrarily-named file paths.

 

Retrieving data from a Mission Plan at different points of the Mission Plan's execution requires non-negligible adjustments to the Mission Plan itself and may also come with time costs from writing to files and then reading from those files in order to move the FreeFlyer outputs back to the calling application itself.

 

Thread and process creation management is a difficult concept for many developers, creating time costs for learning these tasks where needed and debugging costs for diagnosing issues with these types of programs once they manifest.

 

The runtime API addresses these challenges by allowing the user to directly move information between a Mission Plan and the calling application through expressions, allowing a user to pause a Mission Plan's execution by use of the ApiLabel keyword in script, and allowing a user to retrieve data from a Mission Plan even after it has finished execution. Additionally, the runtime API has functionality built into it to support easy asynchronous operations, allowing a user to operate multiple instances of FreeFlyer in tandem and then synchronize them at certain key points to pass data between instances.

 

Installation & Usage


The runtime API comes installed with FreeFlyer, and can be found in a user's My Documents folder. Provided within the Runtime API folder are all of the references for all supported language interfaces as well as examples for each. These samples are described in much greater detail in the Sample Applications page of the help file.

 

Licensing

Instances of one's FreeFlyer license are used when using the runtime API similarly to when just running FreeFlyer. The instance in the case of the runtime API instance, however, is tied to the RuntimeApiEngine class, described in greater detail below. When an instance of the RuntimeApiEngine class is instantiated, FreeFlyer is spun up and a single instance of the active FreeFlyer license is consumed. This action will also check out a seat from an associated server license if such a license configuration is in use. FreeFlyer will only relinquish that license instance when the RuntimeApiEngine class's memory is deallocated, such as follows in C#.

 

RuntimeApiEngine engine = new RuntimeApiEngine("FreeFlyerInstallDirectory"); // Consumes a license instance

engine.Dispose(); //Releases the used license instance

 

 

Getting Started


To get started with the API, we will create a simple interaction between a C# application and a Mission Plan in FreeFlyer. Although the C# interface is being used exclusively for this example, all of the concepts discussed work identically with the other interfaces, and even the code will look very similar because the algorithms we'll use will be identical. The Mission Plan will simply create a Spacecraft and propagate it, although we will use a few ApiLabel keywords so that the API can retrieve and supply data from and to the Mission Plan at different stages. The Mission Plan will look like the following snippet.

 

Note: All FreeFlyer functionality is available for use from within the runtime API, though the runtime API itself interfaces with FreeFler Mission Plans rather than exposing objects, functions, methods, and properties to the driver application itself.

 

Spacecraft sc;

 

ApiLabel "Set State";

 

While (sc.ElapsedTime < TIMESPAN(1 days));

    Step sc;

End;

 

ApiLabel "Get State";

 

With that Mission Plan created and saved to a location on your machine, we will then open up our integrated development environment for C# and create a new C# project. First, we will create an Example namespace with a program class and Main method all with the necessary using statements.

 

Note: Ensure that the bitness of the application you're working in matches the bitness of the FreeFlyer installation and API you're referencing.

 

using System;

using System.IO;

using System.Reflection;

using Aisolutions.FreeFlyer.RuntimeApi;

 

namespace Example

{

  class Program

  {

    static void Main(string[] args)

     {

 

     }

  }

}

 

Note the using line for Aisolutions.FreeFlyer.RuntimeApi. This line is where the code will get access to the runtime API's functionality. You will want to add the reference to the runtime API's source file, which is located in the FreeFlyer folder of your My Documents. A generalized path for Windows 7 is provided below that you can reference to find the necessary file.

 

\My Documents\FreeFlyer\FreeFlyer X.X.X.XXXXX (XX-Bit)\Runtime API\csharp\references\Aisolutions.FreeFlyer.RuntimeAPI.DotNetClient.dll

 

Once this reference is added, we can begin to code using the runtime API inside the Main function. We will first want to declare several variables in the C# script that will allow us to find the Mission Plan and choose the initial state of the FreeFlyer Spacecraft.

 

Note: When declaring paths and locating the FreeFlyer installation directory, you can take advantage of the environment variables and files that FreeFlyer generates when installed. In Windows, this takes the form of a FREEFLYER_64_INSTALL_DIRECTORY environment variable. In Linux, FreeFlyer creates a file called "active_ff_install" that contains the installation path of the active FreeFlyer version. Be mindful that the check we perform in the below code snippet requires that the bitness of the C# project that we're working on be set to match the bitness of FreeFlyer, otherwise it may look for an environment variable that is not present and return null.

 

double initialEpoch      = 25197.500393519;

double[] initialPosition = new double[] {-1400.90777805625, 6858.56290468965,  1080.92254153641};

double[] initialVelocity = new double[] {0.835109399282293, 1.32674441436118, -7.33600093128616};

 

string missionPlanPath   = "C:\\Users\\SomeUser\\Desktop\\TestApp.MissionPlan";

 

//If in Windows:

string ffInstallDir      = IntPtr.Size == 4

                          ?

                          Environment.GetEnvironmentVariable("FREEFLYER_64_INSTALL_DIRECTORY");

 

//If in Linux:

string activeInstallFilePath = "/usr/share/a.i. solutions, Inc/FreeFlyer/active_ff_install";

string ffInstallDir = File.ReadAllText(activeInstallFilePath).Trim(' ','\r','\n');

 

Note: If you are installing and uninstalling FreeFlyer versions on your machine, be aware that the environment variables used above will point to your most recent installation. In managed operational contexts, you should create your own environment variable and use that to point to the FreeFlyer installation directory you want to reference.

 

Now you will actually perform the API actions. In general, the following list of actions is what you will perform when using the API to work with a Mission Plan.

 

Create an API Engine

Load a Mission Plan or provide FreeFlyer script to the engine

Prepare the script, initializing all object browser objects and performing necessary start-up routines

Execute the script

Cleanup the script

 

As noted above, the runtime API allows the user to either load a Mission Plan file directly or to provide flat FreeFlyer script that will be executed as though it were a Mission Plan. The option to run script is important because it enables your application to dynamically build up FreeFlyer scripts to execute as needed based on application context, whereas dynamically building a Mission Plan file would be significantly more difficult. When script is run in this fashion, the engine doesn't have a Mission Plan to tell it what timing precision mode to use and so defaults to nanosecond precision. Millisecond timing precision mode can be used in this script context still by specifying it through the load script overload that takes a TimeSpanMode class as follows.

 

engine.LoadFreeFlyerScriptFromFile(myFreeFlyerScriptPath, TimeSpanMode.Millisecond);

engine.LoadFreeFlyerScriptFromString("Spacecraft sc; sc.Epoch = 25198;", currentDirectory, TimeSpanMode.Millisecond);

 

Note: When loading into an engine with FreeFlyer script instead of a Mission Plan (either via a file or string), the default data files that come with your FreeFlyer installation will be used, regardless of whether these have been changed through User Preferences. See the Data Files page for descriptions of these default data files.

 

In this specific case, we will be executing the Mission Plan in segments to reach the different ApiLabel keywords that we defined in the Mission Plan. We will input the state from the variables we defined at the beginning of this exercise once we reach the "Set State" ApiLabel, and will then retrieve the state and print it to standard out once we reach the "Get State" ApiLabel.

 

Note: Relative paths passed to the API are relative to the current directory of the host application. Relative paths in the Mission Plan itself are relative to the Mission Plan file location. If you are not using a Mission Plan and are instead using a script input to the engine via string, then the relative paths in that script will be relative to the specified directory.

 

try

{

  using (RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir))

  {

    FFTimeSpan initialEpoch = FFTimeSpan.FromWholeSecondsAndNanoseconds(86400, 0).Scale(25197.500393519); // Create initial epoch as an FFTimeSpan

 

     engine.LoadMissionPlanFromFile(missionPlanPath);

     engine.PrepareMissionPlan();

 

     engine.ExecuteUntilApiLabel("Set State");

 

     engine.SetExpressionTimeSpan("sc.Epoch", initialEpoch);

     engine.SetExpressionArray("sc.Position", initialPosition);

     engine.SetExpressionArray("sc.Velocity", initialVelocity);

 

     engine.ExecuteUntilApiLabel("Get State");

 

    Console.WriteLine("The final state is:");

    Console.WriteLine(engine.GetExpressionVariable("sc.Epoch.ToDays()").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Position[0]").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Position[1]").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Position[2]").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Velocity[0]").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Velocity[1]").ToString());

    Console.WriteLine(engine.GetExpressionVariable("sc.Velocity[2]").ToString());

 

     engine.CleanupMissionPlan();

  }

}

catch (RuntimeApiException e)

{

  Console.WriteLine(e.Message);

}

 

Note: In the place of the second ExecuteUntilApiLabel() call, we could have instead called ExecuteRemainingStatements() and still retrieved the state afterward. With the API, expressions can still be retrieved and handled even after the Mission Plan has finished executing all statements up until the point where CleanupMissionPlan() is called.

 

Note: A regular expression can be provided as the argument for ExecuteUntilApiLabel(). In this case, you can use GetLocation() to retrieve the ApiLabel that was reached.

 

At this point, you can build the example and run it and you will see the final state being written to console. If an error case is experienced, then the try block will catch it as a RuntimeApiException and will write the error message to the screen. For a more in-depth discussion of error handling with the runtime API, see the Error Handling page.

 

 

Engine Constructors


The RuntimeApiEngine class has a number of constructors available for use, each resulting in different behaviors in the runtime API. The general constructor used in earlier examples in this guide simply requires a file path to the FreeFlyer installation directory, while the other constructors, discussed below, ask for more information in order to perform specific functionality.

 

ConsoleOutputProcessingMethod

In many cases when using FreeFlyer Mission Plans, ConsoleWindow objects get used in order to provide diagnostic and upkeep information to the users executing the Mission Plans. If you were running these Mission Plans from the FreeFlyer GUI, you could add script to export the ConsoleWindow text to a file, or you could have it echo to standard out. When using the runtime API, however, it becomes useful to have access to the ConsoleWindow output from within the host application itself, hence the existence of this constructor. With this constructor, any ConsoleWindow object configured to echo its contents to standard out will have its contents captured by the RuntimeApiEngine as well.

 

//Redirects ConsoleWindow output to the host application

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, ConsoleOutputProcessingMethod.RedirectToHost)

 

//Redirects ConsoleWindow output to the runtime API to be retrieved by the GetConsoleOutput method

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, ConsoleOutputProcessingMethod.RedirectToRuntimeApi)

 

//Suppresses ConsoleWindow output

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, ConsoleOutputProcessingMethod.Suppress)

 

WindowedOutputMode

The FreeFlyer runtime API supports the generation of output from host applications using this constructor. This constructor, when used and set into appropriate modes for Windows applications, can generate output in a workspace that is fully configurable like if you were running it from the FreeFlyer GUI instead of the runtime API. The varying types of modes that can be used include a mode with a fully configurable workspace, a mode that only shows the OutputWindow objects unconstrained via the OutputLayoutManager, a mode that shows no output but enables the saving of images via FreeFlyer script, and a mode that doesn't have any output support at all. When running FreeFlyer in any of the output modes that generate output, all visualization libraries will be loaded and the start time of the FreeFlyer engine will be increased.

 

//Generates a full output workspace that the user can manipulate

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, WindowedOutputMode.GenerateOutputWindows)

 

//Hides the main workspace, but shows any unconstrained output windows

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, WindowedOutputMode.HideMainOutputWorkspace)

 

//Hides all output windows, but allows the saving of images via FreeFlyer script

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, WindowedOutputMode.GenerateImagesOnly)

 

//Suppresses the generation of output

RuntimeApiEngine engine = new RuntimeApiEngine(ffInstallDir, WindowedOutputMode.NoOutputWindowSupport)

 

For runtime API applications designed to work on the Linux operating system, it does not matter which constructor argument is used to generate output as the behavior is always the same. For Linux, the complete output workspace is unavailable and all windows will always be generated in an unconstrained fashion with remaining options ignored.

 

Note: Another constructor is present that allows the user to select both a ConsoleOutputProcessingMethod and a WindowedOutputMode for the same RuntimeApiEngine class with the full features of both.

 

 

Engine States


Engine states refer to the different states that a runtime API engine can be in, which plays a role in whether or not certain methods will function correctly or return the ErrorInvalidEngineStateForOperation exception type. There are four unique engine states, described in detail in the following table, along with the methods that can be used to move an engine from one state to another. Each of these states has some methods that are invalid in these states, and a few examples of such methods are provided. For a full listing of the methods that can be used from each state, see the autogenerated help documentation provided for each interface.

 

Engine State

Description

Invalid Methods in this State

Ways to put an Engine into this State

Unloaded

This is the beginning and end state of the engine during normal use, and this state indicates that it neither has script loaded nor is the script initialized for execution.

PrepareMissionPlan()

new RuntimeApiEngine()

CleanupMissionPlan()

Loaded

This state indicates that script has been successfully loaded into the engine, but it has not been prepared for actual use yet.

GetExpressionVariable()

SetExpressionVariable()

LoadMissionPlanFromFile()

Prepared

Being in the prepared state means that the engine has both been successfully loaded with script and that script has been successfully prepared for execution. From this state, actually running the Mission Plan or script that you provided the engine is possible.

SetUserInfoArguments()

PrepareMissionPlan()

Error

This state is entered into when a Mission Plan encounters a runtime error. In order to run the Mission Plan again, it would have to be put back into the prepared state by way of reloading the Mission Plan and then preparing it. From this state, expressions can be retrieved from the Mission Plan but cannot be assigned.

SetExpressionVariable()

Mission Plan Runtime Error

 

 

Troubleshooting


There are some commonly encountered user mistakes that occur more frequently than others, and so if something isn't working in your application while using the runtime API and the error messages aren't helping you as much as you need, consider referencing the following list to ensure that your setup is properly configured.

 

Ensure that the bitness of your application matches the bitness of the FreeFlyer from which you're using the runtime API references.

Make sure that you're using the correct path to the FreeFlyer installation on your machine. Remember that you can use the FREEFLYER_64_INSTALL_DIRECTORY environment variable to help with this, as seen in the above example.

Verify that the FreeFlyer installation you're pointing to is FreeFlyer version 7.0 or higher, as previous versions did not support the runtime API.

The runtime API only functions with a valid Mission-tier license of FreeFlyer, so an Engineer license would be insufficient.

Properly close all runtime API engines using the appropriate cleanup method when you're done with them. Not doing so may result in the unwanted buildup of temporary shared memory files based on your operating system.

 

If you cannot determine the issue with your application even after debugging it yourself, comparing the exceptions you're seeing to the exception result list, and checking against these common errors, please send an email to techsupport@ai-solutions.com in order to get additional help.

 

 

See Also


Application Program Interface