source: src/mlx/checks.py@ 356:a9dd7d794212

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

The radio frequencies are not logged before TAKEOFF and after LANDING (#148)

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