JSBSim Flight Dynamics Model 1.3.0 (09 Apr 2026)
An Open Source Flight Dynamics and Control Software Library in C++
Loading...
Searching...
No Matches
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
26FUNCTIONAL DESCRIPTION
27--------------------------------------------------------------------------------
28
29HISTORY
30--------------------------------------------------------------------------------
31
32%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33COMMENTS, REFERENCES, and NOTES
34%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
35
36%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
37INCLUDES
38%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
39
40#include "FGFCSComponent.h"
41#include "models/FGFCS.h"
42#include "math/FGParameterValue.h"
43#include "input_output/FGLog.h"
44
45using namespace std;
46
47namespace JSBSim {
48
49/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
50CLASS IMPLEMENTATION
51%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
52
53FGFCSComponent::FGFCSComponent(FGFCS* _fcs, Element* element) : fcs(_fcs)
54{
55 Input = Output = delay_time = 0.0;
56 delay = index = 0;
57 ClipMin = ClipMax = new FGRealValue(0.0);
58 clip = cyclic_clip = false;
59 dt = fcs->GetChannelDeltaT();
60
61 auto PropertyManager = fcs->GetPropertyManager();
62 if (element->GetName() == string("lag_filter")) {
63 Type = "LAG_FILTER";
64 } else if (element->GetName() == string("lead_lag_filter")) {
65 Type = "LEAD_LAG_FILTER";
66 } else if (element->GetName() == string("washout_filter")) {
67 Type = "WASHOUT_FILTER";
68 } else if (element->GetName() == string("second_order_filter")) {
69 Type = "SECOND_ORDER_FILTER";
70 } else if (element->GetName() == string("integrator")) {
71 Type = "INTEGRATOR";
72 } else if (element->GetName() == string("summer")) {
73 Type = "SUMMER";
74 } else if (element->GetName() == string("pure_gain")) {
75 Type = "PURE_GAIN";
76 } else if (element->GetName() == string("scheduled_gain")) {
77 Type = "SCHEDULED_GAIN";
78 } else if (element->GetName() == string("aerosurface_scale")) {
79 Type = "AEROSURFACE_SCALE";
80 } else if (element->GetName() == string("switch")) {
81 Type = "SWITCH";
82 } else if (element->GetName() == string("kinematic")) {
83 Type = "KINEMATIC";
84 } else if (element->GetName() == string("deadband")) {
85 Type = "DEADBAND";
86 } else if (element->GetName() == string("fcs_function")) {
87 Type = "FCS_FUNCTION";
88 } else if (element->GetName() == string("pid")) {
89 Type = "PID";
90 } else if (element->GetName() == string("sensor")) {
91 Type = "SENSOR";
92 } else if (element->GetName() == string("accelerometer")) {
93 Type = "ACCELEROMETER";
94 } else if (element->GetName() == string("magnetometer")) {
95 Type = "MAGNETOMETER";
96 } else if (element->GetName() == string("gyro")) {
97 Type = "GYRO";
98 } else if (element->GetName() == string("actuator")) {
99 Type = "ACTUATOR";
100 } else if (element->GetName() == string("waypoint_heading")) {
101 Type = "WAYPOINT_HEADING";
102 } else if (element->GetName() == string("waypoint_distance")) {
103 Type = "WAYPOINT_DISTANCE";
104 } else if (element->GetName() == string("angle")) {
105 Type = "ANGLE";
106 } else if (element->GetName() == string("distributor")) {
107 Type = "DISTRIBUTOR";
108 } else { // illegal component in this channel
109 Type = "UNKNOWN";
110 }
111
112 Name = element->GetAttributeValue("name");
113
114 Element *init_element = element->FindElement("init");
115 while (init_element) {
116 InitNodes.push_back(new FGPropertyValue(init_element->GetDataLine(),
117 PropertyManager, init_element));
118 init_element = element->FindNextElement("init");
119 }
120
121 Element *input_element = element->FindElement("input");
122 while (input_element) {
123 InputNodes.push_back(new FGPropertyValue(input_element->GetDataLine(),
124 PropertyManager, input_element));
125
126 input_element = element->FindNextElement("input");
127 }
128
129 Element *out_elem = element->FindElement("output");
130 while (out_elem) {
131 string output_node_name = out_elem->GetDataLine();
132 bool node_exists = PropertyManager->HasNode(output_node_name);
133 SGPropertyNode* OutputNode = PropertyManager->GetNode( output_node_name, true );
134 if (!OutputNode) {
135 XMLLogException err(out_elem);
136 err << " Unable to process property: " << output_node_name << "\n";
137 throw err;
138 }
139 OutputNodes.push_back(OutputNode);
140 // If the node has just been created then it must be initialized to a
141 // sensible value since FGPropertyNode::GetNode() does not take care of
142 // that. If the node was already existing, its current value is kept
143 // unchanged.
144 if (!node_exists)
145 OutputNode->setDoubleValue(Output);
146 out_elem = element->FindNextElement("output");
147 }
148
149 Element* delay_elem = element->FindElement("delay");
150 if ( delay_elem ) {
151 string delay_str = delay_elem->GetDataLine();
152 FGParameterValue delayParam(delay_str, PropertyManager, delay_elem);
153 delay_time = delayParam.GetValue();
154 string delayType = delay_elem->GetAttributeValue("type");
155 if (delayType.length() > 0) {
156 if (delayType == "time") {
157 delay = (unsigned int)(delay_time / dt);
158 } else if (delayType == "frames") {
159 delay = (unsigned int)delay_time;
160 } else {
161 FGXMLLogging log(delay_elem, LogLevel::ERROR);
162 log << "Unallowed delay type\n";
163 }
164 } else {
165 delay = (unsigned int)(delay_time / dt);
166 }
167 output_array.resize(delay);
168 for (unsigned int i=0; i<delay; i++) output_array[i] = 0.0;
169 }
170
171 Element *clip_el = element->FindElement("clipto");
172 if (clip_el) {
173 Element* el = clip_el->FindElement("min");
174 if (!el) {
175 FGXMLLogging log(clip_el, LogLevel::ERROR);
176 log << "Element <min> is missing, <clipto> is ignored.\n";
177 return;
178 }
179
180 ClipMin = new FGParameterValue(el, PropertyManager);
181
182 el = clip_el->FindElement("max");
183 if (!el) {
184 FGXMLLogging log(clip_el, LogLevel::ERROR);
185 log << "Element <max> is missing, <clipto> is ignored.\n";
186 ClipMin = nullptr;
187 return;
188 }
189
190 ClipMax = new FGParameterValue(el, PropertyManager);
191
192 if (clip_el->GetAttributeValue("type") == "cyclic")
193 cyclic_clip = true;
194
195 clip = true;
196 }
197
198 Debug(0);
199}
200
201//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
202
204{
205 Debug(1);
206}
207
208//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
209
210void FGFCSComponent::ResetPastStates(void)
211{
212 index = 0;
213 for (auto &elm: output_array)
214 elm = 0.0;
215}
216
217//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
218
219void FGFCSComponent::CheckInputNodes(size_t MinNodes, size_t MaxNodes, Element* el)
220{
221 size_t num = InputNodes.size();
222
223 if (num < MinNodes) {
224 XMLLogException err(el);
225 err << " Not enough <input> nodes are provided\n"
226 << " Expecting " << MinNodes << " while " << num
227 << " are provided.\n";
228 throw err;
229 }
230
231 if (num > MaxNodes) {
232 FGXMLLogging log(el, LogLevel::ERROR);
233 log << " Too many <input> nodes are provided\n"
234 << " Expecting " << MaxNodes << " while " << num
235 << " are provided.\n"
236 << " The last " << num-MaxNodes << " input nodes will be ignored.\n";
237 }
238}
239
240//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
241
242void FGFCSComponent::SetOutput(void)
243{
244 for (auto node: OutputNodes)
245 node->setDoubleValue(Output);
246}
247
248//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
249
250void FGFCSComponent::Delay(void)
251{
252 if (fcs->GetTrimStatus()) {
253 // Update the whole history while trim routines are executing.
254 // Don't want to model delays while calculating a trim solution.
255 std::fill(output_array.begin(), output_array.end(), Output);
256 }
257 else {
258 output_array[index] = Output;
259 if ((unsigned int)index == delay-1) index = 0;
260 else index++;
261 Output = output_array[index];
262 }
263}
264
265//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
266
267void FGFCSComponent::Clip(void)
268{
269 if (clip) {
270 double vmin = ClipMin->GetValue();
271 double vmax = ClipMax->GetValue();
272 double range = vmax - vmin;
273
274 if (range < 0.0) {
275 FGLogging log(LogLevel::ERROR);
276 log << "Trying to clip with a max value (" << fixed << vmax << ") from "
277 << ClipMax->GetName() << " lower than the min value (" << vmin
278 << ") from " << ClipMin->GetName() << ".\n"
279 << "Clipping is ignored.\n";
280 return;
281 }
282
283 if (cyclic_clip && range != 0.0) {
284 double value = Output - vmin;
285 Output = fmod(value, range) + vmin;
286 if (Output < vmin)
287 Output += range;
288 }
289 else
290 Output = Constrain(vmin, Output, vmax);
291 }
292}
293
294//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
295//
296// The old way of naming FCS components allowed upper or lower case, spaces,
297// etc. but then the names were modified to fit into a property name
298// hierarchy. This was confusing (it wasn't done intentionally - it was a
299// carryover from the early design). We now support the direct naming of
300// properties in the FCS component name attribute. The old way is supported in
301// code at this time, but deprecated.
302
303void FGFCSComponent::bind(Element* el, FGPropertyManager* PropertyManager)
304{
305 string tmp;
306 if (Name.find("/") == string::npos)
307 tmp = "fcs/" + PropertyManager->mkPropertyName(Name, true);
308 else
309 tmp = Name;
310
311 bool node_exists = PropertyManager->HasNode(tmp);
312 SGPropertyNode* node = PropertyManager->GetNode(tmp, true);
313
314 if (node) {
315 OutputNodes.push_back(node);
316 // If the node has just been created then it must be initialized to a
317 // sensible value since FGPropertyNode::GetNode() does not take care of
318 // that. If the node was already existing, its current value is kept
319 // unchanged.
320 if (!node_exists)
321 node->setDoubleValue(Output);
322 }
323 else {
324 FGXMLLogging log(el, LogLevel::ERROR);
325 log << "Could not get or create property " << tmp << "\n";
326 }
327}
328
329//%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
330// The bitmasked value choices are as follows:
331// unset: In this case (the default) JSBSim would only print
332// out the normally expected messages, essentially echoing
333// the config files as they are read. If the environment
334// variable is not set, debug_lvl is set to 1 internally
335// 0: This requests JSBSim not to output any messages
336// whatsoever.
337// 1: This value explicitly requests the normal JSBSim
338// startup messages
339// 2: This value asks for a message to be printed out when
340// a class is instantiated
341// 4: When this value is set, a message is displayed when a
342// FGModel object executes its Run() method
343// 8: When this value is set, various runtime state variables
344// are printed out periodically
345// 16: When set various parameters are sanity checked and
346// a message is printed out when they go out of bounds
347
348void FGFCSComponent::Debug(int from)
349{
350 if (debug_lvl <= 0) return;
351
352 if (debug_lvl & 1) { // Standard console startup message output
353 if (from == 0) {
354 FGLogging log(LogLevel::DEBUG);
355 log << "\n Loading Component \"" << Name << fixed
356 << "\" of type: " << Type << "\n";
357
358 if (clip) {
359 log << " Minimum limit: " << ClipMin->GetName() << "\n";
360 log << " Maximum limit: " << ClipMax->GetName() << "\n";
361 }
362 if (delay > 0) log <<" Frame delay: " << delay << fixed
363 << setprecision(4) << " frames (" << delay*dt << " sec)\n";
364 }
365 }
366 if (debug_lvl & 2 ) { // Instantiation/Destruction notification
367 FGLogging log(LogLevel::DEBUG);
368 if (from == 0) log << "Instantiated: FGFCSComponent\n";
369 if (from == 1) log << "Destroyed: FGFCSComponent\n";
370 }
371 if (debug_lvl & 4 ) { // Run() method entry print for FGModel-derived objects
372 }
373 if (debug_lvl & 8 ) { // Runtime state variables
374 }
375 if (debug_lvl & 16) { // Sanity checking
376 }
377 if (debug_lvl & 64) {
378 if (from == 0) { // Constructor
379 }
380 }
381}
382}
Element * FindElement(const std::string &el="")
Searches for a specified element.
const std::string & GetName(void) const
Retrieves the element name.
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.
Element * FindNextElement(const std::string &el="")
Searches for the next element as specified.
virtual ~FGFCSComponent()
Destructor.
FGFCSComponent(FGFCS *fcs, Element *el)
Constructor.
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.
Represents a property value which can use late binding.
Represents a real value.
Definition FGRealValue.h:58
A node in a property tree.
Definition props.hxx:747
bool setDoubleValue(double value)
Set a double value for this node.
Main namespace for the JSBSim Flight Dynamics Model.
Definition FGFDMExec.cpp:71