JSBSim Flight Dynamics Model 1.0 (23 February 2013)
An Open Source Flight Dynamics and Control Software Library in C++

FGScript Class Reference

Encapsulates the JSBSim scripting capability. More...

#include <FGScript.h>

Inheritance diagram for FGScript:
Collaboration diagram for FGScript:

List of all members.

Classes

struct  event
struct  LocalProps

Public Member Functions

 FGScript (FGFDMExec *exec)
 Default constructor.
 ~FGScript ()
 Default destructor.
bool LoadScript (string script, double deltaT, const string initfile)
 Loads a script to drive JSBSim (usually in standalone mode).
void ResetEvents (void)
bool RunScript (void)
 This function is called each pass through the executive Run() method IF scripting is enabled.

Detailed Description

Scripting support provided via FGScript.

There is support for scripting provided in the FGScript class. Commands are specified using the Scripting Directives for JSBSim. The script file is in XML format. A test condition (or conditions) can be set up in an event in a script and when the condition evaluates to true, the specified action[s] is/are taken. An event can be persistent, meaning that at every time the test condition first evaluates to true (toggling from false to true) then the specified set actions take place. An event can also be defined to execute or evaluate continuously while the condition is true. When the set of tests evaluates to true for a given condition, an item may be set to another value. This value may be a value, or a delta value, and the change from the current value to the new value can be either via a step action, a ramp, or an exponential approach. The speed of a ramp or exponential approach is specified via the time constant. Here is an example illustrating the format of the script file:

<?xml version="1.0"?>
<runscript name="C172-01A takeoff run">
  <!--
    This run is for testing the C172 altitude hold autopilot
  -->

  <use aircraft="c172x"/>
  <use initialize="reset00"/>
  <run start="0.0" end="3000" dt="0.0083333">

    <event name="engine start">
      <notify/>
      <condition>
        sim-time-sec >= 0.25
      </condition>
      <set name="fcs/throttle-cmd-norm" value="1.0" action="FG_RAMP" tc ="0.5"/>
      <set name="fcs/mixture-cmd-norm" value="0.87" action="FG_RAMP" tc ="0.5"/>
      <set name="propulsion/magneto_cmd" value="3"/>
      <set name="propulsion/starter_cmd" value="1"/>
    </event>

    <event name="set heading hold">
      <!-- Set Heading when reach 5 ft -->
      <notify/>
      <condition>
        position/h-agl-ft >= 5
      </condition>
      <set name="ap/heading_setpoint" value="200"/>
      <set name="ap/attitude_hold" value="0"/>
      <set name="ap/heading_hold" value="1"/>
    </event>

    <event name="set autopilot">
      <!-- Set Autopilot for 20 ft -->
      <notify/>
      <condition>
        aero/qbar-psf >= 4
      </condition>
      <set name="ap/altitude_setpoint" value="100.0" action="FG_EXP" tc ="2.0"/>
      <set name="ap/altitude_hold" value="1"/>
      <set name="fcs/flap-cmd-norm" value=".33"/>
    </event>

    <event name="set autopilot 2" persistent="true">
      <!-- Set Autopilot for 6000 ft -->
      <notify/>
      <condition>
        aero/qbar-psf > 5
      </condition>
      <set name="ap/altitude_setpoint" value="6000.0"/>
    </event>

    <event name="Time Notify">
      <notify/>
      <condition> sim-time-sec >= 500 </condition>
    </event>

    <event name="Time Notify">
      <notify/>
      <condition> sim-time-sec >= 1000 </condition>
    </event>

  </run>

</runscript>

The first line must always be present - it identifies the file as an XML format file. The second line identifies this file as a script file, and gives a descriptive name to the script file. Comments are next, delineated by the <!-- and --> symbols. The aircraft and initialization files to be used are specified in the "use" lines. Next, comes the "run" section, where the conditions are described in "event" clauses.

Author:
Jon S. Berndt
Version:
"$Id: FGScript.h,v 1.23 2013/01/26 17:06:49 bcoconni Exp $"

Definition at line 168 of file FGScript.h.


Member Function Documentation

bool LoadScript ( string  script,
double  deltaT,
const string  initfile 
)

The language is the Script Directives for JSBSim. If a simulation step size has been supplied on the command line, it will be override the script- specified simulation step size.

Parameters:
scriptthe filename (including path name, if any) for the script.
deltaTa simulation step size.
initfileAn optional initialization file name passed in, empty by default. If a file name is passed in, it will override the one present in the script.
Returns:
true if successful

Definition at line 102 of file FGScript.cpp.

References FGJSBBase::fgred, Element::FindElement(), Element::FindElementValue(), Element::FindNextElement(), Element::GetAttributeValue(), Element::GetAttributeValueAsNumber(), Element::GetDataLine(), FGFDMExec::GetDeltaT(), FGFDMExec::GetIC(), FGFDMExec::GetInput(), Element::GetName(), FGInitialCondition::Load(), FGInput::Load(), FGFDMExec::LoadModel(), FGJSBBase::reset, FGFDMExec::Setdt(), FGFDMExec::SetOutputDirectives(), FGFDMExec::Setsim_time(), and FGPropertyManager::Tie().

Referenced by FGFDMExec::LoadScript().

{
  string aircraft="", initialize="", comparison = "", prop_name="";
  string notifyPropertyName="";
  Element *element=0, *run_element=0, *event_element=0;
  Element *condition_element=0, *set_element=0, *delay_element=0;
  Element *notify_element = 0L, *notify_property_element = 0L;
  Element *property_element = 0L;
  Element *output_element = 0L;
  Element *input_element = 0L;
  bool result = false;
  double dt = 0.0, value = 0.0;
  struct event *newEvent;
  FGCondition *newCondition;

  document = LoadXMLDocument(script);

  if (!document) {
    cerr << "File: " << script << " could not be loaded." << endl;
    return false;
  }

  // Set up input and output files if specified
  
  output_element = document->FindElement("output");
  input_element = document->FindElement("input");

  if (document->GetName() != string("runscript")) {
    cerr << "File: " << script << " is not a script file" << endl;
    return false;
  }

  ScriptName = document->GetAttributeValue("name");

 // First, find "run" element and set delta T

  run_element = document->FindElement("run");

  if (!run_element) {
    cerr << "No \"run\" element found in script." << endl;
    return false;
  }

  // Set sim timing

  StartTime = run_element->GetAttributeValueAsNumber("start");
  FDMExec->Setsim_time(StartTime);
  EndTime   = run_element->GetAttributeValueAsNumber("end");
  // Make sure that the desired time is reached and executed.
  EndTime += 0.99*FDMExec->GetDeltaT();

  if (deltaT == 0.0)
    dt = run_element->GetAttributeValueAsNumber("dt");
  else {
    dt = deltaT;
    cout << endl << "Overriding simulation step size from the command line. New step size is: "
         << deltaT << " seconds (" << 1/deltaT << " Hz)" << endl << endl;
  }

  FDMExec->Setdt(dt);
  
  // read aircraft and initialization files

  element = document->FindElement("use");
  if (element) {
    aircraft = element->GetAttributeValue("aircraft");
    if (!aircraft.empty()) {
      result = FDMExec->LoadModel(aircraft);
      if (!result) return false;
    } else {
      cerr << "Aircraft must be specified in use element." << endl;
      return false;
    }

    initialize = element->GetAttributeValue("initialize");
    if (initfile.empty()) {
    if (initialize.empty()) {
      cerr << "Initialization file must be specified in use element." << endl;
      return false;
      }
    } else {
      cout << endl << "The initialization file specified in the script file (" << initialize
                   << ") has been overridden with a specified file (" << initfile << ")." << endl;
      initialize = initfile;
    }

  } else {
    cerr << "No \"use\" directives in the script file." << endl;
    return false;
  }

  // Now, read input spec if given.
  if (input_element > 0) {
    FDMExec->GetInput()->Load(input_element);
  }

  // Now, read output spec if given.
  if (output_element > 0) {
    string output_file = output_element->GetAttributeValue("file");
    if (output_file.empty()) {
      cerr << "No logging directives file was specified." << endl;
    } else {
      if (!FDMExec->SetOutputDirectives(output_file)) return false;
    }
  }

  // Read local property/value declarations
  property_element = run_element->FindElement("property");
  while (property_element) {

    double value=0.0;
    string title="";

    title = property_element->GetDataLine();
    if ( ! property_element->GetAttributeValue("value").empty())
      value = property_element->GetAttributeValueAsNumber("value");

    LocalProps *localProp = new LocalProps(value);
    localProp->title = title;
    local_properties.push_back(localProp);
    if (PropertyManager->HasNode(title)) {
      PropertyManager->GetNode(title)->setDoubleValue(value);
    } else {
      PropertyManager->Tie(localProp->title, localProp->value);
    }
    property_element = run_element->FindNextElement("property");
  }

  // Read "events" from script

  event_element = run_element->FindElement("event");
  while (event_element) { // event processing

    // Create the event structure
    newEvent = new struct event();

    // Retrieve the event name if given
    newEvent->Name = event_element->GetAttributeValue("name");

    // Is this event persistent? That is, does it execute every time the
    // condition triggers to true, or does it execute as a one-shot event, only?
    if (event_element->GetAttributeValue("persistent") == string("true")) {
      newEvent->Persistent = true;
    }

    // Does this event execute continuously when triggered to true?
    if (event_element->GetAttributeValue("continuous") == string("true")) {
      newEvent->Continuous = true;
    }

    // Process the conditions
    condition_element = event_element->FindElement("condition");
    if (condition_element != 0) {
      try {
        newCondition = new FGCondition(condition_element, PropertyManager);
      } catch(string str) {
        cout << endl << fgred << str << reset << endl << endl;
        delete newEvent;
        return false;
      }
      newEvent->Condition = newCondition;
    } else {
      cerr << "No condition specified in script event " << newEvent->Name << endl;
      delete newEvent;
      return false;
    }

    // Is there a delay between the time this event is triggered, and when the event
    // actions are executed?

    delay_element = event_element->FindElement("delay");
    if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
    else newEvent->Delay = 0.0;

    // Notify about when this event is triggered?
    if ((notify_element = event_element->FindElement("notify")) != 0) {
      newEvent->Notify = true;
      // Check here for new <description> tag that gets echoed
      string notify_description = notify_element->FindElementValue("description");
      if (!notify_description.empty()) {
        newEvent->Description = notify_description;
      }
      notify_property_element = notify_element->FindElement("property");
      while (notify_property_element) {
        notifyPropertyName = notify_property_element->GetDataLine();
        if (PropertyManager->GetNode(notifyPropertyName)) {
          newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
          string caption_attribute = notify_property_element->GetAttributeValue("caption");
          if (caption_attribute.empty()) {
            newEvent->DisplayString.push_back(notifyPropertyName);
          } else {
            newEvent->DisplayString.push_back(caption_attribute);
          }
        } else {
          cout << endl << fgred << "  Could not find the property named "
               << notifyPropertyName << " in script" << endl << "  \""
               << ScriptName << "\". Execution is aborted. Please recheck "
               << "your input files and scripts." << reset << endl;
          delete newEvent->Condition;
          delete newEvent;
          return false;
        }
        notify_property_element = notify_element->FindNextElement("property");
      }
    }

    // Read set definitions (these define the actions to be taken when the event is triggered).
    set_element = event_element->FindElement("set");
    while (set_element) {
      prop_name = set_element->GetAttributeValue("name");
      newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
      //Todo - should probably do some safety checking here to make sure one or the other
      //of value or function is specified.
      if (!set_element->GetAttributeValue("value").empty()) {
        value = set_element->GetAttributeValueAsNumber("value");
        newEvent->Functions.push_back((FGFunction*)0L);
      } else if (set_element->FindElement("function")) {
        value = 0.0;
        newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
      }
      newEvent->SetValue.push_back(value);
      newEvent->OriginalValue.push_back(0.0);
      newEvent->newValue.push_back(0.0);
      newEvent->ValueSpan.push_back(0.0);
      string tempCompare = set_element->GetAttributeValue("type");
      if      (to_lower(tempCompare).find("delta") != string::npos) newEvent->Type.push_back(FG_DELTA);
      else if (to_lower(tempCompare).find("bool") != string::npos)  newEvent->Type.push_back(FG_BOOL);
      else if (to_lower(tempCompare).find("value") != string::npos) newEvent->Type.push_back(FG_VALUE);
      else                                newEvent->Type.push_back(FG_VALUE); // DEFAULT
      tempCompare = set_element->GetAttributeValue("action");
      if      (to_lower(tempCompare).find("ramp") != string::npos) newEvent->Action.push_back(FG_RAMP);
      else if (to_lower(tempCompare).find("step") != string::npos) newEvent->Action.push_back(FG_STEP);
      else if (to_lower(tempCompare).find("exp") != string::npos) newEvent->Action.push_back(FG_EXP);
      else                               newEvent->Action.push_back(FG_STEP); // DEFAULT

      if (!set_element->GetAttributeValue("tc").empty())
        newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
      else
        newEvent->TC.push_back(1.0); // DEFAULT

      newEvent->Transiting.push_back(false);

      set_element = event_element->FindNextElement("set");
    }
    Events.push_back(*newEvent);
    delete newEvent;

    event_element = run_element->FindNextElement("event");
  }

  Debug(4);

  FGInitialCondition *IC=FDMExec->GetIC();
  if ( ! IC->Load( initialize )) {
    cerr << "Initialization unsuccessful" << endl;
    exit(-1);
  }

  return true;
}

Here is the call graph for this function:

Here is the caller graph for this function:

bool RunScript ( void  )
Returns:
false if script should exit (i.e. if time limits are violated

Definition at line 365 of file FGScript.cpp.

References FGFDMExec::GetSimTime().

Referenced by FGFDMExec::Run().

{
  unsigned i, j;
  unsigned event_ctr = 0;

  double currentTime = FDMExec->GetSimTime();
  double newSetValue = 0;

  if (currentTime > EndTime) return false;

  // Iterate over all events.
  for (unsigned int ev_ctr=0; ev_ctr < Events.size(); ev_ctr++) {
    // Determine whether the set of conditional tests for this condition equate
    // to true and should cause the event to execute. If the conditions evaluate 
    // to true, then the event is triggered. If the event is not persistent,
    // then this trigger will remain set true. If the event is persistent,
    // the trigger will reset to false when the condition evaluates to false.
    if (Events[ev_ctr].Condition->Evaluate()) {
      if (!Events[ev_ctr].Triggered) {

        // The conditions are true, do the setting of the desired Event parameters
        for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
          Events[ev_ctr].OriginalValue[i] = Events[ev_ctr].SetParam[i]->getDoubleValue();
          if (Events[ev_ctr].Functions[i] != 0) { // Parameter should be set to a function value
            try {
              Events[ev_ctr].SetValue[i] = Events[ev_ctr].Functions[i]->GetValue();
            } catch (string msg) {
              std::cerr << std::endl << "A problem occurred in the execution of the script. " << msg << endl;
              throw;
            }
          }
          switch (Events[ev_ctr].Type[i]) {
          case FG_VALUE:
          case FG_BOOL:
            Events[ev_ctr].newValue[i] = Events[ev_ctr].SetValue[i];
            break;
          case FG_DELTA:
            Events[ev_ctr].newValue[i] = Events[ev_ctr].OriginalValue[i] + Events[ev_ctr].SetValue[i];
            break;
          default:
            cerr << "Invalid Type specified" << endl;
            break;
          }
          Events[ev_ctr].StartTime = currentTime + Events[ev_ctr].Delay;
          Events[ev_ctr].ValueSpan[i] = Events[ev_ctr].newValue[i] - Events[ev_ctr].OriginalValue[i];
          Events[ev_ctr].Transiting[i] = true;
        }
      }
      Events[ev_ctr].Triggered = true;

    } else if (Events[ev_ctr].Persistent) { // If the event is persistent, reset the trigger.
      Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
      Events[ev_ctr].Notified = false;  // Also reset the notification flag
    } else if (Events[ev_ctr].Continuous) { // If the event is continuous, reset the trigger.
      Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
      Events[ev_ctr].Notified = false;  // Also reset the notification flag
    }

    if ((currentTime >= Events[ev_ctr].StartTime) && Events[ev_ctr].Triggered) {

      for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
        if (Events[ev_ctr].Transiting[i]) {
          Events[ev_ctr].TimeSpan = currentTime - Events[ev_ctr].StartTime;
          switch (Events[ev_ctr].Action[i]) {
          case FG_RAMP:
            if (Events[ev_ctr].TimeSpan <= Events[ev_ctr].TC[i]) {
              newSetValue = Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
            } else {
              newSetValue = Events[ev_ctr].newValue[i];
              if (Events[ev_ctr].Continuous != true) Events[ev_ctr].Transiting[i] = false;
            }
            break;
          case FG_STEP:
            newSetValue = Events[ev_ctr].newValue[i];

            // If this is not a continuous event, reset the transiting flag.
            // Otherwise, it is known that the event is a continuous event.
            // Furthermore, if the event is to be determined by a function,
            // then the function will be continuously calculated.
            if (Events[ev_ctr].Continuous != true)
              Events[ev_ctr].Transiting[i] = false;
            else if (Events[ev_ctr].Functions[i] != 0)
              newSetValue = Events[ev_ctr].Functions[i]->GetValue();

            break;
          case FG_EXP:
            newSetValue = (1 - exp( -Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] )) * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
            break;
          default:
            cerr << "Invalid Action specified" << endl;
            break;
          }
          Events[ev_ctr].SetParam[i]->setDoubleValue(newSetValue);
        }
      }

      // Print notification values after setting them
      if (Events[ev_ctr].Notify && !Events[ev_ctr].Notified) {
        cout << endl << "  Event " << event_ctr << " (" << Events[ev_ctr].Name << ")"
             << " executed at time: " << currentTime << endl;
        if (!Events[ev_ctr].Description.empty()) {
          cout << "    " << Events[ev_ctr].Description << endl;
        }
        for (j=0; j<Events[ev_ctr].NotifyProperties.size();j++) {
//          cout << "    " << Events[ev_ctr].NotifyProperties[j]->GetRelativeName()
          cout << "    " << Events[ev_ctr].DisplayString[j]
               << " = " << Events[ev_ctr].NotifyProperties[j]->getDoubleValue() << endl;
        }
        cout << endl;
        Events[ev_ctr].Notified = true;
      }

    }

    event_ctr++;
  }
  return true;
}

Here is the call graph for this function:

Here is the caller graph for this function:


The documentation for this class was generated from the following files: