source: src/mlx/checks.py@ 366:21db3f9653ce

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

Added support for the ILS frequency, and made use of it in case of the DA F70 model (#115)

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