source: src/mlx/acft.py@ 802:9d0b8ae7eb19

Last change on this file since 802:9d0b8ae7eb19 was 799:959b8a49cde5, checked in by István Váradi <ivaradi@…>, 8 years ago

Some weights differing from the type default are set up for the appropriate tail numbers

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