source: src/mlx/checks.py@ 562:1d03568e5f67

Last change on this file since 562:1d03568e5f67 was 562:1d03568e5f67, checked in by István Váradi <ivaradi@…>, 10 years ago

The light loggers became delayed loggers (re #228)

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