source: src/mlx/checks.py@ 348:50659419e5d8

Last change on this file since 348:50659419e5d8 was 346:78e378a97bdf, checked in by István Váradi <ivaradi@…>, 12 years ago

It is now possible to log faults so that the last one is updated instead of writing a new one (#143)

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