source: src/mlx/checks.py@ 205:8b389911b3d9

Last change on this file since 205:8b389911b3d9 was 197:93f89e9049be, checked in by István Váradi <ivaradi@…>, 13 years ago

Added support for smoothed IAS and VS values

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