source: src/mlx/checks.py@ 364:0f92095b8949

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

The flaps setting is logged at takeoff (#151)

File size: 62.1 KB
Line 
1
2import fs
3import const
4import util
5from acars import ACARS
6from sound import startSound
7
8import time
9
10#---------------------------------------------------------------------------------------
11
12## @package mlx.checks
13#
14# The classes that check the state of the aircraft.
15#
16# During the flight the program periodically queries various data from the
17# simulator. This data is returned in instances of the \ref
18# mlx.fs.AircraftState class and passed to the various "checkers",
19# i.e. instances of subclasses of the \ref StateChecker class. These checkers
20# perform various checks to see if the aircraft's parameters are within the
21# expected limits, or some of them just logs something.
22#
23# There are a few special ones, such as \ref StageChecker which computes the
24# transitions from one stage of the flight to the next one. Or \ref ACARSSender
25# which sends the ACARS periodically
26
27#---------------------------------------------------------------------------------------
28
29class StateChecker(object):
30 """Base class for classes the instances of which check the aircraft's state
31 from some aspect.
32
33 As a result of the check they may log something, or notify of some fault, etc."""
34 def check(self, flight, aircraft, logger, oldState, state):
35 """Perform the check and do whatever is needed.
36
37 This default implementation raises a NotImplementedError."""
38 raise NotImplementedError()
39
40#---------------------------------------------------------------------------------------
41
42class StageChecker(StateChecker):
43 """Check the flight stage transitions."""
44 def __init__(self):
45 """Construct the stage checker."""
46 self._flareStarted = False
47
48 def check(self, flight, aircraft, logger, oldState, state):
49 """Check the stage of the aircraft."""
50 stage = flight.stage
51 if stage==None:
52 aircraft.setStage(state, const.STAGE_BOARDING)
53 elif stage==const.STAGE_BOARDING:
54 if not state.parking or \
55 (not state.trickMode and state.groundSpeed>5.0):
56 aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
57 elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
58 if state.strobeLightsOn:
59 aircraft.setStage(state, const.STAGE_TAKEOFF)
60 elif stage==const.STAGE_TAKEOFF:
61 if not state.gearsDown or \
62 (state.radioAltitude>3000.0 and state.vs>0):
63 aircraft.setStage(state, const.STAGE_CLIMB)
64 elif not state.landingLightsOn and \
65 not state.strobeLightsOn and \
66 state.onTheGround and \
67 state.groundSpeed<50.0:
68 aircraft.setStage(state, const.STAGE_RTO)
69 elif stage==const.STAGE_CLIMB:
70 if (state.altitude+2000) > flight.cruiseAltitude:
71 aircraft.setStage(state, const.STAGE_CRUISE)
72 elif state.radioAltitude<2000.0 and \
73 state.vs < 0.0 and state.gearsDown:
74 aircraft.setStage(state, const.STAGE_LANDING)
75 elif stage==const.STAGE_CRUISE:
76 if (state.altitude+2000) < flight.cruiseAltitude:
77 aircraft.setStage(state, const.STAGE_DESCENT)
78 elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
79 if state.gearsDown and state.radioAltitude<2000.0:
80 aircraft.setStage(state, const.STAGE_LANDING)
81 elif (state.altitude+2000) > flight.cruiseAltitude:
82 aircraft.setStage(state, const.STAGE_CRUISE)
83 elif stage==const.STAGE_LANDING:
84 if state.onTheGround and state.groundSpeed<25.0:
85 aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
86 elif not state.gearsDown:
87 aircraft.setStage(state, const.STAGE_GOAROUND)
88 elif state.radioAltitude>200 and self._flareStarted:
89 aircraft.cancelFlare()
90 self._flareStarted = False
91 elif state.radioAltitude<150 and not state.onTheGround and \
92 not self._flareStarted:
93 self._flareStarted = True
94 aircraft.prepareFlare()
95 elif stage==const.STAGE_TAXIAFTERLAND:
96 if state.parking and aircraft.checkFlightEnd(state):
97 aircraft.setStage(state, const.STAGE_END)
98
99#---------------------------------------------------------------------------------------
100
101class ACARSSender(StateChecker):
102 """Sender of online ACARS.
103
104 It sends the ACARS every 3 minutes to the MAVA website."""
105
106 ## The interval at which the ACARS are sent
107 INTERVAL = 3*60.0
108
109 def __init__(self, gui):
110 """Construct the ACARS sender."""
111 self._gui = gui
112 self._lastSent = None
113
114 def check(self, flight, aircraft, logger, oldState, state):
115 """If the time has come to send the ACARS, send it."""
116 now = time.time()
117
118 if self._lastSent is not None and \
119 (self._lastSent + ACARSSender.INTERVAL)>now:
120 return
121
122 acars = ACARS(self._gui, state)
123 self._gui.webHandler.sendACARS(self._acarsCallback, acars)
124
125 def _acarsCallback(self, returned, result):
126 """Callback for ACARS sending."""
127 if returned:
128 print "Sent online ACARS"
129 self._lastSent = time.time() if self._lastSent is None \
130 else self._lastSent + ACARSSender.INTERVAL
131 else:
132 print "Failed to send the ACARS"
133
134#---------------------------------------------------------------------------------------
135
136class TakeOffLogger(StateChecker):
137 """Logger for the cruise speed."""
138 def __init__(self):
139 """Construct the logger."""
140 self._onTheGround = True
141
142 def check(self, flight, aircraft, logger, oldState, state):
143 """Log the cruise speed if necessary."""
144 if flight.stage==const.STAGE_TAKEOFF and \
145 self._onTheGround and not state.onTheGround:
146 logger.message(state.timestamp,
147 "Takeoff speed: %.0f %s" % \
148 (flight.speedFromKnots(state.ias),
149 flight.getEnglishSpeedUnit()))
150 logger.message(state.timestamp,
151 "Takeoff heading: %03.0f degrees" % (state.heading,))
152 logger.message(state.timestamp,
153 "Takeoff pitch: %.1f degrees" % (state.pitch,))
154 logger.message(state.timestamp,
155 "Takeoff flaps: %.0f" % (state.flapsSet))
156 logger.message(state.timestamp,
157 "CG/Trim: %.1f%%/%.2f" % \
158 (state.cog*100.0, state.elevatorTrim))
159 self._onTheGround = False
160
161#---------------------------------------------------------------------------------------
162
163class CruiseSpeedLogger(StateChecker):
164 """Logger for the cruise speed."""
165 def __init__(self):
166 """Construct the logger."""
167 self._lastTime = None
168
169 def check(self, flight, aircraft, logger, oldState, state):
170 """Log the cruise speed if necessary."""
171 if flight.stage==const.STAGE_CRUISE and \
172 (self._lastTime is None or \
173 (self._lastTime+800)<=state.timestamp):
174 if state.altitude>24500.0:
175 logger.message(state.timestamp,
176 "Cruise speed: %.3f mach" % (state.mach,))
177 else:
178 logger.message(state.timestamp,
179 "Cruise speed: %.0f %s" %
180 (flight.speedFromKnots(state.ias),
181 flight.getEnglishSpeedUnit()))
182 self._lastTime = state.timestamp
183
184#---------------------------------------------------------------------------------------
185
186class SpoilerLogger(StateChecker):
187 """Logger for the cruise speed."""
188 def __init__(self):
189 """Construct the logger."""
190 self._logged = False
191 self._spoilersExtension = None
192
193 def check(self, flight, aircraft, logger, oldState, state):
194 """Log the cruise speed if necessary."""
195 if flight.stage==const.STAGE_LANDING and not self._logged:
196 if state.onTheGround:
197 if state.spoilersExtension!=self._spoilersExtension:
198 logger.message(state.timestamp, "Spoilers deployed")
199 self._logged = True
200 config = flight.config
201 if config.enableSounds and config.speedbrakeAtTD:
202 startSound(const.SOUND_SPEEDBRAKE)
203 else:
204 self._spoilersExtension = state.spoilersExtension
205
206#---------------------------------------------------------------------------------------
207
208class VisibilityChecker(StateChecker):
209 """Inform the pilot of the visibility once when descending below 2000 ft,
210 then when descending below 1000 ft."""
211 def __init__(self):
212 """Construct the visibility checker."""
213 self._informedBelow2000 = False
214 self._informedBelow1000 = False
215
216 def check(self, flight, aircraft, logger, oldState, state):
217 """Check if we need to inform the pilot of the visibility."""
218 if flight.stage==const.STAGE_DESCENT or \
219 flight.stage==const.STAGE_LANDING:
220 if (state.radioAltitude<2000 and not self._informedBelow2000) or \
221 (state.radioAltitude<1000 and not self._informedBelow1000):
222 visibilityString = util.visibility2String(state.visibility)
223 fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
224 "Current visibility: " + visibilityString,
225 5)
226 logger.message(state.timestamp,
227 "Pilot was informed about the visibility: " +
228 visibilityString)
229 self._informedBelow2000 = True
230 self._informedBelow1000 = state.radioAltitude<1000
231
232#---------------------------------------------------------------------------------------
233
234class ApproachCalloutsPlayer(StateChecker):
235 """A state checker that plays a sequence of approach callouts.
236
237 It tracks the altitude during the descent and landing phases and
238 if the altitude crosses one that has a callout associated with and
239 the vertical speed is negative, that callout will be played."""
240 def __init__(self, approachCallouts):
241 """Construct the approach callouts player."""
242 self._approachCallouts = approachCallouts
243 self._altitudes = approachCallouts.getAltitudes(descending = False)
244
245 def check(self, flight, aircraft, logger, oldState, state):
246 """Check if we need to play a callout."""
247 if (flight.stage==const.STAGE_DESCENT or \
248 flight.stage==const.STAGE_LANDING) and state.vs<0:
249 oldRadioAltitude = oldState.radioAltitude
250 radioAltitude = state.radioAltitude
251 for altitude in self._altitudes:
252 if radioAltitude<=altitude and \
253 oldRadioAltitude>altitude:
254 startSound(self._approachCallouts[altitude])
255 break
256
257#---------------------------------------------------------------------------------------
258
259class StateChangeLogger(StateChecker):
260 """Base class for classes the instances of which check if a specific change has
261 occured in the aircraft's state, and log such change."""
262 def __init__(self, logInitial = True, excludedStages = None):
263 """Construct the logger.
264
265 If logInitial is True, the initial value will be logged, not just the
266 changes later.
267
268 If excludedStages is given, it should be a list containing those stages
269 during which the changes should not be logged.
270
271 Child classes should define the following functions:
272 - _changed(self, oldState, state): returns a boolean indicating if the
273 value has changed or not
274 - _getMessage(self, flight, state, forced): return a strings containing
275 the message to log with the new value
276 """
277 self._logInitial = logInitial
278 self._excludedStages = [] if excludedStages is None else excludedStages
279
280 def _getLogTimestamp(self, state, forced):
281 """Get the log timestamp."""
282 return state.timestamp
283
284 def check(self, flight, aircraft, logger, oldState, state):
285 """Check if the state has changed, and if so, log the new state."""
286 if flight.stage in self._excludedStages:
287 return
288
289 shouldLog = False
290 if oldState is None:
291 shouldLog = self._logInitial
292 else:
293 shouldLog = self._changed(oldState, state)
294
295 if shouldLog:
296 self.logState(flight, logger, state)
297
298 def logState(self, flight, logger, state, forced = False):
299 """Log the state."""
300 message = self._getMessage(flight, state, forced)
301 if message is not None:
302 logger.message(self._getLogTimestamp(state, forced), message)
303
304#-------------------------------------------------------------------------------
305
306class SimpleChangeMixin(object):
307 """A mixin that defines a _changed() function which simply calls a function
308 to retrieve the value two monitor for both states and compares them.
309
310 Child classes should define the following function:
311 - _getValue(state): get the value we are interested in."""
312 def _changed(self, oldState, state):
313 """Determine if the value has changed."""
314 currentValue = self._getValue(state)
315 return currentValue is not None and self._getValue(oldState)!=currentValue
316
317#---------------------------------------------------------------------------------------
318
319class SingleValueMixin(object):
320 """A mixin that provides a _getValue() function to query a value from the
321 state using the name of the attribute."""
322 def __init__(self, attrName):
323 """Construct the mixin with the given attribute name."""
324 self._attrName = attrName
325
326 def _getValue(self, state):
327 """Get the value of the attribute from the state."""
328 return getattr(state, self._attrName)
329
330#---------------------------------------------------------------------------------------
331
332class DelayedChangeMixin(object):
333 """A mixin to a StateChangeLogger that stores the old value and reports a
334 change only if the change has been there for a certain amount of time.
335
336 Child classes should define the following function:
337 - _getValue(state): get the value we are interested in."""
338 def __init__(self, minDelay = 3.0, maxDelay = 10.0):
339 """Construct the mixin with the given delay in seconds."""
340 self._minDelay = minDelay
341 self._maxDelay = maxDelay
342 self._oldValue = None
343 self._firstChange = None
344 self._lastChangeState = None
345 self._lastLoggedValue = None
346
347 self._logState = lambda flight, logger, state, forced = False: \
348 StateChangeLogger.logState(self, flight, logger, state,
349 forced = forced)
350 self.logState = lambda flight, logger, state, forced = False: \
351 DelayedChangeMixin.logState(self, flight, logger, state,
352 forced = forced)
353
354 def _changed(self, oldState, state):
355 """Determine if the value has changed."""
356 if self._oldValue is None:
357 self._oldValue = self._getValue(oldState)
358
359 newValue = self._getValue(state)
360 if self._isDifferent(self._oldValue, newValue):
361 if self._lastLoggedValue is not None and \
362 not self._isDifferent(self._lastLoggedValue, newValue):
363 self._firstChange = None
364 else:
365 if self._firstChange is None:
366 self._firstChange = state.timestamp
367 self._lastChangeState = state
368 self._oldValue = newValue
369
370 if self._firstChange is not None:
371 if state.timestamp >= min(self._lastChangeState.timestamp +
372 self._minDelay,
373 self._firstChange + self._maxDelay):
374 self._firstChange = None
375 return True
376
377 return False
378
379 def _getLogTimestamp(self, state, forced):
380 """Get the log timestamp."""
381 return self._lastChangeState.timestamp \
382 if not forced and self._lastChangeState is not None \
383 else state.timestamp
384
385 def _isDifferent(self, oldValue, newValue):
386 """Determine if the given values are different.
387
388 This default implementation checks for simple equality."""
389 return oldValue!=newValue
390
391 def logState(self, flight, logger, state, forced):
392 """Log the given state.
393
394 The value belonging to that state is recorded in _lastLoggedValue.
395
396 It calls _logState to perform the real logging."""
397 self._lastLoggedValue = self._getValue(state)
398 self._logState(flight, logger, state, forced = forced)
399
400#---------------------------------------------------------------------------------------
401
402class TemplateMessageMixin(object):
403 """Mixin to generate a message based on a template.
404
405 Child classes should define the following function:
406 - _getValue(state): get the value we are interested in."""
407 def __init__(self, template):
408 """Construct the mixin."""
409 self._template = template
410
411 def _getMessage(self, flight, state, forced):
412 """Get the message."""
413 value = self._getValue(state)
414 return None if value is None else self._template % (value,)
415
416#---------------------------------------------------------------------------------------
417
418class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
419 DelayedChangeMixin, TemplateMessageMixin):
420 """Base for generic state change loggers that monitor a single value in the
421 state possibly with a delay and the logged message comes from a template"""
422 def __init__(self, attrName, template, logInitial = True,
423 excludedStages = None, minDelay = 0.0, maxDelay = 0.0):
424 """Construct the object."""
425 StateChangeLogger.__init__(self, logInitial = logInitial,
426 excludedStages = excludedStages)
427 SingleValueMixin.__init__(self, attrName)
428 DelayedChangeMixin.__init__(self, minDelay = minDelay,
429 maxDelay = maxDelay)
430 TemplateMessageMixin.__init__(self, template)
431 self._getLogTimestamp = \
432 lambda state, forced: \
433 DelayedChangeMixin._getLogTimestamp(self, state, forced)
434
435#---------------------------------------------------------------------------------------
436
437class ForceableLoggerMixin(object):
438 """A mixin for loggers that can be forced to log a certain state."""
439 def forceLog(self, flight, logger, state):
440 """Force logging the given state."""
441 self.logState(flight, logger, state, forced = True)
442
443#---------------------------------------------------------------------------------------
444
445class AltimeterLogger(StateChangeLogger, SingleValueMixin,
446 DelayedChangeMixin):
447 """Logger for the altimeter setting."""
448 def __init__(self):
449 """Construct the logger."""
450 StateChangeLogger.__init__(self, logInitial = True)
451 SingleValueMixin.__init__(self, "altimeter")
452 DelayedChangeMixin.__init__(self)
453 self._getLogTimestamp = \
454 lambda state, forced: \
455 DelayedChangeMixin._getLogTimestamp(self, state, forced)
456
457 def _getMessage(self, flight, state, forced):
458 """Get the message to log on a change."""
459 logState = self._lastChangeState if \
460 self._lastChangeState is not None else state
461 return "Altimeter: %.0f hPa at %.0f feet" % \
462 (logState.altimeter, logState.altitude)
463
464#---------------------------------------------------------------------------------------
465
466class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
467 """Logger for NAV radios.
468
469 It also logs the OBS radial set."""
470 excludedStages = [const.STAGE_BOARDING, const.STAGE_PUSHANDTAXI,
471 const.STAGE_RTO, const.STAGE_TAXIAFTERLAND,
472 const.STAGE_PARKING]
473
474 @staticmethod
475 def getMessage(logName, frequency, obs):
476 """Get the message for the given NAV radio setting."""
477 message = u"%s: %s" % (logName, frequency)
478 if obs is not None: message += u" (%03d\u00b0)" % (obs,)
479 return message
480
481 def __init__(self, attrName, logName):
482 """Construct the NAV logger."""
483 StateChangeLogger.__init__(self, logInitial = False,
484 excludedStages = self.excludedStages)
485 DelayedChangeMixin.__init__(self)
486
487 self._getLogTimestamp = \
488 lambda state, forced: \
489 DelayedChangeMixin._getLogTimestamp(self, state, forced)
490
491 self._attrName = attrName
492 self._logName = logName
493
494 def _getValue(self, state):
495 """Get the value.
496
497 If both the frequency and the obs settings are available, a tuple
498 containing them is returned, otherwise None."""
499 frequency = getattr(state, self._attrName)
500 obs = getattr(state, self._attrName + "_obs")
501 manual = getattr(state, self._attrName + "_manual")
502 return (frequency, obs, manual)
503
504 def _getMessage(self, flight, state, forced):
505 """Get the message."""
506 (frequency, obs, manual) = self._getValue(state)
507 return None if frequency is None or obs is None or \
508 (not manual and not forced) else \
509 self.getMessage(self._logName, frequency, obs)
510
511 def _isDifferent(self, oldValue, newValue):
512 """Determine if the valie has changed between the given states."""
513 (oldFrequency, oldOBS, _oldManual) = oldValue
514 (newFrequency, newOBS, _newManual) = newValue
515 return oldFrequency!=newFrequency or oldOBS!=newOBS
516
517#---------------------------------------------------------------------------------------
518
519class NAV1Logger(NAVLogger):
520 """Logger for the NAV1 radio setting."""
521 def __init__(self):
522 """Construct the logger."""
523 super(NAV1Logger, self).__init__("nav1", "NAV1")
524
525#---------------------------------------------------------------------------------------
526
527class NAV2Logger(NAVLogger):
528 """Logger for the NAV2 radio setting."""
529 def __init__(self):
530 """Construct the logger."""
531 super(NAV2Logger, self).__init__("nav2", "NAV2")
532
533#---------------------------------------------------------------------------------------
534
535class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
536 """Base class for the ADF loggers."""
537 def __init__(self, attr, logName):
538 """Construct the ADF logger."""
539 GenericStateChangeLogger.__init__(self, attr,
540 "%s: %%s" % (logName,),
541 logInitial = False,
542 excludedStages =
543 NAVLogger.excludedStages,
544 minDelay = 3.0, maxDelay = 10.0)
545
546#---------------------------------------------------------------------------------------
547
548class ADF1Logger(ADFLogger):
549 """Logger for the ADF1 radio setting."""
550 def __init__(self):
551 """Construct the logger."""
552 super(ADF1Logger, self).__init__("adf1", "ADF1")
553
554#---------------------------------------------------------------------------------------
555
556class ADF2Logger(ADFLogger):
557 """Logger for the ADF2 radio setting."""
558 def __init__(self):
559 """Construct the logger."""
560 super(ADF2Logger, self).__init__("adf2", "ADF2")
561
562#---------------------------------------------------------------------------------------
563
564class SquawkLogger(GenericStateChangeLogger):
565 """Logger for the squawk setting."""
566 def __init__(self):
567 """Construct the logger."""
568 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
569 minDelay = 3.0, maxDelay = 10.0)
570
571#---------------------------------------------------------------------------------------
572
573class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
574 """Base class for the loggers of the various lights."""
575 def __init__(self, attrName, template):
576 """Construct the logger."""
577 StateChangeLogger.__init__(self)
578 SingleValueMixin.__init__(self, attrName)
579
580 self._template = template
581
582 def _getMessage(self, flight, state, forced):
583 """Get the message from the given state."""
584 return self._template % ("ON" if self._getValue(state) else "OFF")
585
586#---------------------------------------------------------------------------------------
587
588class AnticollisionLightsLogger(LightsLogger):
589 """Logger for the anti-collision lights."""
590 def __init__(self):
591 LightsLogger.__init__(self, "antiCollisionLightsOn",
592 "Anti-collision lights: %s")
593
594#---------------------------------------------------------------------------------------
595
596class LandingLightsLogger(LightsLogger):
597 """Logger for the landing lights."""
598 def __init__(self):
599 LightsLogger.__init__(self, "landingLightsOn",
600 "Landing lights: %s")
601
602#---------------------------------------------------------------------------------------
603
604class StrobeLightsLogger(LightsLogger):
605 """Logger for the strobe lights."""
606 def __init__(self):
607 LightsLogger.__init__(self, "strobeLightsOn",
608 "Strobe lights: %s")
609
610#---------------------------------------------------------------------------------------
611
612class NavLightsLogger(LightsLogger):
613 """Logger for the navigational lights."""
614 def __init__(self):
615 LightsLogger.__init__(self, "navLightsOn",
616 "Navigational lights: %s")
617
618#---------------------------------------------------------------------------------------
619
620class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
621 """Logger for the flaps setting."""
622 def __init__(self):
623 """Construct the logger."""
624 StateChangeLogger.__init__(self, logInitial = True,
625 excludedStages =
626 [const.STAGE_BOARDING,
627 const.STAGE_PUSHANDTAXI,
628 const.STAGE_RTO,
629 const.STAGE_TAKEOFF])
630 SingleValueMixin.__init__(self, "flapsSet")
631
632 def _getMessage(self, flight, state, forced):
633 """Get the message to log on a change."""
634 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
635 return "Flaps %.0f - %.0f %s" % \
636 (state.flapsSet, flight.speedFromKnots(speed),
637 flight.getEnglishSpeedUnit())
638
639#---------------------------------------------------------------------------------------
640
641class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
642 """Logger for the gears state."""
643 def __init__(self):
644 """Construct the logger."""
645 StateChangeLogger.__init__(self, logInitial = True)
646 SingleValueMixin.__init__(self, "gearControlDown")
647
648 def _getMessage(self, flight, state, forced):
649 """Get the message to log on a change."""
650 return "Gears SET to %s at %.0f %s, %.0f feet" % \
651 ("DOWN" if state.gearControlDown else "UP",
652 flight.speedFromKnots(state.ias),
653 flight.getEnglishSpeedUnit(), state.altitude)
654
655#---------------------------------------------------------------------------------------
656
657class APLogger(StateChangeLogger, DelayedChangeMixin):
658 """Log the state of the autopilot."""
659 @staticmethod
660 def _logBoolean(logger, timestamp, what, value):
661 """Log a boolean value.
662
663 what is the name of the value that is being logged."""
664 message = what + " "
665 if value is None:
666 message += "cannot be detected, will not log"
667 else:
668 message += "is " + ("ON" if value else "OFF")
669 logger.message(timestamp, message)
670
671 @staticmethod
672 def _logNumeric(logger, timestamp, what, format, value):
673 """Log a numerical value."""
674 message = what
675 if value is None:
676 message += u" cannot be detected, will not log"
677 else:
678 message += u": " + format % (value,)
679 logger.message(timestamp, message)
680
681 @staticmethod
682 def _logAPMaster(logger, timestamp, state):
683 """Log the AP master state."""
684 APLogger._logBoolean(logger, timestamp, "AP master",
685 state.apMaster)
686
687 @staticmethod
688 def _logAPHeadingHold(logger, timestamp, state):
689 """Log the AP heading hold state."""
690 APLogger._logBoolean(logger, timestamp, "AP heading hold",
691 state.apHeadingHold)
692
693 @staticmethod
694 def _logAPHeading(logger, timestamp, state):
695 """Log the AP heading."""
696 APLogger._logNumeric(logger, timestamp, u"AP heading",
697 u"%03.0f\u00b0", state.apHeading)
698
699 @staticmethod
700 def _logAPAltitudeHold(logger, timestamp, state):
701 """Log the AP altitude hold state."""
702 APLogger._logBoolean(logger, timestamp, "AP altitude hold",
703 state.apAltitudeHold)
704
705 @staticmethod
706 def _logAPAltitude(logger, timestamp, state):
707 """Log the AP heading."""
708 APLogger._logNumeric(logger, timestamp, u"AP altitude",
709 u"%.0f ft", state.apAltitude)
710
711 def __init__(self):
712 """Construct the state logger."""
713 StateChangeLogger.__init__(self)
714 DelayedChangeMixin.__init__(self)
715 self._lastLoggedState = None
716 self.logState = lambda flight, logger, state:\
717 APLogger.logState(self, flight, logger, state)
718
719 def _getValue(self, state):
720 """Convert the relevant values from the given state into a tuple."""
721 return (state.apMaster,
722 state.apHeadingHold, state.apHeading,
723 state.apAltitudeHold, state.apAltitude)
724
725 def _isDifferent(self, oldValue, newValue):
726 """Determine if the given old and new values are different (enough) to
727 be logged."""
728 (oldAPMaster, oldAPHeadingHold, oldAPHeading,
729 oldAPAltitudeHold, oldAPAltitude) = oldValue
730 (apMaster, apHeadingHold, apHeading,
731 apAltitudeHold, apAltitude) = newValue
732
733 if apMaster is not None and apMaster!=oldAPMaster:
734 return True
735 if apMaster is False:
736 return False
737
738 if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
739 return True
740 if apHeadingHold is not False and apHeading is not None and \
741 apHeading!=oldAPHeading:
742 return True
743
744 if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
745 return True
746 if apAltitudeHold is not False and apAltitude is not None and \
747 apAltitude!=oldAPAltitude:
748 return True
749
750 return False
751
752 def logState(self, flight, logger, state):
753 """Log the autopilot state."""
754 timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
755 if self._lastLoggedState is None:
756 self._logAPMaster(logger, timestamp, state)
757 if state.apMaster is not False or state.apHeadingHold is None:
758 self._logAPHeadingHold(logger, timestamp, state)
759 if state.apMaster is not False and \
760 (state.apHeadingHold is not False or state.apHeading is None):
761 self._logAPHeading(logger, timestamp, state)
762 if state.apMaster is not False or state.apAltitudeHold is None:
763 self._logAPAltitudeHold(logger, timestamp, state)
764 if state.apMaster is not False and \
765 (state.apAltitudeHold is not False or state.apAltitude is None):
766 self._logAPAltitude(logger, timestamp, state)
767 self._firstCall = False
768 else:
769 oldState = self._lastLoggedState
770
771 apMasterTurnedOn = False
772 if state.apMaster is not None and state.apMaster!=oldState.apMaster:
773 apMasterTurnedOn = state.apMaster
774 self._logAPMaster(logger, timestamp, state)
775
776 if state.apMaster is not False:
777 apHeadingHoldTurnedOn = False
778 if state.apHeadingHold is not None and \
779 (state.apHeadingHold!=oldState.apHeadingHold or
780 apMasterTurnedOn):
781 apHeadingHoldTurnedOn = state.apHeadingHold
782 self._logAPHeadingHold(logger, timestamp, state)
783
784 if state.apHeadingHold is not False and \
785 state.apHeading is not None and \
786 (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
787 apHeadingHoldTurnedOn):
788 self._logAPHeading(logger, timestamp, state)
789
790 apAltitudeHoldTurnedOn = False
791 if state.apAltitudeHold is not None and \
792 (state.apAltitudeHold!=oldState.apAltitudeHold or
793 apMasterTurnedOn):
794 apAltitudeHoldTurnedOn = state.apAltitudeHold
795 self._logAPAltitudeHold(logger, timestamp, state)
796
797 if state.apAltitudeHold is not False and \
798 state.apAltitude is not None and \
799 (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
800 apAltitudeHoldTurnedOn):
801 self._logAPAltitude(logger, timestamp, state)
802
803 self._lastLoggedState = state
804
805#---------------------------------------------------------------------------------------
806
807class FaultChecker(StateChecker):
808 """Base class for checkers that look for faults."""
809 @staticmethod
810 def _appendDuring(flight, message):
811 """Append a 'during XXX' test to the given message, depending on the
812 flight stage."""
813 stageStr = const.stage2string(flight.stage)
814 return message if stageStr is None \
815 else (message + " during " + stageStr.upper())
816
817 @staticmethod
818 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
819 value):
820 """Get the score for a faulty value where the score is calculated
821 linearly within a certain range."""
822 if value<minFaultValue:
823 return 0
824 elif value>maxFaultValue:
825 return maxScore
826 else:
827 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
828 (maxFaultValue - minFaultValue)
829
830#---------------------------------------------------------------------------------------
831
832class SimpleFaultChecker(FaultChecker):
833 """Base class for fault checkers that check for a single occurence of a
834 faulty condition.
835
836 Child classes should implement the following functions:
837 - isCondition(self, flight, aircraft, oldState, state): should return whether the
838 condition holds
839 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
840 via the logger."""
841 def check(self, flight, aircraft, logger, oldState, state):
842 """Perform the check."""
843 if self.isCondition(flight, aircraft, oldState, state):
844 self.logFault(flight, aircraft, logger, oldState, state)
845
846#---------------------------------------------------------------------------------------
847
848class PatientFaultChecker(FaultChecker):
849 """A fault checker that does not decides on a fault when the condition
850 arises immediately, but can wait some time.
851
852 Child classes should implement the following functions:
853 - isCondition(self, flight, aircraft, oldState, state): should return whether the
854 condition holds
855 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
856 via the logger
857 """
858 def __init__(self, timeout = 2.0):
859 """Construct the fault checker with the given timeout."""
860 self._timeout = timeout
861 self._faultStarted = None
862
863 def getTimeout(self, flight, aircraft, oldState, state):
864 """Get the timeout.
865
866 This default implementation returns the timeout given in the
867 constructor, but child classes might want to enforce a different
868 policy."""
869 return self._timeout
870
871 def check(self, flight, aircraft, logger, oldState, state):
872 """Perform the check."""
873 if self.isCondition(flight, aircraft, oldState, state):
874 if self._faultStarted is None:
875 self._faultStarted = state.timestamp
876 timeout = self.getTimeout(flight, aircraft, oldState, state)
877 if state.timestamp>=(self._faultStarted + timeout):
878 self.logFault(flight, aircraft, logger, oldState, state)
879 self._faultStarted = state.timestamp
880 else:
881 self._faultStarted = None
882
883#---------------------------------------------------------------------------------------
884
885class AntiCollisionLightsChecker(PatientFaultChecker):
886 """Check for the anti-collision light being off at high N1 values."""
887 def isCondition(self, flight, aircraft, oldState, state):
888 """Check if the fault condition holds."""
889 return (flight.stage!=const.STAGE_PARKING or \
890 not flight.config.usingFS2Crew) and \
891 not state.antiCollisionLightsOn and \
892 self.isEngineCondition(state)
893
894 def logFault(self, flight, aircraft, logger, oldState, state):
895 """Log the fault."""
896 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
897 FaultChecker._appendDuring(flight,
898 "Anti-collision lights were off"),
899 1)
900
901 def isEngineCondition(self, state):
902 """Determine if the engines are in such a state that the lights should
903 be on."""
904 if state.n1 is not None:
905 return max(state.n1)>5
906 elif state.rpm is not None:
907 return max(state.rpm)>0
908 else:
909 return False
910
911#---------------------------------------------------------------------------------------
912
913class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
914 """Check for the anti-collision light for Tuplev planes."""
915 def isCondition(self, flight, aircraft, oldState, state):
916 """Check if the fault condition holds."""
917 numEnginesRunning = 0
918 for n1 in state.n1:
919 if n1>5: numEnginesRunning += 1
920
921 if flight.stage==const.STAGE_PARKING:
922 return numEnginesRunning<len(state.n1) \
923 and state.antiCollisionLightsOn
924 else:
925 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
926 numEnginesRunning<1 and state.antiCollisionLightsOn
927
928#---------------------------------------------------------------------------------------
929
930class BankChecker(SimpleFaultChecker):
931 """Check for the bank is within limits."""
932 def isCondition(self, flight, aircraft, oldState, state):
933 """Check if the fault condition holds."""
934 if flight.stage==const.STAGE_CRUISE:
935 bankLimit = 30
936 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
937 const.STAGE_DESCENT, const.STAGE_LANDING]:
938 bankLimit = 35
939 else:
940 return False
941
942 return state.bank>bankLimit or state.bank<-bankLimit
943
944 def logFault(self, flight, aircraft, logger, oldState, state):
945 """Log the fault."""
946 flight.handleFault(BankChecker, state.timestamp,
947 FaultChecker._appendDuring(flight, "Bank too steep"),
948 2)
949
950#---------------------------------------------------------------------------------------
951
952class FlapsRetractChecker(SimpleFaultChecker):
953 """Check if the flaps are not retracted too early."""
954 def __init__(self):
955 """Construct the flaps checker."""
956 self._timeStart = None
957
958 def isCondition(self, flight, aircraft, oldState, state):
959 """Check if the fault condition holds.
960
961 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
962 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround and
963 aircraft.type!=const.AIRCRAFT_F70) or \
964 (flight.stage==const.STAGE_LANDING and state.onTheGround):
965 if self._timeStart is None:
966 self._timeStart = state.timestamp
967
968 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
969 return True
970 else:
971 self._timeStart = None
972 return False
973
974 def logFault(self, flight, aircraft, logger, oldState, state):
975 """Log the fault."""
976 flight.handleFault(FlapsRetractChecker, state.timestamp,
977 FaultChecker._appendDuring(flight, "Flaps retracted"),
978 20)
979
980#---------------------------------------------------------------------------------------
981
982class FlapsSpeedLimitChecker(SimpleFaultChecker):
983 """Check if the flaps are extended only at the right speeds."""
984 def isCondition(self, flight, aircraft, oldState, state):
985 """Check if the fault condition holds."""
986 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
987 return speedLimit is not None and state.smoothedIAS>speedLimit
988
989 def logFault(self, flight, aircraft, logger, oldState, state):
990 """Log the fault."""
991 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
992 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
993 5)
994
995#---------------------------------------------------------------------------------------
996
997class GearsDownChecker(SimpleFaultChecker):
998 """Check if the gears are down at low altitudes."""
999 def isCondition(self, flight, aircraft, oldState, state):
1000 """Check if the fault condition holds."""
1001 return state.radioAltitude<10 and not state.gearsDown and \
1002 flight.stage!=const.STAGE_TAKEOFF
1003
1004 def logFault(self, flight, aircraft, logger, oldState, state):
1005 """Log the fault."""
1006 flight.handleNoGo(GearsDownChecker, state.timestamp,
1007 "Gears not down at %.0f feet radio altitude" % \
1008 (state.radioAltitude,),
1009 "GEAR DOWN NO GO")
1010
1011#---------------------------------------------------------------------------------------
1012
1013class GearSpeedLimitChecker(PatientFaultChecker):
1014 """Check if the gears not down at too high a speed."""
1015 def isCondition(self, flight, aircraft, oldState, state):
1016 """Check if the fault condition holds."""
1017 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
1018
1019 def logFault(self, flight, aircraft, logger, oldState, state):
1020 """Log the fault."""
1021 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
1022 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
1023 5)
1024
1025#---------------------------------------------------------------------------------------
1026
1027class GLoadChecker(SimpleFaultChecker):
1028 """Check if the G-load does not exceed 2 except during flare."""
1029 def isCondition(self, flight, aircraft, oldState, state):
1030 """Check if the fault condition holds."""
1031 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
1032 state.radioAltitude>=50)
1033
1034 def logFault(self, flight, aircraft, logger, oldState, state):
1035 """Log the fault."""
1036 flight.handleFault(GLoadChecker, state.timestamp,
1037 "G-load was %.2f" % (state.gLoad,),
1038 10)
1039
1040#---------------------------------------------------------------------------------------
1041
1042class LandingLightsChecker(PatientFaultChecker):
1043 """Check if the landing lights are used properly."""
1044 def getTimeout(self, flight, aircraft, oldState, state):
1045 """Get the timeout.
1046
1047 It is the default timeout except for landing and takeoff."""
1048 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
1049 const.STAGE_LANDING] else self._timeout
1050
1051 def isCondition(self, flight, aircraft, oldState, state):
1052 """Check if the fault condition holds."""
1053 return state.landingLightsOn is not None and \
1054 ((flight.stage==const.STAGE_BOARDING and \
1055 state.landingLightsOn and state.onTheGround) or \
1056 (flight.stage==const.STAGE_TAKEOFF and \
1057 not state.landingLightsOn and not state.onTheGround) or \
1058 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1059 const.STAGE_DESCENT] and \
1060 state.landingLightsOn and state.altitude>12500) or \
1061 (flight.stage==const.STAGE_LANDING and \
1062 not state.landingLightsOn and state.onTheGround) or \
1063 (flight.stage==const.STAGE_PARKING and \
1064 state.landingLightsOn and state.onTheGround))
1065
1066 def logFault(self, flight, aircraft, logger, oldState, state):
1067 """Log the fault."""
1068 score = 0 if flight.stage==const.STAGE_LANDING else 1
1069 message = "Landing lights were %s" % \
1070 (("on" if state.landingLightsOn else "off"),)
1071 flight.handleFault(LandingLightsChecker, state.timestamp,
1072 FaultChecker._appendDuring(flight, message),
1073 score)
1074
1075#---------------------------------------------------------------------------------------
1076
1077class TransponderChecker(PatientFaultChecker):
1078 """Check if the transponder is used properly."""
1079 def __init__(self):
1080 """Construct the transponder checker."""
1081 super(TransponderChecker, self).__init__()
1082 self._liftOffTime = None
1083
1084 def isCondition(self, flight, aircraft, oldState, state):
1085 """Check if the fault condition holds."""
1086 if state.onTheGround:
1087 self._liftOffTime = None
1088 elif self._liftOffTime is None:
1089 self._liftOffTime = state.timestamp
1090
1091 return state.xpdrC is not None and \
1092 ((state.xpdrC and flight.stage in
1093 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
1094 (not state.xpdrC and
1095 (flight.stage in
1096 [const.STAGE_CRUISE, const.STAGE_DESCENT,
1097 const.STAGE_LANDING, const.STAGE_GOAROUND] or \
1098 ((not state.autoXPDR or \
1099 (self._liftOffTime is not None and
1100 state.timestamp > (self._liftOffTime+8))) and \
1101 flight.stage in
1102 [const.STAGE_TAKEOFF, const.STAGE_RTO, const.STAGE_CLIMB])
1103 )
1104 )
1105 )
1106
1107 def logFault(self, flight, aircraft, logger, oldState, state):
1108 """Log the fault."""
1109 score = 0
1110 message = "Transponder was %s" % \
1111 (("mode C" if state.xpdrC else "standby"),)
1112 flight.handleFault(TransponderChecker, state.timestamp,
1113 FaultChecker._appendDuring(flight, message),
1114 score)
1115
1116#---------------------------------------------------------------------------------------
1117
1118class WeightChecker(PatientFaultChecker):
1119 """Base class for checkers that check that some limit is not exceeded."""
1120 def __init__(self, name):
1121 """Construct the checker."""
1122 super(WeightChecker, self).__init__(timeout = 5.0)
1123 self._name = name
1124
1125 def isCondition(self, flight, aircraft, oldState, state):
1126 """Check if the fault condition holds."""
1127 if flight.entranceExam:
1128 return False
1129
1130 limit = self.getLimit(flight, aircraft, state)
1131 if limit is not None:
1132 #if flight.options.compensation is not None:
1133 # limit += flight.options.compensation
1134 return self.getWeight(state)>limit
1135
1136 return False
1137
1138 def logFault(self, flight, aircraft, logger, oldState, state):
1139 """Log the fault."""
1140 mname = "M" + self._name
1141 flight.handleNoGo(self.__class__, state.timestamp,
1142 "%s exceeded: %s is %.0f kg" % \
1143 (mname, self._name, self.getWeight(state)),
1144 "%s NO GO" % (mname,))
1145
1146 def getWeight(self, state):
1147 """Get the weight that is interesting for us."""
1148 return state.grossWeight
1149
1150#---------------------------------------------------------------------------------------
1151
1152class MLWChecker(WeightChecker):
1153 """Checks if the MLW is not exceeded on landing."""
1154 def __init__(self):
1155 """Construct the checker."""
1156 super(MLWChecker, self).__init__("LW")
1157
1158 def getLimit(self, flight, aircraft, state):
1159 """Get the limit if we are in the right state."""
1160 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
1161 state.onTheGround and \
1162 not flight.entranceExam else None
1163
1164#---------------------------------------------------------------------------------------
1165
1166class MTOWChecker(WeightChecker):
1167 """Checks if the MTOW is not exceeded on landing."""
1168 def __init__(self):
1169 """Construct the checker."""
1170 super(MTOWChecker, self).__init__("TOW")
1171
1172 def getLimit(self, flight, aircraft, state):
1173 """Get the limit if we are in the right state."""
1174 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1175 not flight.entranceExam else None
1176
1177#---------------------------------------------------------------------------------------
1178
1179class MZFWChecker(WeightChecker):
1180 """Checks if the MZFW is not exceeded on landing."""
1181 def __init__(self):
1182 """Construct the checker."""
1183 super(MZFWChecker, self).__init__("ZFW")
1184
1185 def getLimit(self, flight, aircraft, state):
1186 """Get the limit if we are in the right state."""
1187 return aircraft.mzfw if not flight.entranceExam else None
1188
1189 def getWeight(self, state):
1190 """Get the weight that is interesting for us."""
1191 return state.zfw
1192
1193#---------------------------------------------------------------------------------------
1194
1195class NavLightsChecker(PatientFaultChecker):
1196 """Check if the navigational lights are used properly."""
1197 def isCondition(self, flight, aircraft, oldState, state):
1198 """Check if the fault condition holds."""
1199 return flight.stage!=const.STAGE_BOARDING and \
1200 flight.stage!=const.STAGE_PARKING and \
1201 state.navLightsOn is False
1202
1203 def logFault(self, flight, aircraft, logger, oldState, state):
1204 """Log the fault."""
1205 flight.handleFault(NavLightsChecker, state.timestamp,
1206 FaultChecker._appendDuring(flight,
1207 "Navigation lights were off"),
1208 1)
1209
1210#---------------------------------------------------------------------------------------
1211
1212class OverspeedChecker(PatientFaultChecker):
1213 """Check if Vne has been exceeded."""
1214 def __init__(self, timeout = 5.0):
1215 """Construct the checker."""
1216 super(OverspeedChecker, self).__init__(timeout = timeout)
1217
1218 def isCondition(self, flight, aircraft, oldState, state):
1219 """Check if the fault condition holds."""
1220 return state.overspeed
1221
1222 def logFault(self, flight, aircraft, logger, oldState, state):
1223 """Log the fault."""
1224 flight.handleFault(OverspeedChecker, state.timestamp,
1225 FaultChecker._appendDuring(flight, "Overspeed"),
1226 20)
1227
1228#---------------------------------------------------------------------------------------
1229
1230class PayloadChecker(SimpleFaultChecker):
1231 """Check if the payload matches the specification."""
1232 TOLERANCE=550
1233
1234 @staticmethod
1235 def isZFWFaulty(aircraftZFW, flightZFW):
1236 """Check if the given aircraft's ZFW is outside of the limits."""
1237 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1238 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1239
1240 def isCondition(self, flight, aircraft, oldState, state):
1241 """Check if the fault condition holds."""
1242 return not flight.entranceExam and \
1243 flight.stage==const.STAGE_PUSHANDTAXI and \
1244 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1245
1246 def logFault(self, flight, aircraft, logger, oldState, state):
1247 """Log the fault."""
1248 flight.handleNoGo(PayloadChecker, state.timestamp,
1249 "ZFW difference is more than %d kgs" % \
1250 (PayloadChecker.TOLERANCE,),
1251 "ZFW NO GO")
1252
1253#---------------------------------------------------------------------------------------
1254
1255class PitotChecker(PatientFaultChecker):
1256 """Check if pitot heat is on."""
1257 def __init__(self):
1258 """Construct the checker."""
1259 super(PitotChecker, self).__init__(timeout = 3.0)
1260
1261 def isCondition(self, flight, aircraft, oldState, state):
1262 """Check if the fault condition holds."""
1263 return state.groundSpeed>80 and not state.pitotHeatOn
1264
1265 def logFault(self, flight, aircraft, logger, oldState, state):
1266 """Log the fault."""
1267 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1268 const.STAGE_CRUISE, const.STAGE_DESCENT,
1269 const.STAGE_LANDING] else 0
1270 flight.handleFault(PitotChecker, state.timestamp,
1271 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1272 score)
1273
1274#---------------------------------------------------------------------------------------
1275
1276class ReverserChecker(SimpleFaultChecker):
1277 """Check if the reverser is not used below the speed prescribed for the
1278 aircraft."""
1279 def isCondition(self, flight, aircraft, oldState, state):
1280 """Check if the fault condition holds."""
1281 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1282 const.STAGE_TAXIAFTERLAND] and \
1283 state.groundSpeed<aircraft.reverseMinSpeed and max(state.reverser)
1284
1285 def logFault(self, flight, aircraft, logger, oldState, state):
1286 """Log the fault."""
1287 message = "Reverser used below %.0f %s" % \
1288 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
1289 flight.handleFault(ReverserChecker, state.timestamp,
1290 FaultChecker._appendDuring(flight, message),
1291 15)
1292
1293#---------------------------------------------------------------------------------------
1294
1295class SpeedChecker(SimpleFaultChecker):
1296 """Check if the speed is in the prescribed limits."""
1297 @staticmethod
1298 def logSpeedFault(flight, state, stage = None, updateID = None):
1299 """Log the speed fault."""
1300 message = "Taxi speed over %.0f %s" % \
1301 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1302 if stage is None:
1303 stage = flight.stage
1304 score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
1305 return flight.handleFault((SpeedChecker, stage), state.timestamp,
1306 FaultChecker._appendDuring(flight, message),
1307 score, updatePrevious = updateID is None,
1308 updateID = updateID)
1309
1310 def isCondition(self, flight, aircraft, oldState, state):
1311 """Check if the fault condition holds."""
1312 return flight.stage in [const.STAGE_PUSHANDTAXI,
1313 const.STAGE_RTO,
1314 const.STAGE_TAXIAFTERLAND] and \
1315 state.groundSpeed>50
1316
1317 def logFault(self, flight, aircraft, logger, oldState, state):
1318 """Log the fault."""
1319 self.logSpeedFault(flight, state)
1320
1321#---------------------------------------------------------------------------------------
1322
1323class NoStrobeSpeedChecker(StateChecker):
1324 """Checker for the ground speed of aircraft that have no strobe lights by
1325 which to detect the takeoff stage.
1326
1327 If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state as
1328 saved as a provisional takeoff state. If the speed then decreases below 50
1329 knots, or the plane remains on the ground for more than 20 seconds, a taxi
1330 speed error is logged with the highest ground speed detected. This state is
1331 also stored in the flight object as a possible
1332
1333 If the plane becomes airborne within 20 seconds, the stage becomes TAKEOFF,
1334 and the previously saved takeoff state is logged.
1335
1336 During the TAXIAFTERLAND stage, speed is checked as in case of
1337 SpeedChecker."""
1338 def __init__(self):
1339 """Initialize the speed checker."""
1340 self._takeoffState = None
1341 self._highestSpeedState = None
1342
1343 def check(self, flight, aircraft, logger, oldState, state):
1344 """Check the state as described above."""
1345 if flight.stage==const.STAGE_PUSHANDTAXI or \
1346 flight.stage==const.STAGE_RTO:
1347 self._checkPushAndTaxi(flight, aircraft, state)
1348 elif flight.stage==const.STAGE_TAXIAFTERLAND:
1349 if state.groundSpeed>50:
1350 SpeedChecker.logSpeedFault(flight, state)
1351
1352 def _checkPushAndTaxi(self, flight, aircraft, state):
1353 """Check the speed during the push and taxi stage."""
1354 if state.groundSpeed>50:
1355 if self._takeoffState is None:
1356 self._takeoffState = state
1357 self._highestSpeedState = state
1358 else:
1359 if state.groundSpeed>self._highestSpeedState.groundSpeed:
1360 self._highestSpeedState = state
1361
1362 if state.timestamp > (self._takeoffState.timestamp + 20):
1363 flight.setRTOState(self._highestSpeedState)
1364 elif not state.onTheGround:
1365 aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
1366 self._takeoffState = None
1367 elif self._takeoffState is not None:
1368 flight.setRTOState(self._highestSpeedState)
1369 self._takeoffState = None
1370
1371#---------------------------------------------------------------------------------------
1372
1373class StallChecker(PatientFaultChecker):
1374 """Check if stall occured."""
1375 def isCondition(self, flight, aircraft, oldState, state):
1376 """Check if the fault condition holds."""
1377 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1378 const.STAGE_CRUISE, const.STAGE_DESCENT,
1379 const.STAGE_LANDING] and state.stalled
1380
1381 def logFault(self, flight, aircraft, logger, oldState, state):
1382 """Log the fault."""
1383 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1384 const.STAGE_LANDING] else 30
1385 flight.handleFault(StallChecker, state.timestamp,
1386 FaultChecker._appendDuring(flight, "Stalled"),
1387 score)
1388
1389#---------------------------------------------------------------------------------------
1390
1391class StrobeLightsChecker(PatientFaultChecker):
1392 """Check if the strobe lights are used properly."""
1393 def isCondition(self, flight, aircraft, oldState, state):
1394 """Check if the fault condition holds."""
1395 return (flight.stage==const.STAGE_BOARDING and \
1396 state.strobeLightsOn and state.onTheGround) or \
1397 (flight.stage==const.STAGE_TAKEOFF and \
1398 not state.strobeLightsOn and not state.gearsDown) or \
1399 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1400 const.STAGE_DESCENT] and \
1401 not state.strobeLightsOn and not state.onTheGround) or \
1402 (flight.stage==const.STAGE_PARKING and \
1403 state.strobeLightsOn and state.onTheGround)
1404
1405 def logFault(self, flight, aircraft, logger, oldState, state):
1406 """Log the fault."""
1407 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1408 flight.handleFault(StrobeLightsChecker, state.timestamp,
1409 FaultChecker._appendDuring(flight, message),
1410 1)
1411
1412#---------------------------------------------------------------------------------------
1413
1414class ThrustChecker(SimpleFaultChecker):
1415 """Check if the thrust setting is not too high during takeoff.
1416
1417 FIXME: is this really so general, for all aircraft?"""
1418 def isCondition(self, flight, aircraft, oldState, state):
1419 """Check if the fault condition holds."""
1420 return flight.stage==const.STAGE_TAKEOFF and \
1421 state.n1 is not None and max(state.n1)>97
1422
1423 def logFault(self, flight, aircraft, logger, oldState, state):
1424 """Log the fault."""
1425 flight.handleFault(ThrustChecker, state.timestamp,
1426 FaultChecker._appendDuring(flight,
1427 "Thrust setting was too high (>97%)"),
1428 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
1429
1430#---------------------------------------------------------------------------------------
1431
1432class VSChecker(SimpleFaultChecker):
1433 """Check if the vertical speed is not too low at certain altitudes"""
1434 BELOW10000 = -5000
1435 BELOW5000 = -2500
1436 BELOW2500 = -1500
1437 BELOW500 = -1000
1438 TOLERANCE = 1.2
1439
1440 def isCondition(self, flight, aircraft, oldState, state):
1441 """Check if the fault condition holds."""
1442 vs = state.smoothedVS
1443 altitude = state.altitude
1444 return vs < -8000 or vs > 8000 or \
1445 (altitude<500 and vs < (VSChecker.BELOW500 *
1446 VSChecker.TOLERANCE)) or \
1447 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1448 VSChecker.TOLERANCE)) or \
1449 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1450 VSChecker.TOLERANCE)) or \
1451 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1452 VSChecker.TOLERANCE))
1453
1454 def logFault(self, flight, aircraft, logger, oldState, state):
1455 """Log the fault."""
1456 vs = state.smoothedVS
1457
1458 message = "Vertical speed was %.0f feet/min" % (vs,)
1459 if vs>-8000 and vs<8000:
1460 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1461
1462 score = 10 if vs<-8000 or vs>8000 else 0
1463
1464 flight.handleFault(VSChecker, state.timestamp,
1465 FaultChecker._appendDuring(flight, message),
1466 score)
1467
1468#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.