source: src/mlx/checks.py@ 241:dea155dd3ac0

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

Added support for calculating speeds in km/h for Soviet aircraft

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