source: src/mlx/checks.py@ 314:09f49b9eef64

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

Added support for the landing lights and the NAV frequencies being unreliable and the Digital Aviation F70 model

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