source: src/mlx/checks.py@ 265:441bc4537a91

Last change on this file since 265:441bc4537a91 was 263:7ee78a903649, checked in by István Váradi <ivaradi@…>, 12 years ago

RPM is handled properly and the fuel tanks of Li-2 match the simulator

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