Index: src/acft.py
===================================================================
--- src/acft.py	(revision 8)
+++ src/acft.py	(revision 8)
@@ -0,0 +1,294 @@
+# 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._checkers = []
+
+        self._checkers.append(checks.StageChecker())
+
+        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())
+
+    @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 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._aircraftState = aircraftState
+    
+    def setStage(self, aircraftState, newStage):
+        """Set the given stage as the new one and do whatever should be
+        done."""
+        self._flight.setStage(aircraftState.timestamp, newStage)
+
+    def flare(self):
+        """Called when it is detected that we are during flare.
+
+        On the first call, it should start monitoring some parameters more
+        closely to determine flare time."""
+        pass
+
+    def cancelFlare(self):
+        """Cancel flare, if it has started."""
+        pass
+
+    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"""
+    pass
+
+#---------------------------------------------------------------------------------------
+
+class B736(Boeing737):
+    """Boeing 737-600 aircraft."""
+    def __init__(self, flight):
+        super(B736, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B737(Boeing737):
+    """Boeing 737-700 aircraft."""
+    def __init__(self, flight):
+        super(B737, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B738(Boeing737):
+    """Boeing 737-800 aircraft."""
+    def __init__(self, flight):
+        super(B738, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B733(Boeing737):
+    """Boeing 737-300 aircraft."""
+    def __init__(self, flight):
+        super(B733, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B734(Boeing737):
+    """Boeing 737-400 aircraft."""
+    def __init__(self, flight):
+        super(B734, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B735(Boeing737):
+    """Boeing 737-500 aircraft."""
+    def __init__(self, flight):
+        super(B735, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+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"""
+
+#---------------------------------------------------------------------------------------
+
+class B762(Boeing767):
+    """Boeing 767-200 aircraft."""
+    def __init__(self, flight):
+        super(B762, self).__init__(flight)
+
+#---------------------------------------------------------------------------------------
+
+class B763(Boeing767):
+    """Boeing 767-300 aircraft."""
+    def __init__(self, flight):
+        super(B763, self).__init__(cflight)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+    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)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+#---------------------------------------------------------------------------------------
+
+_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 7)
+++ 	(revision )
@@ -1,356 +1,0 @@
-# Module for the simulator-independent aircraft classes
-
-#---------------------------------------------------------------------------------------
-
-import time
-
-import const
-
-#---------------------------------------------------------------------------------------
-
-class Aircraft(object):
-    """Base class for aircraft."""
-    def __init__(self, type):
-        """Construct the aircraft for the given type."""
-        self._type = type
-        self._aircraftState = None
-
-    @property
-    def type(self):
-        """Get the type of the aircraft."""
-        return self._type
-
-    def modelChanged(self, aircraftName, modelName):
-        """Called when the simulator's aircraft changes."""
-        print "Aircraft.modelChanged: aircraftName='%s', modelName='%s'" % \
-            (aircraftName, modelName)
-
-    def handleState(self, aircraftState):
-        """Called when the state of the aircraft changes."""
-        timeStr = time.ctime(aircraftState.timestamp)
-
-        if self._aircraftState is None or \
-           self._aircraftState.paused != aircraftState.paused:
-            print "Aircraft.handleState: %s: paused=%d" % \
-                (timeStr, aircraftState.paused)
-
-        if self._aircraftState is None or \
-           self._aircraftState.trickMode != aircraftState.trickMode:
-            print "Aircraft.handleState: %s: trickMode=%d" % \
-                (timeStr, aircraftState.trickMode)
-
-        if self._aircraftState is None or \
-           self._aircraftState.overspeed != aircraftState.overspeed:
-            print "Aircraft.handleState: %s: overspeed=%d" % \
-                (timeStr, aircraftState.overspeed)
-
-        if self._aircraftState is None or \
-           self._aircraftState.stalled != aircraftState.stalled:
-            print "Aircraft.handleState: %s: stalled=%d" % \
-                (timeStr, aircraftState.stalled)
-
-        if self._aircraftState is None or \
-           self._aircraftState.onTheGround != aircraftState.onTheGround:
-            print "Aircraft.handleState: %s: onTheGround=%d" % \
-                (timeStr, aircraftState.onTheGround)
-
-        if self._aircraftState is None or \
-           self._aircraftState.grossWeight != aircraftState.grossWeight:
-            print "Aircraft.handleState: %s: grossWeight=%f" % \
-                (timeStr, aircraftState.grossWeight)
-
-        if self._aircraftState is None or \
-           self._aircraftState.heading != aircraftState.heading:
-            print "Aircraft.handleState: %s: heading=%f" % \
-                (timeStr, aircraftState.heading)
-
-        if self._aircraftState is None or \
-           self._aircraftState.pitch != aircraftState.pitch:
-            print "Aircraft.handleState: %s: pitch=%f" % \
-                (timeStr, aircraftState.pitch)
-
-        if self._aircraftState is None or \
-           self._aircraftState.bank != aircraftState.bank:
-            print "Aircraft.handleState: %s: bank=%f" % \
-                (timeStr, aircraftState.bank)
-
-        if self._aircraftState is None or \
-           self._aircraftState.ias != aircraftState.ias:
-            print "Aircraft.handleState: %s: ias=%f" % \
-                (timeStr, aircraftState.ias)
-
-        if self._aircraftState is None or \
-           self._aircraftState.groundSpeed != aircraftState.groundSpeed:
-            print "Aircraft.handleState: %s: groundSpeed=%f" % \
-                (timeStr, aircraftState.groundSpeed)
-
-        if self._aircraftState is None or \
-           self._aircraftState.vs != aircraftState.vs:
-            print "Aircraft.handleState: %s: vs=%f" % \
-                (timeStr, aircraftState.vs)
-
-        if self._aircraftState is None or \
-           self._aircraftState.altitude != aircraftState.altitude:
-            print "Aircraft.handleState: %s: altitude=%f" % \
-                (timeStr, aircraftState.altitude)
-
-        if self._aircraftState is None or \
-           self._aircraftState.gLoad != aircraftState.gLoad:
-            print "Aircraft.handleState: %s: gLoad=%f" % \
-                (timeStr, aircraftState.gLoad)
-
-        if self._aircraftState is None or \
-           self._aircraftState.flapsSet != aircraftState.flapsSet:
-            print "Aircraft.handleState: %s: flapsSet=%f" % \
-                (timeStr, aircraftState.flapsSet)
-
-        if self._aircraftState is None or \
-           self._aircraftState.flaps != aircraftState.flaps:
-            print "Aircraft.handleState: %s: flaps=%f" % \
-                (timeStr, aircraftState.flaps)
-
-        if self._aircraftState is None or \
-           self._aircraftState.navLightsOn != aircraftState.navLightsOn:
-            print "Aircraft.handleState: %s: navLightsOn=%d" % \
-                (timeStr, aircraftState.navLightsOn)
-
-        if self._aircraftState is None or \
-           self._aircraftState.antiCollisionLightsOn != aircraftState.antiCollisionLightsOn:
-            print "Aircraft.handleState: %s: antiCollisionLightsOn=%d" % \
-                (timeStr, aircraftState.antiCollisionLightsOn)
-
-        if self._aircraftState is None or \
-           self._aircraftState.strobeLightsOn != aircraftState.strobeLightsOn:
-            print "Aircraft.handleState: %s: strobeLightsOn=%d" % \
-                (timeStr, aircraftState.strobeLightsOn)
-
-        if self._aircraftState is None or \
-           self._aircraftState.landingLightsOn != aircraftState.landingLightsOn:
-            print "Aircraft.handleState: %s: landingLightsOn=%d" % \
-                (timeStr, aircraftState.landingLightsOn)
-
-        if self._aircraftState is None or \
-           self._aircraftState.pitotHeatOn != aircraftState.pitotHeatOn:
-            print "Aircraft.handleState: %s: pitotHeatOn=%d" % \
-                (timeStr, aircraftState.pitotHeatOn)
-
-        if self._aircraftState is None or \
-           self._aircraftState.gearsDown != aircraftState.gearsDown:
-            print "Aircraft.handleState: %s: gearsDown=%f" % \
-                (timeStr, aircraftState.gearsDown)
-
-        if self._aircraftState is None or \
-           self._aircraftState.spoilersArmed != aircraftState.spoilersArmed:
-            print "Aircraft.handleState: %s: spoilersArmed=%f" % \
-                (timeStr, aircraftState.spoilersArmed)
-
-        if self._aircraftState is None or \
-           self._aircraftState.spoilersExtension != aircraftState.spoilersExtension:
-            print "Aircraft.handleState: %s: spoilersExtension=%f" % \
-                (timeStr, aircraftState.spoilersExtension)
-
-        if self._aircraftState is None or \
-           self._aircraftState.altimeter != aircraftState.altimeter:
-            print "Aircraft.handleState: %s: altimeter=%f" % \
-                (timeStr, aircraftState.altimeter)
-
-        if self._aircraftState is None or \
-           self._aircraftState.nav1 != aircraftState.nav1:
-            print "Aircraft.handleState: %s: nav1=%s" % \
-                (timeStr, aircraftState.nav1)
-
-        if self._aircraftState is None or \
-           self._aircraftState.nav2 != aircraftState.nav2:
-            print "Aircraft.handleState: %s: nav2=%s" % \
-                (timeStr, aircraftState.nav2)
-
-        if self._aircraftState is None or \
-           self._aircraftState.fuel != aircraftState.fuel:
-            print "Aircraft.handleState: %s: fuel=%s" % \
-                (timeStr, aircraftState.fuel)
-
-        if self._aircraftState is None or \
-           self._aircraftState.n1 != aircraftState.n1:
-            print "Aircraft.handleState: %s: n1=%s" % \
-                (timeStr, aircraftState.n1)
-
-        if self._aircraftState is None or \
-           self._aircraftState.reverser != aircraftState.reverser:
-            print "Aircraft.handleState: %s: reverser=%s" % \
-                (timeStr, aircraftState.reverser)
-
-        self._aircraftState = aircraftState
-            
-#---------------------------------------------------------------------------------------
-
-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"""
-    pass
-
-#---------------------------------------------------------------------------------------
-
-class B736(Boeing737):
-    """Boeing 737-600 aircraft."""
-    def __init__(self):
-        super(B736, self).__init__(const.AIRCRAFT_B736)
-
-#---------------------------------------------------------------------------------------
-
-class B737(Boeing737):
-    """Boeing 737-700 aircraft."""
-    def __init__(self):
-        super(B737, self).__init__(const.AIRCRAFT_B737)
-
-#---------------------------------------------------------------------------------------
-
-class B738(Boeing737):
-    """Boeing 737-800 aircraft."""
-    def __init__(self):
-        super(B738, self).__init__(const.AIRCRAFT_B738)
-
-#---------------------------------------------------------------------------------------
-
-class B733(Boeing737):
-    """Boeing 737-300 aircraft."""
-    def __init__(self):
-        super(B733, self).__init__(const.AIRCRAFT_B733)
-
-#---------------------------------------------------------------------------------------
-
-class B734(Boeing737):
-    """Boeing 737-400 aircraft."""
-    def __init__(self):
-        super(B734, self).__init__(const.AIRCRAFT_B734)
-
-#---------------------------------------------------------------------------------------
-
-class B735(Boeing737):
-    """Boeing 737-500 aircraft."""
-    def __init__(self):
-        super(B735, self).__init__(const.AIRCRAFT_B735)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(DH8D, self).__init__(const.AIRCRAFT_DH8D)
-
-#---------------------------------------------------------------------------------------
-
-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"""
-
-#---------------------------------------------------------------------------------------
-
-class B762(Boeing767):
-    """Boeing 767-200 aircraft."""
-    def __init__(self):
-        super(B762, self).__init__(const.AIRCRAFT_B762)
-
-#---------------------------------------------------------------------------------------
-
-class B763(Boeing767):
-    """Boeing 767-300 aircraft."""
-    def __init__(self):
-        super(B763, self).__init__(const.AIRCRAFT_B763)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(CRJ2, self).__init__(const.AIRCRAFT_CRJ2)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(F70, self).__init__(const.AIRCRAFT_F70)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(DC3, self).__init__(const.AIRCRAFT_DC3)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(T134, self).__init__(const.AIRCRAFT_T134)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(T154, self).__init__(const.AIRCRAFT_T154)
-
-#---------------------------------------------------------------------------------------
-
-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):
-        super(YK40, self).__init__(const.AIRCRAFT_YK40)
-
-#---------------------------------------------------------------------------------------
-
Index: src/checks.py
===================================================================
--- src/checks.py	(revision 8)
+++ src/checks.py	(revision 8)
@@ -0,0 +1,306 @@
+# 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.flare()
+        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 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 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(state.timestamp, 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, delay = 10.0):
+        """Construct the mixin with the given delay in seconds."""
+        self._delay = delay
+        self._oldValue = None
+        self._firstChange = 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 newValue!=self._oldValue:
+            if self._firstChange is None:
+                self._firstChange = state.timestamp
+            if state.timestamp >= (self._firstChange + self._delay):
+                self._oldValue = newValue
+                self._firstChange = None
+                return True
+        else:
+            self._firstChange = None
+
+        return False
+
+#---------------------------------------------------------------------------------------
+
+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, delay = 0.0):
+        """Construct the object."""
+        StateChangeLogger.__init__(self, logInitial = logInitial)
+        SingleValueMixin.__init__(self, attrName)
+        DelayedChangeMixin.__init__(self, delay = delay)
+        TemplateMessageMixin.__init__(self, template)
+
+#---------------------------------------------------------------------------------------
+
+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)
+
+    def _getMessage(self, state):
+        """Get the message to log on a change."""
+        return "Altimeter: %.0f hPa at %.0f feet" % (state.altimeter, state.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",
+                                           delay = 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, %f feet" % \
+            ("DOWN" if state.gearsDown else "UP", state.ias, state.altitude)
+
+#---------------------------------------------------------------------------------------
Index: src/const.py
===================================================================
--- src/const.py	(revision 8)
+++ src/const.py	(revision 8)
@@ -0,0 +1,108 @@
+# Various constants used in the logger
+
+#-------------------------------------------------------------------------------
+
+# 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
Index: src/const.py
===================================================================
--- src/const.py	(revision 7)
+++ 	(revision )
@@ -1,71 +1,0 @@
-# Various constants used in the logger
-
-#-------------------------------------------------------------------------------
-
-# 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
-
Index: src/flight.py
===================================================================
--- src/flight.py	(revision 8)
+++ src/flight.py	(revision 8)
@@ -0,0 +1,47 @@
+# Module related to the high-level tracking of the flight
+
+#---------------------------------------------------------------------------------------
+
+import const
+
+import threading
+
+#---------------------------------------------------------------------------------------
+
+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.aircraftType = None
+        self.aircraft = None
+        self.simulator = None
+
+        self._endCondition = threading.Condition()
+
+    @property
+    def stage(self):
+        """Get the flight stage."""
+        return self._stage
+
+    def setStage(self, timestamp, stage):
+        """Set the flight stage."""
+        if stage!=self._stage:
+            self._stage = stage
+            self.logger.stage(timestamp, stage)
+            if stage==const.STAGE_END:
+                with self._endCondition:
+                    self._endCondition.notify()
+
+    def wait(self):
+        """Wait for the flight to end."""
+        with self._endCondition:
+            while self._stage!=const.STAGE_END:
+                self._endCondition.wait()
+
+#---------------------------------------------------------------------------------------
Index: src/fs.py
===================================================================
--- src/fs.py	(revision 8)
+++ src/fs.py	(revision 8)
@@ -0,0 +1,110 @@
+# 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, aircraft):
+    """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==const.SIM_MSFS9, "Only MS Flight Simulator 2004 is supported"
+    import fsuipc
+    return fsuipc.Simulator(connectionListener, aircraft)
+
+#-------------------------------------------------------------------------------
+
+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
+    - 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)    
+    - 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.
+    - 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
+    - langingLightsOn: 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
+
+    FIXME: needed when taxiing only:
+    - zfw: the Zero Fuel Weight in klograms (float)
+    - payload weight
+
+    FIXME: needed for touchdown only:
+    - ambientWindDirection: the ambient wind direction around the aircraft (float)
+    - ambientWindSpeed: the ambient wind speed around the aircraft in knowns (float)
+    - tdRate: the touchdown rate calculated by FSUIPC (float)
+
+    FIXME: needed rarely:
+    - latitude, longitude
+    - transporter
+    - visibility 
+    """
+    
Index: src/fs.py
===================================================================
--- src/fs.py	(revision 6)
+++ 	(revision )
@@ -1,107 +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, aircraft):
-    """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==const.SIM_MSFS9, "Only MS Flight Simulator 2004 is supported"
-    import fsuipc
-    return fsuipc.Simulator(connectionListener, aircraft)
-
-#-------------------------------------------------------------------------------
-
-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
-    - 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)    
-    - groundSpeed: the ground speed (float)
-    - vs: the vertical speed in feet/minutes (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.
-    - 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
-    - langingLightsOn: a boolean indicating if the landing lights are on
-    - pitotHeatOn: a boolean indicating if the pitot heat is on
-    - 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)
-
-    FIXME: needed when taxiing only:
-    - zfw: the Zero Fuel Weight in klograms (float)
-    - payload weight
-
-    FIXME: needed for touchdown only:
-    - ambientWindDirection: the ambient wind direction around the aircraft (float)
-    - ambientWindSpeed: the ambient wind speed around the aircraft in knowns (float)
-    - tdRate: the touchdown rate calculated by FSUIPC (float)
-
-    FIXME: needed rarely:
-    - latitude, longitude
-    - transporter
-    - visibility 
-    """
-    
Index: src/fsuipc.py
===================================================================
--- src/fsuipc.py	(revision 8)
+++ src/fsuipc.py	(revision 8)
@@ -0,0 +1,1003 @@
+# 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 _callSafe(fun):
+        """Call the given function and swallow any exceptions."""
+        try:
+            return fun()
+        except Exception, e:
+            print >> sys.stderr, str(e)
+            return None
+
+    class Request(object):
+        """A simple, one-shot request."""
+        def __init__(self, forWrite, data, callback, extra):
+            """Construct the request."""
+            self._forWrite = forWrite
+            self._data = data
+            self._callback = callback
+            self._extra = extra
+            
+        def process(self, time):
+            """Process the request."""            
+            if self._forWrite:
+                pyuipc.write(self._data)
+                Handler._callSafe(lambda: self._callback(True, self._extra))
+            else:
+                values = pyuipc.read(self._data)
+                Handler._callSafe(lambda: self._callback(values, self._extra))
+
+            return True
+
+        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):
+            """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
+            
+        @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.
+
+            Return a boolean indicating if the request was executed."""
+            if time < self._nextFire: 
+                return False
+
+            if self._preparedData is None:
+                self._preparedData = pyuipc.prepare_data(self._data)
+                self._data = None
+                
+            values = pyuipc.read(self._preparedData)
+
+            Handler._callSafe(lambda: self._callback(values, self._extra))
+
+            while self._nextFire <= time:
+                self._nextFire += self._period
+            
+            return True
+
+        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._requests = []
+        self._nextPeriodicID = 1
+        self._periodicRequests = []
+
+        self.daemon = True
+
+    def requestRead(self, data, callback, extra = 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))
+            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 read(self, data):
+        """Read the given data synchronously.
+
+        If a problem occurs, an exception is thrown."""
+        with threading.Condition() as condition:
+            extra = [condition]
+            self._requestRead(data, self._readWriteCallback, extra)
+            while len(extra)<2:
+                condition.wait()
+            if extra[1] is None:
+                raise fs.SimulatorException("reading failed")
+            else:
+                return extra[1]
+
+    def write(self, data):
+        """Write the given data synchronously.
+
+        If a problem occurs, an exception is thrown."""
+        with threading.Condition() as condition:
+            extra = [condition]
+            self._requestWrite(data, self._writeCallback, extra)
+            while len(extra)<2:
+                condition.wait()
+            if extra[1] is None:
+                raise fs.SimulatorException("writing failed")
+
+    def requestPeriodicRead(self, period, data, callback, extra = 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)
+            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"""
+        while self._connectionRequested:
+            try:
+                pyuipc.open(pyuipc.SIM_FS2K4)
+                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))
+                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:
+                if not self._processRequests(): 
+                    return
+                timeout = None
+                if self._periodicRequests:
+                    self._periodicRequests.sort()
+                    timeout = self._periodicRequests[0].nextFire - time.time()
+                if timeout is None or timeout > 0.0:
+                    self._requestCondition.wait(timeout)
+                
+    def _disconnect(self):
+        """Disconnect from the flight simulator."""
+        pyuipc.close()
+        Handler._callSafe(lambda: self._connectionListener.disconnected())
+
+    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, we try to reconnect.
+        
+        Returns what the request's process() function returned or None if
+        reconnection failed."""
+        
+        self._requestCondition.release()
+
+        try:
+            return request.process(time)
+        except Exception as e:
+            print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
+                str(e) + ") reconnecting."
+            self._disconnect()
+            self._failRequests(request)
+            if not self._connect(): return None
+            else: return True
+        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]
+            result = self._processRequest(request, time.time())
+            if result is None: return False
+            elif not result: break
+
+        while self._connectionRequested and self._requests:
+            request = self._requests[0]
+            del self._requests[0]
+
+            if self._processRequest(request, None) is None:
+                return False
+
+        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"),
+                   (0x023e, "H"),
+                   (0x023b, "b"),
+                   (0x023c, "b"),
+                   (0x023a, "b"),
+                   (0x3d00, -256),
+                   (0x3c00, -256) ]
+    
+    def __init__(self, connectionListener, aircraft):
+        """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."""
+        self._aircraft = aircraft
+
+        self._handler = Handler(connectionListener)
+        self._handler.start()
+
+        self._normalRequestID = None
+
+        self._monitoringRequested = False
+        self._monitoring = False
+
+        self._aircraftName = None
+        self._aircraftModel = None
+
+    def connect(self):
+        """Initiate a connection to the simulator."""
+        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 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)
+
+    def _stopNormal(self):
+        """Stop the normal period request."""
+        assert self._normalRequestID is not None
+        self._handler.clearPeriodic(self._normalRequestID)
+        self._normalRequestID = None
+
+    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._monitoring = True
+            self._stopNormal()
+            self._startMonitoring()
+        elif self._monitoring and not self._monitoringRequested:
+            self._monitoring = False
+            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."""
+        assert self._monitoring
+
+        data = Simulator.normalData[:]
+        self._aircraftModel.addMonitoringData(data)
+        
+        self._normalRequestID = \
+            self._handler.requestPeriodicRead(1.0, data, 
+                                              self._handleNormal)
+
+#------------------------------------------------------------------------------
+
+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"),
+                      ("grossWeight", 0x30c0, "f"),
+                      ("heading", 0x0580, "d"),
+                      ("pitch", 0x0578, "d"),
+                      ("bank", 0x057c, "d"),
+                      ("ias", 0x02bc, "d"),
+                      ("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")]
+
+    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.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
+        
+        state.heading = data[self._monidx_heading]*360.0/65536.0/65536.0
+        if state.heading<0.0: state.heading += 360.0
+
+        state.pitch = data[self._monidx_pitch]*360.0/65536.0/65536.0
+        state.bank = data[self._monidx_bank]*360.0/65536.0/65536.0
+
+        state.ias = data[self._monidx_ias]/128.0
+        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
+        state.vs = data[self._monidx_vs]*60.0/const.FEETTOMETRES/256.0
+
+        state.radioAltitude = data[self._monidx_radioAltitude]/const.FEETTOMETRES/65536.0
+        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]
+        flapsIndex = flapsLeft / flapsIncrement
+        state.flaps = self._flapsNotches[flapsIndex]
+        if flapsIndex != numNotchesM1:
+            thisNotch = flapsIndex * flapsIncrement
+            nextNotch = thisNotch + flapsIncrement
+            
+            state.flaps += (self._flapsNotches[flapsIndex+1] - state.flaps) * \
+                (flapsLeft - thisNotch) / (nextNotch - thisNotch)
+        
+        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)
+        
+        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, 0x0898 + i * 0x98, "u")  # N1
+                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "d")  # 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]*100.0/16384.0)
+            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 Boeing 737 NG 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 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)
+
+#------------------------------------------------------------------------------
+
Index: src/fsuipc.py
===================================================================
--- src/fsuipc.py	(revision 7)
+++ 	(revision )
@@ -1,915 +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 _callSafe(fun):
-        """Call the given function and swallow any exceptions."""
-        try:
-            return fun()
-        except Exception, e:
-            print >> sys.stderr, str(e)
-            return None
-
-    class Request(object):
-        """A simple, one-shot request."""
-        def __init__(self, forWrite, data, callback, extra):
-            """Construct the request."""
-            self._forWrite = forWrite
-            self._data = data
-            self._callback = callback
-            self._extra = extra
-            
-        def process(self, time):
-            """Process the request."""            
-            if self._forWrite:
-                pyuipc.write(self._data)
-                Handler._callSafe(lambda: self._callback(True, self._extra))
-            else:
-                values = pyuipc.read(self._data)
-                Handler._callSafe(lambda: self._callback(values, self._extra))
-
-            return True
-
-        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):
-            """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
-            
-        @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.
-
-            Return a boolean indicating if the request was executed."""
-            if time < self._nextFire: 
-                return False
-
-            if self._preparedData is None:
-                self._preparedData = pyuipc.prepare_data(self._data)
-                self._data = None
-                
-            values = pyuipc.read(self._preparedData)
-
-            Handler._callSafe(lambda: self._callback(values, self._extra))
-
-            while self._nextFire <= time:
-                self._nextFire += self._period
-            
-            return True
-
-        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._requests = []
-        self._nextPeriodicID = 1
-        self._periodicRequests = []
-
-        self.daemon = True
-
-    def requestRead(self, data, callback, extra = 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))
-            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 read(self, data):
-        """Read the given data synchronously.
-
-        If a problem occurs, an exception is thrown."""
-        with threading.Condition() as condition:
-            extra = [condition]
-            self._requestRead(data, self._readWriteCallback, extra)
-            while len(extra)<2:
-                condition.wait()
-            if extra[1] is None:
-                raise fs.SimulatorException("reading failed")
-            else:
-                return extra[1]
-
-    def write(self, data):
-        """Write the given data synchronously.
-
-        If a problem occurs, an exception is thrown."""
-        with threading.Condition() as condition:
-            extra = [condition]
-            self._requestWrite(data, self._writeCallback, extra)
-            while len(extra)<2:
-                condition.wait()
-            if extra[1] is None:
-                raise fs.SimulatorException("writing failed")
-
-    def requestPeriodicRead(self, period, data, callback, extra = 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)
-            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"""
-        while self._connectionRequested:
-            try:
-                pyuipc.open(pyuipc.SIM_FS2K4)
-                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))
-                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:
-                if not self._processRequests(): 
-                    return
-                timeout = None
-                if self._periodicRequests:
-                    self._periodicRequests.sort()
-                    timeout = self._periodicRequests[0].nextFire - time.time()
-                if timeout is None or timeout > 0.0:
-                    self._requestCondition.wait(timeout)
-                
-    def _disconnect(self):
-        """Disconnect from the flight simulator."""
-        pyuipc.close()
-        Handler._callSafe(lambda: self._connectionListener.disconnected())
-
-    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, we try to reconnect.
-        
-        Returns what the request's process() function returned or None if
-        reconnection failed."""
-        
-        self._requestCondition.release()
-
-        try:
-            return request.process(time)
-        except Exception as e:
-            print "fsuipc.Handler._processRequest: FSUIPC connection failed (" + \
-                str(e) + ") reconnecting."
-            self._disconnect()
-            self._failRequests(request)
-            if not self._connect(): return None
-            else: return True
-        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]
-            result = self._processRequest(request, time.time())
-            if result is None: return False
-            elif not result: break
-
-        while self._connectionRequested and self._requests:
-            request = self._requests[0]
-            del self._requests[0]
-
-            if self._processRequest(request, None) is None:
-                return False
-
-        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 = [ (0x3d00, -256) ]
-
-    def __init__(self, connectionListener, aircraft):
-        """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."""
-        self._aircraft = aircraft
-
-        self._handler = Handler(connectionListener)
-        self._handler.start()
-
-        self._normalRequestID = None
-
-        self._monitoringRequested = False
-        self._monitoring = False
-
-        self._aircraftName = None
-        self._aircraftModel = None
-
-    def connect(self):
-        """Initiate a connection to the simulator."""
-        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 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)
-
-    def _stopNormal(self):
-        """Stop the normal period request."""
-        assert self._normalRequestID is not None
-        self._handler.clearPeriodic(self._normalRequestID)
-        self._normalRequestID = None
-
-    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.
-        """
-        self._setAircraftName(data[0])
-        if self._monitoringRequested and not self._monitoring:
-            self._monitoring = True
-            self._stopNormal()
-            self._startMonitoring()
-        elif self._monitoring and not self._monitoringRequested:
-            self._monitoring = False
-            self._stopNormal()
-            self._startDefaultNormal()
-        elif self._monitoring and self._aircraftModel is not None:
-            aircraftState = self._aircraftModel.getAircraftState(self._aircraft, data)
-            self._aircraft.handleState(aircraftState)
-
-    def _setAircraftName(self, name):
-        """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."""
-        if name==self._aircraftName:
-            return
-
-        self._aircraftName = name
-        if self._aircraftModel is None or \
-           not self._aircraftModel.doesHandle(name):            
-            self._setAircraftModel(AircraftModel.create(self._aircraft, name))
-        
-        self._aircraft.modelChanged(self._aircraftName, 
-                                    self._aircraftModel.name)
-
-    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._handler.clearPeriodic(self._normalRequestID)            
-            self._startMonitoring()
-            
-    def _startMonitoring(self):
-        """Start monitoring with the current aircraft model."""
-        assert self._monitoring
-
-        data = Simulator.normalData[:]
-        self._aircraftModel.addMonitoringData(data)
-        
-        self._normalRequestID = \
-            self._handler.requestPeriodicRead(1.0, data, 
-                                              self._handleNormal)
-
-#------------------------------------------------------------------------------
-
-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 = [("year", 0x0240, "H"),
-                      ("dayOfYear", 0x023e, "H"),
-                      ("zuluHour", 0x023b, "b"),
-                      ("zuluMinute", 0x023c, "b"),
-                      ("seconds", 0x023a, "b"),
-                      ("paused", 0x0264, "H"),
-                      ("frozen", 0x3364, "H"),
-                      ("replay", 0x0628, "d"),
-                      ("slew", 0x05dc, "H"),
-                      ("overspeed", 0x036d, "b"),
-                      ("stalled", 0x036c, "b"),
-                      ("onTheGround", 0x0366, "H"),
-                      ("grossWeight", 0x30c0, "f"),
-                      ("heading", 0x0580, "d"),
-                      ("pitch", 0x0578, "d"),
-                      ("bank", 0x057c, "d"),
-                      ("ias", 0x02bc, "d"),
-                      ("groundSpeed", 0x02b4, "d"),
-                      ("vs", 0x02c8, "d"),
-                      ("altitude", 0x0570, "l"),
-                      ("gLoad", 0x11ba, "H"),
-                      ("flapsControl", 0x0bdc, "d"),
-                      ("flapsLeft", 0x0be0, "d"),
-                      ("flapsRight", 0x0be4, "d"),
-                      ("lights", 0x0d0c, "H"),
-                      ("pitot", 0x029c, "b"),
-                      ("noseGear", 0x0bec, "d"),
-                      ("spoilersArmed", 0x0bcc, "d"),
-                      ("spoilers", 0x0bd0, "d"),
-                      ("altimeter", 0x0330, "H"),
-                      ("nav1", 0x0350, "H"),
-                      ("nav2", 0x0352, "H")]
-
-    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_F79  : F70Model,
-                      const.AIRCRAFT_DC3  : DC3Model,
-                      const.AIRCRAFT_T134 : T134Model,
-                      const.AIRCRAFT_T154 : T154Model,
-                      const.AIRCRAFT_YK40 : YK40Model }
-
-    specialModels = []
-
-    @staticmethod
-    def registerSpecial(clazz):
-        """Register the given class as a special model."""
-        AircraftModel.specialModels.append(clazz)
-
-    @staticmethod
-    def create(aircraft, aircraftName):
-        """Create the model for the given aircraft name, and notify the
-        aircraft about it."""
-        for specialModel in AircraftModel.specialModels:
-            if specialModel.doesHandle(aircraft, aircraftName):
-                return specialModel(aircraft, aircraftName)
-        
-        if aircraft.type in AircraftModel.genericModels:
-            return AircraftModel.genericModels[aircraft.type]()
-        else:
-            return GenericModel()
-
-    @staticmethod
-    def convertFrequency(data):
-        """Convert the given frequency data to a string."""
-        frequency = ""
-        for i in range(0, 4):
-            digit = chr(ord('0') + (data&0x0f))
-            data >>= 4
-            frequency = digit + frequency
-            if i==1:
-                frequency = "." + frequency
-        return "1" + frequency            
-
-    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, 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))
-
-    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, data):
-        """Get an aircraft state object for the given monitoring data."""
-        state = fs.AircraftState()
-        
-        timestamp = calendar.timegm(time.struct_time([data[self._monidx_year],
-                                                      1, 1, 0, 0, 0, -1, 1, 0]))
-        timestamp += data[self._monidx_dayOfYear] * 24 * 3600
-        timestamp += data[self._monidx_zuluHour] * 3600
-        timestamp += data[self._monidx_zuluMinute] * 60
-        timestamp += data[self._monidx_seconds]        
-        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.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
-        
-        state.heading = data[self._monidx_heading]*360.0/65536.0/65536.0
-        if state.heading<0.0: state.heading += 360.0
-
-        state.pitch = data[self._monidx_pitch]*360.0/65536.0/65536.0
-        state.bank = data[self._monidx_bank]*360.0/65536.0/65536.0
-
-        state.ias = data[self._monidx_ias]/128.0
-        state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
-        state.vs = data[self._monidx_vs]*60.0/const.FEETTOMETRES/256.0
-
-        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]
-        flapsIndex = flapsLeft / flapsIncrement
-        state.flaps = self._flapsNotches[flapsIndex]
-        if flapsIndex != numNotchesM1:
-            thisNotch = flapsIndex * flapsIncrement
-            nextNotch = thisNotch + flapsIncrement
-            
-            state.flaps += (self._flapsNotches[flapsIndex+1] - state.flaps) * \
-                (flapsLeft - thisNotch) / (nextNotch - thisNotch)
-        
-        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.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])
-        
-        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 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, 0x0898 + i * 0x98, "u")  # N1
-                self._addOffsetWithIndexMember(data, 0x088c + i * 0x98, "d")  # throttle lever
-        
-    def getAircraftState(self, aircraft, 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,
-                                                                   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]*100.0/16384.0)
-            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(GenericAircraftModel, 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(GenericAircraftModel, 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 B767Model(GenericAircraftModel):
-    """Generic model for the Boeing 767 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(GenericAircraftModel, 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 Boeing 737 NG aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(GenericAircraftModel, 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 CRJ2Model(GenericAircraftModel):
-    """Generic model for the Bombardier CRJ-200 aircraft."""
-    def __init__(self):
-        """Construct the model."""
-        super(GenericAircraftModel, 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(GenericAircraftModel, 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(GenericAircraftModel, 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(GenericAircraftModel, 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(GenericAircraftModel, 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, 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, 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(GenericAircraftModel, 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"
-
-#------------------------------------------------------------------------------
-
Index: src/logger.py
===================================================================
--- src/logger.py	(revision 8)
+++ src/logger.py	(revision 8)
@@ -0,0 +1,64 @@
+# Module for the logging.
+
+#--------------------------------------------------------------------------------------
+
+import const
+
+import sys
+import time
+
+#--------------------------------------------------------------------------------------
+
+class Logger(object):
+    """The class with the interface to log the various events."""
+    _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" }
+
+    def __init__(self, output = sys.stdout):
+        """Construct the logger."""
+        self._score = 100.0
+        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 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 debug(self, timestamp, msg):
+        """Log a debug message."""
+        timeStr = Logger._getTimeStr(timestamp)
+        print >> self._output, timeStr + ": [DEBUG] ", msg
+        print timeStr + ": [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,))
+        
+    def fault(self, timestamp, what, score):
+        """Report a fault."""
+        self._score -= score
+        self.message(timestamp, "%s (%f)" % (what, score))
+
+    def noGo(self, timestamp, what):
+        """Report a No-Go fault."""
+        self._score = -1
+        self.message(timestamp, "%s (NO GO)" % (what,))
+
+#--------------------------------------------------------------------------------------
Index: src/pyuipc.pyd
===================================================================
--- src/pyuipc.pyd	(revision 3)
+++ src/pyuipc.pyd	(revision 3)
@@ -0,0 +1,1 @@
+../pyuipc.pyd
Index: src/pyuipc.pyd
===================================================================
--- src/pyuipc.pyd	(revision 3)
+++ 	(revision )
@@ -1,1 +1,0 @@
-../pyuipc.pyd
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/test.py
===================================================================
--- src/test.py	(revision 8)
+++ src/test.py	(revision 8)
@@ -0,0 +1,34 @@
+# 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_B736
+        fl.aircraft = acft.Aircraft.create(fl)
+        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 6)
+++ 	(revision )
@@ -1,27 +1,0 @@
-# Test module
-
-import fs
-import acft
-import const
-import time
-
-def callback(data, extra):
-    print data
-
-def main():
-    simulator = fs.createSimulator(const.SIM_MSFS9, fs.ConnectionListener(),
-                                   acft.Aircraft(const.AIRCRAFT_B737))
-    simulator.connect()
-
-    time.sleep(10)
-    simulator.startMonitoring()
-
-    while True:
-        time.sleep(1000)
-    simulator.stopMonitoring()
-    simulator.disconnect()
-
-    time.sleep(5)
-
-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
-
