source: src/mlx/checks.py@ 358:770574bb9d15

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

Fixed the handling of the excluded stages in the GenericStateChangeLogger (#148)

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