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>
52 static const char *IdSrc = "$Id$";
53 static const char *IdHdr = ID_FGSCRIPT;
55 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
57 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
59 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
61 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
65 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
67 State = FDMExec->GetState();
68 PropertyManager=FDMExec->GetPropertyManager();
72 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
78 for (i=0; i<local_properties.size(); i++) delete local_properties[i];
79 local_properties.clear();
81 for (i=0; i<Events.size(); i++) delete Events[i].Condition;
87 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
89 bool FGScript::LoadScript( string script )
91 string aircraft="", initialize="", comparison = "", prop_name="";
92 string notifyPropertyName="";
93 Element *element=0, *run_element=0, *event_element=0;
94 Element *condition_element=0, *set_element=0, *delay_element=0;
95 Element *notify_element = 0L, *notify_property_element = 0L;
96 Element *property_element = 0L;
97 Element *output_element = 0L;
98 Element *input_element = 0L;
100 double dt = 0.0, value = 0.0;
101 struct event *newEvent;
102 FGCondition *newCondition;
104 document = LoadXMLDocument(script);
107 cerr << "File: " << script << " could not be loaded." << endl;
111 // Set up input and output files if specified
113 output_element = document->FindElement("output");
114 input_element = document->FindElement("input");
116 if (document->GetName() != string("runscript")) {
117 cerr << "File: " << script << " is not a script file" << endl;
121 ScriptName = document->GetAttributeValue("name");
123 // First, find "run" element and set delta T
125 run_element = document->FindElement("run");
128 cerr << "No \"run\" element found in script." << endl;
134 StartTime = run_element->GetAttributeValueAsNumber("start");
135 State->Setsim_time(StartTime);
136 EndTime = run_element->GetAttributeValueAsNumber("end");
137 dt = run_element->GetAttributeValueAsNumber("dt");
140 // read aircraft and initialization files
142 element = document->FindElement("use");
144 aircraft = element->GetAttributeValue("aircraft");
145 if (!aircraft.empty()) {
146 result = FDMExec->LoadModel(aircraft);
147 if (!result) return false;
149 cerr << "Aircraft must be specified in use element." << endl;
153 initialize = element->GetAttributeValue("initialize");
154 if (initialize.empty()) {
155 cerr << "Initialization file must be specified in use element." << endl;
160 cerr << "No \"use\" directives in the script file." << endl;
164 // Now, read input spec if given.
165 if (input_element > 0) {
166 FDMExec->GetInput()->Load(input_element);
169 // Now, read output spec if given.
170 if (output_element > 0) {
171 string output_file = output_element->GetAttributeValue("file");
172 if (output_file.empty()) {
173 cerr << "No logging directives file was specified." << endl;
175 FDMExec->SetOutputDirectives(output_file);
179 // Read local property/value declarations
180 property_element = run_element->FindElement("property");
181 while (property_element) {
186 title = property_element->GetDataLine();
187 if ( ! property_element->GetAttributeValue("value").empty())
188 value = property_element->GetAttributeValueAsNumber("value");
190 LocalProps *localProp = new LocalProps(value);
191 localProp->title = title;
192 local_properties.push_back(localProp);
193 if (PropertyManager->HasNode(title)) {
194 PropertyManager->GetNode(title)->setDoubleValue(value);
196 PropertyManager->Tie(localProp->title, localProp->value);
198 property_element = run_element->FindNextElement("property");
201 // Read "events" from script
203 event_element = run_element->FindElement("event");
204 while (event_element) { // event processing
206 // Create the event structure
207 newEvent = new struct event();
209 // Retrieve the event name if given
210 newEvent->Name = event_element->GetAttributeValue("name");
212 // Is this event persistent? That is, does it execute every time the
213 // condition triggers to true, or does it execute as a one-shot event, only?
214 if (event_element->GetAttributeValue("persistent") == string("true")) {
215 newEvent->Persistent = true;
218 // Does this event execute continuously when triggered to true?
219 if (event_element->GetAttributeValue("continuous") == string("true")) {
220 newEvent->Continuous = true;
223 // Process the conditions
224 condition_element = event_element->FindElement("condition");
225 if (condition_element != 0) {
226 newCondition = new FGCondition(condition_element, PropertyManager);
227 newEvent->Condition = newCondition;
229 cerr << "No condition specified in script event " << newEvent->Name << endl;
233 // Is there a delay between the time this event is triggered, and when the event
234 // actions are executed?
236 delay_element = event_element->FindElement("delay");
237 if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
238 else newEvent->Delay = 0.0;
240 // Notify about when this event is triggered?
241 if ((notify_element = event_element->FindElement("notify")) != 0) {
242 newEvent->Notify = true;
243 notify_property_element = notify_element->FindElement("property");
244 while (notify_property_element) {
245 notifyPropertyName = notify_property_element->GetDataLine();
246 if (PropertyManager->GetNode(notifyPropertyName)) {
247 newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
249 cout << endl << fgred << " Could not find the property named "
250 << notifyPropertyName << " in script" << endl << " \""
251 << ScriptName << "\". This unknown property will not be "
252 << "echoed for notification." << reset << endl;
254 notify_property_element = notify_element->FindNextElement("property");
258 // Read set definitions (these define the actions to be taken when the event is triggered).
259 set_element = event_element->FindElement("set");
260 while (set_element) {
261 prop_name = set_element->GetAttributeValue("name");
262 newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
263 //Todo - should probably do some safety checking here to make sure one or the other
264 //of value or function is specified.
265 if (!set_element->GetAttributeValue("value").empty()) {
266 value = set_element->GetAttributeValueAsNumber("value");
267 newEvent->Functions.push_back((FGFunction*)0L);
268 } else if (set_element->FindElement("function")) {
270 newEvent->Functions.push_back(new FGFunction(PropertyManager, set_element->FindElement("function")));
272 newEvent->SetValue.push_back(value);
273 newEvent->OriginalValue.push_back(0.0);
274 newEvent->newValue.push_back(0.0);
275 newEvent->ValueSpan.push_back(0.0);
276 string tempCompare = set_element->GetAttributeValue("type");
277 if (tempCompare == "FG_DELTA") newEvent->Type.push_back(FG_DELTA);
278 else if (tempCompare == "FG_BOOL") newEvent->Type.push_back(FG_BOOL);
279 else if (tempCompare == "FG_VALUE") newEvent->Type.push_back(FG_VALUE);
280 else newEvent->Type.push_back(FG_VALUE); // DEFAULT
281 tempCompare = set_element->GetAttributeValue("action");
282 if (tempCompare == "FG_RAMP") newEvent->Action.push_back(FG_RAMP);
283 else if (tempCompare == "FG_STEP") newEvent->Action.push_back(FG_STEP);
284 else if (tempCompare == "FG_EXP") newEvent->Action.push_back(FG_EXP);
285 else newEvent->Action.push_back(FG_STEP); // DEFAULT
287 if (!set_element->GetAttributeValue("tc").empty())
288 newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
290 newEvent->TC.push_back(1.0); // DEFAULT
292 newEvent->Transiting.push_back(false);
294 set_element = event_element->FindNextElement("set");
296 Events.push_back(*newEvent);
299 event_element = run_element->FindNextElement("event");
304 FGInitialCondition *IC=FDMExec->GetIC();
305 if ( ! IC->Load( initialize )) {
306 cerr << "Initialization unsuccessful" << endl;
313 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
315 bool FGScript::RunScript(void)
318 unsigned event_ctr = 0;
320 double currentTime = State->Getsim_time();
321 double newSetValue = 0;
323 if (currentTime > EndTime) return false; //Script done!
325 // Iterate over all events.
326 for (unsigned int ev_ctr=0; ev_ctr < Events.size(); ev_ctr++) {
327 // Determine whether the set of conditional tests for this condition equate
328 // to true and should cause the event to execute. If the conditions evaluate
329 // to true, then the event is triggered. If the event is not persistent,
330 // then this trigger will remain set true. If the event is persistent,
331 // the trigger will reset to false when the condition evaluates to false.
332 if (Events[ev_ctr].Condition->Evaluate()) {
333 if (!Events[ev_ctr].Triggered) {
335 // The conditions are true, do the setting of the desired Event parameters
336 for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
337 Events[ev_ctr].OriginalValue[i] = Events[ev_ctr].SetParam[i]->getDoubleValue();
338 if (Events[ev_ctr].Functions[i] != 0) { // Parameter should be set to a function value
339 Events[ev_ctr].SetValue[i] = Events[ev_ctr].Functions[i]->GetValue();
341 switch (Events[ev_ctr].Type[i]) {
344 Events[ev_ctr].newValue[i] = Events[ev_ctr].SetValue[i];
347 Events[ev_ctr].newValue[i] = Events[ev_ctr].OriginalValue[i] + Events[ev_ctr].SetValue[i];
350 cerr << "Invalid Type specified" << endl;
353 Events[ev_ctr].StartTime = currentTime + Events[ev_ctr].Delay;
354 Events[ev_ctr].ValueSpan[i] = Events[ev_ctr].newValue[i] - Events[ev_ctr].OriginalValue[i];
355 Events[ev_ctr].Transiting[i] = true;
358 Events[ev_ctr].Triggered = true;
360 } else if (Events[ev_ctr].Persistent) { // If the event is persistent, reset the trigger.
362 Events[ev_ctr].Triggered = false; // Reset the trigger for persistent events
363 Events[ev_ctr].Notified = false; // Also reset the notification flag
366 if ((currentTime >= Events[ev_ctr].StartTime) && Events[ev_ctr].Triggered) {
368 for (i=0; i<Events[ev_ctr].SetValue.size(); i++) {
369 if (Events[ev_ctr].Transiting[i]) {
370 Events[ev_ctr].TimeSpan = currentTime - Events[ev_ctr].StartTime;
371 switch (Events[ev_ctr].Action[i]) {
373 if (Events[ev_ctr].TimeSpan <= Events[ev_ctr].TC[i]) {
374 newSetValue = Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
376 newSetValue = Events[ev_ctr].newValue[i];
377 if (Events[ev_ctr].Continuous != true) Events[ev_ctr].Transiting[i] = false;
381 newSetValue = Events[ev_ctr].newValue[i];
383 // If this is not a continuous event, reset the transiting flag.
384 // Otherwise, it is known that the event is a continuous event.
385 // Furthermore, if the event is to be determined by a function,
386 // then the function will be continuously calculated.
387 if (Events[ev_ctr].Continuous != true)
388 Events[ev_ctr].Transiting[i] = false;
389 else if (Events[ev_ctr].Functions[i] != 0)
390 newSetValue = Events[ev_ctr].Functions[i]->GetValue();
394 newSetValue = (1 - exp( -Events[ev_ctr].TimeSpan/Events[ev_ctr].TC[i] )) * Events[ev_ctr].ValueSpan[i] + Events[ev_ctr].OriginalValue[i];
397 cerr << "Invalid Action specified" << endl;
400 Events[ev_ctr].SetParam[i]->setDoubleValue(newSetValue);
404 // Print notification values after setting them
405 if (Events[ev_ctr].Notify && !Events[ev_ctr].Notified) {
406 cout << endl << " Event " << event_ctr << " (" << Events[ev_ctr].Name << ")"
407 << " executed at time: " << currentTime << endl;
408 for (j=0; j<Events[ev_ctr].NotifyProperties.size();j++) {
409 cout << " " << Events[ev_ctr].NotifyProperties[j]->GetName()
410 << " = " << Events[ev_ctr].NotifyProperties[j]->getDoubleValue() << endl;
413 Events[ev_ctr].Notified = true;
423 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
424 // The bitmasked value choices are as follows:
425 // unset: In this case (the default) JSBSim would only print
426 // out the normally expected messages, essentially echoing
427 // the config files as they are read. If the environment
428 // variable is not set, debug_lvl is set to 1 internally
429 // 0: This requests JSBSim not to output any messages
431 // 1: This value explicity requests the normal JSBSim
433 // 2: This value asks for a message to be printed out when
434 // a class is instantiated
435 // 4: When this value is set, a message is displayed when a
436 // FGModel object executes its Run() method
437 // 8: When this value is set, various runtime state variables
438 // are printed out periodically
439 // 16: When set various parameters are sanity checked and
440 // a message is printed out when they go out of bounds
442 void FGScript::Debug(int from)
444 if (debug_lvl <= 0) return;
446 if (debug_lvl & 1) { // Standard console startup message output
447 if (from == 0) { // Constructor
448 } else if (from == 3) {
449 } else if (from == 4) { // print out script data
451 cout << "Script: \"" << ScriptName << "\"" << endl;
452 cout << " begins at " << StartTime << " seconds and runs to " << EndTime
453 << " seconds with dt = " << State->Getdt() << endl;
456 for (unsigned int i=0; i<local_properties.size(); i++) {
457 cout << "Local property: " << local_properties[i]->title
458 << " = " << PropertyManager->GetNode(local_properties[i]->title)->getDoubleValue()
462 if (local_properties.size() > 0) cout << endl;
464 for (unsigned i=0; i<Events.size(); i++) {
465 cout << "Event " << i;
466 if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
469 if (Events[i].Persistent)
470 cout << " " << "Always executes";
472 cout << " " << "Executes once";
474 Events[i].Condition->PrintCondition();
476 cout << endl << " Actions taken:" << endl << " {";
477 for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
478 if (Events[i].SetValue[j] == 0.0 && Events[i].Functions[j] != 0L) {
479 if (Events[i].SetParam[j] == 0) {
480 cerr << fgred << highint << endl
481 << " An attempt has been made to access a non-existent property" << endl
482 << " in this event. Please check the property names used, spelling, etc."
486 cout << endl << " set " << Events[i].SetParam[j]->GetName()
487 << " to function value";
489 if (Events[i].SetParam[j] == 0) {
490 cerr << fgred << highint << endl
491 << " An attempt has been made to access a non-existent property" << endl
492 << " in this event. Please check the property names used, spelling, etc."
496 cout << endl << " set " << Events[i].SetParam[j]->GetName()
497 << " to " << Events[i].SetValue[j];
500 switch (Events[i].Type[j]) {
503 cout << " (constant";
509 cout << " (unspecified type";
512 switch (Events[i].Action[j]) {
517 cout << " via step)";
520 cout << " via exponential approach";
523 cout << " via unspecified action)";
526 if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
527 cout << " with time constant " << Events[i].TC[j] << ")";
529 cout << endl << " }" << endl << endl;
534 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
535 if (from == 0) cout << "Instantiated: FGScript" << endl;
536 if (from == 1) cout << "Destroyed: FGScript" << endl;
538 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
540 if (debug_lvl & 8 ) { // Runtime state variables
542 if (debug_lvl & 16) { // Sanity checking
544 if (debug_lvl & 64) {
545 if (from == 0) { // Constructor
546 cout << IdSrc << endl;
547 cout << IdHdr << endl;