1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 Purpose: Loads and runs JSBSim scripts.
8 ------------- Copyright (C) 1999 Jon S. Berndt (jon@jsbsim.org) -------------
10 This program is free software; you can redistribute it and/or modify it under
11 the terms of the GNU Lesser General Public License as published by the Free Software
12 Foundation; either version 2 of the License, or (at your option) any later
15 This program is distributed in the hope that it will be useful, but WITHOUT
16 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
20 You should have received a copy of the GNU Lesser General Public License along with
21 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
22 Place - Suite 330, Boston, MA 02111-1307, USA.
24 Further information about the GNU Lesser General Public License can also be found on
25 the world wide web at http://www.gnu.org.
27 FUNCTIONAL DESCRIPTION
28 --------------------------------------------------------------------------------
30 This class wraps up the simulation scripting routines.
33 --------------------------------------------------------------------------------
36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37 COMMENTS, REFERENCES, and NOTES
38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
42 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
49 #include "input_output/FGXMLElement.h"
50 #include "input_output/FGXMLFileRead.h"
51 #include "initialization/FGTrim.h"
52 #include "models/FGInput.h"
58 IDENT(IdSrc,"$Id: FGScript.cpp,v 1.56 2014/01/13 10:46:01 ehofman Exp $");
59 IDENT(IdHdr,ID_FGSCRIPT);
61 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
63 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
65 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
67 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
71 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
73 PropertyManager=FDMExec->GetPropertyManager();
78 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
84 for (i=0; i<Events.size(); i++) {
85 delete Events[i].Condition;
86 for (j=0; j<Events[i].Functions.size(); j++)
87 delete Events[i].Functions[j];
94 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
96 bool FGScript::LoadScript(string script, double deltaT, const string initfile)
98 string aircraft="", initialize="", comparison = "", prop_name="";
99 string notifyPropertyName="";
100 Element *element=0, *run_element=0, *event_element=0;
101 Element *condition_element=0, *set_element=0, *delay_element=0;
102 Element *notify_element = 0L, *notify_property_element = 0L;
103 Element *output_element = 0L;
104 Element *input_element = 0L;
106 double dt = 0.0, value = 0.0;
107 struct event *newEvent;
108 FGCondition *newCondition;
110 FGXMLFileRead XMLFileRead;
111 Element* document = XMLFileRead.LoadXMLDocument(script);
114 cerr << "File: " << script << " could not be loaded." << endl;
118 // Set up input and output files if specified
120 output_element = document->FindElement("output");
121 input_element = document->FindElement("input");
123 if (document->GetName() != string("runscript")) {
124 cerr << "File: " << script << " is not a script file" << endl;
128 ScriptName = document->GetAttributeValue("name");
130 // First, find "run" element and set delta T
132 run_element = document->FindElement("run");
135 cerr << "No \"run\" element found in script." << endl;
141 StartTime = run_element->GetAttributeValueAsNumber("start");
142 FDMExec->Setsim_time(StartTime);
143 EndTime = run_element->GetAttributeValueAsNumber("end");
144 // Make sure that the desired time is reached and executed.
145 EndTime += 0.99*FDMExec->GetDeltaT();
148 dt = run_element->GetAttributeValueAsNumber("dt");
151 cout << endl << "Overriding simulation step size from the command line. New step size is: "
152 << deltaT << " seconds (" << 1/deltaT << " Hz)" << endl << endl;
157 // read aircraft and initialization files
159 element = document->FindElement("use");
161 aircraft = element->GetAttributeValue("aircraft");
162 if (!aircraft.empty()) {
163 result = FDMExec->LoadModel(aircraft);
164 if (!result) return false;
166 cerr << "Aircraft must be specified in use element." << endl;
170 initialize = element->GetAttributeValue("initialize");
171 if (initfile.empty()) {
172 if (initialize.empty()) {
173 cerr << "Initialization file must be specified in use element." << endl;
177 cout << endl << "The initialization file specified in the script file (" << initialize
178 << ") has been overridden with a specified file (" << initfile << ")." << endl;
179 initialize = initfile;
183 cerr << "No \"use\" directives in the script file." << endl;
187 // Now, read input spec if given.
188 if (input_element > 0) {
189 FDMExec->GetInput()->Load(input_element);
192 // Now, read output spec if given.
193 if (output_element > 0) {
194 string output_file = output_element->GetAttributeValue("file");
195 if (output_file.empty()) {
196 cerr << "No logging directives file was specified." << endl;
198 if (!FDMExec->SetOutputDirectives(output_file)) return false;
202 // Read local property/value declarations
203 int saved_debug_lvl = debug_lvl;
204 debug_lvl = 0; // Disable messages
205 LoadProperties(run_element, PropertyManager, true);
206 debug_lvl = saved_debug_lvl;
208 // Read "events" from script
210 event_element = run_element->FindElement("event");
211 while (event_element) { // event processing
213 // Create the event structure
214 newEvent = new struct event();
216 // Retrieve the event name if given
217 newEvent->Name = event_element->GetAttributeValue("name");
219 // Is this event persistent? That is, does it execute every time the
220 // condition triggers to true, or does it execute as a one-shot event, only?
221 if (event_element->GetAttributeValue("persistent") == string("true")) {
222 newEvent->Persistent = true;
225 // Does this event execute continuously when triggered to true?
226 if (event_element->GetAttributeValue("continuous") == string("true")) {
227 newEvent->Continuous = true;
230 // Process the conditions
231 condition_element = event_element->FindElement("condition");
232 if (condition_element != 0) {
234 newCondition = new FGCondition(condition_element, PropertyManager);
235 } catch(string str) {
236 cout << endl << fgred << str << reset << endl << endl;
240 newEvent->Condition = newCondition;
242 cerr << "No condition specified in script event " << newEvent->Name << endl;
247 // Is there a delay between the time this event is triggered, and when the event
248 // actions are executed?
250 delay_element = event_element->FindElement("delay");
251 if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
252 else newEvent->Delay = 0.0;
254 // Notify about when this event is triggered?
255 if ((notify_element = event_element->FindElement("notify")) != 0) {
256 if (notify_element->HasAttribute("format")) {
257 if (notify_element->GetAttributeValue("format") == "kml") newEvent->NotifyKML = true;
259 newEvent->Notify = true;
260 // Check here for new <description> tag that gets echoed
261 string notify_description = notify_element->FindElementValue("description");
262 if (!notify_description.empty()) {
263 newEvent->Description = notify_description;
265 notify_property_element = notify_element->FindElement("property");
266 while (notify_property_element) {
267 notifyPropertyName = notify_property_element->GetDataLine();
269 newEvent->NotifyPropertyNames.push_back(notifyPropertyName);
270 newEvent->NotifyProperties.push_back(0);
271 string caption_attribute = notify_property_element->GetAttributeValue("caption");
272 if (caption_attribute.empty()) {
273 newEvent->DisplayString.push_back(notifyPropertyName);
275 newEvent->DisplayString.push_back(caption_attribute);
278 notify_property_element = notify_element->FindNextElement("property");
282 // Read set definitions (these define the actions to be taken when the event is triggered).
283 set_element = event_element->FindElement("set");
284 while (set_element) {
285 prop_name = set_element->GetAttributeValue("name");
286 if (PropertyManager->HasNode(prop_name)) {
287 newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
289 newEvent->SetParam.push_back( 0L );
291 newEvent->SetParamName.push_back( prop_name );
293 //Todo - should probably do some safety checking here to make sure one or the other
294 //of value or function is specified.
295 if (!set_element->GetAttributeValue("value").empty()) {
296 value = set_element->GetAttributeValueAsNumber("value");
297 newEvent->Functions.push_back((FGFunction*)0L);
298 } else if (set_element->FindElement("function")) {
300 newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
302 newEvent->SetValue.push_back(value);
303 newEvent->OriginalValue.push_back(0.0);
304 newEvent->newValue.push_back(0.0);
305 newEvent->ValueSpan.push_back(0.0);
306 string tempCompare = set_element->GetAttributeValue("type");
307 if (to_lower(tempCompare).find("delta") != string::npos) newEvent->Type.push_back(FG_DELTA);
308 else if (to_lower(tempCompare).find("bool") != string::npos) newEvent->Type.push_back(FG_BOOL);
309 else if (to_lower(tempCompare).find("value") != string::npos) newEvent->Type.push_back(FG_VALUE);
310 else newEvent->Type.push_back(FG_VALUE); // DEFAULT
311 tempCompare = set_element->GetAttributeValue("action");
312 if (to_lower(tempCompare).find("ramp") != string::npos) newEvent->Action.push_back(FG_RAMP);
313 else if (to_lower(tempCompare).find("step") != string::npos) newEvent->Action.push_back(FG_STEP);
314 else if (to_lower(tempCompare).find("exp") != string::npos) newEvent->Action.push_back(FG_EXP);
315 else newEvent->Action.push_back(FG_STEP); // DEFAULT
317 if (!set_element->GetAttributeValue("tc").empty())
318 newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
320 newEvent->TC.push_back(1.0); // DEFAULT
322 newEvent->Transiting.push_back(false);
324 set_element = event_element->FindNextElement("set");
326 Events.push_back(*newEvent);
329 event_element = run_element->FindNextElement("event");
334 FGInitialCondition *IC=FDMExec->GetIC();
335 if ( ! IC->Load( initialize )) {
336 cerr << "Initialization unsuccessful" << endl;
343 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
345 void FGScript::ResetEvents(void)
349 for (unsigned int i=0; i<Events.size(); i++)
353 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
355 bool FGScript::RunScript(void)
358 unsigned event_ctr = 0;
360 double currentTime = FDMExec->GetSimTime();
361 double newSetValue = 0;
363 if (currentTime > EndTime) return false;
365 // Iterate over all events.
366 for (unsigned int ev_ctr=0; ev_ctr < Events.size(); ev_ctr++) {
368 struct event &thisEvent = Events[ev_ctr];
370 // Determine whether the set of conditional tests for this condition equate
371 // to true and should cause the event to execute. If the conditions evaluate
372 // to true, then the event is triggered. If the event is not persistent,
373 // then this trigger will remain set true. If the event is persistent,
374 // the trigger will reset to false when the condition evaluates to false.
375 if (thisEvent.Condition->Evaluate()) {
376 if (!thisEvent.Triggered) {
378 // The conditions are true, do the setting of the desired Event parameters
379 for (i=0; i<thisEvent.SetValue.size(); i++) {
380 if (thisEvent.SetParam[i] == 0L) { // Late bind property if necessary
381 if (PropertyManager->HasNode(thisEvent.SetParamName[i])) {
382 thisEvent.SetParam[i] = PropertyManager->GetNode(thisEvent.SetParamName[i]);
384 throw("No property, \""+thisEvent.SetParamName[i]+"\" is defined.");
387 thisEvent.OriginalValue[i] = thisEvent.SetParam[i]->getDoubleValue();
388 if (thisEvent.Functions[i] != 0) { // Parameter should be set to a function value
390 thisEvent.SetValue[i] = thisEvent.Functions[i]->GetValue();
391 } catch (string msg) {
392 std::cerr << std::endl << "A problem occurred in the execution of the script. " << msg << endl;
396 switch (thisEvent.Type[i]) {
399 thisEvent.newValue[i] = thisEvent.SetValue[i];
402 thisEvent.newValue[i] = thisEvent.OriginalValue[i] + thisEvent.SetValue[i];
405 cerr << "Invalid Type specified" << endl;
408 thisEvent.StartTime = currentTime + thisEvent.Delay;
409 thisEvent.ValueSpan[i] = thisEvent.newValue[i] - thisEvent.OriginalValue[i];
410 thisEvent.Transiting[i] = true;
413 thisEvent.Triggered = true;
415 } else if (thisEvent.Persistent) { // If the event is persistent, reset the trigger.
416 thisEvent.Triggered = false; // Reset the trigger for persistent events
417 thisEvent.Notified = false; // Also reset the notification flag
418 } else if (thisEvent.Continuous) { // If the event is continuous, reset the trigger.
419 thisEvent.Triggered = false; // Reset the trigger for persistent events
420 thisEvent.Notified = false; // Also reset the notification flag
423 if ((currentTime >= thisEvent.StartTime) && thisEvent.Triggered) {
425 for (i=0; i<thisEvent.SetValue.size(); i++) {
426 if (thisEvent.Transiting[i]) {
427 thisEvent.TimeSpan = currentTime - thisEvent.StartTime;
428 switch (thisEvent.Action[i]) {
430 if (thisEvent.TimeSpan <= thisEvent.TC[i]) {
431 newSetValue = thisEvent.TimeSpan/thisEvent.TC[i] * thisEvent.ValueSpan[i] + thisEvent.OriginalValue[i];
433 newSetValue = thisEvent.newValue[i];
434 if (thisEvent.Continuous != true) thisEvent.Transiting[i] = false;
438 newSetValue = thisEvent.newValue[i];
440 // If this is not a continuous event, reset the transiting flag.
441 // Otherwise, it is known that the event is a continuous event.
442 // Furthermore, if the event is to be determined by a function,
443 // then the function will be continuously calculated.
444 if (thisEvent.Continuous != true)
445 thisEvent.Transiting[i] = false;
446 else if (thisEvent.Functions[i] != 0)
447 newSetValue = thisEvent.Functions[i]->GetValue();
451 newSetValue = (1 - exp( -thisEvent.TimeSpan/thisEvent.TC[i] )) * thisEvent.ValueSpan[i] + thisEvent.OriginalValue[i];
454 cerr << "Invalid Action specified" << endl;
457 thisEvent.SetParam[i]->setDoubleValue(newSetValue);
461 // Print notification values after setting them
462 if (thisEvent.Notify && !thisEvent.Notified) {
463 if (thisEvent.NotifyKML) {
464 cout << endl << "<Placemark>" << endl;
465 cout << " <name> " << currentTime << " seconds" << " </name>" << endl;
466 cout << " <description>" << endl;
467 cout << " <![CDATA[" << endl;
468 cout << " <b>" << thisEvent.Name << " (Event " << event_ctr << ")" << " executed at time: " << currentTime << "</b><br/>" << endl;
470 cout << endl << underon
471 << highint << thisEvent.Name << normint << underoff
472 << " (Event " << event_ctr << ")"
473 << " executed at time: " << highint << currentTime << normint << endl;
475 if (!thisEvent.Description.empty()) {
476 cout << " " << thisEvent.Description << endl;
478 for (j=0; j<thisEvent.NotifyProperties.size();j++) {
479 if (thisEvent.NotifyProperties[j] == 0) {
480 if (PropertyManager->HasNode(thisEvent.NotifyPropertyNames[j])) {
481 thisEvent.NotifyProperties[j] = PropertyManager->GetNode(thisEvent.NotifyPropertyNames[j]);
483 throw("Could not find property named "+thisEvent.NotifyPropertyNames[j]+" in script.");
486 cout << " " << thisEvent.DisplayString[j] << " = " << thisEvent.NotifyProperties[j]->getDoubleValue();
487 if (thisEvent.NotifyKML) cout << " <br/>";
490 if (thisEvent.NotifyKML) {
491 cout << " ]]>" << endl;
492 cout << " </description>" << endl;
493 cout << " <Point>" << endl;
494 cout << " <altitudeMode> absolute </altitudeMode>" << endl;
495 cout << " <extrude> 1 </extrude>" << endl;
496 cout << " <coordinates>" << FDMExec->GetPropagate()->GetLongitudeDeg()
497 << "," << FDMExec->GetPropagate()->GetGeodLatitudeDeg()
498 << "," << FDMExec->GetPropagate()->GetAltitudeASLmeters() << "</coordinates>" << endl;
499 cout << " </Point>" << endl;
500 cout << "</Placemark>" << endl;
503 thisEvent.Notified = true;
513 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
514 // The bitmasked value choices are as follows:
515 // unset: In this case (the default) JSBSim would only print
516 // out the normally expected messages, essentially echoing
517 // the config files as they are read. If the environment
518 // variable is not set, debug_lvl is set to 1 internally
519 // 0: This requests JSBSim not to output any messages
521 // 1: This value explicity requests the normal JSBSim
523 // 2: This value asks for a message to be printed out when
524 // a class is instantiated
525 // 4: When this value is set, a message is displayed when a
526 // FGModel object executes its Run() method
527 // 8: When this value is set, various runtime state variables
528 // are printed out periodically
529 // 16: When set various parameters are sanity checked and
530 // a message is printed out when they go out of bounds
532 void FGScript::Debug(int from)
534 if (debug_lvl <= 0) return;
536 if (debug_lvl & 1) { // Standard console startup message output
537 if (from == 0) { // Constructor
538 } else if (from == 3) {
539 } else if (from == 4) { // print out script data
541 cout << "Script: \"" << ScriptName << "\"" << endl;
542 cout << " begins at " << StartTime << " seconds and runs to " << EndTime
543 << " seconds with dt = " << setprecision(6) << FDMExec->GetDeltaT() << " (" <<
544 ceil(1.0/FDMExec->GetDeltaT()) << " Hz)" << endl;
547 map<FGPropertyNode_ptr, double>::iterator it = interface_prop_initial_value.begin();
548 for (; it != interface_prop_initial_value.end(); ++it) {
549 FGPropertyNode* node = it->first;
550 cout << "Local property: " << node->GetName()
551 << " = " << node->getDoubleValue()
555 if (!interface_prop_initial_value.empty()) cout << endl;
557 for (unsigned i=0; i<Events.size(); i++) {
558 cout << "Event " << i;
559 if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
562 if (Events[i].Persistent)
563 cout << " " << "Whenever triggered, executes once";
564 else if (Events[i].Continuous)
565 cout << " " << "While true, always executes";
567 cout << " " << "When first triggered, executes once";
569 Events[i].Condition->PrintCondition();
571 cout << endl << " Actions taken";
572 if (Events[i].Delay > 0.0)
573 cout << " (after a delay of " << Events[i].Delay << " secs)";
574 cout << ":" << endl << " {";
575 for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
576 if (Events[i].SetValue[j] == 0.0 && Events[i].Functions[j] != 0L) {
577 if (Events[i].SetParam[j] == 0) {
578 if (Events[i].SetParamName[j].size() == 0) {
579 cerr << fgred << highint << endl
580 << " An attempt has been made to access a non-existent property" << endl
581 << " in this event. Please check the property names used, spelling, etc."
585 cout << endl << " set " << Events[i].SetParamName[j]
586 << " to function value (Late Bound)";
589 cout << endl << " set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
590 << " to function value";
593 if (Events[i].SetParam[j] == 0) {
594 if (Events[i].SetParamName[j].size() == 0) {
595 cerr << fgred << highint << endl
596 << " An attempt has been made to access a non-existent property" << endl
597 << " in this event. Please check the property names used, spelling, etc."
601 cout << endl << " set " << Events[i].SetParamName[j]
602 << " to function value (Late Bound)";
605 cout << endl << " set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
606 << " to " << Events[i].SetValue[j];
610 switch (Events[i].Type[j]) {
613 cout << " (constant";
619 cout << " (unspecified type";
622 switch (Events[i].Action[j]) {
627 cout << " via step)";
630 cout << " via exponential approach";
633 cout << " via unspecified action)";
636 if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
637 cout << " with time constant " << Events[i].TC[j] << ")";
639 cout << endl << " }" << endl;
641 // Print notifications
642 if (Events[i].Notify) {
643 if (Events[i].NotifyProperties.size() > 0) {
644 if (Events[i].NotifyKML) {
645 cout << " Notifications (KML Format):" << endl << " {" << endl;
647 cout << " Notifications:" << endl << " {" << endl;
649 for (unsigned j=0; j<Events[i].NotifyPropertyNames.size();j++) {
651 << Events[i].NotifyPropertyNames[j]
654 cout << " }" << endl;
661 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
662 if (from == 0) cout << "Instantiated: FGScript" << endl;
663 if (from == 1) cout << "Destroyed: FGScript" << endl;
665 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
667 if (debug_lvl & 8 ) { // Runtime state variables
669 if (debug_lvl & 16) { // Sanity checking
671 if (debug_lvl & 64) {
672 if (from == 0) { // Constructor
673 cout << IdSrc << endl;
674 cout << IdHdr << endl;