source: src/mlx/checks.py@ 415:77d406d7d593

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

Modified the landing lights checking (re #181)

File size: 62.8 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 message = "Altimeter: %.2f hPa at %.0f feet" % \
468 (logState.altimeter, logState.altitude)
469 if not logState.altimeterReliable:
470 message += " (u/r)"
471 return message
472
473#---------------------------------------------------------------------------------------
474
475class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
476 """Logger for NAV radios.
477
478 It also logs the OBS radial set."""
479 excludedStages = [const.STAGE_BOARDING, const.STAGE_PUSHANDTAXI,
480 const.STAGE_RTO, const.STAGE_TAXIAFTERLAND,
481 const.STAGE_PARKING]
482
483 @staticmethod
484 def getMessage(logName, frequency, obs):
485 """Get the message for the given NAV radio setting."""
486 message = u"%-5s %s" % (logName + ":", frequency)
487 if obs is not None: message += u" (%03d\u00b0)" % (obs,)
488 return message
489
490 def __init__(self, attrName, logName):
491 """Construct the NAV logger."""
492 StateChangeLogger.__init__(self, logInitial = False,
493 excludedStages = self.excludedStages)
494 DelayedChangeMixin.__init__(self)
495
496 self._getLogTimestamp = \
497 lambda state, forced: \
498 DelayedChangeMixin._getLogTimestamp(self, state, forced)
499
500 self._attrName = attrName
501 self._logName = logName
502
503 def _getValue(self, state):
504 """Get the value.
505
506 If both the frequency and the obs settings are available, a tuple
507 containing them is returned, otherwise None."""
508 frequency = getattr(state, self._attrName)
509 obs = getattr(state, self._attrName + "_obs")
510 manual = getattr(state, self._attrName + "_manual")
511 return (frequency, obs, manual)
512
513 def _getMessage(self, flight, state, forced):
514 """Get the message."""
515 (frequency, obs, manual) = self._getValue(state)
516 return None if frequency is None or obs is None or \
517 (not manual and not forced) else \
518 self.getMessage(self._logName, frequency, obs)
519
520 def _isDifferent(self, oldValue, newValue):
521 """Determine if the valie has changed between the given states."""
522 (oldFrequency, oldOBS, _oldManual) = oldValue
523 (newFrequency, newOBS, _newManual) = newValue
524 return oldFrequency!=newFrequency or oldOBS!=newOBS
525
526#---------------------------------------------------------------------------------------
527
528class ILSLogger(NAVLogger):
529 """Logger for the ILS radio setting."""
530 def __init__(self):
531 """Construct the logger."""
532 super(ILSLogger, self).__init__("ils", "ILS")
533
534#---------------------------------------------------------------------------------------
535
536class NAV1Logger(NAVLogger):
537 """Logger for the NAV1 radio setting."""
538 def __init__(self):
539 """Construct the logger."""
540 super(NAV1Logger, self).__init__("nav1", "NAV1")
541
542#---------------------------------------------------------------------------------------
543
544class NAV2Logger(NAVLogger):
545 """Logger for the NAV2 radio setting."""
546 def __init__(self):
547 """Construct the logger."""
548 super(NAV2Logger, self).__init__("nav2", "NAV2")
549
550#---------------------------------------------------------------------------------------
551
552class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
553 """Base class for the ADF loggers."""
554 def __init__(self, attr, logName):
555 """Construct the ADF logger."""
556 GenericStateChangeLogger.__init__(self, attr,
557 "%s: %%s" % (logName,),
558 logInitial = False,
559 excludedStages =
560 NAVLogger.excludedStages,
561 minDelay = 3.0, maxDelay = 10.0)
562
563#---------------------------------------------------------------------------------------
564
565class ADF1Logger(ADFLogger):
566 """Logger for the ADF1 radio setting."""
567 def __init__(self):
568 """Construct the logger."""
569 super(ADF1Logger, self).__init__("adf1", "ADF1")
570
571#---------------------------------------------------------------------------------------
572
573class ADF2Logger(ADFLogger):
574 """Logger for the ADF2 radio setting."""
575 def __init__(self):
576 """Construct the logger."""
577 super(ADF2Logger, self).__init__("adf2", "ADF2")
578
579#---------------------------------------------------------------------------------------
580
581class SquawkLogger(GenericStateChangeLogger):
582 """Logger for the squawk setting."""
583 def __init__(self):
584 """Construct the logger."""
585 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
586 minDelay = 3.0, maxDelay = 10.0)
587
588#---------------------------------------------------------------------------------------
589
590class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
591 """Base class for the loggers of the various lights."""
592 def __init__(self, attrName, template):
593 """Construct the logger."""
594 StateChangeLogger.__init__(self)
595 SingleValueMixin.__init__(self, attrName)
596
597 self._template = template
598
599 def _getMessage(self, flight, state, forced):
600 """Get the message from the given state."""
601 return self._template % ("ON" if self._getValue(state) else "OFF")
602
603#---------------------------------------------------------------------------------------
604
605class AnticollisionLightsLogger(LightsLogger):
606 """Logger for the anti-collision lights."""
607 def __init__(self):
608 LightsLogger.__init__(self, "antiCollisionLightsOn",
609 "Anti-collision lights: %s")
610
611#---------------------------------------------------------------------------------------
612
613class LandingLightsLogger(LightsLogger):
614 """Logger for the landing lights."""
615 def __init__(self):
616 LightsLogger.__init__(self, "landingLightsOn",
617 "Landing lights: %s")
618
619#---------------------------------------------------------------------------------------
620
621class StrobeLightsLogger(LightsLogger):
622 """Logger for the strobe lights."""
623 def __init__(self):
624 LightsLogger.__init__(self, "strobeLightsOn",
625 "Strobe lights: %s")
626
627#---------------------------------------------------------------------------------------
628
629class NavLightsLogger(LightsLogger):
630 """Logger for the navigational lights."""
631 def __init__(self):
632 LightsLogger.__init__(self, "navLightsOn",
633 "Navigational lights: %s")
634
635#---------------------------------------------------------------------------------------
636
637class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
638 """Logger for the flaps setting."""
639 def __init__(self):
640 """Construct the logger."""
641 StateChangeLogger.__init__(self, logInitial = True,
642 excludedStages =
643 [const.STAGE_BOARDING,
644 const.STAGE_PUSHANDTAXI,
645 const.STAGE_RTO,
646 const.STAGE_TAKEOFF])
647 SingleValueMixin.__init__(self, "flapsSet")
648
649 def _getMessage(self, flight, state, forced):
650 """Get the message to log on a change."""
651 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
652 return "Flaps %.0f - %.0f %s" % \
653 (state.flapsSet, flight.speedFromKnots(speed),
654 flight.getEnglishSpeedUnit())
655
656#---------------------------------------------------------------------------------------
657
658class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
659 """Logger for the gears state."""
660 def __init__(self):
661 """Construct the logger."""
662 StateChangeLogger.__init__(self, logInitial = True)
663 SingleValueMixin.__init__(self, "gearControlDown")
664
665 def _getMessage(self, flight, state, forced):
666 """Get the message to log on a change."""
667 return "Gears SET to %s at %.0f %s, %.0f feet" % \
668 ("DOWN" if state.gearControlDown else "UP",
669 flight.speedFromKnots(state.ias),
670 flight.getEnglishSpeedUnit(), state.altitude)
671
672#---------------------------------------------------------------------------------------
673
674class APLogger(StateChangeLogger, DelayedChangeMixin):
675 """Log the state of the autopilot."""
676 @staticmethod
677 def _logBoolean(logger, timestamp, what, value):
678 """Log a boolean value.
679
680 what is the name of the value that is being logged."""
681 message = what + " "
682 if value is None:
683 message += "cannot be detected, will not log"
684 else:
685 message += "is " + ("ON" if value else "OFF")
686 logger.message(timestamp, message)
687
688 @staticmethod
689 def _logNumeric(logger, timestamp, what, format, value):
690 """Log a numerical value."""
691 message = what
692 if value is None:
693 message += u" cannot be detected, will not log"
694 else:
695 message += u": " + format % (value,)
696 logger.message(timestamp, message)
697
698 @staticmethod
699 def _logAPMaster(logger, timestamp, state):
700 """Log the AP master state."""
701 APLogger._logBoolean(logger, timestamp, "AP master",
702 state.apMaster)
703
704 @staticmethod
705 def _logAPHeadingHold(logger, timestamp, state):
706 """Log the AP heading hold state."""
707 APLogger._logBoolean(logger, timestamp, "AP heading hold",
708 state.apHeadingHold)
709
710 @staticmethod
711 def _logAPHeading(logger, timestamp, state):
712 """Log the AP heading."""
713 APLogger._logNumeric(logger, timestamp, u"AP heading",
714 u"%03.0f\u00b0", state.apHeading)
715
716 @staticmethod
717 def _logAPAltitudeHold(logger, timestamp, state):
718 """Log the AP altitude hold state."""
719 APLogger._logBoolean(logger, timestamp, "AP altitude hold",
720 state.apAltitudeHold)
721
722 @staticmethod
723 def _logAPAltitude(logger, timestamp, state):
724 """Log the AP heading."""
725 APLogger._logNumeric(logger, timestamp, u"AP altitude",
726 u"%.0f ft", state.apAltitude)
727
728 def __init__(self):
729 """Construct the state logger."""
730 StateChangeLogger.__init__(self)
731 DelayedChangeMixin.__init__(self)
732 self._lastLoggedState = None
733 self.logState = lambda flight, logger, state:\
734 APLogger.logState(self, flight, logger, state)
735
736 def _getValue(self, state):
737 """Convert the relevant values from the given state into a tuple."""
738 return (state.apMaster,
739 state.apHeadingHold, state.apHeading,
740 state.apAltitudeHold, state.apAltitude)
741
742 def _isDifferent(self, oldValue, newValue):
743 """Determine if the given old and new values are different (enough) to
744 be logged."""
745 (oldAPMaster, oldAPHeadingHold, oldAPHeading,
746 oldAPAltitudeHold, oldAPAltitude) = oldValue
747 (apMaster, apHeadingHold, apHeading,
748 apAltitudeHold, apAltitude) = newValue
749
750 if apMaster is not None and apMaster!=oldAPMaster:
751 return True
752 if apMaster is False:
753 return False
754
755 if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
756 return True
757 if apHeadingHold is not False and apHeading is not None and \
758 apHeading!=oldAPHeading:
759 return True
760
761 if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
762 return True
763 if apAltitudeHold is not False and apAltitude is not None and \
764 apAltitude!=oldAPAltitude:
765 return True
766
767 return False
768
769 def logState(self, flight, logger, state):
770 """Log the autopilot state."""
771 timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
772 if self._lastLoggedState is None:
773 self._logAPMaster(logger, timestamp, state)
774 if state.apMaster is not False or state.apHeadingHold is None:
775 self._logAPHeadingHold(logger, timestamp, state)
776 if state.apMaster is not False and \
777 (state.apHeadingHold is not False or state.apHeading is None):
778 self._logAPHeading(logger, timestamp, state)
779 if state.apMaster is not False or state.apAltitudeHold is None:
780 self._logAPAltitudeHold(logger, timestamp, state)
781 if state.apMaster is not False and \
782 (state.apAltitudeHold is not False or state.apAltitude is None):
783 self._logAPAltitude(logger, timestamp, state)
784 self._firstCall = False
785 else:
786 oldState = self._lastLoggedState
787
788 apMasterTurnedOn = False
789 if state.apMaster is not None and state.apMaster!=oldState.apMaster:
790 apMasterTurnedOn = state.apMaster
791 self._logAPMaster(logger, timestamp, state)
792
793 if state.apMaster is not False:
794 apHeadingHoldTurnedOn = False
795 if state.apHeadingHold is not None and \
796 (state.apHeadingHold!=oldState.apHeadingHold or
797 apMasterTurnedOn):
798 apHeadingHoldTurnedOn = state.apHeadingHold
799 self._logAPHeadingHold(logger, timestamp, state)
800
801 if state.apHeadingHold is not False and \
802 state.apHeading is not None and \
803 (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
804 apHeadingHoldTurnedOn):
805 self._logAPHeading(logger, timestamp, state)
806
807 apAltitudeHoldTurnedOn = False
808 if state.apAltitudeHold is not None and \
809 (state.apAltitudeHold!=oldState.apAltitudeHold or
810 apMasterTurnedOn):
811 apAltitudeHoldTurnedOn = state.apAltitudeHold
812 self._logAPAltitudeHold(logger, timestamp, state)
813
814 if state.apAltitudeHold is not False and \
815 state.apAltitude is not None and \
816 (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
817 apAltitudeHoldTurnedOn):
818 self._logAPAltitude(logger, timestamp, state)
819
820 self._lastLoggedState = state
821
822#---------------------------------------------------------------------------------------
823
824class FaultChecker(StateChecker):
825 """Base class for checkers that look for faults."""
826 @staticmethod
827 def _appendDuring(flight, message):
828 """Append a 'during XXX' test to the given message, depending on the
829 flight stage."""
830 stageStr = const.stage2string(flight.stage)
831 return message if stageStr is None \
832 else (message + " during " + stageStr.upper())
833
834 @staticmethod
835 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
836 value):
837 """Get the score for a faulty value where the score is calculated
838 linearly within a certain range."""
839 if value<minFaultValue:
840 return 0
841 elif value>maxFaultValue:
842 return maxScore
843 else:
844 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
845 (maxFaultValue - minFaultValue)
846
847#---------------------------------------------------------------------------------------
848
849class SimpleFaultChecker(FaultChecker):
850 """Base class for fault checkers that check for a single occurence of a
851 faulty condition.
852
853 Child classes should implement the following functions:
854 - isCondition(self, flight, aircraft, oldState, state): should return whether the
855 condition holds
856 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
857 via the logger."""
858 def check(self, flight, aircraft, logger, oldState, state):
859 """Perform the check."""
860 if self.isCondition(flight, aircraft, oldState, state):
861 self.logFault(flight, aircraft, logger, oldState, state)
862
863#---------------------------------------------------------------------------------------
864
865class PatientFaultChecker(FaultChecker):
866 """A fault checker that does not decides on a fault when the condition
867 arises immediately, but can wait some time.
868
869 Child classes should implement the following functions:
870 - isCondition(self, flight, aircraft, oldState, state): should return whether the
871 condition holds
872 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
873 via the logger
874 """
875 def __init__(self, timeout = 2.0):
876 """Construct the fault checker with the given timeout."""
877 self._timeout = timeout
878 self._faultStarted = None
879
880 def getTimeout(self, flight, aircraft, oldState, state):
881 """Get the timeout.
882
883 This default implementation returns the timeout given in the
884 constructor, but child classes might want to enforce a different
885 policy."""
886 return self._timeout
887
888 def check(self, flight, aircraft, logger, oldState, state):
889 """Perform the check."""
890 if self.isCondition(flight, aircraft, oldState, state):
891 if self._faultStarted is None:
892 self._faultStarted = state.timestamp
893 timeout = self.getTimeout(flight, aircraft, oldState, state)
894 if state.timestamp>=(self._faultStarted + timeout):
895 self.logFault(flight, aircraft, logger, oldState, state)
896 self._faultStarted = state.timestamp
897 else:
898 self._faultStarted = None
899
900#---------------------------------------------------------------------------------------
901
902class AntiCollisionLightsChecker(PatientFaultChecker):
903 """Check for the anti-collision light being off at high N1 values."""
904 def isCondition(self, flight, aircraft, oldState, state):
905 """Check if the fault condition holds."""
906 return (flight.stage!=const.STAGE_PARKING or \
907 not flight.config.usingFS2Crew) and \
908 not state.antiCollisionLightsOn and \
909 self.isEngineCondition(state)
910
911 def logFault(self, flight, aircraft, logger, oldState, state):
912 """Log the fault."""
913 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
914 FaultChecker._appendDuring(flight,
915 "Anti-collision lights were off"),
916 1)
917
918 def isEngineCondition(self, state):
919 """Determine if the engines are in such a state that the lights should
920 be on."""
921 if state.n1 is not None:
922 return max(state.n1)>5
923 elif state.rpm is not None:
924 return max(state.rpm)>0
925 else:
926 return False
927
928#---------------------------------------------------------------------------------------
929
930class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
931 """Check for the anti-collision light for Tuplev planes."""
932 def isCondition(self, flight, aircraft, oldState, state):
933 """Check if the fault condition holds."""
934 numEnginesRunning = 0
935 for n1 in state.n1:
936 if n1>5: numEnginesRunning += 1
937
938 if flight.stage==const.STAGE_PARKING:
939 return numEnginesRunning<len(state.n1) \
940 and state.antiCollisionLightsOn
941 else:
942 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
943 numEnginesRunning<1 and state.antiCollisionLightsOn
944
945#---------------------------------------------------------------------------------------
946
947class BankChecker(SimpleFaultChecker):
948 """Check for the bank is within limits."""
949 def isCondition(self, flight, aircraft, oldState, state):
950 """Check if the fault condition holds."""
951 if flight.stage==const.STAGE_CRUISE:
952 bankLimit = 30
953 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
954 const.STAGE_DESCENT, const.STAGE_LANDING]:
955 bankLimit = 35
956 else:
957 return False
958
959 return state.bank>bankLimit or state.bank<-bankLimit
960
961 def logFault(self, flight, aircraft, logger, oldState, state):
962 """Log the fault."""
963 flight.handleFault(BankChecker, state.timestamp,
964 FaultChecker._appendDuring(flight, "Bank too steep"),
965 2)
966
967#---------------------------------------------------------------------------------------
968
969class FlapsRetractChecker(SimpleFaultChecker):
970 """Check if the flaps are not retracted too early."""
971 def __init__(self):
972 """Construct the flaps checker."""
973 self._timeStart = None
974
975 def isCondition(self, flight, aircraft, oldState, state):
976 """Check if the fault condition holds.
977
978 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
979 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround and
980 aircraft.type!=const.AIRCRAFT_F70) or \
981 (flight.stage==const.STAGE_LANDING and state.onTheGround):
982 if self._timeStart is None:
983 self._timeStart = state.timestamp
984
985 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
986 return True
987 else:
988 self._timeStart = None
989 return False
990
991 def logFault(self, flight, aircraft, logger, oldState, state):
992 """Log the fault."""
993 flight.handleFault(FlapsRetractChecker, state.timestamp,
994 FaultChecker._appendDuring(flight, "Flaps retracted"),
995 20)
996
997#---------------------------------------------------------------------------------------
998
999class FlapsSpeedLimitChecker(SimpleFaultChecker):
1000 """Check if the flaps are extended only at the right speeds."""
1001 def isCondition(self, flight, aircraft, oldState, state):
1002 """Check if the fault condition holds."""
1003 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
1004 return speedLimit is not None and state.smoothedIAS>speedLimit
1005
1006 def logFault(self, flight, aircraft, logger, oldState, state):
1007 """Log the fault."""
1008 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
1009 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
1010 5)
1011
1012#---------------------------------------------------------------------------------------
1013
1014class GearsDownChecker(SimpleFaultChecker):
1015 """Check if the gears are down at low altitudes."""
1016 def isCondition(self, flight, aircraft, oldState, state):
1017 """Check if the fault condition holds."""
1018 return state.radioAltitude<10 and not state.gearsDown and \
1019 flight.stage!=const.STAGE_TAKEOFF
1020
1021 def logFault(self, flight, aircraft, logger, oldState, state):
1022 """Log the fault."""
1023 flight.handleNoGo(GearsDownChecker, state.timestamp,
1024 "Gears not down at %.0f feet radio altitude" % \
1025 (state.radioAltitude,),
1026 "GEAR DOWN NO GO")
1027
1028#---------------------------------------------------------------------------------------
1029
1030class GearSpeedLimitChecker(PatientFaultChecker):
1031 """Check if the gears not down at too high a speed."""
1032 def isCondition(self, flight, aircraft, oldState, state):
1033 """Check if the fault condition holds."""
1034 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
1035
1036 def logFault(self, flight, aircraft, logger, oldState, state):
1037 """Log the fault."""
1038 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
1039 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
1040 5)
1041
1042#---------------------------------------------------------------------------------------
1043
1044class GLoadChecker(SimpleFaultChecker):
1045 """Check if the G-load does not exceed 2 except during flare."""
1046 def isCondition(self, flight, aircraft, oldState, state):
1047 """Check if the fault condition holds."""
1048 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
1049 state.radioAltitude>=50)
1050
1051 def logFault(self, flight, aircraft, logger, oldState, state):
1052 """Log the fault."""
1053 flight.handleFault(GLoadChecker, state.timestamp,
1054 "G-load was %.2f" % (state.gLoad,),
1055 10)
1056
1057#---------------------------------------------------------------------------------------
1058
1059class LandingLightsChecker(PatientFaultChecker):
1060 """Check if the landing lights are used properly."""
1061 def getTimeout(self, flight, aircraft, oldState, state):
1062 """Get the timeout.
1063
1064 It is the default timeout except for landing and takeoff."""
1065 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
1066 const.STAGE_LANDING] else self._timeout
1067
1068 def isCondition(self, flight, aircraft, oldState, state):
1069 """Check if the fault condition holds."""
1070 return state.landingLightsOn is not None and \
1071 ((flight.stage==const.STAGE_BOARDING and \
1072 state.landingLightsOn and state.onTheGround) or \
1073 (flight.stage==const.STAGE_TAKEOFF and \
1074 not state.landingLightsOn and not state.onTheGround) or \
1075 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1076 const.STAGE_DESCENT] and \
1077 state.landingLightsOn and state.altitude>12500) or \
1078 (flight.stage==const.STAGE_LANDING and \
1079 not state.landingLightsOn and state.onTheGround and
1080 state.groundSpeed>50.0) or \
1081 (flight.stage==const.STAGE_PARKING and \
1082 state.landingLightsOn and state.onTheGround))
1083
1084 def logFault(self, flight, aircraft, logger, oldState, state):
1085 """Log the fault."""
1086 score = 0 if flight.stage in [const.STAGE_TAKEOFF,
1087 const.STAGE_LANDING] else 1
1088 message = "Landing lights were %s" % \
1089 (("on" if state.landingLightsOn else "off"),)
1090 flight.handleFault(LandingLightsChecker, state.timestamp,
1091 FaultChecker._appendDuring(flight, message),
1092 score)
1093
1094#---------------------------------------------------------------------------------------
1095
1096class TransponderChecker(PatientFaultChecker):
1097 """Check if the transponder is used properly."""
1098 def __init__(self):
1099 """Construct the transponder checker."""
1100 super(TransponderChecker, self).__init__()
1101 self._liftOffTime = None
1102
1103 def isCondition(self, flight, aircraft, oldState, state):
1104 """Check if the fault condition holds."""
1105 if state.onTheGround:
1106 self._liftOffTime = None
1107 elif self._liftOffTime is None:
1108 self._liftOffTime = state.timestamp
1109
1110 return state.xpdrC is not None and \
1111 ((state.xpdrC and flight.stage in
1112 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
1113 (not state.xpdrC and
1114 (flight.stage in
1115 [const.STAGE_CRUISE, const.STAGE_DESCENT,
1116 const.STAGE_LANDING, const.STAGE_GOAROUND] or \
1117 ((not state.autoXPDR or \
1118 (self._liftOffTime is not None and
1119 state.timestamp > (self._liftOffTime+8))) and \
1120 flight.stage in
1121 [const.STAGE_TAKEOFF, const.STAGE_RTO, const.STAGE_CLIMB])
1122 )
1123 )
1124 )
1125
1126 def logFault(self, flight, aircraft, logger, oldState, state):
1127 """Log the fault."""
1128 score = 0
1129 message = "Transponder was %s" % \
1130 (("mode C" if state.xpdrC else "standby"),)
1131 flight.handleFault(TransponderChecker, state.timestamp,
1132 FaultChecker._appendDuring(flight, message),
1133 score)
1134
1135#---------------------------------------------------------------------------------------
1136
1137class WeightChecker(PatientFaultChecker):
1138 """Base class for checkers that check that some limit is not exceeded."""
1139 def __init__(self, name):
1140 """Construct the checker."""
1141 super(WeightChecker, self).__init__(timeout = 5.0)
1142 self._name = name
1143
1144 def isCondition(self, flight, aircraft, oldState, state):
1145 """Check if the fault condition holds."""
1146 if flight.entranceExam:
1147 return False
1148
1149 limit = self.getLimit(flight, aircraft, state)
1150 if limit is not None:
1151 #if flight.options.compensation is not None:
1152 # limit += flight.options.compensation
1153 return self.getWeight(state)>limit
1154
1155 return False
1156
1157 def logFault(self, flight, aircraft, logger, oldState, state):
1158 """Log the fault."""
1159 mname = "M" + self._name
1160 flight.handleNoGo(self.__class__, state.timestamp,
1161 "%s exceeded: %s is %.0f kg" % \
1162 (mname, self._name, self.getWeight(state)),
1163 "%s NO GO" % (mname,))
1164
1165 def getWeight(self, state):
1166 """Get the weight that is interesting for us."""
1167 return state.grossWeight
1168
1169#---------------------------------------------------------------------------------------
1170
1171class MLWChecker(WeightChecker):
1172 """Checks if the MLW is not exceeded on landing."""
1173 def __init__(self):
1174 """Construct the checker."""
1175 super(MLWChecker, self).__init__("LW")
1176
1177 def getLimit(self, flight, aircraft, state):
1178 """Get the limit if we are in the right state."""
1179 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
1180 state.onTheGround and \
1181 not flight.entranceExam else None
1182
1183#---------------------------------------------------------------------------------------
1184
1185class MTOWChecker(WeightChecker):
1186 """Checks if the MTOW is not exceeded on landing."""
1187 def __init__(self):
1188 """Construct the checker."""
1189 super(MTOWChecker, self).__init__("TOW")
1190
1191 def getLimit(self, flight, aircraft, state):
1192 """Get the limit if we are in the right state."""
1193 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1194 not flight.entranceExam else None
1195
1196#---------------------------------------------------------------------------------------
1197
1198class MZFWChecker(WeightChecker):
1199 """Checks if the MZFW is not exceeded on landing."""
1200 def __init__(self):
1201 """Construct the checker."""
1202 super(MZFWChecker, self).__init__("ZFW")
1203
1204 def getLimit(self, flight, aircraft, state):
1205 """Get the limit if we are in the right state."""
1206 return aircraft.mzfw if not flight.entranceExam else None
1207
1208 def getWeight(self, state):
1209 """Get the weight that is interesting for us."""
1210 return state.zfw
1211
1212#---------------------------------------------------------------------------------------
1213
1214class NavLightsChecker(PatientFaultChecker):
1215 """Check if the navigational lights are used properly."""
1216 def isCondition(self, flight, aircraft, oldState, state):
1217 """Check if the fault condition holds."""
1218 return flight.stage!=const.STAGE_BOARDING and \
1219 flight.stage!=const.STAGE_PARKING and \
1220 state.navLightsOn is False
1221
1222 def logFault(self, flight, aircraft, logger, oldState, state):
1223 """Log the fault."""
1224 flight.handleFault(NavLightsChecker, state.timestamp,
1225 FaultChecker._appendDuring(flight,
1226 "Navigation lights were off"),
1227 1)
1228
1229#---------------------------------------------------------------------------------------
1230
1231class OverspeedChecker(PatientFaultChecker):
1232 """Check if Vne has been exceeded."""
1233 def __init__(self, timeout = 5.0):
1234 """Construct the checker."""
1235 super(OverspeedChecker, self).__init__(timeout = timeout)
1236
1237 def isCondition(self, flight, aircraft, oldState, state):
1238 """Check if the fault condition holds."""
1239 return state.overspeed
1240
1241 def logFault(self, flight, aircraft, logger, oldState, state):
1242 """Log the fault."""
1243 flight.handleFault(OverspeedChecker, state.timestamp,
1244 FaultChecker._appendDuring(flight, "Overspeed"),
1245 20)
1246
1247#---------------------------------------------------------------------------------------
1248
1249class PayloadChecker(SimpleFaultChecker):
1250 """Check if the payload matches the specification."""
1251 TOLERANCE=550
1252
1253 @staticmethod
1254 def isZFWFaulty(aircraftZFW, flightZFW):
1255 """Check if the given aircraft's ZFW is outside of the limits."""
1256 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1257 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1258
1259 def isCondition(self, flight, aircraft, oldState, state):
1260 """Check if the fault condition holds."""
1261 return not flight.entranceExam and \
1262 flight.stage==const.STAGE_PUSHANDTAXI and \
1263 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1264
1265 def logFault(self, flight, aircraft, logger, oldState, state):
1266 """Log the fault."""
1267 flight.handleNoGo(PayloadChecker, state.timestamp,
1268 "ZFW difference is more than %d kgs" % \
1269 (PayloadChecker.TOLERANCE,),
1270 "ZFW NO GO")
1271
1272#---------------------------------------------------------------------------------------
1273
1274class PitotChecker(PatientFaultChecker):
1275 """Check if pitot heat is on."""
1276 def __init__(self):
1277 """Construct the checker."""
1278 super(PitotChecker, self).__init__(timeout = 3.0)
1279
1280 def isCondition(self, flight, aircraft, oldState, state):
1281 """Check if the fault condition holds."""
1282 return state.groundSpeed>80 and not state.pitotHeatOn
1283
1284 def logFault(self, flight, aircraft, logger, oldState, state):
1285 """Log the fault."""
1286 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1287 const.STAGE_CRUISE, const.STAGE_DESCENT,
1288 const.STAGE_LANDING] else 0
1289 flight.handleFault(PitotChecker, state.timestamp,
1290 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1291 score)
1292
1293#---------------------------------------------------------------------------------------
1294
1295class ReverserChecker(SimpleFaultChecker):
1296 """Check if the reverser is not used below the speed prescribed for the
1297 aircraft."""
1298 def isCondition(self, flight, aircraft, oldState, state):
1299 """Check if the fault condition holds."""
1300 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1301 const.STAGE_TAXIAFTERLAND] and \
1302 state.groundSpeed<aircraft.reverseMinSpeed and max(state.reverser)
1303
1304 def logFault(self, flight, aircraft, logger, oldState, state):
1305 """Log the fault."""
1306 message = "Reverser used below %.0f %s" % \
1307 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
1308 flight.handleFault(ReverserChecker, state.timestamp,
1309 FaultChecker._appendDuring(flight, message),
1310 15)
1311
1312#---------------------------------------------------------------------------------------
1313
1314class SpeedChecker(SimpleFaultChecker):
1315 """Check if the speed is in the prescribed limits."""
1316 @staticmethod
1317 def logSpeedFault(flight, state, stage = None, updateID = None):
1318 """Log the speed fault."""
1319 message = "Taxi speed over %.0f %s" % \
1320 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1321 if stage is None:
1322 stage = flight.stage
1323 score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
1324 return flight.handleFault((SpeedChecker, stage), state.timestamp,
1325 FaultChecker._appendDuring(flight, message),
1326 score, updatePrevious = updateID is None,
1327 updateID = updateID)
1328
1329 def isCondition(self, flight, aircraft, oldState, state):
1330 """Check if the fault condition holds."""
1331 return flight.stage in [const.STAGE_PUSHANDTAXI,
1332 const.STAGE_RTO,
1333 const.STAGE_TAXIAFTERLAND] and \
1334 state.groundSpeed>50
1335
1336 def logFault(self, flight, aircraft, logger, oldState, state):
1337 """Log the fault."""
1338 self.logSpeedFault(flight, state)
1339
1340#---------------------------------------------------------------------------------------
1341
1342class NoStrobeSpeedChecker(StateChecker):
1343 """Checker for the ground speed of aircraft that have no strobe lights by
1344 which to detect the takeoff stage.
1345
1346 If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state as
1347 saved as a provisional takeoff state. If the speed then decreases below 50
1348 knots, or the plane remains on the ground for more than 20 seconds, a taxi
1349 speed error is logged with the highest ground speed detected. This state is
1350 also stored in the flight object as a possible
1351
1352 If the plane becomes airborne within 20 seconds, the stage becomes TAKEOFF,
1353 and the previously saved takeoff state is logged.
1354
1355 During the TAXIAFTERLAND stage, speed is checked as in case of
1356 SpeedChecker."""
1357 def __init__(self):
1358 """Initialize the speed checker."""
1359 self._takeoffState = None
1360 self._highestSpeedState = None
1361
1362 def check(self, flight, aircraft, logger, oldState, state):
1363 """Check the state as described above."""
1364 if flight.stage==const.STAGE_PUSHANDTAXI or \
1365 flight.stage==const.STAGE_RTO:
1366 self._checkPushAndTaxi(flight, aircraft, state)
1367 elif flight.stage==const.STAGE_TAXIAFTERLAND:
1368 if state.groundSpeed>50:
1369 SpeedChecker.logSpeedFault(flight, state)
1370
1371 def _checkPushAndTaxi(self, flight, aircraft, state):
1372 """Check the speed during the push and taxi stage."""
1373 if state.groundSpeed>50:
1374 if self._takeoffState is None:
1375 self._takeoffState = state
1376 self._highestSpeedState = state
1377 else:
1378 if state.groundSpeed>self._highestSpeedState.groundSpeed:
1379 self._highestSpeedState = state
1380
1381 if not state.onTheGround:
1382 aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
1383 self._takeoffState = None
1384 elif state.timestamp > (self._takeoffState.timestamp + 30):
1385 flight.setRTOState(self._highestSpeedState)
1386 elif self._takeoffState is not None:
1387 flight.setRTOState(self._highestSpeedState)
1388 self._takeoffState = None
1389
1390#---------------------------------------------------------------------------------------
1391
1392class StallChecker(PatientFaultChecker):
1393 """Check if stall occured."""
1394 def isCondition(self, flight, aircraft, oldState, state):
1395 """Check if the fault condition holds."""
1396 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1397 const.STAGE_CRUISE, const.STAGE_DESCENT,
1398 const.STAGE_LANDING] and state.stalled
1399
1400 def logFault(self, flight, aircraft, logger, oldState, state):
1401 """Log the fault."""
1402 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1403 const.STAGE_LANDING] else 30
1404 flight.handleFault(StallChecker, state.timestamp,
1405 FaultChecker._appendDuring(flight, "Stalled"),
1406 score)
1407
1408#---------------------------------------------------------------------------------------
1409
1410class StrobeLightsChecker(PatientFaultChecker):
1411 """Check if the strobe lights are used properly."""
1412 def isCondition(self, flight, aircraft, oldState, state):
1413 """Check if the fault condition holds."""
1414 return (flight.stage==const.STAGE_BOARDING and \
1415 state.strobeLightsOn and state.onTheGround) or \
1416 (flight.stage==const.STAGE_TAKEOFF and \
1417 not state.strobeLightsOn and not state.gearsDown) or \
1418 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1419 const.STAGE_DESCENT] and \
1420 not state.strobeLightsOn and not state.onTheGround) or \
1421 (flight.stage==const.STAGE_PARKING and \
1422 state.strobeLightsOn and state.onTheGround)
1423
1424 def logFault(self, flight, aircraft, logger, oldState, state):
1425 """Log the fault."""
1426 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1427 flight.handleFault(StrobeLightsChecker, state.timestamp,
1428 FaultChecker._appendDuring(flight, message),
1429 1)
1430
1431#---------------------------------------------------------------------------------------
1432
1433class ThrustChecker(SimpleFaultChecker):
1434 """Check if the thrust setting is not too high during takeoff.
1435
1436 FIXME: is this really so general, for all aircraft?"""
1437 def isCondition(self, flight, aircraft, oldState, state):
1438 """Check if the fault condition holds."""
1439 return flight.stage==const.STAGE_TAKEOFF and \
1440 state.n1 is not None and max(state.n1)>97
1441
1442 def logFault(self, flight, aircraft, logger, oldState, state):
1443 """Log the fault."""
1444 flight.handleFault(ThrustChecker, state.timestamp,
1445 FaultChecker._appendDuring(flight,
1446 "Thrust setting was too high (>97%)"),
1447 FaultChecker._getLinearScore(97, 110, 0, 10,
1448 max(state.n1)),
1449 updatePrevious = True)
1450
1451#---------------------------------------------------------------------------------------
1452
1453class VSChecker(SimpleFaultChecker):
1454 """Check if the vertical speed is not too low at certain altitudes"""
1455 BELOW10000 = -5000
1456 BELOW5000 = -2500
1457 BELOW2500 = -1500
1458 BELOW500 = -1000
1459 TOLERANCE = 1.2
1460
1461 def isCondition(self, flight, aircraft, oldState, state):
1462 """Check if the fault condition holds."""
1463 vs = state.smoothedVS
1464 altitude = state.altitude
1465 return vs < -8000 or vs > 8000 or \
1466 (altitude<500 and vs < (VSChecker.BELOW500 *
1467 VSChecker.TOLERANCE)) or \
1468 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1469 VSChecker.TOLERANCE)) or \
1470 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1471 VSChecker.TOLERANCE)) or \
1472 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1473 VSChecker.TOLERANCE))
1474
1475 def logFault(self, flight, aircraft, logger, oldState, state):
1476 """Log the fault."""
1477 vs = state.smoothedVS
1478
1479 message = "Vertical speed was %.0f feet/min" % (vs,)
1480 if vs>-8000 and vs<8000:
1481 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1482
1483 score = 10 if vs<-8000 or vs>8000 else 0
1484
1485 flight.handleFault(VSChecker, state.timestamp,
1486 FaultChecker._appendDuring(flight, message),
1487 score)
1488
1489#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.