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 <simgear/compiler.h>
46 # include STL_IOSTREAM
47 # include STL_ITERATOR
49 # if defined(sgi) && !defined(__GNUC__) && (_COMPILER_VERSION < 740)
50 # include <iostream.h>
58 #include <input_output/FGXMLParse.h>
59 #include <initialization/FGTrim.h>
63 static const char *IdSrc = "$Id$";
64 static const char *IdHdr = ID_FGSCRIPT;
66 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
68 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
70 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
72 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
76 FGScript::FGScript(FGFDMExec* fgex) : FDMExec(fgex)
78 State = FDMExec->GetState();
79 PropertyManager=FDMExec->GetPropertyManager();
83 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
88 for (i=0; i<local_properties.size(); i++)
89 PropertyManager->Untie(local_properties[i]->title);
91 for (i=0; i<local_properties.size(); i++)
92 delete local_properties[i];
94 local_properties.clear();
98 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
100 bool FGScript::LoadScript( string script )
102 string aircraft="", initialize="", comparison = "", prop_name="";
103 string notifyPropertyName="";
104 Element *document=0, *element=0, *run_element=0, *event_element=0;
105 Element *condition_element=0, *set_element=0, *delay_element=0;
106 Element *notify_element = 0L, *notify_property_element = 0L;
107 Element *property_element = 0L;
109 double dt = 0.0, value = 0.0;
110 FGXMLParse script_file_parser;
111 struct event *newEvent;
112 FGCondition *newCondition;
113 ifstream script_file(script.c_str());
115 if ( !script_file ) return false;
117 readXML(script_file, script_file_parser);
118 document = script_file_parser.GetDocument();
120 if (document->GetName() != string("runscript")) {
121 cerr << "File: " << script << " is not a script file" << endl;
125 ScriptName = document->GetAttributeValue("name");
127 // First, find "run" element and set delta T
129 run_element = document->FindElement("run");
132 cerr << "No \"run\" element found in script." << endl;
138 StartTime = run_element->GetAttributeValueAsNumber("start");
139 State->Setsim_time(StartTime);
140 EndTime = run_element->GetAttributeValueAsNumber("end");
141 dt = run_element->GetAttributeValueAsNumber("dt");
144 // read aircraft and initialization files
146 element = document->FindElement("use");
148 aircraft = element->GetAttributeValue("aircraft");
149 if (!aircraft.empty()) {
150 result = FDMExec->LoadModel(aircraft);
151 if (!result) return false;
153 cerr << "Aircraft must be specified in use element." << endl;
157 initialize = element->GetAttributeValue("initialize");
158 if (initialize.empty()) {
159 cerr << "Initialization file must be specified in use element." << endl;
164 cerr << "No \"use\" directives in the script file." << endl;
168 // Read local property declarations
169 property_element = run_element->FindElement("property");
170 while (property_element) {
171 LocalProps *localProp = new LocalProps();
172 localProp->title = property_element->GetDataLine();
173 local_properties.push_back(localProp);
174 PropertyManager->Tie(localProp->title, (local_properties.back())->value);
175 property_element = run_element->FindNextElement("property");
178 // Read "events" from script
180 event_element = run_element->FindElement("event");
181 while (event_element) { // event processing
183 // Create the event structure
184 newEvent = new struct event();
186 // Retrieve the event name if given
187 newEvent->Name = event_element->GetAttributeValue("name");
189 // Is this event persistent? That is, does it execute repeatedly as long as the
190 // condition is true, or does it execute as a one-shot event, only?
191 if (event_element->GetAttributeValue("persistent") == string("true")) {
192 newEvent->Persistent = true;
195 // Process the conditions
196 condition_element = event_element->FindElement("condition");
197 if (condition_element != 0) {
198 newCondition = new FGCondition(condition_element, PropertyManager);
199 newEvent->Condition = newCondition;
201 cerr << "No condition specified in script event " << newEvent->Name << endl;
205 // Is there a delay between the time this event is triggered, and when the event
206 // actions are executed?
208 delay_element = event_element->FindElement("delay");
209 if (delay_element) newEvent->Delay = event_element->FindElementValueAsNumber("delay");
210 else newEvent->Delay = 0.0;
212 // Notify about when this event is triggered?
213 if ((notify_element = event_element->FindElement("notify")) != 0) {
214 newEvent->Notify = true;
215 notify_property_element = notify_element->FindElement("property");
216 while (notify_property_element) {
217 notifyPropertyName = notify_property_element->GetDataLine();
218 newEvent->NotifyProperties.push_back( PropertyManager->GetNode(notifyPropertyName) );
219 notify_property_element = notify_element->FindNextElement("property");
223 // Read set definitions (these define the actions to be taken when the event is triggered).
224 set_element = event_element->FindElement("set");
225 while (set_element) {
226 prop_name = set_element->GetAttributeValue("name");
227 newEvent->SetParam.push_back( PropertyManager->GetNode(prop_name) );
228 value = set_element->GetAttributeValueAsNumber("value");
229 newEvent->SetValue.push_back(value);
230 newEvent->OriginalValue.push_back(0.0);
231 newEvent->newValue.push_back(0.0);
232 newEvent->ValueSpan.push_back(0.0);
233 string tempCompare = set_element->GetAttributeValue("type");
234 if (tempCompare == "FG_DELTA") newEvent->Type.push_back(FG_DELTA);
235 else if (tempCompare == "FG_BOOL") newEvent->Type.push_back(FG_BOOL);
236 else if (tempCompare == "FG_VALUE") newEvent->Type.push_back(FG_VALUE);
237 else newEvent->Type.push_back(FG_VALUE); // DEFAULT
238 tempCompare = set_element->GetAttributeValue("action");
239 if (tempCompare == "FG_RAMP") newEvent->Action.push_back(FG_RAMP);
240 else if (tempCompare == "FG_STEP") newEvent->Action.push_back(FG_STEP);
241 else if (tempCompare == "FG_EXP") newEvent->Action.push_back(FG_EXP);
242 else newEvent->Action.push_back(FG_STEP); // DEFAULT
244 if (!set_element->GetAttributeValue("tc").empty())
245 newEvent->TC.push_back(set_element->GetAttributeValueAsNumber("tc"));
247 newEvent->TC.push_back(1.0); // DEFAULT
249 newEvent->Transiting.push_back(false);
251 set_element = event_element->FindNextElement("set");
253 Events.push_back(*newEvent);
254 event_element = run_element->FindNextElement("event");
259 FGInitialCondition *IC=FDMExec->GetIC();
260 if ( ! IC->Load( initialize )) {
261 cerr << "Initialization unsuccessful" << endl;
268 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
270 bool FGScript::RunScript(void)
272 vector <struct event>::iterator iEvent = Events.begin();
274 unsigned event_ctr = 0;
276 double currentTime = State->Getsim_time();
277 double newSetValue = 0;
279 if (currentTime > EndTime) return false; //Script done!
281 // Iterate over all events.
282 while (iEvent < Events.end()) {
283 iEvent->PrevTriggered = iEvent->Triggered;
284 // Determine whether the set of conditional tests for this condition equate
285 // to true and should cause the event to execute.
286 if (iEvent->Condition->Evaluate()) {
287 if (!iEvent->Triggered) {
289 // The conditions are true, do the setting of the desired Event parameters
290 for (i=0; i<iEvent->SetValue.size(); i++) {
291 iEvent->OriginalValue[i] = iEvent->SetParam[i]->getDoubleValue();
292 switch (iEvent->Type[i]) {
295 iEvent->newValue[i] = iEvent->SetValue[i];
298 iEvent->newValue[i] = iEvent->OriginalValue[i] + iEvent->SetValue[i];
301 cerr << "Invalid Type specified" << endl;
304 iEvent->StartTime = currentTime + iEvent->Delay;
305 iEvent->ValueSpan[i] = iEvent->newValue[i] - iEvent->OriginalValue[i];
306 iEvent->Transiting[i] = true;
309 iEvent->Triggered = true;
310 } else if (iEvent->Persistent) {
311 iEvent->Triggered = false; // Reset the trigger for persistent events
314 if ((currentTime >= iEvent->StartTime) && iEvent->Triggered) {
316 if (iEvent->Notify && iEvent->PrevTriggered != iEvent->Triggered) {
317 cout << endl << " Event " << event_ctr << " (" << iEvent->Name << ")"
318 << " executed at time: " << currentTime << endl;
319 for (j=0; j<iEvent->NotifyProperties.size();j++) {
320 cout << " " << iEvent->NotifyProperties[j]->GetName()
321 << " = " << iEvent->NotifyProperties[j]->getDoubleValue() << endl;
326 for (i=0; i<iEvent->SetValue.size(); i++) {
327 if (iEvent->Transiting[i]) {
328 iEvent->TimeSpan = currentTime - iEvent->StartTime;
329 switch (iEvent->Action[i]) {
331 if (iEvent->TimeSpan <= iEvent->TC[i]) {
332 newSetValue = iEvent->TimeSpan/iEvent->TC[i] * iEvent->ValueSpan[i] + iEvent->OriginalValue[i];
334 newSetValue = iEvent->newValue[i];
335 iEvent->Transiting[i] = false;
339 newSetValue = iEvent->newValue[i];
340 iEvent->Transiting[i] = false;
343 newSetValue = (1 - exp( -iEvent->TimeSpan/iEvent->TC[i] )) * iEvent->ValueSpan[i] + iEvent->OriginalValue[i];
346 cerr << "Invalid Action specified" << endl;
349 iEvent->SetParam[i]->setDoubleValue(newSetValue);
360 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
361 // The bitmasked value choices are as follows:
362 // unset: In this case (the default) JSBSim would only print
363 // out the normally expected messages, essentially echoing
364 // the config files as they are read. If the environment
365 // variable is not set, debug_lvl is set to 1 internally
366 // 0: This requests JSBSim not to output any messages
368 // 1: This value explicity requests the normal JSBSim
370 // 2: This value asks for a message to be printed out when
371 // a class is instantiated
372 // 4: When this value is set, a message is displayed when a
373 // FGModel object executes its Run() method
374 // 8: When this value is set, various runtime state variables
375 // are printed out periodically
376 // 16: When set various parameters are sanity checked and
377 // a message is printed out when they go out of bounds
379 void FGScript::Debug(int from)
381 if (debug_lvl <= 0) return;
383 if (debug_lvl & 1) { // Standard console startup message output
384 if (from == 0) { // Constructor
385 } else if (from == 3) {
386 } else if (from == 4) { // print out script data
388 cout << "Script: \"" << ScriptName << "\"" << endl;
389 cout << " begins at " << StartTime << " seconds and runs to " << EndTime
390 << " seconds with dt = " << State->Getdt() << endl;
393 for (unsigned i=0; i<Events.size(); i++) {
394 cout << "Event " << i;
395 if (!Events[i].Name.empty()) cout << " (" << Events[i].Name << ")";
398 if (Events[i].Persistent)
399 cout << " " << "Always executes";
401 cout << " " << "Executes once";
403 Events[i].Condition->PrintCondition();
405 cout << endl << " Actions taken:" << endl << " {";
406 for (unsigned j=0; j<Events[i].SetValue.size(); j++) {
407 cout << endl << " set " << Events[i].SetParam[j]->GetName()
408 << " to " << Events[i].SetValue[j];
410 switch (Events[i].Type[j]) {
413 cout << " (constant";
419 cout << " (unspecified type";
422 switch (Events[i].Action[j]) {
427 cout << " via step)";
430 cout << " via exponential approach";
433 cout << " via unspecified action)";
436 if (Events[i].Action[j] == FG_RAMP || Events[i].Action[j] == FG_EXP)
437 cout << " with time constant " << Events[i].TC[j] << ")";
439 cout << endl << " }" << endl << endl;
444 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
445 if (from == 0) cout << "Instantiated: FGScript" << endl;
446 if (from == 1) cout << "Destroyed: FGScript" << endl;
448 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
450 if (debug_lvl & 8 ) { // Runtime state variables
452 if (debug_lvl & 16) { // Sanity checking
454 if (debug_lvl & 64) {
455 if (from == 0) { // Constructor
456 cout << IdSrc << endl;
457 cout << IdHdr << endl;