source: src/checks.py@ 22:bedd79d8e854

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

Delayed loggers now log sooner after the last change and they preserve the entire state at the last change.

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