source: src/mlx/checks.py@ 212:fab302d5b7f6

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

Reorganized the lights loggin and checks a bit

File size: 43.2 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 return (flight.stage!=const.STAGE_PARKING or \
550 not flight.config.usingFS2Crew) and \
551 not state.antiCollisionLightsOn and \
552 self.isEngineCondition(state)
553
554 def logFault(self, flight, aircraft, logger, oldState, state):
555 """Log the fault."""
556 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
557 FaultChecker._appendDuring(flight,
558 "Anti-collision lights were off"),
559 1)
560
561 def isEngineCondition(self, state):
562 """Determine if the engines are in such a state that the lights should
563 be on."""
564 return max(state.n1)>5
565
566#---------------------------------------------------------------------------------------
567
568class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
569 """Check for the anti-collision light for Tuplev planes."""
570
571 def isEngineCondition(self, state):
572 """Determine if the engines are in such a state that the lights should
573 be on."""
574 return max(state.n1[1:])>5
575
576
577#---------------------------------------------------------------------------------------
578
579class BankChecker(SimpleFaultChecker):
580 """Check for the anti-collision light being off at high N1 values."""
581 def isCondition(self, flight, aircraft, oldState, state):
582 """Check if the fault condition holds."""
583 if flight.stage==const.STAGE_CRUISE:
584 bankLimit = 30
585 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
586 const.STAGE_DESCENT, const.STAGE_LANDING]:
587 bankLimit = 35
588 else:
589 return False
590
591 return state.bank>bankLimit or state.bank<-bankLimit
592
593 def logFault(self, flight, aircraft, logger, oldState, state):
594 """Log the fault."""
595 flight.handleFault(BankChecker, state.timestamp,
596 FaultChecker._appendDuring(flight, "Bank too steep"),
597 2)
598
599#---------------------------------------------------------------------------------------
600
601class FlapsRetractChecker(SimpleFaultChecker):
602 """Check if the flaps are not retracted too early."""
603 def __init__(self):
604 """Construct the flaps checker."""
605 self._timeStart = None
606
607 def isCondition(self, flight, aircraft, oldState, state):
608 """Check if the fault condition holds.
609
610 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
611 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
612 (flight.stage==const.STAGE_LANDING and state.onTheGround):
613 if self._timeStart is None:
614 self._timeStart = state.timestamp
615
616 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
617 return True
618 else:
619 self._timeStart = None
620 return False
621
622 def logFault(self, flight, aircraft, logger, oldState, state):
623 """Log the fault."""
624 flight.handleFault(FlapsRetractChecker, state.timestamp,
625 FaultChecker._appendDuring(flight, "Flaps retracted"),
626 20)
627
628#---------------------------------------------------------------------------------------
629
630class FlapsSpeedLimitChecker(SimpleFaultChecker):
631 """Check if the flaps are extended only at the right speeds."""
632 def isCondition(self, flight, aircraft, oldState, state):
633 """Check if the fault condition holds."""
634 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
635 return speedLimit is not None and state.smoothedIAS>speedLimit
636
637 def logFault(self, flight, aircraft, logger, oldState, state):
638 """Log the fault."""
639 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
640 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
641 5)
642
643#---------------------------------------------------------------------------------------
644
645class GearsDownChecker(SimpleFaultChecker):
646 """Check if the gears are down at low altitudes."""
647 def isCondition(self, flight, aircraft, oldState, state):
648 """Check if the fault condition holds."""
649 return state.radioAltitude<10 and not state.gearsDown and \
650 flight.stage!=const.STAGE_TAKEOFF
651
652 def logFault(self, flight, aircraft, logger, oldState, state):
653 """Log the fault."""
654 flight.handleNoGo(GearsDownChecker, state.timestamp,
655 "Gears not down at %.0f feet radio altitude" % \
656 (state.radioAltitude,),
657 "GEAR DOWN NO GO")
658
659#---------------------------------------------------------------------------------------
660
661class GearSpeedLimitChecker(PatientFaultChecker):
662 """Check if the gears not down at too high a speed."""
663 def isCondition(self, flight, aircraft, oldState, state):
664 """Check if the fault condition holds."""
665 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
666
667 def logFault(self, flight, aircraft, logger, oldState, state):
668 """Log the fault."""
669 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
670 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
671 5)
672
673#---------------------------------------------------------------------------------------
674
675class GLoadChecker(SimpleFaultChecker):
676 """Check if the G-load does not exceed 2 except during flare."""
677 def isCondition(self, flight, aircraft, oldState, state):
678 """Check if the fault condition holds."""
679 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
680 state.radioAltitude>=50)
681
682 def logFault(self, flight, aircraft, logger, oldState, state):
683 """Log the fault."""
684 flight.handleFault(GLoadChecker, state.timestamp,
685 "G-load was %.2f" % (state.gLoad,),
686 10)
687
688#---------------------------------------------------------------------------------------
689
690class LandingLightsChecker(PatientFaultChecker):
691 """Check if the landing lights are used properly."""
692 def getTimeout(self, flight, aircraft, oldState, state):
693 """Get the timeout.
694
695 It is the default timeout except for landing and takeoff."""
696 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
697 const.STAGE_LANDING] else self._timeout
698
699 def isCondition(self, flight, aircraft, oldState, state):
700 """Check if the fault condition holds."""
701 return (flight.stage==const.STAGE_BOARDING and \
702 state.landingLightsOn and state.onTheGround) or \
703 (flight.stage==const.STAGE_TAKEOFF and \
704 not state.landingLightsOn and not state.onTheGround) or \
705 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
706 const.STAGE_DESCENT] and \
707 state.landingLightsOn and state.altitude>12500) or \
708 (flight.stage==const.STAGE_LANDING and \
709 not state.landingLightsOn and state.onTheGround) or \
710 (flight.stage==const.STAGE_PARKING and \
711 state.landingLightsOn and state.onTheGround)
712
713 def logFault(self, flight, aircraft, logger, oldState, state):
714 """Log the fault."""
715 score = 0 if flight.stage==const.STAGE_LANDING else 1
716 message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
717 flight.handleFault(LandingLightsChecker, state.timestamp,
718 FaultChecker._appendDuring(flight, message),
719 score)
720
721#---------------------------------------------------------------------------------------
722
723class WeightChecker(PatientFaultChecker):
724 """Base class for checkers that check that some limit is not exceeded."""
725 def __init__(self, name):
726 """Construct the checker."""
727 super(WeightChecker, self).__init__(timeout = 5.0)
728 self._name = name
729
730 def isCondition(self, flight, aircraft, oldState, state):
731 """Check if the fault condition holds."""
732 if flight.entranceExam:
733 return False
734
735 limit = self.getLimit(flight, aircraft, state)
736 if limit is not None:
737 #if flight.options.compensation is not None:
738 # limit += flight.options.compensation
739 return self.getWeight(state)>limit
740
741 return False
742
743 def logFault(self, flight, aircraft, logger, oldState, state):
744 """Log the fault."""
745 mname = "M" + self._name
746 flight.handleNoGo(self.__class__, state.timestamp,
747 "%s exceeded: %s is %.0f kg" % \
748 (mname, self._name, self.getWeight(state)),
749 "%s NO GO" % (mname,))
750
751 def getWeight(self, state):
752 """Get the weight that is interesting for us."""
753 return state.grossWeight
754
755#---------------------------------------------------------------------------------------
756
757class MLWChecker(WeightChecker):
758 """Checks if the MLW is not exceeded on landing."""
759 def __init__(self):
760 """Construct the checker."""
761 super(MLWChecker, self).__init__("LW")
762
763 def getLimit(self, flight, aircraft, state):
764 """Get the limit if we are in the right state."""
765 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
766 state.onTheGround and \
767 not flight.entranceExam else None
768
769#---------------------------------------------------------------------------------------
770
771class MTOWChecker(WeightChecker):
772 """Checks if the MTOW is not exceeded on landing."""
773 def __init__(self):
774 """Construct the checker."""
775 super(MTOWChecker, self).__init__("TOW")
776
777 def getLimit(self, flight, aircraft, state):
778 """Get the limit if we are in the right state."""
779 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
780 not flight.entranceExam else None
781
782#---------------------------------------------------------------------------------------
783
784class MZFWChecker(WeightChecker):
785 """Checks if the MZFW is not exceeded on landing."""
786 def __init__(self):
787 """Construct the checker."""
788 super(MZFWChecker, self).__init__("ZFW")
789
790 def getLimit(self, flight, aircraft, state):
791 """Get the limit if we are in the right state."""
792 return aircraft.mzfw if not flight.entranceExam else None
793
794 def getWeight(self, state):
795 """Get the weight that is interesting for us."""
796 return state.zfw
797
798#---------------------------------------------------------------------------------------
799
800class NavLightsChecker(PatientFaultChecker):
801 """Check if the navigational lights are used properly."""
802 def isCondition(self, flight, aircraft, oldState, state):
803 """Check if the fault condition holds."""
804 return flight.stage!=const.STAGE_BOARDING and \
805 flight.stage!=const.STAGE_PARKING and \
806 not state.navLightsOn
807
808 def logFault(self, flight, aircraft, logger, oldState, state):
809 """Log the fault."""
810 flight.handleFault(NavLightsChecker, state.timestamp,
811 FaultChecker._appendDuring(flight,
812 "Navigation lights were off"),
813 1)
814
815#---------------------------------------------------------------------------------------
816
817class OverspeedChecker(PatientFaultChecker):
818 """Check if Vne has been exceeded."""
819 def __init__(self, timeout = 5.0):
820 """Construct the checker."""
821 super(OverspeedChecker, self).__init__(timeout = timeout)
822
823 def isCondition(self, flight, aircraft, oldState, state):
824 """Check if the fault condition holds."""
825 return state.overspeed
826
827 def logFault(self, flight, aircraft, logger, oldState, state):
828 """Log the fault."""
829 flight.handleFault(OverspeedChecker, state.timestamp,
830 FaultChecker._appendDuring(flight, "Overspeed"),
831 20)
832
833#---------------------------------------------------------------------------------------
834
835class PayloadChecker(SimpleFaultChecker):
836 """Check if the payload matches the specification."""
837 TOLERANCE=550
838
839 @staticmethod
840 def isZFWFaulty(aircraftZFW, flightZFW):
841 """Check if the given aircraft's ZFW is outside of the limits."""
842 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
843 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
844
845 def isCondition(self, flight, aircraft, oldState, state):
846 """Check if the fault condition holds."""
847 return not flight.entranceExam and \
848 flight.stage==const.STAGE_PUSHANDTAXI and \
849 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
850
851 def logFault(self, flight, aircraft, logger, oldState, state):
852 """Log the fault."""
853 flight.handleNoGo(PayloadChecker, state.timestamp,
854 "ZFW difference is more than %d kgs" % \
855 (PayloadChecker.TOLERANCE,),
856 "ZFW NO GO")
857
858#---------------------------------------------------------------------------------------
859
860class PitotChecker(PatientFaultChecker):
861 """Check if pitot heat is on."""
862 def __init__(self):
863 """Construct the checker."""
864 super(PitotChecker, self).__init__(timeout = 3.0)
865
866 def isCondition(self, flight, aircraft, oldState, state):
867 """Check if the fault condition holds."""
868 return state.groundSpeed>80 and not state.pitotHeatOn
869
870 def logFault(self, flight, aircraft, logger, oldState, state):
871 """Log the fault."""
872 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
873 const.STAGE_CRUISE, const.STAGE_DESCENT,
874 const.STAGE_LANDING] else 0
875 flight.handleFault(PitotChecker, state.timestamp,
876 FaultChecker._appendDuring(flight, "Pitot heat was off"),
877 score)
878
879#---------------------------------------------------------------------------------------
880
881class ReverserChecker(SimpleFaultChecker):
882 """Check if the reverser is not used below 60 knots."""
883 def isCondition(self, flight, aircraft, oldState, state):
884 """Check if the fault condition holds."""
885 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
886 const.STAGE_TAXIAFTERLAND] and \
887 state.groundSpeed<60 and max(state.reverser)
888
889 def logFault(self, flight, aircraft, logger, oldState, state):
890 """Log the fault."""
891 flight.handleFault(ReverserChecker, state.timestamp,
892 FaultChecker._appendDuring(flight,
893 "Reverser used below 60 knots"),
894 15)
895
896#---------------------------------------------------------------------------------------
897
898class SpeedChecker(SimpleFaultChecker):
899 """Check if the speed is in the prescribed limits."""
900 def isCondition(self, flight, aircraft, oldState, state):
901 """Check if the fault condition holds."""
902 return flight.stage in [const.STAGE_PUSHANDTAXI,
903 const.STAGE_TAXIAFTERLAND] and \
904 state.groundSpeed>50
905
906 def logFault(self, flight, aircraft, logger, oldState, state):
907 """Log the fault."""
908 flight.handleFault(SpeedChecker, state.timestamp,
909 FaultChecker._appendDuring(flight,
910 "Taxi speed over 50 knots"),
911 FaultChecker._getLinearScore(50, 80, 10, 15,
912 state.groundSpeed))
913
914#---------------------------------------------------------------------------------------
915
916class StallChecker(PatientFaultChecker):
917 """Check if stall occured."""
918 def isCondition(self, flight, aircraft, oldState, state):
919 """Check if the fault condition holds."""
920 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
921 const.STAGE_CRUISE, const.STAGE_DESCENT,
922 const.STAGE_LANDING] and state.stalled
923
924 def logFault(self, flight, aircraft, logger, oldState, state):
925 """Log the fault."""
926 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
927 const.STAGE_LANDING] else 30
928 flight.handleFault(StallChecker, state.timestamp,
929 FaultChecker._appendDuring(flight, "Stalled"),
930 score)
931
932#---------------------------------------------------------------------------------------
933
934class StrobeLightsChecker(PatientFaultChecker):
935 """Check if the strobe lights are used properly."""
936 def isCondition(self, flight, aircraft, oldState, state):
937 """Check if the fault condition holds."""
938 return (flight.stage==const.STAGE_BOARDING and \
939 state.strobeLightsOn and state.onTheGround) or \
940 (flight.stage==const.STAGE_TAKEOFF and \
941 not state.strobeLightsOn and not state.gearsDown) or \
942 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
943 const.STAGE_DESCENT] and \
944 not state.strobeLightsOn and not state.onTheGround) or \
945 (flight.stage==const.STAGE_PARKING and \
946 state.strobeLightsOn and state.onTheGround)
947
948 def logFault(self, flight, aircraft, logger, oldState, state):
949 """Log the fault."""
950 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
951 flight.handleFault(StrobeLightsChecker, state.timestamp,
952 FaultChecker._appendDuring(flight, message),
953 1)
954
955#---------------------------------------------------------------------------------------
956
957class ThrustChecker(SimpleFaultChecker):
958 """Check if the thrust setting is not too high during takeoff.
959
960 FIXME: is this really so general, for all aircraft?"""
961 def isCondition(self, flight, aircraft, oldState, state):
962 """Check if the fault condition holds."""
963 return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
964
965 def logFault(self, flight, aircraft, logger, oldState, state):
966 """Log the fault."""
967 print state.n1
968 flight.handleFault(ThrustChecker, state.timestamp,
969 FaultChecker._appendDuring(flight,
970 "Thrust setting was too high (>97%)"),
971 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
972
973#---------------------------------------------------------------------------------------
974
975class VSChecker(SimpleFaultChecker):
976 """Check if the vertical speed is not too low at certain altitudes"""
977 BELOW10000 = -5000
978 BELOW5000 = -2500
979 BELOW2500 = -1500
980 BELOW500 = -1000
981 TOLERANCE = 1.2
982
983 def isCondition(self, flight, aircraft, oldState, state):
984 """Check if the fault condition holds."""
985 vs = state.smoothedVS
986 altitude = state.altitude
987 return vs < -8000 or vs > 8000 or \
988 (altitude<500 and vs < (VSChecker.BELOW500 *
989 VSChecker.TOLERANCE)) or \
990 (altitude<2500 and vs < (VSChecker.BELOW2500 *
991 VSChecker.TOLERANCE)) or \
992 (altitude<5000 and vs < (VSChecker.BELOW5000 *
993 VSChecker.TOLERANCE)) or \
994 (altitude<10000 and vs < (VSChecker.BELOW10000 *
995 VSChecker.TOLERANCE))
996
997 def logFault(self, flight, aircraft, logger, oldState, state):
998 """Log the fault."""
999 vs = state.smoothedVS
1000
1001 message = "Vertical speed was %.0f feet/min" % (vs,)
1002 if vs>-8000 and vs<8000:
1003 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1004
1005 score = 10 if vs<-8000 or vs>8000 else 0
1006
1007 flight.handleFault(VSChecker, state.timestamp,
1008 FaultChecker._appendDuring(flight, message),
1009 score)
1010
1011#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.