source: src/mlx/checks.py@ 134:9ce031d5d4a9

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

Most of the remaining messages are implemented

File size: 41.4 KB
Line 
1# The various checks that may be performed during flight
2
3#---------------------------------------------------------------------------------------
4
5import fs
6import const
7import util
8
9#---------------------------------------------------------------------------------------
10
11class StateChecker(object):
12 """Base class for classes the instances of which check the aircraft's state
13 from some aspect.
14
15 As a result of the check they may log something, or notify of some fault, etc."""
16 def check(self, flight, aircraft, logger, oldState, state):
17 """Perform the check and do whatever is needed.
18
19 This default implementation raises a NotImplementedError."""
20 raise NotImplementedError()
21
22#---------------------------------------------------------------------------------------
23
24class StageChecker(StateChecker):
25 """Check the flight stage transitions."""
26 def __init__(self):
27 """Construct the stage checker."""
28 self._flareStarted = False
29
30 def check(self, flight, aircraft, logger, oldState, state):
31 """Check the stage of the aircraft."""
32 stage = flight.stage
33 if stage==None:
34 aircraft.setStage(state, const.STAGE_BOARDING)
35 elif stage==const.STAGE_BOARDING:
36 if not state.parking or \
37 (not state.trickMode and state.groundSpeed>5.0):
38 aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
39 elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
40 if state.landingLightsOn or state.strobeLightsOn or \
41 state.groundSpeed>80.0:
42 aircraft.setStage(state, const.STAGE_TAKEOFF)
43 elif stage==const.STAGE_TAKEOFF:
44 if not state.gearsDown or \
45 (state.radioAltitude>3000.0 and state.vs>0):
46 aircraft.setStage(state, const.STAGE_CLIMB)
47 elif not state.landingLightsOn and \
48 not state.strobeLightsOn and \
49 state.onTheGround and \
50 state.groundSpeed<50.0:
51 aircraft.setStage(state, const.STAGE_RTO)
52 elif stage==const.STAGE_CLIMB:
53 if (state.altitude+2000) > flight.cruiseAltitude:
54 aircraft.setStage(state, const.STAGE_CRUISE)
55 elif state.radioAltitude<2000.0 and \
56 state.vs < 0.0 and state.gearsDown:
57 aircraft.setStage(state, const.STAGE_LANDING)
58 elif stage==const.STAGE_CRUISE:
59 if (state.altitude+2000) < flight.cruiseAltitude:
60 aircraft.setStage(state, const.STAGE_DESCENT)
61 elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
62 if state.gearsDown and state.radioAltitude<2000.0:
63 aircraft.setStage(state, const.STAGE_LANDING)
64 elif (state.altitude+2000) > flight.cruiseAltitude:
65 aircraft.setStage(state, const.STAGE_CRUISE)
66 elif stage==const.STAGE_LANDING:
67 if state.onTheGround and state.groundSpeed<50.0:
68 aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
69 elif not state.gearsDown:
70 aircraft.setStage(state, const.STAGE_GOAROUND)
71 elif state.radioAltitude>200 and self._flareStarted:
72 aircraft.cancelFlare()
73 self._flareStarted = False
74 elif state.radioAltitude<150 and not state.onTheGround and \
75 not self._flareStarted:
76 self._flareStarted = True
77 aircraft.prepareFlare()
78 elif stage==const.STAGE_TAXIAFTERLAND:
79 if state.parking:
80 aircraft.setStage(state, const.STAGE_PARKING)
81 elif stage==const.STAGE_PARKING:
82 if aircraft.checkFlightEnd(state):
83 aircraft.setStage(state, const.STAGE_END)
84
85#---------------------------------------------------------------------------------------
86
87class TakeOffLogger(StateChecker):
88 """Logger for the cruise speed."""
89 def __init__(self):
90 """Construct the logger."""
91 self._onTheGround = True
92
93 def check(self, flight, aircraft, logger, oldState, state):
94 """Log the cruise speed if necessary."""
95 if flight.stage==const.STAGE_TAKEOFF and \
96 self._onTheGround and not state.onTheGround:
97 logger.message(state.timestamp,
98 "Takeoff speed: %.0f knots" % (state.ias,))
99 logger.message(state.timestamp,
100 "Takeoff heading: %03.0f degrees" % (state.heading,))
101 logger.message(state.timestamp,
102 "Takeoff pitch: %.1f degrees" % (state.pitch,))
103 self._onTheGround = False
104
105#---------------------------------------------------------------------------------------
106
107class CruiseSpeedLogger(StateChecker):
108 """Logger for the cruise speed."""
109 def __init__(self):
110 """Construct the logger."""
111 self._lastTime = None
112
113 def check(self, flight, aircraft, logger, oldState, state):
114 """Log the cruise speed if necessary."""
115 if flight.stage==const.STAGE_CRUISE and \
116 (self._lastTime is None or \
117 (self._lastTime+800)<=state.timestamp):
118 if state.altitude>24500.0:
119 logger.message(state.timestamp,
120 "Cruise speed: %.3f mach" % (state.mach,))
121 else:
122 logger.message(state.timestamp,
123 "Cruise speed: %.0f knots" % (state.ias,))
124 self._lastTime = state.timestamp
125
126#---------------------------------------------------------------------------------------
127
128class SpoilerLogger(StateChecker):
129 """Logger for the cruise speed."""
130 def __init__(self):
131 """Construct the logger."""
132 self._logged = False
133 self._spoilersExtension = None
134
135 def check(self, flight, aircraft, logger, oldState, state):
136 """Log the cruise speed if necessary."""
137 if flight.stage==const.STAGE_LANDING and not self._logged:
138 if state.onTheGround:
139 if state.spoilersExtension!=self._spoilersExtension:
140 logger.message(state.timestamp, "Spoilers deployed")
141 self._logged = True
142 else:
143 self._spoilersExtension = state.spoilersExtension
144
145#---------------------------------------------------------------------------------------
146
147class VisibilityChecker(StateChecker):
148 """Inform the pilot of the visibility once when descending below 2000 ft,
149 then when descending below 1000 ft."""
150 def __init__(self):
151 """Construct the visibility checker."""
152 self._informedBelow2000 = False
153 self._informedBelow1000 = False
154
155 def check(self, flight, aircraft, logger, oldState, state):
156 """Check if we need to inform the pilot of the visibility."""
157 if flight.stage==const.STAGE_DESCENT or \
158 flight.stage==const.STAGE_LANDING:
159 if (state.radioAltitude<2000 and not self._informedBelow2000) or \
160 (state.radioAltitude<1000 and not self._informedBelow1000):
161 visibilityString = util.visibility2String(state.visibility)
162 fs.sendMessage(const.MESSAGETYPE_VISIBILITY,
163 "Current visibility: " + visibilityString,
164 5)
165 logger.message(state.timestamp,
166 "Pilot was informed about the visibility: " +
167 visibilityString)
168 self._informedBelow2000 = True
169 self._informedBelow1000 = state.radioAltitude<1000
170
171#---------------------------------------------------------------------------------------
172
173class StateChangeLogger(StateChecker):
174 """Base class for classes the instances of which check if a specific change has
175 occured in the aircraft's state, and log such change."""
176 def __init__(self, logInitial = True):
177 """Construct the logger.
178
179 If logInitial is True, the initial value will be logged, not just the
180 changes later.
181
182 Child classes should define the following functions:
183 - _changed(self, oldState, state): returns a boolean indicating if the
184 value has changed or not
185 - _getMessage(self, state): return a strings containing the message to log
186 with the new value
187 """
188 self._logInitial = logInitial
189
190 def _getLogTimestamp(self, state):
191 """Get the log timestamp."""
192 return state.timestamp
193
194 def check(self, flight, aircraft, logger, oldState, state):
195 """Check if the state has changed, and if so, log the new state."""
196 shouldLog = False
197 if oldState is None:
198 shouldLog = self._logInitial
199 else:
200 shouldLog = self._changed(oldState, state)
201
202 if shouldLog:
203 logger.message(self._getLogTimestamp(state), self._getMessage(state))
204
205#---------------------------------------------------------------------------------------
206
207class SimpleChangeMixin(object):
208 """A mixin that defines a _changed() function which simply calls a function
209 to retrieve the value two monitor for both states and compares them.
210
211 Child classes should define the following function:
212 - _getValue(state): get the value we are interested in."""
213 def _changed(self, oldState, state):
214 """Determine if the value has changed."""
215 return self._getValue(oldState)!=self._getValue(state)
216
217#---------------------------------------------------------------------------------------
218
219class SingleValueMixin(object):
220 """A mixin that provides a _getValue() function to query a value from the
221 state using the name of the attribute."""
222 def __init__(self, attrName):
223 """Construct the mixin with the given attribute name."""
224 self._attrName = attrName
225
226 def _getValue(self, state):
227 """Get the value of the attribute from the state."""
228 return getattr(state, self._attrName)
229
230#---------------------------------------------------------------------------------------
231
232class DelayedChangeMixin(object):
233 """A mixin to a StateChangeLogger that stores the old value and reports a
234 change only if the change has been there for a certain amount of time.
235
236 Child classes should define the following function:
237 - _getValue(state): get the value we are interested in."""
238 def __init__(self, minDelay = 3.0, maxDelay = 10.0):
239 """Construct the mixin with the given delay in seconds."""
240 self._minDelay = minDelay
241 self._maxDelay = maxDelay
242 self._oldValue = None
243 self._firstChange = None
244 self._lastChangeState = None
245
246 def _changed(self, oldState, state):
247 """Determine if the value has changed."""
248 if self._oldValue is None:
249 self._oldValue = self._getValue(oldState)
250
251 newValue = self._getValue(state)
252 if self._oldValue!=newValue:
253 if self._firstChange is None:
254 self._firstChange = state.timestamp
255 self._lastChangeState = state
256 self._oldValue = newValue
257
258 if self._firstChange is not None:
259 if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
260 self._firstChange + self._maxDelay):
261 self._firstChange = None
262 return True
263
264 return False
265
266 def _getLogTimestamp(self, state):
267 """Get the log timestamp."""
268 return self._lastChangeState.timestamp if \
269 self._lastChangeState is not None else state.timestamp
270
271#---------------------------------------------------------------------------------------
272
273class TemplateMessageMixin(object):
274 """Mixin to generate a message based on a template.
275
276 Child classes should define the following function:
277 - _getValue(state): get the value we are interested in."""
278 def __init__(self, template):
279 """Construct the mixin."""
280 self._template = template
281
282 def _getMessage(self, state):
283 """Get the message."""
284 return self._template % (self._getValue(state),)
285
286#---------------------------------------------------------------------------------------
287
288class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
289 DelayedChangeMixin, TemplateMessageMixin):
290 """Base for generic state change loggers that monitor a single value in the
291 state possibly with a delay and the logged message comes from a template"""
292 def __init__(self, attrName, template, logInitial = True,
293 minDelay = 0.0, maxDelay = 0.0):
294 """Construct the object."""
295 StateChangeLogger.__init__(self, logInitial = logInitial)
296 SingleValueMixin.__init__(self, attrName)
297 DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
298 TemplateMessageMixin.__init__(self, template)
299 self._getLogTimestamp = lambda state: \
300 DelayedChangeMixin._getLogTimestamp(self, state)
301
302#---------------------------------------------------------------------------------------
303
304class AltimeterLogger(StateChangeLogger, SingleValueMixin,
305 DelayedChangeMixin):
306 """Logger for the altimeter setting."""
307 def __init__(self):
308 """Construct the logger."""
309 StateChangeLogger.__init__(self, logInitial = True)
310 SingleValueMixin.__init__(self, "altimeter")
311 DelayedChangeMixin.__init__(self)
312 self._getLogTimestamp = lambda state: \
313 DelayedChangeMixin._getLogTimestamp(self, state)
314
315 def _getMessage(self, state):
316 """Get the message to log on a change."""
317 logState = self._lastChangeState if \
318 self._lastChangeState is not None else state
319 return "Altimeter: %.0f hPa at %.0f feet" % \
320 (logState.altimeter, logState.altitude)
321
322#---------------------------------------------------------------------------------------
323
324class NAV1Logger(GenericStateChangeLogger):
325 """Logger for the NAV1 radio setting."""
326 def __init__(self):
327 """Construct the logger."""
328 super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
329
330#---------------------------------------------------------------------------------------
331
332class NAV2Logger(GenericStateChangeLogger):
333 """Logger for the NAV2 radio setting."""
334 def __init__(self):
335 """Construct the logger."""
336 super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
337
338#---------------------------------------------------------------------------------------
339
340class SquawkLogger(GenericStateChangeLogger):
341 """Logger for the squawk setting."""
342 def __init__(self):
343 """Construct the logger."""
344 super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
345 minDelay = 3.0, maxDelay = 10.0)
346
347#---------------------------------------------------------------------------------------
348
349class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
350 """Base class for the loggers of the various lights."""
351 def __init__(self, attrName, template):
352 """Construct the logger."""
353 StateChangeLogger.__init__(self)
354 SingleValueMixin.__init__(self, attrName)
355
356 self._template = template
357
358 def _getMessage(self, state):
359 """Get the message from the given state."""
360 return self._template % ("ON" if self._getValue(state) else "OFF")
361
362#---------------------------------------------------------------------------------------
363
364class AnticollisionLightsLogger(LightsLogger):
365 """Logger for the anti-collision lights."""
366 def __init__(self):
367 LightsLogger.__init__(self, "antiCollisionLightsOn",
368 "Anti-collision lights: %s")
369
370#---------------------------------------------------------------------------------------
371
372class LandingLightsLogger(LightsLogger):
373 """Logger for the landing lights."""
374 def __init__(self):
375 LightsLogger.__init__(self, "landingLightsOn",
376 "Landing lights: %s")
377
378#---------------------------------------------------------------------------------------
379
380class StrobeLightsLogger(LightsLogger):
381 """Logger for the strobe lights."""
382 def __init__(self):
383 LightsLogger.__init__(self, "strobeLightsOn",
384 "Strobe lights: %s")
385
386#---------------------------------------------------------------------------------------
387
388class NavLightsLogger(LightsLogger):
389 """Logger for the navigational lights."""
390 def __init__(self):
391 LightsLogger.__init__(self, "navLightsOn",
392 "Navigational lights: %s")
393
394#---------------------------------------------------------------------------------------
395
396class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
397 """Logger for the flaps setting."""
398 def __init__(self):
399 """Construct the logger."""
400 StateChangeLogger.__init__(self, logInitial = True)
401 SingleValueMixin.__init__(self, "flapsSet")
402
403 def _getMessage(self, state):
404 """Get the message to log on a change."""
405 speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
406 return "Flaps set to %.0f at %.0f knots" % (state.flapsSet, speed)
407
408#---------------------------------------------------------------------------------------
409
410class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
411 """Logger for the gears state."""
412 def __init__(self):
413 """Construct the logger."""
414 StateChangeLogger.__init__(self, logInitial = True)
415 SingleValueMixin.__init__(self, "gearsDown")
416
417 def _getMessage(self, state):
418 """Get the message to log on a change."""
419 return "Gears %s at %.0f knots, %.0f feet" % \
420 ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
421
422#---------------------------------------------------------------------------------------
423
424class FaultChecker(StateChecker):
425 """Base class for checkers that look for faults."""
426 @staticmethod
427 def _appendDuring(flight, message):
428 """Append a 'during XXX' test to the given message, depending on the
429 flight stage."""
430 stageStr = const.stage2string(flight.stage)
431 return message if stageStr is None \
432 else (message + " during " + stageStr.upper())
433
434 @staticmethod
435 def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
436 value):
437 """Get the score for a faulty value where the score is calculated
438 linearly within a certain range."""
439 if value<minFaultValue:
440 return 0
441 elif value>maxFaultValue:
442 return maxScore
443 else:
444 return minScore + (maxScore-minScore) * (value-minFaultValue) / \
445 (maxFaultValue - minFaultValue)
446
447#---------------------------------------------------------------------------------------
448
449class SimpleFaultChecker(FaultChecker):
450 """Base class for fault checkers that check for a single occurence of a
451 faulty condition.
452
453 Child classes should implement the following functions:
454 - isCondition(self, flight, aircraft, oldState, state): should return whether the
455 condition holds
456 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
457 via the logger."""
458 def check(self, flight, aircraft, logger, oldState, state):
459 """Perform the check."""
460 if self.isCondition(flight, aircraft, oldState, state):
461 self.logFault(flight, aircraft, logger, oldState, state)
462
463#---------------------------------------------------------------------------------------
464
465class PatientFaultChecker(FaultChecker):
466 """A fault checker that does not decides on a fault when the condition
467 arises immediately, but can wait some time.
468
469 Child classes should implement the following functions:
470 - isCondition(self, flight, aircraft, oldState, state): should return whether the
471 condition holds
472 - logFault(self, flight, aircraft, logger, oldState, state): log the fault
473 via the logger
474 """
475 def __init__(self, timeout = 2.0):
476 """Construct the fault checker with the given timeout."""
477 self._timeout = timeout
478 self._faultStarted = None
479
480 def getTimeout(self, flight, aircraft, oldState, state):
481 """Get the timeout.
482
483 This default implementation returns the timeout given in the
484 constructor, but child classes might want to enforce a different
485 policy."""
486 return self._timeout
487
488 def check(self, flight, aircraft, logger, oldState, state):
489 """Perform the check."""
490 if self.isCondition(flight, aircraft, oldState, state):
491 if self._faultStarted is None:
492 self._faultStarted = state.timestamp
493 timeout = self.getTimeout(flight, aircraft, oldState, state)
494 if state.timestamp>=(self._faultStarted + timeout):
495 self.logFault(flight, aircraft, logger, oldState, state)
496 self._faultStarted = state.timestamp
497 else:
498 self._faultStarted = None
499
500#---------------------------------------------------------------------------------------
501
502class AntiCollisionLightsChecker(PatientFaultChecker):
503 """Check for the anti-collision light being off at high N1 values."""
504 def isCondition(self, flight, aircraft, oldState, state):
505 """Check if the fault condition holds."""
506 isTupolev = aircraft.type in [const.AIRCRAFT_T134,
507 const.AIRCRAFT_T154]
508 return (flight.stage!=const.STAGE_PARKING or \
509 not flight.options.fs2Crew) and \
510 not state.antiCollisionLightsOn and \
511 ((isTupolev and max(state.n1[1:])>5) or \
512 (not isTupolev and max(state.n1)>5))
513
514 def logFault(self, flight, aircraft, logger, oldState, state):
515 """Log the fault."""
516 flight.handleFault(AntiCollisionLightsChecker, state.timestamp,
517 FaultChecker._appendDuring(flight,
518 "Anti-collision lights were off"),
519 1)
520
521#---------------------------------------------------------------------------------------
522
523class BankChecker(SimpleFaultChecker):
524 """Check for the anti-collision light being off at high N1 values."""
525 def isCondition(self, flight, aircraft, oldState, state):
526 """Check if the fault condition holds."""
527 if flight.stage==const.STAGE_CRUISE:
528 bankLimit = 30
529 elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
530 const.STAGE_DESCENT, const.STAGE_LANDING]:
531 bankLimit = 35
532 else:
533 return False
534
535 return state.bank>bankLimit or state.bank<-bankLimit
536
537 def logFault(self, flight, aircraft, logger, oldState, state):
538 """Log the fault."""
539 flight.handleFault(BankChecker, state.timestamp,
540 FaultChecker._appendDuring(flight, "Bank too steep"),
541 2)
542
543#---------------------------------------------------------------------------------------
544
545class FlapsRetractChecker(SimpleFaultChecker):
546 """Check if the flaps are not retracted too early."""
547 def __init__(self):
548 """Construct the flaps checker."""
549 self._timeStart = None
550
551 def isCondition(self, flight, aircraft, oldState, state):
552 """Check if the fault condition holds.
553
554 FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
555 if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
556 (flight.stage==const.STAGE_LANDING and state.onTheGround):
557 if self._timeStart is None:
558 self._timeStart = state.timestamp
559
560 if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
561 return True
562 else:
563 self._timeStart = None
564 return False
565
566 def logFault(self, flight, aircraft, logger, oldState, state):
567 """Log the fault."""
568 flight.handleFault(FlapsRetractChecker, state.timestamp,
569 FaultChecker._appendDuring(flight, "Flaps retracted"),
570 20)
571
572#---------------------------------------------------------------------------------------
573
574class FlapsSpeedLimitChecker(SimpleFaultChecker):
575 """Check if the flaps are extended only at the right speeds."""
576 def isCondition(self, flight, aircraft, oldState, state):
577 """Check if the fault condition holds."""
578 speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
579 return speedLimit is not None and state.ias>speedLimit
580
581 def logFault(self, flight, aircraft, logger, oldState, state):
582 """Log the fault."""
583 flight.handleFault(FlapsSpeedLimitChecker, state.timestamp,
584 FaultChecker._appendDuring(flight, "Flap speed limit fault"),
585 5)
586
587#---------------------------------------------------------------------------------------
588
589class GearsDownChecker(SimpleFaultChecker):
590 """Check if the gears are down at low altitudes."""
591 def isCondition(self, flight, aircraft, oldState, state):
592 """Check if the fault condition holds."""
593 return state.radioAltitude<10 and not state.gearsDown and \
594 flight.stage!=const.STAGE_TAKEOFF
595
596 def logFault(self, flight, aircraft, logger, oldState, state):
597 """Log the fault."""
598 flight.handleNoGo(GearsDownChecker, state.timestamp,
599 "Gears not down at %.0f feet radio altitude" % \
600 (state.radioAltitude,),
601 "GEAR DOWN NO GO")
602
603#---------------------------------------------------------------------------------------
604
605class GearSpeedLimitChecker(PatientFaultChecker):
606 """Check if the gears not down at too high a speed."""
607 def isCondition(self, flight, aircraft, oldState, state):
608 """Check if the fault condition holds."""
609 return state.gearsDown and state.ias>aircraft.gearSpeedLimit
610
611 def logFault(self, flight, aircraft, logger, oldState, state):
612 """Log the fault."""
613 flight.handleFault(GearSpeedLimitChecker, state.timestamp,
614 FaultChecker._appendDuring(flight, "Gear speed limit fault"),
615 5)
616
617#---------------------------------------------------------------------------------------
618
619class GLoadChecker(SimpleFaultChecker):
620 """Check if the G-load does not exceed 2 except during flare."""
621 def isCondition(self, flight, aircraft, oldState, state):
622 """Check if the fault condition holds."""
623 return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
624 state.radioAltitude>=50)
625
626 def logFault(self, flight, aircraft, logger, oldState, state):
627 """Log the fault."""
628 flight.handleFault(GLoadChecker, state.timestamp,
629 "G-load was %.2f" % (state.gLoad,),
630 10)
631
632#---------------------------------------------------------------------------------------
633
634class LandingLightsChecker(PatientFaultChecker):
635 """Check if the landing lights are used properly."""
636 def getTimeout(self, flight, aircraft, oldState, state):
637 """Get the timeout.
638
639 It is the default timeout except for landing and takeoff."""
640 return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
641 const.STAGE_LANDING] else self._timeout
642
643 def isCondition(self, flight, aircraft, oldState, state):
644 """Check if the fault condition holds."""
645 return (flight.stage==const.STAGE_BOARDING and \
646 state.landingLightsOn and state.onTheGround) or \
647 (flight.stage==const.STAGE_TAKEOFF and \
648 not state.landingLightsOn and not state.onTheGround) or \
649 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
650 const.STAGE_DESCENT] and \
651 state.landingLightsOn and state.altitude>12500) or \
652 (flight.stage==const.STAGE_LANDING and \
653 not state.landingLightsOn and state.onTheGround) or \
654 (flight.stage==const.STAGE_PARKING and \
655 state.landingLightsOn and state.onTheGround)
656
657 def logFault(self, flight, aircraft, logger, oldState, state):
658 """Log the fault."""
659 score = 0 if flight.stage==const.STAGE_LANDING else 1
660 message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
661 flight.handleFault(LandingLightsChecker, state.timestamp,
662 FaultChecker._appendDuring(flight, message),
663 score)
664
665#---------------------------------------------------------------------------------------
666
667class WeightChecker(PatientFaultChecker):
668 """Base class for checkers that check that some limit is not exceeded."""
669 def __init__(self, name):
670 """Construct the checker."""
671 super(WeightChecker, self).__init__(timeout = 5.0)
672 self._name = name
673
674 def isCondition(self, flight, aircraft, oldState, state):
675 """Check if the fault condition holds."""
676 if flight.entranceExam:
677 return False
678
679 limit = self.getLimit(flight, aircraft, state)
680 if limit is not None:
681 if flight.options.compensation is not None:
682 limit += flight.options.compensation
683 return self.getWeight(state)>limit
684
685 return False
686
687 def logFault(self, flight, aircraft, logger, oldState, state):
688 """Log the fault."""
689 mname = "M" + self._name
690 flight.handleNoGo(self.__class__, state.timestamp,
691 "%s exceeded: %s is %.0f kg" % \
692 (mname, self._name, self.getWeight(state)),
693 "%s NO GO" % (mname,))
694
695 def getWeight(self, state):
696 """Get the weight that is interesting for us."""
697 return state.grossWeight
698
699#---------------------------------------------------------------------------------------
700
701class MLWChecker(WeightChecker):
702 """Checks if the MLW is not exceeded on landing."""
703 def __init__(self):
704 """Construct the checker."""
705 super(MLWChecker, self).__init__("LW")
706
707 def getLimit(self, flight, aircraft, state):
708 """Get the limit if we are in the right state."""
709 return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
710 state.onTheGround else None
711
712#---------------------------------------------------------------------------------------
713
714class MTOWChecker(WeightChecker):
715 """Checks if the MTOW is not exceeded on landing."""
716 def __init__(self):
717 """Construct the checker."""
718 super(MTOWChecker, self).__init__("TOW")
719
720 def getLimit(self, flight, aircraft, state):
721 """Get the limit if we are in the right state."""
722 return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
723
724#---------------------------------------------------------------------------------------
725
726class MZFWChecker(WeightChecker):
727 """Checks if the MZFW is not exceeded on landing."""
728 def __init__(self):
729 """Construct the checker."""
730 super(MZFWChecker, self).__init__("ZFW")
731
732 def getLimit(self, flight, aircraft, state):
733 """Get the limit if we are in the right state."""
734 return aircraft.mzfw
735
736 def getWeight(self, state):
737 """Get the weight that is interesting for us."""
738 return state.zfw
739
740#---------------------------------------------------------------------------------------
741
742class NavLightsChecker(PatientFaultChecker):
743 """Check if the navigational lights are used properly."""
744 def isCondition(self, flight, aircraft, oldState, state):
745 """Check if the fault condition holds."""
746 return flight.stage!=const.STAGE_BOARDING and \
747 flight.stage!=const.STAGE_PARKING and \
748 not state.navLightsOn
749
750 def logFault(self, flight, aircraft, logger, oldState, state):
751 """Log the fault."""
752 flight.handleFault(NavLightsChecker, state.timestamp,
753 FaultChecker._appendDuring(flight,
754 "Navigation lights were off"),
755 1)
756
757#---------------------------------------------------------------------------------------
758
759class OverspeedChecker(PatientFaultChecker):
760 """Check if Vne has been exceeded."""
761 def __init__(self):
762 """Construct the checker."""
763 super(OverspeedChecker, self).__init__(timeout = 5.0)
764
765 def isCondition(self, flight, aircraft, oldState, state):
766 """Check if the fault condition holds."""
767 return state.overspeed
768
769 def logFault(self, flight, aircraft, logger, oldState, state):
770 """Log the fault."""
771 flight.handleFault(OverspeedChecker, state.timestamp,
772 FaultChecker._appendDuring(flight, "Overspeed"),
773 20)
774
775#---------------------------------------------------------------------------------------
776
777class PayloadChecker(SimpleFaultChecker):
778 """Check if the payload matches the specification."""
779 TOLERANCE=550
780
781 @staticmethod
782 def isZFWFaulty(aircraftZFW, flightZFW):
783 """Check if the given aircraft's ZFW is outside of the limits."""
784 return aircraftZFW < (flightZFW - PayloadChecker.TOLERANCE) or \
785 aircraftZFW > (flightZFW + PayloadChecker.TOLERANCE)
786
787 def isCondition(self, flight, aircraft, oldState, state):
788 """Check if the fault condition holds."""
789 return flight.stage==const.STAGE_PUSHANDTAXI and \
790 PayloadChecker.isZFWFaulty(state.zfw, flight.zfw)
791
792 def logFault(self, flight, aircraft, logger, oldState, state):
793 """Log the fault."""
794 flight.handleNoGo(PayloadChecker, state.timestamp,
795 "ZFW difference is more than %d kgs" % \
796 (PayloadChecker.TOLERANCE,),
797 "ZFW NO GO")
798
799#---------------------------------------------------------------------------------------
800
801class PitotChecker(PatientFaultChecker):
802 """Check if pitot heat is on."""
803 def __init__(self):
804 """Construct the checker."""
805 super(PitotChecker, self).__init__(timeout = 3.0)
806
807 def isCondition(self, flight, aircraft, oldState, state):
808 """Check if the fault condition holds."""
809 return state.groundSpeed>80 and not state.pitotHeatOn
810
811 def logFault(self, flight, aircraft, logger, oldState, state):
812 """Log the fault."""
813 score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
814 const.STAGE_CRUISE, const.STAGE_DESCENT,
815 const.STAGE_LANDING] else 0
816 flight.handleFault(PitotChecker, state.timestamp,
817 FaultChecker._appendDuring(flight, "Pitot heat was off"),
818 score)
819
820#---------------------------------------------------------------------------------------
821
822class ReverserChecker(SimpleFaultChecker):
823 """Check if the reverser is not used below 60 knots."""
824 def isCondition(self, flight, aircraft, oldState, state):
825 """Check if the fault condition holds."""
826 return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
827 const.STAGE_TAXIAFTERLAND] and \
828 state.groundSpeed<60 and max(state.reverser)
829
830 def logFault(self, flight, aircraft, logger, oldState, state):
831 """Log the fault."""
832 flight.handleFault(ReverserChecker, state.timestamp,
833 FaultChecker._appendDuring(flight,
834 "Reverser used below 60 knots"),
835 15)
836
837#---------------------------------------------------------------------------------------
838
839class SpeedChecker(SimpleFaultChecker):
840 """Check if the speed is in the prescribed limits."""
841 def isCondition(self, flight, aircraft, oldState, state):
842 """Check if the fault condition holds."""
843 return flight.stage in [const.STAGE_PUSHANDTAXI,
844 const.STAGE_TAXIAFTERLAND] and \
845 state.groundSpeed>50
846
847 def logFault(self, flight, aircraft, logger, oldState, state):
848 """Log the fault."""
849 flight.handleFault(SpeedChecker, state.timestamp,
850 FaultChecker._appendDuring(flight,
851 "Taxi speed over 50 knots"),
852 FaultChecker._getLinearScore(50, 80, 10, 15,
853 state.groundSpeed))
854
855#---------------------------------------------------------------------------------------
856
857class StallChecker(PatientFaultChecker):
858 """Check if stall occured."""
859 def isCondition(self, flight, aircraft, oldState, state):
860 """Check if the fault condition holds."""
861 return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
862 const.STAGE_CRUISE, const.STAGE_DESCENT,
863 const.STAGE_LANDING] and state.stalled
864
865 def logFault(self, flight, aircraft, logger, oldState, state):
866 """Log the fault."""
867 score = 40 if flight.stage in [const.STAGE_TAKEOFF,
868 const.STAGE_LANDING] else 30
869 flight.handleFault(StallChecker, state.timestamp,
870 FaultChecker._appendDuring(flight, "Stalled"),
871 score)
872
873#---------------------------------------------------------------------------------------
874
875class StrobeLightsChecker(PatientFaultChecker):
876 """Check if the strobe lights are used properly."""
877 def isCondition(self, flight, aircraft, oldState, state):
878 """Check if the fault condition holds."""
879 if aircraft.type in [const.AIRCRAFT_T134,
880 const.AIRCRAFT_T154,
881 const.AIRCRAFT_YK40]:
882 return False
883 else:
884 return (flight.stage==const.STAGE_BOARDING and \
885 state.strobeLightsOn and state.onTheGround) or \
886 (flight.stage==const.STAGE_TAKEOFF and \
887 not state.strobeLightsOn and not state.gearsDown) or \
888 (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
889 const.STAGE_DESCENT] and \
890 not state.strobeLightsOn and not state.onTheGround) or \
891 (flight.stage==const.STAGE_PARKING and \
892 state.strobeLightsOn and state.onTheGround)
893
894 def logFault(self, flight, aircraft, logger, oldState, state):
895 """Log the fault."""
896 message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
897 flight.handleFault(StrobeLightsChecker, state.timestamp,
898 FaultChecker._appendDuring(flight, message),
899 1)
900
901#---------------------------------------------------------------------------------------
902
903class ThrustChecker(SimpleFaultChecker):
904 """Check if the thrust setting is not too high during takeoff.
905
906 FIXME: is this really so general, for all aircraft?"""
907 def isCondition(self, flight, aircraft, oldState, state):
908 """Check if the fault condition holds."""
909 return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
910
911 def logFault(self, flight, aircraft, logger, oldState, state):
912 """Log the fault."""
913 print state.n1
914 flight.handleFault(ThrustChecker, state.timestamp,
915 FaultChecker._appendDuring(flight,
916 "Thrust setting was too high (>97%)"),
917 FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
918
919#---------------------------------------------------------------------------------------
920
921class VSChecker(SimpleFaultChecker):
922 """Check if the vertical speed is not too low at certain altitudes"""
923 BELOW10000 = -5000
924 BELOW5000 = -2500
925 BELOW2500 = -1500
926 BELOW500 = -1000
927 TOLERANCE = 1.2
928
929 def isCondition(self, flight, aircraft, oldState, state):
930 """Check if the fault condition holds."""
931 vs = state.vs
932 altitude = state.altitude
933 return vs < -8000 or vs > 8000 or \
934 (altitude<500 and vs < (VSChecker.BELOW500 *
935 VSChecker.TOLERANCE)) or \
936 (altitude<2500 and vs < (VSChecker.BELOW2500 *
937 VSChecker.TOLERANCE)) or \
938 (altitude<5000 and vs < (VSChecker.BELOW5000 *
939 VSChecker.TOLERANCE)) or \
940 (altitude<10000 and vs < (VSChecker.BELOW10000 *
941 VSChecker.TOLERANCE))
942
943 def logFault(self, flight, aircraft, logger, oldState, state):
944 """Log the fault."""
945 vs = state.vs
946
947 message = "Vertical speed was %.0f feet/min" % (vs,)
948 if vs>-8000 and vs<8000:
949 message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
950
951 score = 10 if vs<-8000 or vs>8000 else 0
952
953 flight.handleFault(VSChecker, state.timestamp,
954 FaultChecker._appendDuring(flight, message),
955 score)
956
957#---------------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.