source: src/mlx/checks.py@ 298:24c67ec5cdca

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

Documented the non-GUI modules

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