source: src/mlx/checks.py@ 379:476e04537509

Last change on this file since 379:476e04537509 was 375:9db0cb4a0040, checked in by István Váradi <ivaradi@…>, 12 years ago

If the landing light is off during takeoff, it scores only 0 (#156)

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