source: src/mlx/flight.py@ 549:444ce1726a44

xplane
Last change on this file since 549:444ce1726a44 was 430:8a12f6c1066b, checked in by István Váradi <ivaradi@…>, 12 years ago

The flight simulator type is available to the checkers and the bank checking now takes it into account for DH8D to avoid trouble due to the overactive autopilot

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