source: src/mlx/checks.py@ 297:9d1f71a9de51

Last change on this file since 297:9d1f71a9de51 was 297:9d1f71a9de51, checked in by István Váradi <ivaradi@…>, 12 years ago

Added some further documentation

File size: 45.8 KB
RevLine 
[297]1## @package mlx.checks
2#
3# The classes that check the state of the aircraft.
4#
5# During the flight the program periodically queries various data from the
6# simulator. This data is returned in instances of the \ref
7# mlx.fs.AircraftState class and passed to the various "checkers",
8# i.e. instances of subclasses of the \ref StateChecker class. These checkers
9# perform various checks to see if the aircraft's parameters are within the
10# expected limits, or some of them just logs something.
11#
12# There are a few special ones, such as \ref StageChecker which computes the
13# transitions from one stage of the flight to the next one. Or \ref ACARSSender
14# which sends the ACARS periodically
[8]15
16#---------------------------------------------------------------------------------------
17
[134]18import fs
[8]19import const
[134]20import util
[139]21from acars import ACARS
[170]22from sound import startSound
[139]23
24import time
[8]25
26#---------------------------------------------------------------------------------------
27
28class StateChecker(object):
29 """Base class for classes the instances of which check the aircraft's state
30 from some aspect.
31
32 As a result of the check they may log something, or notify of some fault, etc."""
33 def check(self, flight, aircraft, logger, oldState, state):
34 """Perform the check and do whatever is needed.
35
36 This default implementation raises a NotImplementedError."""
37 raise NotImplementedError()
38
39#---------------------------------------------------------------------------------------
40
41class StageChecker(StateChecker):
42 """Check the flight stage transitions."""
[25]43 def __init__(self):
44 """Construct the stage checker."""
45 self._flareStarted = False
46
[8]47 def check(self, flight, aircraft, logger, oldState, state):
48 """Check the stage of the aircraft."""
49 stage = flight.stage
50 if stage==None:
51 aircraft.setStage(state, const.STAGE_BOARDING)
52 elif stage==const.STAGE_BOARDING:
53 if not state.parking or \
54 (not state.trickMode and state.groundSpeed>5.0):
55 aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
56 elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
57 if state.landingLightsOn or state.strobeLightsOn or \
58 state.groundSpeed>80.0:
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:
84 if state.onTheGround and state.groundSpeed<50.0:
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:
96 if state.parking:
97 aircraft.setStage(state, const.STAGE_PARKING)
98 elif stage==const.STAGE_PARKING:
99 if aircraft.checkFlightEnd(state):
100 aircraft.setStage(state, const.STAGE_END)
101
102#---------------------------------------------------------------------------------------
103
[139]104class ACARSSender(StateChecker):
105 """Sender of online ACARS.
106
107 It sends the ACARS every 3 minutes to the MAVA website."""
108
[297]109 ## The interval at which the ACARS are sent
[139]110 INTERVAL = 3*60.0
111
112 def __init__(self, gui):
113 """Construct the ACARS sender."""
114 self._gui = gui
115 self._lastSent = None
116
117 def check(self, flight, aircraft, logger, oldState, state):
118 """If the time has come to send the ACARS, send it."""
119 now = time.time()
120
121 if self._lastSent is not None and \
122 (self._lastSent + ACARSSender.INTERVAL)>now:
123 return
124
125 acars = ACARS(self._gui, state)
126 self._gui.webHandler.sendACARS(self._acarsCallback, acars)
127
128 def _acarsCallback(self, returned, result):
129 """Callback for ACARS sending."""
130 if returned:
131 print "Sent online ACARS"
132 self._lastSent = time.time() if self._lastSent is None \
133 else self._lastSent + ACARSSender.INTERVAL
134 else:
135 print "Failed to send the ACARS"
136
137#---------------------------------------------------------------------------------------
138
[9]139class TakeOffLogger(StateChecker):
140 """Logger for the cruise speed."""
141 def __init__(self):
142 """Construct the logger."""
143 self._onTheGround = True
144
145 def check(self, flight, aircraft, logger, oldState, state):
146 """Log the cruise speed if necessary."""
147 if flight.stage==const.STAGE_TAKEOFF and \
148 self._onTheGround and not state.onTheGround:
149 logger.message(state.timestamp,
[241]150 "Takeoff speed: %.0f %s" % \
151 (flight.speedFromKnots(state.ias),
152 flight.getEnglishSpeedUnit()))
[9]153 logger.message(state.timestamp,
154 "Takeoff heading: %03.0f degrees" % (state.heading,))
155 logger.message(state.timestamp,
156 "Takeoff pitch: %.1f degrees" % (state.pitch,))
[243]157 logger.message(state.timestamp,
[271]158 "Centre of gravity: %.1f%%" % (state.cog*100.0,))
[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
168
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
[10]192
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
239 the vertical speed is negative, that callout will be played."""
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."""
262 def __init__(self, logInitial = True):
263 """Construct the logger.
264
265 If logInitial is True, the initial value will be logged, not just the
266 changes later.
267
268 Child classes should define the following functions:
269 - _changed(self, oldState, state): returns a boolean indicating if the
270 value has changed or not
[241]271 - _getMessage(self, flight, state): return a strings containing the
272 message to log with the new value
[8]273 """
274 self._logInitial = logInitial
275
[22]276 def _getLogTimestamp(self, state):
277 """Get the log timestamp."""
278 return state.timestamp
279
[8]280 def check(self, flight, aircraft, logger, oldState, state):
281 """Check if the state has changed, and if so, log the new state."""
282 shouldLog = False
283 if oldState is None:
284 shouldLog = self._logInitial
285 else:
286 shouldLog = self._changed(oldState, state)
287
288 if shouldLog:
[241]289 logger.message(self._getLogTimestamp(state),
290 self._getMessage(flight, state))
[8]291
[241]292#-------------------------------------------------------------------------------
[8]293
294class SimpleChangeMixin(object):
295 """A mixin that defines a _changed() function which simply calls a function
296 to retrieve the value two monitor for both states and compares them.
297
298 Child classes should define the following function:
299 - _getValue(state): get the value we are interested in."""
300 def _changed(self, oldState, state):
301 """Determine if the value has changed."""
302 return self._getValue(oldState)!=self._getValue(state)
303
304#---------------------------------------------------------------------------------------
305
306class SingleValueMixin(object):
307 """A mixin that provides a _getValue() function to query a value from the
308 state using the name of the attribute."""
309 def __init__(self, attrName):
310 """Construct the mixin with the given attribute name."""
311 self._attrName = attrName
312
313 def _getValue(self, state):
314 """Get the value of the attribute from the state."""
315 return getattr(state, self._attrName)
316
317#---------------------------------------------------------------------------------------
318
319class DelayedChangeMixin(object):
320 """A mixin to a StateChangeLogger that stores the old value and reports a
321 change only if the change has been there for a certain amount of time.
322
323 Child classes should define the following function:
324 - _getValue(state): get the value we are interested in."""
[22]325 def __init__(self, minDelay = 3.0, maxDelay = 10.0):
[8]326 """Construct the mixin with the given delay in seconds."""
[22]327 self._minDelay = minDelay
328 self._maxDelay = maxDelay
[8]329 self._oldValue = None
330 self._firstChange = None
[22]331 self._lastChangeState = None
332
[8]333 def _changed(self, oldState, state):
334 """Determine if the value has changed."""
335 if self._oldValue is None:
336 self._oldValue = self._getValue(oldState)
337
338 newValue = self._getValue(state)
[22]339 if self._oldValue!=newValue:
[8]340 if self._firstChange is None:
341 self._firstChange = state.timestamp
[22]342 self._lastChangeState = state
343 self._oldValue = newValue
344
345 if self._firstChange is not None:
346 if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
347 self._firstChange + self._maxDelay):
[8]348 self._firstChange = None
349 return True
[22]350
351 return False
[8]352
[22]353 def _getLogTimestamp(self, state):
354 """Get the log timestamp."""
355 return self._lastChangeState.timestamp if \
356 self._lastChangeState is not None else state.timestamp
[8]357
358#---------------------------------------------------------------------------------------
359
360class TemplateMessageMixin(object):
361 """Mixin to generate a message based on a template.
362
363 Child classes should define the following function:
364 - _getValue(state): get the value we are interested in."""
365 def __init__(self, template):
366 """Construct the mixin."""
367 self._template = template
368
[241]369 def _getMessage(self, flight, state):
[8]370 """Get the message."""
371 return self._template % (self._getValue(state),)
372
373#---------------------------------------------------------------------------------------
374
375class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
376 DelayedChangeMixin, TemplateMessageMixin):
377 """Base for generic state change loggers that monitor a single value in the
378 state possibly with a delay and the logged message comes from a template"""
[22]379 def __init__(self, attrName, template, logInitial = True,
380 minDelay = 0.0, maxDelay = 0.0):
[8]381 """Construct the object."""
382 StateChangeLogger.__init__(self, logInitial = logInitial)
383 SingleValueMixin.__init__(self, attrName)
[22]384 DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
[8]385 TemplateMessageMixin.__init__(self, template)
[22]386 self._getLogTimestamp = lambda state: \
387 DelayedChangeMixin._getLogTimestamp(self, state)
[8]388
389#---------------------------------------------------------------------------------------
390
391class AltimeterLogger(StateChangeLogger, SingleValueMixin,
392 DelayedChangeMixin):
393 """Logger for the altimeter setting."""
394 def __init__(self):
395 """Construct the logger."""
396 StateChangeLogger.__init__(self, logInitial = True)
397 SingleValueMixin.__init__(self, "altimeter")
398 DelayedChangeMixin.__init__(self)
[22]399 self._getLogTimestamp = lambda state: \
400 DelayedChangeMixin._getLogTimestamp(self, state)
[8]401
[241]402 def _getMessage(self, flight, state):
[8]403 """Get the message to log on a change."""
[22]404 logState = self._lastChangeState if \
405 self._lastChangeState is not None else state
406 return "Altimeter: %.0f hPa at %.0f feet" % \
407 (logState.altimeter, logState.altitude)
[8]408
409#---------------------------------------------------------------------------------------
410
411class NAV1Logger(GenericStateChangeLogger):
412 """Logger for the NAV1 radio setting."""
413 def __init__(self):
414 """Construct the logger."""
415 super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
416
417#---------------------------------------------------------------------------------------
418
419class NAV2Logger(GenericStateChangeLogger):
420 """Logger for the NAV2 radio setting."""
421 def __init__(self):
422 """Construct the logger."""
423 super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
424
425#---------------------------------------------------------------------------------------
426
427class SquawkLogger(GenericStateChangeLogger):
428 """Logger for the squawk setting."""
429 def __init__(self):
430 """Construct the logger."""
431 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
[22]432 minDelay = 3.0, maxDelay = 10.0)
[8]433
434#---------------------------------------------------------------------------------------
435
436class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
437 """Base class for the loggers of the various lights."""
438 def __init__(self, attrName, template):
439 """Construct the logger."""
440 StateChangeLogger.__init__(self)
441 SingleValueMixin.__init__(self, attrName)
442
443 self._template = template
444
[241]445 def _getMessage(self, flight, state):
[8]446 """Get the message from the given state."""
447 return self._template % ("ON" if self._getValue(state) else "OFF")
448
449#---------------------------------------------------------------------------------------
450
451class AnticollisionLightsLogger(LightsLogger):
452 """Logger for the anti-collision lights."""
453 def __init__(self):
454 LightsLogger.__init__(self, "antiCollisionLightsOn",
455 "Anti-collision lights: %s")
456
457#---------------------------------------------------------------------------------------
458
459class LandingLightsLogger(LightsLogger):
460 """Logger for the landing lights."""
461 def __init__(self):
462 LightsLogger.__init__(self, "landingLightsOn",
463 "Landing lights: %s")
464
465#---------------------------------------------------------------------------------------
466
467class StrobeLightsLogger(LightsLogger):
468 """Logger for the strobe lights."""
469 def __init__(self):
470 LightsLogger.__init__(self, "strobeLightsOn",
471 "Strobe lights: %s")
472
473#---------------------------------------------------------------------------------------
474
475class NavLightsLogger(LightsLogger):
476 """Logger for the navigational lights."""
477 def __init__(self):
478 LightsLogger.__init__(self, "navLightsOn",
479 "Navigational lights: %s")
480
481#---------------------------------------------------------------------------------------
482
483class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
484 """Logger for the flaps setting."""
485 def __init__(self):
486 """Construct the logger."""
487 StateChangeLogger.__init__(self, logInitial = True)
488 SingleValueMixin.__init__(self, "flapsSet")
489
[241]490 def _getMessage(self, flight, state):
[8]491 """Get the message to log on a change."""
492 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
[241]493 return "Flaps set to %.0f at %.0f %s" % \
494 (state.flapsSet, flight.speedFromKnots(speed),
495 flight.getEnglishSpeedUnit())
[8]496
497#---------------------------------------------------------------------------------------
498
499class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
500 """Logger for the gears state."""
501 def __init__(self):
502 """Construct the logger."""
503 StateChangeLogger.__init__(self, logInitial = True)
[209]504 SingleValueMixin.__init__(self, "gearControlDown")
[8]505
[241]506 def _getMessage(self, flight, state):
[8]507 """Get the message to log on a change."""
[241]508 return "Gears SET to %s at %.0f %s, %.0f feet" % \
[209]509 ("DOWN" if state.gearControlDown else "UP",
[241]510 flight.speedFromKnots(state.ias),
511 flight.getEnglishSpeedUnit(), state.altitude)
[8]512
513#---------------------------------------------------------------------------------------
[11]514
515class FaultChecker(StateChecker):
516 """Base class for checkers that look for faults."""
517 @staticmethod
518 def _appendDuring(flight, message):
519 """Append a 'during XXX' test to the given message, depending on the
520 flight stage."""
521 stageStr = const.stage2string(flight.stage)
522 return message if stageStr is None \
523 else (message + " during " + stageStr.upper())
524
525 @staticmethod
526 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
527 value):
528 """Get the score for a faulty value where the score is calculated
529 linearly within a certain range."""
530 if value<minFaultValue:
531 return 0
532 elif value>maxFaultValue:
533 return maxScore
534 else:
535 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
536 (maxFaultValue - minFaultValue)
537
538#---------------------------------------------------------------------------------------
539
540class SimpleFaultChecker(FaultChecker):
541 """Base class for fault checkers that check for a single occurence of a
542 faulty condition.
543
544 Child classes should implement the following functions:
545 - isCondition(self, flight, aircraft, oldState, state): should return whether the
546 condition holds
547 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
548 via the logger."""
549 def check(self, flight, aircraft, logger, oldState, state):
550 """Perform the check."""
551 if self.isCondition(flight, aircraft, oldState, state):
552 self.logFault(flight, aircraft, logger, oldState, state)
553
554#---------------------------------------------------------------------------------------
555
556class PatientFaultChecker(FaultChecker):
557 """A fault checker that does not decides on a fault when the condition
558 arises immediately, but can wait some time.
559
560 Child classes should implement the following functions:
561 - isCondition(self, flight, aircraft, oldState, state): should return whether the
562 condition holds
563 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
564 via the logger
565 """
566 def __init__(self, timeout = 2.0):
567 """Construct the fault checker with the given timeout."""
568 self._timeout = timeout
569 self._faultStarted = None
570
571 def getTimeout(self, flight, aircraft, oldState, state):
572 """Get the timeout.
573
574 This default implementation returns the timeout given in the
575 constructor, but child classes might want to enforce a different
576 policy."""
577 return self._timeout
578
579 def check(self, flight, aircraft, logger, oldState, state):
580 """Perform the check."""
581 if self.isCondition(flight, aircraft, oldState, state):
582 if self._faultStarted is None:
583 self._faultStarted = state.timestamp
584 timeout = self.getTimeout(flight, aircraft, oldState, state)
585 if state.timestamp>=(self._faultStarted + timeout):
586 self.logFault(flight, aircraft, logger, oldState, state)
587 self._faultStarted = state.timestamp
588 else:
589 self._faultStarted = None
590
591#---------------------------------------------------------------------------------------
592
593class AntiCollisionLightsChecker(PatientFaultChecker):
594 """Check for the anti-collision light being off at high N1 values."""
595 def isCondition(self, flight, aircraft, oldState, state):
596 """Check if the fault condition holds."""
597 return (flight.stage!=const.STAGE_PARKING or \
[183]598 not flight.config.usingFS2Crew) and \
[11]599 not state.antiCollisionLightsOn and \
[211]600 self.isEngineCondition(state)
[11]601
602 def logFault(self, flight, aircraft, logger, oldState, state):
603 """Log the fault."""
[30]604 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
605 FaultChecker._appendDuring(flight,
606 "Anti-collision lights were off"),
607 1)
[11]608
[211]609 def isEngineCondition(self, state):
610 """Determine if the engines are in such a state that the lights should
611 be on."""
[263]612 if state.n1 is not None:
613 return max(state.n1)>5
614 elif state.rpm is not None:
615 return max(state.rpm)>0
616 else:
617 return False
[211]618
619#---------------------------------------------------------------------------------------
620
621class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
622 """Check for the anti-collision light for Tuplev planes."""
623
624 def isEngineCondition(self, state):
625 """Determine if the engines are in such a state that the lights should
626 be on."""
[263]627 return max(state.n1[1:])>5
[211]628
[11]629#---------------------------------------------------------------------------------------
630
631class BankChecker(SimpleFaultChecker):
[263]632 """Check for the bank is within limits."""
[11]633 def isCondition(self, flight, aircraft, oldState, state):
634 """Check if the fault condition holds."""
635 if flight.stage==const.STAGE_CRUISE:
636 bankLimit = 30
637 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
638 const.STAGE_DESCENT, const.STAGE_LANDING]:
639 bankLimit = 35
640 else:
641 return False
642
643 return state.bank>bankLimit or state.bank<-bankLimit
644
645 def logFault(self, flight, aircraft, logger, oldState, state):
646 """Log the fault."""
[30]647 flight.handleFault(BankChecker, state.timestamp,
648 FaultChecker._appendDuring(flight, "Bank too steep"),
649 2)
[11]650
651#---------------------------------------------------------------------------------------
652
653class FlapsRetractChecker(SimpleFaultChecker):
654 """Check if the flaps are not retracted too early."""
[13]655 def __init__(self):
656 """Construct the flaps checker."""
657 self._timeStart = None
658
[11]659 def isCondition(self, flight, aircraft, oldState, state):
660 """Check if the fault condition holds.
661
662 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
663 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
664 (flight.stage==const.STAGE_LANDING and state.onTheGround):
665 if self._timeStart is None:
666 self._timeStart = state.timestamp
667
668 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
669 return True
[13]670 else:
671 self._timeStart = None
[11]672 return False
673
674 def logFault(self, flight, aircraft, logger, oldState, state):
675 """Log the fault."""
[30]676 flight.handleFault(FlapsRetractChecker, state.timestamp,
677 FaultChecker._appendDuring(flight, "Flaps retracted"),
678 20)
[11]679
680#---------------------------------------------------------------------------------------
681
682class FlapsSpeedLimitChecker(SimpleFaultChecker):
683 """Check if the flaps are extended only at the right speeds."""
684 def isCondition(self, flight, aircraft, oldState, state):
685 """Check if the fault condition holds."""
686 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
[197]687 return speedLimit is not None and state.smoothedIAS>speedLimit
[11]688
689 def logFault(self, flight, aircraft, logger, oldState, state):
690 """Log the fault."""
[30]691 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
692 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
693 5)
[11]694
695#---------------------------------------------------------------------------------------
696
697class GearsDownChecker(SimpleFaultChecker):
698 """Check if the gears are down at low altitudes."""
699 def isCondition(self, flight, aircraft, oldState, state):
700 """Check if the fault condition holds."""
701 return state.radioAltitude<10 and not state.gearsDown and \
702 flight.stage!=const.STAGE_TAKEOFF
703
704 def logFault(self, flight, aircraft, logger, oldState, state):
705 """Log the fault."""
[30]706 flight.handleNoGo(GearsDownChecker, state.timestamp,
707 "Gears not down at %.0f feet radio altitude" % \
708 (state.radioAltitude,),
709 "GEAR DOWN NO GO")
[11]710
711#---------------------------------------------------------------------------------------
712
713class GearSpeedLimitChecker(PatientFaultChecker):
714 """Check if the gears not down at too high a speed."""
715 def isCondition(self, flight, aircraft, oldState, state):
716 """Check if the fault condition holds."""
[197]717 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
[11]718
719 def logFault(self, flight, aircraft, logger, oldState, state):
720 """Log the fault."""
[30]721 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
722 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
723 5)
[11]724
725#---------------------------------------------------------------------------------------
726
727class GLoadChecker(SimpleFaultChecker):
728 """Check if the G-load does not exceed 2 except during flare."""
729 def isCondition(self, flight, aircraft, oldState, state):
730 """Check if the fault condition holds."""
731 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
732 state.radioAltitude>=50)
733
734 def logFault(self, flight, aircraft, logger, oldState, state):
735 """Log the fault."""
[30]736 flight.handleFault(GLoadChecker, state.timestamp,
737 "G-load was %.2f" % (state.gLoad,),
738 10)
[11]739
740#---------------------------------------------------------------------------------------
741
742class LandingLightsChecker(PatientFaultChecker):
743 """Check if the landing lights are used properly."""
744 def getTimeout(self, flight, aircraft, oldState, state):
745 """Get the timeout.
746
747 It is the default timeout except for landing and takeoff."""
748 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
749 const.STAGE_LANDING] else self._timeout
750
751 def isCondition(self, flight, aircraft, oldState, state):
752 """Check if the fault condition holds."""
753 return (flight.stage==const.STAGE_BOARDING and \
754 state.landingLightsOn and state.onTheGround) or \
755 (flight.stage==const.STAGE_TAKEOFF and \
756 not state.landingLightsOn and not state.onTheGround) or \
757 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
758 const.STAGE_DESCENT] and \
759 state.landingLightsOn and state.altitude>12500) or \
760 (flight.stage==const.STAGE_LANDING and \
761 not state.landingLightsOn and state.onTheGround) or \
762 (flight.stage==const.STAGE_PARKING and \
763 state.landingLightsOn and state.onTheGround)
764
765 def logFault(self, flight, aircraft, logger, oldState, state):
766 """Log the fault."""
767 score = 0 if flight.stage==const.STAGE_LANDING else 1
768 message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
[30]769 flight.handleFault(LandingLightsChecker, state.timestamp,
770 FaultChecker._appendDuring(flight, message),
771 score)
[11]772
773#---------------------------------------------------------------------------------------
774
775class WeightChecker(PatientFaultChecker):
776 """Base class for checkers that check that some limit is not exceeded."""
777 def __init__(self, name):
778 """Construct the checker."""
779 super(WeightChecker, self).__init__(timeout = 5.0)
780 self._name = name
781
782 def isCondition(self, flight, aircraft, oldState, state):
783 """Check if the fault condition holds."""
784 if flight.entranceExam:
785 return False
786
787 limit = self.getLimit(flight, aircraft, state)
788 if limit is not None:
[183]789 #if flight.options.compensation is not None:
790 # limit += flight.options.compensation
[13]791 return self.getWeight(state)>limit
[11]792
793 return False
794
795 def logFault(self, flight, aircraft, logger, oldState, state):
796 """Log the fault."""
[13]797 mname = "M" + self._name
[30]798 flight.handleNoGo(self.__class__, state.timestamp,
799 "%s exceeded: %s is %.0f kg" % \
800 (mname, self._name, self.getWeight(state)),
801 "%s NO GO" % (mname,))
[13]802
803 def getWeight(self, state):
804 """Get the weight that is interesting for us."""
805 return state.grossWeight
[11]806
807#---------------------------------------------------------------------------------------
808
809class MLWChecker(WeightChecker):
810 """Checks if the MLW is not exceeded on landing."""
811 def __init__(self):
812 """Construct the checker."""
[13]813 super(MLWChecker, self).__init__("LW")
[11]814
[13]815 def getLimit(self, flight, aircraft, state):
[11]816 """Get the limit if we are in the right state."""
817 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
[184]818 state.onTheGround and \
819 not flight.entranceExam else None
[11]820
821#---------------------------------------------------------------------------------------
822
823class MTOWChecker(WeightChecker):
824 """Checks if the MTOW is not exceeded on landing."""
825 def __init__(self):
826 """Construct the checker."""
[13]827 super(MTOWChecker, self).__init__("TOW")
[11]828
[13]829 def getLimit(self, flight, aircraft, state):
[11]830 """Get the limit if we are in the right state."""
[184]831 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
832 not flight.entranceExam else None
[11]833
834#---------------------------------------------------------------------------------------
835
836class MZFWChecker(WeightChecker):
[60]837 """Checks if the MZFW is not exceeded on landing."""
[11]838 def __init__(self):
839 """Construct the checker."""
[13]840 super(MZFWChecker, self).__init__("ZFW")
[11]841
[13]842 def getLimit(self, flight, aircraft, state):
[11]843 """Get the limit if we are in the right state."""
[184]844 return aircraft.mzfw if not flight.entranceExam else None
[11]845
[13]846 def getWeight(self, state):
847 """Get the weight that is interesting for us."""
848 return state.zfw
849
[11]850#---------------------------------------------------------------------------------------
851
852class NavLightsChecker(PatientFaultChecker):
853 """Check if the navigational lights are used properly."""
854 def isCondition(self, flight, aircraft, oldState, state):
855 """Check if the fault condition holds."""
856 return flight.stage!=const.STAGE_BOARDING and \
857 flight.stage!=const.STAGE_PARKING and \
858 not state.navLightsOn
859
860 def logFault(self, flight, aircraft, logger, oldState, state):
861 """Log the fault."""
[30]862 flight.handleFault(NavLightsChecker, state.timestamp,
863 FaultChecker._appendDuring(flight,
864 "Navigation lights were off"),
865 1)
[11]866
867#---------------------------------------------------------------------------------------
868
869class OverspeedChecker(PatientFaultChecker):
870 """Check if Vne has been exceeded."""
[197]871 def __init__(self, timeout = 5.0):
[11]872 """Construct the checker."""
[197]873 super(OverspeedChecker, self).__init__(timeout = timeout)
[11]874
875 def isCondition(self, flight, aircraft, oldState, state):
876 """Check if the fault condition holds."""
877 return state.overspeed
878
879 def logFault(self, flight, aircraft, logger, oldState, state):
880 """Log the fault."""
[30]881 flight.handleFault(OverspeedChecker, state.timestamp,
882 FaultChecker._appendDuring(flight, "Overspeed"),
883 20)
[11]884
885#---------------------------------------------------------------------------------------
886
887class PayloadChecker(SimpleFaultChecker):
888 """Check if the payload matches the specification."""
889 TOLERANCE=550
[60]890
891 @staticmethod
892 def isZFWFaulty(aircraftZFW, flightZFW):
893 """Check if the given aircraft's ZFW is outside of the limits."""
894 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
895 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
[11]896
897 def isCondition(self, flight, aircraft, oldState, state):
898 """Check if the fault condition holds."""
[192]899 return not flight.entranceExam and \
[184]900 flight.stage==const.STAGE_PUSHANDTAXI and \
[60]901 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
[11]902
903 def logFault(self, flight, aircraft, logger, oldState, state):
904 """Log the fault."""
[30]905 flight.handleNoGo(PayloadChecker, state.timestamp,
906 "ZFW difference is more than %d kgs" % \
907 (PayloadChecker.TOLERANCE,),
908 "ZFW NO GO")
[11]909
910#---------------------------------------------------------------------------------------
911
912class PitotChecker(PatientFaultChecker):
913 """Check if pitot heat is on."""
914 def __init__(self):
915 """Construct the checker."""
916 super(PitotChecker, self).__init__(timeout = 3.0)
917
918 def isCondition(self, flight, aircraft, oldState, state):
919 """Check if the fault condition holds."""
[13]920 return state.groundSpeed>80 and not state.pitotHeatOn
[11]921
922 def logFault(self, flight, aircraft, logger, oldState, state):
923 """Log the fault."""
924 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
925 const.STAGE_CRUISE, const.STAGE_DESCENT,
926 const.STAGE_LANDING] else 0
[30]927 flight.handleFault(PitotChecker, state.timestamp,
928 FaultChecker._appendDuring(flight, "Pitot heat was off"),
929 score)
[11]930
931#---------------------------------------------------------------------------------------
932
933class ReverserChecker(SimpleFaultChecker):
934 """Check if the reverser is not used below 60 knots."""
935 def isCondition(self, flight, aircraft, oldState, state):
936 """Check if the fault condition holds."""
937 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
938 const.STAGE_TAXIAFTERLAND] and \
[25]939 state.groundSpeed<60 and max(state.reverser)
[11]940
941 def logFault(self, flight, aircraft, logger, oldState, state):
942 """Log the fault."""
[241]943 message = "Reverser used below %.0f %s" % \
944 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
[30]945 flight.handleFault(ReverserChecker, state.timestamp,
[241]946 FaultChecker._appendDuring(flight, message),
947 15)
[11]948
949#---------------------------------------------------------------------------------------
950
951class SpeedChecker(SimpleFaultChecker):
952 """Check if the speed is in the prescribed limits."""
953 def isCondition(self, flight, aircraft, oldState, state):
954 """Check if the fault condition holds."""
955 return flight.stage in [const.STAGE_PUSHANDTAXI,
956 const.STAGE_TAXIAFTERLAND] and \
957 state.groundSpeed>50
958
959 def logFault(self, flight, aircraft, logger, oldState, state):
960 """Log the fault."""
[241]961 message = "Taxi speed over %.0f %s" % \
962 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
[30]963 flight.handleFault(SpeedChecker, state.timestamp,
[241]964 FaultChecker._appendDuring(flight, message),
[30]965 FaultChecker._getLinearScore(50, 80, 10, 15,
966 state.groundSpeed))
[11]967
968#---------------------------------------------------------------------------------------
969
970class StallChecker(PatientFaultChecker):
971 """Check if stall occured."""
972 def isCondition(self, flight, aircraft, oldState, state):
973 """Check if the fault condition holds."""
974 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
975 const.STAGE_CRUISE, const.STAGE_DESCENT,
976 const.STAGE_LANDING] and state.stalled
977
978 def logFault(self, flight, aircraft, logger, oldState, state):
979 """Log the fault."""
980 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
981 const.STAGE_LANDING] else 30
[30]982 flight.handleFault(StallChecker, state.timestamp,
983 FaultChecker._appendDuring(flight, "Stalled"),
984 score)
[11]985
986#---------------------------------------------------------------------------------------
987
988class StrobeLightsChecker(PatientFaultChecker):
989 """Check if the strobe lights are used properly."""
990 def isCondition(self, flight, aircraft, oldState, state):
991 """Check if the fault condition holds."""
[211]992 return (flight.stage==const.STAGE_BOARDING and \
993 state.strobeLightsOn and state.onTheGround) or \
994 (flight.stage==const.STAGE_TAKEOFF and \
995 not state.strobeLightsOn and not state.gearsDown) or \
996 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
997 const.STAGE_DESCENT] and \
998 not state.strobeLightsOn and not state.onTheGround) or \
999 (flight.stage==const.STAGE_PARKING and \
1000 state.strobeLightsOn and state.onTheGround)
[11]1001
1002 def logFault(self, flight, aircraft, logger, oldState, state):
1003 """Log the fault."""
1004 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
[30]1005 flight.handleFault(StrobeLightsChecker, state.timestamp,
1006 FaultChecker._appendDuring(flight, message),
1007 1)
[11]1008
1009#---------------------------------------------------------------------------------------
1010
1011class ThrustChecker(SimpleFaultChecker):
1012 """Check if the thrust setting is not too high during takeoff.
1013
1014 FIXME: is this really so general, for all aircraft?"""
1015 def isCondition(self, flight, aircraft, oldState, state):
1016 """Check if the fault condition holds."""
[263]1017 return flight.stage==const.STAGE_TAKEOFF and \
1018 state.n1 is not None and max(state.n1)>97
[11]1019
1020 def logFault(self, flight, aircraft, logger, oldState, state):
1021 """Log the fault."""
[30]1022 flight.handleFault(ThrustChecker, state.timestamp,
1023 FaultChecker._appendDuring(flight,
1024 "Thrust setting was too high (>97%)"),
1025 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
[11]1026
1027#---------------------------------------------------------------------------------------
1028
1029class VSChecker(SimpleFaultChecker):
1030 """Check if the vertical speed is not too low at certain altitudes"""
1031 BELOW10000 = -5000
1032 BELOW5000 = -2500
1033 BELOW2500 = -1500
1034 BELOW500 = -1000
1035 TOLERANCE = 1.2
1036
1037 def isCondition(self, flight, aircraft, oldState, state):
1038 """Check if the fault condition holds."""
[197]1039 vs = state.smoothedVS
[11]1040 altitude = state.altitude
1041 return vs < -8000 or vs > 8000 or \
1042 (altitude<500 and vs < (VSChecker.BELOW500 *
[13]1043 VSChecker.TOLERANCE)) or \
[11]1044 (altitude<2500 and vs < (VSChecker.BELOW2500 *
[13]1045 VSChecker.TOLERANCE)) or \
[11]1046 (altitude<5000 and vs < (VSChecker.BELOW5000 *
[13]1047 VSChecker.TOLERANCE)) or \
[11]1048 (altitude<10000 and vs < (VSChecker.BELOW10000 *
[13]1049 VSChecker.TOLERANCE))
[11]1050
1051 def logFault(self, flight, aircraft, logger, oldState, state):
1052 """Log the fault."""
[197]1053 vs = state.smoothedVS
[11]1054
1055 message = "Vertical speed was %.0f feet/min" % (vs,)
1056 if vs>-8000 and vs<8000:
1057 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1058
1059 score = 10 if vs<-8000 or vs>8000 else 0
1060
[30]1061 flight.handleFault(VSChecker, state.timestamp,
1062 FaultChecker._appendDuring(flight, message),
1063 score)
[11]1064
1065#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.