source: src/mlx/checks.py@ 357:95a02e0c97d3

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

The Fokker F70 aircraft can take of with flaps set to 0 (#149)

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