Index: src/fsuipc.py
===================================================================
--- src/fsuipc.py	(revision 8)
+++ src/fsuipc.py	(revision 9)
@@ -22,4 +22,31 @@
 class Handler(threading.Thread):
     """The thread to handle the FSUIPC requests."""
+    @staticmethod
+    def fsuipc2VS(data):
+        """Convert the given vertical speed data read from FSUIPC into feet/min."""
+        return data*60.0/const.FEETTOMETRES/256.0
+
+    @staticmethod
+    def fsuipc2radioAltitude(data):
+        """Convert the given radio altitude data read from FSUIPC into feet."""
+        return data/const.FEETTOMETRES/65536.0
+
+    @staticmethod
+    def fsuipc2Degrees(data):
+        """Convert the given data into degrees."""
+        return data * 360.0 / 65536.0 / 65536.0
+
+    @staticmethod
+    def fsuipc2PositiveDegrees(data):
+        """Convert the given data into positive degrees."""
+        degrees = Handler.fsuipc2Degrees(data)
+        if degrees<0.0: degrees += 360.0
+        return degrees
+
+    @staticmethod
+    def fsuipc2IAS(data):
+        """Convert the given data into indicated airspeed."""
+        return data / 128.0
+
     @staticmethod
     def _callSafe(fun):
@@ -341,12 +368,29 @@
     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) ]
-    
+    normalData = [ (0x0240, "H"),            # Year
+                   (0x023e, "H"),            # Number of day in year
+                   (0x023b, "b"),            # UTC hour
+                   (0x023c, "b"),            # UTC minute
+                   (0x023a, "b"),            # seconds
+                   (0x3d00, -256),           # The name of the current aircraft
+                   (0x3c00, -256) ]          # The path of the current AIR file
+
+    flareData1 = [ (0x023a, "b"),            # Seconds of time
+                   (0x31e4, "d"),            # Radio altitude
+                   (0x02c8, "d") ]           # Vertical speed
+                   
+    flareStartData = [ (0x0e90, "H"),        # Ambient wind speed
+                       (0x0e92, "H"),        # Ambient wind direction
+                       (0x0e8a, "H") ]       # Visibility
+                       
+    flareData2 = [ (0x023a, "b"),            # Seconds of time
+                   (0x0366, "H"),            # On the ground
+                   (0x02c8, "d"),            # Vertical speed
+                   (0x030c, "d"),            # Touch-down rate
+                   (0x02bc, "d"),            # IAS
+                   (0x0578, "d"),            # Pitch
+                   (0x057c, "d"),            # Bank
+                   (0x0580, "d") ]           # Heading
+
     def __init__(self, connectionListener, aircraft):
         """Construct the simulator.
@@ -356,5 +400,18 @@
         - modelChanged(aircraftName, modelName): called when the model handling
         the aircraft has changed.
-        - handleState(aircraftState): handle the given state."""
+        - handleState(aircraftState): handle the given state.
+        - flareStarted(windSpeed, windDirection, visibility, flareStart,
+                       flareStartFS): called when the flare has
+          started. windSpeed is in knots, windDirection is in degrees and
+          visibility is in metres. flareStart and flareStartFS are two time
+          values expressed in seconds that can be used to calculate the flare
+          time. 
+       - flareFinished(flareEnd, flareEndFS, tdRate, tdRateCalculatedByFS,
+                       ias, pitch, bank, heading): called when the flare has
+         finished, i.e. the aircraft is on the ground. flareEnd and flareEndFS
+         are the two time values corresponding to the touchdown time. tdRate is
+         the touch-down rate, tdRateCalculatedBySim indicates if the data comes
+         from the simulator or was calculated by the adapter. The other data
+         are self-explanatory and expressed in their 'natural' units."""
         self._aircraft = aircraft
 
@@ -370,4 +427,9 @@
         self._aircraftModel = None
 
+        self._flareRequestID = None
+        self._flareRates = []
+        self._flareStart = None
+        self._flareStartFS = None
+        
     def connect(self):
         """Initiate a connection to the simulator."""
@@ -385,4 +447,25 @@
         assert self._monitoringRequested 
         self._monitoringRequested = False
+
+    def startFlare(self):
+        """Start monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        #self._aircraft.logger.debug("startFlare")
+        if self._flareRequestID is None:
+            self._flareRates = []
+            self._flareRequestID = self._handler.requestPeriodicRead(0.1,
+                                                                     Simulator.flareData1,
+                                                                     self._handleFlare1)
+
+    def cancelFlare(self):
+        """Cancel monitoring the flare time.
+
+        At present it is assumed to be called from the FSUIPC thread, hence no
+        protection."""
+        if self._flareRequestID is not None:
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
 
     def disconnect(self):
@@ -485,4 +568,64 @@
                                               self._handleNormal)
 
+    def _addFlareRate(self, data):
+        """Append a flare rate to the list of last rates."""
+        if len(self._flareRates)>=3:
+            del self._flareRates[0]
+        self._flareRates.append(Handler.fsuipc2VS(data))
+
+    def _handleFlare1(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare1: " + str(data))
+        if Handler.fsuipc2radioAltitude(data[1])<=50.0:
+            self._flareStart = time.time()
+            self._flareStartFS = data[0]
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = \
+                self._handler.requestPeriodicRead(0.1, 
+                                                  Simulator.flareData2,
+                                                  self._handleFlare2)
+            self._handler.requestRead(Simulator.flareStartData,
+                                      self._handleFlareStart)
+                
+        self._addFlareRate(data[2])
+
+    def _handleFlareStart(self, data, extra):
+        """Handle the data need to notify the aircraft about the starting of
+        the flare."""
+        #self._aircraft.logger.debug("handleFlareStart: " + str(data))
+        if data is not None:
+            windDirection = data[1]*360.0/65536.0
+            if windDirection<0.0: windDirection += 360.0
+            self._aircraft.flareStarted(data[0], windDirection,
+                                        data[2]*1609.344/100.0,
+                                        self._flareStart, self._flareStartFS)
+
+    def _handleFlare2(self, data, normal):
+        """Handle the first stage of flare monitoring."""
+        #self._aircraft.logger.debug("handleFlare2: " + str(data))
+        if data[1]!=0:
+            flareEnd = time.time()
+            self._handler.clearPeriodic(self._flareRequestID)
+            self._flareRequestID = None
+
+            flareEndFS = data[0]
+            if flareEndFS<self._flareStartFS:
+                flareEndFS += 60
+
+            tdRate = Handler.fsuipc2VS(data[3])
+            tdRateCalculatedByFS = True
+            if tdRate==0 or tdRate>1000.0 or tdRate<-1000.0:
+                tdRate = min(self._flareRates)
+                tdRateCalculatedByFS = False
+
+            self._aircraft.flareFinished(flareEnd, flareEndFS,
+                                         tdRate, tdRateCalculatedByFS,
+                                         Handler.fsuipc2IAS(data[4]),
+                                         Handler.fsuipc2Degrees(data[5]),
+                                         Handler.fsuipc2Degrees(data[6]),
+                                         Handler.fsuipc2PositiveDegrees(data[7]))
+        else:
+            self._addFlareRate(data[2])
+                                                  
 #------------------------------------------------------------------------------
 
@@ -499,4 +642,5 @@
                       ("stalled", 0x036c, "b"),
                       ("onTheGround", 0x0366, "H"),
+                      ("zfw", 0x3bfc, "d"),
                       ("grossWeight", 0x30c0, "f"),
                       ("heading", 0x0580, "d"),
@@ -504,4 +648,5 @@
                       ("bank", 0x057c, "d"),
                       ("ias", 0x02bc, "d"),
+                      ("mach", 0x11c6, "H"),
                       ("groundSpeed", 0x02b4, "d"),
                       ("vs", 0x02c8, "d"),
@@ -521,5 +666,8 @@
                       ("nav1", 0x0350, "H"),
                       ("nav2", 0x0352, "H"),
-                      ("squawk", 0x0354, "H")]
+                      ("squawk", 0x0354, "H"),
+                      ("windSpeed", 0x0e90, "H"),
+                      ("windDirection", 0x0e92, "H")]
+
 
     specialModels = []
@@ -624,17 +772,19 @@
         state.onTheGround = data[self._monidx_onTheGround]!=0
 
+        state.zfw = data[self._monidx_zfw] * const.LBSTOKG / 256.0
         state.grossWeight = data[self._monidx_grossWeight] * const.LBSTOKG
         
-        state.heading = 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.heading = Handler.fsuipc2PositiveDegrees(data[self._monidx_heading])
+
+        state.pitch = Handler.fsuipc2Degrees(data[self._monidx_pitch])
+        state.bank = Handler.fsuipc2Degrees(data[self._monidx_bank])
+
+        state.ias = Handler.fsuipc2IAS(data[self._monidx_ias])
+        state.mach = data[self._monidx_mach] / 20480.0
         state.groundSpeed = data[self._monidx_groundSpeed]* 3600.0/65536.0/1852.0
-        state.vs = data[self._monidx_vs]*60.0/const.FEETTOMETRES/256.0
-
-        state.radioAltitude = data[self._monidx_radioAltitude]/const.FEETTOMETRES/65536.0
+        state.vs = Handler.fsuipc2VS(data[self._monidx_vs])
+
+        state.radioAltitude = \
+            Handler.fsuipc2radioAltitude(data[self._monidx_radioAltitude])
         state.altitude = data[self._monidx_altitude]/const.FEETTOMETRES/65536.0/65536.0
 
@@ -687,4 +837,8 @@
         state.nav2 = AircraftModel.convertFrequency(data[self._monidx_nav2])
         state.squawk = AircraftModel.convertBCD(data[self._monidx_squawk], 4)
+
+        state.windSpeed = data[self._monidx_windSpeed]
+        state.windDirection = data[self._monidx_windDirection]*360.0/65536.0
+        if state.windDirection<0.0: state.windDirection += 360.0
         
         return state
