source: src/mlx/flight.py@ 384:97052bda0e22

Last change on this file since 384:97052bda0e22 was 384:97052bda0e22, checked in by István Váradi <ivaradi@…>, 11 years ago

Implemented support for entering derate values (#158)

File size: 15.7 KB
RevLine 
[8]1
[176]2from soundsched import SoundScheduler, ChecklistScheduler
[349]3from checks import SpeedChecker
[170]4
[8]5import const
[89]6import util
[8]7
8import threading
9
10#---------------------------------------------------------------------------------------
11
[298]12## @package mlx.flight
13#
14# The global flight state.
15#
16# This module defines a single class, \ref Flight, which represents the flight
17# in progress.
18
19#---------------------------------------------------------------------------------------
20
[8]21class Flight(object):
22 """The object with the global flight state.
[348]23
[8]24 It is also the hub for the other main objects participating in the handling of
25 the flight."""
[383]26 @staticmethod
27 def canLogCruiseAltitude(stage):
28 """Determine if the cruise altitude can be logged in the given
29 stage."""
30 return stage in [const.STAGE_CRUISE, const.STAGE_DESCENT,
31 const.STAGE_LANDING]
32
[29]33 def __init__(self, logger, gui):
[8]34 """Construct the flight."""
35 self._stage = None
36 self.logger = logger
[29]37 self._gui = gui
[11]38
[31]39 gui.resetFlightStatus()
40
[170]41 self._pilotHotkeyPressed = False
42 self._checklistHotkeyPressed = False
43
[9]44 self.flareTimeFromFS = False
[11]45
[8]46 self.aircraftType = None
47 self.aircraft = None
48 self.simulator = None
49
[89]50 self.blockTimeStart = None
51 self.flightTimeStart = None
[365]52 self.climbTimeStart = None
53 self.cruiseTimeStart = None
54 self.descentTimeStart = None
[89]55 self.flightTimeEnd = None
56 self.blockTimeEnd = None
57
[349]58 self._rtoState = None
59 self._rtoLogEntryID = None
60
[89]61 self._lastDistanceTime = None
62 self._previousLatitude = None
63 self._previousLongitude = None
64 self.flownDistance = 0.0
65
66 self.startFuel = None
67 self.endFuel = None
68
[8]69 self._endCondition = threading.Condition()
70
[9]71 self._flareStart = None
72 self._flareStartFS = None
73
[170]74 self._tdRate = None
75
[176]76 self._soundScheduler = SoundScheduler(self)
77 self._checklistScheduler = ChecklistScheduler(self)
78
[8]79 @property
[134]80 def config(self):
81 """Get the configuration."""
82 return self._gui.config
83
84 @property
[8]85 def stage(self):
86 """Get the flight stage."""
87 return self._stage
88
[84]89 @property
[215]90 def loggedIn(self):
91 """Indicate if the user has logged in properly."""
92 return self._gui.loggedIn
93
94 @property
[184]95 def entranceExam(self):
96 """Get whether an entrance exam is being performed."""
97 return self._gui.entranceExam
98
99 @property
[134]100 def bookedFlight(self):
101 """Get the booked flight."""
102 return self._gui.bookedFlight
103
104 @property
[303]105 def numCrew(self):
106 """Get the number of crew members on the flight."""
107 return self._gui.numCrew
108
109 @property
110 def numPassengers(self):
111 """Get the number of passengers on the flight."""
112 return self._gui.numPassengers
113
114 @property
115 def bagWeight(self):
116 """Get the baggage weight for the flight."""
117 return self._gui.bagWeight
118
119 @property
[262]120 def cargoWeight(self):
121 """Get the cargo weight for the flight."""
122 return self._gui.cargoWeight
123
124 @property
[303]125 def mailWeight(self):
126 """Get the mail weight for the flight."""
127 return self._gui.mailWeight
128
129 @property
[84]130 def zfw(self):
131 """Get the Zero-Fuel Weight of the flight."""
132 return self._gui.zfw
133
134 @property
[262]135 def filedCruiseAltitude(self):
136 """Get the filed cruise altitude."""
137 return self._gui.filedCruiseAltitude
138
139 @property
[84]140 def cruiseAltitude(self):
141 """Get the cruise altitude of the flight."""
142 return self._gui.cruiseAltitude
143
144 @property
[262]145 def route(self):
146 """Get the route of the flight."""
147 return self._gui.route
148
149 @property
150 def departureMETAR(self):
151 """Get the departure METAR of the flight."""
152 return self._gui.departureMETAR
153
154 @property
155 def arrivalMETAR(self):
156 """Get the arrival METAR of the flight."""
157 return self._gui.arrivalMETAR
158
159 @property
160 def departureRunway(self):
161 """Get the departure runway."""
162 return self._gui.departureRunway
163
164 @property
165 def sid(self):
166 """Get the SID followed."""
167 return self._gui.sid
168
169 @property
[84]170 def v1(self):
171 """Get the V1 speed of the flight."""
172 return self._gui.v1
173
174 @property
175 def vr(self):
176 """Get the Vr speed of the flight."""
177 return self._gui.vr
178
179 @property
180 def v2(self):
181 """Get the V2 speed of the flight."""
182 return self._gui.v2
183
[86]184 @property
[384]185 def derate(self):
186 """Get the derate value of the flight."""
187 return self._gui.derate
188
189 @property
[262]190 def star(self):
191 """Get the STAR planned."""
192 return self._gui.star
193
194 @property
195 def transition(self):
196 """Get the transition planned."""
197 return self._gui.transition
198
199 @property
200 def approachType(self):
201 """Get the approach type."""
202 return self._gui.approachType
203
204 @property
205 def arrivalRunway(self):
206 """Get the arrival runway."""
207 return self._gui.arrivalRunway
208
209 @property
[86]210 def vref(self):
211 """Get the VRef speed of the flight."""
212 return self._gui.vref
213
[170]214 @property
215 def tdRate(self):
216 """Get the touchdown rate if known, None otherwise."""
217 return self._tdRate
218
[241]219 @property
[262]220 def flightType(self):
221 """Get the type of the flight."""
222 return self._gui.flightType
223
224 @property
225 def online(self):
226 """Get whether the flight was an online flight."""
227 return self._gui.online
228
229 @property
230 def comments(self):
231 """Get the comments made by the pilot."""
232 return self._gui.comments
233
234 @property
235 def flightDefects(self):
236 """Get the flight defects reported by the pilot."""
237 return self._gui.flightDefects
238
239 @property
240 def delayCodes(self):
241 """Get the delay codes."""
242 return self._gui.delayCodes
243
244 @property
[241]245 def speedInKnots(self):
246 """Determine if the speeds for the flight are to be expressed in
247 knots."""
248 return self.aircraft.speedInKnots if self.aircraft is not None \
249 else True
250
[349]251 @property
252 def hasRTO(self):
253 """Determine if we have an RTO state."""
254 return self._rtoState is not None
255
256 @property
257 def rtoState(self):
258 """Get the RTO state."""
259 return self._rtoState
260
[89]261 def handleState(self, oldState, currentState):
262 """Handle a new state information."""
263 self._updateFlownDistance(currentState)
[348]264
[274]265 self.endFuel = currentState.totalFuel
[89]266 if self.startFuel is None:
267 self.startFuel = self.endFuel
[348]268
[170]269 self._soundScheduler.schedule(currentState,
270 self._pilotHotkeyPressed)
271 self._pilotHotkeyPressed = False
[89]272
[176]273 if self._checklistHotkeyPressed:
274 self._checklistScheduler.hotkeyPressed()
275 self._checklistHotkeyPressed = False
276
[8]277 def setStage(self, timestamp, stage):
[9]278 """Set the flight stage.
279
280 Returns if the stage has really changed."""
[8]281 if stage!=self._stage:
[365]282 self._logStageDuration(timestamp, stage)
[8]283 self._stage = stage
[84]284 self._gui.setStage(stage)
[8]285 self.logger.stage(timestamp, stage)
[89]286 if stage==const.STAGE_PUSHANDTAXI:
287 self.blockTimeStart = timestamp
288 elif stage==const.STAGE_TAKEOFF:
289 self.flightTimeStart = timestamp
[365]290 elif stage==const.STAGE_CLIMB:
291 self.climbTimeStart = timestamp
292 elif stage==const.STAGE_CRUISE:
293 self.cruiseTimeStart = timestamp
294 elif stage==const.STAGE_DESCENT:
295 self.descentTimeStart = timestamp
[89]296 elif stage==const.STAGE_TAXIAFTERLAND:
297 self.flightTimeEnd = timestamp
[359]298 # elif stage==const.STAGE_PARKING:
299 # self.blockTimeEnd = timestamp
300 elif stage==const.STAGE_END:
[89]301 self.blockTimeEnd = timestamp
[8]302 with self._endCondition:
303 self._endCondition.notify()
[9]304 return True
305 else:
306 return False
307
[346]308 def handleFault(self, faultID, timestamp, what, score,
[349]309 updatePrevious = False, updateID = None):
[30]310 """Handle the given fault.
311
312 faultID as a unique ID for the given kind of fault. If another fault of
313 this ID has been reported earlier, it will be reported again only if
314 the score is greater than last time. This ID can be, e.g. the checker
315 the report comes from."""
[349]316 id = self.logger.fault(faultID, timestamp, what, score,
317 updatePrevious = updatePrevious,
318 updateID = updateID)
[31]319 self._gui.setRating(self.logger.getRating())
[349]320 return id
[30]321
322 def handleNoGo(self, faultID, timestamp, what, shortReason):
323 """Handle a No-Go fault."""
324 self.logger.noGo(faultID, timestamp, what)
[31]325 self._gui.setNoGo(shortReason)
[30]326
[349]327 def setRTOState(self, state):
328 """Set the state that might be used as the RTO state.
329
330 If there has been no RTO state, the GUI is notified that from now on
331 the user may select to report an RTO."""
332 hadNoRTOState = self._rtoState is None
333
334 self._rtoState = state
335 self._rtoLogEntryID = \
336 SpeedChecker.logSpeedFault(self, state,
337 stage = const.STAGE_PUSHANDTAXI)
338
339 if hadNoRTOState:
340 self._gui.updateRTO()
341
342 def rtoToggled(self, indicated):
343 """Called when the user has toggled the RTO indication."""
344 if self._rtoState is not None:
345 if indicated:
346 self.logger.clearFault(self._rtoLogEntryID,
347 "RTO at %d knots" %
348 (self._rtoState.groundSpeed,))
349 self._gui.setRating(self.logger.getRating())
[354]350 if self._stage == const.STAGE_PUSHANDTAXI:
351 self.setStage(self.aircraft.state.timestamp,
352 const.STAGE_RTO)
[349]353 else:
354 SpeedChecker.logSpeedFault(self, self._rtoState,
355 stage = const.STAGE_PUSHANDTAXI,
356 updateID = self._rtoLogEntryID)
[354]357 if self._stage == const.STAGE_RTO:
358 self.setStage(self.aircraft.state.timestamp,
359 const.STAGE_PUSHANDTAXI)
[349]360
[9]361 def flareStarted(self, flareStart, flareStartFS):
362 """Called when the flare time has started."""
363 self._flareStart = flareStart
364 self._flareStartFS = flareStartFS
365
[170]366 def flareFinished(self, flareEnd, flareEndFS, tdRate):
[9]367 """Called when the flare time has ended.
[348]368
[9]369 Return a tuple of the following items:
370 - a boolean indicating if FS time is used
371 - the flare time
372 """
[170]373 self._tdRate = tdRate
[9]374 if self.flareTimeFromFS:
375 return (True, flareEndFS - self._flareStartFS)
376 else:
377 return (False, flareEnd - self._flareStart)
[8]378
379 def wait(self):
380 """Wait for the flight to end."""
381 with self._endCondition:
382 while self._stage!=const.STAGE_END:
[14]383 self._endCondition.wait(1)
[8]384
[134]385 def getFleet(self, callback, force = False):
386 """Get the fleet and call the given callback."""
387 self._gui.getFleetAsync(callback = callback, force = force)
388
[170]389 def pilotHotkeyPressed(self):
390 """Called when the pilot hotkey is pressed."""
391 self._pilotHotkeyPressed = True
392
393 def checklistHotkeyPressed(self):
394 """Called when the checklist hotkey is pressed."""
395 self._checklistHotkeyPressed = True
396
[241]397 def speedFromKnots(self, knots):
398 """Convert the given speed value expressed in knots into the flight's
399 speed unit."""
400 return knots if self.speedInKnots else knots * const.KNOTSTOKMPH
401
402 def speedToKnots(self, speed):
403 """Convert the given speed expressed in the flight's speed unit into
[348]404 knots."""
[241]405 return speed if self.speedInKnots else speed * const.KMPHTOKNOTS
406
407 def getEnglishSpeedUnit(self):
408 """Get the English name of the speed unit used by the flight."""
409 return "knots" if self.speedInKnots else "km/h"
410
411 def getI18NSpeedUnit(self):
412 """Get the speed unit suffix for i18n message identifiers."""
413 return "_knots" if self.speedInKnots else "_kmph"
414
[274]415 def logFuel(self, aircraftState):
416 """Log the amount of fuel"""
417 fuelStr = ""
418 for (tank, amount) in aircraftState.fuel:
419 if fuelStr: fuelStr += " - "
420 fuelStr += "%s=%.0f kg" % (const.fuelTank2logString(tank), amount)
[348]421
[274]422 self.logger.message(aircraftState.timestamp, "Fuel: " + fuelStr)
423 self.logger.message(aircraftState.timestamp,
[348]424 "Total fuel: %.0f kg" % (aircraftState.totalFuel,))
[274]425
[304]426 def cruiseLevelChanged(self):
427 """Called when the cruise level hass changed."""
[383]428 if self.canLogCruiseAltitude(self._stage):
[304]429 message = "Cruise altitude modified to %d feet" % \
[383]430 (self._gui.loggableCruiseAltitude,)
[304]431 self.logger.message(self.aircraft.timestamp, message)
[383]432 return True
433 else:
434 return False
[304]435
[89]436 def _updateFlownDistance(self, currentState):
437 """Update the flown distance."""
438 if not currentState.onTheGround:
439 updateData = False
440 if self._lastDistanceTime is None or \
441 self._previousLatitude is None or \
442 self._previousLongitude is None:
443 updateData = True
444 elif currentState.timestamp >= (self._lastDistanceTime + 30.0):
445 updateData = True
446 self.flownDistance += self._getDistance(currentState)
447
448 if updateData:
449 self._previousLatitude = currentState.latitude
450 self._previousLongitude = currentState.longitude
451 self._lastDistanceTime = currentState.timestamp
452 else:
453 if self._lastDistanceTime is not None and \
454 self._previousLatitude is not None and \
455 self._previousLongitude is not None:
456 self.flownDistance += self._getDistance(currentState)
[348]457
[89]458 self._lastDistanceTime = None
459
460 def _getDistance(self, currentState):
461 """Get the distance between the previous and the current state."""
462 return util.getDistCourse(self._previousLatitude, self._previousLongitude,
463 currentState.latitude, currentState.longitude)[0]
464
[365]465 def _logStageDuration(self, timestamp, stage):
466 """Log the duration of the stage preceding the given one."""
467 what = None
468 startTime = None
469
470 if stage==const.STAGE_TAKEOFF:
471 what = "Pushback and taxi"
472 startTime = self.blockTimeStart
473 elif stage==const.STAGE_CRUISE:
474 what = "Climb"
475 startTime = self.climbTimeStart
476 elif stage==const.STAGE_DESCENT:
477 what = "Cruise"
478 startTime = self.cruiseTimeStart
479 elif stage==const.STAGE_LANDING:
480 what = "Descent"
481 startTime = self.descentTimeStart
482 elif stage==const.STAGE_END:
483 what = "Taxi after landing"
484 startTime = self.flightTimeEnd
485
486 if what is not None and startTime is not None:
487 duration = timestamp - startTime
488 self.logger.message(timestamp,
489 "%s time: %s" % \
490 (what, util.getTimeIntervalString(duration)))
491
[8]492#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.