source: src/mlx/checks.py@ 324:b1d504a874dc

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

Forced logging now logs with the current timestamp even for delayed change loggers (#133)

File size: 52.5 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<50.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, maxDelay = maxDelay)
401 TemplateMessageMixin.__init__(self, template)
402 self._getLogTimestamp = \
403 lambda state, forced: \
404 DelayedChangeMixin._getLogTimestamp(self, state, forced)
405
406#---------------------------------------------------------------------------------------
407
408class ForceableLoggerMixin(object):
409 """A mixin for loggers that can be forced to log a certain state.
410
411 The last logged state is always maintained, and when checking for a change,
412 that state is compared to the current one (which may actually be the same,
413 if a forced logging was performed for that state).
414
415 Children should implement the following functions:
416 - _hasChanged(oldState, state): the real check for a change
417 - _logState(flight, logger, state, forced): the real logging function
418 """
419 def __init__(self):
420 """Construct the mixin."""
421 self._lastLoggedState = None
422
423 def forceLog(self, flight, logger, state):
424 """Force logging the given state."""
425 self.logState(flight, logger, state, forced = True)
426
427 def logState(self, flight, logger, state, forced = False):
428 """Log the state.
429
430 It calls _logState to perform the real logging, and saves the given
431 state as the last logged one."""
432 self._logState(flight, logger, state, forced)
433 self._lastLoggedState = state
434
435 def _changed(self, oldState, state):
436 """Check if the state has changed.
437
438 This function calls _hasChanged for the real check, and replaces
439 oldState with the stored last logged state, if any."""
440 if self._lastLoggedState is not None:
441 oldState = self._lastLoggedState
442 return self._hasChanged(oldState, state)
443
444#---------------------------------------------------------------------------------------
445
446class AltimeterLogger(StateChangeLogger, SingleValueMixin,
447 DelayedChangeMixin):
448 """Logger for the altimeter setting."""
449 def __init__(self):
450 """Construct the logger."""
451 StateChangeLogger.__init__(self, logInitial = True)
452 SingleValueMixin.__init__(self, "altimeter")
453 DelayedChangeMixin.__init__(self)
454 self._getLogTimestamp = \
455 lambda state, forced: \
456 DelayedChangeMixin._getLogTimestamp(self, state, forced)
457
458 def _getMessage(self, flight, state, forced):
459 """Get the message to log on a change."""
460 logState = self._lastChangeState if \
461 self._lastChangeState is not None else state
462 return "Altimeter: %.0f hPa at %.0f feet" % \
463 (logState.altimeter, logState.altitude)
464
465#---------------------------------------------------------------------------------------
466
467class NAVLogger(StateChangeLogger, DelayedChangeMixin, ForceableLoggerMixin):
468 """Logger for NAV radios.
469
470 It also logs the OBS frequency set."""
471 @staticmethod
472 def getMessage(logName, frequency, obs):
473 """Get the message for the given NAV radio setting."""
474 message = u"%s frequency: %s MHz" % (logName, frequency)
475 if obs is not None: message += u" [%d\u00b0]" % (obs,)
476 return message
477
478 def __init__(self, attrName, logName):
479 """Construct the NAV logger."""
480 StateChangeLogger.__init__(self, logInitial = True)
481 DelayedChangeMixin.__init__(self)
482 ForceableLoggerMixin.__init__(self)
483
484 self.logState = lambda flight, logger, state, forced = False: \
485 ForceableLoggerMixin.logState(self, flight, logger, state,
486 forced = forced)
487 self._getLogTimestamp = \
488 lambda state, forced: \
489 DelayedChangeMixin._getLogTimestamp(self, state, forced)
490 self._changed = lambda oldState, state: \
491 ForceableLoggerMixin._changed(self, oldState, state)
492 self._hasChanged = lambda oldState, state: \
493 DelayedChangeMixin._changed(self, oldState, state)
494 self._logState = lambda flight, logger, state, forced: \
495 StateChangeLogger.logState(self, flight, logger, state,
496 forced = forced)
497
498 self._attrName = attrName
499 self._logName = logName
500
501 def _getValue(self, state):
502 """Get the value.
503
504 If both the frequency and the obs settings are available, a tuple
505 containing them is returned, otherwise None."""
506 frequency = getattr(state, self._attrName)
507 obs = getattr(state, self._attrName + "_obs")
508 manual = getattr(state, self._attrName + "_manual")
509 return (frequency, obs, manual)
510
511 def _getMessage(self, flight, state, forced):
512 """Get the message."""
513 (frequency, obs, manual) = self._getValue(state)
514 return None if frequency is None or obs is None or \
515 (not manual and not forced) else \
516 self.getMessage(self._logName, frequency, obs)
517
518 def _isDifferent(self, oldValue, newValue):
519 """Determine if the valie has changed between the given states."""
520 (oldFrequency, oldOBS, _oldManual) = oldValue
521 (newFrequency, newOBS, _newManual) = newValue
522 return oldFrequency!=newFrequency or oldOBS!=newOBS
523
524#---------------------------------------------------------------------------------------
525
526class NAV1Logger(NAVLogger):
527 """Logger for the NAV1 radio setting."""
528 def __init__(self):
529 """Construct the logger."""
530 super(NAV1Logger, self).__init__("nav1", "NAV1")
531
532#---------------------------------------------------------------------------------------
533
534class NAV2Logger(NAVLogger):
535 """Logger for the NAV2 radio setting."""
536 def __init__(self):
537 """Construct the logger."""
538 super(NAV2Logger, self).__init__("nav2", "NAV2")
539
540#---------------------------------------------------------------------------------------
541
542class ADFLogger(GenericStateChangeLogger, ForceableLoggerMixin):
543 """Base class for the ADF loggers."""
544 def __init__(self, attr, logName):
545 """Construct the ADF logger."""
546 GenericStateChangeLogger.__init__(self, attr,
547 "%s frequency: %%s kHz" % (logName,),
548 minDelay = 3.0, maxDelay = 10.0)
549 ForceableLoggerMixin.__init__(self)
550
551 self.logState = lambda flight, logger, state, forced = False: \
552 ForceableLoggerMixin.logState(self, flight, logger, state,
553 forced = forced)
554 self._changed = lambda oldState, state: \
555 ForceableLoggerMixin._changed(self, oldState, state)
556 self._hasChanged = lambda oldState, state: \
557 DelayedChangeMixin._changed(self, oldState, state)
558 self._logState = lambda flight, logger, state, forced: \
559 StateChangeLogger.logState(self, flight, logger, state, forced)
560
561#---------------------------------------------------------------------------------------
562
563class ADF1Logger(ADFLogger):
564 """Logger for the ADF1 radio setting."""
565 def __init__(self):
566 """Construct the logger."""
567 super(ADF1Logger, self).__init__("adf1", "ADF1")
568
569#---------------------------------------------------------------------------------------
570
571class ADF2Logger(ADFLogger):
572 """Logger for the ADF2 radio setting."""
573 def __init__(self):
574 """Construct the logger."""
575 super(ADF2Logger, self).__init__("adf2", "ADF2")
576
577#---------------------------------------------------------------------------------------
578
579class SquawkLogger(GenericStateChangeLogger):
580 """Logger for the squawk setting."""
581 def __init__(self):
582 """Construct the logger."""
583 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
584 minDelay = 3.0, maxDelay = 10.0)
585
586#---------------------------------------------------------------------------------------
587
588class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
589 """Base class for the loggers of the various lights."""
590 def __init__(self, attrName, template):
591 """Construct the logger."""
592 StateChangeLogger.__init__(self)
593 SingleValueMixin.__init__(self, attrName)
594
595 self._template = template
596
597 def _getMessage(self, flight, state, forced):
598 """Get the message from the given state."""
599 return self._template % ("ON" if self._getValue(state) else "OFF")
600
601#---------------------------------------------------------------------------------------
602
603class AnticollisionLightsLogger(LightsLogger):
604 """Logger for the anti-collision lights."""
605 def __init__(self):
606 LightsLogger.__init__(self, "antiCollisionLightsOn",
607 "Anti-collision lights: %s")
608
609#---------------------------------------------------------------------------------------
610
611class LandingLightsLogger(LightsLogger):
612 """Logger for the landing lights."""
613 def __init__(self):
614 LightsLogger.__init__(self, "landingLightsOn",
615 "Landing lights: %s")
616
617#---------------------------------------------------------------------------------------
618
619class StrobeLightsLogger(LightsLogger):
620 """Logger for the strobe lights."""
621 def __init__(self):
622 LightsLogger.__init__(self, "strobeLightsOn",
623 "Strobe lights: %s")
624
625#---------------------------------------------------------------------------------------
626
627class NavLightsLogger(LightsLogger):
628 """Logger for the navigational lights."""
629 def __init__(self):
630 LightsLogger.__init__(self, "navLightsOn",
631 "Navigational lights: %s")
632
633#---------------------------------------------------------------------------------------
634
635class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
636 """Logger for the flaps setting."""
637 def __init__(self):
638 """Construct the logger."""
639 StateChangeLogger.__init__(self, logInitial = True)
640 SingleValueMixin.__init__(self, "flapsSet")
641
642 def _getMessage(self, flight, state, forced):
643 """Get the message to log on a change."""
644 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
645 return "Flaps set to %.0f at %.0f %s" % \
646 (state.flapsSet, flight.speedFromKnots(speed),
647 flight.getEnglishSpeedUnit())
648
649#---------------------------------------------------------------------------------------
650
651class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
652 """Logger for the gears state."""
653 def __init__(self):
654 """Construct the logger."""
655 StateChangeLogger.__init__(self, logInitial = True)
656 SingleValueMixin.__init__(self, "gearControlDown")
657
658 def _getMessage(self, flight, state, forced):
659 """Get the message to log on a change."""
660 return "Gears SET to %s at %.0f %s, %.0f feet" % \
661 ("DOWN" if state.gearControlDown else "UP",
662 flight.speedFromKnots(state.ias),
663 flight.getEnglishSpeedUnit(), state.altitude)
664
665#---------------------------------------------------------------------------------------
666
667class FaultChecker(StateChecker):
668 """Base class for checkers that look for faults."""
669 @staticmethod
670 def _appendDuring(flight, message):
671 """Append a 'during XXX' test to the given message, depending on the
672 flight stage."""
673 stageStr = const.stage2string(flight.stage)
674 return message if stageStr is None \
675 else (message + " during " + stageStr.upper())
676
677 @staticmethod
678 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
679 value):
680 """Get the score for a faulty value where the score is calculated
681 linearly within a certain range."""
682 if value<minFaultValue:
683 return 0
684 elif value>maxFaultValue:
685 return maxScore
686 else:
687 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
688 (maxFaultValue - minFaultValue)
689
690#---------------------------------------------------------------------------------------
691
692class SimpleFaultChecker(FaultChecker):
693 """Base class for fault checkers that check for a single occurence of a
694 faulty condition.
695
696 Child classes should implement the following functions:
697 - isCondition(self, flight, aircraft, oldState, state): should return whether the
698 condition holds
699 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
700 via the logger."""
701 def check(self, flight, aircraft, logger, oldState, state):
702 """Perform the check."""
703 if self.isCondition(flight, aircraft, oldState, state):
704 self.logFault(flight, aircraft, logger, oldState, state)
705
706#---------------------------------------------------------------------------------------
707
708class PatientFaultChecker(FaultChecker):
709 """A fault checker that does not decides on a fault when the condition
710 arises immediately, but can wait some time.
711
712 Child classes should implement the following functions:
713 - isCondition(self, flight, aircraft, oldState, state): should return whether the
714 condition holds
715 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
716 via the logger
717 """
718 def __init__(self, timeout = 2.0):
719 """Construct the fault checker with the given timeout."""
720 self._timeout = timeout
721 self._faultStarted = None
722
723 def getTimeout(self, flight, aircraft, oldState, state):
724 """Get the timeout.
725
726 This default implementation returns the timeout given in the
727 constructor, but child classes might want to enforce a different
728 policy."""
729 return self._timeout
730
731 def check(self, flight, aircraft, logger, oldState, state):
732 """Perform the check."""
733 if self.isCondition(flight, aircraft, oldState, state):
734 if self._faultStarted is None:
735 self._faultStarted = state.timestamp
736 timeout = self.getTimeout(flight, aircraft, oldState, state)
737 if state.timestamp>=(self._faultStarted + timeout):
738 self.logFault(flight, aircraft, logger, oldState, state)
739 self._faultStarted = state.timestamp
740 else:
741 self._faultStarted = None
742
743#---------------------------------------------------------------------------------------
744
745class AntiCollisionLightsChecker(PatientFaultChecker):
746 """Check for the anti-collision light being off at high N1 values."""
747 def isCondition(self, flight, aircraft, oldState, state):
748 """Check if the fault condition holds."""
749 return (flight.stage!=const.STAGE_PARKING or \
750 not flight.config.usingFS2Crew) and \
751 not state.antiCollisionLightsOn and \
752 self.isEngineCondition(state)
753
754 def logFault(self, flight, aircraft, logger, oldState, state):
755 """Log the fault."""
756 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
757 FaultChecker._appendDuring(flight,
758 "Anti-collision lights were off"),
759 1)
760
761 def isEngineCondition(self, state):
762 """Determine if the engines are in such a state that the lights should
763 be on."""
764 if state.n1 is not None:
765 return max(state.n1)>5
766 elif state.rpm is not None:
767 return max(state.rpm)>0
768 else:
769 return False
770
771#---------------------------------------------------------------------------------------
772
773class TupolevAntiCollisionLightsChecker(AntiCollisionLightsChecker):
774 """Check for the anti-collision light for Tuplev planes."""
775 def isCondition(self, flight, aircraft, oldState, state):
776 """Check if the fault condition holds."""
777 numEnginesRunning = 0
778 for n1 in state.n1:
779 if n1>5: numEnginesRunning += 1
780
781 if flight.stage==const.STAGE_PARKING:
782 return numEnginesRunning<len(state.n1) \
783 and state.antiCollisionLightsOn
784 else:
785 return numEnginesRunning>1 and not state.antiCollisionLightsOn or \
786 numEnginesRunning<1 and state.antiCollisionLightsOn
787
788#---------------------------------------------------------------------------------------
789
790class BankChecker(SimpleFaultChecker):
791 """Check for the bank is within limits."""
792 def isCondition(self, flight, aircraft, oldState, state):
793 """Check if the fault condition holds."""
794 if flight.stage==const.STAGE_CRUISE:
795 bankLimit = 30
796 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
797 const.STAGE_DESCENT, const.STAGE_LANDING]:
798 bankLimit = 35
799 else:
800 return False
801
802 return state.bank>bankLimit or state.bank<-bankLimit
803
804 def logFault(self, flight, aircraft, logger, oldState, state):
805 """Log the fault."""
806 flight.handleFault(BankChecker, state.timestamp,
807 FaultChecker._appendDuring(flight, "Bank too steep"),
808 2)
809
810#---------------------------------------------------------------------------------------
811
812class FlapsRetractChecker(SimpleFaultChecker):
813 """Check if the flaps are not retracted too early."""
814 def __init__(self):
815 """Construct the flaps checker."""
816 self._timeStart = None
817
818 def isCondition(self, flight, aircraft, oldState, state):
819 """Check if the fault condition holds.
820
821 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
822 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
823 (flight.stage==const.STAGE_LANDING and state.onTheGround):
824 if self._timeStart is None:
825 self._timeStart = state.timestamp
826
827 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
828 return True
829 else:
830 self._timeStart = None
831 return False
832
833 def logFault(self, flight, aircraft, logger, oldState, state):
834 """Log the fault."""
835 flight.handleFault(FlapsRetractChecker, state.timestamp,
836 FaultChecker._appendDuring(flight, "Flaps retracted"),
837 20)
838
839#---------------------------------------------------------------------------------------
840
841class FlapsSpeedLimitChecker(SimpleFaultChecker):
842 """Check if the flaps are extended only at the right speeds."""
843 def isCondition(self, flight, aircraft, oldState, state):
844 """Check if the fault condition holds."""
845 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
846 return speedLimit is not None and state.smoothedIAS>speedLimit
847
848 def logFault(self, flight, aircraft, logger, oldState, state):
849 """Log the fault."""
850 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
851 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
852 5)
853
854#---------------------------------------------------------------------------------------
855
856class GearsDownChecker(SimpleFaultChecker):
857 """Check if the gears are down at low altitudes."""
858 def isCondition(self, flight, aircraft, oldState, state):
859 """Check if the fault condition holds."""
860 return state.radioAltitude<10 and not state.gearsDown and \
861 flight.stage!=const.STAGE_TAKEOFF
862
863 def logFault(self, flight, aircraft, logger, oldState, state):
864 """Log the fault."""
865 flight.handleNoGo(GearsDownChecker, state.timestamp,
866 "Gears not down at %.0f feet radio altitude" % \
867 (state.radioAltitude,),
868 "GEAR DOWN NO GO")
869
870#---------------------------------------------------------------------------------------
871
872class GearSpeedLimitChecker(PatientFaultChecker):
873 """Check if the gears not down at too high a speed."""
874 def isCondition(self, flight, aircraft, oldState, state):
875 """Check if the fault condition holds."""
876 return state.gearsDown and state.smoothedIAS>aircraft.gearSpeedLimit
877
878 def logFault(self, flight, aircraft, logger, oldState, state):
879 """Log the fault."""
880 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
881 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
882 5)
883
884#---------------------------------------------------------------------------------------
885
886class GLoadChecker(SimpleFaultChecker):
887 """Check if the G-load does not exceed 2 except during flare."""
888 def isCondition(self, flight, aircraft, oldState, state):
889 """Check if the fault condition holds."""
890 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
891 state.radioAltitude>=50)
892
893 def logFault(self, flight, aircraft, logger, oldState, state):
894 """Log the fault."""
895 flight.handleFault(GLoadChecker, state.timestamp,
896 "G-load was %.2f" % (state.gLoad,),
897 10)
898
899#---------------------------------------------------------------------------------------
900
901class LandingLightsChecker(PatientFaultChecker):
902 """Check if the landing lights are used properly."""
903 def getTimeout(self, flight, aircraft, oldState, state):
904 """Get the timeout.
905
906 It is the default timeout except for landing and takeoff."""
907 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
908 const.STAGE_LANDING] else self._timeout
909
910 def isCondition(self, flight, aircraft, oldState, state):
911 """Check if the fault condition holds."""
912 return state.landingLightsOn is not None and \
913 ((flight.stage==const.STAGE_BOARDING and \
914 state.landingLightsOn and state.onTheGround) or \
915 (flight.stage==const.STAGE_TAKEOFF and \
916 not state.landingLightsOn and not state.onTheGround) or \
917 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
918 const.STAGE_DESCENT] and \
919 state.landingLightsOn and state.altitude>12500) or \
920 (flight.stage==const.STAGE_LANDING and \
921 not state.landingLightsOn and state.onTheGround) or \
922 (flight.stage==const.STAGE_PARKING and \
923 state.landingLightsOn and state.onTheGround))
924
925 def logFault(self, flight, aircraft, logger, oldState, state):
926 """Log the fault."""
927 score = 0 if flight.stage==const.STAGE_LANDING else 1
928 message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
929 flight.handleFault(LandingLightsChecker, state.timestamp,
930 FaultChecker._appendDuring(flight, message),
931 score)
932
933#---------------------------------------------------------------------------------------
934
935class WeightChecker(PatientFaultChecker):
936 """Base class for checkers that check that some limit is not exceeded."""
937 def __init__(self, name):
938 """Construct the checker."""
939 super(WeightChecker, self).__init__(timeout = 5.0)
940 self._name = name
941
942 def isCondition(self, flight, aircraft, oldState, state):
943 """Check if the fault condition holds."""
944 if flight.entranceExam:
945 return False
946
947 limit = self.getLimit(flight, aircraft, state)
948 if limit is not None:
949 #if flight.options.compensation is not None:
950 # limit += flight.options.compensation
951 return self.getWeight(state)>limit
952
953 return False
954
955 def logFault(self, flight, aircraft, logger, oldState, state):
956 """Log the fault."""
957 mname = "M" + self._name
958 flight.handleNoGo(self.__class__, state.timestamp,
959 "%s exceeded: %s is %.0f kg" % \
960 (mname, self._name, self.getWeight(state)),
961 "%s NO GO" % (mname,))
962
963 def getWeight(self, state):
964 """Get the weight that is interesting for us."""
965 return state.grossWeight
966
967#---------------------------------------------------------------------------------------
968
969class MLWChecker(WeightChecker):
970 """Checks if the MLW is not exceeded on landing."""
971 def __init__(self):
972 """Construct the checker."""
973 super(MLWChecker, self).__init__("LW")
974
975 def getLimit(self, flight, aircraft, state):
976 """Get the limit if we are in the right state."""
977 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
978 state.onTheGround and \
979 not flight.entranceExam else None
980
981#---------------------------------------------------------------------------------------
982
983class MTOWChecker(WeightChecker):
984 """Checks if the MTOW is not exceeded on landing."""
985 def __init__(self):
986 """Construct the checker."""
987 super(MTOWChecker, self).__init__("TOW")
988
989 def getLimit(self, flight, aircraft, state):
990 """Get the limit if we are in the right state."""
991 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF and \
992 not flight.entranceExam else None
993
994#---------------------------------------------------------------------------------------
995
996class MZFWChecker(WeightChecker):
997 """Checks if the MZFW is not exceeded on landing."""
998 def __init__(self):
999 """Construct the checker."""
1000 super(MZFWChecker, self).__init__("ZFW")
1001
1002 def getLimit(self, flight, aircraft, state):
1003 """Get the limit if we are in the right state."""
1004 return aircraft.mzfw if not flight.entranceExam else None
1005
1006 def getWeight(self, state):
1007 """Get the weight that is interesting for us."""
1008 return state.zfw
1009
1010#---------------------------------------------------------------------------------------
1011
1012class NavLightsChecker(PatientFaultChecker):
1013 """Check if the navigational lights are used properly."""
1014 def isCondition(self, flight, aircraft, oldState, state):
1015 """Check if the fault condition holds."""
1016 return flight.stage!=const.STAGE_BOARDING and \
1017 flight.stage!=const.STAGE_PARKING and \
1018 not state.navLightsOn
1019
1020 def logFault(self, flight, aircraft, logger, oldState, state):
1021 """Log the fault."""
1022 flight.handleFault(NavLightsChecker, state.timestamp,
1023 FaultChecker._appendDuring(flight,
1024 "Navigation lights were off"),
1025 1)
1026
1027#---------------------------------------------------------------------------------------
1028
1029class OverspeedChecker(PatientFaultChecker):
1030 """Check if Vne has been exceeded."""
1031 def __init__(self, timeout = 5.0):
1032 """Construct the checker."""
1033 super(OverspeedChecker, self).__init__(timeout = timeout)
1034
1035 def isCondition(self, flight, aircraft, oldState, state):
1036 """Check if the fault condition holds."""
1037 return state.overspeed
1038
1039 def logFault(self, flight, aircraft, logger, oldState, state):
1040 """Log the fault."""
1041 flight.handleFault(OverspeedChecker, state.timestamp,
1042 FaultChecker._appendDuring(flight, "Overspeed"),
1043 20)
1044
1045#---------------------------------------------------------------------------------------
1046
1047class PayloadChecker(SimpleFaultChecker):
1048 """Check if the payload matches the specification."""
1049 TOLERANCE=550
1050
1051 @staticmethod
1052 def isZFWFaulty(aircraftZFW, flightZFW):
1053 """Check if the given aircraft's ZFW is outside of the limits."""
1054 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
1055 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
1056
1057 def isCondition(self, flight, aircraft, oldState, state):
1058 """Check if the fault condition holds."""
1059 return not flight.entranceExam and \
1060 flight.stage==const.STAGE_PUSHANDTAXI and \
1061 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
1062
1063 def logFault(self, flight, aircraft, logger, oldState, state):
1064 """Log the fault."""
1065 flight.handleNoGo(PayloadChecker, state.timestamp,
1066 "ZFW difference is more than %d kgs" % \
1067 (PayloadChecker.TOLERANCE,),
1068 "ZFW NO GO")
1069
1070#---------------------------------------------------------------------------------------
1071
1072class PitotChecker(PatientFaultChecker):
1073 """Check if pitot heat is on."""
1074 def __init__(self):
1075 """Construct the checker."""
1076 super(PitotChecker, self).__init__(timeout = 3.0)
1077
1078 def isCondition(self, flight, aircraft, oldState, state):
1079 """Check if the fault condition holds."""
1080 return state.groundSpeed>80 and not state.pitotHeatOn
1081
1082 def logFault(self, flight, aircraft, logger, oldState, state):
1083 """Log the fault."""
1084 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1085 const.STAGE_CRUISE, const.STAGE_DESCENT,
1086 const.STAGE_LANDING] else 0
1087 flight.handleFault(PitotChecker, state.timestamp,
1088 FaultChecker._appendDuring(flight, "Pitot heat was off"),
1089 score)
1090
1091#---------------------------------------------------------------------------------------
1092
1093class ReverserChecker(SimpleFaultChecker):
1094 """Check if the reverser is not used below 60 knots."""
1095 def isCondition(self, flight, aircraft, oldState, state):
1096 """Check if the fault condition holds."""
1097 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
1098 const.STAGE_TAXIAFTERLAND] and \
1099 state.groundSpeed<60 and max(state.reverser)
1100
1101 def logFault(self, flight, aircraft, logger, oldState, state):
1102 """Log the fault."""
1103 message = "Reverser used below %.0f %s" % \
1104 (flight.speedFromKnots(60), flight.getEnglishSpeedUnit())
1105 flight.handleFault(ReverserChecker, state.timestamp,
1106 FaultChecker._appendDuring(flight, message),
1107 15)
1108
1109#---------------------------------------------------------------------------------------
1110
1111class SpeedChecker(SimpleFaultChecker):
1112 """Check if the speed is in the prescribed limits."""
1113 def isCondition(self, flight, aircraft, oldState, state):
1114 """Check if the fault condition holds."""
1115 return flight.stage in [const.STAGE_PUSHANDTAXI,
1116 const.STAGE_TAXIAFTERLAND] and \
1117 state.groundSpeed>50
1118
1119 def logFault(self, flight, aircraft, logger, oldState, state):
1120 """Log the fault."""
1121 message = "Taxi speed over %.0f %s" % \
1122 (flight.speedFromKnots(50), flight.getEnglishSpeedUnit())
1123 flight.handleFault(SpeedChecker, state.timestamp,
1124 FaultChecker._appendDuring(flight, message),
1125 FaultChecker._getLinearScore(50, 80, 10, 15,
1126 state.groundSpeed))
1127
1128#---------------------------------------------------------------------------------------
1129
1130class StallChecker(PatientFaultChecker):
1131 """Check if stall occured."""
1132 def isCondition(self, flight, aircraft, oldState, state):
1133 """Check if the fault condition holds."""
1134 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
1135 const.STAGE_CRUISE, const.STAGE_DESCENT,
1136 const.STAGE_LANDING] and state.stalled
1137
1138 def logFault(self, flight, aircraft, logger, oldState, state):
1139 """Log the fault."""
1140 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
1141 const.STAGE_LANDING] else 30
1142 flight.handleFault(StallChecker, state.timestamp,
1143 FaultChecker._appendDuring(flight, "Stalled"),
1144 score)
1145
1146#---------------------------------------------------------------------------------------
1147
1148class StrobeLightsChecker(PatientFaultChecker):
1149 """Check if the strobe lights are used properly."""
1150 def isCondition(self, flight, aircraft, oldState, state):
1151 """Check if the fault condition holds."""
1152 return (flight.stage==const.STAGE_BOARDING and \
1153 state.strobeLightsOn and state.onTheGround) or \
1154 (flight.stage==const.STAGE_TAKEOFF and \
1155 not state.strobeLightsOn and not state.gearsDown) or \
1156 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
1157 const.STAGE_DESCENT] and \
1158 not state.strobeLightsOn and not state.onTheGround) or \
1159 (flight.stage==const.STAGE_PARKING and \
1160 state.strobeLightsOn and state.onTheGround)
1161
1162 def logFault(self, flight, aircraft, logger, oldState, state):
1163 """Log the fault."""
1164 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
1165 flight.handleFault(StrobeLightsChecker, state.timestamp,
1166 FaultChecker._appendDuring(flight, message),
1167 1)
1168
1169#---------------------------------------------------------------------------------------
1170
1171class ThrustChecker(SimpleFaultChecker):
1172 """Check if the thrust setting is not too high during takeoff.
1173
1174 FIXME: is this really so general, for all aircraft?"""
1175 def isCondition(self, flight, aircraft, oldState, state):
1176 """Check if the fault condition holds."""
1177 return flight.stage==const.STAGE_TAKEOFF and \
1178 state.n1 is not None and max(state.n1)>97
1179
1180 def logFault(self, flight, aircraft, logger, oldState, state):
1181 """Log the fault."""
1182 flight.handleFault(ThrustChecker, state.timestamp,
1183 FaultChecker._appendDuring(flight,
1184 "Thrust setting was too high (>97%)"),
1185 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
1186
1187#---------------------------------------------------------------------------------------
1188
1189class VSChecker(SimpleFaultChecker):
1190 """Check if the vertical speed is not too low at certain altitudes"""
1191 BELOW10000 = -5000
1192 BELOW5000 = -2500
1193 BELOW2500 = -1500
1194 BELOW500 = -1000
1195 TOLERANCE = 1.2
1196
1197 def isCondition(self, flight, aircraft, oldState, state):
1198 """Check if the fault condition holds."""
1199 vs = state.smoothedVS
1200 altitude = state.altitude
1201 return vs < -8000 or vs > 8000 or \
1202 (altitude<500 and vs < (VSChecker.BELOW500 *
1203 VSChecker.TOLERANCE)) or \
1204 (altitude<2500 and vs < (VSChecker.BELOW2500 *
1205 VSChecker.TOLERANCE)) or \
1206 (altitude<5000 and vs < (VSChecker.BELOW5000 *
1207 VSChecker.TOLERANCE)) or \
1208 (altitude<10000 and vs < (VSChecker.BELOW10000 *
1209 VSChecker.TOLERANCE))
1210
1211 def logFault(self, flight, aircraft, logger, oldState, state):
1212 """Log the fault."""
1213 vs = state.smoothedVS
1214
1215 message = "Vertical speed was %.0f feet/min" % (vs,)
1216 if vs>-8000 and vs<8000:
1217 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
1218
1219 score = 10 if vs<-8000 or vs>8000 else 0
1220
1221 flight.handleFault(VSChecker, state.timestamp,
1222 FaultChecker._appendDuring(flight, message),
1223 score)
1224
1225#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.