from soundsched import SoundScheduler, ChecklistScheduler from checks import SpeedChecker import const import util import threading #--------------------------------------------------------------------------------------- ## @package mlx.flight # # The global flight state. # # This module defines a single class, \ref Flight, which represents the flight # in progress. #--------------------------------------------------------------------------------------- class Flight(object): """The object with the global flight state. It is also the hub for the other main objects participating in the handling of the flight.""" def __init__(self, logger, gui): """Construct the flight.""" self._stage = None self.logger = logger self._gui = gui gui.resetFlightStatus() self._pilotHotkeyPressed = False self._checklistHotkeyPressed = False self.flareTimeFromFS = False self.aircraftType = None self.aircraft = None self.simulator = None self.blockTimeStart = None self.flightTimeStart = None self.climbTimeStart = None self.cruiseTimeStart = None self.descentTimeStart = None self.flightTimeEnd = None self.blockTimeEnd = None self._rtoState = None self._rtoLogEntryID = None self._lastDistanceTime = None self._previousLatitude = None self._previousLongitude = None self.flownDistance = 0.0 self.startFuel = None self.endFuel = None self._endCondition = threading.Condition() self._flareStart = None self._flareStartFS = None self._tdRate = None self._soundScheduler = SoundScheduler(self) self._checklistScheduler = ChecklistScheduler(self) @property def config(self): """Get the configuration.""" return self._gui.config @property def stage(self): """Get the flight stage.""" return self._stage @property def loggedIn(self): """Indicate if the user has logged in properly.""" return self._gui.loggedIn @property def entranceExam(self): """Get whether an entrance exam is being performed.""" return self._gui.entranceExam @property def bookedFlight(self): """Get the booked flight.""" return self._gui.bookedFlight @property def numCrew(self): """Get the number of crew members on the flight.""" return self._gui.numCrew @property def numPassengers(self): """Get the number of passengers on the flight.""" return self._gui.numPassengers @property def bagWeight(self): """Get the baggage weight for the flight.""" return self._gui.bagWeight @property def cargoWeight(self): """Get the cargo weight for the flight.""" return self._gui.cargoWeight @property def mailWeight(self): """Get the mail weight for the flight.""" return self._gui.mailWeight @property def zfw(self): """Get the Zero-Fuel Weight of the flight.""" return self._gui.zfw @property def filedCruiseAltitude(self): """Get the filed cruise altitude.""" return self._gui.filedCruiseAltitude @property def cruiseAltitude(self): """Get the cruise altitude of the flight.""" return self._gui.cruiseAltitude @property def route(self): """Get the route of the flight.""" return self._gui.route @property def departureMETAR(self): """Get the departure METAR of the flight.""" return self._gui.departureMETAR @property def arrivalMETAR(self): """Get the arrival METAR of the flight.""" return self._gui.arrivalMETAR @property def departureRunway(self): """Get the departure runway.""" return self._gui.departureRunway @property def sid(self): """Get the SID followed.""" return self._gui.sid @property def v1(self): """Get the V1 speed of the flight.""" return self._gui.v1 @property def vr(self): """Get the Vr speed of the flight.""" return self._gui.vr @property def v2(self): """Get the V2 speed of the flight.""" return self._gui.v2 @property def star(self): """Get the STAR planned.""" return self._gui.star @property def transition(self): """Get the transition planned.""" return self._gui.transition @property def approachType(self): """Get the approach type.""" return self._gui.approachType @property def arrivalRunway(self): """Get the arrival runway.""" return self._gui.arrivalRunway @property def vref(self): """Get the VRef speed of the flight.""" return self._gui.vref @property def tdRate(self): """Get the touchdown rate if known, None otherwise.""" return self._tdRate @property def flightType(self): """Get the type of the flight.""" return self._gui.flightType @property def online(self): """Get whether the flight was an online flight.""" return self._gui.online @property def comments(self): """Get the comments made by the pilot.""" return self._gui.comments @property def flightDefects(self): """Get the flight defects reported by the pilot.""" return self._gui.flightDefects @property def delayCodes(self): """Get the delay codes.""" return self._gui.delayCodes @property def speedInKnots(self): """Determine if the speeds for the flight are to be expressed in knots.""" return self.aircraft.speedInKnots if self.aircraft is not None \ else True @property def hasRTO(self): """Determine if we have an RTO state.""" return self._rtoState is not None @property def rtoState(self): """Get the RTO state.""" return self._rtoState def handleState(self, oldState, currentState): """Handle a new state information.""" self._updateFlownDistance(currentState) self.endFuel = currentState.totalFuel if self.startFuel is None: self.startFuel = self.endFuel self._soundScheduler.schedule(currentState, self._pilotHotkeyPressed) self._pilotHotkeyPressed = False if self._checklistHotkeyPressed: self._checklistScheduler.hotkeyPressed() self._checklistHotkeyPressed = False def setStage(self, timestamp, stage): """Set the flight stage. Returns if the stage has really changed.""" if stage!=self._stage: self._logStageDuration(timestamp, stage) self._stage = stage self._gui.setStage(stage) self.logger.stage(timestamp, stage) if stage==const.STAGE_PUSHANDTAXI: self.blockTimeStart = timestamp elif stage==const.STAGE_TAKEOFF: self.flightTimeStart = timestamp elif stage==const.STAGE_CLIMB: self.climbTimeStart = timestamp elif stage==const.STAGE_CRUISE: self.cruiseTimeStart = timestamp elif stage==const.STAGE_DESCENT: self.descentTimeStart = timestamp elif stage==const.STAGE_TAXIAFTERLAND: self.flightTimeEnd = timestamp # elif stage==const.STAGE_PARKING: # self.blockTimeEnd = timestamp elif stage==const.STAGE_END: self.blockTimeEnd = timestamp with self._endCondition: self._endCondition.notify() return True else: return False def handleFault(self, faultID, timestamp, what, score, updatePrevious = False, updateID = None): """Handle the given fault. faultID as a unique ID for the given kind of fault. If another fault of this ID has been reported earlier, it will be reported again only if the score is greater than last time. This ID can be, e.g. the checker the report comes from.""" id = self.logger.fault(faultID, timestamp, what, score, updatePrevious = updatePrevious, updateID = updateID) self._gui.setRating(self.logger.getRating()) return id def handleNoGo(self, faultID, timestamp, what, shortReason): """Handle a No-Go fault.""" self.logger.noGo(faultID, timestamp, what) self._gui.setNoGo(shortReason) def setRTOState(self, state): """Set the state that might be used as the RTO state. If there has been no RTO state, the GUI is notified that from now on the user may select to report an RTO.""" hadNoRTOState = self._rtoState is None self._rtoState = state self._rtoLogEntryID = \ SpeedChecker.logSpeedFault(self, state, stage = const.STAGE_PUSHANDTAXI) if hadNoRTOState: self._gui.updateRTO() def rtoToggled(self, indicated): """Called when the user has toggled the RTO indication.""" if self._rtoState is not None: if indicated: self.logger.clearFault(self._rtoLogEntryID, "RTO at %d knots" % (self._rtoState.groundSpeed,)) self._gui.setRating(self.logger.getRating()) if self._stage == const.STAGE_PUSHANDTAXI: self.setStage(self.aircraft.state.timestamp, const.STAGE_RTO) else: SpeedChecker.logSpeedFault(self, self._rtoState, stage = const.STAGE_PUSHANDTAXI, updateID = self._rtoLogEntryID) if self._stage == const.STAGE_RTO: self.setStage(self.aircraft.state.timestamp, const.STAGE_PUSHANDTAXI) def flareStarted(self, flareStart, flareStartFS): """Called when the flare time has started.""" self._flareStart = flareStart self._flareStartFS = flareStartFS def flareFinished(self, flareEnd, flareEndFS, tdRate): """Called when the flare time has ended. Return a tuple of the following items: - a boolean indicating if FS time is used - the flare time """ self._tdRate = tdRate if self.flareTimeFromFS: return (True, flareEndFS - self._flareStartFS) else: return (False, flareEnd - self._flareStart) def wait(self): """Wait for the flight to end.""" with self._endCondition: while self._stage!=const.STAGE_END: self._endCondition.wait(1) def getFleet(self, callback, force = False): """Get the fleet and call the given callback.""" self._gui.getFleetAsync(callback = callback, force = force) def pilotHotkeyPressed(self): """Called when the pilot hotkey is pressed.""" self._pilotHotkeyPressed = True def checklistHotkeyPressed(self): """Called when the checklist hotkey is pressed.""" self._checklistHotkeyPressed = True def speedFromKnots(self, knots): """Convert the given speed value expressed in knots into the flight's speed unit.""" return knots if self.speedInKnots else knots * const.KNOTSTOKMPH def speedToKnots(self, speed): """Convert the given speed expressed in the flight's speed unit into knots.""" return speed if self.speedInKnots else speed * const.KMPHTOKNOTS def getEnglishSpeedUnit(self): """Get the English name of the speed unit used by the flight.""" return "knots" if self.speedInKnots else "km/h" def getI18NSpeedUnit(self): """Get the speed unit suffix for i18n message identifiers.""" return "_knots" if self.speedInKnots else "_kmph" def logFuel(self, aircraftState): """Log the amount of fuel""" fuelStr = "" for (tank, amount) in aircraftState.fuel: if fuelStr: fuelStr += " - " fuelStr += "%s=%.0f kg" % (const.fuelTank2logString(tank), amount) self.logger.message(aircraftState.timestamp, "Fuel: " + fuelStr) self.logger.message(aircraftState.timestamp, "Total fuel: %.0f kg" % (aircraftState.totalFuel,)) def cruiseLevelChanged(self): """Called when the cruise level hass changed.""" if self._stage in [const.STAGE_CRUISE, const.STAGE_DESCENT, const.STAGE_LANDING]: message = "Cruise altitude modified to %d feet" % \ (self.cruiseAltitude,) self.logger.message(self.aircraft.timestamp, message) def _updateFlownDistance(self, currentState): """Update the flown distance.""" if not currentState.onTheGround: updateData = False if self._lastDistanceTime is None or \ self._previousLatitude is None or \ self._previousLongitude is None: updateData = True elif currentState.timestamp >= (self._lastDistanceTime + 30.0): updateData = True self.flownDistance += self._getDistance(currentState) if updateData: self._previousLatitude = currentState.latitude self._previousLongitude = currentState.longitude self._lastDistanceTime = currentState.timestamp else: if self._lastDistanceTime is not None and \ self._previousLatitude is not None and \ self._previousLongitude is not None: self.flownDistance += self._getDistance(currentState) self._lastDistanceTime = None def _getDistance(self, currentState): """Get the distance between the previous and the current state.""" return util.getDistCourse(self._previousLatitude, self._previousLongitude, currentState.latitude, currentState.longitude)[0] def _logStageDuration(self, timestamp, stage): """Log the duration of the stage preceding the given one.""" what = None startTime = None if stage==const.STAGE_TAKEOFF: what = "Pushback and taxi" startTime = self.blockTimeStart elif stage==const.STAGE_CRUISE: what = "Climb" startTime = self.climbTimeStart elif stage==const.STAGE_DESCENT: what = "Cruise" startTime = self.cruiseTimeStart elif stage==const.STAGE_LANDING: what = "Descent" startTime = self.descentTimeStart elif stage==const.STAGE_END: what = "Taxi after landing" startTime = self.flightTimeEnd if what is not None and startTime is not None: duration = timestamp - startTime self.logger.message(timestamp, "%s time: %s" % \ (what, util.getTimeIntervalString(duration))) #---------------------------------------------------------------------------------------