source: src/mlx/checks.py@ 601:16ff9fcc527c

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

The initial climb speed is logged (re #246)

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" % \
696 (logState.flapsSet, flight.speedFromKnots(speed),
697 flight.getEnglishSpeedUnit())
698
699 if flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING]:
700 message += ", %.0f ft AGL" % (logState.radioAltitude,)
701
702 return message
703
704#---------------------------------------------------------------------------------------
705
706class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
707 """Logger for the gears state."""
708 def __init__(self):
709 """Construct the logger."""
710 StateChangeLogger.__init__(self, logInitial = True)
711 SingleValueMixin.__init__(self, "gearControlDown")
712
713 def _getMessage(self, flight, state, forced):
714 """Get the message to log on a change."""
715 return "Gears SET to %s at %.0f %s, %.0f ft, %.0f ft AGL" % \
716 ("DOWN" if state.gearControlDown else "UP",
717 flight.speedFromKnots(state.ias),
718 flight.getEnglishSpeedUnit(),
719 state.altitude, state.radioAltitude)
720
721#---------------------------------------------------------------------------------------
722
723class APLogger(StateChangeLogger, DelayedChangeMixin):
724 """Log the state of the autopilot."""
725 @staticmethod
726 def _logBoolean(logger, timestamp, what, value):
727 """Log a boolean value.
728
729 what is the name of the value that is being logged."""
730 message = what + " "
731 if value is None:
732 message += "cannot be detected, will not log"
733 else:
734 message += "is " + ("ON" if value else "OFF")
735 logger.message(timestamp, message)
736
737 @staticmethod
738 def _logNumeric(logger, timestamp, what, format, value):
739 """Log a numerical value."""
740 message = what
741 if value is None:
742 message += u" cannot be detected, will not log"
743 else:
744 message += u": " + format % (value,)
745 logger.message(timestamp, message)
746
747 @staticmethod
748 def _logAPMaster(logger, timestamp, state):
749 """Log the AP master state."""
750 APLogger._logBoolean(logger, timestamp, "AP master",
751 state.apMaster)
752
753 @staticmethod
754 def _logAPHeadingHold(logger, timestamp, state):
755 """Log the AP heading hold state."""
756 APLogger._logBoolean(logger, timestamp, "AP heading hold",
757 state.apHeadingHold)
758
759 @staticmethod
760 def _logAPHeading(logger, timestamp, state):
761 """Log the AP heading."""
762 APLogger._logNumeric(logger, timestamp, u"AP heading",
763 u"%03.0f\u00b0", state.apHeading)
764
765 @staticmethod
766 def _logAPAltitudeHold(logger, timestamp, state):
767 """Log the AP altitude hold state."""
768 APLogger._logBoolean(logger, timestamp, "AP altitude hold",
769 state.apAltitudeHold)
770
771 @staticmethod
772 def _logAPAltitude(logger, timestamp, state):
773 """Log the AP heading."""
774 APLogger._logNumeric(logger, timestamp, u"AP altitude",
775 u"%.0f ft", state.apAltitude)
776
777 def __init__(self):
778 """Construct the state logger."""
779 StateChangeLogger.__init__(self)
780 DelayedChangeMixin.__init__(self)
781 self._lastLoggedState = None
782 self.logState = lambda flight, logger, state:\
783 APLogger.logState(self, flight, logger, state)
784
785 def _getValue(self, state):
786 """Convert the relevant values from the given state into a tuple."""
787 return (state.apMaster,
788 state.apHeadingHold, state.apHeading,
789 state.apAltitudeHold, state.apAltitude)
790
791 def _isDifferent(self, oldValue, newValue):
792 """Determine if the given old and new values are different (enough) to
793 be logged."""
794 (oldAPMaster, oldAPHeadingHold, oldAPHeading,
795 oldAPAltitudeHold, oldAPAltitude) = oldValue
796 (apMaster, apHeadingHold, apHeading,
797 apAltitudeHold, apAltitude) = newValue
798
799 if apMaster is not None and apMaster!=oldAPMaster:
800 return True
801 if apMaster is False:
802 return False
803
804 if apHeadingHold is not None and apHeadingHold!=oldAPHeadingHold:
805 return True
806 if apHeadingHold is not False and apHeading is not None and \
807 apHeading!=oldAPHeading:
808 return True
809
810 if apAltitudeHold is not None and apAltitudeHold!=oldAPAltitudeHold:
811 return True
812 if apAltitudeHold is not False and apAltitude is not None and \
813 apAltitude!=oldAPAltitude:
814 return True
815
816 return False
817
818 def logState(self, flight, logger, state):
819 """Log the autopilot state."""
820 timestamp = DelayedChangeMixin._getLogTimestamp(self, state, False)
821 if self._lastLoggedState is None:
822 self._logAPMaster(logger, timestamp, state)
823 if state.apMaster is not False or state.apHeadingHold is None:
824 self._logAPHeadingHold(logger, timestamp, state)
825 if state.apMaster is not False and \
826 (state.apHeadingHold is not False or state.apHeading is None):
827 self._logAPHeading(logger, timestamp, state)
828 if state.apMaster is not False or state.apAltitudeHold is None:
829 self._logAPAltitudeHold(logger, timestamp, state)
830 if state.apMaster is not False and \
831 (state.apAltitudeHold is not False or state.apAltitude is None):
832 self._logAPAltitude(logger, timestamp, state)
833 self._firstCall = False
834 else:
835 oldState = self._lastLoggedState
836
837 apMasterTurnedOn = False
838 if state.apMaster is not None and state.apMaster!=oldState.apMaster:
839 apMasterTurnedOn = state.apMaster
840 self._logAPMaster(logger, timestamp, state)
841
842 if state.apMaster is not False:
843 apHeadingHoldTurnedOn = False
844 if state.apHeadingHold is not None and \
845 (state.apHeadingHold!=oldState.apHeadingHold or
846 apMasterTurnedOn):
847 apHeadingHoldTurnedOn = state.apHeadingHold
848 self._logAPHeadingHold(logger, timestamp, state)
849
850 if state.apHeadingHold is not False and \
851 state.apHeading is not None and \
852 (state.apHeading!=oldState.apHeading or apMasterTurnedOn or
853 apHeadingHoldTurnedOn):
854 self._logAPHeading(logger, timestamp, state)
855
856 apAltitudeHoldTurnedOn = False
857 if state.apAltitudeHold is not None and \
858 (state.apAltitudeHold!=oldState.apAltitudeHold or
859 apMasterTurnedOn):
860 apAltitudeHoldTurnedOn = state.apAltitudeHold
861 self._logAPAltitudeHold(logger, timestamp, state)
862
863 if state.apAltitudeHold is not False and \
864 state.apAltitude is not None and \
865 (state.apAltitude!=oldState.apAltitude or apMasterTurnedOn or
866 apAltitudeHoldTurnedOn):
867 self._logAPAltitude(logger, timestamp, state)
868
869 self._lastLoggedState = state
870
871#---------------------------------------------------------------------------------------
872
873class FaultChecker(StateChecker):
874 """Base class for checkers that look for faults."""
875 @staticmethod
876 def _appendDuring(flight, message):
877 """Append a 'during XXX' test to the given message, depending on the
878 flight stage."""
879 stageStr = const.stage2string(flight.stage)
880 return message if stageStr is None \
881 else (message + " during " + stageStr.upper())
882
883 @staticmethod
884 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
885 value):
886 """Get the score for a faulty value where the score is calculated
887 linearly within a certain range."""
888 if value<minFaultValue:
889 return 0
890 elif value>maxFaultValue:
891 return maxScore
892 else:
893 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
894 (maxFaultValue - minFaultValue)
895
896#---------------------------------------------------------------------------------------
897
898class SimpleFaultChecker(FaultChecker):
899 """Base class for fault checkers that check for a single occurence of a
900 faulty condition.
901
902 Child classes should implement the following functions:
903 - isCondition(self, flight, aircraft, oldState, state): should return whether the
904 condition holds
905 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
906 via the logger."""
907 def check(self, flight, aircraft, logger, oldState, state):
908 """Perform the check."""
909 if self.isCondition(flight, aircraft, oldState, state):
910 self.logFault(flight, aircraft, logger, oldState, state)
911
912#---------------------------------------------------------------------------------------
913
914class PatientFaultChecker(FaultChecker):
915 """A fault checker that does not decides on a fault when the condition
916 arises immediately, but can wait some time.
917
918 Child classes should implement the following functions:
919 - isCondition(self, flight, aircraft, oldState, state): should return whether the
920 condition holds
921 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
922 via the logger
923 """
924 def __init__(self, timeout = 2.0):
925 """Construct the fault checker with the given timeout."""
926 self._timeout = timeout
927 self._faultStarted = None
928
929 def getTimeout(self, flight, aircraft, oldState, state):
930 """Get the timeout.
931
932 This default implementation returns the timeout given in the
933 constructor, but child classes might want to enforce a different
934 policy."""
935 return self._timeout
936
937 def check(self, flight, aircraft, logger, oldState, state):
938 """Perform the check."""
939 if self.isCondition(flight, aircraft, oldState, state):
940 if self._faultStarted is None:
941 self._faultStarted = state.timestamp
942 timeout = self.getTimeout(flight, aircraft, oldState, state)
943 if state.timestamp>=(self._faultStarted + timeout):
944 self.logFault(flight, aircraft, logger, oldState, state)
945 self._faultStarted = state.timestamp
946 else:
947 self._faultStarted = None
948
949#---------------------------------------------------------------------------------------
950
951class AntiCollisionLightsChecker(PatientFaultChecker):
952 """Check for the anti-collision light being off at high N1 values."""
953 def isCondition(self, flight, aircraft, oldState, state):
954 """Check if the fault condition holds."""
955 return (not flight.config.usingFS2Crew or not state.parking or
956 flight.stage!=const.STAGE_TAXIAFTERLAND) and \
957 not state.antiCollisionLightsOn and \
958 self.isEngineCondition(state)
959
960 def logFault(self, flight, aircraft, logger, oldState, state):
961 """Log the fault."""
962 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
963 FaultChecker._appendDuring(flight,
964 "Anti-collision lights were %s" %
965 ("on" if state.antiCollisionLightsOn else "off",)),
966 1)
967
968 def isEngineCondition(self, state):
969 """Determine if the engines are in such a state that the lights should
970 be on."""
971 if state.n1 is not None:
972 return max(state.n1)>5
973 elif state.rpm is not None:
974 return max(state.rpm)>0
975 else:
976 return False
977
978#---------------------------------------------------------------------------------------
979
980class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
981 """Check for the anti-collision light for Tuplev planes."""
982 def isCondition(self, flight, aircraft, oldState, state):
983 """Check if the fault condition holds."""
984 numEnginesRunning = 0
985 for n1 in state.n1:
986 if n1>5: numEnginesRunning += 1
987
988 if flight.stage==const.STAGE_PARKING or \
989 flight.stage==const.STAGE_TAXIAFTERLAND:
990 return numEnginesRunning<len(state.n1) \
991 and state.antiCollisionLightsOn
992 else:
993 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
994 numEnginesRunning<1 and state.antiCollisionLightsOn
995
996#---------------------------------------------------------------------------------------
997
998class BankChecker(SimpleFaultChecker):
999 """Check for the bank is within limits."""
1000 def isCondition(self, flight, aircraft, oldState, state):
1001 """Check if the fault condition holds."""
1002 if flight.stage==const.STAGE_CRUISE:
1003 isDH8DXplane = flight.aircraftType==const.AIRCRAFT_DH8D and \
1004 (flight.fsType==const.SIM_XPLANE10 or
1005 flight.fsType==const.SIM_XPLANE9)
1006 bankLimit = 35 if isDH8DXplane else 30
1007 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1008 const.STAGE_DESCENT, const.STAGE_LANDING]:
1009 bankLimit = 35
1010 else:
1011 return False
1012
1013 return state.bank>bankLimit or state.bank<-bankLimit
1014
1015 def logFault(self, flight, aircraft, logger, oldState, state):
1016 """Log the fault."""
1017 message = "Bank too steep (%.1f)" % (state.bank,)
1018 flight.handleFault(BankChecker, state.timestamp,
1019 FaultChecker._appendDuring(flight, message),
1020 2)
1021
1022#---------------------------------------------------------------------------------------
1023
1024class FlapsRetractChecker(SimpleFaultChecker):
1025 """Check if the flaps are not retracted too early."""
1026 def __init__(self):
1027 """Construct the flaps checker."""
1028 self._timeStart = None
1029
1030 def isCondition(self, flight, aircraft, oldState, state):
1031 """Check if the fault condition holds.
1032
1033 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
1034 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround and
1035 aircraft.type!=const.AIRCRAFT_F70) or \
1036 (flight.stage==const.STAGE_LANDING and state.onTheGround):
1037 if self._timeStart is None:
1038 self._timeStart = state.timestamp
1039
1040 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
1041 return True
1042 else:
1043 self._timeStart = None
1044 return False
1045
1046 def logFault(self, flight, aircraft, logger, oldState, state):
1047 """Log the fault."""
1048 flight.handleFault(FlapsRetractChecker, state.timestamp,
1049 FaultChecker._appendDuring(flight, "Flaps retracted"),
1050 20)
1051
1052#---------------------------------------------------------------------------------------
1053
1054class FlapsSpeedLimitChecker(SimpleFaultChecker):
1055 """Check if the flaps are extended only at the right speeds."""
1056 def isCondition(self, flight, aircraft, oldState, state):
1057 """Check if the fault condition holds."""
1058 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
1059 return speedLimit is not None and state.smoothedIAS>speedLimit
1060
1061 def logFault(self, flight, aircraft, logger, oldState, state):
1062 """Log the fault."""
1063 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
1064 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
1065 5)
1066
1067#---------------------------------------------------------------------------------------
1068
1069class GearsDownChecker(SimpleFaultChecker):
1070 """Check if the gears are down at low altitudes."""
1071 def isCondition(self, flight, aircraft, oldState, state):
1072 """Check if the fault condition holds."""
1073 return state.radioAltitude<10 and not state.gearsDown and \
1074 flight.stage!=const.STAGE_TAKEOFF
1075
1076 def logFault(self, flight, aircraft, logger, oldState, state):
1077 """Log the fault."""
1078 flight.handleNoGo(GearsDownChecker, state.timestamp,
1079 "Gears not down at %.0f feet radio altitude" % \
1080 (state.radioAltitude,),
1081 "GEAR DOWN NO GO")
1082
1083#---------------------------------------------------------------------------------------
1084
1085class GearSpeedLimitChecker(PatientFaultChecker):
1086 """Check if the gears not down at too high a speed."""
1087 def isCondition(self, flight, aircraft, oldState, state):
1088 """Check if the fault condition holds."""
1089 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
1090
1091 def logFault(self, flight, aircraft, logger, oldState, state):
1092 """Log the fault."""
1093 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
1094 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
1095 5)
1096
1097#---------------------------------------------------------------------------------------
1098
1099class GLoadChecker(SimpleFaultChecker):
1100 """Check if the G-load does not exceed 2 except during flare."""
1101 def isCondition(self, flight, aircraft, oldState, state):
1102 """Check if the fault condition holds."""
1103 return state.gLoad>2.0 and not state.onTheGround and \
1104 (flight.stage!=const.STAGE_LANDING or state.radioAltitude>=50)
1105
1106 def logFault(self, flight, aircraft, logger, oldState, state):
1107 """Log the fault."""
1108 flight.handleFault(GLoadChecker, state.timestamp,
1109 "G-load was %.2f" % (state.gLoad,),
1110 10)
1111
1112#---------------------------------------------------------------------------------------
1113
1114class LandingLightsChecker(PatientFaultChecker):
1115 """Check if the landing lights are used properly."""
1116 def getTimeout(self, flight, aircraft, oldState, state):
1117 """Get the timeout.
1118
1119 It is the default timeout except for landing and takeoff."""
1120 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
1121 const.STAGE_LANDING] else self._timeout
1122
1123 def isCondition(self, flight, aircraft, oldState, state):
1124 """Check if the fault condition holds."""
1125 return state.landingLightsOn is not None and \
1126 ((flight.stage==const.STAGE_BOARDING and \
1127 state.landingLightsOn and state.onTheGround) or \
1128 (flight.stage==const.STAGE_TAKEOFF and \
1129 not state.landingLightsOn and not state.onTheGround) or \
1130 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1131 const.STAGE_DESCENT] and \
1132 state.landingLightsOn and state.altitude>12500) or \
1133 (flight.stage==const.STAGE_LANDING and \
1134 not state.landingLightsOn and state.onTheGround and
1135 state.groundSpeed>50.0) or \
1136 (flight.stage==const.STAGE_PARKING and \
1137 state.landingLightsOn and state.onTheGround))
1138
1139 def logFault(self, flight, aircraft, logger, oldState, state):
1140 """Log the fault."""
1141 score = 0 if flight.stage in [const.STAGE_TAKEOFF,
1142 const.STAGE_LANDING] else 1
1143 message = "Landing lights were %s" % \
1144 (("on" if state.landingLightsOn else "off"),)
1145 flight.handleFault(LandingLightsChecker, state.timestamp,
1146 FaultChecker._appendDuring(flight, message),
1147 score)
1148
1149#---------------------------------------------------------------------------------------
1150
1151class TransponderChecker(PatientFaultChecker):
1152 """Check if the transponder is used properly."""
1153 def __init__(self):
1154 """Construct the transponder checker."""
1155 super(TransponderChecker, self).__init__()
1156 self._liftOffTime = None
1157
1158 def isCondition(self, flight, aircraft, oldState, state):
1159 """Check if the fault condition holds."""
1160 if state.onTheGround:
1161 self._liftOffTime = None
1162 elif self._liftOffTime is None:
1163 self._liftOffTime = state.timestamp
1164
1165 return state.xpdrC is not None and \
1166 ((state.xpdrC and flight.stage in
1167 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
1168 (not state.xpdrC and
1169 (flight.stage in
1170 [const.STAGE_CRUISE, const.STAGE_DESCENT,
1171 const.STAGE_GOAROUND] or \
1172 (flight.stage==const.STAGE_LANDING and
1173 state.groundSpeed>50.0) or \
1174 ((not state.autoXPDR or \
1175 (self._liftOffTime is not None and
1176 state.timestamp > (self._liftOffTime+8))) and \
1177 ((flight.stage==const.STAGE_TAKEOFF and
1178 not state.onTheGround) or flight.stage==const.STAGE_CLIMB))
1179 )
1180 )
1181 )
1182
1183 def logFault(self, flight, aircraft, logger, oldState, state):
1184 """Log the fault."""
1185 score = 0
1186 message = "Transponder was %s" % \
1187 (("mode C" if state.xpdrC else "standby"),)
1188 flight.handleFault(TransponderChecker, state.timestamp,
1189 FaultChecker._appendDuring(flight, message),
1190 score)
1191
1192#---------------------------------------------------------------------------------------
1193
1194class WeightChecker(PatientFaultChecker):
1195 """Base class for checkers that check that some limit is not exceeded."""
1196 def __init__(self, name):
1197 """Construct the checker."""
1198 super(WeightChecker, self).__init__(timeout = 5.0)
1199 self._name = name
1200
1201 def isCondition(self, flight, aircraft, oldState, state):
1202 """Check if the fault condition holds."""
1203 if flight.entranceExam:
1204 return False
1205
1206 limit = self.getLimit(flight, aircraft, state)
1207 if limit is not None:
1208 #if flight.options.compensation is not None:
1209 # limit += flight.options.compensation
1210 return self.getWeight(state)>limit
1211
1212 return False
1213
1214 def logFault(self, flight, aircraft, logger, oldState, state):
1215 """Log the fault."""
1216 mname = "M" + self._name
1217 flight.handleNoGo(self.__class__, state.timestamp,
1218 "%s exceeded: %s is %.0f kg" % \
1219 (mname, self._name, self.getWeight(state)),
1220 "%s NO GO" % (mname,))
1221
1222 def getWeight(self, state):
1223 """Get the weight that is interesting for us."""
1224 return state.grossWeight
1225
1226#---------------------------------------------------------------------------------------
1227
1228class MLWChecker(WeightChecker):
1229 """Checks if the MLW is not exceeded on landing."""
1230 def __init__(self):
1231 """Construct the checker."""
1232 super(MLWChecker, self).__init__("LW")
1233
1234 def getLimit(self, flight, aircraft, state):
1235 """Get the limit if we are in the right state."""
1236 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
1237 state.onTheGround and \
1238 not flight.entranceExam else None
1239
1240#---------------------------------------------------------------------------------------
1241
1242class MTOWChecker(WeightChecker):
1243 """Checks if the MTOW is not exceeded on landing."""
1244 def __init__(self):
1245 """Construct the checker."""
1246 super(MTOWChecker, self).__init__("TOW")
1247
1248 def getLimit(self, flight, aircraft, state):
1249 """Get the limit if we are in the right state."""
1250 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1251 not flight.entranceExam else None
1252
1253#---------------------------------------------------------------------------------------
1254
1255class MZFWChecker(WeightChecker):
1256 """Checks if the MZFW is not exceeded on landing."""
1257 def __init__(self):
1258 """Construct the checker."""
1259 super(MZFWChecker, self).__init__("ZFW")
1260
1261 def getLimit(self, flight, aircraft, state):
1262 """Get the limit if we are in the right state."""
1263 return aircraft.mzfw if not flight.entranceExam else None
1264
1265 def getWeight(self, state):
1266 """Get the weight that is interesting for us."""
1267 return state.zfw
1268
1269#---------------------------------------------------------------------------------------
1270
1271class NavLightsChecker(PatientFaultChecker):
1272 """Check if the navigational lights are used properly."""
1273 def isCondition(self, flight, aircraft, oldState, state):
1274 """Check if the fault condition holds."""
1275 return flight.stage!=const.STAGE_BOARDING and \
1276 flight.stage!=const.STAGE_PARKING and \
1277 state.navLightsOn is False
1278
1279 def logFault(self, flight, aircraft, logger, oldState, state):
1280 """Log the fault."""
1281 flight.handleFault(NavLightsChecker, state.timestamp,
1282 FaultChecker._appendDuring(flight,
1283 "Navigation lights were off"),
1284 1)
1285
1286#---------------------------------------------------------------------------------------
1287
1288class OverspeedChecker(PatientFaultChecker):
1289 """Check if Vne has been exceeded."""
1290 def __init__(self, timeout = 30.0):
1291 """Construct the checker."""
1292 super(OverspeedChecker, self).__init__(timeout = timeout)
1293
1294 def isCondition(self, flight, aircraft, oldState, state):
1295 """Check if the fault condition holds."""
1296 return state.overspeed
1297
1298 def logFault(self, flight, aircraft, logger, oldState, state):
1299 """Log the fault."""
1300 flight.handleFault(OverspeedChecker, state.timestamp,
1301 FaultChecker._appendDuring(flight, "Overspeed"),
1302 20)
1303
1304#---------------------------------------------------------------------------------------
1305
1306class PayloadChecker(SimpleFaultChecker):
1307 """Check if the payload matches the specification."""
1308 TOLERANCE=550
1309
1310 @staticmethod
1311 def isZFWFaulty(aircraftZFW, flightZFW):
1312 """Check if the given aircraft's ZFW is outside of the limits."""
1313 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1314 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1315
1316 def isCondition(self, flight, aircraft, oldState, state):
1317 """Check if the fault condition holds."""
1318 return not flight.entranceExam and \
1319 flight.stage==const.STAGE_PUSHANDTAXI and \
1320 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1321
1322 def logFault(self, flight, aircraft, logger, oldState, state):
1323 """Log the fault."""
1324 flight.handleNoGo(PayloadChecker, state.timestamp,
1325 "ZFW difference is more than %d kgs" % \
1326 (PayloadChecker.TOLERANCE,),
1327 "ZFW NO GO")
1328
1329#---------------------------------------------------------------------------------------
1330
1331class PitotChecker(PatientFaultChecker):
1332 """Check if pitot heat is on."""
1333 def __init__(self):
1334 """Construct the checker."""
1335 super(PitotChecker, self).__init__(timeout = 3.0)
1336
1337 def isCondition(self, flight, aircraft, oldState, state):
1338 """Check if the fault condition holds."""
1339 return state.groundSpeed>80 and not state.pitotHeatOn
1340
1341 def logFault(self, flight, aircraft, logger, oldState, state):
1342 """Log the fault."""
1343 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1344 const.STAGE_CRUISE, const.STAGE_DESCENT,
1345 const.STAGE_LANDING] else 0
1346 flight.handleFault(PitotChecker, state.timestamp,
1347 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1348 score)
1349
1350#---------------------------------------------------------------------------------------
1351
1352class ReverserLogger(StateChecker):
1353 """Logger for the reverser."""
1354 def check(self, flight, aircraft, logger, oldState, state):
1355 """Log the cruise speed if necessary."""
1356 if oldState is not None and state.reverser != oldState.reverser:
1357 reverser = max(state.reverser)
1358 logger.message(state.timestamp,
1359 "Reverser " + ("unlocked" if reverser else "closed") +
1360 (" - %.0f %s" % (flight.speedFromKnots(state.ias),
1361 flight.getEnglishSpeedUnit())))
1362
1363#---------------------------------------------------------------------------------------
1364
1365class ReverserChecker(SimpleFaultChecker):
1366 """Check if the reverser is not used below the speed prescribed for the
1367 aircraft."""
1368 def isCondition(self, flight, aircraft, oldState, state):
1369 """Check if the fault condition holds."""
1370 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1371 const.STAGE_TAXIAFTERLAND] and \
1372 state.reverser and \
1373 state.ias<aircraft.reverseMinSpeed and max(state.reverser)
1374
1375 def logFault(self, flight, aircraft, logger, oldState, state):
1376 """Log the fault."""
1377 message = "Reverser used below %.0f %s" % \
1378 (flight.speedFromKnots(aircraft.reverseMinSpeed), flight.getEnglishSpeedUnit())
1379 flight.handleFault(ReverserChecker, state.timestamp,
1380 FaultChecker._appendDuring(flight, message),
1381 15)
1382
1383#---------------------------------------------------------------------------------------
1384
1385class SpeedChecker(SimpleFaultChecker):
1386 """Check if the speed is in the prescribed limits."""
1387 @staticmethod
1388 def needNoStrobe(aircraft, state):
1389 """Determine if the given aircraft and state needs a strobeless speed
1390 checking."""
1391 return aircraft.needNoStrobeSpeedCheck or \
1392 (state.strobeLightsOn is None and state.xpdrC is None)
1393
1394 @staticmethod
1395 def logSpeedFault(flight, state, stage = None, updateID = None):
1396 """Log the speed fault."""
1397 message = "Taxi speed over %.0f %s" % \
1398 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1399 if stage is None:
1400 stage = flight.stage
1401 score = FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed)
1402 return flight.handleFault((SpeedChecker, stage), state.timestamp,
1403 FaultChecker._appendDuring(flight, message),
1404 score, updatePrevious = updateID is None,
1405 updateID = updateID)
1406
1407 def isCondition(self, flight, aircraft, oldState, state):
1408 """Check if the fault condition holds."""
1409 return not self.needNoStrobe(aircraft, state) and \
1410 flight.stage in [const.STAGE_PUSHANDTAXI,
1411 const.STAGE_RTO,
1412 const.STAGE_TAXIAFTERLAND] and \
1413 state.groundSpeed>50
1414
1415 def logFault(self, flight, aircraft, logger, oldState, state):
1416 """Log the fault."""
1417 self.logSpeedFault(flight, state)
1418
1419#---------------------------------------------------------------------------------------
1420
1421class NoStrobeSpeedChecker(StateChecker):
1422 """Checker for the ground speed of aircraft that have no strobe lights by
1423 which to detect the takeoff stage.
1424
1425 If, during the PUSHANDTAXI stage the speed exceeds 50 knots, the state as
1426 saved as a provisional takeoff state. If the speed then decreases below 50
1427 knots, or the plane remains on the ground for more than 40 seconds, a taxi
1428 speed error is logged with the highest ground speed detected. This state is
1429 also stored in the flight object as a possible
1430
1431 If the plane becomes airborne within 40 seconds, the stage becomes TAKEOFF,
1432 and the previously saved takeoff state is logged.
1433
1434 During the TAXIAFTERLAND stage, speed is checked as in case of
1435 SpeedChecker."""
1436 def __init__(self):
1437 """Initialize the speed checker."""
1438 self._takeoffState = None
1439 self._highestSpeedState = None
1440
1441 def check(self, flight, aircraft, logger, oldState, state):
1442 """Check the state as described above."""
1443 if SpeedChecker.needNoStrobe(aircraft, state):
1444 if flight.stage==const.STAGE_PUSHANDTAXI or \
1445 flight.stage==const.STAGE_RTO:
1446 self._checkPushAndTaxi(flight, aircraft, state)
1447 elif flight.stage==const.STAGE_TAXIAFTERLAND:
1448 if state.groundSpeed>50:
1449 SpeedChecker.logSpeedFault(flight, state)
1450 else:
1451 self._takeoffState = None
1452
1453 def _checkPushAndTaxi(self, flight, aircraft, state):
1454 """Check the speed during the push and taxi stage."""
1455 if state.groundSpeed>50:
1456 if self._takeoffState is None:
1457 self._takeoffState = state
1458 self._highestSpeedState = state
1459 else:
1460 if state.groundSpeed>self._highestSpeedState.groundSpeed:
1461 self._highestSpeedState = state
1462
1463 if not state.onTheGround:
1464 aircraft.setStage(self._takeoffState, const.STAGE_TAKEOFF)
1465 self._takeoffState = None
1466 elif state.timestamp > (self._takeoffState.timestamp + 40):
1467 flight.setRTOState(self._highestSpeedState)
1468 elif self._takeoffState is not None:
1469 flight.setRTOState(self._highestSpeedState)
1470 self._takeoffState = None
1471
1472#---------------------------------------------------------------------------------------
1473
1474class StallChecker(PatientFaultChecker):
1475 """Check if stall occured."""
1476 def isCondition(self, flight, aircraft, oldState, state):
1477 """Check if the fault condition holds."""
1478 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1479 const.STAGE_CRUISE, const.STAGE_DESCENT,
1480 const.STAGE_LANDING] and state.stalled
1481
1482 def logFault(self, flight, aircraft, logger, oldState, state):
1483 """Log the fault."""
1484 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1485 const.STAGE_LANDING] else 30
1486 flight.handleFault(StallChecker, state.timestamp,
1487 FaultChecker._appendDuring(flight, "Stalled"),
1488 score)
1489
1490#---------------------------------------------------------------------------------------
1491
1492class StrobeLightsChecker(PatientFaultChecker):
1493 """Check if the strobe lights are used properly."""
1494 def isCondition(self, flight, aircraft, oldState, state):
1495 """Check if the fault condition holds."""
1496 return state.strobeLightsOn is not None and \
1497 ((flight.stage==const.STAGE_BOARDING and \
1498 state.strobeLightsOn and state.onTheGround) or \
1499 (flight.stage==const.STAGE_TAKEOFF and \
1500 not state.strobeLightsOn and not state.gearsDown) or \
1501 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1502 const.STAGE_DESCENT] and \
1503 not state.strobeLightsOn and not state.onTheGround) or \
1504 (flight.stage==const.STAGE_PARKING and \
1505 state.strobeLightsOn and state.onTheGround))
1506
1507 def logFault(self, flight, aircraft, logger, oldState, state):
1508 """Log the fault."""
1509 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1510 flight.handleFault(StrobeLightsChecker, state.timestamp,
1511 FaultChecker._appendDuring(flight, message),
1512 1)
1513
1514#---------------------------------------------------------------------------------------
1515
1516class ThrustChecker(SimpleFaultChecker):
1517 """Check if the thrust setting is not too high during takeoff.
1518
1519 FIXME: is this really so general, for all aircraft?"""
1520 def isCondition(self, flight, aircraft, oldState, state):
1521 """Check if the fault condition holds."""
1522 return flight.stage==const.STAGE_TAKEOFF and \
1523 state.n1 is not None and max(state.n1)>97
1524
1525 def logFault(self, flight, aircraft, logger, oldState, state):
1526 """Log the fault."""
1527 flight.handleFault(ThrustChecker, state.timestamp,
1528 FaultChecker._appendDuring(flight,
1529 "Thrust setting was too high (>97%)"),
1530 FaultChecker._getLinearScore(97, 110, 0, 10,
1531 max(state.n1)),
1532 updatePrevious = True)
1533
1534#---------------------------------------------------------------------------------------
1535
1536class VSChecker(SimpleFaultChecker):
1537 """Check if the vertical speed is not too low at certain altitudes"""
1538 BELOW10000 = -5000
1539 BELOW5000 = -2500
1540 BELOW2500 = -1500
1541 BELOW500 = -1000
1542 TOLERANCE = 1.2
1543
1544 def isCondition(self, flight, aircraft, oldState, state):
1545 """Check if the fault condition holds."""
1546 vs = state.smoothedVS
1547 altitude = state.altitude
1548 return vs < -8000 or vs > 8000 or \
1549 (altitude<500 and vs < (VSChecker.BELOW500 *
1550 VSChecker.TOLERANCE)) or \
1551 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1552 VSChecker.TOLERANCE)) or \
1553 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1554 VSChecker.TOLERANCE)) or \
1555 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1556 VSChecker.TOLERANCE))
1557
1558 def logFault(self, flight, aircraft, logger, oldState, state):
1559 """Log the fault."""
1560 vs = state.smoothedVS
1561
1562 message = "Vertical speed was %.0f feet/min" % (vs,)
1563 if vs>-8000 and vs<8000:
1564 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1565
1566 score = 10 if vs<-8000 or vs>8000 else 0
1567
1568 flight.handleFault(VSChecker, state.timestamp,
1569 FaultChecker._appendDuring(flight, message),
1570 score)
1571
1572#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.