1 // steam.cxx - Steam Gauge Calculations
3 // Copyright (C) 2000 Alexander Perry - alex.perry@ieee.org
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // General Public License for more details.
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
26 #include <simgear/compiler.h>
30 #include <simgear/constants.h>
31 #include <simgear/math/sg_types.hxx>
32 #include <simgear/misc/props.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Aircraft/aircraft.hxx>
37 # include <WeatherCM/FGLocalWeatherDatabase.h>
39 # include <Environment/environment_mgr.hxx>
40 # include <Environment/environment.hxx>
43 SG_USING_NAMESPACE(std);
45 #include "radiostack.hxx"
48 static bool isTied = false;
52 ////////////////////////////////////////////////////////////////////////
53 // Constructor and destructor.
54 ////////////////////////////////////////////////////////////////////////
57 : the_STATIC_inhg(29.92),
59 the_ALT_datum_mb(1013.0),
72 _UpdateTimePending(1000000)
83 _heading_deg_node = fgGetNode("/orientation/heading-deg", true);
84 _mag_var_deg_node = fgGetNode("/environment/magnetic-variation-deg", true);
85 _mag_dip_deg_node = fgGetNode("/environment/magnetic-dip-deg", true);
86 _engine_0_rpm_node = fgGetNode("/engines/engine[0]/rpm", true);
87 _pressure_inhg_node = fgGetNode("environment/pressure-inhg", true);
91 FGSteam::update (double dt_sec)
93 _UpdateTimePending += dt_sec;
100 fgTie("/steam/airspeed-kt", this, &FGSteam::get_ASI_kias);
101 fgSetArchivable("/steam/airspeed-kt");
102 fgTie("/steam/altitude-ft", this, &FGSteam::get_ALT_ft);
103 fgSetArchivable("/steam/altitude-ft");
104 fgTie("/steam/altimeter-datum-mb", this,
105 &FGSteam::get_ALT_datum_mb, &FGSteam::set_ALT_datum_mb,
106 false); /* don't modify the value */
107 fgSetArchivable("/steam/altimeter-datum-mb");
108 fgTie("/steam/turn-rate", this, &FGSteam::get_TC_std);
109 fgSetArchivable("/steam/turn-rate");
110 fgTie("/steam/slip-skid",this, &FGSteam::get_TC_rad);
111 fgSetArchivable("/steam/slip-skid");
112 fgTie("/steam/vertical-speed-fps", this, &FGSteam::get_VSI_fps);
113 fgSetArchivable("/steam/vertical-speed-fps");
114 fgTie("/steam/gyro-compass-deg", this, &FGSteam::get_DG_deg);
115 fgSetArchivable("/steam/gyro-compass-deg");
116 // fgTie("/steam/adf-deg", FGSteam::get_HackADF_deg);
117 // fgSetArchivable("/steam/adf-deg");
118 fgTie("/steam/gyro-compass-error-deg", this,
119 &FGSteam::get_DG_err, &FGSteam::set_DG_err,
120 false); /* don't modify the value */
121 fgSetArchivable("/steam/gyro-compass-error-deg");
122 fgTie("/steam/mag-compass-deg", this, &FGSteam::get_MH_deg);
123 fgSetArchivable("/steam/mag-compass-deg");
129 fgUntie("/steam/airspeed-kt");
130 fgUntie("/steam/altitude-ft");
131 fgUntie("/steam/altimeter-datum-mb");
132 fgUntie("/steam/turn-rate");
133 fgUntie("/steam/slip-skid");
134 fgUntie("/steam/vertical-speed-fps");
135 fgUntie("/steam/gyro-compass-deg");
136 fgUntie("/steam/gyro-compass-error-deg");
137 fgUntie("/steam/mag-compass-deg");
142 ////////////////////////////////////////////////////////////////////////
143 // Declare the functions that read the variables
144 ////////////////////////////////////////////////////////////////////////
147 FGSteam::get_ALT_ft () const
153 FGSteam::get_ALT_datum_mb () const
155 return the_ALT_datum_mb;
159 FGSteam::set_ALT_datum_mb (double datum_mb)
161 the_ALT_datum_mb = datum_mb;
165 FGSteam::get_ASI_kias () const
167 return fgGetDouble("/velocities/airspeed-kt");
171 FGSteam::get_VSI_fps () const
177 FGSteam::get_VACUUM_inhg () const
179 return the_VACUUM_inhg;
183 FGSteam::get_MH_deg () const
189 FGSteam::get_DG_deg () const
195 FGSteam::get_DG_err () const
201 FGSteam::set_DG_err (double approx_magvar)
203 the_DG_err = approx_magvar;
207 FGSteam::get_TC_rad () const
213 FGSteam::get_TC_std () const
220 ////////////////////////////////////////////////////////////////////////
221 // Recording the current time
222 ////////////////////////////////////////////////////////////////////////
225 /* tc should be (elapsed_time_between_updates / desired_smoothing_time) */
226 void FGSteam::set_lowpass ( double *outthe, double inthe, double tc )
230 { /* time went backwards; kill the filter */
233 { /* ignore mildly negative time */
237 { /* Normal mode of operation; fast approximation to exp(-tc) */
238 (*outthe) = (*outthe) * ( 1.0 - tc )
242 { /* Huge time step; assume filter has settled */
245 { /* Moderate time step; non linear response */
246 double keep = exp ( -tc );
247 // printf ( "ARP: Keep is %f\n", keep );
248 (*outthe) = (*outthe) * keep
249 + inthe * ( 1.0 - keep );
254 #define INHG_TO_MB 33.86388 /* Inches_of_mercury * INHG_TO_MB == millibars. */
256 // Convert air pressure to altitude by ICAO Standard Atmosphere
257 double pressInHgToAltFt(double p_inhg)
259 // Ref. Aviation Formulary, Ed Williams, www.best.com/~williams/avform.htm
260 const double P_0 = 29.92126; // Std. MSL pressure, inHg. (=1013.25 mb)
261 const double p_Tr = 0.2233609 * P_0; // Pressure at tropopause, same units.
262 const double h_Tr = 36089.24; // Alt of tropopause, ft. (=11.0 km)
264 if (p_inhg > p_Tr) // 0.0 to 11.0 km
265 return (1.0 - pow((p_inhg / P_0), 1.0 / 5.2558797)) / 6.8755856e-6;
266 return h_Tr + log10(p_inhg / p_Tr) / -4.806346e-5; // 11.0 to 20.0 km
267 // We could put more code for higher altitudes here.
271 // Convert altitude to air pressure by ICAO Standard Atmosphere
272 double altFtToPressInHg(double alt_ft)
274 // Ref. Aviation Formulary, Ed Williams, www.best.com/~williams/avform.htm
275 const double P_0 = 29.92126; // Std. MSL pressure, inHg. (=1013.25 mb)
276 const double p_Tr = 0.2233609 * P_0; // Pressure at tropopause, same units.
277 const double h_Tr = 36089.24; // Alt of tropopause, ft. (=11.0 km)
279 if (alt_ft < h_Tr) // 0.0 to 11.0 km
280 return P_0 * pow(1.0 - 6.8755856e-6 * alt_ft, 5.2558797);
281 return p_Tr * exp(-4.806346e-5 * (alt_ft - h_Tr)); // 11.0 to 20.0 km
282 // We could put more code for higher altitudes here.
287 ////////////////////////////////////////////////////////////////////////
288 // Here the fun really begins
289 ////////////////////////////////////////////////////////////////////////
292 void FGSteam::_CatchUp()
294 if ( _UpdateTimePending != 0 )
296 double dt = _UpdateTimePending;
297 double AccN, AccE, AccU;
299 double d, the_ENGINE_rpm;
301 /**************************
302 Someone has called our update function and
303 it turns out that we are running somewhat behind.
304 Here, we recalculate everything for a 'dt' second step.
307 /**************************
308 The ball responds to the acceleration vector in the body
309 frame, only the components perpendicular to the longitudinal
310 axis of the aircraft. This is only related to actual
311 side slip for a symmetrical aircraft which is not touching
312 the ground and not changing its attitude. Math simplifies
313 by assuming (for small angles) arctan(x)=x in radians.
314 Obvious failure mode is the absence of liquid in the
315 tube, which is there to damp the motion, so that instead
316 the ball will bounce around, hitting the tube ends.
317 More subtle flaw is having it not move or a travel limit
318 occasionally due to some dirt in the tube or on the ball.
320 d = - current_aircraft.fdm_state->get_A_Z_pilot();
322 set_lowpass ( & the_TC_rad,
323 current_aircraft.fdm_state->get_A_Y_pilot () / d,
326 /**************************
327 The rate of turn indication is from an electric gyro.
328 We should have it spin up with the master switch.
329 It is mounted at a funny angle so that it detects
330 both rate of bank (i.e. rolling into and out of turns)
331 and the rate of turn (i.e. how fast heading is changing).
333 set_lowpass ( & the_TC_std,
334 current_aircraft.fdm_state->get_Phi_dot ()
335 * SGD_RADIANS_TO_DEGREES / 20.0 +
336 current_aircraft.fdm_state->get_Psi_dot ()
337 * SGD_RADIANS_TO_DEGREES / 3.0 , dt );
339 /**************************
340 We want to know the pilot accelerations,
341 to compute the magnetic compass errors.
343 AccN = current_aircraft.fdm_state->get_V_dot_north();
344 AccE = current_aircraft.fdm_state->get_V_dot_east();
345 AccU = current_aircraft.fdm_state->get_V_dot_down()
346 - 9.81 * SG_METER_TO_FEET;
347 if ( fabs(the_TC_rad) > 0.2 /* 2.0 */ )
348 { /* Massive sideslip jams it; it stops turning */
350 the_MH_err = _heading_deg_node->getDoubleValue() - the_MH_deg;
352 { double MagDip, MagVar, CosDip;
353 double FrcN, FrcE, FrcU, AccTot;
354 double EdgN, EdgE, EdgU;
355 double TrqN, TrqE, TrqU, Torque;
356 /* Find a force vector towards exact magnetic north */
357 MagVar = _mag_var_deg_node->getDoubleValue()
358 / SGD_RADIANS_TO_DEGREES;
359 MagDip = _mag_var_deg_node->getDoubleValue()
360 / SGD_RADIANS_TO_DEGREES;
361 CosDip = cos ( MagDip );
362 FrcN = CosDip * cos ( MagVar );
363 FrcE = CosDip * sin ( MagVar );
364 FrcU = sin ( MagDip );
365 /* Rotation occurs around acceleration axis,
366 but axis magnitude is irrelevant. So compute it. */
367 AccTot = AccN*AccN + AccE*AccE + AccU*AccU;
368 if ( AccTot > 1.0 ) AccTot = sqrt ( AccTot );
370 /* Force applies to north marking on compass card */
371 EdgN = cos ( the_MH_err / SGD_RADIANS_TO_DEGREES );
372 EdgE = sin ( the_MH_err / SGD_RADIANS_TO_DEGREES );
374 /* Apply the force to the edge to get torques */
375 TrqN = EdgE * FrcU - EdgU * FrcE;
376 TrqE = EdgU * FrcN - EdgN * FrcU;
377 TrqU = EdgN * FrcE - EdgE * FrcN;
378 /* Select the component parallel to the axis */
379 Torque = ( TrqN * AccN +
381 TrqU * AccU ) * 5.0 / AccTot;
382 /* The magnetic compass has angular momentum,
383 so we apply a torque to it and wait */
385 { the_MH_degps= the_MH_degps * (1.0 - dt) - Torque;
386 the_MH_err += dt * the_MH_degps;
388 if ( the_MH_err > 180.0 ) the_MH_err -= 360.0; else
389 if ( the_MH_err < -180.0 ) the_MH_err += 360.0;
390 the_MH_deg = _heading_deg_node->getDoubleValue() - the_MH_err;
393 /**************************
394 This is not actually correct, but provides a
395 scaling capability for the vacuum pump later on.
396 When we have a real engine model, we can ask it.
398 the_ENGINE_rpm = _engine_0_rpm_node->getDoubleValue();
400 /**************************
401 First, we need to know what the static line is reporting,
402 which is a whole simulation area in itself.
403 We filter the actual value by one second to
404 account for the line impedance of the plumbing.
407 sgVec3 plane_pos = { cur_fdm_state->get_Latitude(),
408 cur_fdm_state->get_Longitude(),
409 cur_fdm_state->get_Altitude() * SG_FEET_TO_METER };
410 double static_inhg = WeatherDatabase->get(plane_pos).AirPressure *
413 double static_inhg = _pressure_inhg_node->getDoubleValue();
416 set_lowpass ( & the_STATIC_inhg, static_inhg, dt );
419 NO alternate static source error (student feature),
420 NO possibility of blockage (instructor feature),
421 NO slip-induced error, important for C172 for example.
424 /**************************
426 ICAO standard atmosphere MSL pressure is 1013.25 mb, and pressure
427 gradient is about 28 ft per mb at MSL increasing to about 32 at
428 5000 and 38 at 10000 ft.
429 Standard altimeters apply the subscale offset to the output altitude,
430 not to the input pressure; I don't know exactly what pressure gradient
431 they assume for this. I choose to make it accurate at low altitudes.
432 Remember, we are trying to simulate a real altimeter, not an ideal one.
434 set_lowpass ( & the_ALT_ft,
435 pressInHgToAltFt(the_STATIC_inhg) +
436 (the_ALT_datum_mb - 1013.25) * 28.0, /* accurate at low alt. */
437 dt * 10 ); /* smoothing time 0.1 s */
439 /**************************
440 The VSI case is a low-pass filter of the static line pressure.
441 The instrument reports the difference, scaled to approx ft.
442 NO option for student to break glass when static source fails.
443 NO capability for a fixed non-zero reading when level.
444 NO capability to have a scaling error of maybe a factor of two.
446 the_VSI_fps = ( the_VSI_case - the_STATIC_inhg )
447 * 10000.0; /* manual scaling factor */
448 set_lowpass ( & the_VSI_case, the_STATIC_inhg, dt/6.0 );
450 /**************************
451 The engine driven vacuum pump is directly attached
452 to the engine shaft, so each engine rotation pumps
453 a fixed volume. The amount of air in that volume
454 is determined by the vacuum line's internal pressure.
455 The instruments are essentially leaking air like
456 a fixed source impedance from atmospheric pressure.
457 The regulator provides a digital limit setting,
458 which is open circuit unless the pressure drop is big.
459 Thus, we can compute the vacuum line pressure directly.
460 We assume that there is negligible reservoir space.
461 NO failure of the pump supported (yet)
463 the_VACUUM_inhg = the_STATIC_inhg *
464 the_ENGINE_rpm / ( the_ENGINE_rpm + 10000.0 );
465 if ( the_VACUUM_inhg > 5.0 )
466 the_VACUUM_inhg = 5.0;
469 > I was merely going to do the engine rpm driven vacuum pump for both
470 > the AI and DG, have the gyros spin down down in power off descents,
471 > have it tumble when you exceed the usual pitch or bank limits,
472 > put in those insidious turning errors ... for now anyway.
474 if ( _UpdateTimePending > 999999 )
475 the_DG_err = fgGetDouble("/environment/magnetic-variation-deg");
476 the_DG_degps = 0.01; /* HACK! */
477 if (dt<1.0) the_DG_err += dt * the_DG_degps;
478 the_DG_deg = _heading_deg_node->getDoubleValue() - the_DG_err;
480 /**************************
481 Finished updates, now clear the timer
483 _UpdateTimePending = 0;
485 // cout << "0 Updates pending" << endl;
490 ////////////////////////////////////////////////////////////////////////
491 // Everything below is a transient hack; expect it to disappear
492 ////////////////////////////////////////////////////////////////////////
494 double FGSteam::get_HackOBS1_deg () const
496 return current_radiostack->get_nav1_radial();
499 double FGSteam::get_HackOBS2_deg () const
501 return current_radiostack->get_nav2_radial();