source: src/mlx/acft.py@ 908:fcf3c44650f1

Last change on this file since 908:fcf3c44650f1 was 899:ec5238c77005, checked in by István Váradi <ivaradi@…>, 6 years ago

Cockpit and cabin crew weight is included in DOW, extra people are calulcated with specific weights, and passenger weight is set to 84 kgs (re #332)

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