source: src/mlx/checks.py@ 257:0f7fb7fec4fc

Last change on this file since 257:0f7fb7fec4fc was 243:1a42c5aa468b, checked in by István Váradi <ivaradi@…>, 12 years ago

Added the logging of the CoG value

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