source: src/mlx/flight.py@ 899:ec5238c77005

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

Cockpit and cabin crew weight is included in DOW, extra people are calulcated with specific weights, and passenger weight is set to 84 kgs (re #332)

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