source: src/mlx/flight.py@ 365:db5767329602

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

The durations of certain stages are logged (#146)

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