source: src/mlx/checks.py@ 320:cc7c3c84efa0

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

Added support for logging the OBS settings for navigational radios

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