source: src/mlx/checks.py@ 355:872f6018c59e

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

The delayed change logger now checks whether the user returns to the last logged value (#147)

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