source: src/mlx/checks.py@ 141:3172532920ad

Last change on this file since 141:3172532920ad was 139:839016dcd0d1, checked in by István Váradi <ivaradi@…>, 13 years ago

Implemented ACARS sending

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