source: src/mlx/checks.py@ 277:33fbad0ca891

Last change on this file since 277:33fbad0ca891 was 273:066f7271e849, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the playback of approach callouts

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