source: src/mlx/flight.py@ 386:793e45d628a7

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

Implemented support for entering derate values (#158)

File size: 15.7 KB
Line 
1
2from soundsched import SoundScheduler, ChecklistScheduler
3from checks import SpeedChecker
4
5import const
6import util
7
8import threading
9
10#---------------------------------------------------------------------------------------
11
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
21class Flight(object):
22 """The object with the global flight state.
23
24 It is also the hub for the other main objects participating in the handling of
25 the flight."""
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
33 def __init__(self, logger, gui):
34 """Construct the flight."""
35 self._stage = None
36 self.logger = logger
37 self._gui = gui
38
39 gui.resetFlightStatus()
40
41 self._pilotHotkeyPressed = False
42 self._checklistHotkeyPressed = False
43
44 self.flareTimeFromFS = False
45
46 self.aircraftType = None
47 self.aircraft = None
48 self.simulator = None
49
50 self.blockTimeStart = None
51 self.flightTimeStart = None
52 self.climbTimeStart = None
53 self.cruiseTimeStart = None
54 self.descentTimeStart = None
55 self.flightTimeEnd = None
56 self.blockTimeEnd = None
57
58 self._rtoState = None
59 self._rtoLogEntryID = None
60
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
69 self._endCondition = threading.Condition()
70
71 self._flareStart = None
72 self._flareStartFS = None
73
74 self._tdRate = None
75
76 self._soundScheduler = SoundScheduler(self)
77 self._checklistScheduler = ChecklistScheduler(self)
78
79 @property
80 def config(self):
81 """Get the configuration."""
82 return self._gui.config
83
84 @property
85 def stage(self):
86 """Get the flight stage."""
87 return self._stage
88
89 @property
90 def loggedIn(self):
91 """Indicate if the user has logged in properly."""
92 return self._gui.loggedIn
93
94 @property
95 def entranceExam(self):
96 """Get whether an entrance exam is being performed."""
97 return self._gui.entranceExam
98
99 @property
100 def bookedFlight(self):
101 """Get the booked flight."""
102 return self._gui.bookedFlight
103
104 @property
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
120 def cargoWeight(self):
121 """Get the cargo weight for the flight."""
122 return self._gui.cargoWeight
123
124 @property
125 def mailWeight(self):
126 """Get the mail weight for the flight."""
127 return self._gui.mailWeight
128
129 @property
130 def zfw(self):
131 """Get the Zero-Fuel Weight of the flight."""
132 return self._gui.zfw
133
134 @property
135 def filedCruiseAltitude(self):
136 """Get the filed cruise altitude."""
137 return self._gui.filedCruiseAltitude
138
139 @property
140 def cruiseAltitude(self):
141 """Get the cruise altitude of the flight."""
142 return self._gui.cruiseAltitude
143
144 @property
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
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
184 @property
185 def derate(self):
186 """Get the derate value of the flight."""
187 return self._gui.derate
188
189 @property
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
210 def vref(self):
211 """Get the VRef speed of the flight."""
212 return self._gui.vref
213
214 @property
215 def tdRate(self):
216 """Get the touchdown rate if known, None otherwise."""
217 return self._tdRate
218
219 @property
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
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
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
261 def handleState(self, oldState, currentState):
262 """Handle a new state information."""
263 self._updateFlownDistance(currentState)
264
265 self.endFuel = currentState.totalFuel
266 if self.startFuel is None:
267 self.startFuel = self.endFuel
268
269 self._soundScheduler.schedule(currentState,
270 self._pilotHotkeyPressed)
271 self._pilotHotkeyPressed = False
272
273 if self._checklistHotkeyPressed:
274 self._checklistScheduler.hotkeyPressed()
275 self._checklistHotkeyPressed = False
276
277 def setStage(self, timestamp, stage):
278 """Set the flight stage.
279
280 Returns if the stage has really changed."""
281 if stage!=self._stage:
282 self._logStageDuration(timestamp, stage)
283 self._stage = stage
284 self._gui.setStage(stage)
285 self.logger.stage(timestamp, stage)
286 if stage==const.STAGE_PUSHANDTAXI:
287 self.blockTimeStart = timestamp
288 elif stage==const.STAGE_TAKEOFF:
289 self.flightTimeStart = timestamp
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
296 elif stage==const.STAGE_TAXIAFTERLAND:
297 self.flightTimeEnd = timestamp
298 # elif stage==const.STAGE_PARKING:
299 # self.blockTimeEnd = timestamp
300 elif stage==const.STAGE_END:
301 self.blockTimeEnd = timestamp
302 with self._endCondition:
303 self._endCondition.notify()
304 return True
305 else:
306 return False
307
308 def handleFault(self, faultID, timestamp, what, score,
309 updatePrevious = False, updateID = None):
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."""
316 id = self.logger.fault(faultID, timestamp, what, score,
317 updatePrevious = updatePrevious,
318 updateID = updateID)
319 self._gui.setRating(self.logger.getRating())
320 return id
321
322 def handleNoGo(self, faultID, timestamp, what, shortReason):
323 """Handle a No-Go fault."""
324 self.logger.noGo(faultID, timestamp, what)
325 self._gui.setNoGo(shortReason)
326
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())
350 if self._stage == const.STAGE_PUSHANDTAXI:
351 self.setStage(self.aircraft.state.timestamp,
352 const.STAGE_RTO)
353 else:
354 SpeedChecker.logSpeedFault(self, self._rtoState,
355 stage = const.STAGE_PUSHANDTAXI,
356 updateID = self._rtoLogEntryID)
357 if self._stage == const.STAGE_RTO:
358 self.setStage(self.aircraft.state.timestamp,
359 const.STAGE_PUSHANDTAXI)
360
361 def flareStarted(self, flareStart, flareStartFS):
362 """Called when the flare time has started."""
363 self._flareStart = flareStart
364 self._flareStartFS = flareStartFS
365
366 def flareFinished(self, flareEnd, flareEndFS, tdRate):
367 """Called when the flare time has ended.
368
369 Return a tuple of the following items:
370 - a boolean indicating if FS time is used
371 - the flare time
372 """
373 self._tdRate = tdRate
374 if self.flareTimeFromFS:
375 return (True, flareEndFS - self._flareStartFS)
376 else:
377 return (False, flareEnd - self._flareStart)
378
379 def wait(self):
380 """Wait for the flight to end."""
381 with self._endCondition:
382 while self._stage!=const.STAGE_END:
383 self._endCondition.wait(1)
384
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
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
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
404 knots."""
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
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)
421
422 self.logger.message(aircraftState.timestamp, "Fuel: " + fuelStr)
423 self.logger.message(aircraftState.timestamp,
424 "Total fuel: %.0f kg" % (aircraftState.totalFuel,))
425
426 def cruiseLevelChanged(self):
427 """Called when the cruise level hass changed."""
428 if self.canLogCruiseAltitude(self._stage):
429 message = "Cruise altitude modified to %d feet" % \
430 (self._gui.loggableCruiseAltitude,)
431 self.logger.message(self.aircraft.timestamp, message)
432 return True
433 else:
434 return False
435
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)
457
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
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
492#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.