source: src/mlx/acft.py@ 643:38d72269e87c

Last change on this file since 643:38d72269e87c was 635:717c097e0b12, checked in by István Váradi <ivaradi@…>, 9 years ago

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

File size: 44.6 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
87class Aircraft(object):
88 """Base class for aircraft."""
[8]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
[592]94 def __init__(self, flight, minLandingFuel = None,
95 recommendedLandingFuel = None):
[4]96 """Construct the aircraft for the given type."""
[8]97 self._flight = flight
[591]98 self._minLandingFuel = minLandingFuel
[592]99 self._recommendedLandingFuel = recommendedLandingFuel
[369]100
101 self._name = None
102 self._modelName = None
103
[4]104 self._aircraftState = None
[331]105
[9]106 self._maxVS = -10000.0
107 self._minVS = 10000.0
[4]108
[101]109 self._v1r2LineIndex = None
[384]110 self._derateLineID = None
[391]111 self._takeoffAntiIceLineID = None
[96]112 self._vrefLineIndex = None
[391]113 self._landingAntiIceLineID = None
[96]114
[117]115 self.humanWeight = 82.0
116
[598]117 self.initialClimbSpeedAltitude = 1500
118 self.reverseMinSpeed = 50
[360]119
[611]120 self.maxTakeOffPitch = 15.0
121 self.maxTouchDownPitch = 15.0
[624]122 self.brakeCoolTime = 10.0
[611]123
[8]124 self._checkers = []
125
[197]126 config = flight.config
[11]127 # Loggers
128
[8]129 self._checkers.append(checks.StageChecker())
[9]130 self._checkers.append(checks.TakeOffLogger())
[8]131
132 self._checkers.append(checks.AltimeterLogger())
[321]133
[366]134 self._ilsLogger = checks.ILSLogger()
135 self._checkers.append(self._ilsLogger)
[321]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)
[331]145
[8]146 self._checkers.append(checks.SquawkLogger())
147
[211]148 self._appendLightsLoggers()
[8]149
150 self._checkers.append(checks.FlapsLogger())
151
152 self._checkers.append(checks.GearsLogger())
[601]153 self._checkers.append(checks.InitialClimbSpeedLogger())
[9]154 self._checkers.append(checks.CruiseSpeedLogger())
[10]155 self._checkers.append(checks.SpoilerLogger())
[338]156 self._checkers.append(checks.APLogger())
[9]157
[197]158 if config.isMessageTypeFS(const.MESSAGETYPE_VISIBILITY):
[134]159 self._checkers.append(checks.VisibilityChecker())
160
[139]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!
[215]166 if config.onlineACARS and flight.loggedIn and not flight.entranceExam:
[139]167 self._checkers.append(checks.ACARSSender(flight._gui))
168
[11]169 # Fault checkers
[211]170
171 self._appendLightsCheckers()
[11]172
[335]173 self._checkers.append(checks.TransponderChecker())
174
[11]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
[539]190 self._checkers.append(checks.SpeedChecker())
[11]191 self._checkers.append(checks.VSChecker())
[197]192
[447]193 timeout = 30.0 + config.realIASSmoothingLength - 1
[622]194 self._checkers.append(checks.OverspeedChecker(faultTimeout = timeout))
[331]195
[11]196 self._checkers.append(checks.StallChecker())
197
198 self._checkers.append(checks.PitotChecker())
[331]199
[598]200 self._checkers.append(checks.ReverserLogger())
[11]201 self._checkers.append(checks.ReverserChecker())
202
[273]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
[197]208 self._smoothedIAS = SmoothedValue()
209 self._smoothedVS = SmoothedValue()
210
[4]211 @property
212 def type(self):
213 """Get the type of the aircraft."""
[8]214 return self._flight.aircraftType
215
216 @property
217 def flight(self):
218 """Get the flight the aircraft belongs to."""
219 return self._flight
[4]220
[8]221 @property
[592]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
[8]232 def logger(self):
233 """Get the logger to use for the aircraft."""
234 return self._flight.logger
235
[139]236 @property
237 def state(self):
238 """Get the current aircraft state."""
239 return self._aircraftState
[241]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
[304]247
248 @property
[635]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
[304]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
[331]260
[384]261 @property
[512]262 def derateType(self):
263 """Get the derate type for this aircraft.
264
265 This default implementation returns DERATE_NONE."""
266 return DERATE_NONE
[384]267
[512]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
[384]274
[512]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
[384]290
291 return None
292
[11]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
[8]298 def modelChanged(self, timestamp, aircraftName, modelName):
[4]299 """Called when the simulator's aircraft changes."""
[369]300 self._name = aircraftName
301 self._modelName = modelName
302 if self._flight.stage is not None:
303 self._logNameAndModel(timestamp)
[4]304
305 def handleState(self, aircraftState):
[197]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."""
[274]310 try:
311 config = self._flight.config
[197]312
[274]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()
[197]318
[274]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()
[7]326
[274]327 self._flight.handleState(self._aircraftState, aircraftState)
[89]328
[274]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
[331]336
[8]337 def setStage(self, aircraftState, newStage):
338 """Set the given stage as the new one and do whatever should be
339 done."""
[369]340 if newStage==const.STAGE_BOARDING:
341 self._logNameAndModel(aircraftState.timestamp)
342
[624]343 oldStage = self._flight.stage
344
[9]345 if self._flight.setStage(aircraftState.timestamp, newStage):
346 if newStage==const.STAGE_PUSHANDTAXI:
347 self.logger.message(aircraftState.timestamp, "Block time start")
[274]348 self._flight.logFuel(aircraftState)
[331]349 self.logger.message(aircraftState.timestamp,
[313]350 "ZFW: %.2f kg" % (aircraftState.zfw))
[134]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)
[9]356 elif newStage==const.STAGE_TAKEOFF:
[624]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)
[241]365 self.logger.message(aircraftState.timestamp,
366 "Flight time start")
[331]367 self.logger.message(aircraftState.timestamp,
[9]368 "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
369 (aircraftState.grossWeight, self.mtow))
[409]370 self._logQNH(aircraftState)
[9]371 self.logger.message(aircraftState.timestamp,
[333]372 "Wind %03.0f/%.0f" % \
[331]373 (aircraftState.windDirection,
[9]374 aircraftState.windSpeed))
[321]375 self._logRadios(aircraftState)
[345]376 self._logV1R2(aircraftState)
[384]377 self._logDerate(aircraftState)
[391]378 self._logTakeoffAntiIce(aircraftState)
[321]379 elif newStage==const.STAGE_DESCENT or newStage==const.STAGE_LANDING:
380 self._logRadios(aircraftState)
[409]381 if newStage==const.STAGE_LANDING:
382 self._logQNH(aircraftState)
[634]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)
[9]389 elif newStage==const.STAGE_TAXIAFTERLAND:
[184]390 flight = self._flight
391 bookedFlight = flight.bookedFlight
392 config = flight.config
[136]393 if config.onlineGateSystem and \
[215]394 flight.loggedIn and \
[184]395 not flight.entranceExam and \
[136]396 bookedFlight.arrivalICAO=="LHBP" and \
397 config.isMessageTypeFS(const.MESSAGETYPE_GATE_SYSTEM):
[134]398 self._flight.getFleet(callback = self._fleetRetrieved,
399 force = True)
[9]400 self.logger.message(aircraftState.timestamp, "Flight time end")
[274]401 self._flight.logFuel(aircraftState)
[591]402 if self._minLandingFuel is not None and \
403 aircraftState.totalFuel<self._minLandingFuel:
[597]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)
[9]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" % \
[12]413 (self._minVS, self._maxVS))
[359]414 # elif newStage==const.STAGE_PARKING:
415 # self.logger.message(aircraftState.timestamp, "Block time end")
[89]416 elif newStage==const.STAGE_END:
417 flightLength = self._flight.flightTimeEnd - self._flight.flightTimeStart
[359]418 self.logger.message(aircraftState.timestamp, "Block time end")
[89]419 self.logger.message(aircraftState.timestamp,
420 "Flight time: " +
421 util.getTimeIntervalString(flightLength))
422 self.logger.message(aircraftState.timestamp,
423 "Flown distance: %.2f NM" % \
[331]424 (self._flight.flownDistance,))
[89]425 blockLength = self._flight.blockTimeEnd - self._flight.blockTimeStart
426 self.logger.message(aircraftState.timestamp,
427 "Block time: " +
428 util.getTimeIntervalString(blockLength))
[8]429
[9]430 def prepareFlare(self):
431 """Called when it is detected that we will soon flare.
[8]432
433 On the first call, it should start monitoring some parameters more
434 closely to determine flare time."""
[9]435 self.flight.simulator.startFlare()
436
[331]437 def flareStarted(self, windSpeed, windDirection, visibility,
[9]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,
[333]442 "Wind %03.0f/%.0f" % \
[9]443 (windDirection, windSpeed))
444 self.logger.message(self._aircraftState.timestamp,
445 "Visibility: %.0f metres" % (visibility,))
[409]446 self._logQNH(self._aircraftState)
[101]447 self._logVRef()
[391]448 self._logLandingAntiIce(self._aircraftState)
[9]449 self.flight.flareStarted(flareStart, flareStartFS)
[134]450 fs.sendMessage(const.MESSAGETYPE_INFORMATION, "Flare-time", 3)
[331]451
[9]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,
[170]456 flareEndFS,
457 tdRate)
[9]458 self.logger.message(self._aircraftState.timestamp,
[153]459 "Flaretime: %.3f (from %s)" % \
[331]460 (flareTime,
[9]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",))
[241]467 flight = self._flight
[9]468 self.logger.message(self._aircraftState.timestamp,
[241]469 "Touchdown speed: %.0f %s" % \
470 (flight.speedFromKnots(ias),
471 flight.getEnglishSpeedUnit()))
[9]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,))
[331]478 self.logger.message(self._aircraftState.timestamp,
[340]479 "CG: %.1f%%" % \
[271]480 (self._aircraftState.cog*100.0,))
[8]481
[611]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
[8]488 def cancelFlare(self):
489 """Cancel flare, if it has started."""
[9]490 self.flight.simulator.cancelFlare()
[8]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
[101]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
[384]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
[391]513 def updateTakeoffAntiIce(self):
514 """Update the take-off anti-ice setting."""
515 if self._takeoffAntiIceLineID is not None:
516 self._logTakeoffAntiIce()
517
[211]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
[241]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
[345]546 def _logV1R2(self, state = None):
[101]547 """Log the V1, Vr and V2 value either newly, or by updating the
548 corresponding line."""
[526]549 message = "Calc. TO speeds: V1: %s, VR: %s, V2: %s" % \
[241]550 (self._speedToLog(self._flight.v1),
551 self._speedToLog(self._flight.vr),
552 self._speedToLog(self._flight.v2))
[101]553
554 if self._v1r2LineIndex is None:
[345]555 if state is None:
556 state = self._aircraftState
[101]557 self._v1r2LineIndex = \
[345]558 self.logger.message(state.timestamp, message)
[101]559 else:
560 self.logger.updateLine(self._v1r2LineIndex, message)
561
[384]562 def _logDerate(self, state = None):
563 """Log the derate values either newly or by updating the corresponding
564 line."""
[512]565 dt = self.derateType
566 if dt==DERATE_NONE:
[384]567 return
568
[512]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)
[384]578
[391]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
[96]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" % \
[241]608 (self._speedToLog(self._flight.vref),)
[96]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
[391]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
[134]639 def _fleetRetrieved(self, fleet):
640 """Callback for the fleet retrieval result."""
641 if fleet is not None:
642 gateList = ""
643 occupiedGateNumbers = fleet.getOccupiedGateNumbers()
[619]644 for gate in gates.lhbpGates.gates:
645 if gate.isAvailable(gates.lhbpGates, occupiedGateNumbers):
[134]646 if gateList: gateList += ", "
[619]647 gateList += gate.number
[134]648 fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
649 "Free gates: " + gateList, 20)
[331]650
[134]651
[321]652 def _logRadios(self, aircraftState):
653 """Log the radios from the given aircraft state."""
654 flight = self._flight
655 logger = flight.logger
656
[366]657 self._ilsLogger.forceLog(flight, logger, aircraftState)
[321]658 self._nav1Logger.forceLog(flight, logger, aircraftState)
659 self._nav2Logger.forceLog(flight, logger, aircraftState)
[329]660 self._adf1Logger.forceLog(flight, logger, aircraftState)
661 self._adf2Logger.forceLog(flight, logger, aircraftState)
[321]662
[409]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
[369]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
[4]676#---------------------------------------------------------------------------------------
[7]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:
[140]683 - fuel: left, centre, right
[7]684 - n1: left, right
[331]685 - reverser: left, right"""
[592]686 def __init__(self, flight, minLandingFuel = 2500,
687 recommendedLandingFuel = 3500):
688 super(Boeing737, self).__init__(flight,
689 minLandingFuel = minLandingFuel,
690 recommendedLandingFuel =
691 recommendedLandingFuel)
[95]692
[9]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 }
[7]702
[384]703 @property
[512]704 def derateType(self):
705 """Get the derate type for this type."""
706 return DERATE_BOEING
[384]707
[7]708#---------------------------------------------------------------------------------------
709
710class B736(Boeing737):
711 """Boeing 737-600 aircraft."""
[8]712 def __init__(self, flight):
713 super(B736, self).__init__(flight)
[9]714 self.dow = 38307
715 self.mtow = 58328
716 self.mlw = 54657
717 self.mzfw = 51482
[611]718 self.maxTakeOffPitch = 16.2
719 self.maxTouchDownPitch = 14.7
[7]720
721#---------------------------------------------------------------------------------------
722
723class B737(Boeing737):
724 """Boeing 737-700 aircraft."""
[8]725 def __init__(self, flight):
726 super(B737, self).__init__(flight)
[9]727 self.dow = 39250
728 self.mtow = 61410
729 self.mlw = 58059
730 self.mzfw = 54657
[611]731 self.maxTakeOffPitch = 14.7
732 self.maxTouchDownPitch = 13.2
[7]733
734#---------------------------------------------------------------------------------------
735
736class B738(Boeing737):
737 """Boeing 737-800 aircraft."""
[8]738 def __init__(self, flight):
739 super(B738, self).__init__(flight)
[9]740 self.dow = 42690
741 self.mtow = 71709
742 self.mlw = 65317
743 self.mzfw = 61688
[611]744 self.maxTakeOffPitch = 11
745 self.maxTouchDownPitch = 9.5
[9]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
[7]754
755#---------------------------------------------------------------------------------------
756
[591]757class Boeing737CL(Boeing737):
758 """Base class for the various aircraft in the Boeing 737 Classic family."""
759 def __init__(self, flight):
[592]760 super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
761 recommendedLandingFuel = None)
[591]762
763#---------------------------------------------------------------------------------------
764
765class B733(Boeing737CL):
[7]766 """Boeing 737-300 aircraft."""
[8]767 def __init__(self, flight):
768 super(B733, self).__init__(flight)
[9]769 self.dow = 32700
770 self.mtow = 62820
771 self.mlw = 51700
772 self.mzfw = 48410
[611]773 self.maxTakeOffPitch = 13.4
774 self.maxTouchDownPitch = 12.0
[7]775
776#---------------------------------------------------------------------------------------
777
[591]778class B734(Boeing737CL):
[7]779 """Boeing 737-400 aircraft."""
[8]780 def __init__(self, flight):
781 super(B734, self).__init__(flight)
[9]782 self.dow = 33200
783 self.mtow = 68050
784 self.mlw = 56200
785 self.mzfw = 53100
[611]786 self.maxTakeOffPitch = 11.4
787 self.maxTouchDownPitch = 10
[7]788
789#---------------------------------------------------------------------------------------
790
[591]791class B735(Boeing737CL):
[7]792 """Boeing 737-500 aircraft."""
[8]793 def __init__(self, flight):
794 super(B735, self).__init__(flight)
[9]795 self.dow = 31300
796 self.mtow = 60550
797 self.mlw = 50000
798 self.mzfw = 46700
[611]799 self.maxTakeOffPitch = 14.7
800 self.maxTouchDownPitch = 13.2
[7]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:
[140]809 - fuel: left, right
[7]810 - n1: left, right
811 - reverser: left, right."""
[140]812
[8]813 def __init__(self, flight):
[591]814 super(DH8D, self).__init__(flight, minLandingFuel = 2000)
[9]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 }
[611]824 self.maxTakeOffPitch = 8.0
825 self.maxTouchDownPitch = 7.0
[9]826
[7]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:
[140]834 - fuel: left, centre, right
[7]835 - n1: left, right
836 - reverser: left, right"""
[140]837
[612]838 def __init__(self, flight, minLandingFuel = 7000):
[591]839 super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
[9]840 self.gearSpeedLimit = 270
841 self.flapSpeedLimits = { 1 : 255,
842 5 : 235,
843 10 : 215,
844 20 : 215,
845 25 : 185,
846 30 : 175 }
[7]847
[384]848 @property
[512]849 def derateType(self):
850 """Get the derate type for this type."""
851 return DERATE_BOEING
[384]852
[7]853#---------------------------------------------------------------------------------------
854
855class B762(Boeing767):
856 """Boeing 767-200 aircraft."""
[8]857 def __init__(self, flight):
858 super(B762, self).__init__(flight)
[9]859 self.dow = 84507
860 self.mtow = 175540
861 self.mlw = 126098
862 self.mzfw = 114758
[611]863 self.maxTakeOffPitch = 13.1
864 self.maxTouchDownPitch = 11.6
[7]865
866#---------------------------------------------------------------------------------------
867
868class B763(Boeing767):
869 """Boeing 767-300 aircraft."""
[8]870 def __init__(self, flight):
[331]871 super(B763, self).__init__(flight)
[9]872 self.dow = 91311
873 self.mtow = 181436
874 self.mlw = 137892
875 self.mzfw = 130635
[611]876 self.maxTakeOffPitch = 9.6
877 self.maxTouchDownPitch = 8.1
[7]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:
[140]886 - fuel: left, centre, right
[7]887 - n1: left, right
888 - reverser: left, right."""
[8]889 def __init__(self, flight):
[591]890 super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
[9]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 }
[611]900 self.maxTakeOffPitch = 18.0
901 self.maxTouchDownPitch = 18.0
[7]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:
[140]910 - fuel: left, centre, right
[7]911 - n1: left, right
912 - reverser: left, right."""
[8]913 def __init__(self, flight):
[591]914 super(F70, self).__init__(flight, minLandingFuel = 1900)
[9]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 }
[360]925 self.reverseMinSpeed = 50
[611]926 self.maxTakeOffPitch = 16.0
927 self.maxTouchDownPitch = 16.0
[7]928
[384]929 @property
[512]930 def derateType(self):
931 """Get the derate type for this type."""
932 return DERATE_EPR
[384]933
[7]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:
[140]941 - fuel: left aux, left, right, right aux
[7]942 - rpm: left, right
943 - reverser: left, right."""
[8]944 def __init__(self, flight):
945 super(DC3, self).__init__(flight)
[9]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 }
[8]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:
[29]960 if rpm>0: return False
961 return True
[7]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:
[140]970 - fuel: left tip, left aux, centre, right aux, right tip, external 1,
[7]971 external 2
972 - n1: left, right
973 - reverser: left, right."""
[8]974 def __init__(self, flight):
[591]975 super(T134, self).__init__(flight, minLandingFuel = 3000)
[210]976 self.dow = 29500
977 self.mtow = 49000
[9]978 self.mlw = 43000
979 self.mzfw = 38500
980 self.gearSpeedLimit = 216
[210]981 self.flapSpeedLimits = { 10 : 450,
982 20 : 400,
983 30 : 300 }
[360]984 self.reverseMinSpeed = 50
[7]985
[611]986 self.maxTakeOffPitch = 16.0
987 self.maxTouchDownPitch = 16.0
988
[241]989 @property
[512]990 def derateType(self):
991 """Get the derate type for this type."""
992 return DERATE_TUPOLEV
[384]993
994 @property
[241]995 def speedInKnots(self):
996 """Indicate if the speed is in knots."""
997 return False
[331]998
[635]999 @property
1000 def aglInFeet(self):
1001 """Indicate if AGL altituedes are in feet."""
1002 return False
1003
[211]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())
[631]1013 self._checkers.append(checks.TupolevLandingLightsChecker())
[211]1014 self._checkers.append(checks.LandingLightsChecker())
1015 self._checkers.append(checks.NavLightsChecker())
1016
[7]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:
[140]1024 - fuel: left aux, left, centre, centre 2, right, right aux
[7]1025 - n1: left, centre, right
1026 - reverser: left, right"""
[8]1027 def __init__(self, flight):
[591]1028 super(T154, self).__init__(flight, minLandingFuel = 5000)
[9]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 }
[360]1037 self.reverseMinSpeed = 50
[9]1038
[611]1039 self.maxTakeOffPitch = 16.0
1040 self.maxTouchDownPitch = 16.0
1041
[241]1042 @property
1043 def speedInKnots(self):
1044 """Indicate if the speed is in knots."""
1045 return False
[331]1046
[384]1047 @property
[635]1048 def aglInFeet(self):
1049 """Indicate if AGL altituedes are in feet."""
1050 return False
1051
1052 @property
[512]1053 def derateType(self):
1054 """Get the derate type for this type."""
1055 return DERATE_TUPOLEV
[384]1056
[211]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())
[631]1066 self._checkers.append(checks.TupolevLandingLightsChecker())
[211]1067 self._checkers.append(checks.LandingLightsChecker())
1068 self._checkers.append(checks.NavLightsChecker())
1069
[539]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
[635]1096 def aglInFeet(self):
1097 """Indicate if AGL altituedes are in feet."""
1098 return False
1099
1100 @property
[539]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())
[403]1116
[7]1117#---------------------------------------------------------------------------------------
1118
[443]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
[445]1139 @property
[512]1140 def derateType(self):
1141 """Get the derate type for this type."""
1142 return DERATE_B462
[445]1143
[443]1144#---------------------------------------------------------------------------------------
1145
[274]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]
[141]1151
1152#---------------------------------------------------------------------------------------
1153
[191]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,
[443]1169 const.AIRCRAFT_YK40 : YK40,
1170 const.AIRCRAFT_B462 : B462 }
[8]1171
1172#---------------------------------------------------------------------------------------
[197]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.