1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
6 Purpose: Loads and runs JSBSim scripts.
8 ------------- Copyright (C) 1999 Jon S. Berndt (jsb@hal-pc.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/FGXMLParse.h>
46 #include <initialization/FGTrim.h>
53 static const char *IdSrc = "$Id$";
54 static const char *IdHdr = ID_FGSCRIPT;
56 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
58 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
60 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
62 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
66 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
68 State = FDMExec->GetState();
69 PropertyManager=FDMExec->GetPropertyManager();
73 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 for (i=0; i<local_properties.size(); i++) delete local_properties[i];
80 local_properties.clear();
82 for (i=0; i<Events.size(); i++) delete Events[i].Condition;
88 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
90 bool FGScript::LoadScript( string script )
92 string aircraft="", initialize="", comparison = "", prop_name="";
93 string notifyPropertyName="";
94 Element *element=0, *run_element=0, *event_element=0;
95 Element *condition_element=0, *set_element=0, *delay_element=0;
96 Element *notify_element = 0L, *notify_property_element = 0L;
97 Element *property_element = 0L;
98 Element *output_element = 0L;
99 Element *input_element = 0L;
101 double dt = 0.0, value = 0.0;
102 struct event *newEvent;
103 FGCondition *newCondition;
105 document = LoadXMLDocument(script);
108 cerr << "File: " << script << " could not be loaded." << endl;
112 // Set up input and output files if specified
114 output_element = document->FindElement("output");
115 input_element = document->FindElement("input");
117 if (document->GetName() != string("runscript")) {
118 cerr << "File: " << script << " is not a script file" << endl;
122 ScriptName = document->GetAttributeValue("name");
124 // First, find "run" element and set delta T
126 run_element = document->FindElement("run");
129 cerr << "No \"run\" element found in script." << endl;
135 StartTime = run_element->GetAttributeValueAsNumber("start");
136 State->Setsim_time(StartTime);
137 EndTime = run_element->GetAttributeValueAsNumber("end");
138 dt = run_element->GetAttributeValueAsNumber("dt");
141 // read aircraft and initialization files
143 element = document->FindElement("use");
145 aircraft = element->GetAttributeValue("aircraft");
146 if (!aircraft.empty()) {
147 result = FDMExec->LoadModel(aircraft);
148 if (!result) return false;
150 cerr << "Aircraft must be specified in use element." << endl;
154 initialize = element->GetAttributeValue("initialize");
155 if (initialize.empty()) {
156 cerr << "Initialization file must be specified in use element." << endl;
161 cerr << "No \"use\" directives in the script file." << endl;
165 // Now, read input spec if given.
166 if (input_element > 0) {
167 FDMExec->GetInput()->Load(input_element);
170 // Now, read output spec if given.
171 if (output_element > 0) {
172 string output_file = output_element->GetAttributeValue("file");
173 if (output_file.empty()) {
174 cerr << "No logging directives file was specified." << endl;
176 FDMExec->SetOutputDirectives(output_file);
180 // Read local property/value declarations
181 property_element = run_element->FindElement("property");
182 while (property_element) {
185 if ( ! property_element->GetAttributeValue("value").empty())
186 value = property_element->GetAttributeValueAsNumber("value");
188 LocalProps *localProp = new LocalProps(value);
189 localProp->title = property_element->GetDataLine();
190 local_properties.push_back(localProp);
192 PropertyManager->Tie(localProp->title, (local_properties.back())->value);
193 property_element = run_element->FindNextElement("property");
196 // Read "events" from script
198 event_element = run_element->FindElement("event");
199 while (event_element) { // event processing
201 // Create the event structure
202 newEvent = new struct event();
204 // Retrieve the event name if given
205 newEvent->Name = event_element->GetAttributeValue("name");
207 // Is this event persistent? That is, does it execute repeatedly as long as the
208 // condition is true, or does it execute as a one-shot event, only?
209 if (event_element->GetAttributeValue("persistent") == string("true")) {
210 newEvent->Persistent = true;
213 // Process the conditions
214 condition_element = event_element->FindElement("condition");
215 if (condition_element != 0) {
216 newCondition = new FGCondition(condition_element, PropertyManager);
217 newEvent->Condition = newCondition;
219 cerr << "No condition specified in script event " << newEvent->Name << endl;
223 // Is there a delay between the time this event is triggered, and when the event
224 // actions are executed?
226 delay_element = event_element->FindElement("delay");
227 if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
228 else newEvent->Delay = 0.0;
230 // Notify about when this event is triggered?
231 if ((notify_element = event_element->FindElement("notify")) != 0) {
232 newEvent->Notify = true;
233 notify_property_element = notify_element->FindElement("property");
234 while (notify_property_element) {
235 notifyPropertyName = notify_property_element->GetDataLine();
236 if (PropertyManager->GetNode(notifyPropertyName)) {
237 newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
239 cout << endl << fgred << " Could not find the property named "
240 << notifyPropertyName << " in script" << endl << " \""
241 << ScriptName << "\". This unknown property will not be "
242 << "echoed for notification." << reset << endl;
244 notify_property_element = notify_element->FindNextElement("property");
248 // Read set definitions (these define the actions to be taken when the event is triggered).
249 set_element = event_element->FindElement("set");
250 while (set_element) {
251 prop_name = set_element->GetAttributeValue("name");
252 newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
253 //Todo - should probably do some safety checking here to make sure one or the other
254 //of value or function is specified.
255 if (!set_element->GetAttributeValue("value").empty()) {
256 value = set_element->GetAttributeValueAsNumber("value");
257 newEvent->Functions.push_back((FGFunction*)0L);
258 } else if (set_element->FindElement("function")) {
260 newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
262 newEvent->SetValue.push_back(value);
263 newEvent->OriginalValue.push_back(0.0);
264 newEvent->newValue.push_back(0.0);
265 newEvent->ValueSpan.push_back(0.0);
266 string tempCompare = set_element->GetAttributeValue("type");
267 if (tempCompare == "FG_DELTA") newEvent->Type.push_back(FG_DELTA);
268 else if (tempCompare == "FG_BOOL") newEvent->Type.push_back(FG_BOOL);
269 else if (tempCompare == "FG_VALUE") newEvent->Type.push_back(FG_VALUE);
270 else newEvent->Type.push_back(FG_VALUE); // DEFAULT
271 tempCompare = set_element->GetAttributeValue("action");
272 if (tempCompare == "FG_RAMP") newEvent->Action.push_back(FG_RAMP);
273 else if (tempCompare == "FG_STEP") newEvent->Action.push_back(FG_STEP);
274 else if (tempCompare == "FG_EXP") newEvent->Action.push_back(FG_EXP);
275 else newEvent->Action.push_back(FG_STEP); // DEFAULT
277 if (!set_element->GetAttributeValue("tc").empty())
278 newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
280 newEvent->TC.push_back(1.0); // DEFAULT
282 newEvent->Transiting.push_back(false);
284 set_element = event_element->FindNextElement("set");
286 Events.push_back(*newEvent);
289 event_element = run_element->FindNextElement("event");
294 FGInitialCondition *IC=FDMExec->GetIC();
295 if ( ! IC->Load( initialize )) {
296 cerr << "Initialization unsuccessful" << endl;
303 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305 bool FGScript::RunScript(void)
307 vector <struct event>::iterator iEvent = Events.begin();
309 unsigned event_ctr = 0;
311 double currentTime = State->Getsim_time();
312 double newSetValue = 0;
314 if (currentTime > EndTime) return false; //Script done!
316 // Iterate over all events.
317 while (iEvent < Events.end()) {
318 iEvent->PrevTriggered = iEvent->Triggered;
319 // Determine whether the set of conditional tests for this condition equate
320 // to true and should cause the event to execute.
321 if (iEvent->Condition->Evaluate()) {
322 if (!iEvent->Triggered) {
324 // The conditions are true, do the setting of the desired Event parameters
325 for (i=0; i<iEvent->SetValue.size(); i++) {
326 iEvent->OriginalValue[i] = iEvent->SetParam[i]->getDoubleValue();
327 if (iEvent->Functions[i] != 0) { // Parameter should be set to a function value
328 iEvent->SetValue[i] = iEvent->Functions[i]->GetValue();
330 switch (iEvent->Type[i]) {
333 iEvent->newValue[i] = iEvent->SetValue[i];
336 iEvent->newValue[i] = iEvent->OriginalValue[i] + iEvent->SetValue[i];
339 cerr << "Invalid Type specified" << endl;
342 iEvent->StartTime = currentTime + iEvent->Delay;
343 iEvent->ValueSpan[i] = iEvent->newValue[i] - iEvent->OriginalValue[i];
344 iEvent->Transiting[i] = true;
347 iEvent->Triggered = true;
348 } else if (iEvent->Persistent) {
349 iEvent->Triggered = false; // Reset the trigger for persistent events
350 iEvent->Notified = false; // Also reset the notification flag
353 if ((currentTime >= iEvent->StartTime) && iEvent->Triggered) {
355 for (i=0; i<iEvent->SetValue.size(); i++) {
356 if (iEvent->Transiting[i]) {
357 iEvent->TimeSpan = currentTime - iEvent->StartTime;
358 if (iEvent->Functions[i] == 0) {
359 switch (iEvent->Action[i]) {
361 if (iEvent->TimeSpan <= iEvent->TC[i]) {
362 newSetValue = iEvent->TimeSpan/iEvent->TC[i] * iEvent->ValueSpan[i] + iEvent->OriginalValue[i];
364 newSetValue = iEvent->newValue[i];
365 iEvent->Transiting[i] = false;
369 newSetValue = iEvent->newValue[i];
370 iEvent->Transiting[i] = false;
373 newSetValue = (1 - exp( -iEvent->TimeSpan/iEvent->TC[i] )) * iEvent->ValueSpan[i] + iEvent->OriginalValue[i];
376 cerr << "Invalid Action specified" << endl;
379 } else { // Set the new value based on a function
380 newSetValue = iEvent->Functions[i]->GetValue();
382 iEvent->SetParam[i]->setDoubleValue(newSetValue);
386 // Print notification values after setting them
387 if (iEvent->Notify && !iEvent->Notified) {
388 cout << endl << " Event " << event_ctr << " (" << iEvent->Name << ")"
389 << " executed at time: " << currentTime << endl;
390 for (j=0; j<iEvent->NotifyProperties.size();j++) {
391 cout << " " << iEvent->NotifyProperties[j]->GetName()
392 << " = " << iEvent->NotifyProperties[j]->getDoubleValue() << endl;
395 iEvent->Notified = true;
406 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
407 // The bitmasked value choices are as follows:
408 // unset: In this case (the default) JSBSim would only print
409 // out the normally expected messages, essentially echoing
410 // the config files as they are read. If the environment
411 // variable is not set, debug_lvl is set to 1 internally
412 // 0: This requests JSBSim not to output any messages
414 // 1: This value explicity requests the normal JSBSim
416 // 2: This value asks for a message to be printed out when
417 // a class is instantiated
418 // 4: When this value is set, a message is displayed when a
419 // FGModel object executes its Run() method
420 // 8: When this value is set, various runtime state variables
421 // are printed out periodically
422 // 16: When set various parameters are sanity checked and
423 // a message is printed out when they go out of bounds
425 void FGScript::Debug(int from)
427 if (debug_lvl <= 0) return;
429 if (debug_lvl & 1) { // Standard console startup message output
430 if (from == 0) { // Constructor
431 } else if (from == 3) {
432 } else if (from == 4) { // print out script data
434 cout << "Script: \"" << ScriptName << "\"" << endl;
435 cout << " begins at " << StartTime << " seconds and runs to " << EndTime
436 << " seconds with dt = " << State->Getdt() << endl;
439 for (unsigned i=0; i<Events.size(); i++) {
440 cout << "Event " << i;
441 if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
444 if (Events[i].Persistent)
445 cout << " " << "Always executes";
447 cout << " " << "Executes once";
449 Events[i].Condition->PrintCondition();
451 cout << endl << " Actions taken:" << endl << " {";
452 for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
453 if (Events[i].SetValue[j] == 0.0 && Events[i].Functions[j] != 0L) {
454 if (Events[i].SetParam[j] == 0) {
455 cerr << fgred << highint << endl
456 << " An attempt has been made to access a non-existent property" << endl
457 << " in this event. Please check the property names used, spelling, etc."
461 cout << endl << " set " << Events[i].SetParam[j]->GetName()
462 << " to function value";
464 if (Events[i].SetParam[j] == 0) {
465 cerr << fgred << highint << endl
466 << " An attempt has been made to access a non-existent property" << endl
467 << " in this event. Please check the property names used, spelling, etc."
471 cout << endl << " set " << Events[i].SetParam[j]->GetName()
472 << " to " << Events[i].SetValue[j];
475 switch (Events[i].Type[j]) {
478 cout << " (constant";
484 cout << " (unspecified type";
487 switch (Events[i].Action[j]) {
492 cout << " via step)";
495 cout << " via exponential approach";
498 cout << " via unspecified action)";
501 if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
502 cout << " with time constant " << Events[i].TC[j] << ")";
504 cout << endl << " }" << endl << endl;
509 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
510 if (from == 0) cout << "Instantiated: FGScript" << endl;
511 if (from == 1) cout << "Destroyed: FGScript" << endl;
513 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
515 if (debug_lvl & 8 ) { // Runtime state variables
517 if (debug_lvl & 16) { // Sanity checking
519 if (debug_lvl & 64) {
520 if (from == 0) { // Constructor
521 cout << IdSrc << endl;
522 cout << IdHdr << endl;