FlightGear next
mag_compass.cxx
Go to the documentation of this file.
1// mag_compass.cxx - a magnetic compass.
2// Written by David Megginson, started 2003.
3//
4// This file is in the Public Domain and comes with no warranty.
5
6// This implementation is derived from an earlier one by Alex Perry,
7// which appeared in src/Cockpit/steam.cxx
8
9#ifdef HAVE_CONFIG_H
10# include <config.h>
11#endif
12
13#include <simgear/sg_inlines.h>
14#include <simgear/math/SGMath.hxx>
15
16#include <Main/fg_props.hxx>
17#include <Main/util.hxx>
18
19#include "mag_compass.hxx"
20
21MagCompass::MagCompass ( SGPropertyNode *node )
22 : _rate_degps(0.0),
23 _name(node->getStringValue("name", "magnetic-compass")),
24 _num(node->getIntValue("number", 0))
25{
26 SGPropertyNode_ptr n = node->getNode( "deviation", false );
27 if( n ) {
28 SGPropertyNode_ptr deviation_table_node = n->getNode( "table", false );
29 if( NULL != deviation_table_node ) {
30 _deviation_table = new SGInterpTable( deviation_table_node );
31 } else {
32 std::string deviation_node_name = n->getStringValue();
33 if( !deviation_node_name.empty() )
34 _deviation_node = fgGetNode( deviation_node_name, true );
35 }
36 }
37
38 _cfg_viscosity = node->getDoubleValue("fluid-viscosity", 8.2);
39}
40
44
45void
47{
48 std::string branch;
49 branch = "/instrumentation/" + _name;
50
51 SGPropertyNode *node = fgGetNode(branch, _num, true );
52 _serviceable_node = node->getChild("serviceable", 0, true);
53 _pitch_offset_node = node->getChild("pitch-offset-deg", 0, true);
54 _roll_node = fgGetNode("/orientation/roll-deg", true);
55 _pitch_node = fgGetNode("/orientation/pitch-deg", true);
56 _heading_node = fgGetNode("/orientation/heading-magnetic-deg", true);
57 _beta_node = fgGetNode("/orientation/side-slip-deg", true);
58 _dip_node = fgGetNode("/environment/magnetic-dip-deg", true);
59 _x_accel_node = fgGetNode("/accelerations/pilot/x-accel-fps_sec", true);
60 _y_accel_node = fgGetNode("/accelerations/pilot/y-accel-fps_sec", true);
61 _z_accel_node = fgGetNode("/accelerations/pilot/z-accel-fps_sec", true);
62 _out_node = node->getChild("indicated-heading-deg", 0, true);
63 _roll_out_node = node->getChild("roll-deg", 0, true);
64 _pitch_out_node = node->getChild("pitch-deg", 0, true);
65 _fluid_viscosity = node->getChild("fluid-viscosity", 0, true);
66
67 reinit();
68}
69
70void
72{
73 _rate_degps = 0.0;
74 _fluid_viscosity->setDoubleValue(_cfg_viscosity);
75}
76
77void
78MagCompass::update (double delta_time_sec)
79{
80 // This is the real magnetic
81 // which would be displayed
82 // if the compass had no errors.
83 //double heading_mag_deg = _heading_node->getDoubleValue();
84
85
86 // don't update if the compass
87 // is broken
88 if (!_serviceable_node->getBoolValue())
89 return;
90
91 /*
92 * Calculate roll/pitch-filter-factor based on fluid viscosity
93 *
94 * Note: This is currently very naive/simple. I lack the physics to do a good
95 * formula here; it is guesstimeated on Kerosene (viscosity about 8) and
96 * visual damping on a standard compass.
97 */
98 double fluid_damping = 5.0/8.0 * _fluid_viscosity->getDoubleValue() * 10;
99
100
101 /*
102 * Vassilii: commented out because this way, even when parked,
103 * w/o any accelerations and level, the compass is jammed.
104 * If somebody wants to model jamming, real forces (i.e. accelerations)
105 * and not sideslip angle must be considered.
106 */
107#if 0
108 // jam on excessive sideslip
109 if (fabs(_beta_node->getDoubleValue()) > 12.0) {
110 _rate_degps = 0.0;
111 return;
112 }
113#endif
114
115
116 /*
117 Formula for northernly turning error from
118 http://williams.best.vwh.net/compass/node4.html:
119
120 Hc: compass heading
121 psi: magnetic heading
122 theta: bank angle (right positive; should be phi here)
123 mu: dip angle (down positive)
124
125 Hc = atan2(sin(Hm)cos(theta)-tan(mu)sin(theta), cos(Hm))
126
127 This function changes the variable names to the more common psi
128 for the heading, theta for the pitch, and phi for the roll (and
129 target_deg for Hc). It also modifies the equation to
130 incorporate pitch as well as roll, as suggested by Chris
131 Metzler.
132 */
133
134 // bank angle (radians)
135 double phi = _roll_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
136
137 // pitch angle (radians)
138 double theta = _pitch_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS
139 + _pitch_offset_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
140
141 // magnetic heading (radians)
142 double psi = _heading_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
143
144 // magnetic dip (radians)
145 double mu = _dip_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
146
147
148 /*
149 Tilt adjustments for accelerations.
150
151 The magnitudes of these are totally made up, but in real life,
152 they would depend on the fluid level, the amount of friction,
153 etc. anyway. Basically, the compass float tilts forward for
154 acceleration and backward for deceleration. Tilt about 4
155 degrees (0.07 radians) for every G (32 fps/sec) of
156 acceleration.
157
158 TODO: do something with the vertical acceleration.
159 */
160 double x_accel_g = _x_accel_node->getDoubleValue() / 32;
161 double y_accel_g = _y_accel_node->getDoubleValue() / 32;
162 //double z_accel_g = _z_accel_node->getDoubleValue() / 32;
163
164 theta -= 0.07 * x_accel_g;
165 phi -= 0.07 * y_accel_g;
166
167 // Expose pitch and roll of the disc
168 double d = -_z_accel_node->getDoubleValue();
169 if (d < 1.0) d = 1.0;
170 double x_factor_norm = _x_accel_node->getDoubleValue() / d * 10.0;
171 double y_factor_norm = _y_accel_node->getDoubleValue() / d * 10.0;
172
173 double roll = phi * SGD_RADIANS_TO_DEGREES * abs(y_factor_norm);
174 roll = flightgear::filterExponential(_last_roll, roll, fluid_damping);
175 _roll_out_node->setDoubleValue(roll);
176 _last_roll = roll;
177
178 double pitch = -theta * SGD_RADIANS_TO_DEGREES * abs(x_factor_norm);
179 pitch = flightgear::filterExponential(_last_pitch, pitch, fluid_damping);
180 _pitch_out_node->setDoubleValue(pitch);
181 _last_pitch = pitch;
182
183
185 // calculate target compass heading degrees
187
188 // these are expensive: don't repeat
189 double sin_phi = sin(phi);
190 double sin_theta = sin(theta);
191 double sin_mu = sin(mu);
192 double cos_theta = cos(theta);
193 double cos_psi = cos(psi);
194 double cos_mu = cos(mu);
195
196 double a = cos(phi) * sin(psi) * cos_mu
197 - sin_phi * cos_theta * sin_mu
198 - sin_phi* sin_theta * cos_mu * cos_psi;
199
200 double b = cos_theta * cos_psi * cos(mu)
201 - sin_theta * sin_mu;
202
203 // This is the value that the compass
204 // is *trying* to display.
205 double target_deg = atan2(a, b) * SGD_RADIANS_TO_DEGREES;
206
207 if( _deviation_node ) {
208 target_deg -= _deviation_node->getDoubleValue();
209 } else if( _deviation_table ) {
210 target_deg -= _deviation_table->interpolate( SGMiscd::normalizePeriodic( 0.0, 360.0, target_deg ) );
211 }
212
213 double old_deg = _out_node->getDoubleValue();
214
215 while ((target_deg - old_deg) > 180.0)
216 target_deg -= 360.0;
217 while ((target_deg - old_deg) < -180.0)
218 target_deg += 360.0;
219
220 // The compass has a current rate of
221 // rotation -- move the rate of rotation
222 // towards one that will turn the compass
223 // to the correct heading, but lag a bit.
224 // (so that the compass can keep overshooting
225 // and coming back).
226 double error = target_deg - old_deg;
227 _rate_degps = fgGetLowPass(_rate_degps, error, delta_time_sec / 5.0);
228 double indicated_deg = old_deg + _rate_degps * delta_time_sec;
229 SG_NORMALIZE_RANGE(indicated_deg, 0.0, 360.0);
230
231 // That's it -- set the messed-up heading.
232 _out_node->setDoubleValue(indicated_deg);
233}
234
235
236// Register the subsystem.
237#if 0
238SGSubsystemMgr::InstancedRegistrant<MagCompass> registrantMagCompass(
239 SGSubsystemMgr::FDM,
240 {{"instrumentation", SGSubsystemMgr::Dependency::HARD}});
241#endif
242
243// end of altimeter.cxx
void init() override
virtual ~MagCompass()
void reinit() override
void update(double dt) override
double filterExponential(double current, double target, double timeratio)
exponential filter
Definition util.cxx:101
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.
Definition proptest.cpp:27
double fgGetLowPass(double current, double target, double timeratio)
Move a value towards a target.
Definition util.cxx:46