source: src/mlx/checks.py@ 209:3172e5cab96e

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

The position of the gear control lever is logged

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