source: src/mlx/acft.py@ 790:e69a08004f40

Last change on this file since 790:e69a08004f40 was 790:e69a08004f40, checked in by István Váradi <ivaradi@…>, 8 years ago

Added support for the Boeing 737-200 aircraft type (weight data is still missing, re #302)

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