source: src/mlx/checks.py@ 307:81a8a915717a

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

The condition in the anti-collision lights checker is reworked for Tupolev aircraft

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