source: src/mlx/checks.py@ 317:26cd9b16bf92

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

Added the logging of the ADF frequencies

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