source: src/mlx/acft.py@ 898:55da787ce6e1

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

Aircraft type information (currently DOW, and crew-specific DOW values) are queried via RPC when logging in.

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