source: src/mlx/flight.py@ 391:0f2e90eae832

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

Added support for logging the state of the anti-ice system (re #159)

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