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 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
45 #include "input_output/FGXMLElement.h"
46 #include "input_output/FGXMLParse.h"
47 #include "initialization/FGTrim.h"
57 static const char *IdSrc = "$Id: FGScript.cpp,v 1.46 2011/02/18 12:44:16 jberndt Exp $";
58 static const char *IdHdr = ID_FGSCRIPT;
60 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
62 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
64 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
66 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
70 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
72 PropertyManager=FDMExec->GetPropertyManager();
77 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
83 for (i=0; i<local_properties.size(); i++) {
84 delete local_properties[i]->value;
85 delete local_properties[i];
87 local_properties.clear();
89 for (i=0; i<Events.size(); i++) {
90 delete Events[i].Condition;
91 for (j=0; j<Events[i].Functions.size(); j++)
92 delete Events[i].Functions[j];
99 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
101 bool FGScript::LoadScript(string script, double deltaT)
103 string aircraft="", initialize="", comparison = "", prop_name="";
104 string notifyPropertyName="";
105 Element *element=0, *run_element=0, *event_element=0;
106 Element *condition_element=0, *set_element=0, *delay_element=0;
107 Element *notify_element = 0L, *notify_property_element = 0L;
108 Element *property_element = 0L;
109 Element *output_element = 0L;
110 Element *input_element = 0L;
112 double dt = 0.0, value = 0.0;
113 struct event *newEvent;
114 FGCondition *newCondition;
116 document = LoadXMLDocument(script);
119 cerr << "File: " << script << " could not be loaded." << endl;
123 // Set up input and output files if specified
125 output_element = document->FindElement("output");
126 input_element = document->FindElement("input");
128 if (document->GetName() != string("runscript")) {
129 cerr << "File: " << script << " is not a script file" << endl;
133 ScriptName = document->GetAttributeValue("name");
135 // First, find "run" element and set delta T
137 run_element = document->FindElement("run");
140 cerr << "No \"run\" element found in script." << endl;
146 StartTime = run_element->GetAttributeValueAsNumber("start");
147 FDMExec->Setsim_time(StartTime);
148 EndTime = run_element->GetAttributeValueAsNumber("end");
149 // Make sure that the desired time is reached and executed.
150 EndTime += 0.99*FDMExec->GetDeltaT();
153 dt = run_element->GetAttributeValueAsNumber("dt");
156 cout << endl << "Overriding simulation step size from the command line. New step size is: "
157 << deltaT << " seconds (" << 1/deltaT << " Hz)" << endl << endl;
162 // read aircraft and initialization files
164 element = document->FindElement("use");
166 aircraft = element->GetAttributeValue("aircraft");
167 if (!aircraft.empty()) {
168 result = FDMExec->LoadModel(aircraft);
169 if (!result) return false;
171 cerr << "Aircraft must be specified in use element." << endl;
175 initialize = element->GetAttributeValue("initialize");
176 if (initialize.empty()) {
177 cerr << "Initialization file must be specified in use element." << endl;
182 cerr << "No \"use\" directives in the script file." << endl;
186 // Now, read input spec if given.
187 if (input_element > 0) {
188 FDMExec->GetInput()->Load(input_element);
191 // Now, read output spec if given.
192 if (output_element > 0) {
193 string output_file = output_element->GetAttributeValue("file");
194 if (output_file.empty()) {
195 cerr << "No logging directives file was specified." << endl;
197 if (!FDMExec->SetOutputDirectives(output_file)) return false;
201 // Read local property/value declarations
202 property_element = run_element->FindElement("property");
203 while (property_element) {
208 title = property_element->GetDataLine();
209 if ( ! property_element->GetAttributeValue("value").empty())
210 value = property_element->GetAttributeValueAsNumber("value");
212 LocalProps *localProp = new LocalProps(value);
213 localProp->title = title;
214 local_properties.push_back(localProp);
215 if (PropertyManager->HasNode(title)) {
216 PropertyManager->GetNode(title)->setDoubleValue(value);
218 PropertyManager->Tie(localProp->title, localProp->value);
220 property_element = run_element->FindNextElement("property");
223 // Read "events" from script
225 event_element = run_element->FindElement("event");
226 while (event_element) { // event processing
228 // Create the event structure
229 newEvent = new struct event();
231 // Retrieve the event name if given
232 newEvent->Name = event_element->GetAttributeValue("name");
234 // Is this event persistent? That is, does it execute every time the
235 // condition triggers to true, or does it execute as a one-shot event, only?
236 if (event_element->GetAttributeValue("persistent") == string("true")) {
237 newEvent->Persistent = true;
240 // Does this event execute continuously when triggered to true?
241 if (event_element->GetAttributeValue("continuous") == string("true")) {
242 newEvent->Continuous = true;
245 // Process the conditions
246 condition_element = event_element->FindElement("condition");
247 if (condition_element != 0) {
249 newCondition = new FGCondition(condition_element, PropertyManager);
250 } catch(string str) {
251 cout << endl << fgred << str << reset << endl << endl;
255 newEvent->Condition = newCondition;
257 cerr << "No condition specified in script event " << newEvent->Name << endl;
262 // Is there a delay between the time this event is triggered, and when the event
263 // actions are executed?
265 delay_element = event_element->FindElement("delay");
266 if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
267 else newEvent->Delay = 0.0;
269 // Notify about when this event is triggered?
270 if ((notify_element = event_element->FindElement("notify")) != 0) {
271 newEvent->Notify = true;
272 // Check here for new <description> tag that gets echoed
273 string notify_description = notify_element->FindElementValue("description");
274 if (!notify_description.empty()) {
275 newEvent->Description = notify_description;
277 notify_property_element = notify_element->FindElement("property");
278 while (notify_property_element) {
279 notifyPropertyName = notify_property_element->GetDataLine();
280 if (PropertyManager->GetNode(notifyPropertyName)) {
281 newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
282 string caption_attribute = notify_property_element->GetAttributeValue("caption");
283 if (caption_attribute.empty()) {
284 newEvent->DisplayString.push_back(notifyPropertyName);
286 newEvent->DisplayString.push_back(caption_attribute);
289 cout << endl << fgred << " Could not find the property named "
290 << notifyPropertyName << " in script" << endl << " \""
291 << ScriptName << "\". Execution is aborted. Please recheck "
292 << "your input files and scripts." << reset << endl;
293 delete newEvent->Condition;
297 notify_property_element = notify_element->FindNextElement("property");
301 // Read set definitions (these define the actions to be taken when the event is triggered).
302 set_element = event_element->FindElement("set");
303 while (set_element) {
304 prop_name = set_element->GetAttributeValue("name");
305 newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
306 //Todo - should probably do some safety checking here to make sure one or the other
307 //of value or function is specified.
308 if (!set_element->GetAttributeValue("value").empty()) {
309 value = set_element->GetAttributeValueAsNumber("value");
310 newEvent->Functions.push_back((FGFunction*)0L);
311 } else if (set_element->FindElement("function")) {
313 newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
315 newEvent->SetValue.push_back(value);
316 newEvent->OriginalValue.push_back(0.0);
317 newEvent->newValue.push_back(0.0);
318 newEvent->ValueSpan.push_back(0.0);
319 string tempCompare = set_element->GetAttributeValue("type");
320 if (to_lower(tempCompare).find("delta") != string::npos) newEvent->Type.push_back(FG_DELTA);
321 else if (to_lower(tempCompare).find("bool") != string::npos) newEvent->Type.push_back(FG_BOOL);
322 else if (to_lower(tempCompare).find("value") != string::npos) newEvent->Type.push_back(FG_VALUE);
323 else newEvent->Type.push_back(FG_VALUE); // DEFAULT
324 tempCompare = set_element->GetAttributeValue("action");
325 if (to_lower(tempCompare).find("ramp") != string::npos) newEvent->Action.push_back(FG_RAMP);
326 else if (to_lower(tempCompare).find("step") != string::npos) newEvent->Action.push_back(FG_STEP);
327 else if (to_lower(tempCompare).find("exp") != string::npos) newEvent->Action.push_back(FG_EXP);
328 else newEvent->Action.push_back(FG_STEP); // DEFAULT
330 if (!set_element->GetAttributeValue("tc").empty())
331 newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
333 newEvent->TC.push_back(1.0); // DEFAULT
335 newEvent->Transiting.push_back(false);
337 set_element = event_element->FindNextElement("set");
339 Events.push_back(*newEvent);
342 event_element = run_element->FindNextElement("event");
347 FGInitialCondition *IC=FDMExec->GetIC();
348 if ( ! IC->Load( initialize )) {
349 cerr << "Initialization unsuccessful" << endl;
356 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
358 bool FGScript::RunScript(void)
361 unsigned event_ctr = 0;
363 double currentTime = FDMExec->GetSimTime();
364 double newSetValue = 0;
366 if (currentTime > EndTime) return false;
368 // Iterate over all events.
369 for (unsigned int ev_ctr=0; ev_ctr < Events.size(); 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 (Events[ev_ctr].Condition->Evaluate()) {
376 if (!Events[ev_ctr].Triggered) {
378 // The conditions are true, do the setting of the desired Event parameters
379 for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
380 Events[ev_ctr].OriginalValue[i] = Events[ev_ctr].SetParam[i]->getDoubleValue();
381 if (Events[ev_ctr].Functions[i] != 0) { // Parameter should be set to a function value
382 Events[ev_ctr].SetValue[i] = Events[ev_ctr].Functions[i]->GetValue();
384 switch (Events[ev_ctr].Type[i]) {
387 Events[ev_ctr].newValue[i] = Events[ev_ctr].SetValue[i];
390 Events[ev_ctr].newValue[i] = Events[ev_ctr].OriginalValue[i] + Events[ev_ctr].SetValue[i];
393 cerr << "Invalid Type specified" << endl;
396 Events[ev_ctr].StartTime = currentTime + Events[ev_ctr].Delay;
397 Events[ev_ctr].ValueSpan[i] = Events[ev_ctr].newValue[i] - Events[ev_ctr].OriginalValue[i];
398 Events[ev_ctr].Transiting[i] = true;
401 Events[ev_ctr].Triggered = true;
403 } else if (Events[ev_ctr].Persistent) { // If the event is persistent, reset the trigger.
404 Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
405 Events[ev_ctr].Notified = false; // Also reset the notification flag
406 } else if (Events[ev_ctr].Continuous) { // If the event is continuous, reset the trigger.
407 Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
408 Events[ev_ctr].Notified = false; // Also reset the notification flag
411 if ((currentTime >= Events[ev_ctr].StartTime) && Events[ev_ctr].Triggered) {
413 for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
414 if (Events[ev_ctr].Transiting[i]) {
415 Events[ev_ctr].TimeSpan = currentTime - Events[ev_ctr].StartTime;
416 switch (Events[ev_ctr].Action[i]) {
418 if (Events[ev_ctr].TimeSpan <= Events[ev_ctr].TC[i]) {
419 newSetValue = Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
421 newSetValue = Events[ev_ctr].newValue[i];
422 if (Events[ev_ctr].Continuous != true) Events[ev_ctr].Transiting[i] = false;
426 newSetValue = Events[ev_ctr].newValue[i];
428 // If this is not a continuous event, reset the transiting flag.
429 // Otherwise, it is known that the event is a continuous event.
430 // Furthermore, if the event is to be determined by a function,
431 // then the function will be continuously calculated.
432 if (Events[ev_ctr].Continuous != true)
433 Events[ev_ctr].Transiting[i] = false;
434 else if (Events[ev_ctr].Functions[i] != 0)
435 newSetValue = Events[ev_ctr].Functions[i]->GetValue();
439 newSetValue = (1 - exp( -Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] )) * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
442 cerr << "Invalid Action specified" << endl;
445 Events[ev_ctr].SetParam[i]->setDoubleValue(newSetValue);
449 // Print notification values after setting them
450 if (Events[ev_ctr].Notify && !Events[ev_ctr].Notified) {
451 cout << endl << " Event " << event_ctr << " (" << Events[ev_ctr].Name << ")"
452 << " executed at time: " << currentTime << endl;
453 if (!Events[ev_ctr].Description.empty()) {
454 cout << " " << Events[ev_ctr].Description << endl;
456 for (j=0; j<Events[ev_ctr].NotifyProperties.size();j++) {
457 // cout << " " << Events[ev_ctr].NotifyProperties[j]->GetRelativeName()
458 cout << " " << Events[ev_ctr].DisplayString[j]
459 << " = " << Events[ev_ctr].NotifyProperties[j]->getDoubleValue() << endl;
462 Events[ev_ctr].Notified = true;
472 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
473 // The bitmasked value choices are as follows:
474 // unset: In this case (the default) JSBSim would only print
475 // out the normally expected messages, essentially echoing
476 // the config files as they are read. If the environment
477 // variable is not set, debug_lvl is set to 1 internally
478 // 0: This requests JSBSim not to output any messages
480 // 1: This value explicity requests the normal JSBSim
482 // 2: This value asks for a message to be printed out when
483 // a class is instantiated
484 // 4: When this value is set, a message is displayed when a
485 // FGModel object executes its Run() method
486 // 8: When this value is set, various runtime state variables
487 // are printed out periodically
488 // 16: When set various parameters are sanity checked and
489 // a message is printed out when they go out of bounds
491 void FGScript::Debug(int from)
493 if (debug_lvl <= 0) return;
495 if (debug_lvl & 1) { // Standard console startup message output
496 if (from == 0) { // Constructor
497 } else if (from == 3) {
498 } else if (from == 4) { // print out script data
500 cout << "Script: \"" << ScriptName << "\"" << endl;
501 cout << " begins at " << StartTime << " seconds and runs to " << EndTime
502 << " seconds with dt = " << setprecision(6) << FDMExec->GetDeltaT() << " (" <<
503 ceil(1.0/FDMExec->GetDeltaT()) << " Hz)" << endl;
506 for (unsigned int i=0; i<local_properties.size(); i++) {
507 cout << "Local property: " << local_properties[i]->title
508 << " = " << PropertyManager->GetNode(local_properties[i]->title)->getDoubleValue()
512 if (local_properties.size() > 0) cout << endl;
514 for (unsigned i=0; i<Events.size(); i++) {
515 cout << "Event " << i;
516 if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
519 if (Events[i].Persistent)
520 cout << " " << "Whenever triggered, executes once";
521 else if (Events[i].Continuous)
522 cout << " " << "While true, always executes";
524 cout << " " << "When first triggered, executes once";
526 Events[i].Condition->PrintCondition();
528 cout << endl << " Actions taken";
529 if (Events[i].Delay > 0.0)
530 cout << " (after a delay of " << Events[i].Delay << " secs)";
531 cout << ":" << endl << " {";
532 for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
533 if (Events[i].SetValue[j] == 0.0 && Events[i].Functions[j] != 0L) {
534 if (Events[i].SetParam[j] == 0) {
535 cerr << fgred << highint << endl
536 << " An attempt has been made to access a non-existent property" << endl
537 << " in this event. Please check the property names used, spelling, etc."
541 cout << endl << " set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
542 << " to function value";
544 if (Events[i].SetParam[j] == 0) {
545 cerr << fgred << highint << endl
546 << " An attempt has been made to access a non-existent property" << endl
547 << " in this event. Please check the property names used, spelling, etc."
551 cout << endl << " set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
552 << " to " << Events[i].SetValue[j];
555 switch (Events[i].Type[j]) {
558 cout << " (constant";
564 cout << " (unspecified type";
567 switch (Events[i].Action[j]) {
572 cout << " via step)";
575 cout << " via exponential approach";
578 cout << " via unspecified action)";
581 if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
582 cout << " with time constant " << Events[i].TC[j] << ")";
584 cout << endl << " }" << endl;
586 // Print notifications
587 if (Events[i].Notify) {
588 if (Events[i].NotifyProperties.size() > 0) {
589 cout << " Notifications" << ":" << endl << " {" << endl;
590 for (unsigned j=0; j<Events[i].NotifyProperties.size();j++) {
592 << Events[i].NotifyProperties[j]->GetRelativeName("/fdm/jsbsim/")
595 cout << " }" << endl;
602 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
603 if (from == 0) cout << "Instantiated: FGScript" << endl;
604 if (from == 1) cout << "Destroyed: FGScript" << endl;
606 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
608 if (debug_lvl & 8 ) { // Runtime state variables
610 if (debug_lvl & 16) { // Sanity checking
612 if (debug_lvl & 64) {
613 if (from == 0) { // Constructor
614 cout << IdSrc << endl;
615 cout << IdHdr << endl;