source: src/mlx/checks.py@ 521:9f6c08020c54

xplane
Last change on this file since 521:9f6c08020c54 was 521:9f6c08020c54, checked in by István Váradi <ivaradi@…>, 11 years ago

Merged changes from the default branch

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