kcs55.nas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  1. #############################################################################
  2. # This file is part of FlightGear, the free flight simulator
  3. # http://www.flightgear.org/
  4. #
  5. # Copyright (C) 2009 Torsten Dreyer, Torsten (at) t3r _dot_ de
  6. #
  7. # This program is free software; you can redistribute it and/or
  8. # modify it under the terms of the GNU General Public License as
  9. # published by the Free Software Foundation; either version 2 of the
  10. # License, or (at your option) any later version.
  11. #
  12. # This program is distributed in the hope that it will be useful, but
  13. # WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. # General Public License for more details.
  16. #############################################################################
  17. # This is a model of the HONEYWELL BENDIX/KING KCS55/55a
  18. # Pictorial Navigation System
  19. #
  20. # It is based on the corresponding manuals
  21. # - Installation Manual 006-00111-0010, 10 February 2002
  22. # - Pilots Guide KFC225 006-18035-0000, September 2004, Part KCS55A
  23. # - Real life experience
  24. #
  25. #
  26. # Copyright (C) 2008 Torsten Dreyer - Torsten (at) t3r _dot_ de
  27. # The names HONEYWELL, BENDIX/KING are copyright of the owners
  28. #
  29. # This program is free software; you can redistribute it and/or
  30. # modify it under the terms of the GNU General Public License as
  31. # published by the Free Software Foundation; either version 2 of the
  32. # License, or (at your option) any later version.
  33. #
  34. # This program is distributed in the hope that it will be useful, but
  35. # WITHOUT ANY WARRANTY; without even the implied warranty of
  36. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  37. # General Public License for more details.
  38. #
  39. # You should have received a copy of the GNU General Public License
  40. # along with this program; if not, write to the Free Software
  41. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  42. #
  43. var D2R = globals.D2R;
  44. var R2D = globals.R2D;
  45. var getLowPass = func( current, target, timeratio ) {
  46. if ( timeratio < 0.0 ) {
  47. if ( timeratio < -1.0 ) {
  48. #time went backwards; kill the filter
  49. return target;
  50. }
  51. # ignore mildly negative time
  52. return current;
  53. }
  54. if ( timeratio < 0.2 ) {
  55. # Normal mode of operation; fast
  56. # approximation to exp(-timeratio)
  57. return current * (1.0 - timeratio) + target * timeratio;
  58. }
  59. if ( timeratio > 5.0 ) {
  60. # Huge time step; assume filter has settled
  61. return target;
  62. } else {
  63. # Moderate time step; non linear response
  64. var catch = [];
  65. var keep = 1;
  66. # sometimes math.exp(-0) throws an exception
  67. # this hack catches this unmotivated exception and
  68. # just ignores the low pass filter
  69. # it will go away sometimes
  70. call(func{keep = math.exp(-timeratio);}, [], nil, nil, catch );
  71. if( size(catch) ) {
  72. return target;
  73. } else {
  74. return current * keep + target * (1.0 - keep);
  75. }
  76. }
  77. return target; # unreached code
  78. }
  79. # The fluxgate
  80. # based on mag_compass.cxx
  81. # Input properties:
  82. # /orientation/roll-deg
  83. # /orientation/pitch-deg
  84. # /orientation/heading-magnetic-deg
  85. # /environment/magnetic-dip-deg
  86. #
  87. # Output properties:
  88. # {base}/magnetic-heading-deg
  89. #
  90. var KMT112 = {
  91. new : func( baseNode, name = "kmt112", num = 0 ) {
  92. var obj = {};
  93. obj.parents = [KMT112];
  94. obj.rootNode = baseNode.getNode( name ~ "[" ~ num ~ "]", 1 );
  95. obj.magneticHeadingNode = obj.rootNode.initNode( "magnetic-heading-deg", 0.0 );
  96. obj._roll_node = props.globals.getNode("/orientation/roll-deg", 1);
  97. obj._pitch_node = props.globals.getNode("/orientation/pitch-deg", 1);
  98. obj._heading_node = props.globals.getNode("/orientation/heading-magnetic-deg", 1);
  99. obj._dip_node = props.globals.getNode("/environment/magnetic-dip-deg", 1);
  100. return obj;
  101. },
  102. update : func( dt ) {
  103. # The flux gate (or flux valve) is - like the magnetic compass - prone
  104. # to the dip error but not to acceleration error.
  105. # This is the part of mag_compass.cxx that models the dip error
  106. # mhab fix
  107. #var phi = me._roll_node.getValue() * D2R;
  108. var phi = me._roll_node.getValue();
  109. if ( phi == nil ) phi=0;
  110. phi = phi * D2R;
  111. #var theta = me._pitch_node.getValue() * D2R;
  112. var theta = me._pitch_node.getValue();
  113. if ( theta == nil ) theta=0;
  114. theta = theta * D2R;
  115. #var psi = me._heading_node.getValue() * D2R;
  116. var psi = me._heading_node.getValue();
  117. if ( psi == nil ) psi=0;
  118. psi = psi * D2R;
  119. var mu = me._dip_node.getValue() * D2R;
  120. # these are expensive: don't repeat
  121. var sin_phi = math.sin(phi);
  122. var sin_theta = math.sin(theta);
  123. var sin_mu = math.sin(mu);
  124. var cos_theta = math.cos(theta);
  125. var cos_psi = math.cos(psi);
  126. var cos_mu = math.cos(mu);
  127. var a = math.cos(phi) * math.sin(psi) * cos_mu
  128. - sin_phi * cos_theta * sin_mu
  129. - sin_phi* sin_theta * cos_mu * cos_psi;
  130. var b = cos_theta * cos_psi * math.cos(mu)
  131. - sin_theta * sin_mu;
  132. # var newMagHeading = geo.normdeg(math.atan2(a,b)*R2D);
  133. var newMagHeading = me._heading_node.getValue();
  134. me.magneticHeadingNode.setDoubleValue( getLowPass( me.magneticHeadingNode.getValue(), newMagHeading, dt*4 ) );
  135. },
  136. };
  137. # The gyro
  138. #
  139. # Input properties:
  140. # /position/latitude-deg
  141. # {base}/input-power-node
  142. # {base}/input-power-min
  143. # {base}/input-power-max
  144. # {base}/serviceable
  145. # {base}/spin-decay-percentage
  146. # {base}/fast-correction-rate-degps
  147. # {base}/slow-correction-rate-degps
  148. # ki525.slavingErrorNode
  149. #
  150. # Output properties:
  151. # {base}/indicated-heading-deg
  152. # {base}/spin-norm
  153. # {base}/serviceable
  154. # {base}/slaving-meter-norm
  155. var KG102 = {
  156. new : func( baseNode, name = "kg102", num = 0 ) {
  157. var obj = {};
  158. obj.parents = [KG102];
  159. obj.rootNode = baseNode.getNode( name ~ "[" ~ num ~ "]", 1 );
  160. var inputPowerNodeNameNode = obj.rootNode.getNode( "input-power-node" );
  161. obj.inputPowerNode = nil;
  162. if( inputPowerNodeNameNode != nil ) {
  163. obj.inputPowerNode = props.globals.initNode( inputPowerNodeNameNode.getValue(), 0.0 );
  164. }
  165. obj.inputPowerMinNode = obj.rootNode.getNode( "input-power-min" );
  166. obj.inputPowerMaxNode = obj.rootNode.getNode( "input-power-max" );
  167. obj.positionLatitude = props.globals.getNode( "/position/latitude-deg", 1 );
  168. obj.trueHeadingNode = props.globals.getNode( "/orientation/heading-deg", 1 );
  169. obj.gyroHeadingNode = obj.rootNode.initNode( "indicated-heading-deg", 0.0 );
  170. props.globals.getNode( "/instrumentation/heading-indicator["~ num ~ "]/indicated-heading-deg", 1 ).
  171. alias( obj.gyroHeadingNode );
  172. obj.spinNode = obj.rootNode.initNode( "spin-norm", 0 );
  173. obj.serviceableNode = obj.rootNode.initNode( "serviceable", 1, "BOOL" );
  174. obj.spinDecayPercentageNode = obj.rootNode.initNode( "spin-decay-percentage", 0.005 );
  175. obj.slavingMeterNormNode = obj.rootNode.initNode( "slaving-meter-norm", 0.0 );
  176. obj.flagNode = obj.rootNode.initNode( "flag-norm", 0.0 );
  177. obj.lastTurn = 0.0;
  178. obj.lastHeading = obj.trueHeadingNode.getValue();
  179. obj.offset = 0.0;
  180. var n = nil;
  181. var v = nil;
  182. obj.fastCorrectionRate = 3;
  183. n = obj.rootNode.getNode( "fast-correction-rate-degps", 0 );
  184. if( n != nil ) {
  185. v = n.getValue();
  186. if( v != nil ) {
  187. obj.fastCorrectionRate = v;
  188. }
  189. }
  190. obj.slowCorrectionRate = 0.05;
  191. n = obj.rootNode.getNode( "slow-correction-rate-degps", 0 );
  192. if( n != nil ) {
  193. v = n.getValue();
  194. if( v != nil ) {
  195. obj.slowCorrectionRate = v;
  196. }
  197. }
  198. aircraft.data.add( obj.gyroHeadingNode );
  199. return obj;
  200. },
  201. update : func( dt ) {
  202. var spin = 0;
  203. var flag = 0;
  204. if( me.serviceableNode.getValue() != 0 ) {
  205. #calculate the input power
  206. var powerNorm = 1.0;
  207. if( me.inputPowerNode != nil ) {
  208. var power = me.inputPowerNode.getValue();
  209. if( power != nil ) {
  210. var minPower = me.inputPowerMinNode.getValue();
  211. var maxPower = me.inputPowerMaxNode.getValue();
  212. powerNorm = 2*power/(maxPower+minPower);
  213. flag += power < minPower;
  214. }
  215. }
  216. # calculate the output power
  217. # calculate the gyro
  218. spin = me.spinNode.getValue();
  219. spin -= me.spinDecayPercentageNode.getValue() * dt;
  220. spin += 0.15 * powerNorm * dt;
  221. if( spin > powerNorm )
  222. spin = powerNorm;
  223. if( spin < 0 )
  224. spin = 0;
  225. # calculate the gyrocompass
  226. # time based precession, 360deg per day at the poles
  227. me.offset -= dt / 360 * math.sin(me.positionLatitude.getValue()*D2R);
  228. # add precession due to gyro age, motion, ...
  229. # get the turn angle against last heading and filter
  230. var heading = me.trueHeadingNode.getValue();
  231. var turn = geo.normdeg( heading - me.lastHeading + 180 ) - 180;
  232. turn = getLowPass( me.lastTurn, turn, 100*dt*spin*spin);
  233. me.lastTurn = turn;
  234. me.lastHeading = heading;
  235. # get the current heading of the gyro, apply the filtered turn
  236. heading = me.gyroHeadingNode.getValue();
  237. heading += turn;
  238. if( me.ka51.slavedNode.getValue() != 0 ) {
  239. # slaved mode, apply correction from the flux gate
  240. var slavingError = me.ki525.slavingErrorNode.getValue();
  241. var rate = 0.0;
  242. if( slavingError > 0.0 ) {
  243. rate = me.slowCorrectionRate;
  244. }
  245. if( slavingError < 0.0 ) {
  246. rate = -me.slowCorrectionRate;
  247. }
  248. if( slavingError > 3.0 ) {
  249. rate = me.fastCorrectionRate;
  250. flag += 1;
  251. }
  252. if( slavingError < -3.0 ) {
  253. rate = -me.fastCorrectionRate;
  254. flag += 1;
  255. }
  256. heading += rate * dt;
  257. # calculate the output for the slaving meter
  258. var slavingErrorNorm = slavingError / 3.0;
  259. if( slavingErrorNorm > 1.0 )
  260. slavingErrorNorm = 1.0;
  261. if( slavingErrorNorm < -1.0 )
  262. slavingErrorNorm = -1.0;
  263. me.slavingMeterNormNode.setDoubleValue( slavingErrorNorm );
  264. } else {
  265. # manual mode, apply setting of manual slave switch
  266. flag = 1;
  267. var manualSlave = me.ka51.manualSlaveNode.getValue();
  268. heading += manualSlave * me.fastCorrectionRate * dt;
  269. }
  270. me.gyroHeadingNode.setDoubleValue( geo.normdeg(heading) );
  271. }
  272. me.spinNode.setDoubleValue( spin );
  273. if( spin < 0.5 )
  274. flag += 1-2*spin;
  275. if( flag > 1.0 )
  276. flag = 1.0;
  277. if( flag < 0.0 )
  278. flag = 0.0;
  279. me.flagNode.setDoubleValue( flag );
  280. },
  281. };
  282. # The controller
  283. #
  284. # Output properties
  285. # {base}/slaved
  286. # {base}/direction
  287. var KA51 = {
  288. new : func( baseNode, name = "ka51", num = 0 ) {
  289. var obj = {};
  290. obj.parents = [KA51];
  291. obj.rootNode = baseNode.getNode( name ~ "[" ~ num ~ "]", 1 );
  292. obj.slavedNode = obj.rootNode.initNode( "slaved", 0, "BOOL" );
  293. obj.manualSlaveNode = obj.rootNode.initNode( "manual-slave", 0, "INT" );
  294. aircraft.data.add( obj.slavedNode );
  295. return obj;
  296. },
  297. update : func( dt ) {
  298. },
  299. };
  300. # The KI525 HSI Indicator
  301. var KI525 = {
  302. new : func( baseNode, name = "ki525", num = 0 ) {
  303. var obj = {};
  304. obj.parents = [KI525];
  305. obj.rootNode = baseNode.getNode( name ~ "[" ~ num ~ "]", 1 );
  306. obj.slavingErrorNode = obj.rootNode.initNode( "slaving-error-deg", 0.0 );
  307. obj.courseNode = obj.rootNode.initNode( "selected-course-deg", 0.0 );
  308. obj.headingNode = obj.rootNode.initNode( "selected-heading-deg", 0.0 );
  309. obj.courseErrorNode = obj.rootNode.initNode( "course-error-deg", 0.0 );
  310. obj.headingErrorNode = obj.rootNode.initNode( "heading-error-deg", 0.0 );
  311. # these properties are fed into our system
  312. obj.rootNode.getNode( "cdi-deflection", 1 ).alias( obj.getReferencedProperty( "cdi-deflection-property" ) );
  313. obj.rootNode.getNode( "gs-deflection", 1 ).alias( obj.getReferencedProperty( "gs-deflection-property" ) );
  314. obj.rootNode.getNode( "to-flag", 1 ).alias( obj.getReferencedProperty( "to-flag-property" ) );
  315. obj.rootNode.getNode( "from-flag", 1 ).alias( obj.getReferencedProperty( "from-flag-property" ) );
  316. obj.rootNode.getNode( "nav-flag", 1 ).alias( obj.getReferencedProperty( "nav-flag-property" ) );
  317. # tell the world about these values
  318. obj.getReferencedProperty("selected-course-property").alias( obj.rootNode.getNode( "selected-course-deg" ), 1 );
  319. # we provide these to the autopilo
  320. obj.getReferencedProperty("course-error-property").alias( obj.rootNode.getNode( "course-error-deg" ), 1 );
  321. obj.getReferencedProperty("heading-error-property").alias( obj.rootNode.getNode( "heading-error-deg" ), 1 );
  322. aircraft.data.add(
  323. obj.courseNode,
  324. obj.headingNode
  325. );
  326. return obj;
  327. },
  328. getReferencedProperty : func( name ) {
  329. var n = me.rootNode.getNode( name );
  330. if( n == nil ) {
  331. print( "WARNING: ki525: property not defined: " ~ name );
  332. return nil;
  333. }
  334. var s = n.getValue();
  335. if( s == nil ) {
  336. print( "WARNING: ki525: property no value defined: " ~ name );
  337. return nil;
  338. }
  339. return props.globals.getNode( s, 1 );
  340. },
  341. update : func( dt ) {
  342. var gyroHeading = me.kg102.gyroHeadingNode.getValue();
  343. var v = me.kmt112.magneticHeadingNode.getValue();
  344. me.slavingErrorNode.setDoubleValue( geo.normdeg(v - gyroHeading + 180 )-180);
  345. # provide offset course-arrow % magnetic-heading
  346. v = me.courseNode.getValue();
  347. me.courseErrorNode.setDoubleValue( geo.normdeg(v - gyroHeading + 180 )-180);
  348. # provide offset heading-bug % magnetic-heading
  349. v = me.headingNode.getValue();
  350. me.headingErrorNode.setDoubleValue( geo.normdeg(v - gyroHeading + 180 )-180);
  351. },
  352. };
  353. var KCS55 = {
  354. new : func(name = "kcs55", num = 0) {
  355. var obj = {};
  356. obj.parents = [KCS55];
  357. obj.rootNode = props.globals.getNode( "/instrumentation/" ~ name ~ "[" ~ num ~ "]", 1 );
  358. obj.elapsedTimeSecNode = props.globals.getNode( "/sim/time/elapsed-sec" );
  359. obj.kmt112 = KMT112.new( obj.rootNode );
  360. obj.kg102 = KG102.new( obj.rootNode );
  361. obj.ka51 = KA51.new( obj.rootNode );
  362. obj.ki525 = KI525.new( obj.rootNode );
  363. obj.ka51.kg102 = obj.kg102;
  364. obj.kg102.ka51 = obj.ka51;
  365. obj.kg102.ki525 = obj.ki525;
  366. obj.ki525.kmt112 = obj.kmt112;
  367. obj.ki525.kg102 = obj.kg102;
  368. obj.lastRun = obj.elapsedTimeSecNode.getValue();
  369. obj.update(obj.lastRun);
  370. print( "KCS55 pictorial navigation system initialized" );
  371. return obj;
  372. },
  373. update : func {
  374. var dt = me.elapsedTimeSecNode.getValue() - me.lastRun;
  375. me.kmt112.update( dt );
  376. me.kg102.update( dt );
  377. me.ka51.update( dt );
  378. me.ki525.update( dt );
  379. me.lastRun = me.elapsedTimeSecNode.getValue();
  380. settimer( func { me.update() }, 0 );
  381. },
  382. };