source: src/mlx/flight.py@ 564:695cde9818ea

Last change on this file since 564:695cde9818ea was 564:695cde9818ea, checked in by István Váradi <ivaradi@…>, 10 years ago

The flight type is also considered when checking the arrival and departure times (re #227)

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