Index: src/acft.py
===================================================================
--- src/acft.py	(revision 12)
+++ src/acft.py	(revision 12)
@@ -0,0 +1,606 @@
+# Module for the simulator-independent aircraft classes
+
+#---------------------------------------------------------------------------------------
+
+import const
+import checks
+
+import time
+
+#---------------------------------------------------------------------------------------
+
+class Aircraft(object):
+    """Base class for aircraft."""
+    @staticmethod
+    def create(flight):
+        """Create an aircraft instance for the type in the given flight."""
+        return _classes[flight.aircraftType](flight)
+
+    def __init__(self, flight):
+        """Construct the aircraft for the given type."""
+        self._flight = flight
+        self._aircraftState = None
+       
+        self._maxVS = -10000.0
+        self._minVS = 10000.0
+
+        self._checkers = []
+
+        # Loggers
+
+        self._checkers.append(checks.StageChecker())
+        self._checkers.append(checks.TakeOffLogger())
+
+        self._checkers.append(checks.AltimeterLogger())
+        
+        self._checkers.append(checks.NAV1Logger())
+        self._checkers.append(checks.NAV2Logger())
+        self._checkers.append(checks.SquawkLogger())
+
+        self._checkers.append(checks.AnticollisionLightsLogger())
+        self._checkers.append(checks.LandingLightsLogger())
+        self._checkers.append(checks.StrobeLightsLogger())
+        self._checkers.append(checks.NavLightsLogger())
+
+        self._checkers.append(checks.FlapsLogger())
+
+        self._checkers.append(checks.GearsLogger())
+        self._checkers.append(checks.CruiseSpeedLogger())
+        self._checkers.append(checks.SpoilerLogger())
+
+        # Fault checkers
+        
+        self._checkers.append(checks.AntiCollisionLightsChecker())
+        self._checkers.append(checks.LandingLightsChecker())
+        self._checkers.append(checks.NavLightsChecker())
+        self._checkers.append(checks.StrobeLightsChecker())
+
+        self._checkers.append(checks.BankChecker())
+
+        self._checkers.append(checks.FlapsRetractChecker())
+        self._checkers.append(checks.FlapsSpeedLimitChecker())
+
+        self._checkers.append(checks.GearsDownChecker())
+        self._checkers.append(checks.GearSpeedLimitChecker())
+
+        self._checkers.append(checks.GLoadChecker())
+
+        self._checkers.append(checks.MLWChecker())
+        self._checkers.append(checks.MTOWChecker())
+        self._checkers.append(checks.MZFWChecker())
+        self._checkers.append(checks.PayloadChecker())
+
+        self._checkers.append(checks.SpeedChecker())
+        self._checkers.append(checks.VSChecker())
+        self._checkers.append(checks.OverspeedChecker())
+        self._checkers.append(checks.StallChecker())
+
+        self._checkers.append(checks.PitotChecker())
+        
+        self._checkers.append(checks.ThrustChecker())
+        self._checkers.append(checks.ReverserChecker())
+
+    @property
+    def type(self):
+        """Get the type of the aircraft."""
+        return self._flight.aircraftType
+
+    @property
+    def flight(self):
+        """Get the flight the aircraft belongs to."""
+        return self._flight
+
+    @property
+    def logger(self):
+        """Get the logger to use for the aircraft."""
+        return self._flight.logger
+
+    def getFlapsSpeedLimit(self, flaps):
+        """Get the speed limit for the given flaps setting."""
+        return self.flapSpeedLimits[flaps] if flaps in self.flapSpeedLimits \
+               else None
+
+    def modelChanged(self, timestamp, aircraftName, modelName):
+        """Called when the simulator's aircraft changes."""
+        self._flight.logger.message(timestamp,
+                                    "Aircraft: name='%s', model='%s'" % \
+                                    (aircraftName, modelName))
+
+    def handleState(self, aircraftState):
+        """Called when the state of the aircraft changes."""
+        for checker in self._checkers:
+            checker.check(self._flight, self, self._flight.logger,
+                          self._aircraftState, aircraftState)
+
+        self._maxVS = max(self._maxVS, aircraftState.vs)
+        self._minVS = min(self._minVS, aircraftState.vs)
+
+        self._aircraftState = aircraftState
+    
+    def setStage(self, aircraftState, newStage):
+        """Set the given stage as the new one and do whatever should be
+        done."""
+        if self._flight.setStage(aircraftState.timestamp, newStage):
+            if newStage==const.STAGE_PUSHANDTAXI:
+                self.logger.message(aircraftState.timestamp, "Block time start")
+                self.logFuel(aircraftState)
+                self.logger.message(aircraftState.timestamp, 
+                                    "Zero-fuel weight: %.0f kg" % (aircraftState.zfw))
+            elif newStage==const.STAGE_TAKEOFF:
+                self.logger.message(aircraftState.timestamp, "Flight time start")
+                self.logger.message(aircraftState.timestamp, 
+                                    "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
+                                    (aircraftState.grossWeight, self.mtow))
+                self.logger.message(aircraftState.timestamp,
+                                    "Wind %03.0f degrees at %.0f knots" % \
+                                    (aircraftState.windDirection, 
+                                     aircraftState.windSpeed))
+            elif newStage==const.STAGE_TAXIAFTERLAND:
+                self.logger.message(aircraftState.timestamp, "Flight time end")
+                self.logFuel(aircraftState)
+                self.logger.message(aircraftState.timestamp,
+                                    "Landing weight: %.0f kg, MLW: %.0f" % \
+                                    (aircraftState.grossWeight, self.mlw))
+                self.logger.message(aircraftState.timestamp,
+                                    "Vertical speed range: %.0f..%.0f feet/min" % \
+                                    (self._minVS, self._maxVS))
+            elif newStage==const.STAGE_PARKING:
+                self.logger.message(aircraftState.timestamp, "Block time end")
+
+    def prepareFlare(self):
+        """Called when it is detected that we will soon flare.
+
+        On the first call, it should start monitoring some parameters more
+        closely to determine flare time."""
+        self.flight.simulator.startFlare()
+
+    def flareStarted(self, windSpeed, windDirection, visibility, 
+                     flareStart, flareStartFS):
+        """Called when the flare has started."""
+        self.logger.message(self._aircraftState.timestamp, "The flare has begun")
+        self.logger.message(self._aircraftState.timestamp,
+                            "Wind %03.0f degrees at %.0f knots" % \
+                            (windDirection, windSpeed))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Visibility: %.0f metres" % (visibility,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Altimeter setting: %.0f hPa" % \
+                            (self._aircraftState.altimeter,))
+        self.flight.flareStarted(flareStart, flareStartFS)
+         
+    def flareFinished(self, flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                      ias, pitch, bank, heading):
+        """Called when the flare has finished."""
+        (flareTimeFromFS, flareTime) = self.flight.flareFinished(flareEnd,
+                                                                 flareEndFS)
+        self.logger.message(self._aircraftState.timestamp,
+                            "Flare time: %.1f s (from %s)" % \
+                            (flareTime, 
+                             "the simulator" if flareTimeFromFS else "real time",))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown rate: %.0f feet/min" % (tdRate,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown rate was calculated by the %s" % \
+                            ("simulator" if tdRateCalculatedByFS else "logger",))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown speed: %.0f knots" % (ias,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown pitch: %.1f degrees" % (pitch,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown bank: %.1f degrees" % (bank,))
+        self.logger.message(self._aircraftState.timestamp,
+                            "Touchdown heading: %03.0f degrees" % (heading,))
+
+    def cancelFlare(self):
+        """Cancel flare, if it has started."""
+        self.flight.simulator.cancelFlare()
+
+    def checkFlightEnd(self, aircraftState):
+        """Check if the end of the flight has arrived.
+
+        This default implementation checks the N1 values, but for
+        piston-powered aircraft you need to check the RPMs."""
+        for n1 in aircraftState.n1:
+            if n1>=0.5: return False
+        return True
+
+#---------------------------------------------------------------------------------------
+
+class Boeing737(Aircraft):
+    """Base class for the various aircraft in the Boeing 737 family.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(Boeing737, self).__init__(flight)
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 260,
+                                 2 : 260,
+                                 5 : 250,
+                                 10 : 210,
+                                 15 : 200,
+                                 25 : 190,
+                                 30 : 175,
+                                 40 : 162 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class B736(Boeing737):
+    """Boeing 737-600 aircraft."""
+    def __init__(self, flight):
+        super(B736, self).__init__(flight)
+        self.dow = 38307
+        self.mtow = 58328
+        self.mlw = 54657
+        self.mzfw = 51482
+
+#---------------------------------------------------------------------------------------
+
+class B737(Boeing737):
+    """Boeing 737-700 aircraft."""
+    def __init__(self, flight):
+        super(B737, self).__init__(flight)
+        self.dow = 39250
+        self.mtow = 61410
+        self.mlw = 58059
+        self.mzfw = 54657
+
+#---------------------------------------------------------------------------------------
+
+class B738(Boeing737):
+    """Boeing 737-800 aircraft."""
+    def __init__(self, flight):
+        super(B738, self).__init__(flight)
+        self.dow = 42690
+        self.mtow = 71709
+        self.mlw = 65317
+        self.mzfw = 61688
+
+#---------------------------------------------------------------------------------------
+
+class B738Charter(B738):
+    """Boeing 737-800 aircraft used for charters."""
+    def __init__(self, flight):
+        super(B738Charter, self).__init__(flight)
+        self.mtow = 77791
+
+#---------------------------------------------------------------------------------------
+
+class B733(Boeing737):
+    """Boeing 737-300 aircraft."""
+    def __init__(self, flight):
+        super(B733, self).__init__(flight)
+        self.dow = 32700
+        self.mtow = 62820
+        self.mlw = 51700
+        self.mzfw = 48410
+
+#---------------------------------------------------------------------------------------
+
+class B734(Boeing737):
+    """Boeing 737-400 aircraft."""
+    def __init__(self, flight):
+        super(B734, self).__init__(flight)
+        self.dow = 33200
+        self.mtow = 68050
+        self.mlw = 56200
+        self.mzfw = 53100
+
+#---------------------------------------------------------------------------------------
+
+class B735(Boeing737):
+    """Boeing 737-500 aircraft."""
+    def __init__(self, flight):
+        super(B735, self).__init__(flight)
+        self.dow = 31300
+        self.mtow = 60550
+        self.mlw = 50000
+        self.mzfw = 46700
+
+#---------------------------------------------------------------------------------------
+
+class DH8D(Aircraft):
+    """Bombardier Dash-8 Q400 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(DH8D, self).__init__(flight)
+        self.dow = 17185
+        self.mtow = 29257
+        self.mlw = 28009
+        self.mzfw = 25855
+        self.gearSpeedLimit = 215
+        self.flapSpeedLimits = { 5 : 200,
+                                 10 : 181,
+                                 15 : 172,
+                                 35 : 158 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+class Boeing767(Aircraft):
+    """Base class for the various aircraft in the Boeing 767 family.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(Boeing767, self).__init__(flight)
+        self.gearSpeedLimit = 270
+        self.flapSpeedLimits = { 1 : 255,
+                                 5 : 235,
+                                 10 : 215,
+                                 20 : 215,
+                                 25 : 185,
+                                 30 : 175 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class B762(Boeing767):
+    """Boeing 767-200 aircraft."""
+    def __init__(self, flight):
+        super(B762, self).__init__(flight)
+        self.dow = 84507
+        self.mtow = 175540
+        self.mlw = 126098
+        self.mzfw = 114758
+
+#---------------------------------------------------------------------------------------
+
+class B763(Boeing767):
+    """Boeing 767-300 aircraft."""
+    def __init__(self, flight):
+        super(B763, self).__init__(cflight)
+        self.dow = 91311
+        self.mtow = 181436
+        self.mlw = 137892
+        self.mzfw = 130635
+
+#---------------------------------------------------------------------------------------
+
+class CRJ2(Aircraft):
+    """Bombardier CRJ-200 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(CRJ2, self).__init__(flight)
+        self.dow = 14549
+        self.mtow = 22995
+        self.mlw = 21319
+        self.mzfw = 19958
+        self.gearSpeedLimit = 240
+        self.flapSpeedLimits = { 8 : 260,
+                                 20 : 220,
+                                 30 : 190,
+                                 45 : 175 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class F70(Aircraft):
+    """Fokker 70 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(F70, self).__init__(flight)
+        self.dow = 24283
+        self.mtow = 38100 # FIXME: differentiate by registration number,
+                          # MTOW of HA-LMF: 41955
+        self.mlw = 36740
+        self.mzfw = 32655
+        self.gearSpeedLimit = 200
+        self.flapSpeedLimits = { 8 : 250,
+                                 15 : 220,
+                                 25 : 220,
+                                 42 : 180 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[1], aircraftState.fuel[0],
+                             aircraftState.fuel[2]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class DC3(Aircraft):
+    """Lisunov Li-2 (DC-3) aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, right, left aux, right aix
+    - rpm: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(DC3, self).__init__(flight)
+        self.dow = 8627
+        self.mtow = 11884
+        self.mlw = 11793
+        self.mzfw = 11780
+        self.gearSpeedLimit = 148
+        self.flapSpeedLimits = { 15 : 135,
+                                 30 : 99,
+                                 45 : 97 }
+
+    def _checkFlightEnd(self, aircraftState):
+        """Check if the end of the flight has arrived.
+
+        This implementation checks the RPM values to be 0."""
+        for rpm in aircraftState.rpm:
+            if rpm>0: return
+        self._setStage(aircraftState, const.STAGE_END)
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
+                            (aircraftState.fuel[2], aircraftState.fuel[0],
+                             aircraftState.fuel[1], aircraftState.fuel[3]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class T134(Aircraft):
+    """Tupolev Tu-134 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left tip, left aux, right tip, right aux, external 1,
+    external 2
+    - n1: left, right
+    - reverser: left, right."""
+    def __init__(self, flight):
+        super(T134, self).__init__(flight)
+        self.dow = 29927
+        self.mtow = 47600
+        self.mlw = 43000
+        self.mzfw = 38500
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 10 : 240,
+                                 20 : 216,
+                                 30 : 161 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left tip=%.0f kg - centre= %.0f kg - right tip=%.0f kg - right aux=%.0f kg - external 1=%.0f kg - external 2=%.0f kg" % \
+                            (aircraftState.fuel[2], aircraftState.fuel[1],
+                             aircraftState.fuel[0], 
+                             aircraftState.fuel[3], aircraftState.fuel[4],
+                             aircraftState.fuel[5], aircraftState.fuel[6]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+                            
+#---------------------------------------------------------------------------------------
+
+class T154(Aircraft):
+    """Tupolev Tu-154 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: centre, left, right, centre 2, left aux, right aux
+    - n1: left, centre, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(T154, self).__init__(flight)
+        self.dow = 53259
+        self.mtow = 98000
+        self.mlw = 78000
+        self.mzfw = 72000
+        self.gearSpeedLimit = 216
+        self.flapSpeedLimits = { 15 : 227,
+                                 28 : 194,
+                                 45 : 162 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left aux=%.0f kg - left=%.0f kg - centre=%.0f kg - centre 2=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
+                            (aircraftState.fuel[4], aircraftState.fuel[1],
+                             aircraftState.fuel[0], aircraftState.fuel[3], 
+                             aircraftState.fuel[2], aircraftState.fuel[5]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+
+class YK40(Aircraft):
+    """Yakovlev Yak-40 aircraft.
+
+    The aircraft type-specific values in the aircraft state have the following
+    structure:
+    - fuel: left, right
+    - n1: left, right
+    - reverser: left, right"""
+    def __init__(self, flight):
+        super(YK40, self).__init__(flight)
+        self.dow = 9400
+        self.mtow = 17200
+        self.mlw = 16800
+        self.mzfw = 12100
+        self.gearSpeedLimit = 165
+        self.flapSpeedLimits = { 20 : 165,
+                                 35 : 135 }
+
+    def logFuel(self, aircraftState):
+        """Log the amount of fuel"""
+        self.logger.message(aircraftState.timestamp,
+                            "Fuel: left=%.0f kg - right=%.0f kg" % \
+                            (aircraftState.fuel[0], aircraftState.fuel[1]))
+        self.logger.message(aircraftState.timestamp,
+                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
+
+#---------------------------------------------------------------------------------------
+
+_classes = { const.AIRCRAFT_B736 : B736,
+             const.AIRCRAFT_B737 : B737,
+             const.AIRCRAFT_B738 : B738,
+             const.AIRCRAFT_B733 : B733,
+             const.AIRCRAFT_B734 : B734,
+             const.AIRCRAFT_B735 : B735,
+             const.AIRCRAFT_DH8D : DH8D,
+             const.AIRCRAFT_B762 : B762,
+             const.AIRCRAFT_B763 : B763,
+             const.AIRCRAFT_CRJ2 : CRJ2,
+             const.AIRCRAFT_F70 : F70,
+             const.AIRCRAFT_DC3 : DC3,
+             const.AIRCRAFT_T134 : T134,
+             const.AIRCRAFT_T154 : T154,
+             const.AIRCRAFT_YK40 : YK40 }
+
+#---------------------------------------------------------------------------------------
Index: src/acft.py
===================================================================
--- src/acft.py	(revision 12)
+++ 	(revision )
@@ -1,606 +1,0 @@
-# Module for the simulator-independent aircraft classes
-
-#---------------------------------------------------------------------------------------
-
-import const
-import checks
-
-import time
-
-#---------------------------------------------------------------------------------------
-
-class Aircraft(object):
-    """Base class for aircraft."""
-    @staticmethod
-    def create(flight):
-        """Create an aircraft instance for the type in the given flight."""
-        return _classes[flight.aircraftType](flight)
-
-    def __init__(self, flight):
-        """Construct the aircraft for the given type."""
-        self._flight = flight
-        self._aircraftState = None
-       
-        self._maxVS = -10000.0
-        self._minVS = 10000.0
-
-        self._checkers = []
-
-        # Loggers
-
-        self._checkers.append(checks.StageChecker())
-        self._checkers.append(checks.TakeOffLogger())
-
-        self._checkers.append(checks.AltimeterLogger())
-        
-        self._checkers.append(checks.NAV1Logger())
-        self._checkers.append(checks.NAV2Logger())
-        self._checkers.append(checks.SquawkLogger())
-
-        self._checkers.append(checks.AnticollisionLightsLogger())
-        self._checkers.append(checks.LandingLightsLogger())
-        self._checkers.append(checks.StrobeLightsLogger())
-        self._checkers.append(checks.NavLightsLogger())
-
-        self._checkers.append(checks.FlapsLogger())
-
-        self._checkers.append(checks.GearsLogger())
-        self._checkers.append(checks.CruiseSpeedLogger())
-        self._checkers.append(checks.SpoilerLogger())
-
-        # Fault checkers
-        
-        self._checkers.append(checks.AntiCollisionLightsChecker())
-        self._checkers.append(checks.LandingLightsChecker())
-        self._checkers.append(checks.NavLightsChecker())
-        self._checkers.append(checks.StrobeLightsChecker())
-
-        self._checkers.append(checks.BankChecker())
-
-        self._checkers.append(checks.FlapsRetractChecker())
-        self._checkers.append(checks.FlapsSpeedLimitChecker())
-
-        self._checkers.append(checks.GearsDownChecker())
-        self._checkers.append(checks.GearSpeedLimitChecker())
-
-        self._checkers.append(checks.GLoadChecker())
-
-        self._checkers.append(checks.MLWChecker())
-        self._checkers.append(checks.MTOWChecker())
-        self._checkers.append(checks.MZFWChecker())
-        self._checkers.append(checks.PayloadChecker())
-
-        self._checkers.append(checks.SpeedChecker())
-        self._checkers.append(checks.VSChecker())
-        self._checkers.append(checks.OverspeedChecker())
-        self._checkers.append(checks.StallChecker())
-
-        self._checkers.append(checks.PitotChecker())
-        
-        self._checkers.append(checks.ThrustChecker())
-        self._checkers.append(checks.ReverserChecker())
-
-    @property
-    def type(self):
-        """Get the type of the aircraft."""
-        return self._flight.aircraftType
-
-    @property
-    def flight(self):
-        """Get the flight the aircraft belongs to."""
-        return self._flight
-
-    @property
-    def logger(self):
-        """Get the logger to use for the aircraft."""
-        return self._flight.logger
-
-    def getFlapsSpeedLimit(self, flaps):
-        """Get the speed limit for the given flaps setting."""
-        return self.flapSpeedLimits[flaps] if flaps in self.flapSpeedLimits \
-               else None
-
-    def modelChanged(self, timestamp, aircraftName, modelName):
-        """Called when the simulator's aircraft changes."""
-        self._flight.logger.message(timestamp,
-                                    "Aircraft: name='%s', model='%s'" % \
-                                    (aircraftName, modelName))
-
-    def handleState(self, aircraftState):
-        """Called when the state of the aircraft changes."""
-        for checker in self._checkers:
-            checker.check(self._flight, self, self._flight.logger,
-                          self._aircraftState, aircraftState)
-
-        self._maxVS = max(self._maxVS, aircraftState.vs)
-        self._minVS = min(self._minVS, aircraftState.vs)
-
-        self._aircraftState = aircraftState
-    
-    def setStage(self, aircraftState, newStage):
-        """Set the given stage as the new one and do whatever should be
-        done."""
-        if self._flight.setStage(aircraftState.timestamp, newStage):
-            if newStage==const.STAGE_PUSHANDTAXI:
-                self.logger.message(aircraftState.timestamp, "Block time start")
-                self.logFuel(aircraftState)
-                self.logger.message(aircraftState.timestamp, 
-                                    "Zero-fuel weight: %.0f kg" % (aircraftState.zfw))
-            elif newStage==const.STAGE_TAKEOFF:
-                self.logger.message(aircraftState.timestamp, "Flight time start")
-                self.logger.message(aircraftState.timestamp, 
-                                    "Takeoff weight: %.0f kg, MTOW: %.0f kg" % \
-                                    (aircraftState.grossWeight, self.mtow))
-                self.logger.message(aircraftState.timestamp,
-                                    "Wind %03.0f degrees at %.0f knots" % \
-                                    (aircraftState.windDirection, 
-                                     aircraftState.windSpeed))
-            elif newStage==const.STAGE_TAXIAFTERLAND:
-                self.logger.message(aircraftState.timestamp, "Flight time end")
-                self.logFuel(aircraftState)
-                self.logger.message(aircraftState.timestamp,
-                                    "Landing weight: %.0f kg, MLW: %.0f" % \
-                                    (aircraftState.grossWeight, self.mlw))
-                self.logger.message(aircraftState.timestamp,
-                                    "Vertical speed range: %.0f..%.0f feet/min" % \
-                                    (self._minVS, self._maxVS))
-            elif newStage==const.STAGE_PARKING:
-                self.logger.message(aircraftState.timestamp, "Block time end")
-
-    def prepareFlare(self):
-        """Called when it is detected that we will soon flare.
-
-        On the first call, it should start monitoring some parameters more
-        closely to determine flare time."""
-        self.flight.simulator.startFlare()
-
-    def flareStarted(self, windSpeed, windDirection, visibility, 
-                     flareStart, flareStartFS):
-        """Called when the flare has started."""
-        self.logger.message(self._aircraftState.timestamp, "The flare has begun")
-        self.logger.message(self._aircraftState.timestamp,
-                            "Wind %03.0f degrees at %.0f knots" % \
-                            (windDirection, windSpeed))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Visibility: %.0f metres" % (visibility,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Altimeter setting: %.0f hPa" % \
-                            (self._aircraftState.altimeter,))
-        self.flight.flareStarted(flareStart, flareStartFS)
-         
-    def flareFinished(self, flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
-                      ias, pitch, bank, heading):
-        """Called when the flare has finished."""
-        (flareTimeFromFS, flareTime) = self.flight.flareFinished(flareEnd,
-                                                                 flareEndFS)
-        self.logger.message(self._aircraftState.timestamp,
-                            "Flare time: %.1f s (from %s)" % \
-                            (flareTime, 
-                             "the simulator" if flareTimeFromFS else "real time",))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown rate: %.0f feet/min" % (tdRate,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown rate was calculated by the %s" % \
-                            ("simulator" if tdRateCalculatedByFS else "logger",))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown speed: %.0f knots" % (ias,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown pitch: %.1f degrees" % (pitch,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown bank: %.1f degrees" % (bank,))
-        self.logger.message(self._aircraftState.timestamp,
-                            "Touchdown heading: %03.0f degrees" % (heading,))
-
-    def cancelFlare(self):
-        """Cancel flare, if it has started."""
-        self.flight.simulator.cancelFlare()
-
-    def checkFlightEnd(self, aircraftState):
-        """Check if the end of the flight has arrived.
-
-        This default implementation checks the N1 values, but for
-        piston-powered aircraft you need to check the RPMs."""
-        for n1 in aircraftState.n1:
-            if n1>=0.5: return False
-        return True
-
-#---------------------------------------------------------------------------------------
-
-class Boeing737(Aircraft):
-    """Base class for the various aircraft in the Boeing 737 family.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(Boeing737, self).__init__(flight)
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 260,
-                                 2 : 260,
-                                 5 : 250,
-                                 10 : 210,
-                                 15 : 200,
-                                 25 : 190,
-                                 30 : 175,
-                                 40 : 162 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class B736(Boeing737):
-    """Boeing 737-600 aircraft."""
-    def __init__(self, flight):
-        super(B736, self).__init__(flight)
-        self.dow = 38307
-        self.mtow = 58328
-        self.mlw = 54657
-        self.mzfw = 51482
-
-#---------------------------------------------------------------------------------------
-
-class B737(Boeing737):
-    """Boeing 737-700 aircraft."""
-    def __init__(self, flight):
-        super(B737, self).__init__(flight)
-        self.dow = 39250
-        self.mtow = 61410
-        self.mlw = 58059
-        self.mzfw = 54657
-
-#---------------------------------------------------------------------------------------
-
-class B738(Boeing737):
-    """Boeing 737-800 aircraft."""
-    def __init__(self, flight):
-        super(B738, self).__init__(flight)
-        self.dow = 42690
-        self.mtow = 71709
-        self.mlw = 65317
-        self.mzfw = 61688
-
-#---------------------------------------------------------------------------------------
-
-class B738Charter(B738):
-    """Boeing 737-800 aircraft used for charters."""
-    def __init__(self, flight):
-        super(B738Charter, self).__init__(flight)
-        self.mtow = 77791
-
-#---------------------------------------------------------------------------------------
-
-class B733(Boeing737):
-    """Boeing 737-300 aircraft."""
-    def __init__(self, flight):
-        super(B733, self).__init__(flight)
-        self.dow = 32700
-        self.mtow = 62820
-        self.mlw = 51700
-        self.mzfw = 48410
-
-#---------------------------------------------------------------------------------------
-
-class B734(Boeing737):
-    """Boeing 737-400 aircraft."""
-    def __init__(self, flight):
-        super(B734, self).__init__(flight)
-        self.dow = 33200
-        self.mtow = 68050
-        self.mlw = 56200
-        self.mzfw = 53100
-
-#---------------------------------------------------------------------------------------
-
-class B735(Boeing737):
-    """Boeing 737-500 aircraft."""
-    def __init__(self, flight):
-        super(B735, self).__init__(flight)
-        self.dow = 31300
-        self.mtow = 60550
-        self.mlw = 50000
-        self.mzfw = 46700
-
-#---------------------------------------------------------------------------------------
-
-class DH8D(Aircraft):
-    """Bombardier Dash-8 Q400 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(DH8D, self).__init__(flight)
-        self.dow = 17185
-        self.mtow = 29257
-        self.mlw = 28009
-        self.mzfw = 25855
-        self.gearSpeedLimit = 215
-        self.flapSpeedLimits = { 5 : 200,
-                                 10 : 181,
-                                 15 : 172,
-                                 35 : 158 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-class Boeing767(Aircraft):
-    """Base class for the various aircraft in the Boeing 767 family.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(Boeing767, self).__init__(flight)
-        self.gearSpeedLimit = 270
-        self.flapSpeedLimits = { 1 : 255,
-                                 5 : 235,
-                                 10 : 215,
-                                 20 : 215,
-                                 25 : 185,
-                                 30 : 175 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class B762(Boeing767):
-    """Boeing 767-200 aircraft."""
-    def __init__(self, flight):
-        super(B762, self).__init__(flight)
-        self.dow = 84507
-        self.mtow = 175540
-        self.mlw = 126098
-        self.mzfw = 114758
-
-#---------------------------------------------------------------------------------------
-
-class B763(Boeing767):
-    """Boeing 767-300 aircraft."""
-    def __init__(self, flight):
-        super(B763, self).__init__(cflight)
-        self.dow = 91311
-        self.mtow = 181436
-        self.mlw = 137892
-        self.mzfw = 130635
-
-#---------------------------------------------------------------------------------------
-
-class CRJ2(Aircraft):
-    """Bombardier CRJ-200 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(CRJ2, self).__init__(flight)
-        self.dow = 14549
-        self.mtow = 22995
-        self.mlw = 21319
-        self.mzfw = 19958
-        self.gearSpeedLimit = 240
-        self.flapSpeedLimits = { 8 : 260,
-                                 20 : 220,
-                                 30 : 190,
-                                 45 : 175 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class F70(Aircraft):
-    """Fokker 70 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(F70, self).__init__(flight)
-        self.dow = 24283
-        self.mtow = 38100 # FIXME: differentiate by registration number,
-                          # MTOW of HA-LMF: 41955
-        self.mlw = 36740
-        self.mzfw = 32655
-        self.gearSpeedLimit = 200
-        self.flapSpeedLimits = { 8 : 250,
-                                 15 : 220,
-                                 25 : 220,
-                                 42 : 180 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - centre=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[1], aircraftState.fuel[0],
-                             aircraftState.fuel[2]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class DC3(Aircraft):
-    """Lisunov Li-2 (DC-3) aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, right, left aux, right aix
-    - rpm: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(DC3, self).__init__(flight)
-        self.dow = 8627
-        self.mtow = 11884
-        self.mlw = 11793
-        self.mzfw = 11780
-        self.gearSpeedLimit = 148
-        self.flapSpeedLimits = { 15 : 135,
-                                 30 : 99,
-                                 45 : 97 }
-
-    def _checkFlightEnd(self, aircraftState):
-        """Check if the end of the flight has arrived.
-
-        This implementation checks the RPM values to be 0."""
-        for rpm in aircraftState.rpm:
-            if rpm>0: return
-        self._setStage(aircraftState, const.STAGE_END)
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
-                            (aircraftState.fuel[2], aircraftState.fuel[0],
-                             aircraftState.fuel[1], aircraftState.fuel[3]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class T134(Aircraft):
-    """Tupolev Tu-134 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left tip, left aux, right tip, right aux, external 1,
-    external 2
-    - n1: left, right
-    - reverser: left, right."""
-    def __init__(self, flight):
-        super(T134, self).__init__(flight)
-        self.dow = 29927
-        self.mtow = 47600
-        self.mlw = 43000
-        self.mzfw = 38500
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 10 : 240,
-                                 20 : 216,
-                                 30 : 161 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left tip=%.0f kg - centre= %.0f kg - right tip=%.0f kg - right aux=%.0f kg - external 1=%.0f kg - external 2=%.0f kg" % \
-                            (aircraftState.fuel[2], aircraftState.fuel[1],
-                             aircraftState.fuel[0], 
-                             aircraftState.fuel[3], aircraftState.fuel[4],
-                             aircraftState.fuel[5], aircraftState.fuel[6]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-                            
-#---------------------------------------------------------------------------------------
-
-class T154(Aircraft):
-    """Tupolev Tu-154 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: centre, left, right, centre 2, left aux, right aux
-    - n1: left, centre, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(T154, self).__init__(flight)
-        self.dow = 53259
-        self.mtow = 98000
-        self.mlw = 78000
-        self.mzfw = 72000
-        self.gearSpeedLimit = 216
-        self.flapSpeedLimits = { 15 : 227,
-                                 28 : 194,
-                                 45 : 162 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left aux=%.0f kg - left=%.0f kg - centre=%.0f kg - centre 2=%.0f kg - right=%.0f kg - right aux=%.0f kg" % \
-                            (aircraftState.fuel[4], aircraftState.fuel[1],
-                             aircraftState.fuel[0], aircraftState.fuel[3], 
-                             aircraftState.fuel[2], aircraftState.fuel[5]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-
-class YK40(Aircraft):
-    """Yakovlev Yak-40 aircraft.
-
-    The aircraft type-specific values in the aircraft state have the following
-    structure:
-    - fuel: left, right
-    - n1: left, right
-    - reverser: left, right"""
-    def __init__(self, flight):
-        super(YK40, self).__init__(flight)
-        self.dow = 9400
-        self.mtow = 17200
-        self.mlw = 16800
-        self.mzfw = 12100
-        self.gearSpeedLimit = 165
-        self.flapSpeedLimits = { 20 : 165,
-                                 35 : 135 }
-
-    def logFuel(self, aircraftState):
-        """Log the amount of fuel"""
-        self.logger.message(aircraftState.timestamp,
-                            "Fuel: left=%.0f kg - right=%.0f kg" % \
-                            (aircraftState.fuel[0], aircraftState.fuel[1]))
-        self.logger.message(aircraftState.timestamp,
-                            "Total fuel: %.0f kg" % (sum(aircraftState.fuel),))
-
-#---------------------------------------------------------------------------------------
-
-_classes = { const.AIRCRAFT_B736 : B736,
-             const.AIRCRAFT_B737 : B737,
-             const.AIRCRAFT_B738 : B738,
-             const.AIRCRAFT_B733 : B733,
-             const.AIRCRAFT_B734 : B734,
-             const.AIRCRAFT_B735 : B735,
-             const.AIRCRAFT_DH8D : DH8D,
-             const.AIRCRAFT_B762 : B762,
-             const.AIRCRAFT_B763 : B763,
-             const.AIRCRAFT_CRJ2 : CRJ2,
-             const.AIRCRAFT_F70 : F70,
-             const.AIRCRAFT_DC3 : DC3,
-             const.AIRCRAFT_T134 : T134,
-             const.AIRCRAFT_T154 : T154,
-             const.AIRCRAFT_YK40 : YK40 }
-
-#---------------------------------------------------------------------------------------
Index: src/checks.py
===================================================================
--- src/checks.py	(revision 25)
+++ src/checks.py	(revision 25)
@@ -0,0 +1,909 @@
+# The various checks that may be performed during flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+
+#---------------------------------------------------------------------------------------
+
+class StateChecker(object):
+    """Base class for classes the instances of which check the aircraft's state
+    from some aspect.
+
+    As a result of the check they may log something, or notify of some fault, etc."""
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check and do whatever is needed.
+
+        This default implementation raises a NotImplementedError."""
+        raise NotImplementedError()
+    
+#---------------------------------------------------------------------------------------
+
+class StageChecker(StateChecker):
+    """Check the flight stage transitions."""
+    def __init__(self):
+        """Construct the stage checker."""
+        self._flareStarted = False
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check the stage of the aircraft."""
+        stage = flight.stage
+        if stage==None:
+            aircraft.setStage(state, const.STAGE_BOARDING)
+        elif stage==const.STAGE_BOARDING:
+            if not state.parking or \
+               (not state.trickMode and state.groundSpeed>5.0):
+                aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
+        elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
+            if state.landingLightsOn or state.strobeLightsOn or \
+               state.groundSpeed>80.0:
+                aircraft.setStage(state, const.STAGE_TAKEOFF)
+        elif stage==const.STAGE_TAKEOFF:
+            if not state.gearsDown or \
+               (state.radioAltitude>3000.0 and state.vs>0):
+                aircraft.setStage(state, const.STAGE_CLIMB)
+            elif not state.landingLightsOn and \
+                 not state.strobeLightsOn and \
+                 state.onTheGround and \
+                 state.groundSpeed<50.0:
+                aircraft.setStage(state, const.STAGE_RTO)
+        elif stage==const.STAGE_CLIMB:
+            if (state.altitude+2000) > flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_CRUISE)
+            elif state.radioAltitude<2000.0 and \
+                 state.vs < 0.0 and state.gearsDown:
+                aircraft.setStage(state, const.STAGE_LANDING)
+        elif stage==const.STAGE_CRUISE:
+            if (state.altitude+2000) < flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_DESCENT)
+        elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
+            if state.gearsDown and state.radioAltitude<2000.0:
+                aircraft.setStage(state, const.STAGE_LANDING)
+            elif (state.altitude+2000) > flight.cruiseAltitude:
+                aircraft.setStage(state, const.STAGE_CRUISE)
+        elif stage==const.STAGE_LANDING:
+            if state.onTheGround and state.groundSpeed<50.0:
+                aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
+            elif not state.gearsDown:
+                aircraft.setStage(state, const.STAGE_GOAROUND)
+            elif state.radioAltitude>200 and self._flareStarted:
+                aircraft.cancelFlare()
+                self._flareStarted = False
+            elif state.radioAltitude<150 and not state.onTheGround and \
+                 not self._flareStarted:
+                self._flareStarted = True
+                aircraft.prepareFlare()
+        elif stage==const.STAGE_TAXIAFTERLAND:
+            if state.parking:
+                aircraft.setStage(state, const.STAGE_PARKING)
+        elif stage==const.STAGE_PARKING:
+            if aircraft.checkFlightEnd(state):
+                aircraft.setStage(state, const.STAGE_END)
+
+#---------------------------------------------------------------------------------------
+
+class TakeOffLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._onTheGround = True
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_TAKEOFF and \
+           self._onTheGround and not state.onTheGround:
+            logger.message(state.timestamp,
+                           "Takeoff speed: %.0f knots" % (state.ias,))
+            logger.message(state.timestamp,
+                           "Takeoff heading: %03.0f degrees" % (state.heading,))
+            logger.message(state.timestamp,
+                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
+            self._onTheGround = False
+
+#---------------------------------------------------------------------------------------
+
+class CruiseSpeedLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._lastTime = None
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_CRUISE and \
+           (self._lastTime is None or \
+            (self._lastTime+800)<=state.timestamp):
+                if state.altitude>24500.0:
+                    logger.message(state.timestamp,
+                                   "Cruise speed: %.3f mach" % (state.mach,))
+                else:
+                    logger.message(state.timestamp,
+                                   "Cruise speed: %.0f knots" % (state.ias,))
+                self._lastTime = state.timestamp
+
+#---------------------------------------------------------------------------------------
+
+class SpoilerLogger(StateChecker):
+    """Logger for the cruise speed."""
+    def __init__(self):
+        """Construct the logger."""
+        self._logged = False
+        self._spoilersExtension = None
+    
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Log the cruise speed if necessary."""
+        if flight.stage==const.STAGE_LANDING and not self._logged:
+            if state.onTheGround:
+                if state.spoilersExtension!=self._spoilersExtension:
+                    logger.message(state.timestamp, "Spoilers deployed")
+                    self._logged = True
+            else:
+                self._spoilersExtension = state.spoilersExtension
+
+#---------------------------------------------------------------------------------------
+
+class StateChangeLogger(StateChecker):
+    """Base class for classes the instances of which check if a specific change has
+    occured in the aircraft's state, and log such change."""
+    def __init__(self, logInitial = True):
+        """Construct the logger.
+
+        If logInitial is True, the initial value will be logged, not just the
+        changes later.
+
+        Child classes should define the following functions:
+        - _changed(self, oldState, state): returns a boolean indicating if the
+        value has changed or not
+        - _getMessage(self, state): return a strings containing the message to log
+        with the new value
+        """
+        self._logInitial = logInitial    
+
+    def _getLogTimestamp(self, state):
+        """Get the log timestamp."""
+        return state.timestamp
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Check if the state has changed, and if so, log the new state."""
+        shouldLog = False
+        if oldState is None:
+            shouldLog = self._logInitial
+        else:
+            shouldLog = self._changed(oldState, state)
+        
+        if shouldLog:
+            logger.message(self._getLogTimestamp(state), self._getMessage(state))
+        
+#---------------------------------------------------------------------------------------
+
+class SimpleChangeMixin(object):
+    """A mixin that defines a _changed() function which simply calls a function
+    to retrieve the value two monitor for both states and compares them.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def _changed(self, oldState, state):
+        """Determine if the value has changed."""
+        return self._getValue(oldState)!=self._getValue(state)
+
+#---------------------------------------------------------------------------------------
+
+class SingleValueMixin(object):
+    """A mixin that provides a _getValue() function to query a value from the
+    state using the name of the attribute."""
+    def __init__(self, attrName):
+        """Construct the mixin with the given attribute name."""
+        self._attrName = attrName
+
+    def _getValue(self, state):
+        """Get the value of the attribute from the state."""
+        return getattr(state, self._attrName)
+    
+#---------------------------------------------------------------------------------------
+
+class DelayedChangeMixin(object):
+    """A mixin to a StateChangeLogger that stores the old value and reports a
+    change only if the change has been there for a certain amount of time.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def __init__(self, minDelay = 3.0, maxDelay = 10.0):
+        """Construct the mixin with the given delay in seconds."""
+        self._minDelay = minDelay
+        self._maxDelay = maxDelay
+        self._oldValue = None
+        self._firstChange = None
+        self._lastChangeState = None
+        
+    def _changed(self, oldState, state):
+        """Determine if the value has changed."""
+        if self._oldValue is None:
+            self._oldValue = self._getValue(oldState)
+            
+        newValue = self._getValue(state)
+        if self._oldValue!=newValue:
+            if self._firstChange is None:
+                self._firstChange = state.timestamp
+            self._lastChangeState = state
+            self._oldValue = newValue
+
+        if self._firstChange is not None:
+            if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
+                                      self._firstChange + self._maxDelay):
+                self._firstChange = None
+                return True
+            
+        return False
+
+    def _getLogTimestamp(self, state):
+        """Get the log timestamp."""
+        return self._lastChangeState.timestamp if \
+               self._lastChangeState is not None else state.timestamp
+
+#---------------------------------------------------------------------------------------
+
+class TemplateMessageMixin(object):
+    """Mixin to generate a message based on a template.
+
+    Child classes should define the following function:
+    - _getValue(state): get the value we are interested in."""
+    def __init__(self, template):
+        """Construct the mixin."""
+        self._template = template
+
+    def _getMessage(self, state):
+        """Get the message."""
+        return self._template % (self._getValue(state),)
+
+#---------------------------------------------------------------------------------------
+
+class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
+                               DelayedChangeMixin, TemplateMessageMixin):
+    """Base for generic state change loggers that monitor a single value in the
+    state possibly with a delay and the logged message comes from a template"""
+    def __init__(self, attrName, template, logInitial = True,
+                 minDelay = 0.0, maxDelay = 0.0):
+        """Construct the object."""
+        StateChangeLogger.__init__(self, logInitial = logInitial)
+        SingleValueMixin.__init__(self, attrName)
+        DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
+        TemplateMessageMixin.__init__(self, template)
+        self._getLogTimestamp = lambda state: \
+                                DelayedChangeMixin._getLogTimestamp(self, state)
+
+#---------------------------------------------------------------------------------------
+
+class AltimeterLogger(StateChangeLogger, SingleValueMixin,
+                      DelayedChangeMixin):
+    """Logger for the altimeter setting."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "altimeter")
+        DelayedChangeMixin.__init__(self)
+        self._getLogTimestamp = lambda state: \
+                                DelayedChangeMixin._getLogTimestamp(self, state)
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        logState = self._lastChangeState if \
+                   self._lastChangeState is not None else state
+        return "Altimeter: %.0f hPa at %.0f feet" % \
+               (logState.altimeter, logState.altitude)
+
+#---------------------------------------------------------------------------------------
+
+class NAV1Logger(GenericStateChangeLogger):
+    """Logger for the NAV1 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
+
+#---------------------------------------------------------------------------------------
+
+class NAV2Logger(GenericStateChangeLogger):
+    """Logger for the NAV2 radio setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
+
+#---------------------------------------------------------------------------------------
+
+class SquawkLogger(GenericStateChangeLogger):
+    """Logger for the squawk setting."""
+    def __init__(self):
+        """Construct the logger."""
+        super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
+                                           minDelay = 3.0, maxDelay = 10.0)
+
+#---------------------------------------------------------------------------------------
+
+class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Base class for the loggers of the various lights."""
+    def __init__(self, attrName, template):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self)
+        SingleValueMixin.__init__(self, attrName)
+        
+        self._template = template
+
+    def _getMessage(self, state):
+        """Get the message from the given state."""
+        return self._template % ("ON" if self._getValue(state) else "OFF")
+        
+#---------------------------------------------------------------------------------------
+
+class AnticollisionLightsLogger(LightsLogger):
+    """Logger for the anti-collision lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "antiCollisionLightsOn",
+                              "Anti-collision lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class LandingLightsLogger(LightsLogger):
+    """Logger for the landing lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "landingLightsOn",
+                              "Landing lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class StrobeLightsLogger(LightsLogger):
+    """Logger for the strobe lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "strobeLightsOn",
+                              "Strobe lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class NavLightsLogger(LightsLogger):
+    """Logger for the navigational lights."""
+    def __init__(self):
+        LightsLogger.__init__(self, "navLightsOn",
+                              "Navigational lights: %s")
+
+#---------------------------------------------------------------------------------------
+
+class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Logger for the flaps setting."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "flapsSet")
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
+        return "Flaps set to %.0f at %.0f knots" % (state.flapsSet, speed)
+
+#---------------------------------------------------------------------------------------
+
+class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
+    """Logger for the gears state."""
+    def __init__(self):
+        """Construct the logger."""
+        StateChangeLogger.__init__(self, logInitial = True)
+        SingleValueMixin.__init__(self, "gearsDown")
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        return "Gears %s at %.0f knots, %.0f feet" % \
+            ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
+
+#---------------------------------------------------------------------------------------
+
+class FaultChecker(StateChecker):
+    """Base class for checkers that look for faults."""
+    @staticmethod
+    def _appendDuring(flight, message):
+        """Append a 'during XXX' test to the given message, depending on the
+        flight stage."""
+        stageStr = const.stage2string(flight.stage)
+        return message if stageStr is None \
+               else (message + " during " + stageStr.upper())
+
+    @staticmethod
+    def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
+                        value):
+        """Get the score for a faulty value where the score is calculated
+        linearly within a certain range."""
+        if value<minFaultValue:
+            return 0
+        elif value>maxFaultValue:
+            return maxScore
+        else:
+            return minScore + (maxScore-minScore) * (value-minFaultValue) / \
+                   (maxFaultValue - minFaultValue)
+
+#---------------------------------------------------------------------------------------
+
+class SimpleFaultChecker(FaultChecker):
+    """Base class for fault checkers that check for a single occurence of a
+    faulty condition.
+
+    Child classes should implement the following functions:
+    - isCondition(self, flight, aircraft, oldState, state): should return whether the
+    condition holds
+    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
+    via the logger."""
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check."""
+        if self.isCondition(flight, aircraft, oldState, state):
+            self.logFault(flight, aircraft, logger, oldState, state)
+
+#---------------------------------------------------------------------------------------
+
+class PatientFaultChecker(FaultChecker):
+    """A fault checker that does not decides on a fault when the condition
+    arises immediately, but can wait some time.
+
+    Child classes should implement the following functions:
+    - isCondition(self, flight, aircraft, oldState, state): should return whether the
+    condition holds
+    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
+    via the logger
+    """
+    def __init__(self, timeout = 2.0):
+        """Construct the fault checker with the given timeout."""
+        self._timeout = timeout
+        self._faultStarted = None
+
+    def getTimeout(self, flight, aircraft, oldState, state):
+        """Get the timeout.
+
+        This default implementation returns the timeout given in the
+        constructor, but child classes might want to enforce a different
+        policy."""
+        return self._timeout
+
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Perform the check."""
+        if self.isCondition(flight, aircraft, oldState, state):
+            if self._faultStarted is None:
+                self._faultStarted = state.timestamp
+            timeout = self.getTimeout(flight, aircraft, oldState, state)
+            if state.timestamp>=(self._faultStarted + timeout):
+                self.logFault(flight, aircraft, logger, oldState, state)
+                self._faultStarted = state.timestamp
+        else:
+            self._faultStarted = None
+                
+#---------------------------------------------------------------------------------------
+
+class AntiCollisionLightsChecker(PatientFaultChecker):
+    """Check for the anti-collision light being off at high N1 values."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return (flight.stage!=const.STAGE_PARKING or \
+                not flight.options.fs2Crew) and \
+                not state.antiCollisionLightsOn and \
+                max(state.n1)>5
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(AntiCollisionLightsChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Anti-collision lights were off"),
+                     1)
+
+#---------------------------------------------------------------------------------------
+
+class BankChecker(SimpleFaultChecker):
+    """Check for the anti-collision light being off at high N1 values."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if flight.stage==const.STAGE_CRUISE:
+            bankLimit = 30
+        elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                              const.STAGE_DESCENT, const.STAGE_LANDING]:
+            bankLimit = 35
+        else:
+            return False
+
+        return state.bank>bankLimit or state.bank<-bankLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(BankChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Bank too steep"),
+                     2)
+
+#---------------------------------------------------------------------------------------
+
+class FlapsRetractChecker(SimpleFaultChecker):
+    """Check if the flaps are not retracted too early."""
+    def __init__(self):
+        """Construct the flaps checker."""
+        self._timeStart = None
+    
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds.
+
+        FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
+        if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
+           (flight.stage==const.STAGE_LANDING and state.onTheGround):
+            if self._timeStart is None:
+                self._timeStart = state.timestamp
+
+            if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
+                return True
+        else:
+            self._timeStart = None
+        return False
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(FlapsRetractChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Flaps retracted"),
+                     20)
+
+#---------------------------------------------------------------------------------------
+
+class FlapsSpeedLimitChecker(SimpleFaultChecker):
+    """Check if the flaps are extended only at the right speeds."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
+        return speedLimit is not None and state.ias>speedLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(FlapsSpeedLimitChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Flap speed limit fault"),
+                     5)
+
+#---------------------------------------------------------------------------------------
+
+class GearsDownChecker(SimpleFaultChecker):
+    """Check if the gears are down at low altitudes."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.radioAltitude<10 and not state.gearsDown and \
+               flight.stage!=const.STAGE_TAKEOFF
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.noGo(GearsDownChecker, state.timestamp,
+                    "Gears not down at %.0f feet radio altitude" % \
+                    (state.radioAltitude,),
+                    "GEAR DOWN NO GO")
+
+#---------------------------------------------------------------------------------------
+
+class GearSpeedLimitChecker(PatientFaultChecker):
+    """Check if the gears not down at too high a speed."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.gearsDown and state.ias>aircraft.gearSpeedLimit
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(GearSpeedLimitChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Gear speed limit fault"),
+                     5)
+
+#---------------------------------------------------------------------------------------
+
+class GLoadChecker(SimpleFaultChecker):
+    """Check if the G-load does not exceed 2 except during flare."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
+                                    state.radioAltitude>=50)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(GLoadChecker, state.timestamp,
+                     "G-load was %.2f" % (state.gLoad,),
+                     10)
+
+#---------------------------------------------------------------------------------------
+
+class LandingLightsChecker(PatientFaultChecker):
+    """Check if the landing lights are used properly."""
+    def getTimeout(self, flight, aircraft, oldState, state):
+        """Get the timeout.
+
+        It is the default timeout except for landing and takeoff."""
+        return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
+                                       const.STAGE_LANDING] else self._timeout
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return (flight.stage==const.STAGE_BOARDING and \
+                state.landingLightsOn and state.onTheGround) or \
+               (flight.stage==const.STAGE_TAKEOFF and \
+                not state.landingLightsOn and not state.onTheGround) or \
+               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
+                                 const.STAGE_DESCENT] and \
+                state.landingLightsOn and state.altitude>12500) or \
+               (flight.stage==const.STAGE_LANDING and \
+                not state.landingLightsOn and state.onTheGround) or \
+               (flight.stage==const.STAGE_PARKING and \
+                state.landingLightsOn and state.onTheGround)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 0 if flight.stage==const.STAGE_LANDING else 1
+        message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
+        logger.fault(LandingLightsChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, message),
+                     score)        
+
+#---------------------------------------------------------------------------------------
+
+class WeightChecker(PatientFaultChecker):
+    """Base class for checkers that check that some limit is not exceeded."""
+    def __init__(self, name):
+        """Construct the checker."""
+        super(WeightChecker, self).__init__(timeout = 5.0)
+        self._name = name
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        if flight.entranceExam:
+            return False
+
+        limit = self.getLimit(flight, aircraft, state)
+        if limit is not None:
+            if flight.options.compensation is not None:
+                limit += flight.options.compensation
+            return self.getWeight(state)>limit
+
+        return False
+
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        mname = "M" + self._name
+        logger.noGo(self.__class__, state.timestamp,
+                    "%s exceeded: %s is %.0f kg" % \
+                    (mname, self._name, self.getWeight(state)),
+                    "%s NO GO" % (mname,))
+
+    def getWeight(self, state):
+        """Get the weight that is interesting for us."""
+        return state.grossWeight
+
+#---------------------------------------------------------------------------------------
+
+class MLWChecker(WeightChecker):
+    """Checks if the MLW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MLWChecker, self).__init__("LW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
+                               state.onTheGround else None
+
+#---------------------------------------------------------------------------------------
+
+class MTOWChecker(WeightChecker):
+    """Checks if the MTOW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MTOWChecker, self).__init__("TOW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
+
+#---------------------------------------------------------------------------------------
+
+class MZFWChecker(WeightChecker):
+    """Checks if the MTOW is not exceeded on landing."""
+    def __init__(self):
+        """Construct the checker."""
+        super(MZFWChecker, self).__init__("ZFW")
+
+    def getLimit(self, flight, aircraft, state):
+        """Get the limit if we are in the right state."""
+        return aircraft.mzfw
+
+    def getWeight(self, state):
+        """Get the weight that is interesting for us."""
+        return state.zfw
+
+#---------------------------------------------------------------------------------------
+
+class NavLightsChecker(PatientFaultChecker):
+    """Check if the navigational lights are used properly."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage!=const.STAGE_BOARDING and \
+               flight.stage!=const.STAGE_PARKING and \
+               not state.navLightsOn
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(NavLightsChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Navigation lights were off"),
+                     1)        
+
+#---------------------------------------------------------------------------------------
+
+class OverspeedChecker(PatientFaultChecker):
+    """Check if Vne has been exceeded."""
+    def __init__(self):
+        """Construct the checker."""
+        super(OverspeedChecker, self).__init__(timeout = 5.0)
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.overspeed
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(OverspeedChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Overspeed"),
+                     20)        
+
+#---------------------------------------------------------------------------------------
+
+class PayloadChecker(SimpleFaultChecker):
+    """Check if the payload matches the specification."""
+    TOLERANCE=550
+    
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage==const.STAGE_PUSHANDTAXI and \
+               (state.zfw < (flight.zfw - PayloadChecker.TOLERANCE) or \
+                state.zfw > (flight.zfw + PayloadChecker.TOLERANCE))
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.noGo(PayloadChecker, state.timestamp,
+                    "ZFW difference is more than %d kgs" % (PayloadChecker.TOLERANCE,),
+                    "ZFW NO GO")
+
+#---------------------------------------------------------------------------------------
+
+class PitotChecker(PatientFaultChecker):
+    """Check if pitot heat is on."""
+    def __init__(self):
+        """Construct the checker."""
+        super(PitotChecker, self).__init__(timeout = 3.0)
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return state.groundSpeed>80 and not state.pitotHeatOn
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                                      const.STAGE_CRUISE, const.STAGE_DESCENT,
+                                      const.STAGE_LANDING] else 0
+        logger.fault(PitotChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Pitot heat was off"),
+                     score)        
+
+#---------------------------------------------------------------------------------------
+
+class ReverserChecker(SimpleFaultChecker):
+    """Check if the reverser is not used below 60 knots."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
+                                const.STAGE_TAXIAFTERLAND] and \
+            state.groundSpeed<60 and max(state.reverser)
+                           
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(ReverserChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Reverser used below 60 knots"),
+                     15)        
+
+#---------------------------------------------------------------------------------------
+
+class SpeedChecker(SimpleFaultChecker):
+    """Check if the speed is in the prescribed limits."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_PUSHANDTAXI,
+                                const.STAGE_TAXIAFTERLAND] and \
+            state.groundSpeed>50
+                           
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        logger.fault(SpeedChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Taxi speed over 50 knots"),
+                     FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed))
+
+#---------------------------------------------------------------------------------------
+
+class StallChecker(PatientFaultChecker):
+    """Check if stall occured."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
+                                const.STAGE_CRUISE, const.STAGE_DESCENT,
+                                const.STAGE_LANDING] and state.stalled
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        score = 40 if flight.stage in [const.STAGE_TAKEOFF,
+                                       const.STAGE_LANDING] else 30
+        logger.fault(StallChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Stalled"),
+                     score)        
+
+#---------------------------------------------------------------------------------------
+
+class StrobeLightsChecker(PatientFaultChecker):
+    """Check if the strobe lights are used properly."""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return (flight.stage==const.STAGE_BOARDING and \
+                state.strobeLightsOn and state.onTheGround) or \
+               (flight.stage==const.STAGE_TAKEOFF and \
+                not state.strobeLightsOn and not state.gearsDown) or \
+               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
+                                 const.STAGE_DESCENT] and \
+                not state.strobeLightsOn and not state.onTheGround) or \
+               (flight.stage==const.STAGE_PARKING and \
+                state.strobeLightsOn and state.onTheGround)
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
+        logger.fault(StrobeLightsChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, message),
+                     1)        
+
+#---------------------------------------------------------------------------------------
+
+class ThrustChecker(SimpleFaultChecker):
+    """Check if the thrust setting is not too high during takeoff.
+
+    FIXME: is this really so general, for all aircraft?"""
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        print state.n1
+        logger.fault(ThrustChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, "Thrust setting was too high (>97%)"),
+                     FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
+
+#---------------------------------------------------------------------------------------
+
+class VSChecker(SimpleFaultChecker):
+    """Check if the vertical speed is not too low at certain altitudes"""
+    BELOW10000 = -5000
+    BELOW5000 = -2500
+    BELOW2500 = -1500
+    BELOW500 = -1000
+    TOLERANCE = 1.2
+
+    def isCondition(self, flight, aircraft, oldState, state):
+        """Check if the fault condition holds."""
+        vs = state.vs
+        altitude = state.altitude
+        return vs < -8000 or vs > 8000 or \
+               (altitude<500 and vs < (VSChecker.BELOW500 *
+                                       VSChecker.TOLERANCE)) or \
+               (altitude<2500 and vs < (VSChecker.BELOW2500 *
+                                        VSChecker.TOLERANCE)) or \
+               (altitude<5000 and vs < (VSChecker.BELOW5000 *
+                                        VSChecker.TOLERANCE)) or \
+               (altitude<10000 and vs < (VSChecker.BELOW10000 *
+                                         VSChecker.TOLERANCE))
+               
+    def logFault(self, flight, aircraft, logger, oldState, state):
+        """Log the fault."""
+        vs = state.vs
+
+        message = "Vertical speed was %.0f feet/min" % (vs,)
+        if vs>-8000 and vs<8000:
+            message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
+
+        score = 10 if vs<-8000 or vs>8000 else 0
+
+        logger.fault(VSChecker, state.timestamp,
+                     FaultChecker._appendDuring(flight, message),
+                     score)
+
+#---------------------------------------------------------------------------------------
Index: src/checks.py
===================================================================
--- src/checks.py	(revision 22)
+++ 	(revision )
@@ -1,902 +1,0 @@
-# The various checks that may be performed during flight
-
-#---------------------------------------------------------------------------------------
-
-import const
-
-#---------------------------------------------------------------------------------------
-
-class StateChecker(object):
-    """Base class for classes the instances of which check the aircraft's state
-    from some aspect.
-
-    As a result of the check they may log something, or notify of some fault, etc."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check and do whatever is needed.
-
-        This default implementation raises a NotImplementedError."""
-        raise NotImplementedError()
-    
-#---------------------------------------------------------------------------------------
-
-class StageChecker(StateChecker):
-    """Check the flight stage transitions."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check the stage of the aircraft."""
-        stage = flight.stage
-        if stage==None:
-            aircraft.setStage(state, const.STAGE_BOARDING)
-        elif stage==const.STAGE_BOARDING:
-            if not state.parking or \
-               (not state.trickMode and state.groundSpeed>5.0):
-                aircraft.setStage(state, const.STAGE_PUSHANDTAXI)
-        elif stage==const.STAGE_PUSHANDTAXI or stage==const.STAGE_RTO:
-            if state.landingLightsOn or state.strobeLightsOn or \
-               state.groundSpeed>80.0:
-                aircraft.setStage(state, const.STAGE_TAKEOFF)
-        elif stage==const.STAGE_TAKEOFF:
-            if not state.gearsDown or \
-               (state.radioAltitude>3000.0 and state.vs>0):
-                aircraft.setStage(state, const.STAGE_CLIMB)
-            elif not state.landingLightsOn and \
-                 not state.strobeLightsOn and \
-                 state.onTheGround and \
-                 state.groundSpeed<50.0:
-                aircraft.setStage(state, const.STAGE_RTO)
-        elif stage==const.STAGE_CLIMB:
-            if (state.altitude+2000) > flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_CRUISE)
-            elif state.radioAltitude<2000.0 and \
-                 state.vs < 0.0 and state.gearsDown:
-                aircraft.setStage(state, const.STAGE_LANDING)
-        elif stage==const.STAGE_CRUISE:
-            if (state.altitude+2000) < flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_DESCENT)
-        elif stage==const.STAGE_DESCENT or stage==const.STAGE_GOAROUND:
-            if state.gearsDown and state.radioAltitude<2000.0:
-                aircraft.setStage(state, const.STAGE_LANDING)
-            elif (state.altitude+2000) > flight.cruiseAltitude:
-                aircraft.setStage(state, const.STAGE_CRUISE)
-        elif stage==const.STAGE_LANDING:
-            if state.onTheGround and state.groundSpeed<50.0:
-                aircraft.setStage(state, const.STAGE_TAXIAFTERLAND)
-            elif not state.gearsDown:
-                aircraft.setStage(state, const.STAGE_GOAROUND)
-            elif state.radioAltitude>200:
-                aircraft.cancelFlare()
-            elif state.radioAltitude<150 and not state.onTheGround:
-                aircraft.prepareFlare()
-        elif stage==const.STAGE_TAXIAFTERLAND:
-            if state.parking:
-                aircraft.setStage(state, const.STAGE_PARKING)
-        elif stage==const.STAGE_PARKING:
-            if aircraft.checkFlightEnd(state):
-                aircraft.setStage(state, const.STAGE_END)
-
-#---------------------------------------------------------------------------------------
-
-class TakeOffLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._onTheGround = True
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_TAKEOFF and \
-           self._onTheGround and not state.onTheGround:
-            logger.message(state.timestamp,
-                           "Takeoff speed: %.0f knots" % (state.ias,))
-            logger.message(state.timestamp,
-                           "Takeoff heading: %03.0f degrees" % (state.heading,))
-            logger.message(state.timestamp,
-                           "Takeoff pitch: %.1f degrees" % (state.pitch,))
-            self._onTheGround = False
-
-#---------------------------------------------------------------------------------------
-
-class CruiseSpeedLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._lastTime = None
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_CRUISE and \
-           (self._lastTime is None or \
-            (self._lastTime+800)<=state.timestamp):
-                if state.altitude>24500.0:
-                    logger.message(state.timestamp,
-                                   "Cruise speed: %.3f mach" % (state.mach,))
-                else:
-                    logger.message(state.timestamp,
-                                   "Cruise speed: %.0f knots" % (state.ias,))
-                self._lastTime = state.timestamp
-
-#---------------------------------------------------------------------------------------
-
-class SpoilerLogger(StateChecker):
-    """Logger for the cruise speed."""
-    def __init__(self):
-        """Construct the logger."""
-        self._logged = False
-        self._spoilersExtension = None
-    
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Log the cruise speed if necessary."""
-        if flight.stage==const.STAGE_LANDING and not self._logged:
-            if state.onTheGround:
-                if state.spoilersExtension!=self._spoilersExtension:
-                    logger.message(state.timestamp, "Spoilers deployed")
-                    self._logged = True
-            else:
-                self._spoilersExtension = state.spoilersExtension
-
-#---------------------------------------------------------------------------------------
-
-class StateChangeLogger(StateChecker):
-    """Base class for classes the instances of which check if a specific change has
-    occured in the aircraft's state, and log such change."""
-    def __init__(self, logInitial = True):
-        """Construct the logger.
-
-        If logInitial is True, the initial value will be logged, not just the
-        changes later.
-
-        Child classes should define the following functions:
-        - _changed(self, oldState, state): returns a boolean indicating if the
-        value has changed or not
-        - _getMessage(self, state): return a strings containing the message to log
-        with the new value
-        """
-        self._logInitial = logInitial    
-
-    def _getLogTimestamp(self, state):
-        """Get the log timestamp."""
-        return state.timestamp
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Check if the state has changed, and if so, log the new state."""
-        shouldLog = False
-        if oldState is None:
-            shouldLog = self._logInitial
-        else:
-            shouldLog = self._changed(oldState, state)
-        
-        if shouldLog:
-            logger.message(self._getLogTimestamp(state), self._getMessage(state))
-        
-#---------------------------------------------------------------------------------------
-
-class SimpleChangeMixin(object):
-    """A mixin that defines a _changed() function which simply calls a function
-    to retrieve the value two monitor for both states and compares them.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def _changed(self, oldState, state):
-        """Determine if the value has changed."""
-        return self._getValue(oldState)!=self._getValue(state)
-
-#---------------------------------------------------------------------------------------
-
-class SingleValueMixin(object):
-    """A mixin that provides a _getValue() function to query a value from the
-    state using the name of the attribute."""
-    def __init__(self, attrName):
-        """Construct the mixin with the given attribute name."""
-        self._attrName = attrName
-
-    def _getValue(self, state):
-        """Get the value of the attribute from the state."""
-        return getattr(state, self._attrName)
-    
-#---------------------------------------------------------------------------------------
-
-class DelayedChangeMixin(object):
-    """A mixin to a StateChangeLogger that stores the old value and reports a
-    change only if the change has been there for a certain amount of time.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def __init__(self, minDelay = 3.0, maxDelay = 10.0):
-        """Construct the mixin with the given delay in seconds."""
-        self._minDelay = minDelay
-        self._maxDelay = maxDelay
-        self._oldValue = None
-        self._firstChange = None
-        self._lastChangeState = None
-        
-    def _changed(self, oldState, state):
-        """Determine if the value has changed."""
-        if self._oldValue is None:
-            self._oldValue = self._getValue(oldState)
-            
-        newValue = self._getValue(state)
-        if self._oldValue!=newValue:
-            if self._firstChange is None:
-                self._firstChange = state.timestamp
-            self._lastChangeState = state
-            self._oldValue = newValue
-
-        if self._firstChange is not None:
-            if state.timestamp >= min(self._lastChangeState.timestamp + self._minDelay,
-                                      self._firstChange + self._maxDelay):
-                self._firstChange = None
-                return True
-            
-        return False
-
-    def _getLogTimestamp(self, state):
-        """Get the log timestamp."""
-        return self._lastChangeState.timestamp if \
-               self._lastChangeState is not None else state.timestamp
-
-#---------------------------------------------------------------------------------------
-
-class TemplateMessageMixin(object):
-    """Mixin to generate a message based on a template.
-
-    Child classes should define the following function:
-    - _getValue(state): get the value we are interested in."""
-    def __init__(self, template):
-        """Construct the mixin."""
-        self._template = template
-
-    def _getMessage(self, state):
-        """Get the message."""
-        return self._template % (self._getValue(state),)
-
-#---------------------------------------------------------------------------------------
-
-class GenericStateChangeLogger(StateChangeLogger, SingleValueMixin,
-                               DelayedChangeMixin, TemplateMessageMixin):
-    """Base for generic state change loggers that monitor a single value in the
-    state possibly with a delay and the logged message comes from a template"""
-    def __init__(self, attrName, template, logInitial = True,
-                 minDelay = 0.0, maxDelay = 0.0):
-        """Construct the object."""
-        StateChangeLogger.__init__(self, logInitial = logInitial)
-        SingleValueMixin.__init__(self, attrName)
-        DelayedChangeMixin.__init__(self, minDelay = minDelay, maxDelay = maxDelay)
-        TemplateMessageMixin.__init__(self, template)
-        self._getLogTimestamp = lambda state: \
-                                DelayedChangeMixin._getLogTimestamp(self, state)
-
-#---------------------------------------------------------------------------------------
-
-class AltimeterLogger(StateChangeLogger, SingleValueMixin,
-                      DelayedChangeMixin):
-    """Logger for the altimeter setting."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "altimeter")
-        DelayedChangeMixin.__init__(self)
-        self._getLogTimestamp = lambda state: \
-                                DelayedChangeMixin._getLogTimestamp(self, state)
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        logState = self._lastChangeState if \
-                   self._lastChangeState is not None else state
-        return "Altimeter: %.0f hPa at %.0f feet" % \
-               (logState.altimeter, logState.altitude)
-
-#---------------------------------------------------------------------------------------
-
-class NAV1Logger(GenericStateChangeLogger):
-    """Logger for the NAV1 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV1Logger, self).__init__("nav1", "NAV1 frequency: %s MHz")
-
-#---------------------------------------------------------------------------------------
-
-class NAV2Logger(GenericStateChangeLogger):
-    """Logger for the NAV2 radio setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(NAV2Logger, self).__init__("nav2", "NAV2 frequency: %s MHz")
-
-#---------------------------------------------------------------------------------------
-
-class SquawkLogger(GenericStateChangeLogger):
-    """Logger for the squawk setting."""
-    def __init__(self):
-        """Construct the logger."""
-        super(SquawkLogger, self).__init__("squawk", "Squawk code: %s",
-                                           minDelay = 3.0, maxDelay = 10.0)
-
-#---------------------------------------------------------------------------------------
-
-class LightsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Base class for the loggers of the various lights."""
-    def __init__(self, attrName, template):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self)
-        SingleValueMixin.__init__(self, attrName)
-        
-        self._template = template
-
-    def _getMessage(self, state):
-        """Get the message from the given state."""
-        return self._template % ("ON" if self._getValue(state) else "OFF")
-        
-#---------------------------------------------------------------------------------------
-
-class AnticollisionLightsLogger(LightsLogger):
-    """Logger for the anti-collision lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "antiCollisionLightsOn",
-                              "Anti-collision lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class LandingLightsLogger(LightsLogger):
-    """Logger for the landing lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "landingLightsOn",
-                              "Landing lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class StrobeLightsLogger(LightsLogger):
-    """Logger for the strobe lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "strobeLightsOn",
-                              "Strobe lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class NavLightsLogger(LightsLogger):
-    """Logger for the navigational lights."""
-    def __init__(self):
-        LightsLogger.__init__(self, "navLightsOn",
-                              "Navigational lights: %s")
-
-#---------------------------------------------------------------------------------------
-
-class FlapsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Logger for the flaps setting."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "flapsSet")
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        speed = state.groundSpeed if state.groundSpeed<80.0 else state.ias
-        return "Flaps set to %.0f at %.0f knots" % (state.flapsSet, speed)
-
-#---------------------------------------------------------------------------------------
-
-class GearsLogger(StateChangeLogger, SingleValueMixin, SimpleChangeMixin):
-    """Logger for the gears state."""
-    def __init__(self):
-        """Construct the logger."""
-        StateChangeLogger.__init__(self, logInitial = True)
-        SingleValueMixin.__init__(self, "gearsDown")
-
-    def _getMessage(self, state):
-        """Get the message to log on a change."""
-        return "Gears %s at %.0f knots, %.0f feet" % \
-            ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
-
-#---------------------------------------------------------------------------------------
-
-class FaultChecker(StateChecker):
-    """Base class for checkers that look for faults."""
-    @staticmethod
-    def _appendDuring(flight, message):
-        """Append a 'during XXX' test to the given message, depending on the
-        flight stage."""
-        stageStr = const.stage2string(flight.stage)
-        return message if stageStr is None \
-               else (message + " during " + stageStr.upper())
-
-    @staticmethod
-    def _getLinearScore(minFaultValue, maxFaultValue, minScore, maxScore,
-                        value):
-        """Get the score for a faulty value where the score is calculated
-        linearly within a certain range."""
-        if value<minFaultValue:
-            return 0
-        elif value>maxFaultValue:
-            return maxScore
-        else:
-            return minScore + (maxScore-minScore) * (value-minFaultValue) / \
-                   (maxFaultValue - minFaultValue)
-
-#---------------------------------------------------------------------------------------
-
-class SimpleFaultChecker(FaultChecker):
-    """Base class for fault checkers that check for a single occurence of a
-    faulty condition.
-
-    Child classes should implement the following functions:
-    - isCondition(self, flight, aircraft, oldState, state): should return whether the
-    condition holds
-    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
-    via the logger."""
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check."""
-        if self.isCondition(flight, aircraft, oldState, state):
-            self.logFault(flight, aircraft, logger, oldState, state)
-
-#---------------------------------------------------------------------------------------
-
-class PatientFaultChecker(FaultChecker):
-    """A fault checker that does not decides on a fault when the condition
-    arises immediately, but can wait some time.
-
-    Child classes should implement the following functions:
-    - isCondition(self, flight, aircraft, oldState, state): should return whether the
-    condition holds
-    - logFault(self, flight, aircraft, logger, oldState, state): log the fault
-    via the logger
-    """
-    def __init__(self, timeout = 2.0):
-        """Construct the fault checker with the given timeout."""
-        self._timeout = timeout
-        self._faultStarted = None
-
-    def getTimeout(self, flight, aircraft, oldState, state):
-        """Get the timeout.
-
-        This default implementation returns the timeout given in the
-        constructor, but child classes might want to enforce a different
-        policy."""
-        return self._timeout
-
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Perform the check."""
-        if self.isCondition(flight, aircraft, oldState, state):
-            if self._faultStarted is None:
-                self._faultStarted = state.timestamp
-            timeout = self.getTimeout(flight, aircraft, oldState, state)
-            if state.timestamp>=(self._faultStarted + timeout):
-                self.logFault(flight, aircraft, logger, oldState, state)
-                self._faultStarted = state.timestamp
-        else:
-            self._faultStarted = None
-                
-#---------------------------------------------------------------------------------------
-
-class AntiCollisionLightsChecker(PatientFaultChecker):
-    """Check for the anti-collision light being off at high N1 values."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return (flight.stage!=const.STAGE_PARKING or \
-                not flight.options.fs2Crew) and \
-                not state.antiCollisionLightsOn and \
-                max(state.n1)>5
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(AntiCollisionLightsChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Anti-collision lights were off"),
-                     1)
-
-#---------------------------------------------------------------------------------------
-
-class BankChecker(SimpleFaultChecker):
-    """Check for the anti-collision light being off at high N1 values."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if flight.stage==const.STAGE_CRUISE:
-            bankLimit = 30
-        elif flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                              const.STAGE_DESCENT, const.STAGE_LANDING]:
-            bankLimit = 35
-        else:
-            return False
-
-        return state.bank>bankLimit or state.bank<-bankLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(BankChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Bank too steep"),
-                     2)
-
-#---------------------------------------------------------------------------------------
-
-class FlapsRetractChecker(SimpleFaultChecker):
-    """Check if the flaps are not retracted too early."""
-    def __init__(self):
-        """Construct the flaps checker."""
-        self._timeStart = None
-    
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds.
-
-        FIXME: check if this really is the intention (FlapsRetractedMistake.java)"""
-        if (flight.stage==const.STAGE_TAKEOFF and not state.onTheGround) or \
-           (flight.stage==const.STAGE_LANDING and state.onTheGround):
-            if self._timeStart is None:
-                self._timeStart = state.timestamp
-
-            if state.flapsSet==0 and state.timestamp<=(self._timeStart+2.0):
-                return True
-        else:
-            self._timeStart = None
-        return False
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(FlapsRetractChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Flaps retracted"),
-                     20)
-
-#---------------------------------------------------------------------------------------
-
-class FlapsSpeedLimitChecker(SimpleFaultChecker):
-    """Check if the flaps are extended only at the right speeds."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        speedLimit = aircraft.getFlapsSpeedLimit(state.flapsSet)
-        return speedLimit is not None and state.ias>speedLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(FlapsSpeedLimitChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Flap speed limit fault"),
-                     5)
-
-#---------------------------------------------------------------------------------------
-
-class GearsDownChecker(SimpleFaultChecker):
-    """Check if the gears are down at low altitudes."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.radioAltitude<10 and not state.gearsDown and \
-               flight.stage!=const.STAGE_TAKEOFF
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.noGo(GearsDownChecker, state.timestamp,
-                    "Gears not down at %.0f feet radio altitude" % \
-                    (state.radioAltitude,),
-                    "GEAR DOWN NO GO")
-
-#---------------------------------------------------------------------------------------
-
-class GearSpeedLimitChecker(PatientFaultChecker):
-    """Check if the gears not down at too high a speed."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.gearsDown and state.ias>aircraft.gearSpeedLimit
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(GearSpeedLinmitChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Gear speed limit fault"),
-                     5)
-
-#---------------------------------------------------------------------------------------
-
-class GLoadChecker(SimpleFaultChecker):
-    """Check if the G-load does not exceed 2 except during flare."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.gLoad>2.0 and (flight.stage!=const.STAGE_LANDING or \
-                                    state.radioAltitude>=50)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(GLoadChecker, state.timestamp,
-                     "G-load was %.2f" % (state.gLoad,),
-                     10)
-
-#---------------------------------------------------------------------------------------
-
-class LandingLightsChecker(PatientFaultChecker):
-    """Check if the landing lights are used properly."""
-    def getTimeout(self, flight, aircraft, oldState, state):
-        """Get the timeout.
-
-        It is the default timeout except for landing and takeoff."""
-        return 0.0 if flight.stage in [const.STAGE_TAKEOFF,
-                                       const.STAGE_LANDING] else self._timeout
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return (flight.stage==const.STAGE_BOARDING and \
-                state.landingLightsOn and state.onTheGround) or \
-               (flight.stage==const.STAGE_TAKEOFF and \
-                not state.landingLightsOn and not state.onTheGround) or \
-               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
-                                 const.STAGE_DESCENT] and \
-                state.landingLightsOn and state.altitude>12500) or \
-               (flight.stage==const.STAGE_LANDING and \
-                not state.landingLightsOn and state.onTheGround) or \
-               (flight.stage==const.STAGE_PARKING and \
-                state.landingLightsOn and state.onTheGround)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 0 if flight.stage==const.STAGE_LANDING else 1
-        message = "Landing lights were %s" % (("on" if state.landingLightsOn else "off"),)
-        logger.fault(LandingLightsChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, message),
-                     score)        
-
-#---------------------------------------------------------------------------------------
-
-class WeightChecker(PatientFaultChecker):
-    """Base class for checkers that check that some limit is not exceeded."""
-    def __init__(self, name):
-        """Construct the checker."""
-        super(WeightChecker, self).__init__(timeout = 5.0)
-        self._name = name
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        if flight.entranceExam:
-            return False
-
-        limit = self.getLimit(flight, aircraft, state)
-        if limit is not None:
-            if flight.options.compensation is not None:
-                limit += flight.options.compensation
-            return self.getWeight(state)>limit
-
-        return False
-
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        mname = "M" + self._name
-        logger.noGo(self.__class__, state.timestamp,
-                    "%s exceeded: %s is %.0f kg" % \
-                    (mname, self._name, self.getWeight(state)),
-                    "%s NO GO" % (mname,))
-
-    def getWeight(self, state):
-        """Get the weight that is interesting for us."""
-        return state.grossWeight
-
-#---------------------------------------------------------------------------------------
-
-class MLWChecker(WeightChecker):
-    """Checks if the MLW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MLWChecker, self).__init__("LW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mlw if flight.stage==const.STAGE_LANDING and \
-                               state.onTheGround else None
-
-#---------------------------------------------------------------------------------------
-
-class MTOWChecker(WeightChecker):
-    """Checks if the MTOW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MTOWChecker, self).__init__("TOW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mtow if flight.stage==const.STAGE_TAKEOFF else None
-
-#---------------------------------------------------------------------------------------
-
-class MZFWChecker(WeightChecker):
-    """Checks if the MTOW is not exceeded on landing."""
-    def __init__(self):
-        """Construct the checker."""
-        super(MZFWChecker, self).__init__("ZFW")
-
-    def getLimit(self, flight, aircraft, state):
-        """Get the limit if we are in the right state."""
-        return aircraft.mzfw
-
-    def getWeight(self, state):
-        """Get the weight that is interesting for us."""
-        return state.zfw
-
-#---------------------------------------------------------------------------------------
-
-class NavLightsChecker(PatientFaultChecker):
-    """Check if the navigational lights are used properly."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage!=const.STAGE_BOARDING and \
-               flight.stage!=const.STAGE_PARKING and \
-               not state.navLightsOn
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(NavLightsChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Navigation lights were off"),
-                     1)        
-
-#---------------------------------------------------------------------------------------
-
-class OverspeedChecker(PatientFaultChecker):
-    """Check if Vne has been exceeded."""
-    def __init__(self):
-        """Construct the checker."""
-        super(OverspeedChecker, self).__init__(timeout = 5.0)
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.overspeed
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(OverspeedChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Overspeed"),
-                     20)        
-
-#---------------------------------------------------------------------------------------
-
-class PayloadChecker(SimpleFaultChecker):
-    """Check if the payload matches the specification."""
-    TOLERANCE=550
-    
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage==const.STAGE_PUSHANDTAXI and \
-               (state.zfw < (flight.zfw - PayloadChecker.TOLERANCE) or \
-                state.zfw > (flight.zfw + PayloadChecker.TOLERANCE))
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.noGo(PayloadChecker, state.timestamp,
-                    "ZFW difference is more than %d kgs" % (PayloadChecker.TOLERANCE,),
-                    "ZFW NO GO")
-
-#---------------------------------------------------------------------------------------
-
-class PitotChecker(PatientFaultChecker):
-    """Check if pitot heat is on."""
-    def __init__(self):
-        """Construct the checker."""
-        super(PitotChecker, self).__init__(timeout = 3.0)
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return state.groundSpeed>80 and not state.pitotHeatOn
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 2 if flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                                      const.STAGE_CRUISE, const.STAGE_DESCENT,
-                                      const.STAGE_LANDING] else 0
-        logger.fault(PitotChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Pitot heat was off"),
-                     score)        
-
-#---------------------------------------------------------------------------------------
-
-class ReverserChecker(SimpleFaultChecker):
-    """Check if the reverser is not used below 60 knots."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_DESCENT, const.STAGE_LANDING,
-                                const.STAGE_TAXIAFTERLAND] and \
-            state.groundSpeed<80 and max(state.reverser)
-                           
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(ReverserChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Reverser used below 60 knots"),
-                     15)        
-
-#---------------------------------------------------------------------------------------
-
-class SpeedChecker(SimpleFaultChecker):
-    """Check if the speed is in the prescribed limits."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_PUSHANDTAXI,
-                                const.STAGE_TAXIAFTERLAND] and \
-            state.groundSpeed>50
-                           
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        logger.fault(SpeedChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Taxi speed over 50 knots"),
-                     FaultChecker._getLinearScore(50, 80, 10, 15, state.groundSpeed))
-
-#---------------------------------------------------------------------------------------
-
-class StallChecker(PatientFaultChecker):
-    """Check if stall occured."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage in [const.STAGE_TAKEOFF, const.STAGE_CLIMB,
-                                const.STAGE_CRUISE, const.STAGE_DESCENT,
-                                const.STAGE_LANDING] and state.stalled
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        score = 40 if flight.stage in [const.STAGE_TAKEOFF,
-                                       const.STAGE_LANDING] else 30
-        logger.fault(StallChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Stalled"),
-                     score)        
-
-#---------------------------------------------------------------------------------------
-
-class StrobeLightsChecker(PatientFaultChecker):
-    """Check if the strobe lights are used properly."""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return (flight.stage==const.STAGE_BOARDING and \
-                state.strobeLightsOn and state.onTheGround) or \
-               (flight.stage==const.STAGE_TAKEOFF and \
-                not state.strobeLightsOn and not state.gearsDown) or \
-               (flight.stage in [const.STAGE_CLIMB, const.STAGE_CRUISE,
-                                 const.STAGE_DESCENT] and \
-                not state.strobeLightsOn and not state.onTheGround) or \
-               (flight.stage==const.STAGE_PARKING and \
-                state.strobeLightsOn and state.onTheGround)
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        message = "Strobe lights were %s" % (("on" if state.strobeLightsOn else "off"),)
-        logger.fault(StrobeLightsChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, message),
-                     1)        
-
-#---------------------------------------------------------------------------------------
-
-class ThrustChecker(SimpleFaultChecker):
-    """Check if the thrust setting is not too high during takeoff.
-
-    FIXME: is this really so general, for all aircraft?"""
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        return flight.stage==const.STAGE_TAKEOFF and max(state.n1)>97
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        print state.n1
-        logger.fault(LandingLightsChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, "Thrust setting was too high (>97%)"),
-                     FaultChecker._getLinearScore(97, 110, 0, 10, max(state.n1)))
-
-#---------------------------------------------------------------------------------------
-
-class VSChecker(SimpleFaultChecker):
-    """Check if the vertical speed is not too low at certain altitudes"""
-    BELOW10000 = -5000
-    BELOW5000 = -2500
-    BELOW2500 = -1500
-    BELOW500 = -1000
-    TOLERANCE = 1.2
-
-    def isCondition(self, flight, aircraft, oldState, state):
-        """Check if the fault condition holds."""
-        vs = state.vs
-        altitude = state.altitude
-        return vs < -8000 or vs > 8000 or \
-               (altitude<500 and vs < (VSChecker.BELOW500 *
-                                       VSChecker.TOLERANCE)) or \
-               (altitude<2500 and vs < (VSChecker.BELOW2500 *
-                                        VSChecker.TOLERANCE)) or \
-               (altitude<5000 and vs < (VSChecker.BELOW5000 *
-                                        VSChecker.TOLERANCE)) or \
-               (altitude<10000 and vs < (VSChecker.BELOW10000 *
-                                         VSChecker.TOLERANCE))
-               
-    def logFault(self, flight, aircraft, logger, oldState, state):
-        """Log the fault."""
-        vs = state.vs
-
-        message = "Vertical speed was %.0f feet/min" % (vs,)
-        if vs>-8000 and vs<8000:
-            message += " at %.0f feet (exceeds company limit)" % (state.altitude,)
-
-        score = 10 if vs<-8000 or vs>8000 else 0
-
-        logger.fault(VSChecker, state.timestamp,
-                     FaultChecker._appendDuring(flight, message),
-                     score)
-
-#---------------------------------------------------------------------------------------
Index: src/const.py
===================================================================
--- src/const.py	(revision 20)
+++ src/const.py	(revision 20)
@@ -0,0 +1,134 @@
+# Various constants used in the logger
+
+#-------------------------------------------------------------------------------
+
+# The version of the program
+VERSION="0.001"
+
+#-------------------------------------------------------------------------------
+
+# The ratio between lbs and kg
+LBSTOKG=0.4536
+
+# The ratio between feet and metre
+FEETTOMETRES=0.3048
+
+#-------------------------------------------------------------------------------
+
+# Flight simulator type: MS Flight Simulator 2004
+SIM_MSFS9 = 1
+
+# Flight simulator type: MS Flight Simulator X
+SIM_MSFSX = 2
+
+# Flight simulator type: X-Plane 9
+SIM_XPLANE9 = 3
+
+# Flight simulator type: X-Plane 10
+SIM_XPLANE10 = 4
+
+#-------------------------------------------------------------------------------
+
+# Aircraft type: Boeing 737-600
+AIRCRAFT_B736 = 1
+
+# Aircraft type: Boeing 737-700
+AIRCRAFT_B737 = 2
+
+# Aircraft type: Boeing 737-800
+AIRCRAFT_B738 = 3
+
+# Aircraft type: Boeing 737-300
+AIRCRAFT_B733 = 4
+
+# Aircraft type: Boeing 737-400
+AIRCRAFT_B734 = 5
+
+# Aircraft type: Boeing 737-500
+AIRCRAFT_B735 = 6
+
+# Aircraft type: Dash-8 Q400
+AIRCRAFT_DH8D = 7
+
+# Aircraft type: Boeing 767-200
+AIRCRAFT_B762 = 8
+
+# Aircraft type: Boeing 767-300
+AIRCRAFT_B763 = 9
+
+# Aircraft type: Canadair CRJ-200
+AIRCRAFT_CRJ2 = 10
+
+# Aircraft type: Fokker F-70
+AIRCRAFT_F70 = 11
+
+# Aircraft type: Lisunov Li-2
+AIRCRAFT_DC3 = 12
+
+# Aircraft type: Tupolev Tu-134
+AIRCRAFT_T134 = 13
+
+# Aircraft type: Tupolev Tu-154
+AIRCRAFT_T154 = 14
+
+# Aircraft type: Yakovlev Yak-40
+AIRCRAFT_YK40 = 15
+
+#-------------------------------------------------------------------------------
+
+# Flight stage: boarding
+STAGE_BOARDING = 1
+
+# Flight stage: pushback, startup and taxi
+STAGE_PUSHANDTAXI = 2
+
+# Flight stage: takeoff
+STAGE_TAKEOFF = 3
+
+# Flight stage: RTO
+STAGE_RTO = 4
+
+# Flight stage: climb
+STAGE_CLIMB = 5
+
+# Flight stage: cruise
+STAGE_CRUISE = 6
+
+# Flight stage: descent
+STAGE_DESCENT = 7
+
+# Flight stage: landing
+STAGE_LANDING = 8
+
+# Flight stage: taxi after landing
+STAGE_TAXIAFTERLAND = 9
+
+# Flight stage: parking
+STAGE_PARKING = 10
+
+# Flight stage: go-around
+STAGE_GOAROUND = 11
+
+# Flight stage: end
+STAGE_END = 12
+
+#-------------------------------------------------------------------------------
+
+_stageStrings = { STAGE_BOARDING : "boarding",
+                  STAGE_PUSHANDTAXI : "pushback and taxi",
+                  STAGE_TAKEOFF : "takeoff",
+                  STAGE_RTO : "RTO",
+                  STAGE_CLIMB : "climb",
+                  STAGE_CRUISE : "cruise",
+                  STAGE_DESCENT : "descent",
+                  STAGE_LANDING : "landing",
+                  STAGE_TAXIAFTERLAND : "taxi",
+                  STAGE_PARKING : "parking",
+                  STAGE_GOAROUND : "go-around",
+                  STAGE_END : "end" }
+
+def stage2string(stage):
+    """Convert the given stage to a lower-case string."""
+    return _stageStrings[stage] if stage in _stageStrings else None
+        
+    
Index: src/const.py
===================================================================
--- src/const.py	(revision 20)
+++ 	(revision )
@@ -1,134 +1,0 @@
-# Various constants used in the logger
-
-#-------------------------------------------------------------------------------
-
-# The version of the program
-VERSION="0.001"
-
-#-------------------------------------------------------------------------------
-
-# The ratio between lbs and kg
-LBSTOKG=0.4536
-
-# The ratio between feet and metre
-FEETTOMETRES=0.3048
-
-#-------------------------------------------------------------------------------
-
-# Flight simulator type: MS Flight Simulator 2004
-SIM_MSFS9 = 1
-
-# Flight simulator type: MS Flight Simulator X
-SIM_MSFSX = 2
-
-# Flight simulator type: X-Plane 9
-SIM_XPLANE9 = 3
-
-# Flight simulator type: X-Plane 10
-SIM_XPLANE10 = 4
-
-#-------------------------------------------------------------------------------
-
-# Aircraft type: Boeing 737-600
-AIRCRAFT_B736 = 1
-
-# Aircraft type: Boeing 737-700
-AIRCRAFT_B737 = 2
-
-# Aircraft type: Boeing 737-800
-AIRCRAFT_B738 = 3
-
-# Aircraft type: Boeing 737-300
-AIRCRAFT_B733 = 4
-
-# Aircraft type: Boeing 737-400
-AIRCRAFT_B734 = 5
-
-# Aircraft type: Boeing 737-500
-AIRCRAFT_B735 = 6
-
-# Aircraft type: Dash-8 Q400
-AIRCRAFT_DH8D = 7
-
-# Aircraft type: Boeing 767-200
-AIRCRAFT_B762 = 8
-
-# Aircraft type: Boeing 767-300
-AIRCRAFT_B763 = 9
-
-# Aircraft type: Canadair CRJ-200
-AIRCRAFT_CRJ2 = 10
-
-# Aircraft type: Fokker F-70
-AIRCRAFT_F70 = 11
-
-# Aircraft type: Lisunov Li-2
-AIRCRAFT_DC3 = 12
-
-# Aircraft type: Tupolev Tu-134
-AIRCRAFT_T134 = 13
-
-# Aircraft type: Tupolev Tu-154
-AIRCRAFT_T154 = 14
-
-# Aircraft type: Yakovlev Yak-40
-AIRCRAFT_YK40 = 15
-
-#-------------------------------------------------------------------------------
-
-# Flight stage: boarding
-STAGE_BOARDING = 1
-
-# Flight stage: pushback, startup and taxi
-STAGE_PUSHANDTAXI = 2
-
-# Flight stage: takeoff
-STAGE_TAKEOFF = 3
-
-# Flight stage: RTO
-STAGE_RTO = 4
-
-# Flight stage: climb
-STAGE_CLIMB = 5
-
-# Flight stage: cruise
-STAGE_CRUISE = 6
-
-# Flight stage: descent
-STAGE_DESCENT = 7
-
-# Flight stage: landing
-STAGE_LANDING = 8
-
-# Flight stage: taxi after landing
-STAGE_TAXIAFTERLAND = 9
-
-# Flight stage: parking
-STAGE_PARKING = 10
-
-# Flight stage: go-around
-STAGE_GOAROUND = 11
-
-# Flight stage: end
-STAGE_END = 12
-
-#-------------------------------------------------------------------------------
-
-_stageStrings = { STAGE_BOARDING : "boarding",
-                  STAGE_PUSHANDTAXI : "pushback and taxi",
-                  STAGE_TAKEOFF : "takeoff",
-                  STAGE_RTO : "RTO",
-                  STAGE_CLIMB : "climb",
-                  STAGE_CRUISE : "cruise",
-                  STAGE_DESCENT : "descent",
-                  STAGE_LANDING : "landing",
-                  STAGE_TAXIAFTERLAND : "taxi",
-                  STAGE_PARKING : "parking",
-                  STAGE_GOAROUND : "go-around",
-                  STAGE_END : "end" }
-
-def stage2string(stage):
-    """Convert the given stage to a lower-case string."""
-    return _stageStrings[stage] if stage in _stageStrings else None
-        
-    
Index: src/flight.py
===================================================================
--- src/flight.py	(revision 14)
+++ src/flight.py	(revision 14)
@@ -0,0 +1,88 @@
+# Module related to the high-level tracking of the flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+
+import threading
+
+#---------------------------------------------------------------------------------------
+
+class Options(object):
+    """Various configuration options."""
+    def __init__(self):
+        """Construct the object with default values."""
+        self.fs2Crew = False
+        self.compensation = None        
+
+#---------------------------------------------------------------------------------------
+
+class Flight(object):
+    """The object with the global flight state.
+    
+    It is also the hub for the other main objects participating in the handling of
+    the flight."""
+    def __init__(self, logger):
+        """Construct the flight."""
+        self._stage = None
+        self.logger = logger
+
+        self.cruiseAltitude = None
+        self.flareTimeFromFS = False
+        self.entranceExam = False
+        self.zfw = 50000
+
+        self.options = Options()
+
+        self.aircraftType = None
+        self.aircraft = None
+        self.simulator = None
+
+        self._endCondition = threading.Condition()
+
+        self._flareStart = None
+        self._flareStartFS = None
+
+    @property
+    def stage(self):
+        """Get the flight stage."""
+        return self._stage
+
+    def setStage(self, timestamp, stage):
+        """Set the flight stage.
+
+        Returns if the stage has really changed."""
+        if stage!=self._stage:
+            self._stage = stage
+            self.logger.stage(timestamp, stage)
+            if stage==const.STAGE_END:
+                with self._endCondition:
+                    self._endCondition.notify()
+            return True
+        else:
+            return False
+
+    def flareStarted(self, flareStart, flareStartFS):
+        """Called when the flare time has started."""
+        self._flareStart = flareStart
+        self._flareStartFS = flareStartFS
+
+    def flareFinished(self, flareEnd, flareEndFS):
+        """Called when the flare time has ended.
+        
+        Return a tuple of the following items:
+        - a boolean indicating if FS time is used
+        - the flare time
+        """
+        if self.flareTimeFromFS:
+            return (True, flareEndFS - self._flareStartFS)
+        else:
+            return (False, flareEnd - self._flareStart)
+
+    def wait(self):
+        """Wait for the flight to end."""
+        with self._endCondition:
+            while self._stage!=const.STAGE_END:
+                self._endCondition.wait(1)
+
+#---------------------------------------------------------------------------------------
Index: src/flight.py
===================================================================
--- src/flight.py	(revision 14)
+++ 	(revision )
@@ -1,88 +1,0 @@
-# Module related to the high-level tracking of the flight
-
-#---------------------------------------------------------------------------------------
-
-import const
-
-import threading
-
-#---------------------------------------------------------------------------------------
-
-class Options(object):
-    """Various configuration options."""
-    def __init__(self):
-        """Construct the object with default values."""
-        self.fs2Crew = False
-        self.compensation = None        
-
-#---------------------------------------------------------------------------------------
-
-class Flight(object):
-    """The object with the global flight state.
-    
-    It is also the hub for the other main objects participating in the handling of
-    the flight."""
-    def __init__(self, logger):
-        """Construct the flight."""
-        self._stage = None
-        self.logger = logger
-
-        self.cruiseAltitude = None
-        self.flareTimeFromFS = False
-        self.entranceExam = False
-        self.zfw = 50000
-
-        self.options = Options()
-
-        self.aircraftType = None
-        self.aircraft = None
-        self.simulator = None
-
-        self._endCondition = threading.Condition()
-
-        self._flareStart = None
-        self._flareStartFS = None
-
-    @property
-    def stage(self):
-        """Get the flight stage."""
-        return self._stage
-
-    def setStage(self, timestamp, stage):
-        """Set the flight stage.
-
-        Returns if the stage has really changed."""
-        if stage!=self._stage:
-            self._stage = stage
-            self.logger.stage(timestamp, stage)
-            if stage==const.STAGE_END:
-                with self._endCondition:
-                    self._endCondition.notify()
-            return True
-        else:
-            return False
-
-    def flareStarted(self, flareStart, flareStartFS):
-        """Called when the flare time has started."""
-        self._flareStart = flareStart
-        self._flareStartFS = flareStartFS
-
-    def flareFinished(self, flareEnd, flareEndFS):
-        """Called when the flare time has ended.
-        
-        Return a tuple of the following items:
-        - a boolean indicating if FS time is used
-        - the flare time
-        """
-        if self.flareTimeFromFS:
-            return (True, flareEndFS - self._flareStartFS)
-        else:
-            return (False, flareEnd - self._flareStart)
-
-    def wait(self):
-        """Wait for the flight to end."""
-        with self._endCondition:
-            while self._stage!=const.STAGE_END:
-                self._endCondition.wait(1)
-
-#---------------------------------------------------------------------------------------
Index: src/fs.py
===================================================================
--- src/fs.py	(revision 15)
+++ src/fs.py	(revision 15)
@@ -0,0 +1,109 @@
+# Module for generic flight-simulator interfaces
+
+#-------------------------------------------------------------------------------
+
+import const
+
+#-------------------------------------------------------------------------------
+
+class ConnectionListener(object):
+    """Base class for listeners on connections to the flight simulator."""
+    def connected(self, fsType, descriptor):
+        """Called when a connection has been established to the flight
+        simulator of the given type."""
+        print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
+
+    def disconnected(self):
+        """Called when a connection to the flight simulator has been broken."""
+        print "fs.ConnectionListener.disconnected"
+
+#-------------------------------------------------------------------------------
+
+class SimulatorException(Exception):
+    """Exception thrown by the simulator interface for communication failure."""
+
+#-------------------------------------------------------------------------------
+
+def createSimulator(type, connectionListener):
+    """Create a simulator instance for the given simulator type with the given
+    connection listener.
+
+    The returned object should provide the following members:
+    FIXME: add info
+    """
+    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
+           "Only MS Flight Simulator 2004 and X are supported"
+    import fsuipc
+    return fsuipc.Simulator(connectionListener)
+
+#-------------------------------------------------------------------------------
+
+class AircraftState(object):
+    """Base class for the aircraft state produced by the aircraft model based
+    on readings from the simulator.
+
+    The following data members should be provided at least:
+    - timestamp: the simulator time of the measurement in seconds since the
+    epoch (float)
+    - paused: a boolean indicating if the flight simulator is paused for
+    whatever reason (it could be a pause mode, or a menu, a dialog, or a
+    replay, etc.)
+    - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
+    MSFS) is activated
+    - overspeed: a boolean indicating if the aircraft is in overspeed
+    - stalled: a boolean indicating if the aircraft is stalled
+    - onTheGround: a boolean indicating if the aircraft is on the ground
+    - zfw: the zero-fuel weight in kilograms (float)
+    - grossWeight: the gross weight in kilograms (float)
+    - heading: the heading of the aircraft in degrees (float)
+    - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
+    negative means pitch up (float)
+    - bank: the bank of the aircraft in degrees. Positive means bank left,
+    negative means bank right (float)
+    - ias: the indicated airspeed in knots (float)    
+    - mach: the airspeed in mach (float)    
+    - groundSpeed: the ground speed (float)
+    - vs: the vertical speed in feet/minutes (float)
+    - radioAltitude: the radio altitude of the aircraft in feet (float)
+    - altitude: the altitude of the aircraft in feet (float)
+    - gLoad: G-load (float)
+    - flapsSet: the selected degrees of the flaps (float)
+    - flaps: the actual degrees of the flaps (float)
+    - fuelWeight[]: the fuel weights in the different tanks in kgs (array of
+    floats of as many items as the number fuel tanks)
+    - n1[]: the N1 values of the turbine engines (array of floats
+    of as many items as the number of engines, present only for aircraft with
+    turbines)
+    - rpm[]: the RPM values of the piston engines (array of floats
+    of as many items as the number of engines, present only for aircraft with
+    pistons)
+    - reverser[]: an array of booleans indicating if the thrust reversers are
+    activated on any of the engines. The number of items equals to the number
+    of engines with a reverser.
+    - navLightsOn: a boolean indicating if the navigation lights are on
+    - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
+    - strobeLightsOn: a boolean indicating if the strobe lights are on
+    - landingLightsOn: a boolean indicating if the landing lights are on
+    - pitotHeatOn: a boolean indicating if the pitot heat is on
+    - parking: a boolean indicating if the parking brake is set
+    - gearsDown: a boolean indicating if the gears are down
+    - spoilersArmed: a boolean indicating if the spoilers have been armed for
+    automatic deployment
+    - spoilersExtension: the percentage of how much the spoiler is extended
+    (float) 
+    - altimeter: the altimeter setting in hPa (float)
+    - nav1: the frequency of the NAV1 radio in MHz (string)
+    - nav2: the frequency of the NAV1 radio in MHz (string)
+    - squawk: the transponder code (string)
+    - windSpeed: the speed of the wind at the aircraft in knots (float)
+    - windDirection: the direction of the wind at the aircraft in degrees (float)
+
+    FIXME: needed when taxiing only:
+    - payload weight
+
+    FIXME: needed rarely:
+    - latitude, longitude
+    - transporter
+    - visibility 
+    """
+    
Index: src/fs.py
===================================================================
--- src/fs.py	(revision 15)
+++ 	(revision )
@@ -1,109 +1,0 @@
-# Module for generic flight-simulator interfaces
-
-#-------------------------------------------------------------------------------
-
-import const
-
-#-------------------------------------------------------------------------------
-
-class ConnectionListener(object):
-    """Base class for listeners on connections to the flight simulator."""
-    def connected(self, fsType, descriptor):
-        """Called when a connection has been established to the flight
-        simulator of the given type."""
-        print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
-
-    def disconnected(self):
-        """Called when a connection to the flight simulator has been broken."""
-        print "fs.ConnectionListener.disconnected"
-
-#-------------------------------------------------------------------------------
-
-class SimulatorException(Exception):
-    """Exception thrown by the simulator interface for communication failure."""
-
-#-------------------------------------------------------------------------------
-
-def createSimulator(type, connectionListener):
-    """Create a simulator instance for the given simulator type with the given
-    connection listener.
-
-    The returned object should provide the following members:
-    FIXME: add info
-    """
-    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
-           "Only MS Flight Simulator 2004 and X are supported"
-    import fsuipc
-    return fsuipc.Simulator(connectionListener)
-
-#-------------------------------------------------------------------------------
-
-class AircraftState(object):
-    """Base class for the aircraft state produced by the aircraft model based
-    on readings from the simulator.
-
-    The following data members should be provided at least:
-    - timestamp: the simulator time of the measurement in seconds since the
-    epoch (float)
-    - paused: a boolean indicating if the flight simulator is paused for
-    whatever reason (it could be a pause mode, or a menu, a dialog, or a
-    replay, etc.)
-    - trickMode: a boolean indicating if some "trick" mode (e.g. "SLEW" in
-    MSFS) is activated
-    - overspeed: a boolean indicating if the aircraft is in overspeed
-    - stalled: a boolean indicating if the aircraft is stalled
-    - onTheGround: a boolean indicating if the aircraft is on the ground
-    - zfw: the zero-fuel weight in kilograms (float)
-    - grossWeight: the gross weight in kilograms (float)
-    - heading: the heading of the aircraft in degrees (float)
-    - pitch: the pitch of the aircraft in degrees. Positive means pitch down,
-    negative means pitch up (float)
-    - bank: the bank of the aircraft in degrees. Positive means bank left,
-    negative means bank right (float)
-    - ias: the indicated airspeed in knots (float)    
-    - mach: the airspeed in mach (float)    
-    - groundSpeed: the ground speed (float)
-    - vs: the vertical speed in feet/minutes (float)
-    - radioAltitude: the radio altitude of the aircraft in feet (float)
-    - altitude: the altitude of the aircraft in feet (float)
-    - gLoad: G-load (float)
-    - flapsSet: the selected degrees of the flaps (float)
-    - flaps: the actual degrees of the flaps (float)
-    - fuelWeight[]: the fuel weights in the different tanks in kgs (array of
-    floats of as many items as the number fuel tanks)
-    - n1[]: the N1 values of the turbine engines (array of floats
-    of as many items as the number of engines, present only for aircraft with
-    turbines)
-    - rpm[]: the RPM values of the piston engines (array of floats
-    of as many items as the number of engines, present only for aircraft with
-    pistons)
-    - reverser[]: an array of booleans indicating if the thrust reversers are
-    activated on any of the engines. The number of items equals to the number
-    of engines with a reverser.
-    - navLightsOn: a boolean indicating if the navigation lights are on
-    - antiCollisionLightsOn: a boolean indicating if the anti-collision lights are on
-    - strobeLightsOn: a boolean indicating if the strobe lights are on
-    - landingLightsOn: a boolean indicating if the landing lights are on
-    - pitotHeatOn: a boolean indicating if the pitot heat is on
-    - parking: a boolean indicating if the parking brake is set
-    - gearsDown: a boolean indicating if the gears are down
-    - spoilersArmed: a boolean indicating if the spoilers have been armed for
-    automatic deployment
-    - spoilersExtension: the percentage of how much the spoiler is extended
-    (float) 
-    - altimeter: the altimeter setting in hPa (float)
-    - nav1: the frequency of the NAV1 radio in MHz (string)
-    - nav2: the frequency of the NAV1 radio in MHz (string)
-    - squawk: the transponder code (string)
-    - windSpeed: the speed of the wind at the aircraft in knots (float)
-    - windDirection: the direction of the wind at the aircraft in degrees (float)
-
-    FIXME: needed when taxiing only:
-    - payload weight
-
-    FIXME: needed rarely:
-    - latitude, longitude
-    - transporter
-    - visibility 
-    """
-    
Index: src/fsuipc.py
===================================================================
--- src/fsuipc.py	(revision 23)
+++ src/fsuipc.py	(revision 23)
@@ -0,0 +1,1230 @@
+# Module handling the connection to FSUIPC
+
+#------------------------------------------------------------------------------
+
+import fs
+import const
+import util
+
+import threading
+import os
+import time
+import calendar
+import sys
+
+if os.name == "nt":
+    import pyuipc
+else:
+    import pyuipc_emu as pyuipc
+
+#------------------------------------------------------------------------------
+
+class Handler(threading.Thread):
+    """The thread to handle the FSUIPC requests."""
+    @staticmethod
+    def fsuipc2VS(data):
+        """Convert the given vertical speed data read from FSUIPC into feet/min."""
+        return data*60.0/const.FEETTOMETRES/256.0
+
+    @staticmethod
+    def fsuipc2radioAltitude(data):
+        """Convert the given radio altitude data read from FSUIPC into feet."""
+        return data/const.FEETTOMETRES/65536.0
+
+    @staticmethod
+    def fsuipc2Degrees(data):
+        """Convert the given data into degrees."""
+        return data * 360.0 / 65536.0 / 65536.0
+
+    @staticmethod
+    def fsuipc2PositiveDegrees(data):
+        """Convert the given data into positive degrees."""
+        degrees = Handler.fsuipc2Degrees(data)
+        if degrees<0.0: degrees += 360.0
+        return degrees
+
+    @staticmethod
+    def fsuipc2IAS(data):
+        """Convert the given data into indicated airspeed."""
+        return data / 128.0
+
+    @staticmethod
+    def _callSafe(fun):
+        """Call the given function and swallow any exceptions."""
+        try:
+            return fun()
+        except Exception, e:
+            print >> sys.stderr, str(e)
+            return None
+
+    NUM_READATTEMPTS = 3
+
+    @staticmethod
+    def _performRead(data, callback, extra, validator):
+        """Perform a read request.
+
+        If there is a validator, that will be called with the return values,
+        and if the values are wrong, the request is retried at most a certain
+        number of times.
+
+        Return True if the request has succeeded, False if validation has
+        failed during all attempts. An exception may also be thrown if there is
+        some lower-level communication problem."""
+        attemptsLeft = Handler.NUM_READATTEMPTS
+        while attemptsLeft>0:
+            values = pyuipc.read(data)
+            if validator is None or \
+               Handler._callSafe(lambda: validator(values, extra)):
+                Handler._callSafe(lambda: callback(values, extra))
+                return True
+            else:
+                attemptsLeft -= 1
+        return False
+
+    class Request(object):
+        """A simple, one-shot request."""
+        def __init__(self, forWrite, data, callback, extra, validator = None):
+            """Construct the request."""
+            self._forWrite = forWrite
+            self._data = data
+            self._callback = callback
+            self._extra = extra
+            self._validator = validator
+            
+        def process(self, time):
+            """Process the request.
+
+            Return True if the request has succeeded, False if data validation
+            has failed for a reading request. An exception may also be thrown
+            if there is some lower-level communication problem."""            
+            if self._forWrite:
+                pyuipc.write(self._data)
+                Handler._callSafe(lambda: self._callback(True, self._extra))
+                return True
+            else:
+                return Handler._performRead(self._data, self._callback,
+                                            self._extra, self._validator)
+
+        def fail(self):
+            """Handle the failure of this request."""
+            if self._forWrite:
+                Handler._callSafe(lambda: self._callback(False, self._extra))
+            else:
+                Handler._callSafe(lambda: self._callback(None, self._extra))
+
+    class PeriodicRequest(object):
+        """A periodic request."""
+        def __init__(self, id,  period, data, callback, extra, validator):
+            """Construct the periodic request."""
+            self._id = id
+            self._period = period
+            self._nextFire = time.time() + period
+            self._data = data
+            self._preparedData = None
+            self._callback = callback
+            self._extra = extra
+            self._validator = validator
+            
+        @property
+        def id(self):
+            """Get the ID of this periodic request."""
+            return self._id
+
+        @property
+        def nextFire(self):
+            """Get the next firing time."""
+            return self._nextFire
+
+        def process(self, time):
+            """Check if this request should be executed, and if so, do so.
+
+            time is the time at which the request is being executed. If this
+            function is called too early, nothing is done, and True is
+            returned.
+
+            Return True if the request has succeeded, False if data validation
+            has failed. An exception may also be thrown if there is some
+            lower-level communication problem."""
+            if time<self._nextFire:
+                return True
+            
+            if self._preparedData is None:
+                self._preparedData = pyuipc.prepare_data(self._data)
+                self._data = None
+
+            isOK = Handler._performRead(self._preparedData, self._callback,
+                                        self._extra, self._validator)
+
+            if isOK:
+                while self._nextFire <= time:
+                    self._nextFire += self._period
+            
+            return isOK
+
+        def fail(self):
+            """Handle the failure of this request."""
+            pass
+
+        def __cmp__(self, other):
+            """Compare two periodic requests. They are ordered by their next
+            firing times."""
+            return cmp(self._nextFire, other._nextFire)
+
+    def __init__(self, connectionListener):
+        """Construct the handler with the given connection listener."""
+        threading.Thread.__init__(self)
+
+        self._connectionListener = connectionListener
+
+        self._requestCondition = threading.Condition()
+        self._connectionRequested = False
+        self._connected = False
+
+        self._requests = []
+        self._nextPeriodicID = 1
+        self._periodicRequests = []
+
+        self.daemon = True
+
+    def requestRead(self, data, callback, extra = None, validator = None):
+        """Request the reading of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+
+        callback is a function that receives two pieces of data:
+        - the values retrieved or None on error
+        - the extra parameter
+
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            self._requests.append(Handler.Request(False, data, callback, extra,
+                                                  validator))
+            self._requestCondition.notify()
+
+    def requestWrite(self, data, callback, extra = None):
+        """Request the writing of some data.
+
+        data is a list of tuples of the following items:
+        - the offset of the data as an integer
+        - the type letter of the data as a string
+        - the data to write
+
+        callback is a function that receives two pieces of data:
+        - a boolean indicating if writing was successful
+        - the extra data
+        It will be called in the handler's thread!
+        """
+        with self._requestCondition:
+            self._requests.append(Handler.Request(True, data, callback, extra))
+            self._requestCondition.notify()
+
+    @staticmethod
+    def _readWriteCallback(data, extra):
+        """Callback for the read() and write() calls below."""
+        extra.append(data)
+        with extra[0] as condition:
+            condition.notify()
+
+    def requestPeriodicRead(self, period, data, callback, extra = None,
+                            validator = None):
+        """Request a periodic read of data.
+
+        period is a floating point number with the period in seconds.
+
+        This function returns an identifier which can be used to cancel the
+        request."""
+        with self._requestCondition:
+            id = self._nextPeriodicID
+            self._nextPeriodicID += 1
+            request = Handler.PeriodicRequest(id, period, data, callback,
+                                              extra, validator)
+            self._periodicRequests.append(request)
+            self._requestCondition.notify()
+            return id
+
+    def clearPeriodic(self, id):
+        """Clear the periodic request with the given ID."""
+        with self._requestCondition:
+            for i in range(0, len(self._periodicRequests)):
+                if self._periodicRequests[i].id==id:
+                    del self._periodicRequests[i]
+                    return True
+        return False
+
+    def connect(self):
+        """Initiate the connection to the flight simulator."""
+        with self._requestCondition:
+            if not self._connectionRequested:
+                self._connectionRequested = True
+                self._requestCondition.notify()
+        
+    def disconnect(self):
+        """Disconnect from the flight simulator."""
+        with self._requestCondition:
+            if self._connectionRequested:
+                self._connectionRequested = False
+                self._requestCondition.notify()
+
+    def run(self):
+        """Perform the operation of the thread."""
+        while True:
+            self._waitConnectionRequest()
+            
+            if self._connect():
+                self._handleConnection()
+
+            self._disconnect()
+            
+    def _waitConnectionRequest(self):
+        """Wait for a connection request to arrive."""
+        with self._requestCondition:
+            while not self._connectionRequested:
+                self._requestCondition.wait()
+            
+    def _connect(self):
+        """Try to connect to the flight simulator via FSUIPC
+
+        Returns True if the connection has been established, False if it was
+        not due to no longer requested.
+        """
+        while self._connectionRequested:
+            try:
+                pyuipc.open(pyuipc.SIM_ANY)
+                description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
+                    (pyuipc.fsuipc_version, pyuipc.lib_version, 
+                     pyuipc.fs_version)
+                Handler._callSafe(lambda:     
+                                  self._connectionListener.connected(const.SIM_MSFS9, 
+                                                                     description))
+                self._connected = True
+                return True
+            except Exception, e:
+                print "fsuipc.Handler._connect: connection failed: " + str(e)
+                time.sleep(0.1)
+
+        return False
+                
+    def _handleConnection(self):
+        """Handle a living connection."""
+        with self._requestCondition:
+            while self._connectionRequested:
+                self._processRequests()
+                self._waitRequest()
+
+    def _waitRequest(self):
+        """Wait for the time of the next request.
+
+        Returns also, if the connection is no longer requested.
+
+        Should be called with the request condition lock held."""
+        while self._connectionRequested:
+            timeout = None
+            if self._periodicRequests:
+                self._periodicRequests.sort()
+                timeout = self._periodicRequests[0].nextFire - time.time()
+
+            if timeout is not None and timeout <= 0.0:
+                return
+            
+            self._requestCondition.wait(timeout)
+                
+    def _disconnect(self):
+        """Disconnect from the flight simulator."""
+        if self._connected:
+            pyuipc.close()
+            Handler._callSafe(lambda: self._connectionListener.disconnected())
+            self._connected = False
+
+    def _failRequests(self, request):
+        """Fail the outstanding, single-shot requuests."""
+        request.fail()
+        with self._requestCondition:
+            for request in self._requests:
+                try:
+                    self._requestCondition.release()
+                    request.fail()
+                finally:
+                    self._requestCondition.acquire()
+            self._requests = []        
+
+    def _processRequest(self, request, time):
+        """Process the given request. 
+
+        If an exception occurs or invalid data is read too many times, we try
+        to reconnect.
+
+        This function returns only if the request has succeeded, or if a
+        connection is no longer requested.
+
+        This function is called with the request lock held, but is relased
+        whole processing the request and reconnecting."""
+        self._requestCondition.release()
+
+        needReconnect = False
+        try:
+            try:
+                if not request.process(time):
+                    print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
+                    needReconnect = True
+            except Exception as e:
+                print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
+                      str(e) + "), reconnecting."
+                needReconnect = True
+
+            if needReconnect:
+                self._disconnect()
+                self._failRequests(request)
+                self._connect()
+        finally:
+            self._requestCondition.acquire()
+        
+    def _processRequests(self):
+        """Process any pending requests.
+
+        Will be called with the request lock held."""
+        while self._connectionRequested and self._periodicRequests:
+            self._periodicRequests.sort()
+            request = self._periodicRequests[0]
+
+            t = time.time()
+
+            if request.nextFire>t:
+                break
+            
+            self._processRequest(request, t)
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            self._processRequest(request, None)
+
+        return self._connectionRequested
+
+#------------------------------------------------------------------------------
+
+class Simulator(object):
+    """The simulator class representing the interface to the flight simulator
+    via FSUIPC."""
+    # The basic data that should be queried all the time once we are connected
+    normalData = [ (0x0240, "H"),            # Year
+                   (0x023e, "H"),            # Number of day in year
+                   (0x023b, "b"),            # UTC hour
+                   (0x023c, "b"),            # UTC minute
+                   (0x023a, "b"),            # seconds
+                   (0x3d00, -256),           # The name of the current aircraft
+                   (0x3c00, -256) ]          # The path of the current AIR file
+
+    flareData1 = [ (0x023a, "b"),            # Seconds of time
+                   (0x31e4, "d"),            # Radio altitude
+                   (0x02c8, "d") ]           # Vertical speed
+                   
+    flareStartData = [ (0x0e90, "H"),        # Ambient wind speed
+                       (0x0e92, "H"),        # Ambient wind direction
+                       (0x0e8a, "H") ]       # Visibility
+                       
+    flareData2 = [ (0x023a, "b"),            # Seconds of time
+                   (0x0366, "H"),            # On the ground
+                   (0x02c8, "d"),            # Vertical speed
+                   (0x030c, "d"),            # Touch-down rate
+                   (0x02bc, "d"),            # IAS
+                   (0x0578, "d"),            # Pitch
+                   (0x057c, "d"),            # Bank
+                   (0x0580, "d") ]           # Heading
+
+    def __init__(self, connectionListener):
+        """Construct the simulator.
+        
+        The aircraft object passed must provide the following members:
+        - type: one of the AIRCRAFT_XXX constants from const.py
+        - modelChanged(aircraftName, modelName): called when the model handling
+        the aircraft has changed.
+        - handleState(aircraftState): handle the given state.
+        - flareStarted(windSpeed, windDirection, visibility, flareStart,
+                       flareStartFS): called when the flare has
+          started. windSpeed is in knots, windDirection is in degrees and
+          visibility is in metres. flareStart and flareStartFS are two time
+          values expressed in seconds that can be used to calculate the flare
+          time. 
+       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                       ias, pitch, bank, heading): called when the flare has
+         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
+         are the two time values corresponding to the touchdown time. tdRate is
+         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
+         from the simulator or was calculated by the adapter. The other data
+         are self-explanatory and expressed in their 'natural' units."""
+        self._aircraft = None
+
+        self._handler = Handler(connectionListener)
+        self._handler.start()
+
+        self._normalRequestID = None
+
+        self._monitoringRequested = False
+        self._monitoring = False
+
+        self._aircraftName = None
+        self._aircraftModel = None
+
+        self._flareRequestID = None
+        self._flareRates = []
+        self._flareStart = None
+        self._flareStartFS = None
+        
+    def connect(self, aircraft):
+        """Initiate a connection to the simulator."""
+        self._aircraft = aircraft
+        self._aircraftName = None
+        self._aircraftModel = None
+        self._handler.connect()
+        self._startDefaultNormal()
+                                                            
+    def startMonitoring(self):
+        """Start the periodic monitoring of the aircraft and pass the resulting
+        state to the aircraft object periodically."""
+        assert not self._monitoringRequested         
+        self._monitoringRequested = True
+
+    def stopMonitoring(self):
+        """Stop the periodic monitoring of the aircraft."""
+        assert self._monitoringRequested 
+        self._monitoringRequested = False
+
+    def startFlare(self):
+        """Start monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        #self._aircraft.logger.debug("startFlare")
+        if self._flareRequestID is None:
+            self._flareRates = []
+            self._flareRequestID = self._handler.requestPeriodicRead(0.1,
+                                                                     Simulator.flareData1,
+                                                                     self._handleFlare1)
+
+    def cancelFlare(self):
+        """Cancel monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        if self._flareRequestID is not None:
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+    def disconnect(self):
+        """Disconnect from the simulator."""
+        assert not self._monitoringRequested 
+        
+        self._stopNormal()
+        self._handler.disconnect()
+
+    def _startDefaultNormal(self):
+        """Start the default normal periodic request."""
+        assert self._normalRequestID is None
+        self._normalRequestID = \
+             self._handler.requestPeriodicRead(1.0,
+                                               Simulator.normalData,
+                                               self._handleNormal,
+                                               validator = self._validateNormal)
+
+    def _stopNormal(self):
+        """Stop the normal period request."""
+        assert self._normalRequestID is not None
+        self._handler.clearPeriodic(self._normalRequestID)
+        self._normalRequestID = None
+        self._monitoring = False
+
+    def _validateNormal(self, data, extra):
+        """Validate the normal data."""
+        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
+
+    def _handleNormal(self, data, extra):
+        """Handle the reply to the normal request.
+
+        At the beginning the result consists the data for normalData. When
+        monitoring is started, it contains the result also for the
+        aircraft-specific values.
+        """
+        timestamp = calendar.timegm(time.struct_time([data[0],
+                                                      1, 1, 0, 0, 0, -1, 1, 0]))
+        timestamp += data[1] * 24 * 3600
+        timestamp += data[2] * 3600
+        timestamp += data[3] * 60
+        timestamp += data[4]        
+
+        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
+        
+        if self._monitoringRequested and not self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+        elif self._monitoring and not self._monitoringRequested:
+            self._stopNormal()
+            self._startDefaultNormal()
+        elif self._monitoring and self._aircraftModel is not None and \
+             not createdNewModel:
+            aircraftState = self._aircraftModel.getAircraftState(self._aircraft, 
+                                                                 timestamp, data)
+            self._aircraft.handleState(aircraftState)
+
+    def _setAircraftName(self, timestamp, name, airPath):
+        """Set the name of the aicraft and if it is different from the
+        previous, create a new model for it.
+        
+        If so, also notifty the aircraft about the change.
+
+        Return if a new model was created."""
+        aircraftName = (name, airPath)
+        if aircraftName==self._aircraftName:
+            return False
+
+        self._aircraftName = aircraftName
+        needNew = self._aircraftModel is None
+        needNew = needNew or\
+            not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
+        if not needNew:
+            specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
+            needNew = specialModel is not None and \
+                specialModel is not self._aircraftModel.__class__
+
+        if needNew:
+            self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
+        
+        self._aircraft.modelChanged(timestamp, name, self._aircraftModel.name)        
+
+        return needNew
+
+    def _setAircraftModel(self, model):
+        """Set a new aircraft model.
+
+        It will be queried for the data to monitor and the monitoring request
+        will be replaced by a new one."""
+        self._aircraftModel = model
+        
+        if self._monitoring:
+            self._stopNormal()
+            self._startMonitoring()
+            
+    def _startMonitoring(self):
+        """Start monitoring with the current aircraft model."""
+        data = Simulator.normalData[:]
+        self._aircraftModel.addMonitoringData(data)
+        
+        self._normalRequestID = \
+            self._handler.requestPeriodicRead(1.0, data, 
+                                              self._handleNormal,
+                                              validator = self._validateNormal)
+        self._monitoring = True
+
+    def _addFlareRate(self, data):
+        """Append a flare rate to the list of last rates."""
+        if len(self._flareRates)>=3:
+            del self._flareRates[0]
+        self._flareRates.append(Handler.fsuipc2VS(data))
+
+    def _handleFlare1(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare1: " + str(data))
+        if Handler.fsuipc2radioAltitude(data[1])<=50.0:
+            self._flareStart = time.time()
+            self._flareStartFS = data[0]
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = \
+                self._handler.requestPeriodicRead(0.1, 
+                                                  Simulator.flareData2,
+                                                  self._handleFlare2)
+            self._handler.requestRead(Simulator.flareStartData,
+                                      self._handleFlareStart)
+                
+        self._addFlareRate(data[2])
+
+    def _handleFlareStart(self, data, extra):
+        """Handle the data need to notify the aircraft about the starting of
+        the flare."""
+        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
+        if data is not None:
+            windDirection = data[1]*360.0/65536.0
+            if windDirection<0.0: windDirection += 360.0
+            self._aircraft.flareStarted(data[0], windDirection,
+                                        data[2]*1609.344/100.0,
+                                        self._flareStart, self._flareStartFS)
+
+    def _handleFlare2(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare2: " + str(data))
+        if data[1]!=0:
+            flareEnd = time.time()
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+            flareEndFS = data[0]
+            if flareEndFS<self._flareStartFS:
+                flareEndFS += 60
+
+            tdRate = Handler.fsuipc2VS(data[3])
+            tdRateCalculatedByFS = True
+            if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
+                tdRate = min(self._flareRates)
+                tdRateCalculatedByFS = False
+
+            self._aircraft.flareFinished(flareEnd, flareEndFS,
+                                         tdRate, tdRateCalculatedByFS,
+                                         Handler.fsuipc2IAS(data[4]),
+                                         Handler.fsuipc2Degrees(data[5]),
+                                         Handler.fsuipc2Degrees(data[6]),
+                                         Handler.fsuipc2PositiveDegrees(data[7]))
+        else:
+            self._addFlareRate(data[2])
+                                                  
+#------------------------------------------------------------------------------
+
+class AircraftModel(object):
+    """Base class for the aircraft models.
+
+    Aircraft models handle the data arriving from FSUIPC and turn it into an
+    object describing the aircraft's state."""
+    monitoringData = [("paused", 0x0264, "H"),
+                      ("frozen", 0x3364, "H"),
+                      ("replay", 0x0628, "d"),
+                      ("slew", 0x05dc, "H"),
+                      ("overspeed", 0x036d, "b"),
+                      ("stalled", 0x036c, "b"),
+                      ("onTheGround", 0x0366, "H"),
+                      ("zfw", 0x3bfc, "d"),
+                      ("grossWeight", 0x30c0, "f"),
+                      ("heading", 0x0580, "d"),
+                      ("pitch", 0x0578, "d"),
+                      ("bank", 0x057c, "d"),
+                      ("ias", 0x02bc, "d"),
+                      ("mach", 0x11c6, "H"),
+                      ("groundSpeed", 0x02b4, "d"),
+                      ("vs", 0x02c8, "d"),
+                      ("radioAltitude", 0x31e4, "d"),
+                      ("altitude", 0x0570, "l"),
+                      ("gLoad", 0x11ba, "H"),
+                      ("flapsControl", 0x0bdc, "d"),
+                      ("flapsLeft", 0x0be0, "d"),
+                      ("flapsRight", 0x0be4, "d"),
+                      ("lights", 0x0d0c, "H"),
+                      ("pitot", 0x029c, "b"),
+                      ("parking", 0x0bc8, "H"),
+                      ("noseGear", 0x0bec, "d"),
+                      ("spoilersArmed", 0x0bcc, "d"),
+                      ("spoilers", 0x0bd0, "d"),
+                      ("altimeter", 0x0330, "H"),
+                      ("nav1", 0x0350, "H"),
+                      ("nav2", 0x0352, "H"),
+                      ("squawk", 0x0354, "H"),
+                      ("windSpeed", 0x0e90, "H"),
+                      ("windDirection", 0x0e92, "H")]
+
+
+    specialModels = []
+
+    @staticmethod
+    def registerSpecial(clazz):
+        """Register the given class as a special model."""
+        AircraftModel.specialModels.append(clazz)
+
+    @staticmethod
+    def findSpecial(aircraft, aircraftName):
+        for specialModel in AircraftModel.specialModels:
+            if specialModel.doesHandle(aircraft, aircraftName):
+                return specialModel
+        return None
+
+    @staticmethod
+    def create(aircraft, aircraftName):
+        """Create the model for the given aircraft name, and notify the
+        aircraft about it."""
+        specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
+        if specialModel is not None:
+            return specialModel()
+        if aircraft.type in _genericModels:
+            return _genericModels[aircraft.type]()
+        else:
+            return GenericModel()
+
+    @staticmethod
+    def convertBCD(data, length):
+        """Convert a data item encoded as BCD into a string of the given number
+        of digits."""
+        bcd = ""
+        for i in range(0, length):
+            digit = chr(ord('0') + (data&0x0f))
+            data >>= 4
+            bcd = digit + bcd
+        return bcd
+
+    @staticmethod
+    def convertFrequency(data):
+        """Convert the given frequency data to a string."""
+        bcd = AircraftModel.convertBCD(data, 4)
+        return "1" + bcd[0:2] + "." + bcd[2:4]
+
+    def __init__(self, flapsNotches):
+        """Construct the aircraft model.
+        
+        flapsNotches is a list of degrees of flaps that are available on the aircraft."""
+        self._flapsNotches = flapsNotches
+    
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic"
+    
+    def doesHandle(self, aircraft, aircraftName):
+        """Determine if the model handles the given aircraft name.
+        
+        This default implementation returns False."""
+        return False
+
+    def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
+        """Add the given FSUIPC offset and type to the given array and a member
+        attribute with the given name."""        
+        dest.append((offset, type))
+        if attrName is not None:
+            setattr(self, attrName, len(dest)-1)
+
+    def _addDataWithIndexMembers(self, dest, prefix, data):
+        """Add FSUIPC data to the given array and also corresponding index
+        member variables with the given prefix.
+
+        data is a list of triplets of the following items:
+        - the name of the data item. The index member variable will have a name
+        created by prepending the given prefix to this name.
+        - the FSUIPC offset
+        - the FSUIPC type
+        
+        The latter two items will be appended to dest."""
+        for (name, offset, type) in data:
+            self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
+            
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        self._addDataWithIndexMembers(data, "_monidx_",
+                                      AircraftModel.monitoringData)
+    
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data."""
+        state = fs.AircraftState()
+        
+        state.timestamp = timestamp
+        
+        state.paused = data[self._monidx_paused]!=0 or \
+            data[self._monidx_frozen]!=0 or \
+            data[self._monidx_replay]!=0
+        state.trickMode = data[self._monidx_slew]!=0
+
+        state.overspeed = data[self._monidx_overspeed]!=0
+        state.stalled = data[self._monidx_stalled]!=0
+        state.onTheGround = data[self._monidx_onTheGround]!=0
+
+        state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
+        state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
+        
+        state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
+
+        state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
+        state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
+
+        state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
+        state.mach = data[self._monidx_mach] / 20480.0
+        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
+        state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
+
+        state.radioAltitude = \
+            Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
+        state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
+
+        state.gLoad = data[self._monidx_gLoad] / 625.0
+        
+        numNotchesM1 = len(self._flapsNotches) - 1
+        flapsIncrement = 16383 / numNotchesM1
+        flapsControl = data[self._monidx_flapsControl]
+        flapsIndex = flapsControl / flapsIncrement
+        if flapsIndex < numNotchesM1:
+            if (flapsControl - (flapsIndex*flapsIncrement) >
+                (flapsIndex+1)*flapsIncrement - flapsControl):
+                flapsIndex += 1
+        state.flapsSet = self._flapsNotches[flapsIndex]
+        
+        flapsLeft = data[self._monidx_flapsLeft]
+        state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
+        
+        lights = data[self._monidx_lights]
+        
+        state.navLightsOn = (lights&0x01) != 0
+        state.antiCollisionLightsOn = (lights&0x02) != 0
+        state.landingLightsOn = (lights&0x04) != 0
+        state.strobeLightsOn = (lights&0x10) != 0
+        
+        state.pitotHeatOn = data[self._monidx_pitot]!=0
+
+        state.parking = data[self._monidx_parking]!=0
+
+        state.gearsDown = data[self._monidx_noseGear]==16383
+
+        state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
+        
+        spoilers = data[self._monidx_spoilers]
+        if spoilers<=4800:
+            state.spoilersExtension = 0.0
+        else:
+            state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
+
+        state.altimeter = data[self._monidx_altimeter] / 16.0
+           
+        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
+        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
+        state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
+
+        state.windSpeed = data[self._monidx_windSpeed]
+        state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
+        if state.windDirection<0.0: state.windDirection += 360.0
+        
+        return state
+
+#------------------------------------------------------------------------------
+
+class GenericAircraftModel(AircraftModel):
+    """A generic aircraft model that can handle the fuel levels, the N1 or RPM
+    values and some other common parameters in a generic way."""
+    def __init__(self, flapsNotches, fuelInfo, numEngines, isN1 = True):
+        """Construct the generic aircraft model with the given data.
+
+        flapsNotches is an array of how much degrees the individual flaps
+        notches mean.
+
+        fuelInfo is an array of FSUIPC offsets for the levels of the fuel
+        tanks. It is assumed to be a 4-byte value, followed by another 4-byte
+        value, which is the fuel tank capacity.
+
+        numEngines is the number of engines the aircraft has.
+
+        isN1 determines if the engines have an N1 value or an RPM value
+        (e.g. pistons)."""
+        super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
+
+        self._fuelInfo = fuelInfo
+        self._fuelStartIndex = None
+        self._numEngines = numEngines
+        self._engineStartIndex = None
+        self._isN1 = isN1
+
+    def doesHandle(self, aircraft, aircraftName):
+        """Determine if the model handles the given aircraft name.
+        
+        This implementation returns True."""
+        return True
+
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        super(GenericAircraftModel, self).addMonitoringData(data)
+        
+        self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
+
+        self._fuelStartIndex = len(data)
+        for offset in self._fuelInfo:
+            self._addOffsetWithIndexMember(data, offset, "u")    # tank level
+            self._addOffsetWithIndexMember(data, offset+4, "u")  # tank capacity
+
+        if self._isN1:
+            self._engineStartIndex = len(data)
+            for i in range(0, self._numEngines):
+                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
+                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
+        
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then add the data about the fuel levels and 
+        the engine parameters."""
+        state = super(GenericAircraftModel, self).getAircraftState(aircraft,
+                                                                   timestamp,
+                                                                   data)
+
+        fuelWeight = data[self._monidx_fuelWeight]/256.0
+        state.fuel = []
+        for i in range(self._fuelStartIndex, 
+                       self._fuelStartIndex + 2*len(self._fuelInfo), 2):
+            fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
+            state.fuel.append(fuel)
+
+        state.n1 = []
+        state.reverser = []
+        for i in range(self._engineStartIndex,
+                       self._engineStartIndex + 2*self._numEngines, 2):
+            state.n1.append(data[i])
+            state.reverser.append(data[i+1]<0)
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class GenericModel(GenericAircraftModel):
+    """Generic aircraft model for an unknown type."""
+    def __init__(self):
+        """Construct the model."""
+        super(GenericModel, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic"    
+
+#------------------------------------------------------------------------------
+
+class B737Model(GenericAircraftModel):
+    """Generic model for the Boeing 737 Classing and NG aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(B737Model, self). \
+            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Boeing 737"
+
+#------------------------------------------------------------------------------
+
+class PMDGBoeing737NGModel(B737Model):
+    """A model handler for the PMDG Boeing 737NG model."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type in [const.AIRCRAFT_B736,
+                                 const.AIRCRAFT_B737,
+                                 const.AIRCRAFT_B738] and \
+            (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
+            (name.find("737")!=-1 or airPath.find("737")!=-1) and \
+            (name.find("600")!=-1 or airPath.find("600")!=-1 or \
+             name.find("700")!=-1 or airPath.find("700")!=-1 or \
+             name.find("800")!=-1 or airPath.find("800")!=-1 or \
+             name.find("900")!=-1 or airPath.find("900")!=-1)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/PMDG Boeing 737NG"
+
+    def addMonitoringData(self, data):
+        """Add the model-specific monitoring data to the given array."""
+        super(PMDGBoeing737NGModel, self).addMonitoringData(data)
+                
+        self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then check some PMDG-specific stuff."""
+        state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
+                                                                   timestamp,
+                                                                   data)
+        if data[self._pmdgidx_switches]&0x01==0x01:
+            state.altimeter = 1013.25
+
+        return state
+
+#------------------------------------------------------------------------------
+
+class B767Model(GenericAircraftModel):
+    """Generic model for the Boeing 767 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(B767Model, self). \
+            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Boeing 767"
+
+#------------------------------------------------------------------------------
+
+class DH8DModel(GenericAircraftModel):
+    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(DH8DModel, self). \
+            __init__(flapsNotches = [0, 5, 10, 15, 35],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Bombardier Dash 8-Q400"
+
+#------------------------------------------------------------------------------
+
+class DreamwingsDH8DModel(DH8DModel):
+    """Model handler for the Dreamwings Dash 8-Q400."""
+    @staticmethod
+    def doesHandle(aircraft, (name, airPath)):
+        """Determine if this model handler handles the aircraft with the given
+        name."""
+        return aircraft.type==const.AIRCRAFT_DH8D and \
+            (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
+            (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
+            (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
+            airPath.find("Dash8Q400")!=-1
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get the aircraft state.
+
+        Get it from the parent, and then invert the pitot heat state."""
+        state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
+                                                                  timestamp,
+                                                                  data)
+        state.pitotHeatOn = not state.pitotHeatOn
+
+        return state
+#------------------------------------------------------------------------------
+
+class CRJ2Model(GenericAircraftModel):
+    """Generic model for the Bombardier CRJ-200 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(CRJ2Model, self). \
+            __init__(flapsNotches = [0, 8, 20, 30, 45],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Bombardier CRJ-200"
+
+#------------------------------------------------------------------------------
+
+class F70Model(GenericAircraftModel):
+    """Generic model for the Fokker F70 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(F70Model, self). \
+            __init__(flapsNotches = [0, 8, 15, 25, 42],
+                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Fokker 70"
+
+#------------------------------------------------------------------------------
+
+class DC3Model(GenericAircraftModel):
+    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(DC3Model, self). \
+            __init__(flapsNotches = [0, 15, 30, 45],
+                     fuelInfo = [0x0b7c, 0x0b84, 0x0b94, 0x0b9c], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Lisunov Li-2"
+
+#------------------------------------------------------------------------------
+
+class T134Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(T134Model, self). \
+            __init__(flapsNotches = [0, 10, 20, 30],
+                     fuelInfo = [0x0b74, 
+                                 0x0b8c, 0x0b84, 
+                                 0x0ba4, 0x0b9c,
+                                 0x1254, 0x125c], 
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Tupolev Tu-134"
+
+#------------------------------------------------------------------------------
+
+class T154Model(GenericAircraftModel):
+    """Generic model for the Tupolev Tu-134 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(T154Model, self). \
+            __init__(flapsNotches = [0, 15, 28, 45],
+                     fuelInfo = [0x0b74, 0x0b7c, 0x0b94, 
+                                 0x1244, 0x0b84, 0x0b9c],
+                     numEngines = 3)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Tupolev Tu-154"
+
+    def getAircraftState(self, aircraft, timestamp, data):
+        """Get an aircraft state object for the given monitoring data.
+
+        This removes the reverser value for the middle engine."""
+        state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
+        del state.reverser[1]
+        return state
+
+#------------------------------------------------------------------------------
+
+class YK40Model(GenericAircraftModel):
+    """Generic model for the Yakovlev Yak-40 aircraft."""
+    def __init__(self):
+        """Construct the model."""
+        super(YK40Model, self). \
+            __init__(flapsNotches = [0, 20, 35],
+                     fuelInfo = [0x0b7c, 0x0b94],
+                     numEngines = 2)
+
+    @property
+    def name(self):
+        """Get the name for this aircraft model."""
+        return "FSUIPC/Generic Yakovlev Yak-40"
+
+#------------------------------------------------------------------------------
+
+_genericModels = { const.AIRCRAFT_B736 : B737Model,
+                   const.AIRCRAFT_B737 : B737Model,
+                   const.AIRCRAFT_B738 : B737Model,
+                   const.AIRCRAFT_B733 : B737Model,
+                   const.AIRCRAFT_B734 : B737Model,
+                   const.AIRCRAFT_B735 : B737Model,
+                   const.AIRCRAFT_DH8D : DH8DModel,
+                   const.AIRCRAFT_B762 : B767Model,
+                   const.AIRCRAFT_B763 : B767Model,
+                   const.AIRCRAFT_CRJ2 : B767Model,
+                   const.AIRCRAFT_F70  : F70Model,
+                   const.AIRCRAFT_DC3  : DC3Model,
+                   const.AIRCRAFT_T134 : T134Model,
+                   const.AIRCRAFT_T154 : T154Model,
+                   const.AIRCRAFT_YK40 : YK40Model }
+
+#------------------------------------------------------------------------------
+
+AircraftModel.registerSpecial(PMDGBoeing737NGModel)
+AircraftModel.registerSpecial(DreamwingsDH8DModel)
+
+#------------------------------------------------------------------------------
Index: src/fsuipc.py
===================================================================
--- src/fsuipc.py	(revision 23)
+++ 	(revision )
@@ -1,1230 +1,0 @@
-# Module handling the connection to FSUIPC
-
-#------------------------------------------------------------------------------
-
-import fs
-import const
-import util
-
-import threading
-import os
-import time
-import calendar
-import sys
-
-if os.name == "nt":
-    import pyuipc
-else:
-    import pyuipc_emu as pyuipc
-
-#------------------------------------------------------------------------------
-
-class Handler(threading.Thread):
-    """The thread to handle the FSUIPC requests."""
-    @staticmethod
-    def fsuipc2VS(data):
-        """Convert the given vertical speed data read from FSUIPC into feet/min."""
-        return data*60.0/const.FEETTOMETRES/256.0
-
-    @staticmethod
-    def fsuipc2radioAltitude(data):
-        """Convert the given radio altitude data read from FSUIPC into feet."""
-        return data/const.FEETTOMETRES/65536.0
-
-    @staticmethod
-    def fsuipc2Degrees(data):
-        """Convert the given data into degrees."""
-        return data * 360.0 / 65536.0 / 65536.0
-
-    @staticmethod
-    def fsuipc2PositiveDegrees(data):
-        """Convert the given data into positive degrees."""
-        degrees = Handler.fsuipc2Degrees(data)
-        if degrees<0.0: degrees += 360.0
-        return degrees
-
-    @staticmethod
-    def fsuipc2IAS(data):
-        """Convert the given data into indicated airspeed."""
-        return data / 128.0
-
-    @staticmethod
-    def _callSafe(fun):
-        """Call the given function and swallow any exceptions."""
-        try:
-            return fun()
-        except Exception, e:
-            print >> sys.stderr, str(e)
-            return None
-
-    NUM_READATTEMPTS = 3
-
-    @staticmethod
-    def _performRead(data, callback, extra, validator):
-        """Perform a read request.
-
-        If there is a validator, that will be called with the return values,
-        and if the values are wrong, the request is retried at most a certain
-        number of times.
-
-        Return True if the request has succeeded, False if validation has
-        failed during all attempts. An exception may also be thrown if there is
-        some lower-level communication problem."""
-        attemptsLeft = Handler.NUM_READATTEMPTS
-        while attemptsLeft>0:
-            values = pyuipc.read(data)
-            if validator is None or \
-               Handler._callSafe(lambda: validator(values, extra)):
-                Handler._callSafe(lambda: callback(values, extra))
-                return True
-            else:
-                attemptsLeft -= 1
-        return False
-
-    class Request(object):
-        """A simple, one-shot request."""
-        def __init__(self, forWrite, data, callback, extra, validator = None):
-            """Construct the request."""
-            self._forWrite = forWrite
-            self._data = data
-            self._callback = callback
-            self._extra = extra
-            self._validator = validator
-            
-        def process(self, time):
-            """Process the request.
-
-            Return True if the request has succeeded, False if data validation
-            has failed for a reading request. An exception may also be thrown
-            if there is some lower-level communication problem."""            
-            if self._forWrite:
-                pyuipc.write(self._data)
-                Handler._callSafe(lambda: self._callback(True, self._extra))
-                return True
-            else:
-                return Handler._performRead(self._data, self._callback,
-                                            self._extra, self._validator)
-
-        def fail(self):
-            """Handle the failure of this request."""
-            if self._forWrite:
-                Handler._callSafe(lambda: self._callback(False, self._extra))
-            else:
-                Handler._callSafe(lambda: self._callback(None, self._extra))
-
-    class PeriodicRequest(object):
-        """A periodic request."""
-        def __init__(self, id,  period, data, callback, extra, validator):
-            """Construct the periodic request."""
-            self._id = id
-            self._period = period
-            self._nextFire = time.time() + period
-            self._data = data
-            self._preparedData = None
-            self._callback = callback
-            self._extra = extra
-            self._validator = validator
-            
-        @property
-        def id(self):
-            """Get the ID of this periodic request."""
-            return self._id
-
-        @property
-        def nextFire(self):
-            """Get the next firing time."""
-            return self._nextFire
-
-        def process(self, time):
-            """Check if this request should be executed, and if so, do so.
-
-            time is the time at which the request is being executed. If this
-            function is called too early, nothing is done, and True is
-            returned.
-
-            Return True if the request has succeeded, False if data validation
-            has failed. An exception may also be thrown if there is some
-            lower-level communication problem."""
-            if time<self._nextFire:
-                return True
-            
-            if self._preparedData is None:
-                self._preparedData = pyuipc.prepare_data(self._data)
-                self._data = None
-
-            isOK = Handler._performRead(self._preparedData, self._callback,
-                                        self._extra, self._validator)
-
-            if isOK:
-                while self._nextFire <= time:
-                    self._nextFire += self._period
-            
-            return isOK
-
-        def fail(self):
-            """Handle the failure of this request."""
-            pass
-
-        def __cmp__(self, other):
-            """Compare two periodic requests. They are ordered by their next
-            firing times."""
-            return cmp(self._nextFire, other._nextFire)
-
-    def __init__(self, connectionListener):
-        """Construct the handler with the given connection listener."""
-        threading.Thread.__init__(self)
-
-        self._connectionListener = connectionListener
-
-        self._requestCondition = threading.Condition()
-        self._connectionRequested = False
-        self._connected = False
-
-        self._requests = []
-        self._nextPeriodicID = 1
-        self._periodicRequests = []
-
-        self.daemon = True
-
-    def requestRead(self, data, callback, extra = None, validator = None):
-        """Request the reading of some data.
-
-        data is a list of tuples of the following items:
-        - the offset of the data as an integer
-        - the type letter of the data as a string
-
-        callback is a function that receives two pieces of data:
-        - the values retrieved or None on error
-        - the extra parameter
-
-        It will be called in the handler's thread!
-        """
-        with self._requestCondition:
-            self._requests.append(Handler.Request(False, data, callback, extra,
-                                                  validator))
-            self._requestCondition.notify()
-
-    def requestWrite(self, data, callback, extra = None):
-        """Request the writing of some data.
-
-        data is a list of tuples of the following items:
-        - the offset of the data as an integer
-        - the type letter of the data as a string
-        - the data to write
-
-        callback is a function that receives two pieces of data:
-        - a boolean indicating if writing was successful
-        - the extra data
-        It will be called in the handler's thread!
-        """
-        with self._requestCondition:
-            self._requests.append(Handler.Request(True, data, callback, extra))
-            self._requestCondition.notify()
-
-    @staticmethod
-    def _readWriteCallback(data, extra):
-        """Callback for the read() and write() calls below."""
-        extra.append(data)
-        with extra[0] as condition:
-            condition.notify()
-
-    def requestPeriodicRead(self, period, data, callback, extra = None,
-                            validator = None):
-        """Request a periodic read of data.
-
-        period is a floating point number with the period in seconds.
-
-        This function returns an identifier which can be used to cancel the
-        request."""
-        with self._requestCondition:
-            id = self._nextPeriodicID
-            self._nextPeriodicID += 1
-            request = Handler.PeriodicRequest(id, period, data, callback,
-                                              extra, validator)
-            self._periodicRequests.append(request)
-            self._requestCondition.notify()
-            return id
-
-    def clearPeriodic(self, id):
-        """Clear the periodic request with the given ID."""
-        with self._requestCondition:
-            for i in range(0, len(self._periodicRequests)):
-                if self._periodicRequests[i].id==id:
-                    del self._periodicRequests[i]
-                    return True
-        return False
-
-    def connect(self):
-        """Initiate the connection to the flight simulator."""
-        with self._requestCondition:
-            if not self._connectionRequested:
-                self._connectionRequested = True
-                self._requestCondition.notify()
-        
-    def disconnect(self):
-        """Disconnect from the flight simulator."""
-        with self._requestCondition:
-            if self._connectionRequested:
-                self._connectionRequested = False
-                self._requestCondition.notify()
-
-    def run(self):
-        """Perform the operation of the thread."""
-        while True:
-            self._waitConnectionRequest()
-            
-            if self._connect():
-                self._handleConnection()
-
-            self._disconnect()
-            
-    def _waitConnectionRequest(self):
-        """Wait for a connection request to arrive."""
-        with self._requestCondition:
-            while not self._connectionRequested:
-                self._requestCondition.wait()
-            
-    def _connect(self):
-        """Try to connect to the flight simulator via FSUIPC
-
-        Returns True if the connection has been established, False if it was
-        not due to no longer requested.
-        """
-        while self._connectionRequested:
-            try:
-                pyuipc.open(pyuipc.SIM_ANY)
-                description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
-                    (pyuipc.fsuipc_version, pyuipc.lib_version, 
-                     pyuipc.fs_version)
-                Handler._callSafe(lambda:     
-                                  self._connectionListener.connected(const.SIM_MSFS9, 
-                                                                     description))
-                self._connected = True
-                return True
-            except Exception, e:
-                print "fsuipc.Handler._connect: connection failed: " + str(e)
-                time.sleep(0.1)
-
-        return False
-                
-    def _handleConnection(self):
-        """Handle a living connection."""
-        with self._requestCondition:
-            while self._connectionRequested:
-                self._processRequests()
-                self._waitRequest()
-
-    def _waitRequest(self):
-        """Wait for the time of the next request.
-
-        Returns also, if the connection is no longer requested.
-
-        Should be called with the request condition lock held."""
-        while self._connectionRequested:
-            timeout = None
-            if self._periodicRequests:
-                self._periodicRequests.sort()
-                timeout = self._periodicRequests[0].nextFire - time.time()
-
-            if timeout is not None and timeout <= 0.0:
-                return
-            
-            self._requestCondition.wait(timeout)
-                
-    def _disconnect(self):
-        """Disconnect from the flight simulator."""
-        if self._connected:
-            pyuipc.close()
-            Handler._callSafe(lambda: self._connectionListener.disconnected())
-            self._connected = False
-
-    def _failRequests(self, request):
-        """Fail the outstanding, single-shot requuests."""
-        request.fail()
-        with self._requestCondition:
-            for request in self._requests:
-                try:
-                    self._requestCondition.release()
-                    request.fail()
-                finally:
-                    self._requestCondition.acquire()
-            self._requests = []        
-
-    def _processRequest(self, request, time):
-        """Process the given request. 
-
-        If an exception occurs or invalid data is read too many times, we try
-        to reconnect.
-
-        This function returns only if the request has succeeded, or if a
-        connection is no longer requested.
-
-        This function is called with the request lock held, but is relased
-        whole processing the request and reconnecting."""
-        self._requestCondition.release()
-
-        needReconnect = False
-        try:
-            try:
-                if not request.process(time):
-                    print "fsuipc.Handler._processRequest: FSUIPC returned invalid data too many times, reconnecting"
-                    needReconnect = True
-            except Exception as e:
-                print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
-                      str(e) + "), reconnecting."
-                needReconnect = True
-
-            if needReconnect:
-                self._disconnect()
-                self._failRequests(request)
-                self._connect()
-        finally:
-            self._requestCondition.acquire()
-        
-    def _processRequests(self):
-        """Process any pending requests.
-
-        Will be called with the request lock held."""
-        while self._connectionRequested and self._periodicRequests:
-            self._periodicRequests.sort()
-            request = self._periodicRequests[0]
-
-            t = time.time()
-
-            if request.nextFire>t:
-                break
-            
-            self._processRequest(request, t)
-
-        while self._connectionRequested and self._requests:
-            request = self._requests[0]
-            del self._requests[0]
-
-            self._processRequest(request, None)
-
-        return self._connectionRequested
-
-#------------------------------------------------------------------------------
-
-class Simulator(object):
-    """The simulator class representing the interface to the flight simulator
-    via FSUIPC."""
-    # The basic data that should be queried all the time once we are connected
-    normalData = [ (0x0240, "H"),            # Year
-                   (0x023e, "H"),            # Number of day in year
-                   (0x023b, "b"),            # UTC hour
-                   (0x023c, "b"),            # UTC minute
-                   (0x023a, "b"),            # seconds
-                   (0x3d00, -256),           # The name of the current aircraft
-                   (0x3c00, -256) ]          # The path of the current AIR file
-
-    flareData1 = [ (0x023a, "b"),            # Seconds of time
-                   (0x31e4, "d"),            # Radio altitude
-                   (0x02c8, "d") ]           # Vertical speed
-                   
-    flareStartData = [ (0x0e90, "H"),        # Ambient wind speed
-                       (0x0e92, "H"),        # Ambient wind direction
-                       (0x0e8a, "H") ]       # Visibility
-                       
-    flareData2 = [ (0x023a, "b"),            # Seconds of time
-                   (0x0366, "H"),            # On the ground
-                   (0x02c8, "d"),            # Vertical speed
-                   (0x030c, "d"),            # Touch-down rate
-                   (0x02bc, "d"),            # IAS
-                   (0x0578, "d"),            # Pitch
-                   (0x057c, "d"),            # Bank
-                   (0x0580, "d") ]           # Heading
-
-    def __init__(self, connectionListener):
-        """Construct the simulator.
-        
-        The aircraft object passed must provide the following members:
-        - type: one of the AIRCRAFT_XXX constants from const.py
-        - modelChanged(aircraftName, modelName): called when the model handling
-        the aircraft has changed.
-        - handleState(aircraftState): handle the given state.
-        - flareStarted(windSpeed, windDirection, visibility, flareStart,
-                       flareStartFS): called when the flare has
-          started. windSpeed is in knots, windDirection is in degrees and
-          visibility is in metres. flareStart and flareStartFS are two time
-          values expressed in seconds that can be used to calculate the flare
-          time. 
-       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
-                       ias, pitch, bank, heading): called when the flare has
-         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
-         are the two time values corresponding to the touchdown time. tdRate is
-         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
-         from the simulator or was calculated by the adapter. The other data
-         are self-explanatory and expressed in their 'natural' units."""
-        self._aircraft = None
-
-        self._handler = Handler(connectionListener)
-        self._handler.start()
-
-        self._normalRequestID = None
-
-        self._monitoringRequested = False
-        self._monitoring = False
-
-        self._aircraftName = None
-        self._aircraftModel = None
-
-        self._flareRequestID = None
-        self._flareRates = []
-        self._flareStart = None
-        self._flareStartFS = None
-        
-    def connect(self, aircraft):
-        """Initiate a connection to the simulator."""
-        self._aircraft = aircraft
-        self._aircraftName = None
-        self._aircraftModel = None
-        self._handler.connect()
-        self._startDefaultNormal()
-                                                            
-    def startMonitoring(self):
-        """Start the periodic monitoring of the aircraft and pass the resulting
-        state to the aircraft object periodically."""
-        assert not self._monitoringRequested         
-        self._monitoringRequested = True
-
-    def stopMonitoring(self):
-        """Stop the periodic monitoring of the aircraft."""
-        assert self._monitoringRequested 
-        self._monitoringRequested = False
-
-    def startFlare(self):
-        """Start monitoring the flare time.
-
-        At present it is assumed to be called from the FSUIPC thread, hence no
-        protection."""
-        #self._aircraft.logger.debug("startFlare")
-        if self._flareRequestID is None:
-            self._flareRates = []
-            self._flareRequestID = self._handler.requestPeriodicRead(0.1,
-                                                                     Simulator.flareData1,
-                                                                     self._handleFlare1)
-
-    def cancelFlare(self):
-        """Cancel monitoring the flare time.
-
-        At present it is assumed to be called from the FSUIPC thread, hence no
-        protection."""
-        if self._flareRequestID is not None:
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = None
-
-    def disconnect(self):
-        """Disconnect from the simulator."""
-        assert not self._monitoringRequested 
-        
-        self._stopNormal()
-        self._handler.disconnect()
-
-    def _startDefaultNormal(self):
-        """Start the default normal periodic request."""
-        assert self._normalRequestID is None
-        self._normalRequestID = \
-             self._handler.requestPeriodicRead(1.0,
-                                               Simulator.normalData,
-                                               self._handleNormal,
-                                               validator = self._validateNormal)
-
-    def _stopNormal(self):
-        """Stop the normal period request."""
-        assert self._normalRequestID is not None
-        self._handler.clearPeriodic(self._normalRequestID)
-        self._normalRequestID = None
-        self._monitoring = False
-
-    def _validateNormal(self, data, extra):
-        """Validate the normal data."""
-        return data[0]!=0 and data[1]!=0 and len(data[5])>0 and len(data[6])>0
-
-    def _handleNormal(self, data, extra):
-        """Handle the reply to the normal request.
-
-        At the beginning the result consists the data for normalData. When
-        monitoring is started, it contains the result also for the
-        aircraft-specific values.
-        """
-        timestamp = calendar.timegm(time.struct_time([data[0],
-                                                      1, 1, 0, 0, 0, -1, 1, 0]))
-        timestamp += data[1] * 24 * 3600
-        timestamp += data[2] * 3600
-        timestamp += data[3] * 60
-        timestamp += data[4]        
-
-        createdNewModel = self._setAircraftName(timestamp, data[5], data[6])
-        
-        if self._monitoringRequested and not self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-        elif self._monitoring and not self._monitoringRequested:
-            self._stopNormal()
-            self._startDefaultNormal()
-        elif self._monitoring and self._aircraftModel is not None and \
-             not createdNewModel:
-            aircraftState = self._aircraftModel.getAircraftState(self._aircraft, 
-                                                                 timestamp, data)
-            self._aircraft.handleState(aircraftState)
-
-    def _setAircraftName(self, timestamp, name, airPath):
-        """Set the name of the aicraft and if it is different from the
-        previous, create a new model for it.
-        
-        If so, also notifty the aircraft about the change.
-
-        Return if a new model was created."""
-        aircraftName = (name, airPath)
-        if aircraftName==self._aircraftName:
-            return False
-
-        self._aircraftName = aircraftName
-        needNew = self._aircraftModel is None
-        needNew = needNew or\
-            not self._aircraftModel.doesHandle(self._aircraft, aircraftName)
-        if not needNew:
-            specialModel = AircraftModel.findSpecial(self._aircraft, aircraftName)
-            needNew = specialModel is not None and \
-                specialModel is not self._aircraftModel.__class__
-
-        if needNew:
-            self._setAircraftModel(AircraftModel.create(self._aircraft, aircraftName))
-        
-        self._aircraft.modelChanged(timestamp, name, self._aircraftModel.name)        
-
-        return needNew
-
-    def _setAircraftModel(self, model):
-        """Set a new aircraft model.
-
-        It will be queried for the data to monitor and the monitoring request
-        will be replaced by a new one."""
-        self._aircraftModel = model
-        
-        if self._monitoring:
-            self._stopNormal()
-            self._startMonitoring()
-            
-    def _startMonitoring(self):
-        """Start monitoring with the current aircraft model."""
-        data = Simulator.normalData[:]
-        self._aircraftModel.addMonitoringData(data)
-        
-        self._normalRequestID = \
-            self._handler.requestPeriodicRead(1.0, data, 
-                                              self._handleNormal,
-                                              validator = self._validateNormal)
-        self._monitoring = True
-
-    def _addFlareRate(self, data):
-        """Append a flare rate to the list of last rates."""
-        if len(self._flareRates)>=3:
-            del self._flareRates[0]
-        self._flareRates.append(Handler.fsuipc2VS(data))
-
-    def _handleFlare1(self, data, normal):
-        """Handle the first stage of flare monitoring."""
-        #self._aircraft.logger.debug("handleFlare1: " + str(data))
-        if Handler.fsuipc2radioAltitude(data[1])<=50.0:
-            self._flareStart = time.time()
-            self._flareStartFS = data[0]
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = \
-                self._handler.requestPeriodicRead(0.1, 
-                                                  Simulator.flareData2,
-                                                  self._handleFlare2)
-            self._handler.requestRead(Simulator.flareStartData,
-                                      self._handleFlareStart)
-                
-        self._addFlareRate(data[2])
-
-    def _handleFlareStart(self, data, extra):
-        """Handle the data need to notify the aircraft about the starting of
-        the flare."""
-        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
-        if data is not None:
-            windDirection = data[1]*360.0/65536.0
-            if windDirection<0.0: windDirection += 360.0
-            self._aircraft.flareStarted(data[0], windDirection,
-                                        data[2]*1609.344/100.0,
-                                        self._flareStart, self._flareStartFS)
-
-    def _handleFlare2(self, data, normal):
-        """Handle the first stage of flare monitoring."""
-        #self._aircraft.logger.debug("handleFlare2: " + str(data))
-        if data[1]!=0:
-            flareEnd = time.time()
-            self._handler.clearPeriodic(self._flareRequestID)
-            self._flareRequestID = None
-
-            flareEndFS = data[0]
-            if flareEndFS<self._flareStartFS:
-                flareEndFS += 60
-
-            tdRate = Handler.fsuipc2VS(data[3])
-            tdRateCalculatedByFS = True
-            if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
-                tdRate = min(self._flareRates)
-                tdRateCalculatedByFS = False
-
-            self._aircraft.flareFinished(flareEnd, flareEndFS,
-                                         tdRate, tdRateCalculatedByFS,
-                                         Handler.fsuipc2IAS(data[4]),
-                                         Handler.fsuipc2Degrees(data[5]),
-                                         Handler.fsuipc2Degrees(data[6]),
-                                         Handler.fsuipc2PositiveDegrees(data[7]))
-        else:
-            self._addFlareRate(data[2])
-                                                  
-#------------------------------------------------------------------------------
-
-class AircraftModel(object):
-    """Base class for the aircraft models.
-
-    Aircraft models handle the data arriving from FSUIPC and turn it into an
-    object describing the aircraft's state."""
-    monitoringData = [("paused", 0x0264, "H"),
-                      ("frozen", 0x3364, "H"),
-                      ("replay", 0x0628, "d"),
-                      ("slew", 0x05dc, "H"),
-                      ("overspeed", 0x036d, "b"),
-                      ("stalled", 0x036c, "b"),
-                      ("onTheGround", 0x0366, "H"),
-                      ("zfw", 0x3bfc, "d"),
-                      ("grossWeight", 0x30c0, "f"),
-                      ("heading", 0x0580, "d"),
-                      ("pitch", 0x0578, "d"),
-                      ("bank", 0x057c, "d"),
-                      ("ias", 0x02bc, "d"),
-                      ("mach", 0x11c6, "H"),
-                      ("groundSpeed", 0x02b4, "d"),
-                      ("vs", 0x02c8, "d"),
-                      ("radioAltitude", 0x31e4, "d"),
-                      ("altitude", 0x0570, "l"),
-                      ("gLoad", 0x11ba, "H"),
-                      ("flapsControl", 0x0bdc, "d"),
-                      ("flapsLeft", 0x0be0, "d"),
-                      ("flapsRight", 0x0be4, "d"),
-                      ("lights", 0x0d0c, "H"),
-                      ("pitot", 0x029c, "b"),
-                      ("parking", 0x0bc8, "H"),
-                      ("noseGear", 0x0bec, "d"),
-                      ("spoilersArmed", 0x0bcc, "d"),
-                      ("spoilers", 0x0bd0, "d"),
-                      ("altimeter", 0x0330, "H"),
-                      ("nav1", 0x0350, "H"),
-                      ("nav2", 0x0352, "H"),
-                      ("squawk", 0x0354, "H"),
-                      ("windSpeed", 0x0e90, "H"),
-                      ("windDirection", 0x0e92, "H")]
-
-
-    specialModels = []
-
-    @staticmethod
-    def registerSpecial(clazz):
-        """Register the given class as a special model."""
-        AircraftModel.specialModels.append(clazz)
-
-    @staticmethod
-    def findSpecial(aircraft, aircraftName):
-        for specialModel in AircraftModel.specialModels:
-            if specialModel.doesHandle(aircraft, aircraftName):
-                return specialModel
-        return None
-
-    @staticmethod
-    def create(aircraft, aircraftName):
-        """Create the model for the given aircraft name, and notify the
-        aircraft about it."""
-        specialModel = AircraftModel.findSpecial(aircraft, aircraftName)
-        if specialModel is not None:
-            return specialModel()
-        if aircraft.type in _genericModels:
-            return _genericModels[aircraft.type]()
-        else:
-            return GenericModel()
-
-    @staticmethod
-    def convertBCD(data, length):
-        """Convert a data item encoded as BCD into a string of the given number
-        of digits."""
-        bcd = ""
-        for i in range(0, length):
-            digit = chr(ord('0') + (data&0x0f))
-            data >>= 4
-            bcd = digit + bcd
-        return bcd
-
-    @staticmethod
-    def convertFrequency(data):
-        """Convert the given frequency data to a string."""
-        bcd = AircraftModel.convertBCD(data, 4)
-        return "1" + bcd[0:2] + "." + bcd[2:4]
-
-    def __init__(self, flapsNotches):
-        """Construct the aircraft model.
-        
-        flapsNotches is a list of degrees of flaps that are available on the aircraft."""
-        self._flapsNotches = flapsNotches
-    
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic"
-    
-    def doesHandle(self, aircraft, aircraftName):
-        """Determine if the model handles the given aircraft name.
-        
-        This default implementation returns False."""
-        return False
-
-    def _addOffsetWithIndexMember(self, dest, offset, type, attrName = None):
-        """Add the given FSUIPC offset and type to the given array and a member
-        attribute with the given name."""        
-        dest.append((offset, type))
-        if attrName is not None:
-            setattr(self, attrName, len(dest)-1)
-
-    def _addDataWithIndexMembers(self, dest, prefix, data):
-        """Add FSUIPC data to the given array and also corresponding index
-        member variables with the given prefix.
-
-        data is a list of triplets of the following items:
-        - the name of the data item. The index member variable will have a name
-        created by prepending the given prefix to this name.
-        - the FSUIPC offset
-        - the FSUIPC type
-        
-        The latter two items will be appended to dest."""
-        for (name, offset, type) in data:
-            self._addOffsetWithIndexMember(dest, offset, type, prefix + name)
-            
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        self._addDataWithIndexMembers(data, "_monidx_",
-                                      AircraftModel.monitoringData)
-    
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get an aircraft state object for the given monitoring data."""
-        state = fs.AircraftState()
-        
-        state.timestamp = timestamp
-        
-        state.paused = data[self._monidx_paused]!=0 or \
-            data[self._monidx_frozen]!=0 or \
-            data[self._monidx_replay]!=0
-        state.trickMode = data[self._monidx_slew]!=0
-
-        state.overspeed = data[self._monidx_overspeed]!=0
-        state.stalled = data[self._monidx_stalled]!=0
-        state.onTheGround = data[self._monidx_onTheGround]!=0
-
-        state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
-        state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
-        
-        state.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
-
-        state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
-        state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
-
-        state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
-        state.mach = data[self._monidx_mach] / 20480.0
-        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
-        state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
-
-        state.radioAltitude = \
-            Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
-        state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
-
-        state.gLoad = data[self._monidx_gLoad] / 625.0
-        
-        numNotchesM1 = len(self._flapsNotches) - 1
-        flapsIncrement = 16383 / numNotchesM1
-        flapsControl = data[self._monidx_flapsControl]
-        flapsIndex = flapsControl / flapsIncrement
-        if flapsIndex < numNotchesM1:
-            if (flapsControl - (flapsIndex*flapsIncrement) >
-                (flapsIndex+1)*flapsIncrement - flapsControl):
-                flapsIndex += 1
-        state.flapsSet = self._flapsNotches[flapsIndex]
-        
-        flapsLeft = data[self._monidx_flapsLeft]
-        state.flaps = self._flapsNotches[-1]*flapsLeft/16383.0
-        
-        lights = data[self._monidx_lights]
-        
-        state.navLightsOn = (lights&0x01) != 0
-        state.antiCollisionLightsOn = (lights&0x02) != 0
-        state.landingLightsOn = (lights&0x04) != 0
-        state.strobeLightsOn = (lights&0x10) != 0
-        
-        state.pitotHeatOn = data[self._monidx_pitot]!=0
-
-        state.parking = data[self._monidx_parking]!=0
-
-        state.gearsDown = data[self._monidx_noseGear]==16383
-
-        state.spoilersArmed = data[self._monidx_spoilersArmed]!=0
-        
-        spoilers = data[self._monidx_spoilers]
-        if spoilers<=4800:
-            state.spoilersExtension = 0.0
-        else:
-            state.spoilersExtension = (spoilers - 4800) * 100.0 / (16383 - 4800)
-
-        state.altimeter = data[self._monidx_altimeter] / 16.0
-           
-        state.nav1 = AircraftModel.convertFrequency(data[self._monidx_nav1])
-        state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
-        state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
-
-        state.windSpeed = data[self._monidx_windSpeed]
-        state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
-        if state.windDirection<0.0: state.windDirection += 360.0
-        
-        return state
-
-#------------------------------------------------------------------------------
-
-class GenericAircraftModel(AircraftModel):
-    """A generic aircraft model that can handle the fuel levels, the N1 or RPM
-    values and some other common parameters in a generic way."""
-    def __init__(self, flapsNotches, fuelInfo, numEngines, isN1 = True):
-        """Construct the generic aircraft model with the given data.
-
-        flapsNotches is an array of how much degrees the individual flaps
-        notches mean.
-
-        fuelInfo is an array of FSUIPC offsets for the levels of the fuel
-        tanks. It is assumed to be a 4-byte value, followed by another 4-byte
-        value, which is the fuel tank capacity.
-
-        numEngines is the number of engines the aircraft has.
-
-        isN1 determines if the engines have an N1 value or an RPM value
-        (e.g. pistons)."""
-        super(GenericAircraftModel, self).__init__(flapsNotches = flapsNotches)
-
-        self._fuelInfo = fuelInfo
-        self._fuelStartIndex = None
-        self._numEngines = numEngines
-        self._engineStartIndex = None
-        self._isN1 = isN1
-
-    def doesHandle(self, aircraft, aircraftName):
-        """Determine if the model handles the given aircraft name.
-        
-        This implementation returns True."""
-        return True
-
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        super(GenericAircraftModel, self).addMonitoringData(data)
-        
-        self._addOffsetWithIndexMember(data, 0x0af4, "H", "_monidx_fuelWeight")
-
-        self._fuelStartIndex = len(data)
-        for offset in self._fuelInfo:
-            self._addOffsetWithIndexMember(data, offset, "u")    # tank level
-            self._addOffsetWithIndexMember(data, offset+4, "u")  # tank capacity
-
-        if self._isN1:
-            self._engineStartIndex = len(data)
-            for i in range(0, self._numEngines):
-                self._addOffsetWithIndexMember(data, 0x2000 + i * 0x100, "f")  # N1
-                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "h")  # throttle lever
-        
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then add the data about the fuel levels and 
-        the engine parameters."""
-        state = super(GenericAircraftModel, self).getAircraftState(aircraft,
-                                                                   timestamp,
-                                                                   data)
-
-        fuelWeight = data[self._monidx_fuelWeight]/256.0
-        state.fuel = []
-        for i in range(self._fuelStartIndex, 
-                       self._fuelStartIndex + 2*len(self._fuelInfo), 2):
-            fuel = data[i+1]*data[i]*fuelWeight*const.LBSTOKG/128.0/65536.0
-            state.fuel.append(fuel)
-
-        state.n1 = []
-        state.reverser = []
-        for i in range(self._engineStartIndex,
-                       self._engineStartIndex + 2*self._numEngines, 2):
-            state.n1.append(data[i])
-            state.reverser.append(data[i+1]<0)
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class GenericModel(GenericAircraftModel):
-    """Generic aircraft model for an unknown type."""
-    def __init__(self):
-        """Construct the model."""
-        super(GenericModel, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic"    
-
-#------------------------------------------------------------------------------
-
-class B737Model(GenericAircraftModel):
-    """Generic model for the Boeing 737 Classing and NG aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(B737Model, self). \
-            __init__(flapsNotches = [0, 1, 2, 5, 10, 15, 25, 30, 40],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Boeing 737"
-
-#------------------------------------------------------------------------------
-
-class PMDGBoeing737NGModel(B737Model):
-    """A model handler for the PMDG Boeing 737NG model."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type in [const.AIRCRAFT_B736,
-                                 const.AIRCRAFT_B737,
-                                 const.AIRCRAFT_B738] and \
-            (name.find("PMDG")!=-1 or airPath.find("PMDG")!=-1) and \
-            (name.find("737")!=-1 or airPath.find("737")!=-1) and \
-            (name.find("600")!=-1 or airPath.find("600")!=-1 or \
-             name.find("700")!=-1 or airPath.find("700")!=-1 or \
-             name.find("800")!=-1 or airPath.find("800")!=-1 or \
-             name.find("900")!=-1 or airPath.find("900")!=-1)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/PMDG Boeing 737NG"
-
-    def addMonitoringData(self, data):
-        """Add the model-specific monitoring data to the given array."""
-        super(PMDGBoeing737NGModel, self).addMonitoringData(data)
-                
-        self._addOffsetWithIndexMember(data, 0x6202, "b", "_pmdgidx_switches")
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then check some PMDG-specific stuff."""
-        state = super(PMDGBoeing737NGModel, self).getAircraftState(aircraft,
-                                                                   timestamp,
-                                                                   data)
-        if data[self._pmdgidx_switches]&0x01==0x01:
-            state.altimeter = 1013.25
-
-        return state
-
-#------------------------------------------------------------------------------
-
-class B767Model(GenericAircraftModel):
-    """Generic model for the Boeing 767 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(B767Model, self). \
-            __init__(flapsNotches = [0, 1, 5, 15, 20, 25, 30],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Boeing 767"
-
-#------------------------------------------------------------------------------
-
-class DH8DModel(GenericAircraftModel):
-    """Generic model for the Bombardier  Dash 8-Q400 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(DH8DModel, self). \
-            __init__(flapsNotches = [0, 5, 10, 15, 35],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Bombardier Dash 8-Q400"
-
-#------------------------------------------------------------------------------
-
-class DreamwingsDH8DModel(DH8DModel):
-    """Model handler for the Dreamwings Dash 8-Q400."""
-    @staticmethod
-    def doesHandle(aircraft, (name, airPath)):
-        """Determine if this model handler handles the aircraft with the given
-        name."""
-        return aircraft.type==const.AIRCRAFT_DH8D and \
-            (name.find("Dreamwings")!=-1 or airPath.find("Dreamwings")!=-1) and \
-            (name.find("Dash")!=-1 or airPath.find("Dash")!=-1) and \
-            (name.find("Q400")!=-1 or airPath.find("Q400")!=-1) and \
-            airPath.find("Dash8Q400")!=-1
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Dreamwings Bombardier Dash 8-Q400"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get the aircraft state.
-
-        Get it from the parent, and then invert the pitot heat state."""
-        state = super(DreamwingsDH8DModel, self).getAircraftState(aircraft,
-                                                                  timestamp,
-                                                                  data)
-        state.pitotHeatOn = not state.pitotHeatOn
-
-        return state
-#------------------------------------------------------------------------------
-
-class CRJ2Model(GenericAircraftModel):
-    """Generic model for the Bombardier CRJ-200 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(CRJ2Model, self). \
-            __init__(flapsNotches = [0, 8, 20, 30, 45],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Bombardier CRJ-200"
-
-#------------------------------------------------------------------------------
-
-class F70Model(GenericAircraftModel):
-    """Generic model for the Fokker F70 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(F70Model, self). \
-            __init__(flapsNotches = [0, 8, 15, 25, 42],
-                     fuelInfo = [0x0b74, 0x0b7c, 0xb94], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Fokker 70"
-
-#------------------------------------------------------------------------------
-
-class DC3Model(GenericAircraftModel):
-    """Generic model for the Lisunov Li-2 (DC-3) aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(DC3Model, self). \
-            __init__(flapsNotches = [0, 15, 30, 45],
-                     fuelInfo = [0x0b7c, 0x0b84, 0x0b94, 0x0b9c], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Lisunov Li-2"
-
-#------------------------------------------------------------------------------
-
-class T134Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(T134Model, self). \
-            __init__(flapsNotches = [0, 10, 20, 30],
-                     fuelInfo = [0x0b74, 
-                                 0x0b8c, 0x0b84, 
-                                 0x0ba4, 0x0b9c,
-                                 0x1254, 0x125c], 
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Tupolev Tu-134"
-
-#------------------------------------------------------------------------------
-
-class T154Model(GenericAircraftModel):
-    """Generic model for the Tupolev Tu-134 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(T154Model, self). \
-            __init__(flapsNotches = [0, 15, 28, 45],
-                     fuelInfo = [0x0b74, 0x0b7c, 0x0b94, 
-                                 0x1244, 0x0b84, 0x0b9c],
-                     numEngines = 3)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Tupolev Tu-154"
-
-    def getAircraftState(self, aircraft, timestamp, data):
-        """Get an aircraft state object for the given monitoring data.
-
-        This removes the reverser value for the middle engine."""
-        state = super(T154Model, self).getAircraftState(aircraft, timestamp, data)
-        del state.reverser[1]
-        return state
-
-#------------------------------------------------------------------------------
-
-class YK40Model(GenericAircraftModel):
-    """Generic model for the Yakovlev Yak-40 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(YK40Model, self). \
-            __init__(flapsNotches = [0, 20, 35],
-                     fuelInfo = [0x0b7c, 0x0b94],
-                     numEngines = 2)
-
-    @property
-    def name(self):
-        """Get the name for this aircraft model."""
-        return "FSUIPC/Generic Yakovlev Yak-40"
-
-#------------------------------------------------------------------------------
-
-_genericModels = { const.AIRCRAFT_B736 : B737Model,
-                   const.AIRCRAFT_B737 : B737Model,
-                   const.AIRCRAFT_B738 : B737Model,
-                   const.AIRCRAFT_B733 : B737Model,
-                   const.AIRCRAFT_B734 : B737Model,
-                   const.AIRCRAFT_B735 : B737Model,
-                   const.AIRCRAFT_DH8D : DH8DModel,
-                   const.AIRCRAFT_B762 : B767Model,
-                   const.AIRCRAFT_B763 : B767Model,
-                   const.AIRCRAFT_CRJ2 : B767Model,
-                   const.AIRCRAFT_F70  : F70Model,
-                   const.AIRCRAFT_DC3  : DC3Model,
-                   const.AIRCRAFT_T134 : T134Model,
-                   const.AIRCRAFT_T154 : T154Model,
-                   const.AIRCRAFT_YK40 : YK40Model }
-
-#------------------------------------------------------------------------------
-
-AircraftModel.registerSpecial(PMDGBoeing737NGModel)
-AircraftModel.registerSpecial(DreamwingsDH8DModel)
-
-#------------------------------------------------------------------------------
Index: src/logger.py
===================================================================
--- src/logger.py	(revision 24)
+++ src/logger.py	(revision 24)
@@ -0,0 +1,92 @@
+# Module for the logging.
+
+#--------------------------------------------------------------------------------------
+
+import const
+
+import sys
+import time
+
+#--------------------------------------------------------------------------------------
+
+class Logger(object):
+    """The class with the interface to log the various events."""
+    # FIXME: shall we use const.stage2string() instead?
+    _stages = { const.STAGE_BOARDING : "Boarding",
+                const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
+                const.STAGE_TAKEOFF : "Takeoff",
+                const.STAGE_RTO : "RTO",
+                const.STAGE_CLIMB : "Climb",
+                const.STAGE_CRUISE : "Cruise",
+                const.STAGE_DESCENT : "Descent",
+                const.STAGE_LANDING : "Landing",
+                const.STAGE_TAXIAFTERLAND : "Taxi",
+                const.STAGE_PARKING : "Parking",
+                const.STAGE_GOAROUND : "Go-Around",
+                const.STAGE_END : "End" }
+
+    _noGoScore = 10000
+
+    def __init__(self, output = sys.stdout):
+        """Construct the logger."""
+        self._faults = {}
+        self._output = output
+
+    @staticmethod
+    def _getTimeStr(timestamp):
+        """Get the string representation of the given timestamp."""
+        return time.strftime("%H:%M:%S", time.gmtime(timestamp))
+
+    def reset(self):
+        """Reset the logger.
+
+        The faults logged so far will be cleared."""
+        self._faults.clear()
+                
+    def message(self, timestamp, msg):
+        """Put a simple textual message into the log with the given timestamp."""
+        timeStr = Logger._getTimeStr(timestamp)
+        print >> self._output, timeStr + ":", msg
+        print timeStr + ":", msg        
+
+    def untimedMessage(self, msg):
+        """Put an untimed message into the log."""
+        print >> self._output, msg
+        print msg        
+
+    def debug(self, msg):
+        """Log a debug message."""
+        print >> self._output, "[DEBUG]", msg
+        print "[DEBUG]", msg
+
+    def stage(self, timestamp, stage):
+        """Report a change in the flight stage."""
+        s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
+        self.message(timestamp, "--- %s ---" % (s,))
+        if stage==const.STAGE_END:
+            totalScore = 100
+            for (id, score) in self._faults.iteritems():
+                totalScore -= score
+            self.untimedMessage("Score: %.0f" % (totalScore,))
+        
+    def fault(self, faultID, timestamp, what, score):
+        """Report a fault.
+
+        faultID as a unique ID for the given kind of fault. If another fault of
+        this ID has been reported earlier, it will be reported again only if
+        the score is greater than last time. This ID can be, e.g. the checker
+        the report comes from."""
+        if faultID in self._faults:
+            if score<=self._faults[faultID]:
+                return
+        self._faults[faultID] = score
+        if score==Logger._noGoScore:
+            self.message(timestamp, "%s (NO GO)" % (what))
+        else:
+            self.message(timestamp, "%s (%.1f)" % (what, score))
+
+    def noGo(self, faultID, timestamp, what, shortReason):
+        """Report a No-Go fault."""
+        self.fault(faultID, timestamp, what, Logger._noGoScore)
+
+#--------------------------------------------------------------------------------------
Index: src/logger.py
===================================================================
--- src/logger.py	(revision 24)
+++ 	(revision )
@@ -1,92 +1,0 @@
-# Module for the logging.
-
-#--------------------------------------------------------------------------------------
-
-import const
-
-import sys
-import time
-
-#--------------------------------------------------------------------------------------
-
-class Logger(object):
-    """The class with the interface to log the various events."""
-    # FIXME: shall we use const.stage2string() instead?
-    _stages = { const.STAGE_BOARDING : "Boarding",
-                const.STAGE_PUSHANDTAXI : "Pushback and Taxi",
-                const.STAGE_TAKEOFF : "Takeoff",
-                const.STAGE_RTO : "RTO",
-                const.STAGE_CLIMB : "Climb",
-                const.STAGE_CRUISE : "Cruise",
-                const.STAGE_DESCENT : "Descent",
-                const.STAGE_LANDING : "Landing",
-                const.STAGE_TAXIAFTERLAND : "Taxi",
-                const.STAGE_PARKING : "Parking",
-                const.STAGE_GOAROUND : "Go-Around",
-                const.STAGE_END : "End" }
-
-    _noGoScore = 10000
-
-    def __init__(self, output = sys.stdout):
-        """Construct the logger."""
-        self._faults = {}
-        self._output = output
-
-    @staticmethod
-    def _getTimeStr(timestamp):
-        """Get the string representation of the given timestamp."""
-        return time.strftime("%H:%M:%S", time.gmtime(timestamp))
-
-    def reset(self):
-        """Reset the logger.
-
-        The faults logged so far will be cleared."""
-        self._faults.clear()
-                
-    def message(self, timestamp, msg):
-        """Put a simple textual message into the log with the given timestamp."""
-        timeStr = Logger._getTimeStr(timestamp)
-        print >> self._output, timeStr + ":", msg
-        print timeStr + ":", msg        
-
-    def untimedMessage(self, msg):
-        """Put an untimed message into the log."""
-        print >> self._output, msg
-        print msg        
-
-    def debug(self, msg):
-        """Log a debug message."""
-        print >> self._output, "[DEBUG]", msg
-        print "[DEBUG]", msg
-
-    def stage(self, timestamp, stage):
-        """Report a change in the flight stage."""
-        s = Logger._stages[stage] if stage in Logger._stages else "<Unknown>"
-        self.message(timestamp, "--- %s ---" % (s,))
-        if stage==const.STAGE_END:
-            totalScore = 100
-            for (id, score) in self._faults.iteritems():
-                totalScore -= score
-            self.untimedMessage("Score: %.0f" % (totalScore,))
-        
-    def fault(self, faultID, timestamp, what, score):
-        """Report a fault.
-
-        faultID as a unique ID for the given kind of fault. If another fault of
-        this ID has been reported earlier, it will be reported again only if
-        the score is greater than last time. This ID can be, e.g. the checker
-        the report comes from."""
-        if faultID in self._faults:
-            if score<=self._faults[faultID]:
-                return
-        self._faults[faultID] = score
-        if score==Logger._noGoScore:
-            self.message(timestamp, "%s (NO GO)" % (what))
-        else:
-            self.message(timestamp, "%s (%.1f)" % (what, score))
-
-    def noGo(self, faultID, timestamp, what, shortReason):
-        """Report a No-Go fault."""
-        self.fault(faultID, timestamp, what, Logger._noGoScore)
-
-#--------------------------------------------------------------------------------------
Index: src/mlx.py
===================================================================
--- src/mlx.py	(revision 24)
+++ src/mlx.py	(revision 24)
@@ -0,0 +1,598 @@
+# The main program
+
+import const
+import fs
+import flight
+import logger
+import acft
+import sys
+
+import os
+import math
+import time
+
+if os.name=="nt" or "FORCE_PYGTK" in os.environ:
+    print "Using PyGTK"
+    pygobject = False
+    import pygtk
+    import gtk
+    import gobject
+else:
+    print "Using PyGObject"
+    pygobject = True
+    from gi.repository import Gtk as gtk
+    from gi.repository import GObject as gobject
+
+import cairo
+
+acftTypes = [ ("Boeing 737-600", const.AIRCRAFT_B736),
+              ("Boeing 737-700", const.AIRCRAFT_B737),
+              ("Boeing 737-800", const.AIRCRAFT_B738),
+              ("Bombardier Dash 8-Q400", const.AIRCRAFT_DH8D),
+              ("Boeing 737-300", const.AIRCRAFT_B733),
+              ("Boeing 737-400", const.AIRCRAFT_B734),
+              ("Boeing 737-500", const.AIRCRAFT_B735),
+              ("Boeing 767-200", const.AIRCRAFT_B762),
+              ("Boeing 767-300", const.AIRCRAFT_B763),
+              ("Bombardier CRJ200", const.AIRCRAFT_CRJ2),
+              ("Fokker 70", const.AIRCRAFT_F70),
+              ("Lisunov Li-2", const.AIRCRAFT_DC3),
+              ("Tupolev Tu-134", const.AIRCRAFT_T134),
+              ("Tupolev Tu-154", const.AIRCRAFT_T154),
+              ("Yakovlev Yak-40", const.AIRCRAFT_YK40) ]
+
+class GUI(fs.ConnectionListener):
+    """The main GUI class."""
+    def __init__(self):
+        """Construct the GUI."""
+        self._connecting = False
+        self._connected = False
+        self._logger = logger.Logger(output = self)
+        self._flight = None
+        self._simulator = None
+
+    def build(self):
+        """Build the GUI."""
+        win = gtk.Window()
+        win.set_title("MAVA Logger X " + const.VERSION)
+        win.connect("delete-event", gtk.main_quit)
+
+        mainVBox = gtk.VBox()
+        win.add(mainVBox)
+
+        setupFrame = self._buildSetupFrame()
+        setupFrame.set_border_width(8)
+        mainVBox.pack_start(setupFrame, False, False, 0)
+
+        dataFrame = self._buildDataFrame()
+        dataFrame.set_border_width(8)
+        mainVBox.pack_start(dataFrame, False, False, 0)
+
+        logFrame = self._buildLogFrame()
+        logFrame.set_border_width(8)
+        mainVBox.pack_start(logFrame, True, True, 0)
+
+        win.show_all()        
+
+    def run(self):
+        """Run the GUI."""
+        gtk.main()
+        if self._flight is not None:
+            simulator = self._flight.simulator
+            simulator.stopMonitoring()
+            simulator.disconnect()            
+
+    def connected(self, fsType, descriptor):
+        """Called when we have connected to the simulator."""
+        self._connected = True
+        self._updateConnState()
+        self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
+        
+    def disconnected(self):
+        """Called when we have disconnected from the simulator."""
+        self._connected = False
+        self._updateConnState()
+        self._logger.untimedMessage("Disconnected from the simulator")
+
+    def write(self, msg):
+        """Write the given message to the log."""
+        gobject.idle_add(self._writeLog, msg)
+        
+    def check(self, flight, aircraft, logger, oldState, state):
+        """Update the data."""
+        gobject.idle_add(self._setData, state)
+
+    def _drawConnState(self, connStateArea, eventOrContext):
+        """Draw the connection state."""
+        context = eventOrContext if pygobject else connStateArea.window.cairo_create()
+
+        if self._connecting:
+            if self._connected:
+                context.set_source_rgb(0.0, 1.0, 0.0)
+            else:
+                context.set_source_rgb(1.0, 0.0, 0.0)
+        else:
+            context.set_source_rgb(0.75, 0.75, 0.75)
+
+        width = connStateArea.get_allocated_width() if pygobject \
+                else connStateArea.allocation.width
+        height = connStateArea.get_allocated_height() if pygobject \
+                 else connStateArea.allocation.height
+        context.arc(width/2, height/2, 8, 0, 2*math.pi)
+
+        context.fill()
+
+    def _updateConnState(self):
+        """Initiate the updating of the connection state icon."""
+        self._connStateArea.queue_draw()
+
+    def _connectToggled(self, button):
+        """Callback for the connection button."""
+        if self._connectButton.get_active():
+            self._logger.reset()
+            self._flight = flight.Flight(self._logger)
+
+            acftListModel = self._acftList.get_model()
+            self._flight.aircraftType = \
+                acftListModel[self._acftList.get_active()][1]
+            self._flight.aircraft = acft.Aircraft.create(self._flight)
+            self._flight.aircraft._checkers.append(self)
+
+            self._flight.cruiseAltitude = self._flSpinButton.get_value_as_int() * 100
+
+            self._flight.zfw = self._zfwSpinButton.get_value_as_int()
+
+            if self._simulator is None:
+                self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
+
+            self._flight.simulator = self._simulator
+
+            self._connecting = True
+            self._simulator.connect(self._flight.aircraft)
+            self._simulator.startMonitoring()
+        else:
+            self._connecting = False
+            self._simulator.stopMonitoring()
+            self._simulator.disconnect()
+            self._flight = None
+
+        self._updateConnState()
+
+    def _buildSetupFrame(self):
+        """Build the setup frame."""
+        setupFrame = gtk.Frame(label = "Setup")
+
+        frameAlignment = gtk.Alignment(xalign = 0.5)
+
+        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                   padding_left = 16, padding_right = 16)
+
+        setupFrame.add(frameAlignment)
+
+        setupBox = gtk.HBox()
+        frameAlignment.add(setupBox)
+
+        # self._fs9Button = gtk.RadioButton(label = "FS9")
+        # self._fs9Button.set_tooltip_text("Use MS Flight Simulator 2004")
+        # setupBox.pack_start(self._fs9Button, False, False, 0)
+
+        # self._fsxButton = gtk.RadioButton(group = self._fs9Button, label = "FSX")
+        # self._fsxButton.set_tooltip_text("Use MS Flight Simulator X")
+        # setupBox.pack_start(self._fsxButton, False, False, 0)
+
+        # setupBox.pack_start(gtk.VSeparator(), False, False, 8)
+
+        alignment = gtk.Alignment(yalign = 0.5)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 16)
+        alignment.add(gtk.Label("Aicraft:"))
+        setupBox.pack_start(alignment, False, False, 0)
+
+        acftListModel = gtk.ListStore(str, int)
+        for (name, type) in acftTypes:
+            acftListModel.append([name, type])
+
+        self._acftList = gtk.ComboBox(model = acftListModel)    
+        renderer_text = gtk.CellRendererText()
+        self._acftList.pack_start(renderer_text, True)
+        self._acftList.add_attribute(renderer_text, "text", 0)
+        self._acftList.set_active(0)
+        self._acftList.set_tooltip_text("Select the type of the aircraft used for the flight.")
+
+        setupBox.pack_start(self._acftList, True, True, 0)
+
+        setupBox.pack_start(gtk.VSeparator(), False, False, 8)
+
+        alignment = gtk.Alignment(yalign = 0.5)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 16)
+        alignment.add(gtk.Label("Cruise FL:"))
+        setupBox.pack_start(alignment, False, False, 0)
+
+        self._flSpinButton = gtk.SpinButton()
+        self._flSpinButton.set_increments(step = 10, page = 100)
+        self._flSpinButton.set_range(min = 0, max = 500)
+        self._flSpinButton.set_value(240)
+        self._flSpinButton.set_tooltip_text("The cruise flight level.")
+        self._flSpinButton.set_numeric(True)
+
+        setupBox.pack_start(self._flSpinButton, False, False, 0)
+
+        setupBox.pack_start(gtk.VSeparator(), False, False, 8)
+
+        alignment = gtk.Alignment(yalign = 0.5)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 16)
+        alignment.add(gtk.Label("ZFW:"))
+        setupBox.pack_start(alignment, False, False, 0)
+
+        self._zfwSpinButton = gtk.SpinButton()
+        self._zfwSpinButton.set_increments(step = 100, page = 1000)
+        self._zfwSpinButton.set_range(min = 0, max = 500000)
+        self._zfwSpinButton.set_value(50000)
+        self._zfwSpinButton.set_tooltip_text("The Zero Fuel Weight for the flight in kgs")
+        self._zfwSpinButton.set_numeric(True)
+
+        setupBox.pack_start(self._zfwSpinButton, False, False, 0)
+
+        setupBox.pack_start(gtk.VSeparator(), False, False, 8)    
+
+        self._connectButton = gtk.ToggleButton(label = "Connect")
+        self._connectButton.set_tooltip_text("Push to connect to Flight Simulator and start a new flight.\n"
+                                             "Push again to disconnect from FS.")
+        
+        self._connectButton.connect("toggled", self._connectToggled)
+
+        setupBox.pack_start(self._connectButton, False, False, 0)
+
+        setupBox.pack_start(gtk.VSeparator(), False, False, 8)    
+
+        self._connStateArea = gtk.DrawingArea()
+        self._connStateArea.set_size_request(16, 16)
+        self._connStateArea.set_tooltip_markup('The state of the connection.\n'
+                                               '<span foreground="grey">Grey</span> means idle.\n'
+                                               '<span foreground="red">Red</span> means trying to connect.\n'
+                                               '<span foreground="green">Green</span> means connected.')
+
+        if pygobject:
+            self._connStateArea.connect("draw", self._drawConnState)
+        else:
+            self._connStateArea.connect("expose_event", self._drawConnState)
+
+        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5)
+        alignment.add(self._connStateArea)
+
+        setupBox.pack_start(alignment, False, False, 8)
+
+        return setupFrame
+
+    def _createLabeledEntry(self, label, width = 8, xalign = 1.0):
+        """Create a labeled entry.
+
+        Return a tuple consisting of:
+        - the box
+        - the entry."""
+        
+        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5, xscale = 1.0)
+        alignment.set_padding(padding_top = 0, padding_bottom = 0,
+                              padding_left = 0, padding_right = 16)
+        alignment.add(gtk.Label(label))
+
+        entry = gtk.Entry()
+        entry.set_editable(False)
+        entry.set_width_chars(width)
+        entry.set_max_length(width)
+        entry.set_alignment(xalign)
+
+        return (alignment, entry)
+
+    def _buildDataFrame(self):
+        """Build the frame for the data."""
+        dataFrame = gtk.Frame(label = "Data")
+
+        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                   padding_left = 16, padding_right = 16)
+
+        table = gtk.Table(rows = 7, columns = 12)
+        table.set_homogeneous(False)
+        table.set_row_spacings(4)
+        table.set_col_spacings(8)
+
+        (label, self._timestamp) = self._createLabeledEntry("Time:")
+        table.attach(label, 0, 1, 0, 1)
+        table.attach(self._timestamp, 1, 2, 0, 1)
+
+        self._paused = gtk.Label("PAUSED")
+        table.attach(self._paused, 2, 4, 0, 1)
+        
+        self._trickMode = gtk.Label("TRICKMODE")
+        table.attach(self._trickMode, 4, 6, 0, 1, xoptions = 0)
+        
+        self._overspeed = gtk.Label("OVERSPEED")
+        table.attach(self._overspeed, 6, 8, 0, 1)
+        
+        self._stalled = gtk.Label("STALLED")
+        table.attach(self._stalled, 8, 10, 0, 1)
+        
+        self._onTheGround = gtk.Label("ONTHEGROUND")
+        table.attach(self._onTheGround, 10, 12, 0, 1)
+        
+        (label, self._zfw) = self._createLabeledEntry("ZFW:", 6)
+        table.attach(label, 0, 1, 1, 2)
+        table.attach(self._zfw, 1, 2, 1, 2)
+
+        (label, self._grossWeight) = self._createLabeledEntry("Weight:", 6)
+        table.attach(label, 2, 3, 1, 2)
+        table.attach(self._grossWeight, 3, 4, 1, 2)
+
+        (label, self._heading) = self._createLabeledEntry("Heading:", 3)
+        table.attach(label, 4, 5, 1, 2)
+        table.attach(self._heading, 5, 6, 1, 2)
+
+        (label, self._pitch) = self._createLabeledEntry("Pitch:", 3)
+        table.attach(label, 6, 7, 1, 2)
+        table.attach(self._pitch, 7, 8, 1, 2)
+
+        (label, self._bank) = self._createLabeledEntry("Bank:", 3)
+        table.attach(label, 8, 9, 1, 2)
+        table.attach(self._bank, 9, 10, 1, 2)
+
+        (label, self._vs) = self._createLabeledEntry("VS:", 5)
+        table.attach(label, 10, 11, 1, 2)
+        table.attach(self._vs, 11, 12, 1, 2)
+
+        (label, self._ias) = self._createLabeledEntry("IAS:", 4)
+        table.attach(label, 0, 1, 2, 3)
+        table.attach(self._ias, 1, 2, 2, 3)
+
+        (label, self._mach) = self._createLabeledEntry("Mach:", 4)
+        table.attach(label, 2, 3, 2, 3)
+        table.attach(self._mach, 3, 4, 2, 3)
+
+        (label, self._groundSpeed) = self._createLabeledEntry("GS:", 4)
+        table.attach(label, 4, 5, 2, 3)
+        table.attach(self._groundSpeed, 5, 6, 2, 3)
+
+        (label, self._radioAltitude) = self._createLabeledEntry("Radio alt.:", 6)
+        table.attach(label, 6, 7, 2, 3)
+        table.attach(self._radioAltitude, 7, 8, 2, 3)
+
+        (label, self._altitude) = self._createLabeledEntry("Altitude:", 6)
+        table.attach(label, 8, 9, 2, 3)
+        table.attach(self._altitude, 9, 10, 2, 3)
+
+        (label, self._gLoad) = self._createLabeledEntry("G-Load:", 4)
+        table.attach(label, 10, 11, 2, 3)
+        table.attach(self._gLoad, 11, 12, 2, 3)
+
+        (label, self._flapsSet) = self._createLabeledEntry("Flaps set:", 2)
+        table.attach(label, 0, 1, 3, 4)
+        table.attach(self._flapsSet, 1, 2, 3, 4)
+
+        (label, self._flaps) = self._createLabeledEntry("Flaps:", 2)
+        table.attach(label, 2, 3, 3, 4)
+        table.attach(self._flaps, 3, 4, 3, 4)
+
+        (label, self._altimeter) = self._createLabeledEntry("Altimeter:", 4)
+        table.attach(label, 4, 5, 3, 4)
+        table.attach(self._altimeter, 5, 6, 3, 4)
+
+        (label, self._squawk) = self._createLabeledEntry("Squawk:", 4)
+        table.attach(label, 6, 7, 3, 4)
+        table.attach(self._squawk, 7, 8, 3, 4)
+
+        (label, self._nav1) = self._createLabeledEntry("NAV1:", 5)
+        table.attach(label, 8, 9, 3, 4)
+        table.attach(self._nav1, 9, 10, 3, 4)
+
+        (label, self._nav2) = self._createLabeledEntry("NAV2:", 5)
+        table.attach(label, 10, 11, 3, 4)
+        table.attach(self._nav2, 11, 12, 3, 4)
+
+        (label, self._fuel) = self._createLabeledEntry("Fuel:", 20, xalign = 0.0)
+        table.attach(label, 0, 1, 4, 5)
+        table.attach(self._fuel, 1, 4, 4, 5)
+
+        (label, self._n1) = self._createLabeledEntry("N1/RPM:", 20, xalign = 0.0)
+        table.attach(label, 4, 5, 4, 5)
+        table.attach(self._n1, 5, 8, 4, 5)
+
+        (label, self._reverser) = self._createLabeledEntry("Reverser:", 20, xalign = 0.0)
+        table.attach(label, 8, 9, 4, 5)
+        table.attach(self._reverser, 9, 12, 4, 5)
+
+        self._navLightsOn = gtk.Label("NAV")
+        table.attach(self._navLightsOn, 0, 1, 5, 6)
+
+        self._antiCollisionLightsOn = gtk.Label("ANTICOLLISION")
+        table.attach(self._antiCollisionLightsOn, 1, 3, 5, 6)
+
+        self._strobeLightsOn = gtk.Label("STROBE")
+        table.attach(self._strobeLightsOn, 3, 4, 5, 6)
+
+        self._landingLightsOn = gtk.Label("LANDING")
+        table.attach(self._landingLightsOn, 4, 5, 5, 6)
+
+        self._pitotHeatOn = gtk.Label("PITOT HEAT")
+        table.attach(self._pitotHeatOn, 5, 7, 5, 6)
+
+        self._parking = gtk.Label("PARKING")
+        table.attach(self._parking, 7, 8, 5, 6)
+
+        self._gearsDown = gtk.Label("GEARS DOWN")
+        table.attach(self._gearsDown, 8, 10, 5, 6)
+
+        self._spoilersArmed = gtk.Label("SPOILERS ARMED")
+        table.attach(self._spoilersArmed, 10, 12, 5, 6)
+
+        (label, self._spoilersExtension) = self._createLabeledEntry("Spoilers:", 3)
+        table.attach(label, 0, 1, 6, 7)
+        table.attach(self._spoilersExtension, 1, 2, 6, 7)
+
+        (label, self._windSpeed) = self._createLabeledEntry("Wind speed:", 3)
+        table.attach(label, 2, 3, 6, 7)
+        table.attach(self._windSpeed, 3, 4, 6, 7)
+
+        (label, self._windDirection) = self._createLabeledEntry("Wind from:", 3)
+        table.attach(label, 4, 5, 6, 7)
+        table.attach(self._windDirection, 5, 6, 6, 7)
+
+        frameAlignment.add(table)
+
+        dataFrame.add(frameAlignment)
+
+        self._setData()
+
+        return dataFrame        
+
+    def _setData(self, aircraftState = None):
+        """Set the data.
+
+        If aircraftState is None, everything will be set to its default."""
+        if aircraftState is None:
+            self._timestamp.set_text("--:--:--")
+            self._paused.set_sensitive(False)
+            self._trickMode.set_sensitive(False)
+            self._overspeed.set_sensitive(False)
+            self._stalled.set_sensitive(False)
+            self._onTheGround.set_sensitive(False)
+            self._zfw.set_text("-")
+            self._grossWeight.set_text("-")
+            self._heading.set_text("-")
+            self._pitch.set_text("-")
+            self._bank.set_text("-")
+            self._vs.set_text("-")
+            self._ias.set_text("-")
+            self._mach.set_text("-")
+            self._groundSpeed.set_text("-")
+            self._radioAltitude.set_text("-")
+            self._altitude.set_text("-")
+            self._gLoad.set_text("-")
+            self._flapsSet.set_text("-")
+            self._flaps.set_text("-")
+            self._altimeter.set_text("-")
+            self._squawk.set_text("-")
+            self._nav1.set_text("-")
+            self._nav2.set_text("-")
+            self._fuel.set_text("-")
+            self._n1.set_text("-")
+            self._reverser.set_text("-")
+            self._navLightsOn.set_sensitive(False)
+            self._antiCollisionLightsOn.set_sensitive(False)
+            self._strobeLightsOn.set_sensitive(False)
+            self._landingLightsOn.set_sensitive(False)
+            self._pitotHeatOn.set_sensitive(False)
+            self._parking.set_sensitive(False)
+            self._gearsDown.set_sensitive(False)
+            self._spoilersArmed.set_sensitive(False)
+            self._spoilersExtension.set_text("-")
+            self._windSpeed.set_text("-")
+            self._windDirection.set_text("-")
+        else:
+            self._timestamp.set_text(time.strftime("%H:%M:%S",
+                                                   time.gmtime(aircraftState.timestamp)))
+            self._paused.set_sensitive(aircraftState.paused)
+            self._trickMode.set_sensitive(aircraftState.trickMode)
+            self._overspeed.set_sensitive(aircraftState.overspeed)
+            self._stalled.set_sensitive(aircraftState.stalled)
+            self._onTheGround.set_sensitive(aircraftState.onTheGround)
+            self._zfw.set_text("%.0f" % (aircraftState.zfw,))
+            self._grossWeight.set_text("%.0f" % (aircraftState.grossWeight,))
+            self._heading.set_text("%03.0f" % (aircraftState.heading,))
+            self._pitch.set_text("%.0f" % (aircraftState.pitch,))
+            self._bank.set_text("%.0f" % (aircraftState.bank,))
+            self._vs.set_text("%.0f" % (aircraftState.vs,))
+            self._ias.set_text("%.0f" % (aircraftState.ias,))
+            self._mach.set_text("%.2f" % (aircraftState.mach,))
+            self._groundSpeed.set_text("%.0f" % (aircraftState.groundSpeed,))
+            self._radioAltitude.set_text("%.0f" % (aircraftState.radioAltitude,))
+            self._altitude.set_text("%.0f" % (aircraftState.altitude,))
+            self._gLoad.set_text("%.2f" % (aircraftState.gLoad,))
+            self._flapsSet.set_text("%.0f" % (aircraftState.flapsSet,))
+            self._flaps.set_text("%.0f" % (aircraftState.flaps,))
+            self._altimeter.set_text("%.0f" % (aircraftState.altimeter,))
+            self._squawk.set_text(aircraftState.squawk)
+            self._nav1.set_text(aircraftState.nav1)
+            self._nav2.set_text(aircraftState.nav2)
+
+            fuelStr = ""
+            for fuel in aircraftState.fuel:
+                if fuelStr: fuelStr += ", "
+                fuelStr += "%.0f" % (fuel,)
+            self._fuel.set_text(fuelStr)
+
+            if hasattr(aircraftState, "n1"):
+                n1Str = ""
+                for n1 in aircraftState.n1:
+                    if n1Str: n1Str += ", "
+                    n1Str += "%.0f" % (n1,)
+            elif hasattr(aircraftState, "rpm"):
+                n1Str = ""
+                for rpm in aircraftState.rpm:
+                    if n1Str: n1Str += ", "
+                    n1Str += "%.0f" % (rpm,)
+            else:
+                n1Str = "-"
+            self._n1.set_text(n1Str)
+
+            reverserStr = ""
+            for reverser in aircraftState.reverser:
+                if reverserStr: reverserStr += ", "
+                reverserStr += "ON" if reverser else "OFF"
+            self._reverser.set_text(reverserStr)
+
+            self._navLightsOn.set_sensitive(aircraftState.navLightsOn)
+            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn)
+            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn)
+            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn)
+            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn)
+            self._parking.set_sensitive(aircraftState.parking)
+            self._gearsDown.set_sensitive(aircraftState.gearsDown)
+            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed)
+            self._spoilersExtension.set_text("%.0f" % (aircraftState.spoilersExtension,))
+            self._windSpeed.set_text("%.0f" % (aircraftState.windSpeed,))
+            self._windDirection.set_text("%03.0f" % (aircraftState.windDirection,))
+
+    def _buildLogFrame(self):
+        """Build the frame for the log."""
+        logFrame = gtk.Frame(label = "Log")
+
+        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
+
+        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
+                                   padding_left = 16, padding_right = 16)
+
+        logFrame.add(frameAlignment)
+
+        logScroller = gtk.ScrolledWindow()
+        self._logView = gtk.TextView()
+        self._logView.set_editable(False)
+        logScroller.add(self._logView)
+
+        logBox = gtk.VBox()
+        logBox.pack_start(logScroller, True, True, 0)
+        logBox.set_size_request(-1, 200)
+
+        frameAlignment.add(logBox)
+
+        return logFrame
+
+    def _writeLog(self, msg):
+        """Write the given message to the log."""
+        buffer = self._logView.get_buffer()
+        buffer.insert(buffer.get_end_iter(), msg)
+        self._logView.scroll_mark_onscreen(buffer.get_insert())
+
+def main():
+    """The main operation of the program."""
+    gobject.threads_init()
+    gui = GUI()
+    gui.build()
+    gui.run()
+              
+if __name__ == "__main__":
+    main()
+
+
Index: src/mlx.py
===================================================================
--- src/mlx.py	(revision 24)
+++ 	(revision )
@@ -1,598 +1,0 @@
-# The main program
-
-import const
-import fs
-import flight
-import logger
-import acft
-import sys
-
-import os
-import math
-import time
-
-if os.name=="nt" or "FORCE_PYGTK" in os.environ:
-    print "Using PyGTK"
-    pygobject = False
-    import pygtk
-    import gtk
-    import gobject
-else:
-    print "Using PyGObject"
-    pygobject = True
-    from gi.repository import Gtk as gtk
-    from gi.repository import GObject as gobject
-
-import cairo
-
-acftTypes = [ ("Boeing 737-600", const.AIRCRAFT_B736),
-              ("Boeing 737-700", const.AIRCRAFT_B737),
-              ("Boeing 737-800", const.AIRCRAFT_B738),
-              ("Bombardier Dash 8-Q400", const.AIRCRAFT_DH8D),
-              ("Boeing 737-300", const.AIRCRAFT_B733),
-              ("Boeing 737-400", const.AIRCRAFT_B734),
-              ("Boeing 737-500", const.AIRCRAFT_B735),
-              ("Boeing 767-200", const.AIRCRAFT_B762),
-              ("Boeing 767-300", const.AIRCRAFT_B763),
-              ("Bombardier CRJ200", const.AIRCRAFT_CRJ2),
-              ("Fokker 70", const.AIRCRAFT_F70),
-              ("Lisunov Li-2", const.AIRCRAFT_DC3),
-              ("Tupolev Tu-134", const.AIRCRAFT_T134),
-              ("Tupolev Tu-154", const.AIRCRAFT_T154),
-              ("Yakovlev Yak-40", const.AIRCRAFT_YK40) ]
-
-class GUI(fs.ConnectionListener):
-    """The main GUI class."""
-    def __init__(self):
-        """Construct the GUI."""
-        self._connecting = False
-        self._connected = False
-        self._logger = logger.Logger(output = self)
-        self._flight = None
-        self._simulator = None
-
-    def build(self):
-        """Build the GUI."""
-        win = gtk.Window()
-        win.set_title("MAVA Logger X " + const.VERSION)
-        win.connect("delete-event", gtk.main_quit)
-
-        mainVBox = gtk.VBox()
-        win.add(mainVBox)
-
-        setupFrame = self._buildSetupFrame()
-        setupFrame.set_border_width(8)
-        mainVBox.pack_start(setupFrame, False, False, 0)
-
-        dataFrame = self._buildDataFrame()
-        dataFrame.set_border_width(8)
-        mainVBox.pack_start(dataFrame, False, False, 0)
-
-        logFrame = self._buildLogFrame()
-        logFrame.set_border_width(8)
-        mainVBox.pack_start(logFrame, True, True, 0)
-
-        win.show_all()        
-
-    def run(self):
-        """Run the GUI."""
-        gtk.main()
-        if self._flight is not None:
-            simulator = self._flight.simulator
-            simulator.stopMonitoring()
-            simulator.disconnect()            
-
-    def connected(self, fsType, descriptor):
-        """Called when we have connected to the simulator."""
-        self._connected = True
-        self._updateConnState()
-        self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
-        
-    def disconnected(self):
-        """Called when we have disconnected from the simulator."""
-        self._connected = False
-        self._updateConnState()
-        self._logger.untimedMessage("Disconnected from the simulator")
-
-    def write(self, msg):
-        """Write the given message to the log."""
-        gobject.idle_add(self._writeLog, msg)
-        
-    def check(self, flight, aircraft, logger, oldState, state):
-        """Update the data."""
-        gobject.idle_add(self._setData, state)
-
-    def _drawConnState(self, connStateArea, eventOrContext):
-        """Draw the connection state."""
-        context = eventOrContext if pygobject else connStateArea.window.cairo_create()
-
-        if self._connecting:
-            if self._connected:
-                context.set_source_rgb(0.0, 1.0, 0.0)
-            else:
-                context.set_source_rgb(1.0, 0.0, 0.0)
-        else:
-            context.set_source_rgb(0.75, 0.75, 0.75)
-
-        width = connStateArea.get_allocated_width() if pygobject \
-                else connStateArea.allocation.width
-        height = connStateArea.get_allocated_height() if pygobject \
-                 else connStateArea.allocation.height
-        context.arc(width/2, height/2, 8, 0, 2*math.pi)
-
-        context.fill()
-
-    def _updateConnState(self):
-        """Initiate the updating of the connection state icon."""
-        self._connStateArea.queue_draw()
-
-    def _connectToggled(self, button):
-        """Callback for the connection button."""
-        if self._connectButton.get_active():
-            self._logger.reset()
-            self._flight = flight.Flight(self._logger)
-
-            acftListModel = self._acftList.get_model()
-            self._flight.aircraftType = \
-                acftListModel[self._acftList.get_active()][1]
-            self._flight.aircraft = acft.Aircraft.create(self._flight)
-            self._flight.aircraft._checkers.append(self)
-
-            self._flight.cruiseAltitude = self._flSpinButton.get_value_as_int() * 100
-
-            self._flight.zfw = self._zfwSpinButton.get_value_as_int()
-
-            if self._simulator is None:
-                self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
-
-            self._flight.simulator = self._simulator
-
-            self._connecting = True
-            self._simulator.connect(self._flight.aircraft)
-            self._simulator.startMonitoring()
-        else:
-            self._connecting = False
-            self._simulator.stopMonitoring()
-            self._simulator.disconnect()
-            self._flight = None
-
-        self._updateConnState()
-
-    def _buildSetupFrame(self):
-        """Build the setup frame."""
-        setupFrame = gtk.Frame(label = "Setup")
-
-        frameAlignment = gtk.Alignment(xalign = 0.5)
-
-        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                   padding_left = 16, padding_right = 16)
-
-        setupFrame.add(frameAlignment)
-
-        setupBox = gtk.HBox()
-        frameAlignment.add(setupBox)
-
-        # self._fs9Button = gtk.RadioButton(label = "FS9")
-        # self._fs9Button.set_tooltip_text("Use MS Flight Simulator 2004")
-        # setupBox.pack_start(self._fs9Button, False, False, 0)
-
-        # self._fsxButton = gtk.RadioButton(group = self._fs9Button, label = "FSX")
-        # self._fsxButton.set_tooltip_text("Use MS Flight Simulator X")
-        # setupBox.pack_start(self._fsxButton, False, False, 0)
-
-        # setupBox.pack_start(gtk.VSeparator(), False, False, 8)
-
-        alignment = gtk.Alignment(yalign = 0.5)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 16)
-        alignment.add(gtk.Label("Aicraft:"))
-        setupBox.pack_start(alignment, False, False, 0)
-
-        acftListModel = gtk.ListStore(str, int)
-        for (name, type) in acftTypes:
-            acftListModel.append([name, type])
-
-        self._acftList = gtk.ComboBox(model = acftListModel)    
-        renderer_text = gtk.CellRendererText()
-        self._acftList.pack_start(renderer_text, True)
-        self._acftList.add_attribute(renderer_text, "text", 0)
-        self._acftList.set_active(0)
-        self._acftList.set_tooltip_text("Select the type of the aircraft used for the flight.")
-
-        setupBox.pack_start(self._acftList, True, True, 0)
-
-        setupBox.pack_start(gtk.VSeparator(), False, False, 8)
-
-        alignment = gtk.Alignment(yalign = 0.5)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 16)
-        alignment.add(gtk.Label("Cruise FL:"))
-        setupBox.pack_start(alignment, False, False, 0)
-
-        self._flSpinButton = gtk.SpinButton()
-        self._flSpinButton.set_increments(step = 10, page = 100)
-        self._flSpinButton.set_range(min = 0, max = 500)
-        self._flSpinButton.set_value(240)
-        self._flSpinButton.set_tooltip_text("The cruise flight level.")
-        self._flSpinButton.set_numeric(True)
-
-        setupBox.pack_start(self._flSpinButton, False, False, 0)
-
-        setupBox.pack_start(gtk.VSeparator(), False, False, 8)
-
-        alignment = gtk.Alignment(yalign = 0.5)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 16)
-        alignment.add(gtk.Label("ZFW:"))
-        setupBox.pack_start(alignment, False, False, 0)
-
-        self._zfwSpinButton = gtk.SpinButton()
-        self._zfwSpinButton.set_increments(step = 100, page = 1000)
-        self._zfwSpinButton.set_range(min = 0, max = 500000)
-        self._zfwSpinButton.set_value(50000)
-        self._zfwSpinButton.set_tooltip_text("The Zero Fuel Weight for the flight in kgs")
-        self._zfwSpinButton.set_numeric(True)
-
-        setupBox.pack_start(self._zfwSpinButton, False, False, 0)
-
-        setupBox.pack_start(gtk.VSeparator(), False, False, 8)    
-
-        self._connectButton = gtk.ToggleButton(label = "Connect")
-        self._connectButton.set_tooltip_text("Push to connect to Flight Simulator and start a new flight.\n"
-                                             "Push again to disconnect from FS.")
-        
-        self._connectButton.connect("toggled", self._connectToggled)
-
-        setupBox.pack_start(self._connectButton, False, False, 0)
-
-        setupBox.pack_start(gtk.VSeparator(), False, False, 8)    
-
-        self._connStateArea = gtk.DrawingArea()
-        self._connStateArea.set_size_request(16, 16)
-        self._connStateArea.set_tooltip_markup('The state of the connection.\n'
-                                               '<span foreground="grey">Grey</span> means idle.\n'
-                                               '<span foreground="red">Red</span> means trying to connect.\n'
-                                               '<span foreground="green">Green</span> means connected.')
-
-        if pygobject:
-            self._connStateArea.connect("draw", self._drawConnState)
-        else:
-            self._connStateArea.connect("expose_event", self._drawConnState)
-
-        alignment = gtk.Alignment(xalign = 0.5, yalign = 0.5)
-        alignment.add(self._connStateArea)
-
-        setupBox.pack_start(alignment, False, False, 8)
-
-        return setupFrame
-
-    def _createLabeledEntry(self, label, width = 8, xalign = 1.0):
-        """Create a labeled entry.
-
-        Return a tuple consisting of:
-        - the box
-        - the entry."""
-        
-        alignment = gtk.Alignment(xalign = 1.0, yalign = 0.5, xscale = 1.0)
-        alignment.set_padding(padding_top = 0, padding_bottom = 0,
-                              padding_left = 0, padding_right = 16)
-        alignment.add(gtk.Label(label))
-
-        entry = gtk.Entry()
-        entry.set_editable(False)
-        entry.set_width_chars(width)
-        entry.set_max_length(width)
-        entry.set_alignment(xalign)
-
-        return (alignment, entry)
-
-    def _buildDataFrame(self):
-        """Build the frame for the data."""
-        dataFrame = gtk.Frame(label = "Data")
-
-        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                   padding_left = 16, padding_right = 16)
-
-        table = gtk.Table(rows = 7, columns = 12)
-        table.set_homogeneous(False)
-        table.set_row_spacings(4)
-        table.set_col_spacings(8)
-
-        (label, self._timestamp) = self._createLabeledEntry("Time:")
-        table.attach(label, 0, 1, 0, 1)
-        table.attach(self._timestamp, 1, 2, 0, 1)
-
-        self._paused = gtk.Label("PAUSED")
-        table.attach(self._paused, 2, 4, 0, 1)
-        
-        self._trickMode = gtk.Label("TRICKMODE")
-        table.attach(self._trickMode, 4, 6, 0, 1, xoptions = 0)
-        
-        self._overspeed = gtk.Label("OVERSPEED")
-        table.attach(self._overspeed, 6, 8, 0, 1)
-        
-        self._stalled = gtk.Label("STALLED")
-        table.attach(self._stalled, 8, 10, 0, 1)
-        
-        self._onTheGround = gtk.Label("ONTHEGROUND")
-        table.attach(self._onTheGround, 10, 12, 0, 1)
-        
-        (label, self._zfw) = self._createLabeledEntry("ZFW:", 6)
-        table.attach(label, 0, 1, 1, 2)
-        table.attach(self._zfw, 1, 2, 1, 2)
-
-        (label, self._grossWeight) = self._createLabeledEntry("Weight:", 6)
-        table.attach(label, 2, 3, 1, 2)
-        table.attach(self._grossWeight, 3, 4, 1, 2)
-
-        (label, self._heading) = self._createLabeledEntry("Heading:", 3)
-        table.attach(label, 4, 5, 1, 2)
-        table.attach(self._heading, 5, 6, 1, 2)
-
-        (label, self._pitch) = self._createLabeledEntry("Pitch:", 3)
-        table.attach(label, 6, 7, 1, 2)
-        table.attach(self._pitch, 7, 8, 1, 2)
-
-        (label, self._bank) = self._createLabeledEntry("Bank:", 3)
-        table.attach(label, 8, 9, 1, 2)
-        table.attach(self._bank, 9, 10, 1, 2)
-
-        (label, self._vs) = self._createLabeledEntry("VS:", 5)
-        table.attach(label, 10, 11, 1, 2)
-        table.attach(self._vs, 11, 12, 1, 2)
-
-        (label, self._ias) = self._createLabeledEntry("IAS:", 4)
-        table.attach(label, 0, 1, 2, 3)
-        table.attach(self._ias, 1, 2, 2, 3)
-
-        (label, self._mach) = self._createLabeledEntry("Mach:", 4)
-        table.attach(label, 2, 3, 2, 3)
-        table.attach(self._mach, 3, 4, 2, 3)
-
-        (label, self._groundSpeed) = self._createLabeledEntry("GS:", 4)
-        table.attach(label, 4, 5, 2, 3)
-        table.attach(self._groundSpeed, 5, 6, 2, 3)
-
-        (label, self._radioAltitude) = self._createLabeledEntry("Radio alt.:", 6)
-        table.attach(label, 6, 7, 2, 3)
-        table.attach(self._radioAltitude, 7, 8, 2, 3)
-
-        (label, self._altitude) = self._createLabeledEntry("Altitude:", 6)
-        table.attach(label, 8, 9, 2, 3)
-        table.attach(self._altitude, 9, 10, 2, 3)
-
-        (label, self._gLoad) = self._createLabeledEntry("G-Load:", 4)
-        table.attach(label, 10, 11, 2, 3)
-        table.attach(self._gLoad, 11, 12, 2, 3)
-
-        (label, self._flapsSet) = self._createLabeledEntry("Flaps set:", 2)
-        table.attach(label, 0, 1, 3, 4)
-        table.attach(self._flapsSet, 1, 2, 3, 4)
-
-        (label, self._flaps) = self._createLabeledEntry("Flaps:", 2)
-        table.attach(label, 2, 3, 3, 4)
-        table.attach(self._flaps, 3, 4, 3, 4)
-
-        (label, self._altimeter) = self._createLabeledEntry("Altimeter:", 4)
-        table.attach(label, 4, 5, 3, 4)
-        table.attach(self._altimeter, 5, 6, 3, 4)
-
-        (label, self._squawk) = self._createLabeledEntry("Squawk:", 4)
-        table.attach(label, 6, 7, 3, 4)
-        table.attach(self._squawk, 7, 8, 3, 4)
-
-        (label, self._nav1) = self._createLabeledEntry("NAV1:", 5)
-        table.attach(label, 8, 9, 3, 4)
-        table.attach(self._nav1, 9, 10, 3, 4)
-
-        (label, self._nav2) = self._createLabeledEntry("NAV2:", 5)
-        table.attach(label, 10, 11, 3, 4)
-        table.attach(self._nav2, 11, 12, 3, 4)
-
-        (label, self._fuel) = self._createLabeledEntry("Fuel:", 20, xalign = 0.0)
-        table.attach(label, 0, 1, 4, 5)
-        table.attach(self._fuel, 1, 4, 4, 5)
-
-        (label, self._n1) = self._createLabeledEntry("N1/RPM:", 20, xalign = 0.0)
-        table.attach(label, 4, 5, 4, 5)
-        table.attach(self._n1, 5, 8, 4, 5)
-
-        (label, self._reverser) = self._createLabeledEntry("Reverser:", 20, xalign = 0.0)
-        table.attach(label, 8, 9, 4, 5)
-        table.attach(self._reverser, 9, 12, 4, 5)
-
-        self._navLightsOn = gtk.Label("NAV")
-        table.attach(self._navLightsOn, 0, 1, 5, 6)
-
-        self._antiCollisionLightsOn = gtk.Label("ANTICOLLISION")
-        table.attach(self._antiCollisionLightsOn, 1, 3, 5, 6)
-
-        self._strobeLightsOn = gtk.Label("STROBE")
-        table.attach(self._strobeLightsOn, 3, 4, 5, 6)
-
-        self._landingLightsOn = gtk.Label("LANDING")
-        table.attach(self._landingLightsOn, 4, 5, 5, 6)
-
-        self._pitotHeatOn = gtk.Label("PITOT HEAT")
-        table.attach(self._pitotHeatOn, 5, 7, 5, 6)
-
-        self._parking = gtk.Label("PARKING")
-        table.attach(self._parking, 7, 8, 5, 6)
-
-        self._gearsDown = gtk.Label("GEARS DOWN")
-        table.attach(self._gearsDown, 8, 10, 5, 6)
-
-        self._spoilersArmed = gtk.Label("SPOILERS ARMED")
-        table.attach(self._spoilersArmed, 10, 12, 5, 6)
-
-        (label, self._spoilersExtension) = self._createLabeledEntry("Spoilers:", 3)
-        table.attach(label, 0, 1, 6, 7)
-        table.attach(self._spoilersExtension, 1, 2, 6, 7)
-
-        (label, self._windSpeed) = self._createLabeledEntry("Wind speed:", 3)
-        table.attach(label, 2, 3, 6, 7)
-        table.attach(self._windSpeed, 3, 4, 6, 7)
-
-        (label, self._windDirection) = self._createLabeledEntry("Wind from:", 3)
-        table.attach(label, 4, 5, 6, 7)
-        table.attach(self._windDirection, 5, 6, 6, 7)
-
-        frameAlignment.add(table)
-
-        dataFrame.add(frameAlignment)
-
-        self._setData()
-
-        return dataFrame        
-
-    def _setData(self, aircraftState = None):
-        """Set the data.
-
-        If aircraftState is None, everything will be set to its default."""
-        if aircraftState is None:
-            self._timestamp.set_text("--:--:--")
-            self._paused.set_sensitive(False)
-            self._trickMode.set_sensitive(False)
-            self._overspeed.set_sensitive(False)
-            self._stalled.set_sensitive(False)
-            self._onTheGround.set_sensitive(False)
-            self._zfw.set_text("-")
-            self._grossWeight.set_text("-")
-            self._heading.set_text("-")
-            self._pitch.set_text("-")
-            self._bank.set_text("-")
-            self._vs.set_text("-")
-            self._ias.set_text("-")
-            self._mach.set_text("-")
-            self._groundSpeed.set_text("-")
-            self._radioAltitude.set_text("-")
-            self._altitude.set_text("-")
-            self._gLoad.set_text("-")
-            self._flapsSet.set_text("-")
-            self._flaps.set_text("-")
-            self._altimeter.set_text("-")
-            self._squawk.set_text("-")
-            self._nav1.set_text("-")
-            self._nav2.set_text("-")
-            self._fuel.set_text("-")
-            self._n1.set_text("-")
-            self._reverser.set_text("-")
-            self._navLightsOn.set_sensitive(False)
-            self._antiCollisionLightsOn.set_sensitive(False)
-            self._strobeLightsOn.set_sensitive(False)
-            self._landingLightsOn.set_sensitive(False)
-            self._pitotHeatOn.set_sensitive(False)
-            self._parking.set_sensitive(False)
-            self._gearsDown.set_sensitive(False)
-            self._spoilersArmed.set_sensitive(False)
-            self._spoilersExtension.set_text("-")
-            self._windSpeed.set_text("-")
-            self._windDirection.set_text("-")
-        else:
-            self._timestamp.set_text(time.strftime("%H:%M:%S",
-                                                   time.gmtime(aircraftState.timestamp)))
-            self._paused.set_sensitive(aircraftState.paused)
-            self._trickMode.set_sensitive(aircraftState.trickMode)
-            self._overspeed.set_sensitive(aircraftState.overspeed)
-            self._stalled.set_sensitive(aircraftState.stalled)
-            self._onTheGround.set_sensitive(aircraftState.onTheGround)
-            self._zfw.set_text("%.0f" % (aircraftState.zfw,))
-            self._grossWeight.set_text("%.0f" % (aircraftState.grossWeight,))
-            self._heading.set_text("%03.0f" % (aircraftState.heading,))
-            self._pitch.set_text("%.0f" % (aircraftState.pitch,))
-            self._bank.set_text("%.0f" % (aircraftState.bank,))
-            self._vs.set_text("%.0f" % (aircraftState.vs,))
-            self._ias.set_text("%.0f" % (aircraftState.ias,))
-            self._mach.set_text("%.2f" % (aircraftState.mach,))
-            self._groundSpeed.set_text("%.0f" % (aircraftState.groundSpeed,))
-            self._radioAltitude.set_text("%.0f" % (aircraftState.radioAltitude,))
-            self._altitude.set_text("%.0f" % (aircraftState.altitude,))
-            self._gLoad.set_text("%.2f" % (aircraftState.gLoad,))
-            self._flapsSet.set_text("%.0f" % (aircraftState.flapsSet,))
-            self._flaps.set_text("%.0f" % (aircraftState.flaps,))
-            self._altimeter.set_text("%.0f" % (aircraftState.altimeter,))
-            self._squawk.set_text(aircraftState.squawk)
-            self._nav1.set_text(aircraftState.nav1)
-            self._nav2.set_text(aircraftState.nav2)
-
-            fuelStr = ""
-            for fuel in aircraftState.fuel:
-                if fuelStr: fuelStr += ", "
-                fuelStr += "%.0f" % (fuel,)
-            self._fuel.set_text(fuelStr)
-
-            if hasattr(aircraftState, "n1"):
-                n1Str = ""
-                for n1 in aircraftState.n1:
-                    if n1Str: n1Str += ", "
-                    n1Str += "%.0f" % (n1,)
-            elif hasattr(aircraftState, "rpm"):
-                n1Str = ""
-                for rpm in aircraftState.rpm:
-                    if n1Str: n1Str += ", "
-                    n1Str += "%.0f" % (rpm,)
-            else:
-                n1Str = "-"
-            self._n1.set_text(n1Str)
-
-            reverserStr = ""
-            for reverser in aircraftState.reverser:
-                if reverserStr: reverserStr += ", "
-                reverserStr += "ON" if reverser else "OFF"
-            self._reverser.set_text(reverserStr)
-
-            self._navLightsOn.set_sensitive(aircraftState.navLightsOn)
-            self._antiCollisionLightsOn.set_sensitive(aircraftState.antiCollisionLightsOn)
-            self._strobeLightsOn.set_sensitive(aircraftState.strobeLightsOn)
-            self._landingLightsOn.set_sensitive(aircraftState.landingLightsOn)
-            self._pitotHeatOn.set_sensitive(aircraftState.pitotHeatOn)
-            self._parking.set_sensitive(aircraftState.parking)
-            self._gearsDown.set_sensitive(aircraftState.gearsDown)
-            self._spoilersArmed.set_sensitive(aircraftState.spoilersArmed)
-            self._spoilersExtension.set_text("%.0f" % (aircraftState.spoilersExtension,))
-            self._windSpeed.set_text("%.0f" % (aircraftState.windSpeed,))
-            self._windDirection.set_text("%03.0f" % (aircraftState.windDirection,))
-
-    def _buildLogFrame(self):
-        """Build the frame for the log."""
-        logFrame = gtk.Frame(label = "Log")
-
-        frameAlignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
-
-        frameAlignment.set_padding(padding_top = 4, padding_bottom = 10,
-                                   padding_left = 16, padding_right = 16)
-
-        logFrame.add(frameAlignment)
-
-        logScroller = gtk.ScrolledWindow()
-        self._logView = gtk.TextView()
-        self._logView.set_editable(False)
-        logScroller.add(self._logView)
-
-        logBox = gtk.VBox()
-        logBox.pack_start(logScroller, True, True, 0)
-        logBox.set_size_request(-1, 200)
-
-        frameAlignment.add(logBox)
-
-        return logFrame
-
-    def _writeLog(self, msg):
-        """Write the given message to the log."""
-        buffer = self._logView.get_buffer()
-        buffer.insert(buffer.get_end_iter(), msg)
-        self._logView.scroll_mark_onscreen(buffer.get_insert())
-
-def main():
-    """The main operation of the program."""
-    gobject.threads_init()
-    gui = GUI()
-    gui.build()
-    gui.run()
-              
-if __name__ == "__main__":
-    main()
-
-
Index: src/pyuipc_emu.py
===================================================================
--- src/pyuipc_emu.py	(revision 3)
+++ src/pyuipc_emu.py	(revision 3)
@@ -0,0 +1,280 @@
+# Emulator for the pyuipc module
+#------------------------------------------------------------------------------
+
+import time
+
+#------------------------------------------------------------------------------
+
+# The ratio between kg and lbs
+KGTOLBS=1/0.4536
+
+# Feet to metres
+FEETTOMETRES=0.3048
+
+#------------------------------------------------------------------------------
+
+def interpolate_proportion(proportion, value0, value1):
+    """
+    Interpolate between value0 and value1 using the given proportion.
+    """
+    return value0 + (value1-value0)*1.0*proportion
+
+#------------------------------------------------------------------------------
+
+def interpolate(point0, point, point1, value0, value1):
+    """
+    Interpolate linearly between the given points for the given
+    values.
+    """
+    if point0==point1:
+        if point>=point1: return value1
+        else: return value0
+    else:
+        return interpolate_proportion((point-point0)*1.0/(point1-point0),
+                                      value0, value1)
+
+#------------------------------------------------------------------------------
+
+# Version constants
+SIM_ANY=0
+SIM_FS98=1
+SIM_FS2K=2
+SIM_CFS2=3
+SIM_CFS1=4
+SIM_FLY=5
+SIM_FS2K2=6
+SIM_FS2K4=7
+
+#------------------------------------------------------------------------------
+
+# Error constants
+ERR_OK=0
+ERR_OPEN=1
+ERR_NOFS=2
+ERR_REGMSG=3
+ERR_ATOM=4
+ERR_MAP=5
+ERR_VIEW=6
+ERR_VERSION=7
+ERR_WRONGFS=8
+ERR_NOTOPEN=9
+ERR_NODATA=10
+ERR_TIMEOUT=11
+ERR_SENDMSG=12
+ERR_DATA=13
+ERR_RUNNING=14
+ERR_SIZE=15
+
+#------------------------------------------------------------------------------
+
+# The version of FSUIPC
+fsuipc_version=0x0401
+lib_version=0x0302
+fs_version=SIM_FS2K4
+
+#------------------------------------------------------------------------------
+
+class FSUIPCException(Exception):
+    """
+    FSUIPC exception class. It contains a member variable named
+    errorCode. The string is a text describing the error.
+    """
+
+    errors=["OK",
+            "Attempt to Open when already Open",
+            "Cannot link to FSUIPC or WideClient",
+            "Failed to Register common message with Windows",
+            "Failed to create Atom for mapping filename",
+            "Failed to create a file mapping object",
+            "Failed to open a view to the file map",
+            "Incorrect version of FSUIPC, or not FSUIPC",
+            "Sim is not version requested",
+            "Call cannot execute, link not Open",
+            "Call cannot execute: no requests accumulated",
+            "IPC timed out all retries",
+            "IPC sendmessage failed all retries",
+            "IPC request contains bad data",
+            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
+            "Read or Write request cannot be added, memory for Process is full"]
+
+    def __init__(self, errorCode):
+        """
+        Construct the exception
+        """
+        if errorCode<len(self.errors):
+            self.errorString =  self.errors[errorCode]
+        else:
+            self.errorString = "Unknown error"
+        Exception.__init__(self, self.errorString)
+        self.errorCode = errorCode
+
+    def __str__(self):
+        """
+        Convert the excption to string
+        """
+        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
+
+#------------------------------------------------------------------------------
+
+open_count=0
+read_count=0
+
+#------------------------------------------------------------------------------
+
+def open(request):
+    """
+    Open the connection.
+    """
+
+    global open_count, read_count
+    
+    open_count += 1
+    if open_count<5:
+        raise FSUIPCException(open_count+2)
+    elif open_count==5:
+        return True
+    else:
+        raise FSUIPCException(ERR_OPEN)
+
+#------------------------------------------------------------------------------
+
+def prepare_data(pattern, forRead = True):
+    """
+    Prepate the given pattern for reading and/or writing
+    """
+    return pattern
+
+#------------------------------------------------------------------------------
+
+flight=None
+schedule_points=None
+
+#------------------------------------------------------------------------------
+
+def set_flight(f):
+    """
+    Set the flight to use for providing the data
+    """
+
+    global flight, schedule_points, read_count
+
+    flight=f
+    schedule_points=flight.get_schedule_points()
+    read_count = 0
+
+#------------------------------------------------------------------------------
+
+fuel_weight=4.5
+centre_tank_capacity=12900
+side_tank_capacity=3900
+
+def read(data):
+    """
+    Read the given data.
+    """
+
+    global open_count, read_count
+    if open_count<5:
+        raise FSUIPCException(ERR_NOTOPEN)
+    
+    global flight, schedule_points
+
+    tm=time.gmtime()
+
+    if read_count<=10:
+        latitude = 0
+        longitude = 0
+        altitude = 0
+        tas = 0
+        vs = 0.0
+        fuel_flow = 0.0
+        fuel_remaining = 1000
+        ground_altitude = 0
+    else:
+        dist = read_count - 10
+
+        latitude = 0
+        longitude = 0
+        altitude = dist*20
+        tas = 100+dist*20
+        vs = dist*100.0
+        fuel_flow = 3.0
+        fuel_remaining = 1000 - dist*2
+
+        ground_altitude = 0
+        
+    results=[]
+    for (offset, type) in data:
+        if offset==0x02b8:    # True airspeed (128*knots)
+            results.append(int(tas*128))
+        elif offset==0x02c8:    # Vertical speed (256*m/s)
+            results.append(int(vs / 60.0 * 0.3048 * 256))
+        elif offset==0x0560:  # Latitude
+            results.append(long(latitude*10001750.0*65536.0*65536.0/90.0))
+        elif offset==0x0568:  # Longitude
+            results.append(long(longitude*65536.9*65536.0*65536.0*65536.0/360.0))
+        elif offset==0x0570:  # Aircraft altitude in metres (fractional part)
+            results.append(long( (altitude*0.3048*65536*65536)%(65536*65536)))
+        elif offset==0x0574:  # Aircraft altitude in metres (whole part)
+            results.append(int(altitude*.3048))
+        elif offset==0x0918 or \
+             offset==0x09b0:  # Engine 1 and 2 fuel flow (pounds per hour)
+            results.append(fuel_flow*KGTOLBS/2.0)
+        elif offset==0x0a48 or \
+             offset==0x0ae0:  # Engine 3 and 4 fuel flow (pounds per hour)
+            results.append(0.0)
+        elif offset==0x0aec:  # Number of engines
+            results.append(2)
+        elif offset==0x0af4:  # Fuel weight (pounds per gallon)
+            results.append(int(round(fuel_weight * 256)))
+        elif offset==0x0b74:  # Centre tank level (%*128*65536)
+            centre_fuel = fuel_remaining - 2*side_tank_capacity
+            if centre_fuel<0: centre_fuel = 0.0
+            results.append(long(round(centre_fuel/centre_tank_capacity*128.0*65536.0)))
+        elif offset==0x0b78:  # Centre tank capacity (gallons)
+            results.append(long(round(centre_tank_capacity*KGTOLBS/fuel_weight)))
+        elif offset==0x0b7c or \
+             offset==0x0b94:  # Left and right main tank level (%*128*65536)
+            fuel = fuel_remaining/2
+            if fuel>side_tank_capacity: fuel = side_tank_capacity
+            results.append(long(round(fuel/side_tank_capacity*128.0*65536.0)))
+        elif offset==0x0b80 or \
+             offset==0x0b98:  # Left and right main tank capacity (gallons)
+            results.append(long(round(side_tank_capacity*KGTOLBS/fuel_weight)))
+        elif offset in [0x0b84, 0x0b88, 0x0b8c, 0x0b90,
+                        0x0b9c, 0x0ba0, 0x0ba4, 0x0ba8,
+                        0x1244, 0x1248, 0x124c, 0x1250,
+                        0x1254, 0x1258, 0x125c, 0x1260]:
+                              # Other tank capacities and levels
+            results.append(long(0))
+        elif offset==0x023a:  # Second of time
+            results.append(int(tm[5]))
+        elif offset==0x023b:  # Hour of UTC time
+            results.append(int(tm[3]))
+        elif offset==0x023c:  # Minute of UTC time
+            results.append(int(tm[4]))
+        elif offset==0x023e:  # Day number in year
+            results.append(int(tm[7]))
+        elif offset==0x0240:  # Year
+            results.append(int(tm[0]))
+        elif offset==0x0020:  # Ground altitude (metres*256)
+            results.append(int(round(ground_altitude*FEETTOMETRES*256.0)))
+        else:
+            raise FSUIPCException(ERR_DATA)
+
+    read_count += 1
+
+    return results
+                
+
+#------------------------------------------------------------------------------
+
+def close():
+    """
+    Close the connection
+    """
+    global open_count
+    open_count = 0
+
+#------------------------------------------------------------------------------
+
Index: src/pyuipc_emu.py
===================================================================
--- src/pyuipc_emu.py	(revision 3)
+++ 	(revision )
@@ -1,280 +1,0 @@
-# Emulator for the pyuipc module
-#------------------------------------------------------------------------------
-
-import time
-
-#------------------------------------------------------------------------------
-
-# The ratio between kg and lbs
-KGTOLBS=1/0.4536
-
-# Feet to metres
-FEETTOMETRES=0.3048
-
-#------------------------------------------------------------------------------
-
-def interpolate_proportion(proportion, value0, value1):
-    """
-    Interpolate between value0 and value1 using the given proportion.
-    """
-    return value0 + (value1-value0)*1.0*proportion
-
-#------------------------------------------------------------------------------
-
-def interpolate(point0, point, point1, value0, value1):
-    """
-    Interpolate linearly between the given points for the given
-    values.
-    """
-    if point0==point1:
-        if point>=point1: return value1
-        else: return value0
-    else:
-        return interpolate_proportion((point-point0)*1.0/(point1-point0),
-                                      value0, value1)
-
-#------------------------------------------------------------------------------
-
-# Version constants
-SIM_ANY=0
-SIM_FS98=1
-SIM_FS2K=2
-SIM_CFS2=3
-SIM_CFS1=4
-SIM_FLY=5
-SIM_FS2K2=6
-SIM_FS2K4=7
-
-#------------------------------------------------------------------------------
-
-# Error constants
-ERR_OK=0
-ERR_OPEN=1
-ERR_NOFS=2
-ERR_REGMSG=3
-ERR_ATOM=4
-ERR_MAP=5
-ERR_VIEW=6
-ERR_VERSION=7
-ERR_WRONGFS=8
-ERR_NOTOPEN=9
-ERR_NODATA=10
-ERR_TIMEOUT=11
-ERR_SENDMSG=12
-ERR_DATA=13
-ERR_RUNNING=14
-ERR_SIZE=15
-
-#------------------------------------------------------------------------------
-
-# The version of FSUIPC
-fsuipc_version=0x0401
-lib_version=0x0302
-fs_version=SIM_FS2K4
-
-#------------------------------------------------------------------------------
-
-class FSUIPCException(Exception):
-    """
-    FSUIPC exception class. It contains a member variable named
-    errorCode. The string is a text describing the error.
-    """
-
-    errors=["OK",
-            "Attempt to Open when already Open",
-            "Cannot link to FSUIPC or WideClient",
-            "Failed to Register common message with Windows",
-            "Failed to create Atom for mapping filename",
-            "Failed to create a file mapping object",
-            "Failed to open a view to the file map",
-            "Incorrect version of FSUIPC, or not FSUIPC",
-            "Sim is not version requested",
-            "Call cannot execute, link not Open",
-            "Call cannot execute: no requests accumulated",
-            "IPC timed out all retries",
-            "IPC sendmessage failed all retries",
-            "IPC request contains bad data",
-            "Maybe running on WideClient, but FS not running on Server, or wrong FSUIPC",
-            "Read or Write request cannot be added, memory for Process is full"]
-
-    def __init__(self, errorCode):
-        """
-        Construct the exception
-        """
-        if errorCode<len(self.errors):
-            self.errorString =  self.errors[errorCode]
-        else:
-            self.errorString = "Unknown error"
-        Exception.__init__(self, self.errorString)
-        self.errorCode = errorCode
-
-    def __str__(self):
-        """
-        Convert the excption to string
-        """
-        return "FSUIPC error: %d (%s)" % (self.errorCode, self.errorString)
-
-#------------------------------------------------------------------------------
-
-open_count=0
-read_count=0
-
-#------------------------------------------------------------------------------
-
-def open(request):
-    """
-    Open the connection.
-    """
-
-    global open_count, read_count
-    
-    open_count += 1
-    if open_count<5:
-        raise FSUIPCException(open_count+2)
-    elif open_count==5:
-        return True
-    else:
-        raise FSUIPCException(ERR_OPEN)
-
-#------------------------------------------------------------------------------
-
-def prepare_data(pattern, forRead = True):
-    """
-    Prepate the given pattern for reading and/or writing
-    """
-    return pattern
-
-#------------------------------------------------------------------------------
-
-flight=None
-schedule_points=None
-
-#------------------------------------------------------------------------------
-
-def set_flight(f):
-    """
-    Set the flight to use for providing the data
-    """
-
-    global flight, schedule_points, read_count
-
-    flight=f
-    schedule_points=flight.get_schedule_points()
-    read_count = 0
-
-#------------------------------------------------------------------------------
-
-fuel_weight=4.5
-centre_tank_capacity=12900
-side_tank_capacity=3900
-
-def read(data):
-    """
-    Read the given data.
-    """
-
-    global open_count, read_count
-    if open_count<5:
-        raise FSUIPCException(ERR_NOTOPEN)
-    
-    global flight, schedule_points
-
-    tm=time.gmtime()
-
-    if read_count<=10:
-        latitude = 0
-        longitude = 0
-        altitude = 0
-        tas = 0
-        vs = 0.0
-        fuel_flow = 0.0
-        fuel_remaining = 1000
-        ground_altitude = 0
-    else:
-        dist = read_count - 10
-
-        latitude = 0
-        longitude = 0
-        altitude = dist*20
-        tas = 100+dist*20
-        vs = dist*100.0
-        fuel_flow = 3.0
-        fuel_remaining = 1000 - dist*2
-
-        ground_altitude = 0
-        
-    results=[]
-    for (offset, type) in data:
-        if offset==0x02b8:    # True airspeed (128*knots)
-            results.append(int(tas*128))
-        elif offset==0x02c8:    # Vertical speed (256*m/s)
-            results.append(int(vs / 60.0 * 0.3048 * 256))
-        elif offset==0x0560:  # Latitude
-            results.append(long(latitude*10001750.0*65536.0*65536.0/90.0))
-        elif offset==0x0568:  # Longitude
-            results.append(long(longitude*65536.9*65536.0*65536.0*65536.0/360.0))
-        elif offset==0x0570:  # Aircraft altitude in metres (fractional part)
-            results.append(long( (altitude*0.3048*65536*65536)%(65536*65536)))
-        elif offset==0x0574:  # Aircraft altitude in metres (whole part)
-            results.append(int(altitude*.3048))
-        elif offset==0x0918 or \
-             offset==0x09b0:  # Engine 1 and 2 fuel flow (pounds per hour)
-            results.append(fuel_flow*KGTOLBS/2.0)
-        elif offset==0x0a48 or \
-             offset==0x0ae0:  # Engine 3 and 4 fuel flow (pounds per hour)
-            results.append(0.0)
-        elif offset==0x0aec:  # Number of engines
-            results.append(2)
-        elif offset==0x0af4:  # Fuel weight (pounds per gallon)
-            results.append(int(round(fuel_weight * 256)))
-        elif offset==0x0b74:  # Centre tank level (%*128*65536)
-            centre_fuel = fuel_remaining - 2*side_tank_capacity
-            if centre_fuel<0: centre_fuel = 0.0
-            results.append(long(round(centre_fuel/centre_tank_capacity*128.0*65536.0)))
-        elif offset==0x0b78:  # Centre tank capacity (gallons)
-            results.append(long(round(centre_tank_capacity*KGTOLBS/fuel_weight)))
-        elif offset==0x0b7c or \
-             offset==0x0b94:  # Left and right main tank level (%*128*65536)
-            fuel = fuel_remaining/2
-            if fuel>side_tank_capacity: fuel = side_tank_capacity
-            results.append(long(round(fuel/side_tank_capacity*128.0*65536.0)))
-        elif offset==0x0b80 or \
-             offset==0x0b98:  # Left and right main tank capacity (gallons)
-            results.append(long(round(side_tank_capacity*KGTOLBS/fuel_weight)))
-        elif offset in [0x0b84, 0x0b88, 0x0b8c, 0x0b90,
-                        0x0b9c, 0x0ba0, 0x0ba4, 0x0ba8,
-                        0x1244, 0x1248, 0x124c, 0x1250,
-                        0x1254, 0x1258, 0x125c, 0x1260]:
-                              # Other tank capacities and levels
-            results.append(long(0))
-        elif offset==0x023a:  # Second of time
-            results.append(int(tm[5]))
-        elif offset==0x023b:  # Hour of UTC time
-            results.append(int(tm[3]))
-        elif offset==0x023c:  # Minute of UTC time
-            results.append(int(tm[4]))
-        elif offset==0x023e:  # Day number in year
-            results.append(int(tm[7]))
-        elif offset==0x0240:  # Year
-            results.append(int(tm[0]))
-        elif offset==0x0020:  # Ground altitude (metres*256)
-            results.append(int(round(ground_altitude*FEETTOMETRES*256.0)))
-        else:
-            raise FSUIPCException(ERR_DATA)
-
-    read_count += 1
-
-    return results
-                
-
-#------------------------------------------------------------------------------
-
-def close():
-    """
-    Close the connection
-    """
-    global open_count
-    open_count = 0
-
-#------------------------------------------------------------------------------
-
Index: src/runmlx.py
===================================================================
--- src/runmlx.py	(revision 20)
+++ src/runmlx.py	(revision 20)
@@ -0,0 +1,5 @@
+# Script to run the logger
+
+if __name__ == "__main__":
+    import mlx
+    mlx.main()
Index: src/runmlx.py
===================================================================
--- src/runmlx.py	(revision 20)
+++ 	(revision )
@@ -1,5 +1,0 @@
-# Script to run the logger
-
-if __name__ == "__main__":
-    import mlx
-    mlx.main()
Index: src/test.py
===================================================================
--- src/test.py	(revision 18)
+++ src/test.py	(revision 18)
@@ -0,0 +1,35 @@
+# Test module
+
+import fs
+import flight
+import logger
+import acft
+import const
+
+import time
+import sys
+
+def callback(data, extra):
+    print data
+
+def main():
+    with open(sys.argv[1], "wt") as output:
+        fl = flight.Flight(logger.Logger(output = output))
+        fl.cruiseAltitude = 18000
+        fl.aircraftType = const.AIRCRAFT_DH8D
+        fl.aircraft = acft.Aircraft.create(fl)
+        #fl._stage = const.STAGE_LANDING
+        simulator = fs.createSimulator(const.SIM_MSFS9, fs.ConnectionListener(),
+                                       fl.aircraft)
+        fl.simulator = simulator
+
+        simulator.connect()
+        simulator.startMonitoring()
+        
+        fl.wait()
+
+        simulator.stopMonitoring()
+        simulator.disconnect()
+
+if __name__ == "__main__":
+    main()
Index: src/test.py
===================================================================
--- src/test.py	(revision 18)
+++ 	(revision )
@@ -1,35 +1,0 @@
-# Test module
-
-import fs
-import flight
-import logger
-import acft
-import const
-
-import time
-import sys
-
-def callback(data, extra):
-    print data
-
-def main():
-    with open(sys.argv[1], "wt") as output:
-        fl = flight.Flight(logger.Logger(output = output))
-        fl.cruiseAltitude = 18000
-        fl.aircraftType = const.AIRCRAFT_DH8D
-        fl.aircraft = acft.Aircraft.create(fl)
-        #fl._stage = const.STAGE_LANDING
-        simulator = fs.createSimulator(const.SIM_MSFS9, fs.ConnectionListener(),
-                                       fl.aircraft)
-        fl.simulator = simulator
-
-        simulator.connect()
-        simulator.startMonitoring()
-        
-        fl.wait()
-
-        simulator.stopMonitoring()
-        simulator.disconnect()
-
-if __name__ == "__main__":
-    main()
Index: src/util.py
===================================================================
--- src/util.py	(revision 6)
+++ src/util.py	(revision 6)
@@ -0,0 +1,2 @@
+# Various utilities
+
Index: src/util.py
===================================================================
--- src/util.py	(revision 6)
+++ 	(revision )
@@ -1,2 +1,0 @@
-# Various utilities
-
