source: src/mlx/checks.py@ 335:db900a4a0930

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

#139: added a checker for thev transponder state

File size: 53.2 KB
Line 
1
2import fs
3import const
4import util
5from acars import ACARS
6from sound import startSound
7
8import time
9
10#---------------------------------------------------------------------------------------
11
12## @package mlx.checks
13#
14# The classes that check the state of the aircraft.
15#
16# During the flight the program periodically queries various data from the
17# simulator. This data is returned in instances of the \ref
18# mlx.fs.AircraftState class and passed to the various "checkers",
19# i.e. instances of subclasses of the \ref StateChecker class. These checkers
20# perform various checks to see if the aircraft's parameters are within the
21# expected limits, or some of them just logs something.
22#
23# There are a few special ones, such as \ref StageChecker which computes the
24# transitions from one stage of the flight to the next one. Or \ref ACARSSender
25# which sends the ACARS periodically
26
27#---------------------------------------------------------------------------------------
28
29class StateChecker(object):
30 """Base class for classes the instances of which check the aircraft's state
31 from some aspect.
32
33 As a result of the check they may log something, or notify of some fault, etc."""
34 def check(self, flight, aircraft, logger, oldState, state):
35 """Perform the check and do whatever is needed.
36
37 This default implementation raises a NotImplementedError."""
38 raise NotImplementedError()
39
40#---------------------------------------------------------------------------------------
41
42class StageChecker(StateChecker):
43 """Check the flight stage transitions."""
44 def __init__(self):
45 """Construct the stage checker."""
46 self._flareStarted = False
47
48 def check(self, flight, aircraft, logger, oldState, state):
49 """Check the stage of the aircraft."""
50 stage = flight.stage
51 if stage==None:
52 aircraft.setStage(state, const.STAGE_BOARDING)
53 elif stage==const.STAGE_BOARDING:
54 if not state.parking or \
55 (not state.trickMode and state.groundSpeed>5.0):
56 aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
57 elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
58 if state.landingLightsOn or state.strobeLightsOn or \
59 state.groundSpeed>80.0:
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 not state.strobeLightsOn and \
67 state.onTheGround and \
68 state.groundSpeed<50.0:
69 aircraft.setStage(state, const.STAGE_RTO)
70 elif stage==const.STAGE_CLIMB:
71 if (state.altitude+2000) > flight.cruiseAltitude:
72 aircraft.setStage(state, const.STAGE_CRUISE)
73 elif state.radioAltitude<2000.0 and \
74 state.vs < 0.0 and state.gearsDown:
75 aircraft.setStage(state, const.STAGE_LANDING)
76 elif stage==const.STAGE_CRUISE:
77 if (state.altitude+2000) < flight.cruiseAltitude:
78 aircraft.setStage(state, const.STAGE_DESCENT)
79 elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
80 if state.gearsDown and state.radioAltitude<2000.0:
81 aircraft.setStage(state, const.STAGE_LANDING)
82 elif (state.altitude+2000) > flight.cruiseAltitude:
83 aircraft.setStage(state, const.STAGE_CRUISE)
84 elif stage==const.STAGE_LANDING:
85 if state.onTheGround and state.groundSpeed<25.0:
86 aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
87 elif not state.gearsDown:
88 aircraft.setStage(state, const.STAGE_GOAROUND)
89 elif state.radioAltitude>200 and self._flareStarted:
90 aircraft.cancelFlare()
91 self._flareStarted = False
92 elif state.radioAltitude<150 and not state.onTheGround and \
93 not self._flareStarted:
94 self._flareStarted = True
95 aircraft.prepareFlare()
96 elif stage==const.STAGE_TAXIAFTERLAND:
97 if state.parking:
98 aircraft.setStage(state, const.STAGE_PARKING)
99 elif stage==const.STAGE_PARKING:
100 if aircraft.checkFlightEnd(state):
101 aircraft.setStage(state, const.STAGE_END)
102
103#---------------------------------------------------------------------------------------
104
105class ACARSSender(StateChecker):
106 """Sender of online ACARS.
107
108 It sends the ACARS every 3 minutes to the MAVA website."""
109
110 ## The interval at which the ACARS are sent
111 INTERVAL = 3*60.0
112
113 def __init__(self, gui):
114 """Construct the ACARS sender."""
115 self._gui = gui
116 self._lastSent = None
117
118 def check(self, flight, aircraft, logger, oldState, state):
119 """If the time has come to send the ACARS, send it."""
120 now = time.time()
121
122 if self._lastSent is not None and \
123 (self._lastSent + ACARSSender.INTERVAL)>now:
124 return
125
126 acars = ACARS(self._gui, state)
127 self._gui.webHandler.sendACARS(self._acarsCallback, acars)
128
129 def _acarsCallback(self, returned, result):
130 """Callback for ACARS sending."""
131 if returned:
132 print "Sent online ACARS"
133 self._lastSent = time.time() if self._lastSent is None \
134 else self._lastSent + ACARSSender.INTERVAL
135 else:
136 print "Failed to send the ACARS"
137
138#---------------------------------------------------------------------------------------
139
140class TakeOffLogger(StateChecker):
141 """Logger for the cruise speed."""
142 def __init__(self):
143 """Construct the logger."""
144 self._onTheGround = True
145
146 def check(self, flight, aircraft, logger, oldState, state):
147 """Log the cruise speed if necessary."""
148 if flight.stage==const.STAGE_TAKEOFF and \
149 self._onTheGround and not state.onTheGround:
150 logger.message(state.timestamp,
151 "Takeoff speed: %.0f %s" % \
152 (flight.speedFromKnots(state.ias),
153 flight.getEnglishSpeedUnit()))
154 logger.message(state.timestamp,
155 "Takeoff heading: %03.0f degrees" % (state.heading,))
156 logger.message(state.timestamp,
157 "Takeoff pitch: %.1f degrees" % (state.pitch,))
158 logger.message(state.timestamp,
159 "Centre of gravity: %.1f%%" % (state.cog*100.0,))
160 self._onTheGround = False
161
162#---------------------------------------------------------------------------------------
163
164class CruiseSpeedLogger(StateChecker):
165 """Logger for the cruise speed."""
166 def __init__(self):
167 """Construct the logger."""
168 self._lastTime = None
169
170 def check(self, flight, aircraft, logger, oldState, state):
171 """Log the cruise speed if necessary."""
172 if flight.stage==const.STAGE_CRUISE and \
173 (self._lastTime is None or \
174 (self._lastTime+800)<=state.timestamp):
175 if state.altitude>24500.0:
176 logger.message(state.timestamp,
177 "Cruise speed: %.3f mach" % (state.mach,))
178 else:
179 logger.message(state.timestamp,
180 "Cruise speed: %.0f %s" %
181 (flight.speedFromKnots(state.ias),
182 flight.getEnglishSpeedUnit()))
183 self._lastTime = state.timestamp
184
185#---------------------------------------------------------------------------------------
186
187class SpoilerLogger(StateChecker):
188 """Logger for the cruise speed."""
189 def __init__(self):
190 """Construct the logger."""
191 self._logged = False
192 self._spoilersExtension = None
193
194 def check(self, flight, aircraft, logger, oldState, state):
195 """Log the cruise speed if necessary."""
196 if flight.stage==const.STAGE_LANDING and not self._logged:
197 if state.onTheGround:
198 if state.spoilersExtension!=self._spoilersExtension:
199 logger.message(state.timestamp, "Spoilers deployed")
200 self._logged = True
201 config = flight.config
202 if config.enableSounds and config.speedbrakeAtTD:
203 startSound(const.SOUND_SPEEDBRAKE)
204 else:
205 self._spoilersExtension = state.spoilersExtension
206
207#---------------------------------------------------------------------------------------
208
209class VisibilityChecker(StateChecker):
210 """Inform the pilot of the visibility once when descending below 2000 ft,
211 then when descending below 1000 ft."""
212 def __init__(self):
213 """Construct the visibility checker."""
214 self._informedBelow2000 = False
215 self._informedBelow1000 = False
216
217 def check(self, flight, aircraft, logger, oldState, state):
218 """Check if we need to inform the pilot of the visibility."""
219 if flight.stage==const.STAGE_DESCENT or \
220 flight.stage==const.STAGE_LANDING:
221 if (state.radioAltitude<2000 and not self._informedBelow2000) or \
222 (state.radioAltitude<1000 and not self._informedBelow1000):
223 visibilityString = util.visibility2String(state.visibility)
224 fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
225 "Current visibility: " + visibilityString,
226 5)
227 logger.message(state.timestamp,
228 "Pilot was informed about the visibility: " +
229 visibilityString)
230 self._informedBelow2000 = True
231 self._informedBelow1000 = state.radioAltitude<1000
232
233#---------------------------------------------------------------------------------------
234
235class ApproachCalloutsPlayer(StateChecker):
236 """A state checker that plays a sequence of approach callouts.
237
238 It tracks the altitude during the descent and landing phases and
239 if the altitude crosses one that has a callout associated with and
240 the vertical speed is negative, that callout will be played."""
241 def __init__(self, approachCallouts):
242 """Construct the approach callouts player."""
243 self._approachCallouts = approachCallouts
244 self._altitudes = approachCallouts.getAltitudes(descending = False)
245
246 def check(self, flight, aircraft, logger, oldState, state):
247 """Check if we need to play a callout."""
248 if (flight.stage==const.STAGE_DESCENT or \
249 flight.stage==const.STAGE_LANDING) and state.vs<0:
250 oldRadioAltitude = oldState.radioAltitude
251 radioAltitude = state.radioAltitude
252 for altitude in self._altitudes:
253 if radioAltitude<=altitude and \
254 oldRadioAltitude>altitude:
255 startSound(self._approachCallouts[altitude])
256 break
257
258#---------------------------------------------------------------------------------------
259
260class StateChangeLogger(StateChecker):
261 """Base class for classes the instances of which check if a specific change has
262 occured in the aircraft's state, and log such change."""
263 def __init__(self, logInitial = True):
264 """Construct the logger.
265
266 If logInitial is True, the initial value will be logged, not just the
267 changes later.
268
269 Child classes should define the following functions:
270 - _changed(self, oldState, state): returns a boolean indicating if the
271 value has changed or not
272 - _getMessage(self, flight, state, forced): return a strings containing
273 the message to log with the new value
274 """
275 self._logInitial = logInitial
276
277 def _getLogTimestamp(self, state, forced):
278 """Get the log timestamp."""
279 return state.timestamp
280
281 def check(self, flight, aircraft, logger, oldState, state):
282 """Check if the state has changed, and if so, log the new state."""
283 shouldLog = False
284 if oldState is None:
285 shouldLog = self._logInitial
286 else:
287 shouldLog = self._changed(oldState, state)
288
289 if shouldLog:
290 self.logState(flight, logger, state)
291
292 def logState(self, flight, logger, state, forced = False):
293 """Log the state."""
294 message = self._getMessage(flight, state, forced)
295 if message is not None:
296 logger.message(self._getLogTimestamp(state, forced), message)
297
298#-------------------------------------------------------------------------------
299
300class SimpleChangeMixin(object):
301 """A mixin that defines a _changed() function which simply calls a function
302 to retrieve the value two monitor for both states and compares them.
303
304 Child classes should define the following function:
305 - _getValue(state): get the value we are interested in."""
306 def _changed(self, oldState, state):
307 """Determine if the value has changed."""
308 currentValue = self._getValue(state)
309 return currentValue is not None and self._getValue(oldState)!=currentValue
310
311#---------------------------------------------------------------------------------------
312
313class SingleValueMixin(object):
314 """A mixin that provides a _getValue() function to query a value from the
315 state using the name of the attribute."""
316 def __init__(self, attrName):
317 """Construct the mixin with the given attribute name."""
318 self._attrName = attrName
319
320 def _getValue(self, state):
321 """Get the value of the attribute from the state."""
322 return getattr(state, self._attrName)
323
324#---------------------------------------------------------------------------------------
325
326class DelayedChangeMixin(object):
327 """A mixin to a StateChangeLogger that stores the old value and reports a
328 change only if the change has been there for a certain amount of time.
329
330 Child classes should define the following function:
331 - _getValue(state): get the value we are interested in."""
332 def __init__(self, minDelay = 3.0, maxDelay = 10.0):
333 """Construct the mixin with the given delay in seconds."""
334 self._minDelay = minDelay
335 self._maxDelay = maxDelay
336 self._oldValue = None
337 self._firstChange = None
338 self._lastChangeState = None
339
340 def _changed(self, oldState, state):
341 """Determine if the value has changed."""
342 if self._oldValue is None:
343 self._oldValue = self._getValue(oldState)
344
345 newValue = self._getValue(state)
346 if self._isDifferent(self._oldValue, newValue):
347 if self._firstChange is None:
348 self._firstChange = state.timestamp
349 self._lastChangeState = state
350 self._oldValue = newValue
351
352 if self._firstChange is not None:
353 if state.timestamp >= min(self._lastChangeState.timestamp +
354 self._minDelay,
355 self._firstChange + self._maxDelay):
356 self._firstChange = None
357 return True
358
359 return False
360
361 def _getLogTimestamp(self, state, forced):
362 """Get the log timestamp."""
363 return self._lastChangeState.timestamp \
364 if not forced and self._lastChangeState is not None \
365 else state.timestamp
366
367 def _isDifferent(self, oldValue, newValue):
368 """Determine if the given values are different.
369
370 This default implementation checks for simple equality."""
371 return oldValue!=newValue
372
373#---------------------------------------------------------------------------------------
374
375class TemplateMessageMixin(object):
376 """Mixin to generate a message based on a template.
377
378 Child classes should define the following function:
379 - _getValue(state): get the value we are interested in."""
380 def __init__(self, template):
381 """Construct the mixin."""
382 self._template = template
383
384 def _getMessage(self, flight, state, forced):
385 """Get the message."""
386 value = self._getValue(state)
387 return None if value is None else self._template % (value,)
388
389#---------------------------------------------------------------------------------------
390
391class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
392 DelayedChangeMixin, TemplateMessageMixin):
393 """Base for generic state change loggers that monitor a single value in the
394 state possibly with a delay and the logged message comes from a template"""
395 def __init__(self, attrName, template, logInitial = True,
396 minDelay = 0.0, maxDelay = 0.0):
397 """Construct the object."""
398 StateChangeLogger.__init__(self, logInitial = logInitial)
399 SingleValueMixin.__init__(self, attrName)
400 DelayedChangeMixin.__init__(self, minDelay = minDelay,
401 maxDelay = maxDelay)
402 TemplateMessageMixin.__init__(self, template)
403 self._getLogTimestamp = \
404 lambda state, forced: \
405 DelayedChangeMixin._getLogTimestamp(self, state, forced)
406
407#---------------------------------------------------------------------------------------
408
409class ForceableLoggerMixin(object):
410 """A mixin for loggers that can be forced to log a certain state.
411
412 The last logged state is always maintained, and when checking for a change,
413 that state is compared to the current one (which may actually be the same,
414 if a forced logging was performed for that state).
415
416 Children should implement the following functions:
417 - _hasChanged(oldState, state): the real check for a change
418 - _logState(flight, logger, state, forced): the real logging function
419 """
420 def __init__(self):
421 """Construct the mixin."""
422 self._lastLoggedState = None
423
424 def forceLog(self, flight, logger, state):
425 """Force logging the given state."""
426 self.logState(flight, logger, state, forced = True)
427
428 def logState(self, flight, logger, state, forced = False):
429 """Log the state.
430
431 It calls _logState to perform the real logging, and saves the given
432 state as the last logged one."""
433 self._logState(flight, logger, state, forced)
434 self._lastLoggedState = state
435
436 def _changed(self, oldState, state):
437 """Check if the state has changed.
438
439 This function calls _hasChanged for the real check, and replaces
440 oldState with the stored last logged state, if any."""
441 if self._lastLoggedState is not None:
442 oldState = self._lastLoggedState
443 return self._hasChanged(oldState, state)
444
445#---------------------------------------------------------------------------------------
446
447class AltimeterLogger(StateChangeLogger, SingleValueMixin,
448 DelayedChangeMixin):
449 """Logger for the altimeter setting."""
450 def __init__(self):
451 """Construct the logger."""
452 StateChangeLogger.__init__(self, logInitial = True)
453 SingleValueMixin.__init__(self, "altimeter")
454 DelayedChangeMixin.__init__(self)
455 self._getLogTimestamp = \
456 lambda state, forced: \
457 DelayedChangeMixin._getLogTimestamp(self, state, forced)
458
459 def _getMessage(self, flight, state, forced):
460 """Get the message to log on a change."""
461 logState = self._lastChangeState if \
462 self._lastChangeState is not None else state
463 return "Altimeter: %.0f hPa at %.0f feet" % \
464 (logState.altimeter, logState.altitude)
465
466#---------------------------------------------------------------------------------------
467
468class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
469 """Logger for NAV radios.
470
471 It also logs the OBS frequency set."""
472 @staticmethod
473 def getMessage(logName, frequency, obs):
474 """Get the message for the given NAV radio setting."""
475 message = u"%s: %s" % (logName, frequency)
476 if obs is not None: message += u" (%d\u00b0)" % (obs,)
477 return message
478
479 def __init__(self, attrName, logName):
480 """Construct the NAV logger."""
481 StateChangeLogger.__init__(self, logInitial = True)
482 DelayedChangeMixin.__init__(self)
483 ForceableLoggerMixin.__init__(self)
484
485 self.logState = lambda flight, logger, state, forced = False: \
486 ForceableLoggerMixin.logState(self, flight, logger, state,
487 forced = forced)
488 self._getLogTimestamp = \
489 lambda state, forced: \
490 DelayedChangeMixin._getLogTimestamp(self, state, forced)
491 self._changed = lambda oldState, state: \
492 ForceableLoggerMixin._changed(self, oldState, state)
493 self._hasChanged = lambda oldState, state: \
494 DelayedChangeMixin._changed(self, oldState, state)
495 self._logState = lambda flight, logger, state, forced: \
496 StateChangeLogger.logState(self, flight, logger, state,
497 forced = forced)
498
499 self._attrName = attrName
500 self._logName = logName
501
502 def _getValue(self, state):
503 """Get the value.
504
505 If both the frequency and the obs settings are available, a tuple
506 containing them is returned, otherwise None."""
507 frequency = getattr(state, self._attrName)
508 obs = getattr(state, self._attrName + "_obs")
509 manual = getattr(state, self._attrName + "_manual")
510 return (frequency, obs, manual)
511
512 def _getMessage(self, flight, state, forced):
513 """Get the message."""
514 (frequency, obs, manual) = self._getValue(state)
515 return None if frequency is None or obs is None or \
516 (not manual and not forced) else \
517 self.getMessage(self._logName, frequency, obs)
518
519 def _isDifferent(self, oldValue, newValue):
520 """Determine if the valie has changed between the given states."""
521 (oldFrequency, oldOBS, _oldManual) = oldValue
522 (newFrequency, newOBS, _newManual) = newValue
523 return oldFrequency!=newFrequency or oldOBS!=newOBS
524
525#---------------------------------------------------------------------------------------
526
527class NAV1Logger(NAVLogger):
528 """Logger for the NAV1 radio setting."""
529 def __init__(self):
530 """Construct the logger."""
531 super(NAV1Logger, self).__init__("nav1", "NAV1")
532
533#---------------------------------------------------------------------------------------
534
535class NAV2Logger(NAVLogger):
536 """Logger for the NAV2 radio setting."""
537 def __init__(self):
538 """Construct the logger."""
539 super(NAV2Logger, self).__init__("nav2", "NAV2")
540
541#---------------------------------------------------------------------------------------
542
543class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
544 """Base class for the ADF loggers."""
545 def __init__(self, attr, logName):
546 """Construct the ADF logger."""
547 GenericStateChangeLogger.__init__(self, attr,
548 "%s: %%s" % (logName,),
549 minDelay = 3.0, maxDelay = 10.0)
550 ForceableLoggerMixin.__init__(self)
551
552 self.logState = lambda flight, logger, state, forced = False: \
553 ForceableLoggerMixin.logState(self, flight, logger, state,
554 forced = forced)
555 self._changed = lambda oldState, state: \
556 ForceableLoggerMixin._changed(self, oldState, state)
557 self._hasChanged = lambda oldState, state: \
558 DelayedChangeMixin._changed(self, oldState, state)
559 self._logState = lambda flight, logger, state, forced: \
560 StateChangeLogger.logState(self, flight, logger, state, forced)
561
562#---------------------------------------------------------------------------------------
563
564class ADF1Logger(ADFLogger):
565 """Logger for the ADF1 radio setting."""
566 def __init__(self):
567 """Construct the logger."""
568 super(ADF1Logger, self).__init__("adf1", "ADF1")
569
570#---------------------------------------------------------------------------------------
571
572class ADF2Logger(ADFLogger):
573 """Logger for the ADF2 radio setting."""
574 def __init__(self):
575 """Construct the logger."""
576 super(ADF2Logger, self).__init__("adf2", "ADF2")
577
578#---------------------------------------------------------------------------------------
579
580class SquawkLogger(GenericStateChangeLogger):
581 """Logger for the squawk setting."""
582 def __init__(self):
583 """Construct the logger."""
584 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
585 minDelay = 3.0, maxDelay = 10.0)
586
587#---------------------------------------------------------------------------------------
588
589class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
590 """Base class for the loggers of the various lights."""
591 def __init__(self, attrName, template):
592 """Construct the logger."""
593 StateChangeLogger.__init__(self)
594 SingleValueMixin.__init__(self, attrName)
595
596 self._template = template
597
598 def _getMessage(self, flight, state, forced):
599 """Get the message from the given state."""
600 return self._template % ("ON" if self._getValue(state) else "OFF")
601
602#---------------------------------------------------------------------------------------
603
604class AnticollisionLightsLogger(LightsLogger):
605 """Logger for the anti-collision lights."""
606 def __init__(self):
607 LightsLogger.__init__(self, "antiCollisionLightsOn",
608 "Anti-collision lights: %s")
609
610#---------------------------------------------------------------------------------------
611
612class LandingLightsLogger(LightsLogger):
613 """Logger for the landing lights."""
614 def __init__(self):
615 LightsLogger.__init__(self, "landingLightsOn",
616 "Landing lights: %s")
617
618#---------------------------------------------------------------------------------------
619
620class StrobeLightsLogger(LightsLogger):
621 """Logger for the strobe lights."""
622 def __init__(self):
623 LightsLogger.__init__(self, "strobeLightsOn",
624 "Strobe lights: %s")
625
626#---------------------------------------------------------------------------------------
627
628class NavLightsLogger(LightsLogger):
629 """Logger for the navigational lights."""
630 def __init__(self):
631 LightsLogger.__init__(self, "navLightsOn",
632 "Navigational lights: %s")
633
634#---------------------------------------------------------------------------------------
635
636class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
637 """Logger for the flaps setting."""
638 def __init__(self):
639 """Construct the logger."""
640 StateChangeLogger.__init__(self, logInitial = True)
641 SingleValueMixin.__init__(self, "flapsSet")
642
643 def _getMessage(self, flight, state, forced):
644 """Get the message to log on a change."""
645 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
646 return "Flaps %.0f - %.0f %s" % \
647 (state.flapsSet, flight.speedFromKnots(speed),
648 flight.getEnglishSpeedUnit())
649
650#---------------------------------------------------------------------------------------
651
652class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
653 """Logger for the gears state."""
654 def __init__(self):
655 """Construct the logger."""
656 StateChangeLogger.__init__(self, logInitial = True)
657 SingleValueMixin.__init__(self, "gearControlDown")
658
659 def _getMessage(self, flight, state, forced):
660 """Get the message to log on a change."""
661 return "Gears SET to %s at %.0f %s, %.0f feet" % \
662 ("DOWN" if state.gearControlDown else "UP",
663 flight.speedFromKnots(state.ias),
664 flight.getEnglishSpeedUnit(), state.altitude)
665
666#---------------------------------------------------------------------------------------
667
668class FaultChecker(StateChecker):
669 """Base class for checkers that look for faults."""
670 @staticmethod
671 def _appendDuring(flight, message):
672 """Append a 'during XXX' test to the given message, depending on the
673 flight stage."""
674 stageStr = const.stage2string(flight.stage)
675 return message if stageStr is None \
676 else (message + " during " + stageStr.upper())
677
678 @staticmethod
679 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
680 value):
681 """Get the score for a faulty value where the score is calculated
682 linearly within a certain range."""
683 if value<minFaultValue:
684 return 0
685 elif value>maxFaultValue:
686 return maxScore
687 else:
688 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
689 (maxFaultValue - minFaultValue)
690
691#---------------------------------------------------------------------------------------
692
693class SimpleFaultChecker(FaultChecker):
694 """Base class for fault checkers that check for a single occurence of a
695 faulty condition.
696
697 Child classes should implement the following functions:
698 - isCondition(self, flight, aircraft, oldState, state): should return whether the
699 condition holds
700 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
701 via the logger."""
702 def check(self, flight, aircraft, logger, oldState, state):
703 """Perform the check."""
704 if self.isCondition(flight, aircraft, oldState, state):
705 self.logFault(flight, aircraft, logger, oldState, state)
706
707#---------------------------------------------------------------------------------------
708
709class PatientFaultChecker(FaultChecker):
710 """A fault checker that does not decides on a fault when the condition
711 arises immediately, but can wait some time.
712
713 Child classes should implement the following functions:
714 - isCondition(self, flight, aircraft, oldState, state): should return whether the
715 condition holds
716 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
717 via the logger
718 """
719 def __init__(self, timeout = 2.0):
720 """Construct the fault checker with the given timeout."""
721 self._timeout = timeout
722 self._faultStarted = None
723
724 def getTimeout(self, flight, aircraft, oldState, state):
725 """Get the timeout.
726
727 This default implementation returns the timeout given in the
728 constructor, but child classes might want to enforce a different
729 policy."""
730 return self._timeout
731
732 def check(self, flight, aircraft, logger, oldState, state):
733 """Perform the check."""
734 if self.isCondition(flight, aircraft, oldState, state):
735 if self._faultStarted is None:
736 self._faultStarted = state.timestamp
737 timeout = self.getTimeout(flight, aircraft, oldState, state)
738 if state.timestamp>=(self._faultStarted + timeout):
739 self.logFault(flight, aircraft, logger, oldState, state)
740 self._faultStarted = state.timestamp
741 else:
742 self._faultStarted = None
743
744#---------------------------------------------------------------------------------------
745
746class AntiCollisionLightsChecker(PatientFaultChecker):
747 """Check for the anti-collision light being off at high N1 values."""
748 def isCondition(self, flight, aircraft, oldState, state):
749 """Check if the fault condition holds."""
750 return (flight.stage!=const.STAGE_PARKING or \
751 not flight.config.usingFS2Crew) and \
752 not state.antiCollisionLightsOn and \
753 self.isEngineCondition(state)
754
755 def logFault(self, flight, aircraft, logger, oldState, state):
756 """Log the fault."""
757 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
758 FaultChecker._appendDuring(flight,
759 "Anti-collision lights were off"),
760 1)
761
762 def isEngineCondition(self, state):
763 """Determine if the engines are in such a state that the lights should
764 be on."""
765 if state.n1 is not None:
766 return max(state.n1)>5
767 elif state.rpm is not None:
768 return max(state.rpm)>0
769 else:
770 return False
771
772#---------------------------------------------------------------------------------------
773
774class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
775 """Check for the anti-collision light for Tuplev planes."""
776 def isCondition(self, flight, aircraft, oldState, state):
777 """Check if the fault condition holds."""
778 numEnginesRunning = 0
779 for n1 in state.n1:
780 if n1>5: numEnginesRunning += 1
781
782 if flight.stage==const.STAGE_PARKING:
783 return numEnginesRunning<len(state.n1) \
784 and state.antiCollisionLightsOn
785 else:
786 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
787 numEnginesRunning<1 and state.antiCollisionLightsOn
788
789#---------------------------------------------------------------------------------------
790
791class BankChecker(SimpleFaultChecker):
792 """Check for the bank is within limits."""
793 def isCondition(self, flight, aircraft, oldState, state):
794 """Check if the fault condition holds."""
795 if flight.stage==const.STAGE_CRUISE:
796 bankLimit = 30
797 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
798 const.STAGE_DESCENT, const.STAGE_LANDING]:
799 bankLimit = 35
800 else:
801 return False
802
803 return state.bank>bankLimit or state.bank<-bankLimit
804
805 def logFault(self, flight, aircraft, logger, oldState, state):
806 """Log the fault."""
807 flight.handleFault(BankChecker, state.timestamp,
808 FaultChecker._appendDuring(flight, "Bank too steep"),
809 2)
810
811#---------------------------------------------------------------------------------------
812
813class FlapsRetractChecker(SimpleFaultChecker):
814 """Check if the flaps are not retracted too early."""
815 def __init__(self):
816 """Construct the flaps checker."""
817 self._timeStart = None
818
819 def isCondition(self, flight, aircraft, oldState, state):
820 """Check if the fault condition holds.
821
822 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
823 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
824 (flight.stage==const.STAGE_LANDING and state.onTheGround):
825 if self._timeStart is None:
826 self._timeStart = state.timestamp
827
828 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
829 return True
830 else:
831 self._timeStart = None
832 return False
833
834 def logFault(self, flight, aircraft, logger, oldState, state):
835 """Log the fault."""
836 flight.handleFault(FlapsRetractChecker, state.timestamp,
837 FaultChecker._appendDuring(flight, "Flaps retracted"),
838 20)
839
840#---------------------------------------------------------------------------------------
841
842class FlapsSpeedLimitChecker(SimpleFaultChecker):
843 """Check if the flaps are extended only at the right speeds."""
844 def isCondition(self, flight, aircraft, oldState, state):
845 """Check if the fault condition holds."""
846 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
847 return speedLimit is not None and state.smoothedIAS>speedLimit
848
849 def logFault(self, flight, aircraft, logger, oldState, state):
850 """Log the fault."""
851 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
852 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
853 5)
854
855#---------------------------------------------------------------------------------------
856
857class GearsDownChecker(SimpleFaultChecker):
858 """Check if the gears are down at low altitudes."""
859 def isCondition(self, flight, aircraft, oldState, state):
860 """Check if the fault condition holds."""
861 return state.radioAltitude<10 and not state.gearsDown and \
862 flight.stage!=const.STAGE_TAKEOFF
863
864 def logFault(self, flight, aircraft, logger, oldState, state):
865 """Log the fault."""
866 flight.handleNoGo(GearsDownChecker, state.timestamp,
867 "Gears not down at %.0f feet radio altitude" % \
868 (state.radioAltitude,),
869 "GEAR DOWN NO GO")
870
871#---------------------------------------------------------------------------------------
872
873class GearSpeedLimitChecker(PatientFaultChecker):
874 """Check if the gears not down at too high a speed."""
875 def isCondition(self, flight, aircraft, oldState, state):
876 """Check if the fault condition holds."""
877 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
878
879 def logFault(self, flight, aircraft, logger, oldState, state):
880 """Log the fault."""
881 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
882 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
883 5)
884
885#---------------------------------------------------------------------------------------
886
887class GLoadChecker(SimpleFaultChecker):
888 """Check if the G-load does not exceed 2 except during flare."""
889 def isCondition(self, flight, aircraft, oldState, state):
890 """Check if the fault condition holds."""
891 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
892 state.radioAltitude>=50)
893
894 def logFault(self, flight, aircraft, logger, oldState, state):
895 """Log the fault."""
896 flight.handleFault(GLoadChecker, state.timestamp,
897 "G-load was %.2f" % (state.gLoad,),
898 10)
899
900#---------------------------------------------------------------------------------------
901
902class LandingLightsChecker(PatientFaultChecker):
903 """Check if the landing lights are used properly."""
904 def getTimeout(self, flight, aircraft, oldState, state):
905 """Get the timeout.
906
907 It is the default timeout except for landing and takeoff."""
908 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
909 const.STAGE_LANDING] else self._timeout
910
911 def isCondition(self, flight, aircraft, oldState, state):
912 """Check if the fault condition holds."""
913 return state.landingLightsOn is not None and \
914 ((flight.stage==const.STAGE_BOARDING and \
915 state.landingLightsOn and state.onTheGround) or \
916 (flight.stage==const.STAGE_TAKEOFF and \
917 not state.landingLightsOn and not state.onTheGround) or \
918 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
919 const.STAGE_DESCENT] and \
920 state.landingLightsOn and state.altitude>12500) or \
921 (flight.stage==const.STAGE_LANDING and \
922 not state.landingLightsOn and state.onTheGround) or \
923 (flight.stage==const.STAGE_PARKING and \
924 state.landingLightsOn and state.onTheGround))
925
926 def logFault(self, flight, aircraft, logger, oldState, state):
927 """Log the fault."""
928 score = 0 if flight.stage==const.STAGE_LANDING else 1
929 message = "Landing lights were %s" % \
930 (("on" if state.landingLightsOn else "off"),)
931 flight.handleFault(LandingLightsChecker, state.timestamp,
932 FaultChecker._appendDuring(flight, message),
933 score)
934
935#---------------------------------------------------------------------------------------
936
937class TransponderChecker(PatientFaultChecker):
938 """Check if the transponder is used properly."""
939 def isCondition(self, flight, aircraft, oldState, state):
940 """Check if the fault condition holds."""
941 return state.xpdrC is not None and \
942 ((state.xpdrC and flight.stage in
943 [const.STAGE_BOARDING, const.STAGE_PARKING]) or \
944 (not state.xpdrC and flight.stage in
945 [const.STAGE_TAKEOFF, const.STAGE_RTO, const.STAGE_CLIMB,
946 const.STAGE_CRUISE, const.STAGE_DESCENT,
947 const.STAGE_LANDING, const.STAGE_GOAROUND]))
948
949 def logFault(self, flight, aircraft, logger, oldState, state):
950 """Log the fault."""
951 score = 0
952 message = "Transponder was %s" % \
953 (("mode C" if state.xpdrC else "standby"),)
954 flight.handleFault(TransponderChecker, state.timestamp,
955 FaultChecker._appendDuring(flight, message),
956 score)
957
958#---------------------------------------------------------------------------------------
959
960class WeightChecker(PatientFaultChecker):
961 """Base class for checkers that check that some limit is not exceeded."""
962 def __init__(self, name):
963 """Construct the checker."""
964 super(WeightChecker, self).__init__(timeout = 5.0)
965 self._name = name
966
967 def isCondition(self, flight, aircraft, oldState, state):
968 """Check if the fault condition holds."""
969 if flight.entranceExam:
970 return False
971
972 limit = self.getLimit(flight, aircraft, state)
973 if limit is not None:
974 #if flight.options.compensation is not None:
975 # limit += flight.options.compensation
976 return self.getWeight(state)>limit
977
978 return False
979
980 def logFault(self, flight, aircraft, logger, oldState, state):
981 """Log the fault."""
982 mname = "M" + self._name
983 flight.handleNoGo(self.__class__, state.timestamp,
984 "%s exceeded: %s is %.0f kg" % \
985 (mname, self._name, self.getWeight(state)),
986 "%s NO GO" % (mname,))
987
988 def getWeight(self, state):
989 """Get the weight that is interesting for us."""
990 return state.grossWeight
991
992#---------------------------------------------------------------------------------------
993
994class MLWChecker(WeightChecker):
995 """Checks if the MLW is not exceeded on landing."""
996 def __init__(self):
997 """Construct the checker."""
998 super(MLWChecker, self).__init__("LW")
999
1000 def getLimit(self, flight, aircraft, state):
1001 """Get the limit if we are in the right state."""
1002 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
1003 state.onTheGround and \
1004 not flight.entranceExam else None
1005
1006#---------------------------------------------------------------------------------------
1007
1008class MTOWChecker(WeightChecker):
1009 """Checks if the MTOW is not exceeded on landing."""
1010 def __init__(self):
1011 """Construct the checker."""
1012 super(MTOWChecker, self).__init__("TOW")
1013
1014 def getLimit(self, flight, aircraft, state):
1015 """Get the limit if we are in the right state."""
1016 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
1017 not flight.entranceExam else None
1018
1019#---------------------------------------------------------------------------------------
1020
1021class MZFWChecker(WeightChecker):
1022 """Checks if the MZFW is not exceeded on landing."""
1023 def __init__(self):
1024 """Construct the checker."""
1025 super(MZFWChecker, self).__init__("ZFW")
1026
1027 def getLimit(self, flight, aircraft, state):
1028 """Get the limit if we are in the right state."""
1029 return aircraft.mzfw if not flight.entranceExam else None
1030
1031 def getWeight(self, state):
1032 """Get the weight that is interesting for us."""
1033 return state.zfw
1034
1035#---------------------------------------------------------------------------------------
1036
1037class NavLightsChecker(PatientFaultChecker):
1038 """Check if the navigational lights are used properly."""
1039 def isCondition(self, flight, aircraft, oldState, state):
1040 """Check if the fault condition holds."""
1041 return flight.stage!=const.STAGE_BOARDING and \
1042 flight.stage!=const.STAGE_PARKING and \
1043 not state.navLightsOn
1044
1045 def logFault(self, flight, aircraft, logger, oldState, state):
1046 """Log the fault."""
1047 flight.handleFault(NavLightsChecker, state.timestamp,
1048 FaultChecker._appendDuring(flight,
1049 "Navigation lights were off"),
1050 1)
1051
1052#---------------------------------------------------------------------------------------
1053
1054class OverspeedChecker(PatientFaultChecker):
1055 """Check if Vne has been exceeded."""
1056 def __init__(self, timeout = 5.0):
1057 """Construct the checker."""
1058 super(OverspeedChecker, self).__init__(timeout = timeout)
1059
1060 def isCondition(self, flight, aircraft, oldState, state):
1061 """Check if the fault condition holds."""
1062 return state.overspeed
1063
1064 def logFault(self, flight, aircraft, logger, oldState, state):
1065 """Log the fault."""
1066 flight.handleFault(OverspeedChecker, state.timestamp,
1067 FaultChecker._appendDuring(flight, "Overspeed"),
1068 20)
1069
1070#---------------------------------------------------------------------------------------
1071
1072class PayloadChecker(SimpleFaultChecker):
1073 """Check if the payload matches the specification."""
1074 TOLERANCE=550
1075
1076 @staticmethod
1077 def isZFWFaulty(aircraftZFW, flightZFW):
1078 """Check if the given aircraft's ZFW is outside of the limits."""
1079 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1080 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1081
1082 def isCondition(self, flight, aircraft, oldState, state):
1083 """Check if the fault condition holds."""
1084 return not flight.entranceExam and \
1085 flight.stage==const.STAGE_PUSHANDTAXI and \
1086 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1087
1088 def logFault(self, flight, aircraft, logger, oldState, state):
1089 """Log the fault."""
1090 flight.handleNoGo(PayloadChecker, state.timestamp,
1091 "ZFW difference is more than %d kgs" % \
1092 (PayloadChecker.TOLERANCE,),
1093 "ZFW NO GO")
1094
1095#---------------------------------------------------------------------------------------
1096
1097class PitotChecker(PatientFaultChecker):
1098 """Check if pitot heat is on."""
1099 def __init__(self):
1100 """Construct the checker."""
1101 super(PitotChecker, self).__init__(timeout = 3.0)
1102
1103 def isCondition(self, flight, aircraft, oldState, state):
1104 """Check if the fault condition holds."""
1105 return state.groundSpeed>80 and not state.pitotHeatOn
1106
1107 def logFault(self, flight, aircraft, logger, oldState, state):
1108 """Log the fault."""
1109 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1110 const.STAGE_CRUISE, const.STAGE_DESCENT,
1111 const.STAGE_LANDING] else 0
1112 flight.handleFault(PitotChecker, state.timestamp,
1113 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1114 score)
1115
1116#---------------------------------------------------------------------------------------
1117
1118class ReverserChecker(SimpleFaultChecker):
1119 """Check if the reverser is not used below 60 knots."""
1120 def isCondition(self, flight, aircraft, oldState, state):
1121 """Check if the fault condition holds."""
1122 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1123 const.STAGE_TAXIAFTERLAND] and \
1124 state.groundSpeed<60 and max(state.reverser)
1125
1126 def logFault(self, flight, aircraft, logger, oldState, state):
1127 """Log the fault."""
1128 message = "Reverser used below %.0f %s" % \
1129 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
1130 flight.handleFault(ReverserChecker, state.timestamp,
1131 FaultChecker._appendDuring(flight, message),
1132 15)
1133
1134#---------------------------------------------------------------------------------------
1135
1136class SpeedChecker(SimpleFaultChecker):
1137 """Check if the speed is in the prescribed limits."""
1138 def isCondition(self, flight, aircraft, oldState, state):
1139 """Check if the fault condition holds."""
1140 return flight.stage in [const.STAGE_PUSHANDTAXI,
1141 const.STAGE_TAXIAFTERLAND] and \
1142 state.groundSpeed>50
1143
1144 def logFault(self, flight, aircraft, logger, oldState, state):
1145 """Log the fault."""
1146 message = "Taxi speed over %.0f %s" % \
1147 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1148 flight.handleFault(SpeedChecker, state.timestamp,
1149 FaultChecker._appendDuring(flight, message),
1150 FaultChecker._getLinearScore(50, 80, 10, 15,
1151 state.groundSpeed))
1152
1153#---------------------------------------------------------------------------------------
1154
1155class StallChecker(PatientFaultChecker):
1156 """Check if stall occured."""
1157 def isCondition(self, flight, aircraft, oldState, state):
1158 """Check if the fault condition holds."""
1159 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1160 const.STAGE_CRUISE, const.STAGE_DESCENT,
1161 const.STAGE_LANDING] and state.stalled
1162
1163 def logFault(self, flight, aircraft, logger, oldState, state):
1164 """Log the fault."""
1165 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1166 const.STAGE_LANDING] else 30
1167 flight.handleFault(StallChecker, state.timestamp,
1168 FaultChecker._appendDuring(flight, "Stalled"),
1169 score)
1170
1171#---------------------------------------------------------------------------------------
1172
1173class StrobeLightsChecker(PatientFaultChecker):
1174 """Check if the strobe lights are used properly."""
1175 def isCondition(self, flight, aircraft, oldState, state):
1176 """Check if the fault condition holds."""
1177 return (flight.stage==const.STAGE_BOARDING and \
1178 state.strobeLightsOn and state.onTheGround) or \
1179 (flight.stage==const.STAGE_TAKEOFF and \
1180 not state.strobeLightsOn and not state.gearsDown) or \
1181 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1182 const.STAGE_DESCENT] and \
1183 not state.strobeLightsOn and not state.onTheGround) or \
1184 (flight.stage==const.STAGE_PARKING and \
1185 state.strobeLightsOn and state.onTheGround)
1186
1187 def logFault(self, flight, aircraft, logger, oldState, state):
1188 """Log the fault."""
1189 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1190 flight.handleFault(StrobeLightsChecker, state.timestamp,
1191 FaultChecker._appendDuring(flight, message),
1192 1)
1193
1194#---------------------------------------------------------------------------------------
1195
1196class ThrustChecker(SimpleFaultChecker):
1197 """Check if the thrust setting is not too high during takeoff.
1198
1199 FIXME: is this really so general, for all aircraft?"""
1200 def isCondition(self, flight, aircraft, oldState, state):
1201 """Check if the fault condition holds."""
1202 return flight.stage==const.STAGE_TAKEOFF and \
1203 state.n1 is not None and max(state.n1)>97
1204
1205 def logFault(self, flight, aircraft, logger, oldState, state):
1206 """Log the fault."""
1207 flight.handleFault(ThrustChecker, state.timestamp,
1208 FaultChecker._appendDuring(flight,
1209 "Thrust setting was too high (>97%)"),
1210 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
1211
1212#---------------------------------------------------------------------------------------
1213
1214class VSChecker(SimpleFaultChecker):
1215 """Check if the vertical speed is not too low at certain altitudes"""
1216 BELOW10000 = -5000
1217 BELOW5000 = -2500
1218 BELOW2500 = -1500
1219 BELOW500 = -1000
1220 TOLERANCE = 1.2
1221
1222 def isCondition(self, flight, aircraft, oldState, state):
1223 """Check if the fault condition holds."""
1224 vs = state.smoothedVS
1225 altitude = state.altitude
1226 return vs < -8000 or vs > 8000 or \
1227 (altitude<500 and vs < (VSChecker.BELOW500 *
1228 VSChecker.TOLERANCE)) or \
1229 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1230 VSChecker.TOLERANCE)) or \
1231 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1232 VSChecker.TOLERANCE)) or \
1233 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1234 VSChecker.TOLERANCE))
1235
1236 def logFault(self, flight, aircraft, logger, oldState, state):
1237 """Log the fault."""
1238 vs = state.smoothedVS
1239
1240 message = "Vertical speed was %.0f feet/min" % (vs,)
1241 if vs>-8000 and vs<8000:
1242 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1243
1244 score = 10 if vs<-8000 or vs>8000 else 0
1245
1246 flight.handleFault(VSChecker, state.timestamp,
1247 FaultChecker._appendDuring(flight, message),
1248 score)
1249
1250#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.