source: src/mlx/checks.py@ 388:61c304ff3d63

Last change on this file since 388:61c304ff3d63 was 380:6d5595f2c24a, checked in by István Váradi <ivaradi@…>, 12 years ago

Added missing variable assignment (#155)

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