source: src/mlx/checks.py@ 600:c788380d2fea

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

The manual speedbrake operation is logged (re #245)

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