source: src/mlx/acft.py@ 636:556d1ea3c836

Last change on this file since 636:556d1ea3c836 was 635:717c097e0b12, checked in by István Váradi <ivaradi@…>, 10 years ago

The AGL altitude values are logged with aircraft type-specific units (re #264)

File size: 44.6 KB
Line 
1
2import const
3import gates
4import checks
5import fs
6from i18n import xstr
7import util
8
9import sys
10import time
11import traceback
12
13from collections import deque
14
15#---------------------------------------------------------------------------------------
16
17## @package mlx.acft
18#
19# The simulator-independent aircraft classes.
20#
21# This module contains the aircraft classes that contain some general data
22# about each aircraft type in the MAVA Fleet. The base class, \ref Aircraft
23# also implements some parts of the logging and traces some data. The classes
24# are also responsible for creating the \ref mlx.checks "checkers". Most of
25# them are created by the \ref Aircraft class' constructor, but there can be
26# type-specific differences. For example the lights are different for different
27# types, so there is a \ref Aircraft._appendLightsLoggers "function" which can
28# be reimplemented in child classes, if needed. This class maintains also the
29# \ref SmoothedValue "smoothed values" of the IAS and the VS and set these
30# values in the \ref mlx.fs.AircraftState "aircraft state" when it is received
31# from the simulator.
32
33#---------------------------------------------------------------------------------------
34
35# Derate type: no derate possible
36DERATE_NONE = 0
37
38# Derate type: Boeing, i.e. a percentage value.
39# For logging, the percentage value is expected as a string (i.e. whatever the
40# pilot enters into the text field).
41DERATE_BOEING = 1
42
43# Derate type: EPR, i.e. an EPR value.
44# For logging, the EPR value is expected as a string (i.e. whatever the pilot
45# enters into the text field).
46DERATE_EPR = 2
47
48# Derate type: Tupolev, i.e. nominal or takeoff
49# For logging, one of the DERATE_TUPOLEV_xxx values are expected.
50DERATE_TUPOLEV = 3
51
52# Tupolev derate value: nominal
53DERATE_TUPOLEV_NOMINAL = 1
54
55# Tupolev derate value: takeoff
56DERATE_TUPOLEV_TAKEOFF = 2
57
58# Derate type: BAe-146, i.e. enabled or not
59# For logging, a boolean is expected.
60DERATE_B462 = 4
61
62#---------------------------------------------------------------------------------------
63
64class SmoothedValue(object):
65 """A smoothed value."""
66 def __init__(self):
67 """Construct the value."""
68 self._deque = deque()
69 self._sum = 0
70
71 def add(self, length, value):
72 """Add the given value and smooth with the given length."""
73 dequeLength = len(self._deque)
74 while dequeLength>=length:
75 self._sum -= self._deque.popleft()
76 dequeLength -= 1
77
78 self._sum += value
79 self._deque.append(value)
80
81 def get(self):
82 """Get the average."""
83 return self._sum / len(self._deque)
84
85#---------------------------------------------------------------------------------------
86
87class Aircraft(object):
88 """Base class for aircraft."""
89 @staticmethod
90 def create(flight):
91 """Create an aircraft instance for the type in the given flight."""
92 return _classes[flight.aircraftType](flight)
93
94 def __init__(self, flight, minLandingFuel = None,
95 recommendedLandingFuel = None):
96 """Construct the aircraft for the given type."""
97 self._flight = flight
98 self._minLandingFuel = minLandingFuel
99 self._recommendedLandingFuel = recommendedLandingFuel
100
101 self._name = None
102 self._modelName = None
103
104 self._aircraftState = None
105
106 self._maxVS = -10000.0
107 self._minVS = 10000.0
108
109 self._v1r2LineIndex = None
110 self._derateLineID = None
111 self._takeoffAntiIceLineID = None
112 self._vrefLineIndex = None
113 self._landingAntiIceLineID = None
114
115 self.humanWeight = 82.0
116
117 self.initialClimbSpeedAltitude = 1500
118 self.reverseMinSpeed = 50
119
120 self.maxTakeOffPitch = 15.0
121 self.maxTouchDownPitch = 15.0
122 self.brakeCoolTime = 10.0
123
124 self._checkers = []
125
126 config = flight.config
127 # Loggers
128
129 self._checkers.append(checks.StageChecker())
130 self._checkers.append(checks.TakeOffLogger())
131
132 self._checkers.append(checks.AltimeterLogger())
133
134 self._ilsLogger = checks.ILSLogger()
135 self._checkers.append(self._ilsLogger)
136 self._nav1Logger = checks.NAV1Logger()
137 self._checkers.append(self._nav1Logger)
138 self._nav2Logger = checks.NAV2Logger()
139 self._checkers.append(self._nav2Logger)
140
141 self._adf1Logger = checks.ADF1Logger()
142 self._checkers.append(self._adf1Logger)
143 self._adf2Logger = checks.ADF2Logger()
144 self._checkers.append(self._adf2Logger)
145
146 self._checkers.append(checks.SquawkLogger())
147
148 self._appendLightsLoggers()
149
150 self._checkers.append(checks.FlapsLogger())
151
152 self._checkers.append(checks.GearsLogger())
153 self._checkers.append(checks.InitialClimbSpeedLogger())
154 self._checkers.append(checks.CruiseSpeedLogger())
155 self._checkers.append(checks.SpoilerLogger())
156 self._checkers.append(checks.APLogger())
157
158 if config.isMessageTypeFS(const.MESSAGETYPE_VISIBILITY):
159 self._checkers.append(checks.VisibilityChecker())
160
161 # FIXME: we should have a central data model object, and not collect
162 # the data from the GUI. However, some pieces of data (e.g. V-speeds,
163 # etc. that is entered into the GUI) *should* be a part of the GUI and
164 # queried from it, so the model should have a reference to the GUI as
165 # well and access such data via the GUI!
166 if config.onlineACARS and flight.loggedIn and not flight.entranceExam:
167 self._checkers.append(checks.ACARSSender(flight._gui))
168
169 # Fault checkers
170
171 self._appendLightsCheckers()
172
173 self._checkers.append(checks.TransponderChecker())
174
175 self._checkers.append(checks.BankChecker())
176
177 self._checkers.append(checks.FlapsRetractChecker())
178 self._checkers.append(checks.FlapsSpeedLimitChecker())
179
180 self._checkers.append(checks.GearsDownChecker())
181 self._checkers.append(checks.GearSpeedLimitChecker())
182
183 self._checkers.append(checks.GLoadChecker())
184
185 self._checkers.append(checks.MLWChecker())
186 self._checkers.append(checks.MTOWChecker())
187 self._checkers.append(checks.MZFWChecker())
188 self._checkers.append(checks.PayloadChecker())
189
190 self._checkers.append(checks.SpeedChecker())
191 self._checkers.append(checks.VSChecker())
192
193 timeout = 30.0 + config.realIASSmoothingLength - 1
194 self._checkers.append(checks.OverspeedChecker(faultTimeout = timeout))
195
196 self._checkers.append(checks.StallChecker())
197
198 self._checkers.append(checks.PitotChecker())
199
200 self._checkers.append(checks.ReverserLogger())
201 self._checkers.append(checks.ReverserChecker())
202
203 if flight.aircraftType is not None and config.enableApproachCallouts:
204 approachCallouts = flight.config.getApproachCallouts(flight.aircraftType)
205 if approachCallouts:
206 self._checkers.append(checks.ApproachCalloutsPlayer(approachCallouts))
207
208 self._smoothedIAS = SmoothedValue()
209 self._smoothedVS = SmoothedValue()
210
211 @property
212 def type(self):
213 """Get the type of the aircraft."""
214 return self._flight.aircraftType
215
216 @property
217 def flight(self):
218 """Get the flight the aircraft belongs to."""
219 return self._flight
220
221 @property
222 def minLandingFuel(self):
223 """Get the minimum acceptable amount of the landing fuel."""
224 return self._minLandingFuel
225
226 @property
227 def recommendedLandingFuel(self):
228 """Get the recommended amount of the landing fuel."""
229 return self._recommendedLandingFuel
230
231 @property
232 def logger(self):
233 """Get the logger to use for the aircraft."""
234 return self._flight.logger
235
236 @property
237 def state(self):
238 """Get the current aircraft state."""
239 return self._aircraftState
240
241 @property
242 def speedInKnots(self):
243 """Indicate if the speed is in knots.
244
245 This default implementation returns True."""
246 return True
247
248 @property
249 def aglInFeet(self):
250 """Indicate if AGL altitudes are to be logged in feet.
251
252 This default implementation returns True."""
253 return True
254
255 @property
256 def timestamp(self):
257 """Get the timestamp of the current state."""
258 return None if self._aircraftState is None \
259 else self._aircraftState.timestamp
260
261 @property
262 def derateType(self):
263 """Get the derate type for this aircraft.
264
265 This default implementation returns DERATE_NONE."""
266 return DERATE_NONE
267
268 def getDerateLine(self, value):
269 """Get the log line for the given derate value.
270
271 It uses the the derate type and produces the standard message for
272 each. This children need not override it, although they can."""
273 dt = self.derateType
274
275 if dt==DERATE_BOEING:
276 return "Derate calculated by the pilot: %s %%" % \
277 ("-" if value is None else value,)
278 elif dt==DERATE_EPR:
279 return "EPR calculated by the pilot: %s" % \
280 ("-" if value is None else value,)
281 elif dt==DERATE_TUPOLEV:
282 return "Thrust setting calculated by the pilot: %s" % \
283 ("-" if value is None else
284 "nominal" if value==DERATE_TUPOLEV_NOMINAL else "takeoff",)
285 elif dt==DERATE_B462:
286 return "Derate setting: %s" % \
287 ("-" if value is None else "enabled" if value else "disabled",)
288 elif dt!=DERATE_NONE:
289 print "mlx.acft.getDerateLine: invalid derate type: " + dt
290
291 return None
292
293 def getFlapsSpeedLimit(self, flaps):
294 """Get the speed limit for the given flaps setting."""
295 return self.flapSpeedLimits[flaps] if flaps in self.flapSpeedLimits \
296 else None
297
298 def modelChanged(self, timestamp, aircraftName, modelName):
299 """Called when the simulator's aircraft changes."""
300 self._name = aircraftName
301 self._modelName = modelName
302 if self._flight.stage is not None:
303 self._logNameAndModel(timestamp)
304
305 def handleState(self, aircraftState):
306 """Called when the state of the aircraft changes.
307
308 This is the function that the simulator calls directly with the new
309 state."""
310 try:
311 config = self._flight.config
312
313 self._smoothedIAS.add(config.realIASSmoothingLength, aircraftState.ias)
314 aircraftState.smoothedIAS = self._smoothedIAS.get()
315
316 self._smoothedVS.add(config.realVSSmoothingLength, aircraftState.vs)
317 aircraftState.smoothedVS = self._smoothedVS.get()
318
319 for checker in self._checkers:
320 try:
321 checker.check(self._flight, self, self._flight.logger,
322 self._aircraftState, aircraftState)
323 except:
324 print >> sys.stderr, "Checker", checker, "failed"
325 traceback.print_exc()
326
327 self._flight.handleState(self._aircraftState, aircraftState)
328
329 self._maxVS = max(self._maxVS, aircraftState.vs)
330 self._minVS = min(self._minVS, aircraftState.vs)
331 except:
332 print >> sys.stderr, "Failed to handle the state"
333 traceback.print_exc()
334 finally:
335 self._aircraftState = aircraftState
336
337 def setStage(self, aircraftState, newStage):
338 """Set the given stage as the new one and do whatever should be
339 done."""
340 if newStage==const.STAGE_BOARDING:
341 self._logNameAndModel(aircraftState.timestamp)
342
343 oldStage = self._flight.stage
344
345 if self._flight.setStage(aircraftState.timestamp, newStage):
346 if newStage==const.STAGE_PUSHANDTAXI:
347 self.logger.message(aircraftState.timestamp, "Block time start")
348 self._flight.logFuel(aircraftState)
349 self.logger.message(aircraftState.timestamp,
350 "ZFW: %.2f kg" % (aircraftState.zfw))
351 flight = self._flight
352 if flight.v1 is None or flight.vr is None or flight.v2 is None:
353 fs.sendMessage(const.MESSAGETYPE_HELP,
354 "Don't forget to set the takeoff V-speeds!",
355 5)
356 elif newStage==const.STAGE_TAKEOFF:
357 if oldStage == const.STAGE_RTO and self._flight.hasRTO:
358 rtoState = self._flight.rtoState
359 if (aircraftState.timestamp - rtoState.timestamp) < \
360 (self.brakeCoolTime * 60.0):
361 self.logger.fault("brakeCoolTime",
362 aircraftState.timestamp,
363 "Did not cool the brakes for at least %.f minutes after the RTO" % (self.brakeCoolTime,),
364 15.0)
365 self.logger.message(aircraftState.timestamp,
366 "Flight time start")
367 self.logger.message(aircraftState.timestamp,
368 "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
369 (aircraftState.grossWeight, self.mtow))
370 self._logQNH(aircraftState)
371 self.logger.message(aircraftState.timestamp,
372 "Wind %03.0f/%.0f" % \
373 (aircraftState.windDirection,
374 aircraftState.windSpeed))
375 self._logRadios(aircraftState)
376 self._logV1R2(aircraftState)
377 self._logDerate(aircraftState)
378 self._logTakeoffAntiIce(aircraftState)
379 elif newStage==const.STAGE_DESCENT or newStage==const.STAGE_LANDING:
380 self._logRadios(aircraftState)
381 if newStage==const.STAGE_LANDING:
382 self._logQNH(aircraftState)
383 elif newStage==const.STAGE_GOAROUND:
384 from logger import Logger
385 self._flight.handleFault("goaround",
386 aircraftState.timestamp,
387 "Go-around detected, please, explain!",
388 Logger.NO_SCORE)
389 elif newStage==const.STAGE_TAXIAFTERLAND:
390 flight = self._flight
391 bookedFlight = flight.bookedFlight
392 config = flight.config
393 if config.onlineGateSystem and \
394 flight.loggedIn and \
395 not flight.entranceExam and \
396 bookedFlight.arrivalICAO=="LHBP" and \
397 config.isMessageTypeFS(const.MESSAGETYPE_GATE_SYSTEM):
398 self._flight.getFleet(callback = self._fleetRetrieved,
399 force = True)
400 self.logger.message(aircraftState.timestamp, "Flight time end")
401 self._flight.logFuel(aircraftState)
402 if self._minLandingFuel is not None and \
403 aircraftState.totalFuel<self._minLandingFuel:
404 self._flight.handleFault(self.__class__,
405 aircraftState.timestamp,
406 "The amount of the landing fuel is less than the minimum for this type: %ukgs (possible NO GO!)" %
407 (self._minLandingFuel,), 0)
408 self.logger.message(aircraftState.timestamp,
409 "Landing weight: %.0f kg, MLW: %.0f" % \
410 (aircraftState.grossWeight, self.mlw))
411 self.logger.message(aircraftState.timestamp,
412 "Vertical speed range: %.0f..%.0f feet/min" % \
413 (self._minVS, self._maxVS))
414 # elif newStage==const.STAGE_PARKING:
415 # self.logger.message(aircraftState.timestamp, "Block time end")
416 elif newStage==const.STAGE_END:
417 flightLength = self._flight.flightTimeEnd - self._flight.flightTimeStart
418 self.logger.message(aircraftState.timestamp, "Block time end")
419 self.logger.message(aircraftState.timestamp,
420 "Flight time: " +
421 util.getTimeIntervalString(flightLength))
422 self.logger.message(aircraftState.timestamp,
423 "Flown distance: %.2f NM" % \
424 (self._flight.flownDistance,))
425 blockLength = self._flight.blockTimeEnd - self._flight.blockTimeStart
426 self.logger.message(aircraftState.timestamp,
427 "Block time: " +
428 util.getTimeIntervalString(blockLength))
429
430 def prepareFlare(self):
431 """Called when it is detected that we will soon flare.
432
433 On the first call, it should start monitoring some parameters more
434 closely to determine flare time."""
435 self.flight.simulator.startFlare()
436
437 def flareStarted(self, windSpeed, windDirection, visibility,
438 flareStart, flareStartFS):
439 """Called when the flare has started."""
440 self.logger.message(self._aircraftState.timestamp, "The flare has begun")
441 self.logger.message(self._aircraftState.timestamp,
442 "Wind %03.0f/%.0f" % \
443 (windDirection, windSpeed))
444 self.logger.message(self._aircraftState.timestamp,
445 "Visibility: %.0f metres" % (visibility,))
446 self._logQNH(self._aircraftState)
447 self._logVRef()
448 self._logLandingAntiIce(self._aircraftState)
449 self.flight.flareStarted(flareStart, flareStartFS)
450 fs.sendMessage(const.MESSAGETYPE_INFORMATION, "Flare-time", 3)
451
452 def flareFinished(self, flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
453 ias, pitch, bank, heading):
454 """Called when the flare has finished."""
455 (flareTimeFromFS, flareTime) = self.flight.flareFinished(flareEnd,
456 flareEndFS,
457 tdRate)
458 self.logger.message(self._aircraftState.timestamp,
459 "Flaretime: %.3f (from %s)" % \
460 (flareTime,
461 "the simulator" if flareTimeFromFS else "real time",))
462 self.logger.message(self._aircraftState.timestamp,
463 "Touchdown rate: %.0f feet/min" % (tdRate,))
464 self.logger.message(self._aircraftState.timestamp,
465 "Touchdown rate was calculated by the %s" % \
466 ("simulator" if tdRateCalculatedByFS else "logger",))
467 flight = self._flight
468 self.logger.message(self._aircraftState.timestamp,
469 "Touchdown speed: %.0f %s" % \
470 (flight.speedFromKnots(ias),
471 flight.getEnglishSpeedUnit()))
472 self.logger.message(self._aircraftState.timestamp,
473 "Touchdown pitch: %.1f degrees" % (pitch,))
474 self.logger.message(self._aircraftState.timestamp,
475 "Touchdown bank: %.1f degrees" % (bank,))
476 self.logger.message(self._aircraftState.timestamp,
477 "Touchdown heading: %03.0f degrees" % (heading,))
478 self.logger.message(self._aircraftState.timestamp,
479 "CG: %.1f%%" % \
480 (self._aircraftState.cog*100.0,))
481
482 if abs(pitch)>self.maxTouchDownPitch:
483 self._flight.handleNoGo("TDPitch", self._aircraftState.timestamp,
484 "Touchdown pitch higher than aircraft maximum (%.2f)" % \
485 (self.maxTouchDownPitch,),
486 "TD TAILSTRIKE NO GO")
487
488 def cancelFlare(self):
489 """Cancel flare, if it has started."""
490 self.flight.simulator.cancelFlare()
491
492 def checkFlightEnd(self, aircraftState):
493 """Check if the end of the flight has arrived.
494
495 This default implementation checks the N1 values, but for
496 piston-powered aircraft you need to check the RPMs."""
497 for n1 in aircraftState.n1:
498 if n1>=0.5: return False
499 return True
500
501 def updateV1R2(self):
502 """Update the V1, Vr and V2 values from the flight, if the these values
503 have already been logged."""
504 if self._v1r2LineIndex is not None:
505 self._logV1R2()
506
507 def updateDerate(self):
508 """Update the derate value from the flight, if the these values
509 have already been logged."""
510 if self._derateLineID is not None:
511 self._logDerate()
512
513 def updateTakeoffAntiIce(self):
514 """Update the take-off anti-ice setting."""
515 if self._takeoffAntiIceLineID is not None:
516 self._logTakeoffAntiIce()
517
518 def _appendLightsLoggers(self):
519 """Append the loggers needed for the lights.
520
521 This default implementation adds the loggers for the anti-collision
522 lights, the landing lights, the strobe lights and the NAV lights."""
523 self._checkers.append(checks.AnticollisionLightsLogger())
524 self._checkers.append(checks.LandingLightsLogger())
525 self._checkers.append(checks.StrobeLightsLogger())
526 self._checkers.append(checks.NavLightsLogger())
527
528 def _appendLightsCheckers(self):
529 """Append the checkers needed for the lights.
530
531 This default implementation adds the checkers for the anti-collision
532 lights, the landing lights, the strobe lights and the NAV lights."""
533 self._checkers.append(checks.AntiCollisionLightsChecker())
534 self._checkers.append(checks.LandingLightsChecker())
535 self._checkers.append(checks.NavLightsChecker())
536 self._checkers.append(checks.StrobeLightsChecker())
537
538 def _speedToLog(self, speed):
539 """Convert the given speed (being either None or expressed in the
540 flight's speed unit into a string."""
541 if speed is None:
542 return "-"
543 else:
544 return str(speed) + " " + self._flight.getEnglishSpeedUnit()
545
546 def _logV1R2(self, state = None):
547 """Log the V1, Vr and V2 value either newly, or by updating the
548 corresponding line."""
549 message = "Calc. TO speeds: V1: %s, VR: %s, V2: %s" % \
550 (self._speedToLog(self._flight.v1),
551 self._speedToLog(self._flight.vr),
552 self._speedToLog(self._flight.v2))
553
554 if self._v1r2LineIndex is None:
555 if state is None:
556 state = self._aircraftState
557 self._v1r2LineIndex = \
558 self.logger.message(state.timestamp, message)
559 else:
560 self.logger.updateLine(self._v1r2LineIndex, message)
561
562 def _logDerate(self, state = None):
563 """Log the derate values either newly or by updating the corresponding
564 line."""
565 dt = self.derateType
566 if dt==DERATE_NONE:
567 return
568
569 message = self.getDerateLine(self._flight.derate)
570 if message is not None:
571 if self._derateLineID is None:
572 if state is None:
573 state = self._aircraftState
574 self._derateLineID = \
575 self.logger.message(state.timestamp, message)
576 else:
577 self.logger.updateLine(self._derateLineID, message)
578
579 def _logTakeoffAntiIce(self, state = None):
580 """Log the take-off anti-ice setting either newly or by updating the
581 corresponding line."""
582 antiIceOn = self._flight.takeoffAntiIceOn
583 if state is not None:
584 antiIceOn = antiIceOn or state.antiIceOn is True
585 self._flight.takeoffAntiIceOn = antiIceOn
586
587 message = "Anti-ice was turned %s" % \
588 ("ON" if antiIceOn else "OFF")
589
590 if self._takeoffAntiIceLineID is None:
591 if state is None:
592 state = self._aircraftState
593 self._takeoffAntiIceLineID = \
594 self.logger.message(state.timestamp, message)
595 else:
596 self.logger.updateLine(self._takeoffAntiIceLineID, message)
597
598 def updateVRef(self):
599 """Update the Vref value from the flight, if the Vref value has already
600 been logged."""
601 if self._vrefLineIndex is not None:
602 self._logVRef()
603
604 def _logVRef(self):
605 """Log the Vref value either newly, or by updating the corresponding
606 line."""
607 message = "VRef speed calculated by the pilot: %s" % \
608 (self._speedToLog(self._flight.vref),)
609 if self._vrefLineIndex is None:
610 self._vrefLineIndex = \
611 self.logger.message(self._aircraftState.timestamp, message)
612 else:
613 self.logger.updateLine(self._vrefLineIndex, message)
614
615 def updateLandingAntiIce(self):
616 """Update the landing anti-ice setting."""
617 if self._landingAntiIceLineID is not None:
618 self._logLandingAntiIce()
619
620 def _logLandingAntiIce(self, state = None):
621 """Log the landing anti-ice setting either newly or by updating the
622 corresponding line."""
623 antiIceOn = self._flight.landingAntiIceOn
624 if state is not None:
625 antiIceOn = antiIceOn or state.antiIceOn is True
626 self._flight.landingAntiIceOn = antiIceOn
627
628 message = "Anti-ice was turned %s" % \
629 ("ON" if antiIceOn else "OFF")
630
631 if self._landingAntiIceLineID is None:
632 if state is None:
633 state = self._aircraftState
634 self._landingAntiIceLineID = \
635 self.logger.message(state.timestamp, message)
636 else:
637 self.logger.updateLine(self._landingAntiIceLineID, message)
638
639 def _fleetRetrieved(self, fleet):
640 """Callback for the fleet retrieval result."""
641 if fleet is not None:
642 gateList = ""
643 occupiedGateNumbers = fleet.getOccupiedGateNumbers()
644 for gate in gates.lhbpGates.gates:
645 if gate.isAvailable(gates.lhbpGates, occupiedGateNumbers):
646 if gateList: gateList += ", "
647 gateList += gate.number
648 fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
649 "Free gates: " + gateList, 20)
650
651
652 def _logRadios(self, aircraftState):
653 """Log the radios from the given aircraft state."""
654 flight = self._flight
655 logger = flight.logger
656
657 self._ilsLogger.forceLog(flight, logger, aircraftState)
658 self._nav1Logger.forceLog(flight, logger, aircraftState)
659 self._nav2Logger.forceLog(flight, logger, aircraftState)
660 self._adf1Logger.forceLog(flight, logger, aircraftState)
661 self._adf2Logger.forceLog(flight, logger, aircraftState)
662
663 def _logQNH(self, aircraftState):
664 """Log the current QNH along with the altimeter setting."""
665 self.logger.message(aircraftState.timestamp,
666 "QNH: %.2f hPa, altimeter: %.2f hPa" % \
667 (aircraftState.qnh, aircraftState.altimeter))
668
669 def _logNameAndModel(self, timestamp):
670 """Log the aircraft's name and model with taking the timestamp from the
671 given state."""
672 self._flight.logger.message(timestamp,
673 "Aircraft: name='%s', model='%s'" % \
674 (self._name, self._modelName))
675
676#---------------------------------------------------------------------------------------
677
678class Boeing737(Aircraft):
679 """Base class for the various aircraft in the Boeing 737 family.
680
681 The aircraft type-specific values in the aircraft state have the following
682 structure:
683 - fuel: left, centre, right
684 - n1: left, right
685 - reverser: left, right"""
686 def __init__(self, flight, minLandingFuel = 2500,
687 recommendedLandingFuel = 3500):
688 super(Boeing737, self).__init__(flight,
689 minLandingFuel = minLandingFuel,
690 recommendedLandingFuel =
691 recommendedLandingFuel)
692
693 self.gearSpeedLimit = 270
694 self.flapSpeedLimits = { 1 : 260,
695 2 : 260,
696 5 : 250,
697 10 : 210,
698 15 : 200,
699 25 : 190,
700 30 : 175,
701 40 : 162 }
702
703 @property
704 def derateType(self):
705 """Get the derate type for this type."""
706 return DERATE_BOEING
707
708#---------------------------------------------------------------------------------------
709
710class B736(Boeing737):
711 """Boeing 737-600 aircraft."""
712 def __init__(self, flight):
713 super(B736, self).__init__(flight)
714 self.dow = 38307
715 self.mtow = 58328
716 self.mlw = 54657
717 self.mzfw = 51482
718 self.maxTakeOffPitch = 16.2
719 self.maxTouchDownPitch = 14.7
720
721#---------------------------------------------------------------------------------------
722
723class B737(Boeing737):
724 """Boeing 737-700 aircraft."""
725 def __init__(self, flight):
726 super(B737, self).__init__(flight)
727 self.dow = 39250
728 self.mtow = 61410
729 self.mlw = 58059
730 self.mzfw = 54657
731 self.maxTakeOffPitch = 14.7
732 self.maxTouchDownPitch = 13.2
733
734#---------------------------------------------------------------------------------------
735
736class B738(Boeing737):
737 """Boeing 737-800 aircraft."""
738 def __init__(self, flight):
739 super(B738, self).__init__(flight)
740 self.dow = 42690
741 self.mtow = 71709
742 self.mlw = 65317
743 self.mzfw = 61688
744 self.maxTakeOffPitch = 11
745 self.maxTouchDownPitch = 9.5
746
747#---------------------------------------------------------------------------------------
748
749class B738Charter(B738):
750 """Boeing 737-800 aircraft used for charters."""
751 def __init__(self, flight):
752 super(B738Charter, self).__init__(flight)
753 self.mtow = 77791
754
755#---------------------------------------------------------------------------------------
756
757class Boeing737CL(Boeing737):
758 """Base class for the various aircraft in the Boeing 737 Classic family."""
759 def __init__(self, flight):
760 super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
761 recommendedLandingFuel = None)
762
763#---------------------------------------------------------------------------------------
764
765class B733(Boeing737CL):
766 """Boeing 737-300 aircraft."""
767 def __init__(self, flight):
768 super(B733, self).__init__(flight)
769 self.dow = 32700
770 self.mtow = 62820
771 self.mlw = 51700
772 self.mzfw = 48410
773 self.maxTakeOffPitch = 13.4
774 self.maxTouchDownPitch = 12.0
775
776#---------------------------------------------------------------------------------------
777
778class B734(Boeing737CL):
779 """Boeing 737-400 aircraft."""
780 def __init__(self, flight):
781 super(B734, self).__init__(flight)
782 self.dow = 33200
783 self.mtow = 68050
784 self.mlw = 56200
785 self.mzfw = 53100
786 self.maxTakeOffPitch = 11.4
787 self.maxTouchDownPitch = 10
788
789#---------------------------------------------------------------------------------------
790
791class B735(Boeing737CL):
792 """Boeing 737-500 aircraft."""
793 def __init__(self, flight):
794 super(B735, self).__init__(flight)
795 self.dow = 31300
796 self.mtow = 60550
797 self.mlw = 50000
798 self.mzfw = 46700
799 self.maxTakeOffPitch = 14.7
800 self.maxTouchDownPitch = 13.2
801
802#---------------------------------------------------------------------------------------
803
804class DH8D(Aircraft):
805 """Bombardier Dash-8 Q400 aircraft.
806
807 The aircraft type-specific values in the aircraft state have the following
808 structure:
809 - fuel: left, right
810 - n1: left, right
811 - reverser: left, right."""
812
813 def __init__(self, flight):
814 super(DH8D, self).__init__(flight, minLandingFuel = 2000)
815 self.dow = 17185
816 self.mtow = 29257
817 self.mlw = 28009
818 self.mzfw = 25855
819 self.gearSpeedLimit = 215
820 self.flapSpeedLimits = { 5 : 200,
821 10 : 181,
822 15 : 172,
823 35 : 158 }
824 self.maxTakeOffPitch = 8.0
825 self.maxTouchDownPitch = 7.0
826
827#---------------------------------------------------------------------------------------
828
829class Boeing767(Aircraft):
830 """Base class for the various aircraft in the Boeing 767 family.
831
832 The aircraft type-specific values in the aircraft state have the following
833 structure:
834 - fuel: left, centre, right
835 - n1: left, right
836 - reverser: left, right"""
837
838 def __init__(self, flight, minLandingFuel = 7000):
839 super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
840 self.gearSpeedLimit = 270
841 self.flapSpeedLimits = { 1 : 255,
842 5 : 235,
843 10 : 215,
844 20 : 215,
845 25 : 185,
846 30 : 175 }
847
848 @property
849 def derateType(self):
850 """Get the derate type for this type."""
851 return DERATE_BOEING
852
853#---------------------------------------------------------------------------------------
854
855class B762(Boeing767):
856 """Boeing 767-200 aircraft."""
857 def __init__(self, flight):
858 super(B762, self).__init__(flight)
859 self.dow = 84507
860 self.mtow = 175540
861 self.mlw = 126098
862 self.mzfw = 114758
863 self.maxTakeOffPitch = 13.1
864 self.maxTouchDownPitch = 11.6
865
866#---------------------------------------------------------------------------------------
867
868class B763(Boeing767):
869 """Boeing 767-300 aircraft."""
870 def __init__(self, flight):
871 super(B763, self).__init__(flight)
872 self.dow = 91311
873 self.mtow = 181436
874 self.mlw = 137892
875 self.mzfw = 130635
876 self.maxTakeOffPitch = 9.6
877 self.maxTouchDownPitch = 8.1
878
879#---------------------------------------------------------------------------------------
880
881class CRJ2(Aircraft):
882 """Bombardier CRJ-200 aircraft.
883
884 The aircraft type-specific values in the aircraft state have the following
885 structure:
886 - fuel: left, centre, right
887 - n1: left, right
888 - reverser: left, right."""
889 def __init__(self, flight):
890 super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
891 self.dow = 14549
892 self.mtow = 22995
893 self.mlw = 21319
894 self.mzfw = 19958
895 self.gearSpeedLimit = 240
896 self.flapSpeedLimits = { 8 : 260,
897 20 : 220,
898 30 : 190,
899 45 : 175 }
900 self.maxTakeOffPitch = 18.0
901 self.maxTouchDownPitch = 18.0
902
903#---------------------------------------------------------------------------------------
904
905class F70(Aircraft):
906 """Fokker 70 aircraft.
907
908 The aircraft type-specific values in the aircraft state have the following
909 structure:
910 - fuel: left, centre, right
911 - n1: left, right
912 - reverser: left, right."""
913 def __init__(self, flight):
914 super(F70, self).__init__(flight, minLandingFuel = 1900)
915 self.dow = 24283
916 self.mtow = 38100 # FIXME: differentiate by registration number,
917 # MTOW of HA-LMF: 41955
918 self.mlw = 36740
919 self.mzfw = 32655
920 self.gearSpeedLimit = 200
921 self.flapSpeedLimits = { 8 : 250,
922 15 : 220,
923 25 : 220,
924 42 : 180 }
925 self.reverseMinSpeed = 50
926 self.maxTakeOffPitch = 16.0
927 self.maxTouchDownPitch = 16.0
928
929 @property
930 def derateType(self):
931 """Get the derate type for this type."""
932 return DERATE_EPR
933
934#---------------------------------------------------------------------------------------
935
936class DC3(Aircraft):
937 """Lisunov Li-2 (DC-3) aircraft.
938
939 The aircraft type-specific values in the aircraft state have the following
940 structure:
941 - fuel: left aux, left, right, right aux
942 - rpm: left, right
943 - reverser: left, right."""
944 def __init__(self, flight):
945 super(DC3, self).__init__(flight)
946 self.dow = 8627
947 self.mtow = 11884
948 self.mlw = 11793
949 self.mzfw = 11780
950 self.gearSpeedLimit = 148
951 self.flapSpeedLimits = { 15 : 135,
952 30 : 99,
953 45 : 97 }
954
955 def _checkFlightEnd(self, aircraftState):
956 """Check if the end of the flight has arrived.
957
958 This implementation checks the RPM values to be 0."""
959 for rpm in aircraftState.rpm:
960 if rpm>0: return False
961 return True
962
963#---------------------------------------------------------------------------------------
964
965class T134(Aircraft):
966 """Tupolev Tu-134 aircraft.
967
968 The aircraft type-specific values in the aircraft state have the following
969 structure:
970 - fuel: left tip, left aux, centre, right aux, right tip, external 1,
971 external 2
972 - n1: left, right
973 - reverser: left, right."""
974 def __init__(self, flight):
975 super(T134, self).__init__(flight, minLandingFuel = 3000)
976 self.dow = 29500
977 self.mtow = 49000
978 self.mlw = 43000
979 self.mzfw = 38500
980 self.gearSpeedLimit = 216
981 self.flapSpeedLimits = { 10 : 450,
982 20 : 400,
983 30 : 300 }
984 self.reverseMinSpeed = 50
985
986 self.maxTakeOffPitch = 16.0
987 self.maxTouchDownPitch = 16.0
988
989 @property
990 def derateType(self):
991 """Get the derate type for this type."""
992 return DERATE_TUPOLEV
993
994 @property
995 def speedInKnots(self):
996 """Indicate if the speed is in knots."""
997 return False
998
999 @property
1000 def aglInFeet(self):
1001 """Indicate if AGL altituedes are in feet."""
1002 return False
1003
1004 def _appendLightsLoggers(self):
1005 """Append the loggers needed for the lights."""
1006 self._checkers.append(checks.AnticollisionLightsLogger())
1007 self._checkers.append(checks.LandingLightsLogger())
1008 self._checkers.append(checks.NavLightsLogger())
1009
1010 def _appendLightsCheckers(self):
1011 """Append the checkers needed for the lights."""
1012 self._checkers.append(checks.TupolevAntiCollisionLightsChecker())
1013 self._checkers.append(checks.TupolevLandingLightsChecker())
1014 self._checkers.append(checks.LandingLightsChecker())
1015 self._checkers.append(checks.NavLightsChecker())
1016
1017#---------------------------------------------------------------------------------------
1018
1019class T154(Aircraft):
1020 """Tupolev Tu-154 aircraft.
1021
1022 The aircraft type-specific values in the aircraft state have the following
1023 structure:
1024 - fuel: left aux, left, centre, centre 2, right, right aux
1025 - n1: left, centre, right
1026 - reverser: left, right"""
1027 def __init__(self, flight):
1028 super(T154, self).__init__(flight, minLandingFuel = 5000)
1029 self.dow = 53259
1030 self.mtow = 98000
1031 self.mlw = 78000
1032 self.mzfw = 72000
1033 self.gearSpeedLimit = 216
1034 self.flapSpeedLimits = { 15 : 227,
1035 28 : 194,
1036 45 : 162 }
1037 self.reverseMinSpeed = 50
1038
1039 self.maxTakeOffPitch = 16.0
1040 self.maxTouchDownPitch = 16.0
1041
1042 @property
1043 def speedInKnots(self):
1044 """Indicate if the speed is in knots."""
1045 return False
1046
1047 @property
1048 def aglInFeet(self):
1049 """Indicate if AGL altituedes are in feet."""
1050 return False
1051
1052 @property
1053 def derateType(self):
1054 """Get the derate type for this type."""
1055 return DERATE_TUPOLEV
1056
1057 def _appendLightsLoggers(self):
1058 """Append the loggers needed for the lights."""
1059 self._checkers.append(checks.AnticollisionLightsLogger())
1060 self._checkers.append(checks.LandingLightsLogger())
1061 self._checkers.append(checks.NavLightsLogger())
1062
1063 def _appendLightsCheckers(self):
1064 """Append the checkers needed for the lights."""
1065 self._checkers.append(checks.AntiCollisionLightsChecker())
1066 self._checkers.append(checks.TupolevLandingLightsChecker())
1067 self._checkers.append(checks.LandingLightsChecker())
1068 self._checkers.append(checks.NavLightsChecker())
1069
1070#---------------------------------------------------------------------------------------
1071
1072class YK40(Aircraft):
1073 """Yakovlev Yak-40 aircraft.
1074
1075 The aircraft type-specific values in the aircraft state have the following
1076 structure:
1077 - fuel: left, right
1078 - n1: left, right
1079 - reverser: left, right"""
1080 def __init__(self, flight):
1081 super(YK40, self).__init__(flight)
1082 self.dow = 9400
1083 self.mtow = 17200
1084 self.mlw = 16800
1085 self.mzfw = 12100
1086 self.gearSpeedLimit = 165
1087 self.flapSpeedLimits = { 20 : 165,
1088 35 : 135 }
1089
1090 @property
1091 def speedInKnots(self):
1092 """Indicate if the speed is in knots."""
1093 return False
1094
1095 @property
1096 def aglInFeet(self):
1097 """Indicate if AGL altituedes are in feet."""
1098 return False
1099
1100 @property
1101 def derateType(self):
1102 """Get the derate type for this type."""
1103 return DERATE_TUPOLEV
1104
1105 def _appendLightsLoggers(self):
1106 """Append the loggers needed for the lights."""
1107 self._checkers.append(checks.AnticollisionLightsLogger())
1108 self._checkers.append(checks.LandingLightsLogger())
1109 self._checkers.append(checks.NavLightsLogger())
1110
1111 def _appendLightsCheckers(self):
1112 """Append the checkers needed for the lights."""
1113 self._checkers.append(checks.AntiCollisionLightsChecker())
1114 self._checkers.append(checks.LandingLightsChecker())
1115 self._checkers.append(checks.NavLightsChecker())
1116
1117#---------------------------------------------------------------------------------------
1118
1119class B462(Aircraft):
1120 """British Aerospace BAe-146 aircraft.
1121
1122 The aircraft type-specific values in the aircraft state have the following
1123 structure:
1124 - fuel: left, centre, right
1125 - n1: left outer, left inner, right inner, right outer
1126 - reverser: empty (the plane has no reversers)"""
1127 def __init__(self, flight):
1128 super(B462, self).__init__(flight)
1129 self.dow = 25706
1130 self.mtow = 43998
1131 self.mlw = 38599
1132 self.mzfw = 33792
1133 self.gearSpeedLimit = 210
1134 self.flapSpeedLimits = { 18 : 217,
1135 24 : 180,
1136 30 : 170,
1137 33 : 150 }
1138
1139 @property
1140 def derateType(self):
1141 """Get the derate type for this type."""
1142 return DERATE_B462
1143
1144#---------------------------------------------------------------------------------------
1145
1146mostFuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1147 const.FUELTANK_LEFT_AUX,
1148 const.FUELTANK_CENTRE,
1149 const.FUELTANK_RIGHT_AUX,
1150 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1151
1152#---------------------------------------------------------------------------------------
1153
1154_classes = { const.AIRCRAFT_B736 : B736,
1155 const.AIRCRAFT_B737 : B737,
1156 const.AIRCRAFT_B738 : B738,
1157 const.AIRCRAFT_B738C : B738Charter,
1158 const.AIRCRAFT_B733 : B733,
1159 const.AIRCRAFT_B734 : B734,
1160 const.AIRCRAFT_B735 : B735,
1161 const.AIRCRAFT_DH8D : DH8D,
1162 const.AIRCRAFT_B762 : B762,
1163 const.AIRCRAFT_B763 : B763,
1164 const.AIRCRAFT_CRJ2 : CRJ2,
1165 const.AIRCRAFT_F70 : F70,
1166 const.AIRCRAFT_DC3 : DC3,
1167 const.AIRCRAFT_T134 : T134,
1168 const.AIRCRAFT_T154 : T154,
1169 const.AIRCRAFT_YK40 : YK40,
1170 const.AIRCRAFT_B462 : B462 }
1171
1172#---------------------------------------------------------------------------------------
1173
1174if __name__ == "__main__":
1175 value = SmoothedValue()
1176
1177 print "Adding 1, 12.0"
1178 value.add(1, 12.0)
1179 print value.get()
1180
1181 print "Adding 1, 15.0"
1182 value.add(1, 15.0)
1183 print value.get()
1184
1185 print "Adding 2, 18.0"
1186 value.add(2, 18.0)
1187 print value.get()
1188
1189 print "Adding 2, 20.0"
1190 value.add(2, 20.0)
1191 print value.get()
1192
1193 print "Adding 5, 22.0"
1194 value.add(5, 22.0)
1195 print value.get()
1196
1197 print "Adding 5, 25.0"
1198 value.add(5, 25.0)
1199 print value.get()
1200
1201 print "Adding 5, 29.0"
1202 value.add(5, 29.0)
1203 print value.get()
1204
1205 print "Adding 5, 21.0"
1206 value.add(5, 21.0)
1207 print value.get()
1208
1209 print "Adding 5, 26.0"
1210 value.add(5, 26.0)
1211 print value.get()
1212
1213 print "Adding 2, 30.0"
1214 value.add(2, 30.0)
1215 print value.get()
1216
1217 print "Adding 2, 55.0"
1218 value.add(2, 55.0)
1219 print value.get()
1220
1221#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.