source: src/mlx/checks.py@ 548:bcb90fca2956

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

Removed the logging in the Tu-134 anti-collision lights checker and changed the conditions to match procedures. The logged text is also changed to reflect reality (re #220)

File size: 64.7 KB
RevLine 
[298]1
2import fs
3import const
4import util
5from acars import ACARS
6from sound import startSound
7
8import time
9
10#---------------------------------------------------------------------------------------
11
[297]12## @package mlx.checks
13#
14# The classes that check the state of the aircraft.
15#
16# During the flight the program periodically queries various data from the
17# simulator. This data is returned in instances of the \ref
18# mlx.fs.AircraftState class and passed to the various "checkers",
19# i.e. instances of subclasses of the \ref StateChecker class. These checkers
20# perform various checks to see if the aircraft's parameters are within the
21# expected limits, or some of them just logs something.
22#
23# There are a few special ones, such as \ref StageChecker which computes the
24# transitions from one stage of the flight to the next one. Or \ref ACARSSender
25# which sends the ACARS periodically
[8]26
27#---------------------------------------------------------------------------------------
28
29class StateChecker(object):
30 """Base class for classes the instances of which check the aircraft's state
31 from some aspect.
32
33 As a result of the check they may log something, or notify of some fault, etc."""
34 def check(self, flight, aircraft, logger, oldState, state):
35 """Perform the check and do whatever is needed.
36
37 This default implementation raises a NotImplementedError."""
38 raise NotImplementedError()
[332]39
[8]40#---------------------------------------------------------------------------------------
41
42class StageChecker(StateChecker):
43 """Check the flight stage transitions."""
[25]44 def __init__(self):
45 """Construct the stage checker."""
46 self._flareStarted = False
47
[8]48 def check(self, flight, aircraft, logger, oldState, state):
49 """Check the stage of the aircraft."""
50 stage = flight.stage
51 if stage==None:
52 aircraft.setStage(state, const.STAGE_BOARDING)
53 elif stage==const.STAGE_BOARDING:
54 if not state.parking or \
55 (not state.trickMode and state.groundSpeed>5.0):
56 aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
57 elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
[477]58 if state.strobeLightsOn or \
59 (state.strobeLightsOn is None and state.xpdrC):
[8]60 aircraft.setStage(state, const.STAGE_TAKEOFF)
61 elif stage==const.STAGE_TAKEOFF:
62 if not state.gearsDown or \
63 (state.radioAltitude>3000.0 and state.vs>0):
64 aircraft.setStage(state, const.STAGE_CLIMB)
65 elif not state.landingLightsOn and \
[477]66 (state.strobeLightsOn is False or
[529]67 (state.strobeLightsOn is None and state.xpdrC is False)) and \
[8]68 state.onTheGround and \
69 state.groundSpeed<50.0:
70 aircraft.setStage(state, const.STAGE_RTO)
71 elif stage==const.STAGE_CLIMB:
72 if (state.altitude+2000) > flight.cruiseAltitude:
73 aircraft.setStage(state, const.STAGE_CRUISE)
74 elif state.radioAltitude<2000.0 and \
75 state.vs < 0.0 and state.gearsDown:
76 aircraft.setStage(state, const.STAGE_LANDING)
77 elif stage==const.STAGE_CRUISE:
78 if (state.altitude+2000) < flight.cruiseAltitude:
79 aircraft.setStage(state, const.STAGE_DESCENT)
80 elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
81 if state.gearsDown and state.radioAltitude<2000.0:
82 aircraft.setStage(state, const.STAGE_LANDING)
83 elif (state.altitude+2000) > flight.cruiseAltitude:
84 aircraft.setStage(state, const.STAGE_CRUISE)
85 elif stage==const.STAGE_LANDING:
[332]86 if state.onTheGround and state.groundSpeed<25.0:
[8]87 aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
88 elif not state.gearsDown:
89 aircraft.setStage(state, const.STAGE_GOAROUND)
[25]90 elif state.radioAltitude>200 and self._flareStarted:
[8]91 aircraft.cancelFlare()
[25]92 self._flareStarted = False
93 elif state.radioAltitude<150 and not state.onTheGround and \
94 not self._flareStarted:
95 self._flareStarted = True
[9]96 aircraft.prepareFlare()
[8]97 elif stage==const.STAGE_TAXIAFTERLAND:
[359]98 if state.parking and aircraft.checkFlightEnd(state):
[8]99 aircraft.setStage(state, const.STAGE_END)
100
101#---------------------------------------------------------------------------------------
102
[139]103class ACARSSender(StateChecker):
104 """Sender of online ACARS.
105
106 It sends the ACARS every 3 minutes to the MAVA website."""
107
[297]108 ## The interval at which the ACARS are sent
[139]109 INTERVAL = 3*60.0
[332]110
[139]111 def __init__(self, gui):
112 """Construct the ACARS sender."""
113 self._gui = gui
114 self._lastSent = None
[374]115 self._sending = False
[139]116
117 def check(self, flight, aircraft, logger, oldState, state):
118 """If the time has come to send the ACARS, send it."""
[374]119 if self._sending:
120 return
121
[139]122 now = time.time()
[332]123
[139]124 if self._lastSent is not None and \
125 (self._lastSent + ACARSSender.INTERVAL)>now:
[332]126 return
[139]127
[380]128 self._sending = True
[139]129 acars = ACARS(self._gui, state)
130 self._gui.webHandler.sendACARS(self._acarsCallback, acars)
131
132 def _acarsCallback(self, returned, result):
133 """Callback for ACARS sending."""
134 if returned:
135 print "Sent online ACARS"
136 self._lastSent = time.time() if self._lastSent is None \
137 else self._lastSent + ACARSSender.INTERVAL
138 else:
139 print "Failed to send the ACARS"
[374]140 self._sending = False
[332]141
[139]142#---------------------------------------------------------------------------------------
143
[9]144class TakeOffLogger(StateChecker):
145 """Logger for the cruise speed."""
146 def __init__(self):
147 """Construct the logger."""
148 self._onTheGround = True
[332]149
[9]150 def check(self, flight, aircraft, logger, oldState, state):
151 """Log the cruise speed if necessary."""
152 if flight.stage==const.STAGE_TAKEOFF and \
153 self._onTheGround and not state.onTheGround:
154 logger.message(state.timestamp,
[241]155 "Takeoff speed: %.0f %s" % \
156 (flight.speedFromKnots(state.ias),
157 flight.getEnglishSpeedUnit()))
[9]158 logger.message(state.timestamp,
159 "Takeoff heading: %03.0f degrees" % (state.heading,))
160 logger.message(state.timestamp,
161 "Takeoff pitch: %.1f degrees" % (state.pitch,))
[332]162 logger.message(state.timestamp,
[364]163 "Takeoff flaps: %.0f" % (state.flapsSet))
164 logger.message(state.timestamp,
[340]165 "CG/Trim: %.1f%%/%.2f" % \
166 (state.cog*100.0, state.elevatorTrim))
[9]167 self._onTheGround = False
168
169#---------------------------------------------------------------------------------------
170
171class CruiseSpeedLogger(StateChecker):
172 """Logger for the cruise speed."""
173 def __init__(self):
174 """Construct the logger."""
175 self._lastTime = None
[332]176
[9]177 def check(self, flight, aircraft, logger, oldState, state):
178 """Log the cruise speed if necessary."""
179 if flight.stage==const.STAGE_CRUISE and \
180 (self._lastTime is None or \
181 (self._lastTime+800)<=state.timestamp):
182 if state.altitude>24500.0:
183 logger.message(state.timestamp,
[10]184 "Cruise speed: %.3f mach" % (state.mach,))
[9]185 else:
186 logger.message(state.timestamp,
[241]187 "Cruise speed: %.0f %s" %
188 (flight.speedFromKnots(state.ias),
189 flight.getEnglishSpeedUnit()))
[9]190 self._lastTime = state.timestamp
191
192#---------------------------------------------------------------------------------------
193
[10]194class SpoilerLogger(StateChecker):
195 """Logger for the cruise speed."""
196 def __init__(self):
197 """Construct the logger."""
198 self._logged = False
[13]199 self._spoilersExtension = None
[332]200
[10]201 def check(self, flight, aircraft, logger, oldState, state):
202 """Log the cruise speed if necessary."""
203 if flight.stage==const.STAGE_LANDING and not self._logged:
204 if state.onTheGround:
[13]205 if state.spoilersExtension!=self._spoilersExtension:
[10]206 logger.message(state.timestamp, "Spoilers deployed")
207 self._logged = True
[170]208 config = flight.config
209 if config.enableSounds and config.speedbrakeAtTD:
210 startSound(const.SOUND_SPEEDBRAKE)
[10]211 else:
[13]212 self._spoilersExtension = state.spoilersExtension
[10]213
214#---------------------------------------------------------------------------------------
215
[134]216class VisibilityChecker(StateChecker):
217 """Inform the pilot of the visibility once when descending below 2000 ft,
218 then when descending below 1000 ft."""
219 def __init__(self):
220 """Construct the visibility checker."""
221 self._informedBelow2000 = False
222 self._informedBelow1000 = False
223
224 def check(self, flight, aircraft, logger, oldState, state):
225 """Check if we need to inform the pilot of the visibility."""
226 if flight.stage==const.STAGE_DESCENT or \
227 flight.stage==const.STAGE_LANDING:
228 if (state.radioAltitude<2000 and not self._informedBelow2000) or \
229 (state.radioAltitude<1000 and not self._informedBelow1000):
230 visibilityString = util.visibility2String(state.visibility)
231 fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
232 "Current visibility: " + visibilityString,
233 5)
234 logger.message(state.timestamp,
235 "Pilot was informed about the visibility: " +
236 visibilityString)
237 self._informedBelow2000 = True
238 self._informedBelow1000 = state.radioAltitude<1000
239
240#---------------------------------------------------------------------------------------
241
[273]242class ApproachCalloutsPlayer(StateChecker):
243 """A state checker that plays a sequence of approach callouts.
244
245 It tracks the altitude during the descent and landing phases and
246 if the altitude crosses one that has a callout associated with and
[332]247 the vertical speed is negative, that callout will be played."""
[273]248 def __init__(self, approachCallouts):
249 """Construct the approach callouts player."""
250 self._approachCallouts = approachCallouts
251 self._altitudes = approachCallouts.getAltitudes(descending = False)
252
253 def check(self, flight, aircraft, logger, oldState, state):
254 """Check if we need to play a callout."""
255 if (flight.stage==const.STAGE_DESCENT or \
256 flight.stage==const.STAGE_LANDING) and state.vs<0:
257 oldRadioAltitude = oldState.radioAltitude
258 radioAltitude = state.radioAltitude
259 for altitude in self._altitudes:
260 if radioAltitude<=altitude and \
261 oldRadioAltitude>altitude:
262 startSound(self._approachCallouts[altitude])
263 break
264
265#---------------------------------------------------------------------------------------
266
[8]267class StateChangeLogger(StateChecker):
268 """Base class for classes the instances of which check if a specific change has
269 occured in the aircraft's state, and log such change."""
[356]270 def __init__(self, logInitial = True, excludedStages = None):
[8]271 """Construct the logger.
272
273 If logInitial is True, the initial value will be logged, not just the
274 changes later.
275
[356]276 If excludedStages is given, it should be a list containing those stages
277 during which the changes should not be logged.
278
[8]279 Child classes should define the following functions:
280 - _changed(self, oldState, state): returns a boolean indicating if the
281 value has changed or not
[321]282 - _getMessage(self, flight, state, forced): return a strings containing
283 the message to log with the new value
[8]284 """
[332]285 self._logInitial = logInitial
[356]286 self._excludedStages = [] if excludedStages is None else excludedStages
[8]287
[324]288 def _getLogTimestamp(self, state, forced):
[22]289 """Get the log timestamp."""
290 return state.timestamp
291
[8]292 def check(self, flight, aircraft, logger, oldState, state):
293 """Check if the state has changed, and if so, log the new state."""
[356]294 if flight.stage in self._excludedStages:
295 return
296
[8]297 shouldLog = False
298 if oldState is None:
299 shouldLog = self._logInitial
300 else:
301 shouldLog = self._changed(oldState, state)
[332]302
[8]303 if shouldLog:
[321]304 self.logState(flight, logger, state)
305
306 def logState(self, flight, logger, state, forced = False):
307 """Log the state."""
308 message = self._getMessage(flight, state, forced)
309 if message is not None:
[324]310 logger.message(self._getLogTimestamp(state, forced), message)
[332]311
[241]312#-------------------------------------------------------------------------------
[8]313
314class SimpleChangeMixin(object):
315 """A mixin that defines a _changed() function which simply calls a function
316 to retrieve the value two monitor for both states and compares them.
317
318 Child classes should define the following function:
319 - _getValue(state): get the value we are interested in."""
320 def _changed(self, oldState, state):
321 """Determine if the value has changed."""
[314]322 currentValue = self._getValue(state)
323 return currentValue is not None and self._getValue(oldState)!=currentValue
[8]324
325#---------------------------------------------------------------------------------------
326
327class SingleValueMixin(object):
328 """A mixin that provides a _getValue() function to query a value from the
329 state using the name of the attribute."""
330 def __init__(self, attrName):
331 """Construct the mixin with the given attribute name."""
332 self._attrName = attrName
333
334 def _getValue(self, state):
335 """Get the value of the attribute from the state."""
336 return getattr(state, self._attrName)
[332]337
[8]338#---------------------------------------------------------------------------------------
339
340class DelayedChangeMixin(object):
341 """A mixin to a StateChangeLogger that stores the old value and reports a
342 change only if the change has been there for a certain amount of time.
343
344 Child classes should define the following function:
345 - _getValue(state): get the value we are interested in."""
[22]346 def __init__(self, minDelay = 3.0, maxDelay = 10.0):
[8]347 """Construct the mixin with the given delay in seconds."""
[22]348 self._minDelay = minDelay
349 self._maxDelay = maxDelay
[8]350 self._oldValue = None
351 self._firstChange = None
[22]352 self._lastChangeState = None
[355]353 self._lastLoggedValue = None
354
355 self._logState = lambda flight, logger, state, forced = False: \
356 StateChangeLogger.logState(self, flight, logger, state,
357 forced = forced)
358 self.logState = lambda flight, logger, state, forced = False: \
359 DelayedChangeMixin.logState(self, flight, logger, state,
360 forced = forced)
[332]361
[8]362 def _changed(self, oldState, state):
363 """Determine if the value has changed."""
364 if self._oldValue is None:
365 self._oldValue = self._getValue(oldState)
[332]366
[8]367 newValue = self._getValue(state)
[321]368 if self._isDifferent(self._oldValue, newValue):
[355]369 if self._lastLoggedValue is not None and \
370 not self._isDifferent(self._lastLoggedValue, newValue):
371 self._firstChange = None
372 else:
373 if self._firstChange is None:
374 self._firstChange = state.timestamp
375 self._lastChangeState = state
[22]376 self._oldValue = newValue
377
378 if self._firstChange is not None:
[324]379 if state.timestamp >= min(self._lastChangeState.timestamp +
380 self._minDelay,
[22]381 self._firstChange + self._maxDelay):
[8]382 self._firstChange = None
383 return True
[332]384
[22]385 return False
[8]386
[324]387 def _getLogTimestamp(self, state, forced):
[22]388 """Get the log timestamp."""
[324]389 return self._lastChangeState.timestamp \
390 if not forced and self._lastChangeState is not None \
391 else state.timestamp
[8]392
[321]393 def _isDifferent(self, oldValue, newValue):
394 """Determine if the given values are different.
395
396 This default implementation checks for simple equality."""
397 return oldValue!=newValue
398
[355]399 def logState(self, flight, logger, state, forced):
400 """Log the given state.
401
402 The value belonging to that state is recorded in _lastLoggedValue.
403
404 It calls _logState to perform the real logging."""
405 self._lastLoggedValue = self._getValue(state)
406 self._logState(flight, logger, state, forced = forced)
407
[8]408#---------------------------------------------------------------------------------------
409
410class TemplateMessageMixin(object):
411 """Mixin to generate a message based on a template.
412
413 Child classes should define the following function:
414 - _getValue(state): get the value we are interested in."""
415 def __init__(self, template):
416 """Construct the mixin."""
417 self._template = template
418
[321]419 def _getMessage(self, flight, state, forced):
[8]420 """Get the message."""
[332]421 value = self._getValue(state)
[320]422 return None if value is None else self._template % (value,)
[8]423
424#---------------------------------------------------------------------------------------
425
426class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
427 DelayedChangeMixin, TemplateMessageMixin):
428 """Base for generic state change loggers that monitor a single value in the
429 state possibly with a delay and the logged message comes from a template"""
[22]430 def __init__(self, attrName, template, logInitial = True,
[356]431 excludedStages = None, minDelay = 0.0, maxDelay = 0.0):
[8]432 """Construct the object."""
[358]433 StateChangeLogger.__init__(self, logInitial = logInitial,
434 excludedStages = excludedStages)
[8]435 SingleValueMixin.__init__(self, attrName)
[329]436 DelayedChangeMixin.__init__(self, minDelay = minDelay,
437 maxDelay = maxDelay)
[8]438 TemplateMessageMixin.__init__(self, template)
[324]439 self._getLogTimestamp = \
440 lambda state, forced: \
441 DelayedChangeMixin._getLogTimestamp(self, state, forced)
[8]442
443#---------------------------------------------------------------------------------------
444
[321]445class ForceableLoggerMixin(object):
[355]446 """A mixin for loggers that can be forced to log a certain state."""
[321]447 def forceLog(self, flight, logger, state):
448 """Force logging the given state."""
449 self.logState(flight, logger, state, forced = True)
450
451#---------------------------------------------------------------------------------------
452
[8]453class AltimeterLogger(StateChangeLogger, SingleValueMixin,
454 DelayedChangeMixin):
455 """Logger for the altimeter setting."""
456 def __init__(self):
457 """Construct the logger."""
458 StateChangeLogger.__init__(self, logInitial = True)
459 SingleValueMixin.__init__(self, "altimeter")
460 DelayedChangeMixin.__init__(self)
[324]461 self._getLogTimestamp = \
462 lambda state, forced: \
463 DelayedChangeMixin._getLogTimestamp(self, state, forced)
[8]464
[321]465 def _getMessage(self, flight, state, forced):
[8]466 """Get the message to log on a change."""
[22]467 logState = self._lastChangeState if \
468 self._lastChangeState is not None else state
[409]469 message = "Altimeter: %.2f hPa at %.0f feet" % \
[394]470 (logState.altimeter, logState.altitude)
471 if not logState.altimeterReliable:
472 message += " (u/r)"
473 return message
[8]474
475#---------------------------------------------------------------------------------------
476
[321]477class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
[320]478 """Logger for NAV radios.
479
[355]480 It also logs the OBS radial set."""
[356]481 excludedStages = [const.STAGE_BOARDING, const.STAGE_PUSHANDTAXI,
482 const.STAGE_RTO, const.STAGE_TAXIAFTERLAND,
483 const.STAGE_PARKING]
484
[321]485 @staticmethod
486 def getMessage(logName, frequency, obs):
487 """Get the message for the given NAV radio setting."""
[389]488 message = u"%-5s %s" % (logName + ":", frequency)
[363]489 if obs is not None: message += u" (%03d\u00b0)" % (obs,)
[321]490 return message
[332]491
[320]492 def __init__(self, attrName, logName):
493 """Construct the NAV logger."""
[356]494 StateChangeLogger.__init__(self, logInitial = False,
495 excludedStages = self.excludedStages)
[320]496 DelayedChangeMixin.__init__(self)
[321]497
498 self._getLogTimestamp = \
[324]499 lambda state, forced: \
500 DelayedChangeMixin._getLogTimestamp(self, state, forced)
[320]501
502 self._attrName = attrName
503 self._logName = logName
[321]504
[320]505 def _getValue(self, state):
506 """Get the value.
507
508 If both the frequency and the obs settings are available, a tuple
509 containing them is returned, otherwise None."""
510 frequency = getattr(state, self._attrName)
511 obs = getattr(state, self._attrName + "_obs")
[321]512 manual = getattr(state, self._attrName + "_manual")
513 return (frequency, obs, manual)
[320]514
[321]515 def _getMessage(self, flight, state, forced):
[320]516 """Get the message."""
[321]517 (frequency, obs, manual) = self._getValue(state)
518 return None if frequency is None or obs is None or \
519 (not manual and not forced) else \
520 self.getMessage(self._logName, frequency, obs)
521
522 def _isDifferent(self, oldValue, newValue):
523 """Determine if the valie has changed between the given states."""
524 (oldFrequency, oldOBS, _oldManual) = oldValue
525 (newFrequency, newOBS, _newManual) = newValue
526 return oldFrequency!=newFrequency or oldOBS!=newOBS
[320]527
528#---------------------------------------------------------------------------------------
529
[366]530class ILSLogger(NAVLogger):
531 """Logger for the ILS radio setting."""
532 def __init__(self):
533 """Construct the logger."""
534 super(ILSLogger, self).__init__("ils", "ILS")
535
536#---------------------------------------------------------------------------------------
537
[320]538class NAV1Logger(NAVLogger):
[8]539 """Logger for the NAV1 radio setting."""
540 def __init__(self):
541 """Construct the logger."""
[320]542 super(NAV1Logger, self).__init__("nav1", "NAV1")
[8]543
544#---------------------------------------------------------------------------------------
545
[320]546class NAV2Logger(NAVLogger):
[8]547 """Logger for the NAV2 radio setting."""
548 def __init__(self):
549 """Construct the logger."""
[320]550 super(NAV2Logger, self).__init__("nav2", "NAV2")
[8]551
552#---------------------------------------------------------------------------------------
553
[321]554class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
555 """Base class for the ADF loggers."""
556 def __init__(self, attr, logName):
557 """Construct the ADF logger."""
558 GenericStateChangeLogger.__init__(self, attr,
[333]559 "%s: %%s" % (logName,),
[356]560 logInitial = False,
561 excludedStages =
562 NAVLogger.excludedStages,
[321]563 minDelay = 3.0, maxDelay = 10.0)
564
565#---------------------------------------------------------------------------------------
566
567class ADF1Logger(ADFLogger):
[317]568 """Logger for the ADF1 radio setting."""
569 def __init__(self):
570 """Construct the logger."""
[321]571 super(ADF1Logger, self).__init__("adf1", "ADF1")
[317]572
573#---------------------------------------------------------------------------------------
574
[321]575class ADF2Logger(ADFLogger):
[317]576 """Logger for the ADF2 radio setting."""
577 def __init__(self):
578 """Construct the logger."""
[321]579 super(ADF2Logger, self).__init__("adf2", "ADF2")
[317]580
581#---------------------------------------------------------------------------------------
582
[8]583class SquawkLogger(GenericStateChangeLogger):
584 """Logger for the squawk setting."""
585 def __init__(self):
586 """Construct the logger."""
587 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
[22]588 minDelay = 3.0, maxDelay = 10.0)
[8]589
590#---------------------------------------------------------------------------------------
591
592class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
593 """Base class for the loggers of the various lights."""
594 def __init__(self, attrName, template):
595 """Construct the logger."""
596 StateChangeLogger.__init__(self)
597 SingleValueMixin.__init__(self, attrName)
[332]598
[8]599 self._template = template
600
[321]601 def _getMessage(self, flight, state, forced):
[8]602 """Get the message from the given state."""
[416]603 value = self._getValue(state)
604 if value is None:
605 return None
606 else:
607 return self._template % ("ON" if value else "OFF")
[332]608
[8]609#---------------------------------------------------------------------------------------
610
611class AnticollisionLightsLogger(LightsLogger):
612 """Logger for the anti-collision lights."""
613 def __init__(self):
614 LightsLogger.__init__(self, "antiCollisionLightsOn",
615 "Anti-collision lights: %s")
616
617#---------------------------------------------------------------------------------------
618
619class LandingLightsLogger(LightsLogger):
620 """Logger for the landing lights."""
621 def __init__(self):
622 LightsLogger.__init__(self, "landingLightsOn",
623 "Landing lights: %s")
624
625#---------------------------------------------------------------------------------------
626
627class StrobeLightsLogger(LightsLogger):
628 """Logger for the strobe lights."""
629 def __init__(self):
630 LightsLogger.__init__(self, "strobeLightsOn",
631 "Strobe lights: %s")
632
633#---------------------------------------------------------------------------------------
634
635class NavLightsLogger(LightsLogger):
636 """Logger for the navigational lights."""
637 def __init__(self):
638 LightsLogger.__init__(self, "navLightsOn",
639 "Navigational lights: %s")
640
641#---------------------------------------------------------------------------------------
642
[520]643class FlapsLogger(StateChangeLogger, SingleValueMixin, DelayedChangeMixin):
[8]644 """Logger for the flaps setting."""
645 def __init__(self):
646 """Construct the logger."""
[364]647 StateChangeLogger.__init__(self, logInitial = True,
648 excludedStages =
649 [const.STAGE_BOARDING,
650 const.STAGE_PUSHANDTAXI,
651 const.STAGE_RTO,
652 const.STAGE_TAKEOFF])
[8]653 SingleValueMixin.__init__(self, "flapsSet")
[520]654 DelayedChangeMixin.__init__(self)
655 self._getLogTimestamp = \
656 lambda state, forced: \
657 DelayedChangeMixin._getLogTimestamp(self, state, forced)
[8]658
[321]659 def _getMessage(self, flight, state, forced):
[8]660 """Get the message to log on a change."""
[520]661 logState = self._lastChangeState if \
662 self._lastChangeState is not None else state
663 speed = logState.groundSpeed if logState.groundSpeed<80.0 \
664 else logState.ias
[525]665 message = "Flaps %.0f - %.0f %s" % \
[520]666 (logState.flapsSet, flight.speedFromKnots(speed),
[241]667 flight.getEnglishSpeedUnit())
[8]668
[525]669 if flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING]:
670 message += ", %.0f ft AGL" % (logState.radioAltitude,)
671
672 return message
673
[8]674#---------------------------------------------------------------------------------------
675
676class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
677 """Logger for the gears state."""
678 def __init__(self):
679 """Construct the logger."""
680 StateChangeLogger.__init__(self, logInitial = True)
[209]681 SingleValueMixin.__init__(self, "gearControlDown")
[8]682
[321]683 def _getMessage(self, flight, state, forced):
[8]684 """Get the message to log on a change."""
[525]685 return "Gears SET to %s at %.0f %s, %.0f ft, %.0f ft AGL" % \
[209]686 ("DOWN" if state.gearControlDown else "UP",
[241]687 flight.speedFromKnots(state.ias),
[525]688 flight.getEnglishSpeedUnit(),
689 state.altitude, state.radioAltitude)
[8]690
691#---------------------------------------------------------------------------------------
[11]692
[338]693class APLogger(StateChangeLogger, DelayedChangeMixin):
694 """Log the state of the autopilot."""
695 @staticmethod
696 def _logBoolean(logger, timestamp, what, value):
697 """Log a boolean value.
698
699 what is the name of the value that is being logged."""
700 message = what + " "
701 if value is None:
702 message += "cannot be detected, will not log"
703 else:
704 message += "is " + ("ON" if value else "OFF")
705 logger.message(timestamp, message)
706
707 @staticmethod
708 def _logNumeric(logger, timestamp, what, format, value):
709 """Log a numerical value."""
710 message = what
711 if value is None:
712 message += u" cannot be detected, will not log"
713 else:
714 message += u": " + format % (value,)
715 logger.message(timestamp, message)
716
717 @staticmethod
718 def _logAPMaster(logger, timestamp, state):
719 """Log the AP master state."""
720 APLogger._logBoolean(logger, timestamp, "AP master",
721 state.apMaster)
722
723 @staticmethod
724 def _logAPHeadingHold(logger, timestamp, state):
725 """Log the AP heading hold state."""
726 APLogger._logBoolean(logger, timestamp, "AP heading hold",
727 state.apHeadingHold)
728
729 @staticmethod
730 def _logAPHeading(logger, timestamp, state):
731 """Log the AP heading."""
732 APLogger._logNumeric(logger, timestamp, u"AP heading",
733 u"%03.0f\u00b0", state.apHeading)
734
735 @staticmethod
736 def _logAPAltitudeHold(logger, timestamp, state):
737 """Log the AP altitude hold state."""
738 APLogger._logBoolean(logger, timestamp, "AP altitude hold",
739 state.apAltitudeHold)
740
741 @staticmethod
742 def _logAPAltitude(logger, timestamp, state):
743 """Log the AP heading."""
744 APLogger._logNumeric(logger, timestamp, u"AP altitude",
745 u"%.0f ft", state.apAltitude)
746
747 def __init__(self):
748 """Construct the state logger."""
749 StateChangeLogger.__init__(self)
750 DelayedChangeMixin.__init__(self)
751 self._lastLoggedState = None
[355]752 self.logState = lambda flight, logger, state:\
753 APLogger.logState(self, flight, logger, state)
[338]754
755 def _getValue(self, state):
756 """Convert the relevant values from the given state into a tuple."""
757 return (state.apMaster,
758 state.apHeadingHold, state.apHeading,
759 state.apAltitudeHold, state.apAltitude)
760
761 def _isDifferent(self, oldValue, newValue):
762 """Determine if the given old and new values are different (enough) to
763 be logged."""
764 (oldAPMaster, oldAPHeadingHold, oldAPHeading,
765 oldAPAltitudeHold, oldAPAltitude) = oldValue
766 (apMaster, apHeadingHold, apHeading,
767 apAltitudeHold, apAltitude) = newValue
768
769 if apMaster is not None and apMaster!=oldAPMaster:
770 return True
771 if apMaster is False:
772 return False
773
774 if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
775 return True
776 if apHeadingHold is not False and apHeading is not None and \
777 apHeading!=oldAPHeading:
778 return True
779
780 if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
781 return True
782 if apAltitudeHold is not False and apAltitude is not None and \
783 apAltitude!=oldAPAltitude:
784 return True
785
786 return False
787
788 def logState(self, flight, logger, state):
789 """Log the autopilot state."""
790 timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
791 if self._lastLoggedState is None:
792 self._logAPMaster(logger, timestamp, state)
793 if state.apMaster is not False or state.apHeadingHold is None:
794 self._logAPHeadingHold(logger, timestamp, state)
795 if state.apMaster is not False and \
796 (state.apHeadingHold is not False or state.apHeading is None):
797 self._logAPHeading(logger, timestamp, state)
798 if state.apMaster is not False or state.apAltitudeHold is None:
799 self._logAPAltitudeHold(logger, timestamp, state)
800 if state.apMaster is not False and \
801 (state.apAltitudeHold is not False or state.apAltitude is None):
802 self._logAPAltitude(logger, timestamp, state)
803 self._firstCall = False
804 else:
805 oldState = self._lastLoggedState
806
807 apMasterTurnedOn = False
808 if state.apMaster is not None and state.apMaster!=oldState.apMaster:
809 apMasterTurnedOn = state.apMaster
810 self._logAPMaster(logger, timestamp, state)
811
812 if state.apMaster is not False:
813 apHeadingHoldTurnedOn = False
814 if state.apHeadingHold is not None and \
815 (state.apHeadingHold!=oldState.apHeadingHold or
816 apMasterTurnedOn):
817 apHeadingHoldTurnedOn = state.apHeadingHold
818 self._logAPHeadingHold(logger, timestamp, state)
819
820 if state.apHeadingHold is not False and \
821 state.apHeading is not None and \
822 (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
823 apHeadingHoldTurnedOn):
824 self._logAPHeading(logger, timestamp, state)
825
826 apAltitudeHoldTurnedOn = False
827 if state.apAltitudeHold is not None and \
828 (state.apAltitudeHold!=oldState.apAltitudeHold or
829 apMasterTurnedOn):
830 apAltitudeHoldTurnedOn = state.apAltitudeHold
831 self._logAPAltitudeHold(logger, timestamp, state)
832
833 if state.apAltitudeHold is not False and \
834 state.apAltitude is not None and \
835 (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
836 apAltitudeHoldTurnedOn):
837 self._logAPAltitude(logger, timestamp, state)
838
839 self._lastLoggedState = state
840
841#---------------------------------------------------------------------------------------
842
[11]843class FaultChecker(StateChecker):
844 """Base class for checkers that look for faults."""
845 @staticmethod
846 def _appendDuring(flight, message):
847 """Append a 'during XXX' test to the given message, depending on the
848 flight stage."""
849 stageStr = const.stage2string(flight.stage)
850 return message if stageStr is None \
851 else (message + " during " + stageStr.upper())
852
853 @staticmethod
854 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
855 value):
856 """Get the score for a faulty value where the score is calculated
857 linearly within a certain range."""
858 if value<minFaultValue:
859 return 0
860 elif value>maxFaultValue:
861 return maxScore
862 else:
863 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
864 (maxFaultValue - minFaultValue)
865
866#---------------------------------------------------------------------------------------
867
868class SimpleFaultChecker(FaultChecker):
869 """Base class for fault checkers that check for a single occurence of a
870 faulty condition.
871
872 Child classes should implement the following functions:
873 - isCondition(self, flight, aircraft, oldState, state): should return whether the
874 condition holds
875 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
876 via the logger."""
877 def check(self, flight, aircraft, logger, oldState, state):
878 """Perform the check."""
879 if self.isCondition(flight, aircraft, oldState, state):
880 self.logFault(flight, aircraft, logger, oldState, state)
881
882#---------------------------------------------------------------------------------------
883
884class PatientFaultChecker(FaultChecker):
885 """A fault checker that does not decides on a fault when the condition
886 arises immediately, but can wait some time.
887
888 Child classes should implement the following functions:
889 - isCondition(self, flight, aircraft, oldState, state): should return whether the
890 condition holds
891 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
892 via the logger
893 """
894 def __init__(self, timeout = 2.0):
895 """Construct the fault checker with the given timeout."""
896 self._timeout = timeout
897 self._faultStarted = None
898
899 def getTimeout(self, flight, aircraft, oldState, state):
900 """Get the timeout.
901
902 This default implementation returns the timeout given in the
903 constructor, but child classes might want to enforce a different
904 policy."""
905 return self._timeout
906
907 def check(self, flight, aircraft, logger, oldState, state):
908 """Perform the check."""
909 if self.isCondition(flight, aircraft, oldState, state):
910 if self._faultStarted is None:
911 self._faultStarted = state.timestamp
912 timeout = self.getTimeout(flight, aircraft, oldState, state)
913 if state.timestamp>=(self._faultStarted + timeout):
914 self.logFault(flight, aircraft, logger, oldState, state)
915 self._faultStarted = state.timestamp
916 else:
917 self._faultStarted = None
[332]918
[11]919#---------------------------------------------------------------------------------------
920
921class AntiCollisionLightsChecker(PatientFaultChecker):
922 """Check for the anti-collision light being off at high N1 values."""
923 def isCondition(self, flight, aircraft, oldState, state):
924 """Check if the fault condition holds."""
[519]925 return (not flight.config.usingFS2Crew or not state.parking or
926 flight.stage!=const.STAGE_TAXIAFTERLAND) and \
[11]927 not state.antiCollisionLightsOn and \
[211]928 self.isEngineCondition(state)
[11]929
930 def logFault(self, flight, aircraft, logger, oldState, state):
931 """Log the fault."""
[30]932 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
933 FaultChecker._appendDuring(flight,
[548]934 "Anti-collision lights were %s" %
935 ("on" if state.antiCollisionLightsOn else "off",)),
[30]936 1)
[11]937
[211]938 def isEngineCondition(self, state):
939 """Determine if the engines are in such a state that the lights should
940 be on."""
[263]941 if state.n1 is not None:
942 return max(state.n1)>5
943 elif state.rpm is not None:
944 return max(state.rpm)>0
945 else:
946 return False
[211]947
948#---------------------------------------------------------------------------------------
949
950class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
951 """Check for the anti-collision light for Tuplev planes."""
[307]952 def isCondition(self, flight, aircraft, oldState, state):
[332]953 """Check if the fault condition holds."""
[307]954 numEnginesRunning = 0
955 for n1 in state.n1:
956 if n1>5: numEnginesRunning += 1
[332]957
[452]958 if flight.stage==const.STAGE_PARKING or \
[548]959 flight.stage==const.STAGE_TAXIAFTERLAND:
[307]960 return numEnginesRunning<len(state.n1) \
961 and state.antiCollisionLightsOn
962 else:
963 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
964 numEnginesRunning<1 and state.antiCollisionLightsOn
[211]965
[11]966#---------------------------------------------------------------------------------------
967
968class BankChecker(SimpleFaultChecker):
[263]969 """Check for the bank is within limits."""
[11]970 def isCondition(self, flight, aircraft, oldState, state):
971 """Check if the fault condition holds."""
972 if flight.stage==const.STAGE_CRUISE:
[430]973 isDH8DXplane = flight.aircraftType==const.AIRCRAFT_DH8D and \
974 (flight.fsType==const.SIM_XPLANE10 or
975 flight.fsType==const.SIM_XPLANE9)
976 bankLimit = 35 if isDH8DXplane else 30
[11]977 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
978 const.STAGE_DESCENT, const.STAGE_LANDING]:
979 bankLimit = 35
980 else:
981 return False
982
983 return state.bank>bankLimit or state.bank<-bankLimit
984
985 def logFault(self, flight, aircraft, logger, oldState, state):
986 """Log the fault."""
[420]987 message = "Bank too steep (%.1f)" % (state.bank,)
[30]988 flight.handleFault(BankChecker, state.timestamp,
[420]989 FaultChecker._appendDuring(flight, message),
[30]990 2)
[11]991
992#---------------------------------------------------------------------------------------
993
994class FlapsRetractChecker(SimpleFaultChecker):
995 """Check if the flaps are not retracted too early."""
[13]996 def __init__(self):
997 """Construct the flaps checker."""
998 self._timeStart = None
[332]999
[11]1000 def isCondition(self, flight, aircraft, oldState, state):
1001 """Check if the fault condition holds.
1002
1003 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
[357]1004 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround and
1005 aircraft.type!=const.AIRCRAFT_F70) or \
[11]1006 (flight.stage==const.STAGE_LANDING and state.onTheGround):
1007 if self._timeStart is None:
1008 self._timeStart = state.timestamp
1009
1010 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
1011 return True
[13]1012 else:
1013 self._timeStart = None
[11]1014 return False
1015
1016 def logFault(self, flight, aircraft, logger, oldState, state):
1017 """Log the fault."""
[30]1018 flight.handleFault(FlapsRetractChecker, state.timestamp,
1019 FaultChecker._appendDuring(flight, "Flaps retracted"),
1020 20)
[11]1021
1022#---------------------------------------------------------------------------------------
1023
1024class FlapsSpeedLimitChecker(SimpleFaultChecker):
1025 """Check if the flaps are extended only at the right speeds."""
1026 def isCondition(self, flight, aircraft, oldState, state):
1027 """Check if the fault condition holds."""
1028 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
[197]1029 return speedLimit is not None and state.smoothedIAS>speedLimit
[11]1030
1031 def logFault(self, flight, aircraft, logger, oldState, state):
1032 """Log the fault."""
[30]1033 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
1034 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
1035 5)
[11]1036
1037#---------------------------------------------------------------------------------------
1038
1039class GearsDownChecker(SimpleFaultChecker):
1040 """Check if the gears are down at low altitudes."""
1041 def isCondition(self, flight, aircraft, oldState, state):
1042 """Check if the fault condition holds."""
1043 return state.radioAltitude<10 and not state.gearsDown and \
1044 flight.stage!=const.STAGE_TAKEOFF
1045
1046 def logFault(self, flight, aircraft, logger, oldState, state):
1047 """Log the fault."""
[30]1048 flight.handleNoGo(GearsDownChecker, state.timestamp,
1049 "Gears not down at %.0f feet radio altitude" % \
1050 (state.radioAltitude,),
1051 "GEAR DOWN NO GO")
[11]1052
1053#---------------------------------------------------------------------------------------
1054
1055class GearSpeedLimitChecker(PatientFaultChecker):
1056 """Check if the gears not down at too high a speed."""
1057 def isCondition(self, flight, aircraft, oldState, state):
1058 """Check if the fault condition holds."""
[197]1059 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
[11]1060
1061 def logFault(self, flight, aircraft, logger, oldState, state):
1062 """Log the fault."""
[30]1063 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
1064 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
1065 5)
[11]1066
1067#---------------------------------------------------------------------------------------
1068
1069class GLoadChecker(SimpleFaultChecker):
1070 """Check if the G-load does not exceed 2 except during flare."""
1071 def isCondition(self, flight, aircraft, oldState, state):
1072 """Check if the fault condition holds."""
[448]1073 return state.gLoad>2.0 and not state.onTheGround and \
1074 (flight.stage!=const.STAGE_LANDING or state.radioAltitude>=50)
[332]1075
[11]1076 def logFault(self, flight, aircraft, logger, oldState, state):
1077 """Log the fault."""
[30]1078 flight.handleFault(GLoadChecker, state.timestamp,
1079 "G-load was %.2f" % (state.gLoad,),
1080 10)
[11]1081
1082#---------------------------------------------------------------------------------------
1083
1084class LandingLightsChecker(PatientFaultChecker):
1085 """Check if the landing lights are used properly."""
1086 def getTimeout(self, flight, aircraft, oldState, state):
1087 """Get the timeout.
1088
1089 It is the default timeout except for landing and takeoff."""
1090 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
1091 const.STAGE_LANDING] else self._timeout
1092
1093 def isCondition(self, flight, aircraft, oldState, state):
1094 """Check if the fault condition holds."""
[314]1095 return state.landingLightsOn is not None and \
1096 ((flight.stage==const.STAGE_BOARDING and \
1097 state.landingLightsOn and state.onTheGround) or \
1098 (flight.stage==const.STAGE_TAKEOFF and \
1099 not state.landingLightsOn and not state.onTheGround) or \
1100 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1101 const.STAGE_DESCENT] and \
1102 state.landingLightsOn and state.altitude>12500) or \
1103 (flight.stage==const.STAGE_LANDING and \
[415]1104 not state.landingLightsOn and state.onTheGround and
1105 state.groundSpeed>50.0) or \
[314]1106 (flight.stage==const.STAGE_PARKING and \
1107 state.landingLightsOn and state.onTheGround))
[332]1108
[11]1109 def logFault(self, flight, aircraft, logger, oldState, state):
1110 """Log the fault."""
[375]1111 score = 0 if flight.stage in [const.STAGE_TAKEOFF,
1112 const.STAGE_LANDING] else 1
[335]1113 message = "Landing lights were %s" % \
1114 (("on" if state.landingLightsOn else "off"),)
[30]1115 flight.handleFault(LandingLightsChecker, state.timestamp,
1116 FaultChecker._appendDuring(flight, message),
[332]1117 score)
[11]1118
1119#---------------------------------------------------------------------------------------
1120
[335]1121class TransponderChecker(PatientFaultChecker):
1122 """Check if the transponder is used properly."""
[361]1123 def __init__(self):
1124 """Construct the transponder checker."""
1125 super(TransponderChecker, self).__init__()
1126 self._liftOffTime = None
1127
[335]1128 def isCondition(self, flight, aircraft, oldState, state):
1129 """Check if the fault condition holds."""
[361]1130 if state.onTheGround:
1131 self._liftOffTime = None
1132 elif self._liftOffTime is None:
1133 self._liftOffTime = state.timestamp
1134
[335]1135 return state.xpdrC is not None and \
1136 ((state.xpdrC and flight.stage in
1137 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
[361]1138 (not state.xpdrC and
1139 (flight.stage in
1140 [const.STAGE_CRUISE, const.STAGE_DESCENT,
[446]1141 const.STAGE_GOAROUND] or \
[518]1142 (flight.stage==const.STAGE_LANDING and
[446]1143 state.groundSpeed>50.0) or \
[361]1144 ((not state.autoXPDR or \
1145 (self._liftOffTime is not None and
1146 state.timestamp > (self._liftOffTime+8))) and \
[518]1147 ((flight.stage==const.STAGE_TAKEOFF and
1148 not state.onTheGround) or flight.stage==const.STAGE_CLIMB))
[361]1149 )
1150 )
1151 )
[335]1152
1153 def logFault(self, flight, aircraft, logger, oldState, state):
1154 """Log the fault."""
1155 score = 0
1156 message = "Transponder was %s" % \
1157 (("mode C" if state.xpdrC else "standby"),)
1158 flight.handleFault(TransponderChecker, state.timestamp,
1159 FaultChecker._appendDuring(flight, message),
1160 score)
1161
1162#---------------------------------------------------------------------------------------
1163
[11]1164class WeightChecker(PatientFaultChecker):
1165 """Base class for checkers that check that some limit is not exceeded."""
1166 def __init__(self, name):
1167 """Construct the checker."""
1168 super(WeightChecker, self).__init__(timeout = 5.0)
1169 self._name = name
1170
1171 def isCondition(self, flight, aircraft, oldState, state):
1172 """Check if the fault condition holds."""
1173 if flight.entranceExam:
1174 return False
1175
1176 limit = self.getLimit(flight, aircraft, state)
1177 if limit is not None:
[183]1178 #if flight.options.compensation is not None:
1179 # limit += flight.options.compensation
[13]1180 return self.getWeight(state)>limit
[11]1181
1182 return False
1183
1184 def logFault(self, flight, aircraft, logger, oldState, state):
1185 """Log the fault."""
[13]1186 mname = "M" + self._name
[30]1187 flight.handleNoGo(self.__class__, state.timestamp,
1188 "%s exceeded: %s is %.0f kg" % \
1189 (mname, self._name, self.getWeight(state)),
1190 "%s NO GO" % (mname,))
[13]1191
1192 def getWeight(self, state):
1193 """Get the weight that is interesting for us."""
1194 return state.grossWeight
[11]1195
1196#---------------------------------------------------------------------------------------
1197
1198class MLWChecker(WeightChecker):
1199 """Checks if the MLW is not exceeded on landing."""
1200 def __init__(self):
1201 """Construct the checker."""
[13]1202 super(MLWChecker, self).__init__("LW")
[11]1203
[13]1204 def getLimit(self, flight, aircraft, state):
[11]1205 """Get the limit if we are in the right state."""
1206 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
[184]1207 state.onTheGround and \
1208 not flight.entranceExam else None
[11]1209
1210#---------------------------------------------------------------------------------------
1211
1212class MTOWChecker(WeightChecker):
1213 """Checks if the MTOW is not exceeded on landing."""
1214 def __init__(self):
1215 """Construct the checker."""
[13]1216 super(MTOWChecker, self).__init__("TOW")
[11]1217
[13]1218 def getLimit(self, flight, aircraft, state):
[11]1219 """Get the limit if we are in the right state."""
[184]1220 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1221 not flight.entranceExam else None
[11]1222
1223#---------------------------------------------------------------------------------------
1224
1225class MZFWChecker(WeightChecker):
[60]1226 """Checks if the MZFW is not exceeded on landing."""
[11]1227 def __init__(self):
1228 """Construct the checker."""
[13]1229 super(MZFWChecker, self).__init__("ZFW")
[11]1230
[13]1231 def getLimit(self, flight, aircraft, state):
[11]1232 """Get the limit if we are in the right state."""
[184]1233 return aircraft.mzfw if not flight.entranceExam else None
[11]1234
[13]1235 def getWeight(self, state):
1236 """Get the weight that is interesting for us."""
1237 return state.zfw
1238
[11]1239#---------------------------------------------------------------------------------------
1240
1241class NavLightsChecker(PatientFaultChecker):
1242 """Check if the navigational lights are used properly."""
1243 def isCondition(self, flight, aircraft, oldState, state):
1244 """Check if the fault condition holds."""
1245 return flight.stage!=const.STAGE_BOARDING and \
1246 flight.stage!=const.STAGE_PARKING and \
[341]1247 state.navLightsOn is False
[332]1248
[11]1249 def logFault(self, flight, aircraft, logger, oldState, state):
1250 """Log the fault."""
[30]1251 flight.handleFault(NavLightsChecker, state.timestamp,
1252 FaultChecker._appendDuring(flight,
1253 "Navigation lights were off"),
1254 1)
[11]1255
1256#---------------------------------------------------------------------------------------
1257
1258class OverspeedChecker(PatientFaultChecker):
1259 """Check if Vne has been exceeded."""
[447]1260 def __init__(self, timeout = 30.0):
[11]1261 """Construct the checker."""
[197]1262 super(OverspeedChecker, self).__init__(timeout = timeout)
[11]1263
1264 def isCondition(self, flight, aircraft, oldState, state):
1265 """Check if the fault condition holds."""
1266 return state.overspeed
[332]1267
[11]1268 def logFault(self, flight, aircraft, logger, oldState, state):
1269 """Log the fault."""
[30]1270 flight.handleFault(OverspeedChecker, state.timestamp,
1271 FaultChecker._appendDuring(flight, "Overspeed"),
[332]1272 20)
[11]1273
1274#---------------------------------------------------------------------------------------
1275
1276class PayloadChecker(SimpleFaultChecker):
1277 """Check if the payload matches the specification."""
1278 TOLERANCE=550
[60]1279
1280 @staticmethod
1281 def isZFWFaulty(aircraftZFW, flightZFW):
1282 """Check if the given aircraft's ZFW is outside of the limits."""
1283 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
[332]1284 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1285
[11]1286 def isCondition(self, flight, aircraft, oldState, state):
1287 """Check if the fault condition holds."""
[192]1288 return not flight.entranceExam and \
[184]1289 flight.stage==const.STAGE_PUSHANDTAXI and \
[60]1290 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
[332]1291
[11]1292 def logFault(self, flight, aircraft, logger, oldState, state):
1293 """Log the fault."""
[30]1294 flight.handleNoGo(PayloadChecker, state.timestamp,
1295 "ZFW difference is more than %d kgs" % \
1296 (PayloadChecker.TOLERANCE,),
1297 "ZFW NO GO")
[11]1298
1299#---------------------------------------------------------------------------------------
1300
1301class PitotChecker(PatientFaultChecker):
1302 """Check if pitot heat is on."""
1303 def __init__(self):
1304 """Construct the checker."""
1305 super(PitotChecker, self).__init__(timeout = 3.0)
1306
1307 def isCondition(self, flight, aircraft, oldState, state):
1308 """Check if the fault condition holds."""
[13]1309 return state.groundSpeed>80 and not state.pitotHeatOn
[332]1310
[11]1311 def logFault(self, flight, aircraft, logger, oldState, state):
1312 """Log the fault."""
1313 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1314 const.STAGE_CRUISE, const.STAGE_DESCENT,
1315 const.STAGE_LANDING] else 0
[30]1316 flight.handleFault(PitotChecker, state.timestamp,
1317 FaultChecker._appendDuring(flight, "Pitot heat was off"),
[332]1318 score)
[11]1319
1320#---------------------------------------------------------------------------------------
1321
1322class ReverserChecker(SimpleFaultChecker):
[360]1323 """Check if the reverser is not used below the speed prescribed for the
1324 aircraft."""
[11]1325 def isCondition(self, flight, aircraft, oldState, state):
1326 """Check if the fault condition holds."""
1327 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1328 const.STAGE_TAXIAFTERLAND] and \
[443]1329 state.reverser and \
[360]1330 state.groundSpeed<aircraft.reverseMinSpeed and max(state.reverser)
[332]1331
[11]1332 def logFault(self, flight, aircraft, logger, oldState, state):
1333 """Log the fault."""
[241]1334 message = "Reverser used below %.0f %s" % \
1335 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
[30]1336 flight.handleFault(ReverserChecker, state.timestamp,
[241]1337 FaultChecker._appendDuring(flight, message),
1338 15)
[11]1339
1340#---------------------------------------------------------------------------------------
1341
1342class SpeedChecker(SimpleFaultChecker):
1343 """Check if the speed is in the prescribed limits."""
[344]1344 @staticmethod
[539]1345 def needNoStrobe(aircraft, state):
1346 """Determine if the given aircraft and state needs a strobeless speed
1347 checking."""
1348 return aircraft.needNoStrobeSpeedCheck or \
1349 (state.strobeLightsOn is None and state.xpdrC is None)
1350
1351 @staticmethod
[349]1352 def logSpeedFault(flight, state, stage = None, updateID = None):
[344]1353 """Log the speed fault."""
1354 message = "Taxi speed over %.0f %s" % \
1355 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
[349]1356 if stage is None:
1357 stage = flight.stage
1358 score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
1359 return flight.handleFault((SpeedChecker, stage), state.timestamp,
1360 FaultChecker._appendDuring(flight, message),
1361 score, updatePrevious = updateID is None,
1362 updateID = updateID)
[344]1363
[11]1364 def isCondition(self, flight, aircraft, oldState, state):
1365 """Check if the fault condition holds."""
[539]1366 return not self.needNoStrobe(aircraft, state) and \
1367 flight.stage in [const.STAGE_PUSHANDTAXI,
1368 const.STAGE_RTO,
1369 const.STAGE_TAXIAFTERLAND] and \
[11]1370 state.groundSpeed>50
[332]1371
[11]1372 def logFault(self, flight, aircraft, logger, oldState, state):
1373 """Log the fault."""
[344]1374 self.logSpeedFault(flight, state)
1375
1376#---------------------------------------------------------------------------------------
1377
1378class NoStrobeSpeedChecker(StateChecker):
1379 """Checker for the ground speed of aircraft that have no strobe lights by
1380 which to detect the takeoff stage.
1381
1382 If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state as
1383 saved as a provisional takeoff state. If the speed then decreases below 50
[453]1384 knots, or the plane remains on the ground for more than 40 seconds, a taxi
[349]1385 speed error is logged with the highest ground speed detected. This state is
1386 also stored in the flight object as a possible
[344]1387
[453]1388 If the plane becomes airborne within 40 seconds, the stage becomes TAKEOFF,
[344]1389 and the previously saved takeoff state is logged.
1390
1391 During the TAXIAFTERLAND stage, speed is checked as in case of
1392 SpeedChecker."""
1393 def __init__(self):
1394 """Initialize the speed checker."""
1395 self._takeoffState = None
1396 self._highestSpeedState = None
1397
1398 def check(self, flight, aircraft, logger, oldState, state):
1399 """Check the state as described above."""
[539]1400 if SpeedChecker.needNoStrobe(aircraft, state):
1401 if flight.stage==const.STAGE_PUSHANDTAXI or \
1402 flight.stage==const.STAGE_RTO:
1403 self._checkPushAndTaxi(flight, aircraft, state)
1404 elif flight.stage==const.STAGE_TAXIAFTERLAND:
1405 if state.groundSpeed>50:
1406 SpeedChecker.logSpeedFault(flight, state)
1407 else:
1408 self._takeoffState = None
[344]1409
1410 def _checkPushAndTaxi(self, flight, aircraft, state):
1411 """Check the speed during the push and taxi stage."""
1412 if state.groundSpeed>50:
1413 if self._takeoffState is None:
1414 self._takeoffState = state
1415 self._highestSpeedState = state
1416 else:
1417 if state.groundSpeed>self._highestSpeedState.groundSpeed:
1418 self._highestSpeedState = state
1419
[414]1420 if not state.onTheGround:
[344]1421 aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
1422 self._takeoffState = None
[453]1423 elif state.timestamp > (self._takeoffState.timestamp + 40):
[414]1424 flight.setRTOState(self._highestSpeedState)
[344]1425 elif self._takeoffState is not None:
[349]1426 flight.setRTOState(self._highestSpeedState)
[344]1427 self._takeoffState = None
[11]1428
1429#---------------------------------------------------------------------------------------
1430
1431class StallChecker(PatientFaultChecker):
1432 """Check if stall occured."""
1433 def isCondition(self, flight, aircraft, oldState, state):
1434 """Check if the fault condition holds."""
1435 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1436 const.STAGE_CRUISE, const.STAGE_DESCENT,
1437 const.STAGE_LANDING] and state.stalled
[332]1438
[11]1439 def logFault(self, flight, aircraft, logger, oldState, state):
1440 """Log the fault."""
1441 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1442 const.STAGE_LANDING] else 30
[30]1443 flight.handleFault(StallChecker, state.timestamp,
1444 FaultChecker._appendDuring(flight, "Stalled"),
[332]1445 score)
[11]1446
1447#---------------------------------------------------------------------------------------
1448
1449class StrobeLightsChecker(PatientFaultChecker):
1450 """Check if the strobe lights are used properly."""
1451 def isCondition(self, flight, aircraft, oldState, state):
1452 """Check if the fault condition holds."""
[480]1453 return state.strobeLightsOn is not None and \
1454 ((flight.stage==const.STAGE_BOARDING and \
[211]1455 state.strobeLightsOn and state.onTheGround) or \
1456 (flight.stage==const.STAGE_TAKEOFF and \
1457 not state.strobeLightsOn and not state.gearsDown) or \
1458 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1459 const.STAGE_DESCENT] and \
1460 not state.strobeLightsOn and not state.onTheGround) or \
1461 (flight.stage==const.STAGE_PARKING and \
[480]1462 state.strobeLightsOn and state.onTheGround))
[332]1463
[11]1464 def logFault(self, flight, aircraft, logger, oldState, state):
1465 """Log the fault."""
1466 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
[30]1467 flight.handleFault(StrobeLightsChecker, state.timestamp,
1468 FaultChecker._appendDuring(flight, message),
1469 1)
[11]1470
1471#---------------------------------------------------------------------------------------
1472
1473class ThrustChecker(SimpleFaultChecker):
1474 """Check if the thrust setting is not too high during takeoff.
1475
1476 FIXME: is this really so general, for all aircraft?"""
1477 def isCondition(self, flight, aircraft, oldState, state):
1478 """Check if the fault condition holds."""
[263]1479 return flight.stage==const.STAGE_TAKEOFF and \
1480 state.n1 is not None and max(state.n1)>97
[332]1481
[11]1482 def logFault(self, flight, aircraft, logger, oldState, state):
1483 """Log the fault."""
[30]1484 flight.handleFault(ThrustChecker, state.timestamp,
1485 FaultChecker._appendDuring(flight,
1486 "Thrust setting was too high (>97%)"),
[405]1487 FaultChecker._getLinearScore(97, 110, 0, 10,
1488 max(state.n1)),
1489 updatePrevious = True)
[11]1490
1491#---------------------------------------------------------------------------------------
1492
1493class VSChecker(SimpleFaultChecker):
1494 """Check if the vertical speed is not too low at certain altitudes"""
1495 BELOW10000 = -5000
1496 BELOW5000 = -2500
1497 BELOW2500 = -1500
1498 BELOW500 = -1000
1499 TOLERANCE = 1.2
1500
1501 def isCondition(self, flight, aircraft, oldState, state):
1502 """Check if the fault condition holds."""
[197]1503 vs = state.smoothedVS
[11]1504 altitude = state.altitude
1505 return vs < -8000 or vs > 8000 or \
1506 (altitude<500 and vs < (VSChecker.BELOW500 *
[13]1507 VSChecker.TOLERANCE)) or \
[11]1508 (altitude<2500 and vs < (VSChecker.BELOW2500 *
[13]1509 VSChecker.TOLERANCE)) or \
[11]1510 (altitude<5000 and vs < (VSChecker.BELOW5000 *
[13]1511 VSChecker.TOLERANCE)) or \
[11]1512 (altitude<10000 and vs < (VSChecker.BELOW10000 *
[13]1513 VSChecker.TOLERANCE))
[332]1514
[11]1515 def logFault(self, flight, aircraft, logger, oldState, state):
1516 """Log the fault."""
[197]1517 vs = state.smoothedVS
[11]1518
1519 message = "Vertical speed was %.0f feet/min" % (vs,)
1520 if vs>-8000 and vs<8000:
1521 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1522
1523 score = 10 if vs<-8000 or vs>8000 else 0
1524
[30]1525 flight.handleFault(VSChecker, state.timestamp,
1526 FaultChecker._appendDuring(flight, message),
1527 score)
[11]1528
1529#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.