]> git.mxchange.org Git - flightgear.git/blob - src/FDM/JSBSim/input_output/FGScript.cpp
Merge branch 'next' of gitorious.org:fg/flightgear into next
[flightgear.git] / src / FDM / JSBSim / input_output / FGScript.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2
3  Module:       FGScript.cpp
4  Author:       Jon S. Berndt
5  Date started: 12/21/01
6  Purpose:      Loads and runs JSBSim scripts.
7
8  ------------- Copyright (C) 1999  Jon S. Berndt (jon@jsbsim.org) -------------
9
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
13  version.
14
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
18  details.
19
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.
23
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.
26
27 FUNCTIONAL DESCRIPTION
28 --------------------------------------------------------------------------------
29
30 This class wraps up the simulation scripting routines.
31
32 HISTORY
33 --------------------------------------------------------------------------------
34 12/21/01   JSB   Created
35
36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37 COMMENTS, REFERENCES,  and NOTES
38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
39
40 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
41 INCLUDES
42 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
43
44 #include "FGScript.h"
45 #include "input_output/FGXMLElement.h"
46 #include "input_output/FGXMLParse.h"
47 #include "initialization/FGTrim.h"
48
49 #include <iostream>
50 #include <cstdlib>
51 #include <iomanip>
52
53 using namespace std;
54
55 namespace JSBSim {
56
57 static const char *IdSrc = "$Id: FGScript.cpp,v 1.43 2011/01/16 15:27:34 jberndt Exp $";
58 static const char *IdHdr = ID_FGSCRIPT;
59
60 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61 GLOBAL DECLARATIONS
62 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
63
64 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
65 CLASS IMPLEMENTATION
66 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
67
68 // Constructor
69
70 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
71 {
72   PropertyManager=FDMExec->GetPropertyManager();
73
74   Debug(0);
75 }
76
77 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78
79 FGScript::~FGScript()
80 {
81   unsigned int i;
82
83   for (i=0; i<local_properties.size(); i++) delete local_properties[i];
84   local_properties.clear();
85
86   for (i=0; i<Events.size(); i++) delete Events[i].Condition;
87   Events.clear();
88
89   Debug(1);
90 }
91
92 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
93
94 bool FGScript::LoadScript(string script, double deltaT)
95 {
96   string aircraft="", initialize="", comparison = "", prop_name="";
97   string notifyPropertyName="";
98   Element *element=0, *run_element=0, *event_element=0;
99   Element *condition_element=0, *set_element=0, *delay_element=0;
100   Element *notify_element = 0L, *notify_property_element = 0L;
101   Element *property_element = 0L;
102   Element *output_element = 0L;
103   Element *input_element = 0L;
104   bool result = false;
105   double dt = 0.0, value = 0.0;
106   struct event *newEvent;
107   FGCondition *newCondition;
108
109   document = LoadXMLDocument(script);
110
111   if (!document) {
112     cerr << "File: " << script << " could not be loaded." << endl;
113     return false;
114   }
115
116   // Set up input and output files if specified
117   
118   output_element = document->FindElement("output");
119   input_element = document->FindElement("input");
120
121   if (document->GetName() != string("runscript")) {
122     cerr << "File: " << script << " is not a script file" << endl;
123     return false;
124   }
125
126   ScriptName = document->GetAttributeValue("name");
127
128  // First, find "run" element and set delta T
129
130   run_element = document->FindElement("run");
131
132   if (!run_element) {
133     cerr << "No \"run\" element found in script." << endl;
134     return false;
135   }
136
137   // Set sim timing
138
139   StartTime = run_element->GetAttributeValueAsNumber("start");
140   FDMExec->Setsim_time(StartTime);
141   EndTime   = run_element->GetAttributeValueAsNumber("end");
142
143   if (deltaT == 0.0)
144     dt = run_element->GetAttributeValueAsNumber("dt");
145   else {
146     dt = deltaT;
147     cout << endl << "Overriding simulation step size from the command line. New step size is: "
148          << deltaT << " seconds (" << 1/deltaT << " Hz)" << endl << endl;
149   }
150
151   FDMExec->Setdt(dt);
152   
153   // read aircraft and initialization files
154
155   element = document->FindElement("use");
156   if (element) {
157     aircraft = element->GetAttributeValue("aircraft");
158     if (!aircraft.empty()) {
159       result = FDMExec->LoadModel(aircraft);
160       if (!result) return false;
161     } else {
162       cerr << "Aircraft must be specified in use element." << endl;
163       return false;
164     }
165
166     initialize = element->GetAttributeValue("initialize");
167     if (initialize.empty()) {
168       cerr << "Initialization file must be specified in use element." << endl;
169       return false;
170     }
171
172   } else {
173     cerr << "No \"use\" directives in the script file." << endl;
174     return false;
175   }
176
177   // Now, read input spec if given.
178   if (input_element > 0) {
179     FDMExec->GetInput()->Load(input_element);
180   }
181
182   // Now, read output spec if given.
183   if (output_element > 0) {
184     string output_file = output_element->GetAttributeValue("file");
185     if (output_file.empty()) {
186       cerr << "No logging directives file was specified." << endl;
187     } else {
188       if (!FDMExec->SetOutputDirectives(output_file)) return false;
189     }
190   }
191
192   // Read local property/value declarations
193   property_element = run_element->FindElement("property");
194   while (property_element) {
195
196     double value=0.0;
197     string title="";
198
199     title = property_element->GetDataLine();
200     if ( ! property_element->GetAttributeValue("value").empty())
201       value = property_element->GetAttributeValueAsNumber("value");
202
203     LocalProps *localProp = new LocalProps(value);
204     localProp->title = title;
205     local_properties.push_back(localProp);
206     if (PropertyManager->HasNode(title)) {
207       PropertyManager->GetNode(title)->setDoubleValue(value);
208     } else {
209       PropertyManager->Tie(localProp->title, localProp->value);
210     }
211     property_element = run_element->FindNextElement("property");
212   }
213
214   // Read "events" from script
215
216   event_element = run_element->FindElement("event");
217   while (event_element) { // event processing
218
219     // Create the event structure
220     newEvent = new struct event();
221
222     // Retrieve the event name if given
223     newEvent->Name = event_element->GetAttributeValue("name");
224
225     // Is this event persistent? That is, does it execute every time the
226     // condition triggers to true, or does it execute as a one-shot event, only?
227     if (event_element->GetAttributeValue("persistent") == string("true")) {
228       newEvent->Persistent = true;
229     }
230
231     // Does this event execute continuously when triggered to true?
232     if (event_element->GetAttributeValue("continuous") == string("true")) {
233       newEvent->Continuous = true;
234     }
235
236     // Process the conditions
237     condition_element = event_element->FindElement("condition");
238     if (condition_element != 0) {
239       try {
240         newCondition = new FGCondition(condition_element, PropertyManager);
241       } catch(string str) {
242         cout << endl << fgred << str << reset << endl << endl;
243         return false;
244       }
245       newEvent->Condition = newCondition;
246     } else {
247       cerr << "No condition specified in script event " << newEvent->Name << endl;
248       return false;
249     }
250
251     // Is there a delay between the time this event is triggered, and when the event
252     // actions are executed?
253
254     delay_element = event_element->FindElement("delay");
255     if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
256     else newEvent->Delay = 0.0;
257
258     // Notify about when this event is triggered?
259     if ((notify_element = event_element->FindElement("notify")) != 0) {
260       newEvent->Notify = true;
261       notify_property_element = notify_element->FindElement("property");
262       while (notify_property_element) {
263         notifyPropertyName = notify_property_element->GetDataLine();
264         if (PropertyManager->GetNode(notifyPropertyName)) {
265           newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
266         } else {
267           cout << endl << fgred << "  Could not find the property named "
268                << notifyPropertyName << " in script" << endl << "  \""
269                << ScriptName << "\". Execution is aborted. Please recheck "
270                << "your input files and scripts." << reset << endl;
271           return false;
272         }
273         notify_property_element = notify_element->FindNextElement("property");
274       }
275     }
276
277     // Read set definitions (these define the actions to be taken when the event is triggered).
278     set_element = event_element->FindElement("set");
279     while (set_element) {
280       prop_name = set_element->GetAttributeValue("name");
281       newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
282       //Todo - should probably do some safety checking here to make sure one or the other
283       //of value or function is specified.
284       if (!set_element->GetAttributeValue("value").empty()) {
285         value = set_element->GetAttributeValueAsNumber("value");
286         newEvent->Functions.push_back((FGFunction*)0L);
287       } else if (set_element->FindElement("function")) {
288         value = 0.0;
289         newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
290       }
291       newEvent->SetValue.push_back(value);
292       newEvent->OriginalValue.push_back(0.0);
293       newEvent->newValue.push_back(0.0);
294       newEvent->ValueSpan.push_back(0.0);
295       string tempCompare = set_element->GetAttributeValue("type");
296       if      (to_lower(tempCompare).find("delta") != string::npos) newEvent->Type.push_back(FG_DELTA);
297       else if (to_lower(tempCompare).find("bool") != string::npos)  newEvent->Type.push_back(FG_BOOL);
298       else if (to_lower(tempCompare).find("value") != string::npos) newEvent->Type.push_back(FG_VALUE);
299       else                                newEvent->Type.push_back(FG_VALUE); // DEFAULT
300       tempCompare = set_element->GetAttributeValue("action");
301       if      (to_lower(tempCompare).find("ramp") != string::npos) newEvent->Action.push_back(FG_RAMP);
302       else if (to_lower(tempCompare).find("step") != string::npos) newEvent->Action.push_back(FG_STEP);
303       else if (to_lower(tempCompare).find("exp") != string::npos) newEvent->Action.push_back(FG_EXP);
304       else                               newEvent->Action.push_back(FG_STEP); // DEFAULT
305
306       if (!set_element->GetAttributeValue("tc").empty())
307         newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
308       else
309         newEvent->TC.push_back(1.0); // DEFAULT
310
311       newEvent->Transiting.push_back(false);
312
313       set_element = event_element->FindNextElement("set");
314     }
315     Events.push_back(*newEvent);
316     delete newEvent;
317
318     event_element = run_element->FindNextElement("event");
319   }
320
321   Debug(4);
322
323   FGInitialCondition *IC=FDMExec->GetIC();
324   if ( ! IC->Load( initialize )) {
325     cerr << "Initialization unsuccessful" << endl;
326     exit(-1);
327   }
328
329   return true;
330 }
331
332 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
333
334 bool FGScript::RunScript(void)
335 {
336   unsigned i, j;
337   unsigned event_ctr = 0;
338
339   double currentTime = FDMExec->GetSimTime();
340   double newSetValue = 0;
341
342   if (currentTime > EndTime) return false; //Script done!
343
344   // Iterate over all events.
345   for (unsigned int ev_ctr=0; ev_ctr < Events.size(); ev_ctr++) {
346     // Determine whether the set of conditional tests for this condition equate
347     // to true and should cause the event to execute. If the conditions evaluate 
348     // to true, then the event is triggered. If the event is not persistent,
349     // then this trigger will remain set true. If the event is persistent,
350     // the trigger will reset to false when the condition evaluates to false.
351     if (Events[ev_ctr].Condition->Evaluate()) {
352       if (!Events[ev_ctr].Triggered) {
353
354         // The conditions are true, do the setting of the desired Event parameters
355         for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
356           Events[ev_ctr].OriginalValue[i] = Events[ev_ctr].SetParam[i]->getDoubleValue();
357           if (Events[ev_ctr].Functions[i] != 0) { // Parameter should be set to a function value
358             Events[ev_ctr].SetValue[i] = Events[ev_ctr].Functions[i]->GetValue();
359           }
360           switch (Events[ev_ctr].Type[i]) {
361           case FG_VALUE:
362           case FG_BOOL:
363             Events[ev_ctr].newValue[i] = Events[ev_ctr].SetValue[i];
364             break;
365           case FG_DELTA:
366             Events[ev_ctr].newValue[i] = Events[ev_ctr].OriginalValue[i] + Events[ev_ctr].SetValue[i];
367             break;
368           default:
369             cerr << "Invalid Type specified" << endl;
370             break;
371           }
372           Events[ev_ctr].StartTime = currentTime + Events[ev_ctr].Delay;
373           Events[ev_ctr].ValueSpan[i] = Events[ev_ctr].newValue[i] - Events[ev_ctr].OriginalValue[i];
374           Events[ev_ctr].Transiting[i] = true;
375         }
376       }
377       Events[ev_ctr].Triggered = true;
378
379     } else if (Events[ev_ctr].Persistent) { // If the event is persistent, reset the trigger.
380       Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
381       Events[ev_ctr].Notified = false;  // Also reset the notification flag
382     } else if (Events[ev_ctr].Continuous) { // If the event is continuous, reset the trigger.
383       Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
384       Events[ev_ctr].Notified = false;  // Also reset the notification flag
385     }
386
387     if ((currentTime >= Events[ev_ctr].StartTime) && Events[ev_ctr].Triggered) {
388
389       for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
390         if (Events[ev_ctr].Transiting[i]) {
391           Events[ev_ctr].TimeSpan = currentTime - Events[ev_ctr].StartTime;
392           switch (Events[ev_ctr].Action[i]) {
393           case FG_RAMP:
394             if (Events[ev_ctr].TimeSpan <= Events[ev_ctr].TC[i]) {
395               newSetValue = Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
396             } else {
397               newSetValue = Events[ev_ctr].newValue[i];
398               if (Events[ev_ctr].Continuous != true) Events[ev_ctr].Transiting[i] = false;
399             }
400             break;
401           case FG_STEP:
402             newSetValue = Events[ev_ctr].newValue[i];
403
404             // If this is not a continuous event, reset the transiting flag.
405             // Otherwise, it is known that the event is a continuous event.
406             // Furthermore, if the event is to be determined by a function,
407             // then the function will be continuously calculated.
408             if (Events[ev_ctr].Continuous != true)
409               Events[ev_ctr].Transiting[i] = false;
410             else if (Events[ev_ctr].Functions[i] != 0)
411               newSetValue = Events[ev_ctr].Functions[i]->GetValue();
412
413             break;
414           case FG_EXP:
415             newSetValue = (1 - exp( -Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] )) * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
416             break;
417           default:
418             cerr << "Invalid Action specified" << endl;
419             break;
420           }
421           Events[ev_ctr].SetParam[i]->setDoubleValue(newSetValue);
422         }
423       }
424
425       // Print notification values after setting them
426       if (Events[ev_ctr].Notify && !Events[ev_ctr].Notified) {
427         cout << endl << "  Event " << event_ctr << " (" << Events[ev_ctr].Name << ")"
428              << " executed at time: " << currentTime << endl;
429         for (j=0; j<Events[ev_ctr].NotifyProperties.size();j++) {
430           cout << "    " << Events[ev_ctr].NotifyProperties[j]->GetRelativeName()
431                << " = " << Events[ev_ctr].NotifyProperties[j]->getDoubleValue() << endl;
432         }
433         cout << endl;
434         Events[ev_ctr].Notified = true;
435       }
436
437     }
438
439     event_ctr++;
440   }
441   return true;
442 }
443
444 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
445 //    The bitmasked value choices are as follows:
446 //    unset: In this case (the default) JSBSim would only print
447 //       out the normally expected messages, essentially echoing
448 //       the config files as they are read. If the environment
449 //       variable is not set, debug_lvl is set to 1 internally
450 //    0: This requests JSBSim not to output any messages
451 //       whatsoever.
452 //    1: This value explicity requests the normal JSBSim
453 //       startup messages
454 //    2: This value asks for a message to be printed out when
455 //       a class is instantiated
456 //    4: When this value is set, a message is displayed when a
457 //       FGModel object executes its Run() method
458 //    8: When this value is set, various runtime state variables
459 //       are printed out periodically
460 //    16: When set various parameters are sanity checked and
461 //       a message is printed out when they go out of bounds
462
463 void FGScript::Debug(int from)
464 {
465   if (debug_lvl <= 0) return;
466
467   if (debug_lvl & 1) { // Standard console startup message output
468     if (from == 0) { // Constructor
469     } else if (from == 3) {
470     } else if (from == 4)  { // print out script data
471       cout << endl;
472       cout << "Script: \"" << ScriptName << "\"" << endl;
473       cout << "  begins at " << StartTime << " seconds and runs to " << EndTime
474         << " seconds with dt = " << setprecision(6) << FDMExec->GetDeltaT() << " (" <<
475         ceil(1.0/FDMExec->GetDeltaT()) << " Hz)" << endl;
476       cout << endl;
477
478       for (unsigned int i=0; i<local_properties.size(); i++) {
479         cout << "Local property: " << local_properties[i]->title 
480              << " = " << PropertyManager->GetNode(local_properties[i]->title)->getDoubleValue()
481              << endl;
482       }
483       
484       if (local_properties.size() > 0) cout << endl;
485
486       for (unsigned i=0; i<Events.size(); i++) {
487         cout << "Event " << i;
488         if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
489         cout << ":" << endl;
490
491         if (Events[i].Persistent)
492           cout << "  " << "Whenever triggered, executes once";
493         else if (Events[i].Continuous)
494           cout << "  " << "While true, always executes";
495         else
496           cout << "  " << "When first triggered, executes once";
497
498         Events[i].Condition->PrintCondition();
499
500         cout << endl << "  Actions taken";
501         if (Events[i].Delay > 0.0)
502           cout << " (after a delay of " << Events[i].Delay << " secs)";
503         cout << ":" << endl << "    {";
504         for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
505           if (Events[i].SetValue[j] == 0.0 && Events[i].Functions[j] != 0L) {
506             if (Events[i].SetParam[j] == 0) {
507               cerr << fgred << highint << endl
508                    << "  An attempt has been made to access a non-existent property" << endl
509                    << "  in this event. Please check the property names used, spelling, etc."
510                    << reset << endl;
511               exit(-1);
512             }
513             cout << endl << "      set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
514                  << " to function value";
515           } else {
516             if (Events[i].SetParam[j] == 0) {
517               cerr << fgred << highint << endl
518                    << "  An attempt has been made to access a non-existent property" << endl
519                    << "  in this event. Please check the property names used, spelling, etc."
520                    << reset << endl;
521               exit(-1);
522             }
523             cout << endl << "      set " << Events[i].SetParam[j]->GetRelativeName("/fdm/jsbsim/")
524                  << " to " << Events[i].SetValue[j];
525           }
526
527           switch (Events[i].Type[j]) {
528           case FG_VALUE:
529           case FG_BOOL:
530             cout << " (constant";
531             break;
532           case FG_DELTA:
533             cout << " (delta";
534             break;
535           default:
536             cout << " (unspecified type";
537           }
538
539           switch (Events[i].Action[j]) {
540           case FG_RAMP:
541             cout << " via ramp";
542             break;
543           case FG_STEP:
544             cout << " via step)";
545             break;
546           case FG_EXP:
547             cout << " via exponential approach";
548             break;
549           default:
550             cout << " via unspecified action)";
551           }
552
553           if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
554             cout << " with time constant " << Events[i].TC[j] << ")";
555         }
556         cout << endl << "    }" << endl;
557
558         // Print notifications
559         if (Events[i].Notify) {
560           if (Events[i].NotifyProperties.size() > 0) {
561             cout << "  Notifications" << ":" << endl << "    {" << endl;
562             for (unsigned j=0; j<Events[i].NotifyProperties.size();j++) {
563               cout << "      "
564                    << Events[i].NotifyProperties[j]->GetRelativeName("/fdm/jsbsim/")
565                    << endl;
566             }
567             cout << "    }" << endl;
568           }
569         }
570         cout << endl;
571       }
572     }
573   }
574   if (debug_lvl & 2 ) { // Instantiation/Destruction notification
575     if (from == 0) cout << "Instantiated: FGScript" << endl;
576     if (from == 1) cout << "Destroyed:    FGScript" << endl;
577   }
578   if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
579   }
580   if (debug_lvl & 8 ) { // Runtime state variables
581   }
582   if (debug_lvl & 16) { // Sanity checking
583   }
584   if (debug_lvl & 64) {
585     if (from == 0) { // Constructor
586       cout << IdSrc << endl;
587       cout << IdHdr << endl;
588     }
589   }
590 }
591 }