JSBSim Flight Dynamics Model  1.2.0 (05 Nov 2023)
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  auto 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, init_element));
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, input_element));
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  string delay_str = delay_elem->GetDataLine();
151  FGParameterValue delayParam(delay_str, PropertyManager, delay_elem);
152  delay_time = delayParam.GetValue();
153  string delayType = delay_elem->GetAttributeValue("type");
154  if (delayType.length() > 0) {
155  if (delayType == "time") {
156  delay = (unsigned int)(delay_time / dt);
157  } else if (delayType == "frames") {
158  delay = (unsigned int)delay_time;
159  } else {
160  cerr << "Unallowed delay type" << endl;
161  }
162  } else {
163  delay = (unsigned int)(delay_time / dt);
164  }
165  output_array.resize(delay);
166  for (unsigned int i=0; i<delay; i++) output_array[i] = 0.0;
167  }
168 
169  Element *clip_el = element->FindElement("clipto");
170  if (clip_el) {
171  Element* el = clip_el->FindElement("min");
172  if (!el) {
173  cerr << clip_el->ReadFrom()
174  << "Element <min> is missing, <clipto> is ignored." << endl;
175  return;
176  }
177 
178  ClipMin = new FGParameterValue(el, PropertyManager);
179 
180  el = clip_el->FindElement("max");
181  if (!el) {
182  cerr << clip_el->ReadFrom()
183  << "Element <max> is missing, <clipto> is ignored." << endl;
184  ClipMin = nullptr;
185  return;
186  }
187 
188  ClipMax = new FGParameterValue(el, PropertyManager);
189 
190  if (clip_el->GetAttributeValue("type") == "cyclic")
191  cyclic_clip = true;
192 
193  clip = true;
194  }
195 
196  Debug(0);
197 }
198 
199 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
200 
202 {
203  Debug(1);
204 }
205 
206 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
207 
208 void FGFCSComponent::ResetPastStates(void)
209 {
210  index = 0;
211  for (auto &elm: output_array)
212  elm = 0.0;
213 }
214 
215 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
216 
217 void FGFCSComponent::CheckInputNodes(size_t MinNodes, size_t MaxNodes, Element* el)
218 {
219  size_t num = InputNodes.size();
220 
221  if (num < MinNodes) {
222  cerr << el->ReadFrom()
223  << " Not enough <input> nodes are provided" << endl
224  << " Expecting " << MinNodes << " while " << num
225  << " are provided." << endl;
226  throw("Some inputs are missing.");
227  }
228 
229  if (num > MaxNodes) {
230  cerr << el->ReadFrom()
231  << " Too many <input> nodes are provided" << endl
232  << " Expecting " << MaxNodes << " while " << num
233  << " are provided." << endl
234  << " The last " << num-MaxNodes << " input nodes will be ignored."
235  << endl;
236  }
237 }
238 
239 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
240 
241 void FGFCSComponent::SetOutput(void)
242 {
243  for (auto node: OutputNodes)
244  node->setDoubleValue(Output);
245 }
246 
247 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
248 
249 void FGFCSComponent::Delay(void)
250 {
251  if (fcs->GetTrimStatus()) {
252  // Update the whole history while trim routines are executing.
253  // Don't want to model delays while calculating a trim solution.
254  std::fill(output_array.begin(), output_array.end(), Output);
255  }
256  else {
257  output_array[index] = Output;
258  if ((unsigned int)index == delay-1) index = 0;
259  else index++;
260  Output = output_array[index];
261  }
262 }
263 
264 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
265 
266 void FGFCSComponent::Clip(void)
267 {
268  if (clip) {
269  double vmin = ClipMin->GetValue();
270  double vmax = ClipMax->GetValue();
271  double range = vmax - vmin;
272 
273  if (range < 0.0) {
274  cerr << "Trying to clip with a max value (" << vmax << ") from "
275  << ClipMax->GetName() << " lower than the min value (" << vmin
276  << ") from " << ClipMin->GetName() << "." << endl
277  << "Clipping is ignored." << endl;
278  return;
279  }
280 
281  if (cyclic_clip && range != 0.0) {
282  double value = Output - vmin;
283  Output = fmod(value, range) + vmin;
284  if (Output < vmin)
285  Output += range;
286  }
287  else
288  Output = Constrain(vmin, Output, vmax);
289  }
290 }
291 
292 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
293 //
294 // The old way of naming FCS components allowed upper or lower case, spaces,
295 // etc. but then the names were modified to fit into a property name
296 // hierarchy. This was confusing (it wasn't done intentionally - it was a
297 // carryover from the early design). We now support the direct naming of
298 // properties in the FCS component name attribute. The old way is supported in
299 // code at this time, but deprecated.
300 
301 void FGFCSComponent::bind(Element* el, FGPropertyManager* PropertyManager)
302 {
303  string tmp;
304  if (Name.find("/") == string::npos)
305  tmp = "fcs/" + PropertyManager->mkPropertyName(Name, true);
306  else
307  tmp = Name;
308 
309  bool node_exists = PropertyManager->HasNode(tmp);
310  FGPropertyNode* node = PropertyManager->GetNode(tmp, true);
311 
312  if (node) {
313  OutputNodes.push_back(node);
314  // If the node has just been created then it must be initialized to a
315  // sensible value since FGPropertyNode::GetNode() does not take care of
316  // that. If the node was already existing, its current value is kept
317  // unchanged.
318  if (!node_exists)
319  node->setDoubleValue(Output);
320  }
321  else {
322  cerr << el->ReadFrom()
323  << "Could not get or create property " << tmp << endl;
324  }
325 }
326 
327 //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
328 // The bitmasked value choices are as follows:
329 // unset: In this case (the default) JSBSim would only print
330 // out the normally expected messages, essentially echoing
331 // the config files as they are read. If the environment
332 // variable is not set, debug_lvl is set to 1 internally
333 // 0: This requests JSBSim not to output any messages
334 // whatsoever.
335 // 1: This value explicity requests the normal JSBSim
336 // startup messages
337 // 2: This value asks for a message to be printed out when
338 // a class is instantiated
339 // 4: When this value is set, a message is displayed when a
340 // FGModel object executes its Run() method
341 // 8: When this value is set, various runtime state variables
342 // are printed out periodically
343 // 16: When set various parameters are sanity checked and
344 // a message is printed out when they go out of bounds
345 
346 void FGFCSComponent::Debug(int from)
347 {
348  if (debug_lvl <= 0) return;
349 
350  if (debug_lvl & 1) { // Standard console startup message output
351  if (from == 0) {
352  cout << endl << " Loading Component \"" << Name
353  << "\" of type: " << Type << endl;
354 
355  if (clip) {
356  cout << " Minimum limit: " << ClipMin->GetName() << endl;
357  cout << " Maximum limit: " << ClipMax->GetName() << endl;
358  }
359  if (delay > 0) cout <<" Frame delay: " << delay
360  << " frames (" << delay*dt << " sec)" << endl;
361  }
362  }
363  if (debug_lvl & 2 ) { // Instantiation/Destruction notification
364  if (from == 0) cout << "Instantiated: FGFCSComponent" << endl;
365  if (from == 1) cout << "Destroyed: FGFCSComponent" << endl;
366  }
367  if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
368  }
369  if (debug_lvl & 8 ) { // Runtime state variables
370  }
371  if (debug_lvl & 16) { // Sanity checking
372  }
373  if (debug_lvl & 64) {
374  if (from == 0) { // Constructor
375  }
376  }
377 }
378 }
Element * FindElement(const std::string &el="")
Searches for a specified element.
std::string GetAttributeValue(const std::string &key)
Retrieves an attribute.
std::string GetDataLine(unsigned int i=0)
Gets a line of data belonging to an element.
std::string ReadFrom(void) const
Return a string that contains a description of the location where the current XML element was read fr...
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
const std::string & GetName(void) const
Retrieves the element name.
Definition: FGXMLElement.h:179
virtual ~FGFCSComponent()
Destructor.
Encapsulates the Flight Control System (FCS) functionality.
Definition: FGFCS.h:189
static constexpr double Constrain(double min, double value, double max)
Constrain a value between a minimum and a maximum value.
Definition: FGJSBBase.h:289
Represents a either a real value or a property value.
Class wrapper for property handling.
FGPropertyNode * GetNode(const std::string &path, bool create=false)
Get a property node.
Represents a property value which can use late binding.
Represents a real value.
Definition: FGRealValue.h:58