source: src/mlx/checks.py@ 602:130e4cf75677

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

Altitude is logged with flaps changes during all flight stages (re #247)

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