source: src/mlx/acft.py@ 996:8035d80d5feb

python3
Last change on this file since 996:8035d80d5feb was 919:2ce8ca39525b, checked in by István Váradi <ivaradi@…>, 6 years ago

Ran 2to3

File size: 48.6 KB
Line 
1
2from . import const
3from . import gates
4from . import checks
5from . import fs
6from .i18n import xstr
7from . import 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("Checker", checker, "failed", file=sys.stderr)
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("Failed to handle the state", file=sys.stderr)
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 dow = 38307
734
735 def __init__(self, flight):
736 super(B736, self).__init__(flight)
737 self.mtow = 58328
738 self.mlw = 54657
739 self.mzfw = 51482
740 self.maxTakeOffPitch = 16.2
741 self.maxTouchDownPitch = 14.7
742 self.simBriefData = SimBriefData(["250/280/78"],
743 ["CI", "M75", "M78", "M79", "M80", "LRC"],
744 ["78/280/250"])
745
746#---------------------------------------------------------------------------------------
747
748class B737(Boeing737):
749 """Boeing 737-700 aircraft."""
750 dow = 39250
751
752 def __init__(self, flight):
753 super(B737, self).__init__(flight)
754 self.mtow = 61410
755 self.mlw = 58059
756 self.mzfw = 54657
757 self.maxTakeOffPitch = 14.7
758 self.maxTouchDownPitch = 13.2
759 self.simBriefData = SimBriefData(["250/280/78"],
760 ["CI", "M75", "M78", "M79", "M80", "LRC"],
761 ["78/280/250", "78/250/250"])
762
763#---------------------------------------------------------------------------------------
764
765class B738(Boeing737):
766 """Boeing 737-800 aircraft."""
767 dow = 42690
768
769 def __init__(self, flight):
770 super(B738, self).__init__(flight)
771 self.mtow = 71791
772 self.mlw = 65317
773 self.mzfw = 61688
774 self.maxTakeOffPitch = 11
775 self.maxTouchDownPitch = 9.5
776 self.simBriefData = SimBriefData(["250/280/78"],
777 ["CI", "M76", "M78", "M79", "M80", "LRC"],
778 ["78/280/250", "78/250/250"])
779
780#---------------------------------------------------------------------------------------
781
782class B738Charter(B738):
783 """Boeing 737-800 aircraft used for charters."""
784 def __init__(self, flight):
785 super(B738Charter, self).__init__(flight)
786 self.mtow = 77791
787 self.simBriefData = SimBriefData(["AUTO"],
788 ["280/M74"],
789 ["AUTO"])
790
791#---------------------------------------------------------------------------------------
792
793class Boeing737CL(Boeing737):
794 """Base class for the various aircraft in the Boeing 737 Classic family."""
795 def __init__(self, flight):
796 super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
797 recommendedLandingFuel = None)
798
799#---------------------------------------------------------------------------------------
800
801class B732(Boeing737CL):
802 """Boeing 737-200 aircraft."""
803 dow = 27646
804
805 def __init__(self, flight):
806 super(B732, self).__init__(flight)
807 self.mtow = 52390
808 self.mlw = 46720
809 self.mzfw = 43091
810 self.maxTakeOffPitch = 15.5
811 self.maxTouchDownPitch = 15.5
812
813#---------------------------------------------------------------------------------------
814
815class B733(Boeing737CL):
816 """Boeing 737-300 aircraft."""
817 dow = 32900
818
819 def __init__(self, flight):
820 super(B733, self).__init__(flight)
821 self.mtow = 56472
822 self.mlw = 51710
823 self.mzfw = 47625
824 self.maxTakeOffPitch = 13.4
825 self.maxTouchDownPitch = 12.0
826
827#---------------------------------------------------------------------------------------
828
829class B734(Boeing737CL):
830 """Boeing 737-400 aircraft."""
831 def __init__(self, flight):
832 super(B734, self).__init__(flight)
833 self.dow = 35100
834 self.mtow = 62822
835 self.mlw = 54885
836 self.mzfw = 51256
837 self.maxTakeOffPitch = 11.4
838 self.maxTouchDownPitch = 10
839
840#---------------------------------------------------------------------------------------
841
842class B735(Boeing737CL):
843 """Boeing 737-500 aircraft."""
844 dow = 31900
845
846 def __init__(self, flight):
847 super(B735, self).__init__(flight)
848 self.mtow = 62823
849 self.mlw = 49895
850 self.mzfw = 46720
851 self.maxTakeOffPitch = 14.7
852 self.maxTouchDownPitch = 13.2
853
854#---------------------------------------------------------------------------------------
855
856class DH8D(Aircraft):
857 """Bombardier Dash-8 Q400 aircraft.
858
859 The aircraft type-specific values in the aircraft state have the following
860 structure:
861 - fuel: left, right
862 - n1: left, right
863 - reverser: left, right."""
864 dow = 18508
865
866 def __init__(self, flight):
867 super(DH8D, self).__init__(flight, minLandingFuel = 2000)
868 self.mtow = 29574
869 self.mlw = 28123
870 self.mzfw = 26308
871 self.gearSpeedLimit = 215
872 self.flapSpeedLimits = { 5 : 200,
873 10 : 181,
874 15 : 172,
875 35 : 158 }
876 self.maxTakeOffPitch = 8.0
877 self.maxTouchDownPitch = 7.0
878 self.simBriefData = SimBriefData(["I-900", "II-900", "III-900",
879 "I-850", "II-850", "III-850"],
880 ["MCR", "ISC", "LRC", "HSC"],
881 ["I-850", "II-850", "III-850"])
882
883#---------------------------------------------------------------------------------------
884
885class Boeing767(Aircraft):
886 """Base class for the various aircraft in the Boeing 767 family.
887
888 The aircraft type-specific values in the aircraft state have the following
889 structure:
890 - fuel: left, centre, right
891 - n1: left, right
892 - reverser: left, right"""
893
894 def __init__(self, flight, minLandingFuel = 7000):
895 super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
896 self.gearSpeedLimit = 270
897 self.flapSpeedLimits = { 1 : 255,
898 5 : 235,
899 10 : 215,
900 20 : 215,
901 25 : 185,
902 30 : 175 }
903
904 @property
905 def derateType(self):
906 """Get the derate type for this type."""
907 return DERATE_BOEING
908
909#---------------------------------------------------------------------------------------
910
911class B762(Boeing767):
912 """Boeing 767-200 aircraft."""
913 dow = 84507
914
915 def __init__(self, flight):
916 super(B762, self).__init__(flight)
917 self.mtow = 159210
918 self.mlw = 126098
919 self.mzfw = 114758
920 self.maxTakeOffPitch = 13.1
921 self.maxTouchDownPitch = 11.6
922
923#---------------------------------------------------------------------------------------
924
925class B763(Boeing767):
926 """Boeing 767-300 aircraft."""
927 dow = 91311
928
929 def __init__(self, flight):
930 super(B763, self).__init__(flight)
931 self.mtow = 181436
932 self.mlw = 137892
933 self.mzfw = 114758
934 self.maxTakeOffPitch = 9.6
935 self.maxTouchDownPitch = 8.1
936 self.simBriefData = SimBriefData(["250/290/78"],
937 ["CI", "M76", "M78", "M80", "M82", "M84", "LRC"],
938 ["78/290/250"])
939
940 def setBookedFlight(self, bookedFlight):
941 """Update the aircraft based on the booked flight data (e.g. tail number)."""
942 if bookedFlight.tailNumber=="HA-LHD":
943 self.mtow = 159210
944 self.mlw = 126098
945
946#---------------------------------------------------------------------------------------
947
948class CRJ2(Aircraft):
949 """Bombardier CRJ-200 aircraft.
950
951 The aircraft type-specific values in the aircraft state have the following
952 structure:
953 - fuel: left, centre, right
954 - n1: left, right
955 - reverser: left, right."""
956 dow = 14549
957
958 def __init__(self, flight):
959 super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
960 self.mtow = 22995
961 self.mlw = 21319
962 self.mzfw = 19958
963 self.gearSpeedLimit = 240
964 self.flapSpeedLimits = { 8 : 260,
965 20 : 220,
966 30 : 190,
967 45 : 175 }
968 self.maxTakeOffPitch = 18.0
969 self.maxTouchDownPitch = 18.0
970 self.simBriefData = SimBriefData(["250/70", "290/74"],
971 ["CI", "LRC", "M70", "M72", "M74", "M77", "M80"],
972 ["74/290/250", "77/320/250"])
973
974#---------------------------------------------------------------------------------------
975
976class F70(Aircraft):
977 """Fokker 70 aircraft.
978
979 The aircraft type-specific values in the aircraft state have the following
980 structure:
981 - fuel: left, centre, right
982 - n1: left, right
983 - reverser: left, right."""
984 dow = 24283
985
986 def __init__(self, flight):
987 super(F70, self).__init__(flight, minLandingFuel = 1900)
988 self.mtow = 38100 # FIXME: differentiate by registration number,
989 # MTOW of HA-LMF: 41955
990 self.mlw = 36740
991 self.mzfw = 32655
992 self.gearSpeedLimit = 200
993 self.flapSpeedLimits = { 8 : 250,
994 15 : 220,
995 25 : 220,
996 42 : 180 }
997 self.reverseMinSpeed = 50
998 self.maxTakeOffPitch = 16.0
999 self.maxTouchDownPitch = 16.0
1000
1001 @property
1002 def derateType(self):
1003 """Get the derate type for this type."""
1004 return DERATE_EPR
1005
1006#---------------------------------------------------------------------------------------
1007
1008class DC3(Aircraft):
1009 """Lisunov Li-2 (DC-3) aircraft.
1010
1011 The aircraft type-specific values in the aircraft state have the following
1012 structure:
1013 - fuel: left aux, left, right, right aux
1014 - rpm: left, right
1015 - reverser: left, right."""
1016 dow = 8627
1017
1018 def __init__(self, flight):
1019 super(DC3, self).__init__(flight)
1020 self.mtow = 11884
1021 self.mlw = 11793
1022 self.mzfw = 11780
1023 self.gearSpeedLimit = 148
1024 self.flapSpeedLimits = { 15 : 135,
1025 30 : 99,
1026 45 : 97 }
1027
1028 def _checkFlightEnd(self, aircraftState):
1029 """Check if the end of the flight has arrived.
1030
1031 This implementation checks the RPM values to be 0."""
1032 for rpm in aircraftState.rpm:
1033 if rpm>0: return False
1034 return True
1035
1036#---------------------------------------------------------------------------------------
1037
1038class T134(Aircraft):
1039 """Tupolev Tu-134 aircraft.
1040
1041 The aircraft type-specific values in the aircraft state have the following
1042 structure:
1043 - fuel: left tip, left aux, centre, right aux, right tip, external 1,
1044 external 2
1045 - n1: left, right
1046 - reverser: left, right."""
1047 dow = 29500
1048
1049 def __init__(self, flight):
1050 super(T134, self).__init__(flight, minLandingFuel = 3000)
1051 self.mtow = 49000
1052 self.mlw = 43000
1053 self.mzfw = 38500
1054 self.gearSpeedLimit = 216
1055 self.flapSpeedLimits = { 10 : 450,
1056 20 : 400,
1057 30 : 300 }
1058 self.reverseMinSpeed = 50
1059
1060 self.hasStrobeLight = False
1061
1062 self.maxTakeOffPitch = 16.0
1063 self.maxTouchDownPitch = 16.0
1064
1065 @property
1066 def derateType(self):
1067 """Get the derate type for this type."""
1068 return DERATE_TUPOLEV
1069
1070 @property
1071 def speedInKnots(self):
1072 """Indicate if the speed is in knots."""
1073 return False
1074
1075 @property
1076 def aglInFeet(self):
1077 """Indicate if AGL altituedes are in feet."""
1078 return False
1079
1080 def _appendLightsLoggers(self):
1081 """Append the loggers needed for the lights."""
1082 self._checkers.append(checks.AnticollisionLightsLogger())
1083 self._checkers.append(checks.LandingLightsLogger())
1084 self._checkers.append(checks.NavLightsLogger())
1085
1086 def _appendLightsCheckers(self):
1087 """Append the checkers needed for the lights."""
1088 self._checkers.append(checks.TupolevAntiCollisionLightsChecker())
1089 self._checkers.append(checks.TupolevLandingLightsChecker())
1090 self._checkers.append(checks.LandingLightsChecker())
1091 self._checkers.append(checks.NavLightsChecker())
1092
1093#---------------------------------------------------------------------------------------
1094
1095class T154(Aircraft):
1096 """Tupolev Tu-154 aircraft.
1097
1098 The aircraft type-specific values in the aircraft state have the following
1099 structure:
1100 - fuel: left aux, left, centre, centre 2, right, right aux
1101 - n1: left, centre, right
1102 - reverser: left, right"""
1103 dow = 53259
1104
1105 def __init__(self, flight):
1106 super(T154, self).__init__(flight, minLandingFuel = 5000)
1107 self.mtow = 98000
1108 self.mlw = 78000
1109 self.mzfw = 72000
1110 self.gearSpeedLimit = 216
1111 self.flapSpeedLimits = { 15 : 227,
1112 28 : 194,
1113 45 : 162 }
1114 self.reverseMinSpeed = 50
1115
1116 self.hasStrobeLight = False
1117
1118 self.maxTakeOffPitch = 16.0
1119 self.maxTouchDownPitch = 16.0
1120 self.simBriefData = SimBriefData(["AUTO"],
1121 ["300/M80"],
1122 ["AUTO"])
1123
1124 @property
1125 def speedInKnots(self):
1126 """Indicate if the speed is in knots."""
1127 return False
1128
1129 @property
1130 def aglInFeet(self):
1131 """Indicate if AGL altituedes are in feet."""
1132 return False
1133
1134 @property
1135 def derateType(self):
1136 """Get the derate type for this type."""
1137 return DERATE_TUPOLEV
1138
1139 def setBookedFlight(self, bookedFlight):
1140 """Update the aircraft based on the booked flight data (e.g. tail number)."""
1141 if bookedFlight.tailNumber in ["HA-LCM", "HA-LCN", "HA-LCO", "HA-LCP",
1142 "HA-LCR", "HA-LCU", "HA-LCV"]:
1143 self.mtow = 100000
1144 self.mlw = 80000
1145 elif bookedFlight.tailNumber=="HA-LCX":
1146 self.mtow = 100000
1147 self.mlw = 80000
1148 self.mzfw = 74000
1149
1150 self.flapSpeedLimits = { 15 : 227,
1151 28 : 194,
1152 36 : 178,
1153 45 : 162 }
1154
1155 def _appendLightsLoggers(self):
1156 """Append the loggers needed for the lights."""
1157 self._checkers.append(checks.AnticollisionLightsLogger())
1158 self._checkers.append(checks.LandingLightsLogger())
1159 self._checkers.append(checks.NavLightsLogger())
1160
1161 def _appendLightsCheckers(self):
1162 """Append the checkers needed for the lights."""
1163 self._checkers.append(checks.AntiCollisionLightsChecker())
1164 self._checkers.append(checks.TupolevLandingLightsChecker())
1165 self._checkers.append(checks.LandingLightsChecker())
1166 self._checkers.append(checks.NavLightsChecker())
1167
1168#---------------------------------------------------------------------------------------
1169
1170class YK40(Aircraft):
1171 """Yakovlev Yak-40 aircraft.
1172
1173 The aircraft type-specific values in the aircraft state have the following
1174 structure:
1175 - fuel: left, right
1176 - n1: left, right
1177 - reverser: left, right"""
1178 dow = 9400
1179
1180 def __init__(self, flight):
1181 super(YK40, self).__init__(flight)
1182 self.mtow = 17200
1183 self.mlw = 16800
1184 self.mzfw = 12100
1185 self.gearSpeedLimit = 165
1186 self.flapSpeedLimits = { 20 : 165,
1187 35 : 135 }
1188
1189 self.hasStrobeLight = False
1190
1191 @property
1192 def speedInKnots(self):
1193 """Indicate if the speed is in knots."""
1194 return False
1195
1196 @property
1197 def aglInFeet(self):
1198 """Indicate if AGL altituedes are in feet."""
1199 return False
1200
1201 @property
1202 def derateType(self):
1203 """Get the derate type for this type."""
1204 return DERATE_TUPOLEV
1205
1206 def _appendLightsLoggers(self):
1207 """Append the loggers needed for the lights."""
1208 self._checkers.append(checks.AnticollisionLightsLogger())
1209 self._checkers.append(checks.LandingLightsLogger())
1210 self._checkers.append(checks.NavLightsLogger())
1211
1212 def _appendLightsCheckers(self):
1213 """Append the checkers needed for the lights."""
1214 self._checkers.append(checks.AntiCollisionLightsChecker())
1215 self._checkers.append(checks.LandingLightsChecker())
1216 self._checkers.append(checks.NavLightsChecker())
1217
1218#---------------------------------------------------------------------------------------
1219
1220class B462(Aircraft):
1221 """British Aerospace BAe-146 aircraft.
1222
1223 The aircraft type-specific values in the aircraft state have the following
1224 structure:
1225 - fuel: left, centre, right
1226 - n1: left outer, left inner, right inner, right outer
1227 - reverser: empty (the plane has no reversers)"""
1228 dow = 25706
1229
1230 def __init__(self, flight):
1231 super(B462, self).__init__(flight)
1232 self.mtow = 43998
1233 self.mlw = 38599
1234 self.mzfw = 33792
1235 self.gearSpeedLimit = 210
1236 self.flapSpeedLimits = { 18 : 217,
1237 24 : 180,
1238 30 : 170,
1239 33 : 150 }
1240
1241 @property
1242 def derateType(self):
1243 """Get the derate type for this type."""
1244 return DERATE_B462
1245
1246#---------------------------------------------------------------------------------------
1247
1248mostFuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1249 const.FUELTANK_LEFT_AUX,
1250 const.FUELTANK_CENTRE,
1251 const.FUELTANK_RIGHT_AUX,
1252 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1253
1254#---------------------------------------------------------------------------------------
1255
1256_classes = { const.AIRCRAFT_B736 : B736,
1257 const.AIRCRAFT_B737 : B737,
1258 const.AIRCRAFT_B738 : B738,
1259 const.AIRCRAFT_B738C : B738Charter,
1260 const.AIRCRAFT_B732 : B732,
1261 const.AIRCRAFT_B733 : B733,
1262 const.AIRCRAFT_B734 : B734,
1263 const.AIRCRAFT_B735 : B735,
1264 const.AIRCRAFT_DH8D : DH8D,
1265 const.AIRCRAFT_B762 : B762,
1266 const.AIRCRAFT_B763 : B763,
1267 const.AIRCRAFT_CRJ2 : CRJ2,
1268 const.AIRCRAFT_F70 : F70,
1269 const.AIRCRAFT_DC3 : DC3,
1270 const.AIRCRAFT_T134 : T134,
1271 const.AIRCRAFT_T154 : T154,
1272 const.AIRCRAFT_YK40 : YK40,
1273 const.AIRCRAFT_B462 : B462 }
1274
1275#---------------------------------------------------------------------------------------
1276
1277def getClass(aircraftType):
1278 """Get the class representing the given aircraft types"""
1279 return _classes[aircraftType]
1280
1281#---------------------------------------------------------------------------------------
1282
1283if __name__ == "__main__":
1284 value = SmoothedValue()
1285
1286 print("Adding 1, 12.0")
1287 value.add(1, 12.0)
1288 print(value.get())
1289
1290 print("Adding 1, 15.0")
1291 value.add(1, 15.0)
1292 print(value.get())
1293
1294 print("Adding 2, 18.0")
1295 value.add(2, 18.0)
1296 print(value.get())
1297
1298 print("Adding 2, 20.0")
1299 value.add(2, 20.0)
1300 print(value.get())
1301
1302 print("Adding 5, 22.0")
1303 value.add(5, 22.0)
1304 print(value.get())
1305
1306 print("Adding 5, 25.0")
1307 value.add(5, 25.0)
1308 print(value.get())
1309
1310 print("Adding 5, 29.0")
1311 value.add(5, 29.0)
1312 print(value.get())
1313
1314 print("Adding 5, 21.0")
1315 value.add(5, 21.0)
1316 print(value.get())
1317
1318 print("Adding 5, 26.0")
1319 value.add(5, 26.0)
1320 print(value.get())
1321
1322 print("Adding 2, 30.0")
1323 value.add(2, 30.0)
1324 print(value.get())
1325
1326 print("Adding 2, 55.0")
1327 value.add(2, 55.0)
1328 print(value.get())
1329
1330#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.