source: src/mlx/checks.py@ 319:e9faf50244db

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

Added delay to the ADF settings

File size: 47.0 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 ADF1Logger(GenericStateChangeLogger):
430 """Logger for the ADF1 radio setting."""
431 def __init__(self):
432 """Construct the logger."""
433 super(ADF1Logger, self).__init__("adf1", "ADF1 frequency: %s kHz",
434 minDelay = 3.0, maxDelay = 10.0)
435
436#---------------------------------------------------------------------------------------
437
438class ADF2Logger(GenericStateChangeLogger):
439 """Logger for the ADF2 radio setting."""
440 def __init__(self):
441 """Construct the logger."""
442 super(ADF2Logger, self).__init__("adf2", "ADF2 frequency: %s kHz",
443 minDelay = 3.0, maxDelay = 10.0)
444
445#---------------------------------------------------------------------------------------
446
447class SquawkLogger(GenericStateChangeLogger):
448 """Logger for the squawk setting."""
449 def __init__(self):
450 """Construct the logger."""
451 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
452 minDelay = 3.0, maxDelay = 10.0)
453
454#---------------------------------------------------------------------------------------
455
456class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
457 """Base class for the loggers of the various lights."""
458 def __init__(self, attrName, template):
459 """Construct the logger."""
460 StateChangeLogger.__init__(self)
461 SingleValueMixin.__init__(self, attrName)
462
463 self._template = template
464
465 def _getMessage(self, flight, state):
466 """Get the message from the given state."""
467 return self._template % ("ON" if self._getValue(state) else "OFF")
468
469#---------------------------------------------------------------------------------------
470
471class AnticollisionLightsLogger(LightsLogger):
472 """Logger for the anti-collision lights."""
473 def __init__(self):
474 LightsLogger.__init__(self, "antiCollisionLightsOn",
475 "Anti-collision lights: %s")
476
477#---------------------------------------------------------------------------------------
478
479class LandingLightsLogger(LightsLogger):
480 """Logger for the landing lights."""
481 def __init__(self):
482 LightsLogger.__init__(self, "landingLightsOn",
483 "Landing lights: %s")
484
485#---------------------------------------------------------------------------------------
486
487class StrobeLightsLogger(LightsLogger):
488 """Logger for the strobe lights."""
489 def __init__(self):
490 LightsLogger.__init__(self, "strobeLightsOn",
491 "Strobe lights: %s")
492
493#---------------------------------------------------------------------------------------
494
495class NavLightsLogger(LightsLogger):
496 """Logger for the navigational lights."""
497 def __init__(self):
498 LightsLogger.__init__(self, "navLightsOn",
499 "Navigational lights: %s")
500
501#---------------------------------------------------------------------------------------
502
503class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
504 """Logger for the flaps setting."""
505 def __init__(self):
506 """Construct the logger."""
507 StateChangeLogger.__init__(self, logInitial = True)
508 SingleValueMixin.__init__(self, "flapsSet")
509
510 def _getMessage(self, flight, state):
511 """Get the message to log on a change."""
512 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
513 return "Flaps set to %.0f at %.0f %s" % \
514 (state.flapsSet, flight.speedFromKnots(speed),
515 flight.getEnglishSpeedUnit())
516
517#---------------------------------------------------------------------------------------
518
519class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
520 """Logger for the gears state."""
521 def __init__(self):
522 """Construct the logger."""
523 StateChangeLogger.__init__(self, logInitial = True)
524 SingleValueMixin.__init__(self, "gearControlDown")
525
526 def _getMessage(self, flight, state):
527 """Get the message to log on a change."""
528 return "Gears SET to %s at %.0f %s, %.0f feet" % \
529 ("DOWN" if state.gearControlDown else "UP",
530 flight.speedFromKnots(state.ias),
531 flight.getEnglishSpeedUnit(), state.altitude)
532
533#---------------------------------------------------------------------------------------
534
535class FaultChecker(StateChecker):
536 """Base class for checkers that look for faults."""
537 @staticmethod
538 def _appendDuring(flight, message):
539 """Append a 'during XXX' test to the given message, depending on the
540 flight stage."""
541 stageStr = const.stage2string(flight.stage)
542 return message if stageStr is None \
543 else (message + " during " + stageStr.upper())
544
545 @staticmethod
546 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
547 value):
548 """Get the score for a faulty value where the score is calculated
549 linearly within a certain range."""
550 if value<minFaultValue:
551 return 0
552 elif value>maxFaultValue:
553 return maxScore
554 else:
555 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
556 (maxFaultValue - minFaultValue)
557
558#---------------------------------------------------------------------------------------
559
560class SimpleFaultChecker(FaultChecker):
561 """Base class for fault checkers that check for a single occurence of a
562 faulty condition.
563
564 Child classes should implement the following functions:
565 - isCondition(self, flight, aircraft, oldState, state): should return whether the
566 condition holds
567 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
568 via the logger."""
569 def check(self, flight, aircraft, logger, oldState, state):
570 """Perform the check."""
571 if self.isCondition(flight, aircraft, oldState, state):
572 self.logFault(flight, aircraft, logger, oldState, state)
573
574#---------------------------------------------------------------------------------------
575
576class PatientFaultChecker(FaultChecker):
577 """A fault checker that does not decides on a fault when the condition
578 arises immediately, but can wait some time.
579
580 Child classes should implement the following functions:
581 - isCondition(self, flight, aircraft, oldState, state): should return whether the
582 condition holds
583 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
584 via the logger
585 """
586 def __init__(self, timeout = 2.0):
587 """Construct the fault checker with the given timeout."""
588 self._timeout = timeout
589 self._faultStarted = None
590
591 def getTimeout(self, flight, aircraft, oldState, state):
592 """Get the timeout.
593
594 This default implementation returns the timeout given in the
595 constructor, but child classes might want to enforce a different
596 policy."""
597 return self._timeout
598
599 def check(self, flight, aircraft, logger, oldState, state):
600 """Perform the check."""
601 if self.isCondition(flight, aircraft, oldState, state):
602 if self._faultStarted is None:
603 self._faultStarted = state.timestamp
604 timeout = self.getTimeout(flight, aircraft, oldState, state)
605 if state.timestamp>=(self._faultStarted + timeout):
606 self.logFault(flight, aircraft, logger, oldState, state)
607 self._faultStarted = state.timestamp
608 else:
609 self._faultStarted = None
610
611#---------------------------------------------------------------------------------------
612
613class AntiCollisionLightsChecker(PatientFaultChecker):
614 """Check for the anti-collision light being off at high N1 values."""
615 def isCondition(self, flight, aircraft, oldState, state):
616 """Check if the fault condition holds."""
617 return (flight.stage!=const.STAGE_PARKING or \
618 not flight.config.usingFS2Crew) and \
619 not state.antiCollisionLightsOn and \
620 self.isEngineCondition(state)
621
622 def logFault(self, flight, aircraft, logger, oldState, state):
623 """Log the fault."""
624 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
625 FaultChecker._appendDuring(flight,
626 "Anti-collision lights were off"),
627 1)
628
629 def isEngineCondition(self, state):
630 """Determine if the engines are in such a state that the lights should
631 be on."""
632 if state.n1 is not None:
633 return max(state.n1)>5
634 elif state.rpm is not None:
635 return max(state.rpm)>0
636 else:
637 return False
638
639#---------------------------------------------------------------------------------------
640
641class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
642 """Check for the anti-collision light for Tuplev planes."""
643 def isCondition(self, flight, aircraft, oldState, state):
644 """Check if the fault condition holds."""
645 numEnginesRunning = 0
646 for n1 in state.n1:
647 if n1>5: numEnginesRunning += 1
648
649 if flight.stage==const.STAGE_PARKING:
650 return numEnginesRunning<len(state.n1) \
651 and state.antiCollisionLightsOn
652 else:
653 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
654 numEnginesRunning<1 and state.antiCollisionLightsOn
655
656#---------------------------------------------------------------------------------------
657
658class BankChecker(SimpleFaultChecker):
659 """Check for the bank is within limits."""
660 def isCondition(self, flight, aircraft, oldState, state):
661 """Check if the fault condition holds."""
662 if flight.stage==const.STAGE_CRUISE:
663 bankLimit = 30
664 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
665 const.STAGE_DESCENT, const.STAGE_LANDING]:
666 bankLimit = 35
667 else:
668 return False
669
670 return state.bank>bankLimit or state.bank<-bankLimit
671
672 def logFault(self, flight, aircraft, logger, oldState, state):
673 """Log the fault."""
674 flight.handleFault(BankChecker, state.timestamp,
675 FaultChecker._appendDuring(flight, "Bank too steep"),
676 2)
677
678#---------------------------------------------------------------------------------------
679
680class FlapsRetractChecker(SimpleFaultChecker):
681 """Check if the flaps are not retracted too early."""
682 def __init__(self):
683 """Construct the flaps checker."""
684 self._timeStart = None
685
686 def isCondition(self, flight, aircraft, oldState, state):
687 """Check if the fault condition holds.
688
689 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
690 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
691 (flight.stage==const.STAGE_LANDING and state.onTheGround):
692 if self._timeStart is None:
693 self._timeStart = state.timestamp
694
695 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
696 return True
697 else:
698 self._timeStart = None
699 return False
700
701 def logFault(self, flight, aircraft, logger, oldState, state):
702 """Log the fault."""
703 flight.handleFault(FlapsRetractChecker, state.timestamp,
704 FaultChecker._appendDuring(flight, "Flaps retracted"),
705 20)
706
707#---------------------------------------------------------------------------------------
708
709class FlapsSpeedLimitChecker(SimpleFaultChecker):
710 """Check if the flaps are extended only at the right speeds."""
711 def isCondition(self, flight, aircraft, oldState, state):
712 """Check if the fault condition holds."""
713 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
714 return speedLimit is not None and state.smoothedIAS>speedLimit
715
716 def logFault(self, flight, aircraft, logger, oldState, state):
717 """Log the fault."""
718 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
719 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
720 5)
721
722#---------------------------------------------------------------------------------------
723
724class GearsDownChecker(SimpleFaultChecker):
725 """Check if the gears are down at low altitudes."""
726 def isCondition(self, flight, aircraft, oldState, state):
727 """Check if the fault condition holds."""
728 return state.radioAltitude<10 and not state.gearsDown and \
729 flight.stage!=const.STAGE_TAKEOFF
730
731 def logFault(self, flight, aircraft, logger, oldState, state):
732 """Log the fault."""
733 flight.handleNoGo(GearsDownChecker, state.timestamp,
734 "Gears not down at %.0f feet radio altitude" % \
735 (state.radioAltitude,),
736 "GEAR DOWN NO GO")
737
738#---------------------------------------------------------------------------------------
739
740class GearSpeedLimitChecker(PatientFaultChecker):
741 """Check if the gears not down at too high a speed."""
742 def isCondition(self, flight, aircraft, oldState, state):
743 """Check if the fault condition holds."""
744 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
745
746 def logFault(self, flight, aircraft, logger, oldState, state):
747 """Log the fault."""
748 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
749 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
750 5)
751
752#---------------------------------------------------------------------------------------
753
754class GLoadChecker(SimpleFaultChecker):
755 """Check if the G-load does not exceed 2 except during flare."""
756 def isCondition(self, flight, aircraft, oldState, state):
757 """Check if the fault condition holds."""
758 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
759 state.radioAltitude>=50)
760
761 def logFault(self, flight, aircraft, logger, oldState, state):
762 """Log the fault."""
763 flight.handleFault(GLoadChecker, state.timestamp,
764 "G-load was %.2f" % (state.gLoad,),
765 10)
766
767#---------------------------------------------------------------------------------------
768
769class LandingLightsChecker(PatientFaultChecker):
770 """Check if the landing lights are used properly."""
771 def getTimeout(self, flight, aircraft, oldState, state):
772 """Get the timeout.
773
774 It is the default timeout except for landing and takeoff."""
775 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
776 const.STAGE_LANDING] else self._timeout
777
778 def isCondition(self, flight, aircraft, oldState, state):
779 """Check if the fault condition holds."""
780 return state.landingLightsOn is not None and \
781 ((flight.stage==const.STAGE_BOARDING and \
782 state.landingLightsOn and state.onTheGround) or \
783 (flight.stage==const.STAGE_TAKEOFF and \
784 not state.landingLightsOn and not state.onTheGround) or \
785 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
786 const.STAGE_DESCENT] and \
787 state.landingLightsOn and state.altitude>12500) or \
788 (flight.stage==const.STAGE_LANDING and \
789 not state.landingLightsOn and state.onTheGround) or \
790 (flight.stage==const.STAGE_PARKING and \
791 state.landingLightsOn and state.onTheGround))
792
793 def logFault(self, flight, aircraft, logger, oldState, state):
794 """Log the fault."""
795 score = 0 if flight.stage==const.STAGE_LANDING else 1
796 message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
797 flight.handleFault(LandingLightsChecker, state.timestamp,
798 FaultChecker._appendDuring(flight, message),
799 score)
800
801#---------------------------------------------------------------------------------------
802
803class WeightChecker(PatientFaultChecker):
804 """Base class for checkers that check that some limit is not exceeded."""
805 def __init__(self, name):
806 """Construct the checker."""
807 super(WeightChecker, self).__init__(timeout = 5.0)
808 self._name = name
809
810 def isCondition(self, flight, aircraft, oldState, state):
811 """Check if the fault condition holds."""
812 if flight.entranceExam:
813 return False
814
815 limit = self.getLimit(flight, aircraft, state)
816 if limit is not None:
817 #if flight.options.compensation is not None:
818 # limit += flight.options.compensation
819 return self.getWeight(state)>limit
820
821 return False
822
823 def logFault(self, flight, aircraft, logger, oldState, state):
824 """Log the fault."""
825 mname = "M" + self._name
826 flight.handleNoGo(self.__class__, state.timestamp,
827 "%s exceeded: %s is %.0f kg" % \
828 (mname, self._name, self.getWeight(state)),
829 "%s NO GO" % (mname,))
830
831 def getWeight(self, state):
832 """Get the weight that is interesting for us."""
833 return state.grossWeight
834
835#---------------------------------------------------------------------------------------
836
837class MLWChecker(WeightChecker):
838 """Checks if the MLW is not exceeded on landing."""
839 def __init__(self):
840 """Construct the checker."""
841 super(MLWChecker, self).__init__("LW")
842
843 def getLimit(self, flight, aircraft, state):
844 """Get the limit if we are in the right state."""
845 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
846 state.onTheGround and \
847 not flight.entranceExam else None
848
849#---------------------------------------------------------------------------------------
850
851class MTOWChecker(WeightChecker):
852 """Checks if the MTOW is not exceeded on landing."""
853 def __init__(self):
854 """Construct the checker."""
855 super(MTOWChecker, self).__init__("TOW")
856
857 def getLimit(self, flight, aircraft, state):
858 """Get the limit if we are in the right state."""
859 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
860 not flight.entranceExam else None
861
862#---------------------------------------------------------------------------------------
863
864class MZFWChecker(WeightChecker):
865 """Checks if the MZFW is not exceeded on landing."""
866 def __init__(self):
867 """Construct the checker."""
868 super(MZFWChecker, self).__init__("ZFW")
869
870 def getLimit(self, flight, aircraft, state):
871 """Get the limit if we are in the right state."""
872 return aircraft.mzfw if not flight.entranceExam else None
873
874 def getWeight(self, state):
875 """Get the weight that is interesting for us."""
876 return state.zfw
877
878#---------------------------------------------------------------------------------------
879
880class NavLightsChecker(PatientFaultChecker):
881 """Check if the navigational lights are used properly."""
882 def isCondition(self, flight, aircraft, oldState, state):
883 """Check if the fault condition holds."""
884 return flight.stage!=const.STAGE_BOARDING and \
885 flight.stage!=const.STAGE_PARKING and \
886 not state.navLightsOn
887
888 def logFault(self, flight, aircraft, logger, oldState, state):
889 """Log the fault."""
890 flight.handleFault(NavLightsChecker, state.timestamp,
891 FaultChecker._appendDuring(flight,
892 "Navigation lights were off"),
893 1)
894
895#---------------------------------------------------------------------------------------
896
897class OverspeedChecker(PatientFaultChecker):
898 """Check if Vne has been exceeded."""
899 def __init__(self, timeout = 5.0):
900 """Construct the checker."""
901 super(OverspeedChecker, self).__init__(timeout = timeout)
902
903 def isCondition(self, flight, aircraft, oldState, state):
904 """Check if the fault condition holds."""
905 return state.overspeed
906
907 def logFault(self, flight, aircraft, logger, oldState, state):
908 """Log the fault."""
909 flight.handleFault(OverspeedChecker, state.timestamp,
910 FaultChecker._appendDuring(flight, "Overspeed"),
911 20)
912
913#---------------------------------------------------------------------------------------
914
915class PayloadChecker(SimpleFaultChecker):
916 """Check if the payload matches the specification."""
917 TOLERANCE=550
918
919 @staticmethod
920 def isZFWFaulty(aircraftZFW, flightZFW):
921 """Check if the given aircraft's ZFW is outside of the limits."""
922 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
923 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
924
925 def isCondition(self, flight, aircraft, oldState, state):
926 """Check if the fault condition holds."""
927 return not flight.entranceExam and \
928 flight.stage==const.STAGE_PUSHANDTAXI and \
929 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
930
931 def logFault(self, flight, aircraft, logger, oldState, state):
932 """Log the fault."""
933 flight.handleNoGo(PayloadChecker, state.timestamp,
934 "ZFW difference is more than %d kgs" % \
935 (PayloadChecker.TOLERANCE,),
936 "ZFW NO GO")
937
938#---------------------------------------------------------------------------------------
939
940class PitotChecker(PatientFaultChecker):
941 """Check if pitot heat is on."""
942 def __init__(self):
943 """Construct the checker."""
944 super(PitotChecker, self).__init__(timeout = 3.0)
945
946 def isCondition(self, flight, aircraft, oldState, state):
947 """Check if the fault condition holds."""
948 return state.groundSpeed>80 and not state.pitotHeatOn
949
950 def logFault(self, flight, aircraft, logger, oldState, state):
951 """Log the fault."""
952 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
953 const.STAGE_CRUISE, const.STAGE_DESCENT,
954 const.STAGE_LANDING] else 0
955 flight.handleFault(PitotChecker, state.timestamp,
956 FaultChecker._appendDuring(flight, "Pitot heat was off"),
957 score)
958
959#---------------------------------------------------------------------------------------
960
961class ReverserChecker(SimpleFaultChecker):
962 """Check if the reverser is not used below 60 knots."""
963 def isCondition(self, flight, aircraft, oldState, state):
964 """Check if the fault condition holds."""
965 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
966 const.STAGE_TAXIAFTERLAND] and \
967 state.groundSpeed<60 and max(state.reverser)
968
969 def logFault(self, flight, aircraft, logger, oldState, state):
970 """Log the fault."""
971 message = "Reverser used below %.0f %s" % \
972 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
973 flight.handleFault(ReverserChecker, state.timestamp,
974 FaultChecker._appendDuring(flight, message),
975 15)
976
977#---------------------------------------------------------------------------------------
978
979class SpeedChecker(SimpleFaultChecker):
980 """Check if the speed is in the prescribed limits."""
981 def isCondition(self, flight, aircraft, oldState, state):
982 """Check if the fault condition holds."""
983 return flight.stage in [const.STAGE_PUSHANDTAXI,
984 const.STAGE_TAXIAFTERLAND] and \
985 state.groundSpeed>50
986
987 def logFault(self, flight, aircraft, logger, oldState, state):
988 """Log the fault."""
989 message = "Taxi speed over %.0f %s" % \
990 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
991 flight.handleFault(SpeedChecker, state.timestamp,
992 FaultChecker._appendDuring(flight, message),
993 FaultChecker._getLinearScore(50, 80, 10, 15,
994 state.groundSpeed))
995
996#---------------------------------------------------------------------------------------
997
998class StallChecker(PatientFaultChecker):
999 """Check if stall occured."""
1000 def isCondition(self, flight, aircraft, oldState, state):
1001 """Check if the fault condition holds."""
1002 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1003 const.STAGE_CRUISE, const.STAGE_DESCENT,
1004 const.STAGE_LANDING] and state.stalled
1005
1006 def logFault(self, flight, aircraft, logger, oldState, state):
1007 """Log the fault."""
1008 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1009 const.STAGE_LANDING] else 30
1010 flight.handleFault(StallChecker, state.timestamp,
1011 FaultChecker._appendDuring(flight, "Stalled"),
1012 score)
1013
1014#---------------------------------------------------------------------------------------
1015
1016class StrobeLightsChecker(PatientFaultChecker):
1017 """Check if the strobe lights are used properly."""
1018 def isCondition(self, flight, aircraft, oldState, state):
1019 """Check if the fault condition holds."""
1020 return (flight.stage==const.STAGE_BOARDING and \
1021 state.strobeLightsOn and state.onTheGround) or \
1022 (flight.stage==const.STAGE_TAKEOFF and \
1023 not state.strobeLightsOn and not state.gearsDown) or \
1024 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1025 const.STAGE_DESCENT] and \
1026 not state.strobeLightsOn and not state.onTheGround) or \
1027 (flight.stage==const.STAGE_PARKING and \
1028 state.strobeLightsOn and state.onTheGround)
1029
1030 def logFault(self, flight, aircraft, logger, oldState, state):
1031 """Log the fault."""
1032 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1033 flight.handleFault(StrobeLightsChecker, state.timestamp,
1034 FaultChecker._appendDuring(flight, message),
1035 1)
1036
1037#---------------------------------------------------------------------------------------
1038
1039class ThrustChecker(SimpleFaultChecker):
1040 """Check if the thrust setting is not too high during takeoff.
1041
1042 FIXME: is this really so general, for all aircraft?"""
1043 def isCondition(self, flight, aircraft, oldState, state):
1044 """Check if the fault condition holds."""
1045 return flight.stage==const.STAGE_TAKEOFF and \
1046 state.n1 is not None and max(state.n1)>97
1047
1048 def logFault(self, flight, aircraft, logger, oldState, state):
1049 """Log the fault."""
1050 flight.handleFault(ThrustChecker, state.timestamp,
1051 FaultChecker._appendDuring(flight,
1052 "Thrust setting was too high (>97%)"),
1053 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
1054
1055#---------------------------------------------------------------------------------------
1056
1057class VSChecker(SimpleFaultChecker):
1058 """Check if the vertical speed is not too low at certain altitudes"""
1059 BELOW10000 = -5000
1060 BELOW5000 = -2500
1061 BELOW2500 = -1500
1062 BELOW500 = -1000
1063 TOLERANCE = 1.2
1064
1065 def isCondition(self, flight, aircraft, oldState, state):
1066 """Check if the fault condition holds."""
1067 vs = state.smoothedVS
1068 altitude = state.altitude
1069 return vs < -8000 or vs > 8000 or \
1070 (altitude<500 and vs < (VSChecker.BELOW500 *
1071 VSChecker.TOLERANCE)) or \
1072 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1073 VSChecker.TOLERANCE)) or \
1074 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1075 VSChecker.TOLERANCE)) or \
1076 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1077 VSChecker.TOLERANCE))
1078
1079 def logFault(self, flight, aircraft, logger, oldState, state):
1080 """Log the fault."""
1081 vs = state.smoothedVS
1082
1083 message = "Vertical speed was %.0f feet/min" % (vs,)
1084 if vs>-8000 and vs<8000:
1085 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1086
1087 score = 10 if vs<-8000 or vs>8000 else 0
1088
1089 flight.handleFault(VSChecker, state.timestamp,
1090 FaultChecker._appendDuring(flight, message),
1091 score)
1092
1093#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.