source: src/mlx/checks.py@ 622:21bb632b0961

Last change on this file since 622:21bb632b0961 was 622:21bb632b0961, checked in by István Váradi <ivaradi@…>, 9 years ago

The overspeed and stall conditions are logged with the winds (re #259)

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