source: src/checks.py@ 25:d3bf7580e98e

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

Eliminated the detection of multiple flare-touchdown sequences when bouncing after landing and some other small fixes.

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