JSBSim Flight Dynamics Model  1.1.11 (13 Feb 2022)
An Open Source Flight Dynamics and Control Software Library in C++
FGFCSComponent.cpp
1 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
2 
3  Module: FGFCSComponent.cpp
4  Author: Jon S. Berndt
5  Date started: 11/1999
6 
7  ------------- Copyright (C) 2000 -------------
8 
9  This program is free software; you can redistribute it and/or modify it under
10  the terms of the GNU Lesser General Public License as published by the Free
11  Software Foundation; either version 2 of the License, or (at your option) any
12  later version.
13 
14  This program is distributed in the hope that it will be useful, but WITHOUT
15  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
16  FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
17  details.
18 
19  You should have received a copy of the GNU Lesser General Public License along
20  with this program; if not, write to the Free Software Foundation, Inc., 59
21  Temple Place - Suite 330, Boston, MA 02111-1307, USA.
22 
23  Further information about the GNU Lesser General Public License can also be
24  found on the world wide web at http://www.gnu.org.
25 
26 FUNCTIONAL DESCRIPTION
27 --------------------------------------------------------------------------------
28 
29 HISTORY
30 --------------------------------------------------------------------------------
31 
32 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33 COMMENTS, REFERENCES, and NOTES
34 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35 
36 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37 INCLUDES
38 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
39 
40 #include "FGFCSComponent.h"
41 #include "models/FGFCS.h"
42 #include "math/FGParameterValue.h"
43 
44 using namespace std;
45 
46 namespace JSBSim {
47 
48 /*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
49 CLASS IMPLEMENTATION
50 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
51 
52 FGFCSComponent::FGFCSComponent(FGFCS* _fcs, Element* element) : fcs(_fcs)
53 {
54  Input = Output = delay_time = 0.0;
55  delay = index = 0;
56  ClipMin = ClipMax = new FGRealValue(0.0);
57  clip = cyclic_clip = false;
58  dt = fcs->GetChannelDeltaT();
59 
60  PropertyManager = fcs->GetPropertyManager();
61  if (element->GetName() == string("lag_filter")) {
62  Type = "LAG_FILTER";
63  } else if (element->GetName() == string("lead_lag_filter")) {
64  Type = "LEAD_LAG_FILTER";
65  } else if (element->GetName() == string("washout_filter")) {
66  Type = "WASHOUT_FILTER";
67  } else if (element->GetName() == string("second_order_filter")) {
68  Type = "SECOND_ORDER_FILTER";
69  } else if (element->GetName() == string("integrator")) {
70  Type = "INTEGRATOR";
71  } else if (element->GetName() == string("summer")) {
72  Type = "SUMMER";
73  } else if (element->GetName() == string("pure_gain")) {
74  Type = "PURE_GAIN";
75  } else if (element->GetName() == string("scheduled_gain")) {
76  Type = "SCHEDULED_GAIN";
77  } else if (element->GetName() == string("aerosurface_scale")) {
78  Type = "AEROSURFACE_SCALE";
79  } else if (element->GetName() == string("switch")) {
80  Type = "SWITCH";
81  } else if (element->GetName() == string("kinematic")) {
82  Type = "KINEMATIC";
83  } else if (element->GetName() == string("deadband")) {
84  Type = "DEADBAND";
85  } else if (element->GetName() == string("fcs_function")) {
86  Type = "FCS_FUNCTION";
87  } else if (element->GetName() == string("pid")) {
88  Type = "PID";
89  } else if (element->GetName() == string("sensor")) {
90  Type = "SENSOR";
91  } else if (element->GetName() == string("accelerometer")) {
92  Type = "ACCELEROMETER";
93  } else if (element->GetName() == string("magnetometer")) {
94  Type = "MAGNETOMETER";
95  } else if (element->GetName() == string("gyro")) {
96  Type = "GYRO";
97  } else if (element->GetName() == string("actuator")) {
98  Type = "ACTUATOR";
99  } else if (element->GetName() == string("waypoint_heading")) {
100  Type = "WAYPOINT_HEADING";
101  } else if (element->GetName() == string("waypoint_distance")) {
102  Type = "WAYPOINT_DISTANCE";
103  } else if (element->GetName() == string("angle")) {
104  Type = "ANGLE";
105  } else if (element->GetName() == string("distributor")) {
106  Type = "DISTRIBUTOR";
107  } else { // illegal component in this channel
108  Type = "UNKNOWN";
109  }
110 
111  Name = element->GetAttributeValue("name");
112 
113  Element *init_element = element->FindElement("init");
114  while (init_element) {
115  InitNodes.push_back(new FGPropertyValue(init_element->GetDataLine(),
116  PropertyManager ));
117  init_element = element->FindNextElement("init");
118  }
119 
120  Element *input_element = element->FindElement("input");
121  while (input_element) {
122  InputNodes.push_back(new FGPropertyValue(input_element->GetDataLine(),
123  PropertyManager ));
124 
125  input_element = element->FindNextElement("input");
126  }
127 
128  Element *out_elem = element->FindElement("output");
129  while (out_elem) {
130  string output_node_name = out_elem->GetDataLine();
131  bool node_exists = PropertyManager->HasNode(output_node_name);
132  FGPropertyNode* OutputNode = PropertyManager->GetNode( output_node_name, true );
133  if (!OutputNode) {
134  cerr << out_elem->ReadFrom() << " Unable to process property: "
135  << output_node_name << endl;
136  throw(string("Invalid output property name in flight control definition"));
137  }
138  OutputNodes.push_back(OutputNode);
139  // If the node has just been created then it must be initialized to a
140  // sensible value since FGPropertyNode::GetNode() does not take care of
141  // that. If the node was already existing, its current value is kept
142  // unchanged.
143  if (!node_exists)
144  OutputNode->setDoubleValue(Output);
145  out_elem = element->FindNextElement("output");
146  }
147 
148  Element* delay_elem = element->FindElement("delay");
149  if ( delay_elem ) {
150  delay_time = delay_elem->GetDataAsNumber();
151  string delayType = delay_elem->GetAttributeValue("type");
152  if (delayType.length() > 0) {
153  if (delayType == "time") {
154  delay = (unsigned int)(delay_time / dt);
155  } else if (delayType == "frames") {
156  delay = (unsigned int)delay_time;
157  } else {
158  cerr << "Unallowed delay type" << endl;
159  }
160  } else {
161  delay = (unsigned int)(delay_time / dt);
162  }
163  output_array.resize(delay);
164  for (unsigned int i=0; i<delay; i++) output_array[i] = 0.0;
165  }
166 
167  Element *clip_el = element->FindElement("clipto");
168  if (clip_el) {
169  Element* el = clip_el->FindElement("min");
170  if (!el) {
171  cerr << clip_el->ReadFrom()
172  << "Element <min> is missing, <clipto> is ignored." << endl;
173  return;
174  }
175 
176  ClipMin = new FGParameterValue(el, PropertyManager);
177 
178  el = clip_el->FindElement("max");
179  if (!el) {
180  cerr << clip_el->ReadFrom()
181  << "Element <max> is missing, <clipto> is ignored." << endl;
182  ClipMin = nullptr;
183  return;
184  }
185 
186  ClipMax = new FGParameterValue(el, PropertyManager);
187 
188  if (clip_el->GetAttributeValue("type") == "cyclic")
189  cyclic_clip = true;
190 
191  clip = true;
192  }
193 
194  Debug(0);
195 }
196 
197 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
198 
200 {
201  Debug(1);
202 }
203 
204 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
205 
206 void FGFCSComponent::ResetPastStates(void)
207 {
208  index = 0;
209  for (auto &elm: output_array)
210  elm = 0.0;
211 }
212 
213 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
214 
215 void FGFCSComponent::CheckInputNodes(size_t MinNodes, size_t MaxNodes, Element* el)
216 {
217  size_t num = InputNodes.size();
218 
219  if (num < MinNodes) {
220  cerr << el->ReadFrom()
221  << " Not enough <input> nodes are provided" << endl
222  << " Expecting " << MinNodes << " while " << num
223  << " are provided." << endl;
224  throw("Some inputs are missing.");
225  }
226 
227  if (num > MaxNodes) {
228  cerr << el->ReadFrom()
229  << " Too many <input> nodes are provided" << endl
230  << " Expecting " << MaxNodes << " while " << num
231  << " are provided." << endl
232  << " The last " << num-MaxNodes << " input nodes will be ignored."
233  << endl;
234  }
235 }
236 
237 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
238 
239 void FGFCSComponent::SetOutput(void)
240 {
241  for (auto node: OutputNodes)
242  node->setDoubleValue(Output);
243 }
244 
245 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
246 
247 void FGFCSComponent::Delay(void)
248 {
249  if (fcs->GetTrimStatus()) {
250  // Update the whole history while trim routines are executing.
251  // Don't want to model delays while calculating a trim solution.
252  std::fill(output_array.begin(), output_array.end(), Output);
253  }
254  else {
255  output_array[index] = Output;
256  if ((unsigned int)index == delay-1) index = 0;
257  else index++;
258  Output = output_array[index];
259  }
260 }
261 
262 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
263 
264 void FGFCSComponent::Clip(void)
265 {
266  if (clip) {
267  double vmin = ClipMin->GetValue();
268  double vmax = ClipMax->GetValue();
269  double range = vmax - vmin;
270 
271  if (range < 0.0) {
272  cerr << "Trying to clip with a max value (" << vmax << ") from "
273  << ClipMax->GetName() << " lower than the min value (" << vmin
274  << ") from " << ClipMin->GetName() << "." << endl
275  << "Clipping is ignored." << endl;
276  return;
277  }
278 
279  if (cyclic_clip && range != 0.0) {
280  double value = Output - vmin;
281  Output = fmod(value, range) + vmin;
282  if (Output < vmin)
283  Output += range;
284  }
285  else
286  Output = Constrain(vmin, Output, vmax);
287  }
288 }
289 
290 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
291 //
292 // The old way of naming FCS components allowed upper or lower case, spaces,
293 // etc. but then the names were modified to fit into a property name
294 // hierarchy. This was confusing (it wasn't done intentionally - it was a
295 // carryover from the early design). We now support the direct naming of
296 // properties in the FCS component name attribute. The old way is supported in
297 // code at this time, but deprecated.
298 
299 void FGFCSComponent::bind(Element* el)
300 {
301  string tmp;
302  if (Name.find("/") == string::npos)
303  tmp = "fcs/" + PropertyManager->mkPropertyName(Name, true);
304  else
305  tmp = Name;
306 
307  bool node_exists = PropertyManager->HasNode(tmp);
308  FGPropertyNode* node = PropertyManager->GetNode(tmp, true);
309 
310  if (node) {
311  OutputNodes.push_back(node);
312  // If the node has just been created then it must be initialized to a
313  // sensible value since FGPropertyNode::GetNode() does not take care of
314  // that. If the node was already existing, its current value is kept
315  // unchanged.
316  if (!node_exists)
317  node->setDoubleValue(Output);
318  }
319  else {
320  cerr << el->ReadFrom()
321  << "Could not get or create property " << tmp << endl;
322  }
323 }
324 
325 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
326 // The bitmasked value choices are as follows:
327 // unset: In this case (the default) JSBSim would only print
328 // out the normally expected messages, essentially echoing
329 // the config files as they are read. If the environment
330 // variable is not set, debug_lvl is set to 1 internally
331 // 0: This requests JSBSim not to output any messages
332 // whatsoever.
333 // 1: This value explicity requests the normal JSBSim
334 // startup messages
335 // 2: This value asks for a message to be printed out when
336 // a class is instantiated
337 // 4: When this value is set, a message is displayed when a
338 // FGModel object executes its Run() method
339 // 8: When this value is set, various runtime state variables
340 // are printed out periodically
341 // 16: When set various parameters are sanity checked and
342 // a message is printed out when they go out of bounds
343 
344 void FGFCSComponent::Debug(int from)
345 {
346  if (debug_lvl <= 0) return;
347 
348  if (debug_lvl & 1) { // Standard console startup message output
349  if (from == 0) {
350  cout << endl << " Loading Component \"" << Name
351  << "\" of type: " << Type << endl;
352 
353  if (clip) {
354  cout << " Minimum limit: " << ClipMin->GetName() << endl;
355  cout << " Maximum limit: " << ClipMax->GetName() << endl;
356  }
357  if (delay > 0) cout <<" Frame delay: " << delay
358  << " frames (" << delay*dt << " sec)" << endl;
359  }
360  }
361  if (debug_lvl & 2 ) { // Instantiation/Destruction notification
362  if (from == 0) cout << "Instantiated: FGFCSComponent" << endl;
363  if (from == 1) cout << "Destroyed: FGFCSComponent" << endl;
364  }
365  if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
366  }
367  if (debug_lvl & 8 ) { // Runtime state variables
368  }
369  if (debug_lvl & 16) { // Sanity checking
370  }
371  if (debug_lvl & 64) {
372  if (from == 0) { // Constructor
373  }
374  }
375 }
376 }
JSBSim::FGRealValue
Represents a real value.
Definition: FGRealValue.h:57
JSBSim::Element::GetAttributeValue
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
Definition: FGXMLElement.cpp:260
JSBSim::Element::FindElement
Element * FindElement(const std::string &el="")
Searches for a specified element.
Definition: FGXMLElement.cpp:389
JSBSim::FGPropertyNode
Class wrapper for property handling.
Definition: FGPropertyManager.h:70
JSBSim::Element::GetDataAsNumber
double GetDataAsNumber(void)
Converts the element data to a number.
Definition: FGXMLElement.cpp:341
JSBSim::FGPropertyValue
Represents a property value which can use late binding.
Definition: FGPropertyValue.h:59
JSBSim::FGParameterValue
Represents a either a real value or a property value.
Definition: FGParameterValue.h:63
JSBSim::FGFCSComponent::~FGFCSComponent
virtual ~FGFCSComponent()
Destructor.
Definition: FGFCSComponent.cpp:199
JSBSim::Element::GetDataLine
std::string GetDataLine(unsigned int i=0)
Gets a line of data belonging to an element.
Definition: FGXMLElement.cpp:333
JSBSim::Element::GetName
const std::string & GetName(void) const
Retrieves the element name.
Definition: FGXMLElement.h:179
JSBSim::FGJSBBase::Constrain
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
Definition: FGJSBBase.h:333
JSBSim::FGFCS
Encapsulates the Flight Control System (FCS) functionality.
Definition: FGFCS.h:187
JSBSim::FGPropertyManager::mkPropertyName
std::string mkPropertyName(std::string name, bool lowercase)
Property-ify a name replaces spaces with '-' and, optionally, makes name all lower case.
Definition: FGPropertyManager.cpp:64
JSBSim::Element::ReadFrom
std::string ReadFrom(void) const
Return a string that contains a description of the location where the current XML element was read fr...
Definition: FGXMLElement.cpp:738
JSBSim::Element::FindNextElement
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
Definition: FGXMLElement.cpp:407
JSBSim::Element
Definition: FGXMLElement.h:143