source: src/mlx/checks.py@ 374:a505684c3723

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

Added guard against flooding the web handler with ACARS sending request in case of a network problem (#155)

File size: 62.5 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==const.STAGE_LANDING else 1
1082 message = "Landing lights were %s" % \
1083 (("on" if state.landingLightsOn else "off"),)
1084 flight.handleFault(LandingLightsChecker, state.timestamp,
1085 FaultChecker._appendDuring(flight, message),
1086 score)
1087
1088#---------------------------------------------------------------------------------------
1089
1090class TransponderChecker(PatientFaultChecker):
1091 """Check if the transponder is used properly."""
1092 def __init__(self):
1093 """Construct the transponder checker."""
1094 super(TransponderChecker, self).__init__()
1095 self._liftOffTime = None
1096
1097 def isCondition(self, flight, aircraft, oldState, state):
1098 """Check if the fault condition holds."""
1099 if state.onTheGround:
1100 self._liftOffTime = None
1101 elif self._liftOffTime is None:
1102 self._liftOffTime = state.timestamp
1103
1104 return state.xpdrC is not None and \
1105 ((state.xpdrC and flight.stage in
1106 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
1107 (not state.xpdrC and
1108 (flight.stage in
1109 [const.STAGE_CRUISE, const.STAGE_DESCENT,
1110 const.STAGE_LANDING, const.STAGE_GOAROUND] or \
1111 ((not state.autoXPDR or \
1112 (self._liftOffTime is not None and
1113 state.timestamp > (self._liftOffTime+8))) and \
1114 flight.stage in
1115 [const.STAGE_TAKEOFF, const.STAGE_RTO, const.STAGE_CLIMB])
1116 )
1117 )
1118 )
1119
1120 def logFault(self, flight, aircraft, logger, oldState, state):
1121 """Log the fault."""
1122 score = 0
1123 message = "Transponder was %s" % \
1124 (("mode C" if state.xpdrC else "standby"),)
1125 flight.handleFault(TransponderChecker, state.timestamp,
1126 FaultChecker._appendDuring(flight, message),
1127 score)
1128
1129#---------------------------------------------------------------------------------------
1130
1131class WeightChecker(PatientFaultChecker):
1132 """Base class for checkers that check that some limit is not exceeded."""
1133 def __init__(self, name):
1134 """Construct the checker."""
1135 super(WeightChecker, self).__init__(timeout = 5.0)
1136 self._name = name
1137
1138 def isCondition(self, flight, aircraft, oldState, state):
1139 """Check if the fault condition holds."""
1140 if flight.entranceExam:
1141 return False
1142
1143 limit = self.getLimit(flight, aircraft, state)
1144 if limit is not None:
1145 #if flight.options.compensation is not None:
1146 # limit += flight.options.compensation
1147 return self.getWeight(state)>limit
1148
1149 return False
1150
1151 def logFault(self, flight, aircraft, logger, oldState, state):
1152 """Log the fault."""
1153 mname = "M" + self._name
1154 flight.handleNoGo(self.__class__, state.timestamp,
1155 "%s exceeded: %s is %.0f kg" % \
1156 (mname, self._name, self.getWeight(state)),
1157 "%s NO GO" % (mname,))
1158
1159 def getWeight(self, state):
1160 """Get the weight that is interesting for us."""
1161 return state.grossWeight
1162
1163#---------------------------------------------------------------------------------------
1164
1165class MLWChecker(WeightChecker):
1166 """Checks if the MLW is not exceeded on landing."""
1167 def __init__(self):
1168 """Construct the checker."""
1169 super(MLWChecker, self).__init__("LW")
1170
1171 def getLimit(self, flight, aircraft, state):
1172 """Get the limit if we are in the right state."""
1173 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
1174 state.onTheGround and \
1175 not flight.entranceExam else None
1176
1177#---------------------------------------------------------------------------------------
1178
1179class MTOWChecker(WeightChecker):
1180 """Checks if the MTOW is not exceeded on landing."""
1181 def __init__(self):
1182 """Construct the checker."""
1183 super(MTOWChecker, self).__init__("TOW")
1184
1185 def getLimit(self, flight, aircraft, state):
1186 """Get the limit if we are in the right state."""
1187 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1188 not flight.entranceExam else None
1189
1190#---------------------------------------------------------------------------------------
1191
1192class MZFWChecker(WeightChecker):
1193 """Checks if the MZFW is not exceeded on landing."""
1194 def __init__(self):
1195 """Construct the checker."""
1196 super(MZFWChecker, self).__init__("ZFW")
1197
1198 def getLimit(self, flight, aircraft, state):
1199 """Get the limit if we are in the right state."""
1200 return aircraft.mzfw if not flight.entranceExam else None
1201
1202 def getWeight(self, state):
1203 """Get the weight that is interesting for us."""
1204 return state.zfw
1205
1206#---------------------------------------------------------------------------------------
1207
1208class NavLightsChecker(PatientFaultChecker):
1209 """Check if the navigational lights are used properly."""
1210 def isCondition(self, flight, aircraft, oldState, state):
1211 """Check if the fault condition holds."""
1212 return flight.stage!=const.STAGE_BOARDING and \
1213 flight.stage!=const.STAGE_PARKING and \
1214 state.navLightsOn is False
1215
1216 def logFault(self, flight, aircraft, logger, oldState, state):
1217 """Log the fault."""
1218 flight.handleFault(NavLightsChecker, state.timestamp,
1219 FaultChecker._appendDuring(flight,
1220 "Navigation lights were off"),
1221 1)
1222
1223#---------------------------------------------------------------------------------------
1224
1225class OverspeedChecker(PatientFaultChecker):
1226 """Check if Vne has been exceeded."""
1227 def __init__(self, timeout = 5.0):
1228 """Construct the checker."""
1229 super(OverspeedChecker, self).__init__(timeout = timeout)
1230
1231 def isCondition(self, flight, aircraft, oldState, state):
1232 """Check if the fault condition holds."""
1233 return state.overspeed
1234
1235 def logFault(self, flight, aircraft, logger, oldState, state):
1236 """Log the fault."""
1237 flight.handleFault(OverspeedChecker, state.timestamp,
1238 FaultChecker._appendDuring(flight, "Overspeed"),
1239 20)
1240
1241#---------------------------------------------------------------------------------------
1242
1243class PayloadChecker(SimpleFaultChecker):
1244 """Check if the payload matches the specification."""
1245 TOLERANCE=550
1246
1247 @staticmethod
1248 def isZFWFaulty(aircraftZFW, flightZFW):
1249 """Check if the given aircraft's ZFW is outside of the limits."""
1250 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1251 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1252
1253 def isCondition(self, flight, aircraft, oldState, state):
1254 """Check if the fault condition holds."""
1255 return not flight.entranceExam and \
1256 flight.stage==const.STAGE_PUSHANDTAXI and \
1257 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1258
1259 def logFault(self, flight, aircraft, logger, oldState, state):
1260 """Log the fault."""
1261 flight.handleNoGo(PayloadChecker, state.timestamp,
1262 "ZFW difference is more than %d kgs" % \
1263 (PayloadChecker.TOLERANCE,),
1264 "ZFW NO GO")
1265
1266#---------------------------------------------------------------------------------------
1267
1268class PitotChecker(PatientFaultChecker):
1269 """Check if pitot heat is on."""
1270 def __init__(self):
1271 """Construct the checker."""
1272 super(PitotChecker, self).__init__(timeout = 3.0)
1273
1274 def isCondition(self, flight, aircraft, oldState, state):
1275 """Check if the fault condition holds."""
1276 return state.groundSpeed>80 and not state.pitotHeatOn
1277
1278 def logFault(self, flight, aircraft, logger, oldState, state):
1279 """Log the fault."""
1280 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1281 const.STAGE_CRUISE, const.STAGE_DESCENT,
1282 const.STAGE_LANDING] else 0
1283 flight.handleFault(PitotChecker, state.timestamp,
1284 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1285 score)
1286
1287#---------------------------------------------------------------------------------------
1288
1289class ReverserChecker(SimpleFaultChecker):
1290 """Check if the reverser is not used below the speed prescribed for the
1291 aircraft."""
1292 def isCondition(self, flight, aircraft, oldState, state):
1293 """Check if the fault condition holds."""
1294 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1295 const.STAGE_TAXIAFTERLAND] and \
1296 state.groundSpeed<aircraft.reverseMinSpeed and max(state.reverser)
1297
1298 def logFault(self, flight, aircraft, logger, oldState, state):
1299 """Log the fault."""
1300 message = "Reverser used below %.0f %s" % \
1301 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
1302 flight.handleFault(ReverserChecker, state.timestamp,
1303 FaultChecker._appendDuring(flight, message),
1304 15)
1305
1306#---------------------------------------------------------------------------------------
1307
1308class SpeedChecker(SimpleFaultChecker):
1309 """Check if the speed is in the prescribed limits."""
1310 @staticmethod
1311 def logSpeedFault(flight, state, stage = None, updateID = None):
1312 """Log the speed fault."""
1313 message = "Taxi speed over %.0f %s" % \
1314 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1315 if stage is None:
1316 stage = flight.stage
1317 score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
1318 return flight.handleFault((SpeedChecker, stage), state.timestamp,
1319 FaultChecker._appendDuring(flight, message),
1320 score, updatePrevious = updateID is None,
1321 updateID = updateID)
1322
1323 def isCondition(self, flight, aircraft, oldState, state):
1324 """Check if the fault condition holds."""
1325 return flight.stage in [const.STAGE_PUSHANDTAXI,
1326 const.STAGE_RTO,
1327 const.STAGE_TAXIAFTERLAND] and \
1328 state.groundSpeed>50
1329
1330 def logFault(self, flight, aircraft, logger, oldState, state):
1331 """Log the fault."""
1332 self.logSpeedFault(flight, state)
1333
1334#---------------------------------------------------------------------------------------
1335
1336class NoStrobeSpeedChecker(StateChecker):
1337 """Checker for the ground speed of aircraft that have no strobe lights by
1338 which to detect the takeoff stage.
1339
1340 If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state as
1341 saved as a provisional takeoff state. If the speed then decreases below 50
1342 knots, or the plane remains on the ground for more than 20 seconds, a taxi
1343 speed error is logged with the highest ground speed detected. This state is
1344 also stored in the flight object as a possible
1345
1346 If the plane becomes airborne within 20 seconds, the stage becomes TAKEOFF,
1347 and the previously saved takeoff state is logged.
1348
1349 During the TAXIAFTERLAND stage, speed is checked as in case of
1350 SpeedChecker."""
1351 def __init__(self):
1352 """Initialize the speed checker."""
1353 self._takeoffState = None
1354 self._highestSpeedState = None
1355
1356 def check(self, flight, aircraft, logger, oldState, state):
1357 """Check the state as described above."""
1358 if flight.stage==const.STAGE_PUSHANDTAXI or \
1359 flight.stage==const.STAGE_RTO:
1360 self._checkPushAndTaxi(flight, aircraft, state)
1361 elif flight.stage==const.STAGE_TAXIAFTERLAND:
1362 if state.groundSpeed>50:
1363 SpeedChecker.logSpeedFault(flight, state)
1364
1365 def _checkPushAndTaxi(self, flight, aircraft, state):
1366 """Check the speed during the push and taxi stage."""
1367 if state.groundSpeed>50:
1368 if self._takeoffState is None:
1369 self._takeoffState = state
1370 self._highestSpeedState = state
1371 else:
1372 if state.groundSpeed>self._highestSpeedState.groundSpeed:
1373 self._highestSpeedState = state
1374
1375 if state.timestamp > (self._takeoffState.timestamp + 20):
1376 flight.setRTOState(self._highestSpeedState)
1377 elif not state.onTheGround:
1378 aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
1379 self._takeoffState = None
1380 elif self._takeoffState is not None:
1381 flight.setRTOState(self._highestSpeedState)
1382 self._takeoffState = None
1383
1384#---------------------------------------------------------------------------------------
1385
1386class StallChecker(PatientFaultChecker):
1387 """Check if stall occured."""
1388 def isCondition(self, flight, aircraft, oldState, state):
1389 """Check if the fault condition holds."""
1390 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1391 const.STAGE_CRUISE, const.STAGE_DESCENT,
1392 const.STAGE_LANDING] and state.stalled
1393
1394 def logFault(self, flight, aircraft, logger, oldState, state):
1395 """Log the fault."""
1396 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1397 const.STAGE_LANDING] else 30
1398 flight.handleFault(StallChecker, state.timestamp,
1399 FaultChecker._appendDuring(flight, "Stalled"),
1400 score)
1401
1402#---------------------------------------------------------------------------------------
1403
1404class StrobeLightsChecker(PatientFaultChecker):
1405 """Check if the strobe lights are used properly."""
1406 def isCondition(self, flight, aircraft, oldState, state):
1407 """Check if the fault condition holds."""
1408 return (flight.stage==const.STAGE_BOARDING and \
1409 state.strobeLightsOn and state.onTheGround) or \
1410 (flight.stage==const.STAGE_TAKEOFF and \
1411 not state.strobeLightsOn and not state.gearsDown) or \
1412 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1413 const.STAGE_DESCENT] and \
1414 not state.strobeLightsOn and not state.onTheGround) or \
1415 (flight.stage==const.STAGE_PARKING and \
1416 state.strobeLightsOn and state.onTheGround)
1417
1418 def logFault(self, flight, aircraft, logger, oldState, state):
1419 """Log the fault."""
1420 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1421 flight.handleFault(StrobeLightsChecker, state.timestamp,
1422 FaultChecker._appendDuring(flight, message),
1423 1)
1424
1425#---------------------------------------------------------------------------------------
1426
1427class ThrustChecker(SimpleFaultChecker):
1428 """Check if the thrust setting is not too high during takeoff.
1429
1430 FIXME: is this really so general, for all aircraft?"""
1431 def isCondition(self, flight, aircraft, oldState, state):
1432 """Check if the fault condition holds."""
1433 return flight.stage==const.STAGE_TAKEOFF and \
1434 state.n1 is not None and max(state.n1)>97
1435
1436 def logFault(self, flight, aircraft, logger, oldState, state):
1437 """Log the fault."""
1438 flight.handleFault(ThrustChecker, state.timestamp,
1439 FaultChecker._appendDuring(flight,
1440 "Thrust setting was too high (>97%)"),
1441 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
1442
1443#---------------------------------------------------------------------------------------
1444
1445class VSChecker(SimpleFaultChecker):
1446 """Check if the vertical speed is not too low at certain altitudes"""
1447 BELOW10000 = -5000
1448 BELOW5000 = -2500
1449 BELOW2500 = -1500
1450 BELOW500 = -1000
1451 TOLERANCE = 1.2
1452
1453 def isCondition(self, flight, aircraft, oldState, state):
1454 """Check if the fault condition holds."""
1455 vs = state.smoothedVS
1456 altitude = state.altitude
1457 return vs < -8000 or vs > 8000 or \
1458 (altitude<500 and vs < (VSChecker.BELOW500 *
1459 VSChecker.TOLERANCE)) or \
1460 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1461 VSChecker.TOLERANCE)) or \
1462 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1463 VSChecker.TOLERANCE)) or \
1464 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1465 VSChecker.TOLERANCE))
1466
1467 def logFault(self, flight, aircraft, logger, oldState, state):
1468 """Log the fault."""
1469 vs = state.smoothedVS
1470
1471 message = "Vertical speed was %.0f feet/min" % (vs,)
1472 if vs>-8000 and vs<8000:
1473 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1474
1475 score = 10 if vs<-8000 or vs>8000 else 0
1476
1477 flight.handleFault(VSChecker, state.timestamp,
1478 FaultChecker._appendDuring(flight, message),
1479 score)
1480
1481#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.