source: src/mlx/acft.py@ 644:bd36997f0b8a

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

Too high taxi speed is logged immediately if an aircraft has a strobe light, but it is not turned on when exceedin 50 knots (re #269)

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