source: src/mlx/checks.py@ 450:d009a75685e8

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

Merged with the main branch

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