source: src/mlx/checks.py@ 363:2fa78f40f929

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

OBS values are printed with 3 digits (#145)

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