source: src/mlx/acft.py@ 1067:9ce88d0b881d

python3
Last change on this file since 1067:9ce88d0b881d was 1043:3629e16455ef, checked in by István Váradi <ivaradi@…>, 3 years ago

Unreadable N1 values are handled better (re #359)

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 if aircraftState.n1 is not None:
519 for n1 in aircraftState.n1:
520 if n1 is not None and n1>=0.5: return False
521 return True
522
523 def updateV1R2(self):
524 """Update the V1, Vr and V2 values from the flight, if the these values
525 have already been logged."""
526 if self._v1r2LineIndex is not None:
527 self._logV1R2()
528
529 def updateDerate(self):
530 """Update the derate value from the flight, if the these values
531 have already been logged."""
532 if self._derateLineID is not None:
533 self._logDerate()
534
535 def updateTakeoffAntiIce(self):
536 """Update the take-off anti-ice setting."""
537 if self._takeoffAntiIceLineID is not None:
538 self._logTakeoffAntiIce()
539
540 def _appendLightsLoggers(self):
541 """Append the loggers needed for the lights.
542
543 This default implementation adds the loggers for the anti-collision
544 lights, the landing lights, the strobe lights and the NAV lights."""
545 self._checkers.append(checks.AnticollisionLightsLogger())
546 self._checkers.append(checks.LandingLightsLogger())
547 self._checkers.append(checks.StrobeLightsLogger())
548 self._checkers.append(checks.NavLightsLogger())
549
550 def _appendLightsCheckers(self):
551 """Append the checkers needed for the lights.
552
553 This default implementation adds the checkers for the anti-collision
554 lights, the landing lights, the strobe lights and the NAV lights."""
555 self._checkers.append(checks.AntiCollisionLightsChecker())
556 self._checkers.append(checks.LandingLightsChecker())
557 self._checkers.append(checks.NavLightsChecker())
558 self._checkers.append(checks.StrobeLightsChecker())
559
560 def _speedToLog(self, speed):
561 """Convert the given speed (being either None or expressed in the
562 flight's speed unit into a string."""
563 if speed is None:
564 return "-"
565 else:
566 return str(speed) + " " + self._flight.getEnglishSpeedUnit()
567
568 def _logV1R2(self, state = None):
569 """Log the V1, Vr and V2 value either newly, or by updating the
570 corresponding line."""
571 message = "Calc. TO speeds: V1: %s, VR: %s, V2: %s" % \
572 (self._speedToLog(self._flight.v1),
573 self._speedToLog(self._flight.vr),
574 self._speedToLog(self._flight.v2))
575
576 if self._v1r2LineIndex is None:
577 if state is None:
578 state = self._aircraftState
579 self._v1r2LineIndex = \
580 self.logger.message(state.timestamp, message)
581 else:
582 self.logger.updateLine(self._v1r2LineIndex, message)
583
584 def _logDerate(self, state = None):
585 """Log the derate values either newly or by updating the corresponding
586 line."""
587 dt = self.derateType
588 if dt==DERATE_NONE:
589 return
590
591 message = self.getDerateLine(self._flight.derate)
592 if message is not None:
593 if self._derateLineID is None:
594 if state is None:
595 state = self._aircraftState
596 self._derateLineID = \
597 self.logger.message(state.timestamp, message)
598 else:
599 self.logger.updateLine(self._derateLineID, message)
600
601 def _logTakeoffAntiIce(self, state = None):
602 """Log the take-off anti-ice setting either newly or by updating the
603 corresponding line."""
604 antiIceOn = self._flight.takeoffAntiIceOn
605 if state is not None:
606 antiIceOn = antiIceOn or state.antiIceOn is True
607 self._flight.takeoffAntiIceOn = antiIceOn
608
609 message = "Anti-ice was turned %s" % \
610 ("ON" if antiIceOn else "OFF")
611
612 if self._takeoffAntiIceLineID is None:
613 if state is None:
614 state = self._aircraftState
615 self._takeoffAntiIceLineID = \
616 self.logger.message(state.timestamp, message)
617 else:
618 self.logger.updateLine(self._takeoffAntiIceLineID, message)
619
620 def updateVRef(self):
621 """Update the Vref value from the flight, if the Vref value has already
622 been logged."""
623 if self._vrefLineIndex is not None:
624 self._logVRef()
625
626 def _logVRef(self):
627 """Log the Vref value either newly, or by updating the corresponding
628 line."""
629 message = "VRef speed calculated by the pilot: %s" % \
630 (self._speedToLog(self._flight.vref),)
631 if self._vrefLineIndex is None:
632 self._vrefLineIndex = \
633 self.logger.message(self._aircraftState.timestamp, message)
634 else:
635 self.logger.updateLine(self._vrefLineIndex, message)
636
637 def updateLandingAntiIce(self):
638 """Update the landing anti-ice setting."""
639 if self._landingAntiIceLineID is not None:
640 self._logLandingAntiIce()
641
642 def _logLandingAntiIce(self, state = None):
643 """Log the landing anti-ice setting either newly or by updating the
644 corresponding line."""
645 antiIceOn = self._flight.landingAntiIceOn
646 if state is not None:
647 antiIceOn = antiIceOn or state.antiIceOn is True
648 self._flight.landingAntiIceOn = antiIceOn
649
650 message = "Anti-ice was turned %s" % \
651 ("ON" if antiIceOn else "OFF")
652
653 if self._landingAntiIceLineID is None:
654 if state is None:
655 state = self._aircraftState
656 self._landingAntiIceLineID = \
657 self.logger.message(state.timestamp, message)
658 else:
659 self.logger.updateLine(self._landingAntiIceLineID, message)
660
661 def _fleetRetrieved(self, fleet):
662 """Callback for the fleet retrieval result."""
663 if fleet is not None:
664 gateList = ""
665 occupiedGateNumbers = fleet.getOccupiedGateNumbers()
666 for gate in gates.lhbpGates.gates:
667 if gate.isAvailable(gates.lhbpGates, occupiedGateNumbers):
668 if gateList: gateList += ", "
669 gateList += gate.number
670 fs.sendMessage(const.MESSAGETYPE_GATE_SYSTEM,
671 "Free gates: " + gateList, 20)
672
673
674 def _logRadios(self, aircraftState):
675 """Log the radios from the given aircraft state."""
676 flight = self._flight
677 logger = flight.logger
678
679 self._ilsLogger.forceLog(flight, logger, aircraftState)
680 self._nav1Logger.forceLog(flight, logger, aircraftState)
681 self._nav2Logger.forceLog(flight, logger, aircraftState)
682 self._adf1Logger.forceLog(flight, logger, aircraftState)
683 self._adf2Logger.forceLog(flight, logger, aircraftState)
684
685 def _logQNH(self, aircraftState):
686 """Log the current QNH along with the altimeter setting."""
687 self.logger.message(aircraftState.timestamp,
688 "QNH: %.2f hPa, altimeter: %.2f hPa" % \
689 (aircraftState.qnh, aircraftState.altimeter))
690
691 def _logNameAndModel(self, timestamp):
692 """Log the aircraft's name and model with taking the timestamp from the
693 given state."""
694 self._flight.logger.message(timestamp,
695 "Aircraft: name='%s', model='%s'" % \
696 (self._name, self._modelName))
697
698#---------------------------------------------------------------------------------------
699
700class Boeing737(Aircraft):
701 """Base class for the various aircraft in the Boeing 737 family.
702
703 The aircraft type-specific values in the aircraft state have the following
704 structure:
705 - fuel: left, centre, right
706 - n1: left, right
707 - reverser: left, right"""
708 def __init__(self, flight, minLandingFuel = 2500,
709 recommendedLandingFuel = 3500):
710 super(Boeing737, self).__init__(flight,
711 minLandingFuel = minLandingFuel,
712 recommendedLandingFuel =
713 recommendedLandingFuel)
714
715 self.gearSpeedLimit = 270
716 self.flapSpeedLimits = { 1 : 260,
717 2 : 260,
718 5 : 250,
719 10 : 210,
720 15 : 200,
721 25 : 190,
722 30 : 175,
723 40 : 162 }
724
725 @property
726 def derateType(self):
727 """Get the derate type for this type."""
728 return DERATE_BOEING
729
730#---------------------------------------------------------------------------------------
731
732class B736(Boeing737):
733 """Boeing 737-600 aircraft."""
734 dow = 38307
735
736 def __init__(self, flight):
737 super(B736, self).__init__(flight)
738 self.mtow = 58328
739 self.mlw = 54657
740 self.mzfw = 51482
741 self.maxTakeOffPitch = 16.2
742 self.maxTouchDownPitch = 14.7
743 self.simBriefData = SimBriefData(["250/280/78"],
744 ["CI", "M75", "M78", "M79", "M80", "LRC"],
745 ["78/280/250"])
746
747#---------------------------------------------------------------------------------------
748
749class B737(Boeing737):
750 """Boeing 737-700 aircraft."""
751 dow = 39250
752
753 def __init__(self, flight):
754 super(B737, self).__init__(flight)
755 self.mtow = 61410
756 self.mlw = 58059
757 self.mzfw = 54657
758 self.maxTakeOffPitch = 14.7
759 self.maxTouchDownPitch = 13.2
760 self.simBriefData = SimBriefData(["250/280/78"],
761 ["CI", "M75", "M78", "M79", "M80", "LRC"],
762 ["78/280/250", "78/250/250"])
763
764#---------------------------------------------------------------------------------------
765
766class B738(Boeing737):
767 """Boeing 737-800 aircraft."""
768 dow = 42690
769
770 def __init__(self, flight):
771 super(B738, self).__init__(flight)
772 self.mtow = 71791
773 self.mlw = 65317
774 self.mzfw = 61688
775 self.maxTakeOffPitch = 11
776 self.maxTouchDownPitch = 9.5
777 self.simBriefData = SimBriefData(["250/280/78"],
778 ["CI", "M76", "M78", "M79", "M80", "LRC"],
779 ["78/280/250", "78/250/250"])
780
781#---------------------------------------------------------------------------------------
782
783class B738Charter(B738):
784 """Boeing 737-800 aircraft used for charters."""
785 def __init__(self, flight):
786 super(B738Charter, self).__init__(flight)
787 self.mtow = 77791
788 self.simBriefData = SimBriefData(["AUTO"],
789 ["280/M74"],
790 ["AUTO"])
791
792#---------------------------------------------------------------------------------------
793
794class Boeing737CL(Boeing737):
795 """Base class for the various aircraft in the Boeing 737 Classic family."""
796 def __init__(self, flight):
797 super(Boeing737CL, self).__init__(flight, minLandingFuel = 3500,
798 recommendedLandingFuel = None)
799
800#---------------------------------------------------------------------------------------
801
802class B732(Boeing737CL):
803 """Boeing 737-200 aircraft."""
804 dow = 27646
805
806 def __init__(self, flight):
807 super(B732, self).__init__(flight)
808 self.mtow = 52390
809 self.mlw = 46720
810 self.mzfw = 43091
811 self.maxTakeOffPitch = 15.5
812 self.maxTouchDownPitch = 15.5
813
814#---------------------------------------------------------------------------------------
815
816class B733(Boeing737CL):
817 """Boeing 737-300 aircraft."""
818 dow = 32900
819
820 def __init__(self, flight):
821 super(B733, self).__init__(flight)
822 self.mtow = 56472
823 self.mlw = 51710
824 self.mzfw = 47625
825 self.maxTakeOffPitch = 13.4
826 self.maxTouchDownPitch = 12.0
827
828#---------------------------------------------------------------------------------------
829
830class B734(Boeing737CL):
831 """Boeing 737-400 aircraft."""
832 def __init__(self, flight):
833 super(B734, self).__init__(flight)
834 self.dow = 35100
835 self.mtow = 62822
836 self.mlw = 54885
837 self.mzfw = 51256
838 self.maxTakeOffPitch = 11.4
839 self.maxTouchDownPitch = 10
840
841#---------------------------------------------------------------------------------------
842
843class B735(Boeing737CL):
844 """Boeing 737-500 aircraft."""
845 dow = 31900
846
847 def __init__(self, flight):
848 super(B735, self).__init__(flight)
849 self.mtow = 62823
850 self.mlw = 49895
851 self.mzfw = 46720
852 self.maxTakeOffPitch = 14.7
853 self.maxTouchDownPitch = 13.2
854
855#---------------------------------------------------------------------------------------
856
857class DH8D(Aircraft):
858 """Bombardier Dash-8 Q400 aircraft.
859
860 The aircraft type-specific values in the aircraft state have the following
861 structure:
862 - fuel: left, right
863 - n1: left, right
864 - reverser: left, right."""
865 dow = 18508
866
867 def __init__(self, flight):
868 super(DH8D, self).__init__(flight, minLandingFuel = 2000)
869 self.mtow = 29574
870 self.mlw = 28123
871 self.mzfw = 26308
872 self.gearSpeedLimit = 215
873 self.flapSpeedLimits = { 5 : 200,
874 10 : 181,
875 15 : 172,
876 35 : 158 }
877 self.maxTakeOffPitch = 8.0
878 self.maxTouchDownPitch = 7.0
879 self.simBriefData = SimBriefData(["I-900", "II-900", "III-900",
880 "I-850", "II-850", "III-850"],
881 ["MCR", "ISC", "LRC", "HSC"],
882 ["I-850", "II-850", "III-850"])
883
884#---------------------------------------------------------------------------------------
885
886class Boeing767(Aircraft):
887 """Base class for the various aircraft in the Boeing 767 family.
888
889 The aircraft type-specific values in the aircraft state have the following
890 structure:
891 - fuel: left, centre, right
892 - n1: left, right
893 - reverser: left, right"""
894
895 def __init__(self, flight, minLandingFuel = 7000):
896 super(Boeing767, self).__init__(flight, minLandingFuel = minLandingFuel)
897 self.gearSpeedLimit = 270
898 self.flapSpeedLimits = { 1 : 255,
899 5 : 235,
900 10 : 215,
901 20 : 215,
902 25 : 185,
903 30 : 175 }
904
905 @property
906 def derateType(self):
907 """Get the derate type for this type."""
908 return DERATE_BOEING
909
910#---------------------------------------------------------------------------------------
911
912class B762(Boeing767):
913 """Boeing 767-200 aircraft."""
914 dow = 84507
915
916 def __init__(self, flight):
917 super(B762, self).__init__(flight)
918 self.mtow = 159210
919 self.mlw = 126098
920 self.mzfw = 114758
921 self.maxTakeOffPitch = 13.1
922 self.maxTouchDownPitch = 11.6
923
924#---------------------------------------------------------------------------------------
925
926class B763(Boeing767):
927 """Boeing 767-300 aircraft."""
928 dow = 91311
929
930 def __init__(self, flight):
931 super(B763, self).__init__(flight)
932 self.mtow = 181436
933 self.mlw = 137892
934 self.mzfw = 114758
935 self.maxTakeOffPitch = 9.6
936 self.maxTouchDownPitch = 8.1
937 self.simBriefData = SimBriefData(["250/290/78"],
938 ["CI", "M76", "M78", "M80", "M82", "M84", "LRC"],
939 ["78/290/250"])
940
941 def setBookedFlight(self, bookedFlight):
942 """Update the aircraft based on the booked flight data (e.g. tail number)."""
943 if bookedFlight.tailNumber=="HA-LHD":
944 self.mtow = 159210
945 self.mlw = 126098
946
947#---------------------------------------------------------------------------------------
948
949class CRJ2(Aircraft):
950 """Bombardier CRJ-200 aircraft.
951
952 The aircraft type-specific values in the aircraft state have the following
953 structure:
954 - fuel: left, centre, right
955 - n1: left, right
956 - reverser: left, right."""
957 dow = 14549
958
959 def __init__(self, flight):
960 super(CRJ2, self).__init__(flight, minLandingFuel = 1000)
961 self.mtow = 22995
962 self.mlw = 21319
963 self.mzfw = 19958
964 self.gearSpeedLimit = 240
965 self.flapSpeedLimits = { 8 : 260,
966 20 : 220,
967 30 : 190,
968 45 : 175 }
969 self.maxTakeOffPitch = 18.0
970 self.maxTouchDownPitch = 18.0
971 self.simBriefData = SimBriefData(["250/70", "290/74"],
972 ["CI", "LRC", "M70", "M72", "M74", "M77", "M80"],
973 ["74/290/250", "77/320/250"])
974
975#---------------------------------------------------------------------------------------
976
977class F70(Aircraft):
978 """Fokker 70 aircraft.
979
980 The aircraft type-specific values in the aircraft state have the following
981 structure:
982 - fuel: left, centre, right
983 - n1: left, right
984 - reverser: left, right."""
985 dow = 24283
986
987 def __init__(self, flight):
988 super(F70, self).__init__(flight, minLandingFuel = 1900)
989 self.mtow = 38100 # FIXME: differentiate by registration number,
990 # MTOW of HA-LMF: 41955
991 self.mlw = 36740
992 self.mzfw = 32655
993 self.gearSpeedLimit = 200
994 self.flapSpeedLimits = { 8 : 250,
995 15 : 220,
996 25 : 220,
997 42 : 180 }
998 self.reverseMinSpeed = 50
999 self.maxTakeOffPitch = 16.0
1000 self.maxTouchDownPitch = 16.0
1001
1002 @property
1003 def derateType(self):
1004 """Get the derate type for this type."""
1005 return DERATE_EPR
1006
1007#---------------------------------------------------------------------------------------
1008
1009class DC3(Aircraft):
1010 """Lisunov Li-2 (DC-3) aircraft.
1011
1012 The aircraft type-specific values in the aircraft state have the following
1013 structure:
1014 - fuel: left aux, left, right, right aux
1015 - rpm: left, right
1016 - reverser: left, right."""
1017 dow = 8627
1018
1019 def __init__(self, flight):
1020 super(DC3, self).__init__(flight)
1021 self.mtow = 11884
1022 self.mlw = 11793
1023 self.mzfw = 11780
1024 self.gearSpeedLimit = 148
1025 self.flapSpeedLimits = { 15 : 135,
1026 30 : 99,
1027 45 : 97 }
1028
1029 def _checkFlightEnd(self, aircraftState):
1030 """Check if the end of the flight has arrived.
1031
1032 This implementation checks the RPM values to be 0."""
1033 for rpm in aircraftState.rpm:
1034 if rpm>0: return False
1035 return True
1036
1037#---------------------------------------------------------------------------------------
1038
1039class T134(Aircraft):
1040 """Tupolev Tu-134 aircraft.
1041
1042 The aircraft type-specific values in the aircraft state have the following
1043 structure:
1044 - fuel: left tip, left aux, centre, right aux, right tip, external 1,
1045 external 2
1046 - n1: left, right
1047 - reverser: left, right."""
1048 dow = 29500
1049
1050 def __init__(self, flight):
1051 super(T134, self).__init__(flight, minLandingFuel = 3000)
1052 self.mtow = 49000
1053 self.mlw = 43000
1054 self.mzfw = 38500
1055 self.gearSpeedLimit = 216
1056 self.flapSpeedLimits = { 10 : 450,
1057 20 : 400,
1058 30 : 300 }
1059 self.reverseMinSpeed = 50
1060
1061 self.hasStrobeLight = False
1062
1063 self.maxTakeOffPitch = 16.0
1064 self.maxTouchDownPitch = 16.0
1065
1066 @property
1067 def derateType(self):
1068 """Get the derate type for this type."""
1069 return DERATE_TUPOLEV
1070
1071 @property
1072 def speedInKnots(self):
1073 """Indicate if the speed is in knots."""
1074 return False
1075
1076 @property
1077 def aglInFeet(self):
1078 """Indicate if AGL altituedes are in feet."""
1079 return False
1080
1081 def _appendLightsLoggers(self):
1082 """Append the loggers needed for the lights."""
1083 self._checkers.append(checks.AnticollisionLightsLogger())
1084 self._checkers.append(checks.LandingLightsLogger())
1085 self._checkers.append(checks.NavLightsLogger())
1086
1087 def _appendLightsCheckers(self):
1088 """Append the checkers needed for the lights."""
1089 self._checkers.append(checks.TupolevAntiCollisionLightsChecker())
1090 self._checkers.append(checks.TupolevLandingLightsChecker())
1091 self._checkers.append(checks.LandingLightsChecker())
1092 self._checkers.append(checks.NavLightsChecker())
1093
1094#---------------------------------------------------------------------------------------
1095
1096class T154(Aircraft):
1097 """Tupolev Tu-154 aircraft.
1098
1099 The aircraft type-specific values in the aircraft state have the following
1100 structure:
1101 - fuel: left aux, left, centre, centre 2, right, right aux
1102 - n1: left, centre, right
1103 - reverser: left, right"""
1104 dow = 53259
1105
1106 def __init__(self, flight):
1107 super(T154, self).__init__(flight, minLandingFuel = 5000)
1108 self.mtow = 98000
1109 self.mlw = 78000
1110 self.mzfw = 72000
1111 self.gearSpeedLimit = 216
1112 self.flapSpeedLimits = { 15 : 227,
1113 28 : 194,
1114 45 : 162 }
1115 self.reverseMinSpeed = 50
1116
1117 self.hasStrobeLight = False
1118
1119 self.maxTakeOffPitch = 16.0
1120 self.maxTouchDownPitch = 16.0
1121 self.simBriefData = SimBriefData(["AUTO"],
1122 ["300/M80"],
1123 ["AUTO"])
1124
1125 @property
1126 def speedInKnots(self):
1127 """Indicate if the speed is in knots."""
1128 return False
1129
1130 @property
1131 def aglInFeet(self):
1132 """Indicate if AGL altituedes are in feet."""
1133 return False
1134
1135 @property
1136 def derateType(self):
1137 """Get the derate type for this type."""
1138 return DERATE_TUPOLEV
1139
1140 def setBookedFlight(self, bookedFlight):
1141 """Update the aircraft based on the booked flight data (e.g. tail number)."""
1142 if bookedFlight.tailNumber in ["HA-LCM", "HA-LCN", "HA-LCO", "HA-LCP",
1143 "HA-LCR", "HA-LCU", "HA-LCV"]:
1144 self.mtow = 100000
1145 self.mlw = 80000
1146 elif bookedFlight.tailNumber=="HA-LCX":
1147 self.mtow = 100000
1148 self.mlw = 80000
1149 self.mzfw = 74000
1150
1151 self.flapSpeedLimits = { 15 : 227,
1152 28 : 194,
1153 36 : 178,
1154 45 : 162 }
1155
1156 def _appendLightsLoggers(self):
1157 """Append the loggers needed for the lights."""
1158 self._checkers.append(checks.AnticollisionLightsLogger())
1159 self._checkers.append(checks.LandingLightsLogger())
1160 self._checkers.append(checks.NavLightsLogger())
1161
1162 def _appendLightsCheckers(self):
1163 """Append the checkers needed for the lights."""
1164 self._checkers.append(checks.AntiCollisionLightsChecker())
1165 self._checkers.append(checks.TupolevLandingLightsChecker())
1166 self._checkers.append(checks.LandingLightsChecker())
1167 self._checkers.append(checks.NavLightsChecker())
1168
1169#---------------------------------------------------------------------------------------
1170
1171class YK40(Aircraft):
1172 """Yakovlev Yak-40 aircraft.
1173
1174 The aircraft type-specific values in the aircraft state have the following
1175 structure:
1176 - fuel: left, right
1177 - n1: left, right
1178 - reverser: left, right"""
1179 dow = 9400
1180
1181 def __init__(self, flight):
1182 super(YK40, self).__init__(flight)
1183 self.mtow = 17200
1184 self.mlw = 16800
1185 self.mzfw = 12100
1186 self.gearSpeedLimit = 165
1187 self.flapSpeedLimits = { 20 : 165,
1188 35 : 135 }
1189
1190 self.hasStrobeLight = False
1191
1192 @property
1193 def speedInKnots(self):
1194 """Indicate if the speed is in knots."""
1195 return False
1196
1197 @property
1198 def aglInFeet(self):
1199 """Indicate if AGL altituedes are in feet."""
1200 return False
1201
1202 @property
1203 def derateType(self):
1204 """Get the derate type for this type."""
1205 return DERATE_TUPOLEV
1206
1207 def _appendLightsLoggers(self):
1208 """Append the loggers needed for the lights."""
1209 self._checkers.append(checks.AnticollisionLightsLogger())
1210 self._checkers.append(checks.LandingLightsLogger())
1211 self._checkers.append(checks.NavLightsLogger())
1212
1213 def _appendLightsCheckers(self):
1214 """Append the checkers needed for the lights."""
1215 self._checkers.append(checks.AntiCollisionLightsChecker())
1216 self._checkers.append(checks.LandingLightsChecker())
1217 self._checkers.append(checks.NavLightsChecker())
1218
1219#---------------------------------------------------------------------------------------
1220
1221class B462(Aircraft):
1222 """British Aerospace BAe-146 aircraft.
1223
1224 The aircraft type-specific values in the aircraft state have the following
1225 structure:
1226 - fuel: left, centre, right
1227 - n1: left outer, left inner, right inner, right outer
1228 - reverser: empty (the plane has no reversers)"""
1229 dow = 25706
1230
1231 def __init__(self, flight):
1232 super(B462, self).__init__(flight)
1233 self.mtow = 43998
1234 self.mlw = 38599
1235 self.mzfw = 33792
1236 self.gearSpeedLimit = 210
1237 self.flapSpeedLimits = { 18 : 217,
1238 24 : 180,
1239 30 : 170,
1240 33 : 150 }
1241
1242 @property
1243 def derateType(self):
1244 """Get the derate type for this type."""
1245 return DERATE_B462
1246
1247#---------------------------------------------------------------------------------------
1248
1249mostFuelTanks = [const.FUELTANK_LEFT_TIP, const.FUELTANK_EXTERNAL1,
1250 const.FUELTANK_LEFT_AUX,
1251 const.FUELTANK_CENTRE,
1252 const.FUELTANK_RIGHT_AUX,
1253 const.FUELTANK_EXTERNAL2, const.FUELTANK_RIGHT_TIP]
1254
1255#---------------------------------------------------------------------------------------
1256
1257_classes = { const.AIRCRAFT_B736 : B736,
1258 const.AIRCRAFT_B737 : B737,
1259 const.AIRCRAFT_B738 : B738,
1260 const.AIRCRAFT_B738C : B738Charter,
1261 const.AIRCRAFT_B732 : B732,
1262 const.AIRCRAFT_B733 : B733,
1263 const.AIRCRAFT_B734 : B734,
1264 const.AIRCRAFT_B735 : B735,
1265 const.AIRCRAFT_DH8D : DH8D,
1266 const.AIRCRAFT_B762 : B762,
1267 const.AIRCRAFT_B763 : B763,
1268 const.AIRCRAFT_CRJ2 : CRJ2,
1269 const.AIRCRAFT_F70 : F70,
1270 const.AIRCRAFT_DC3 : DC3,
1271 const.AIRCRAFT_T134 : T134,
1272 const.AIRCRAFT_T154 : T154,
1273 const.AIRCRAFT_YK40 : YK40,
1274 const.AIRCRAFT_B462 : B462 }
1275
1276#---------------------------------------------------------------------------------------
1277
1278def getClass(aircraftType):
1279 """Get the class representing the given aircraft types"""
1280 return _classes[aircraftType]
1281
1282#---------------------------------------------------------------------------------------
1283
1284if __name__ == "__main__":
1285 value = SmoothedValue()
1286
1287 print("Adding 1, 12.0")
1288 value.add(1, 12.0)
1289 print(value.get())
1290
1291 print("Adding 1, 15.0")
1292 value.add(1, 15.0)
1293 print(value.get())
1294
1295 print("Adding 2, 18.0")
1296 value.add(2, 18.0)
1297 print(value.get())
1298
1299 print("Adding 2, 20.0")
1300 value.add(2, 20.0)
1301 print(value.get())
1302
1303 print("Adding 5, 22.0")
1304 value.add(5, 22.0)
1305 print(value.get())
1306
1307 print("Adding 5, 25.0")
1308 value.add(5, 25.0)
1309 print(value.get())
1310
1311 print("Adding 5, 29.0")
1312 value.add(5, 29.0)
1313 print(value.get())
1314
1315 print("Adding 5, 21.0")
1316 value.add(5, 21.0)
1317 print(value.get())
1318
1319 print("Adding 5, 26.0")
1320 value.add(5, 26.0)
1321 print(value.get())
1322
1323 print("Adding 2, 30.0")
1324 value.add(2, 30.0)
1325 print(value.get())
1326
1327 print("Adding 2, 55.0")
1328 value.add(2, 55.0)
1329 print(value.get())
1330
1331#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.