source: src/mlx/checks.py@ 172:565a6ea1f630

Last change on this file since 172:565a6ea1f630 was 170:7cda0cc74e19, checked in by István Váradi <ivaradi@…>, 13 years ago

The background sounds play back properly

File size: 42.7 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.options.fs2Crew) 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.ias>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.ias>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 else None
753
754#---------------------------------------------------------------------------------------
755
756class MTOWChecker(WeightChecker):
757 """Checks if the MTOW is not exceeded on landing."""
758 def __init__(self):
759 """Construct the checker."""
760 super(MTOWChecker, self).__init__("TOW")
761
762 def getLimit(self, flight, aircraft, state):
763 """Get the limit if we are in the right state."""
764 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
765
766#---------------------------------------------------------------------------------------
767
768class MZFWChecker(WeightChecker):
769 """Checks if the MZFW is not exceeded on landing."""
770 def __init__(self):
771 """Construct the checker."""
772 super(MZFWChecker, self).__init__("ZFW")
773
774 def getLimit(self, flight, aircraft, state):
775 """Get the limit if we are in the right state."""
776 return aircraft.mzfw
777
778 def getWeight(self, state):
779 """Get the weight that is interesting for us."""
780 return state.zfw
781
782#---------------------------------------------------------------------------------------
783
784class NavLightsChecker(PatientFaultChecker):
785 """Check if the navigational lights are used properly."""
786 def isCondition(self, flight, aircraft, oldState, state):
787 """Check if the fault condition holds."""
788 return flight.stage!=const.STAGE_BOARDING and \
789 flight.stage!=const.STAGE_PARKING and \
790 not state.navLightsOn
791
792 def logFault(self, flight, aircraft, logger, oldState, state):
793 """Log the fault."""
794 flight.handleFault(NavLightsChecker, state.timestamp,
795 FaultChecker._appendDuring(flight,
796 "Navigation lights were off"),
797 1)
798
799#---------------------------------------------------------------------------------------
800
801class OverspeedChecker(PatientFaultChecker):
802 """Check if Vne has been exceeded."""
803 def __init__(self):
804 """Construct the checker."""
805 super(OverspeedChecker, self).__init__(timeout = 5.0)
806
807 def isCondition(self, flight, aircraft, oldState, state):
808 """Check if the fault condition holds."""
809 return state.overspeed
810
811 def logFault(self, flight, aircraft, logger, oldState, state):
812 """Log the fault."""
813 flight.handleFault(OverspeedChecker, state.timestamp,
814 FaultChecker._appendDuring(flight, "Overspeed"),
815 20)
816
817#---------------------------------------------------------------------------------------
818
819class PayloadChecker(SimpleFaultChecker):
820 """Check if the payload matches the specification."""
821 TOLERANCE=550
822
823 @staticmethod
824 def isZFWFaulty(aircraftZFW, flightZFW):
825 """Check if the given aircraft's ZFW is outside of the limits."""
826 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
827 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
828
829 def isCondition(self, flight, aircraft, oldState, state):
830 """Check if the fault condition holds."""
831 return flight.stage==const.STAGE_PUSHANDTAXI and \
832 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
833
834 def logFault(self, flight, aircraft, logger, oldState, state):
835 """Log the fault."""
836 flight.handleNoGo(PayloadChecker, state.timestamp,
837 "ZFW difference is more than %d kgs" % \
838 (PayloadChecker.TOLERANCE,),
839 "ZFW NO GO")
840
841#---------------------------------------------------------------------------------------
842
843class PitotChecker(PatientFaultChecker):
844 """Check if pitot heat is on."""
845 def __init__(self):
846 """Construct the checker."""
847 super(PitotChecker, self).__init__(timeout = 3.0)
848
849 def isCondition(self, flight, aircraft, oldState, state):
850 """Check if the fault condition holds."""
851 return state.groundSpeed>80 and not state.pitotHeatOn
852
853 def logFault(self, flight, aircraft, logger, oldState, state):
854 """Log the fault."""
855 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
856 const.STAGE_CRUISE, const.STAGE_DESCENT,
857 const.STAGE_LANDING] else 0
858 flight.handleFault(PitotChecker, state.timestamp,
859 FaultChecker._appendDuring(flight, "Pitot heat was off"),
860 score)
861
862#---------------------------------------------------------------------------------------
863
864class ReverserChecker(SimpleFaultChecker):
865 """Check if the reverser is not used below 60 knots."""
866 def isCondition(self, flight, aircraft, oldState, state):
867 """Check if the fault condition holds."""
868 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
869 const.STAGE_TAXIAFTERLAND] and \
870 state.groundSpeed<60 and max(state.reverser)
871
872 def logFault(self, flight, aircraft, logger, oldState, state):
873 """Log the fault."""
874 flight.handleFault(ReverserChecker, state.timestamp,
875 FaultChecker._appendDuring(flight,
876 "Reverser used below 60 knots"),
877 15)
878
879#---------------------------------------------------------------------------------------
880
881class SpeedChecker(SimpleFaultChecker):
882 """Check if the speed is in the prescribed limits."""
883 def isCondition(self, flight, aircraft, oldState, state):
884 """Check if the fault condition holds."""
885 return flight.stage in [const.STAGE_PUSHANDTAXI,
886 const.STAGE_TAXIAFTERLAND] and \
887 state.groundSpeed>50
888
889 def logFault(self, flight, aircraft, logger, oldState, state):
890 """Log the fault."""
891 flight.handleFault(SpeedChecker, state.timestamp,
892 FaultChecker._appendDuring(flight,
893 "Taxi speed over 50 knots"),
894 FaultChecker._getLinearScore(50, 80, 10, 15,
895 state.groundSpeed))
896
897#---------------------------------------------------------------------------------------
898
899class StallChecker(PatientFaultChecker):
900 """Check if stall occured."""
901 def isCondition(self, flight, aircraft, oldState, state):
902 """Check if the fault condition holds."""
903 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
904 const.STAGE_CRUISE, const.STAGE_DESCENT,
905 const.STAGE_LANDING] and state.stalled
906
907 def logFault(self, flight, aircraft, logger, oldState, state):
908 """Log the fault."""
909 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
910 const.STAGE_LANDING] else 30
911 flight.handleFault(StallChecker, state.timestamp,
912 FaultChecker._appendDuring(flight, "Stalled"),
913 score)
914
915#---------------------------------------------------------------------------------------
916
917class StrobeLightsChecker(PatientFaultChecker):
918 """Check if the strobe lights are used properly."""
919 def isCondition(self, flight, aircraft, oldState, state):
920 """Check if the fault condition holds."""
921 if aircraft.type in [const.AIRCRAFT_T134,
922 const.AIRCRAFT_T154,
923 const.AIRCRAFT_YK40]:
924 return False
925 else:
926 return (flight.stage==const.STAGE_BOARDING and \
927 state.strobeLightsOn and state.onTheGround) or \
928 (flight.stage==const.STAGE_TAKEOFF and \
929 not state.strobeLightsOn and not state.gearsDown) or \
930 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
931 const.STAGE_DESCENT] and \
932 not state.strobeLightsOn and not state.onTheGround) or \
933 (flight.stage==const.STAGE_PARKING and \
934 state.strobeLightsOn and state.onTheGround)
935
936 def logFault(self, flight, aircraft, logger, oldState, state):
937 """Log the fault."""
938 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
939 flight.handleFault(StrobeLightsChecker, state.timestamp,
940 FaultChecker._appendDuring(flight, message),
941 1)
942
943#---------------------------------------------------------------------------------------
944
945class ThrustChecker(SimpleFaultChecker):
946 """Check if the thrust setting is not too high during takeoff.
947
948 FIXME: is this really so general, for all aircraft?"""
949 def isCondition(self, flight, aircraft, oldState, state):
950 """Check if the fault condition holds."""
951 return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
952
953 def logFault(self, flight, aircraft, logger, oldState, state):
954 """Log the fault."""
955 print state.n1
956 flight.handleFault(ThrustChecker, state.timestamp,
957 FaultChecker._appendDuring(flight,
958 "Thrust setting was too high (>97%)"),
959 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
960
961#---------------------------------------------------------------------------------------
962
963class VSChecker(SimpleFaultChecker):
964 """Check if the vertical speed is not too low at certain altitudes"""
965 BELOW10000 = -5000
966 BELOW5000 = -2500
967 BELOW2500 = -1500
968 BELOW500 = -1000
969 TOLERANCE = 1.2
970
971 def isCondition(self, flight, aircraft, oldState, state):
972 """Check if the fault condition holds."""
973 vs = state.vs
974 altitude = state.altitude
975 return vs < -8000 or vs > 8000 or \
976 (altitude<500 and vs < (VSChecker.BELOW500 *
977 VSChecker.TOLERANCE)) or \
978 (altitude<2500 and vs < (VSChecker.BELOW2500 *
979 VSChecker.TOLERANCE)) or \
980 (altitude<5000 and vs < (VSChecker.BELOW5000 *
981 VSChecker.TOLERANCE)) or \
982 (altitude<10000 and vs < (VSChecker.BELOW10000 *
983 VSChecker.TOLERANCE))
984
985 def logFault(self, flight, aircraft, logger, oldState, state):
986 """Log the fault."""
987 vs = state.vs
988
989 message = "Vertical speed was %.0f feet/min" % (vs,)
990 if vs>-8000 and vs<8000:
991 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
992
993 score = 10 if vs<-8000 or vs>8000 else 0
994
995 flight.handleFault(VSChecker, state.timestamp,
996 FaultChecker._appendDuring(flight, message),
997 score)
998
999#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.