source: src/mlx/checks.py@ 341:c18840deae77

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

#114: the NAV lights are not considered for the DA F70

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