Changeset 59:a3e0b8455dc8


Ignore:
Timestamp:
04/07/12 08:48:34 (13 years ago)
Author:
István Váradi <ivaradi@…>
Branch:
default
Phase:
public
Message:

Implemented better connection and connection failure handling.

Location:
src/mlx
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • src/mlx/flight.py

    r31 r59  
    3434        self.flareTimeFromFS = False
    3535        self.entranceExam = False
    36         self.zfw = 50000
     36        self.zfw = None
    3737
    3838        self.options = Options()
  • src/mlx/fs.py

    r27 r59  
    44
    55import const
     6
     7import fsuipc
    68
    79#-------------------------------------------------------------------------------
     
    1315        simulator of the given type."""
    1416        print "fs.ConnectionListener.connected, fsType:", fsType, ", descriptor:", descriptor
     17
     18    def connectionFailed(self):
     19        """Called when the connection could not be established."""
     20        print "fs.ConnectionListener.connectionFailed"       
    1521
    1622    def disconnected(self):
     
    3440    assert type in [const.SIM_MSFS9, const.SIM_MSFSX], \
    3541           "Only MS Flight Simulator 2004 and X are supported"
    36     import fsuipc
    37     return fsuipc.Simulator(connectionListener)
     42    return fsuipc.Simulator(connectionListener, connectAttempts = 3)
    3843
    3944#-------------------------------------------------------------------------------
  • src/mlx/fsuipc.py

    r51 r59  
    5959            return None
    6060
     61    # The number of times a read is attempted
    6162    NUM_READATTEMPTS = 3
     63
     64    # The number of connection attempts
     65    NUM_CONNECTATTEMPTS = 3
     66
     67    # The interval between successive connect attempts
     68    CONNECT_INTERVAL = 0.25
    6269
    6370    @staticmethod
     
    172179            return cmp(self._nextFire, other._nextFire)
    173180
    174     def __init__(self, connectionListener):
     181    def __init__(self, connectionListener,
     182                 connectAttempts = -1, connectInterval = 0.2):
    175183        """Construct the handler with the given connection listener."""
    176184        threading.Thread.__init__(self)
    177185
    178186        self._connectionListener = connectionListener
     187        self._connectAttempts = connectAttempts
     188        self._connectInterval = connectInterval
    179189
    180190        self._requestCondition = threading.Condition()
    181191        self._connectionRequested = False
    182         self._connected = False
     192        self._connected = False       
    183193
    184194        self._requests = []
     
    266276        """Disconnect from the flight simulator."""
    267277        with self._requestCondition:
     278            self._requests = []
    268279            if self._connectionRequested:
    269                 self._connectionRequested = False
     280                self._connectionRequested = False               
    270281                self._requestCondition.notify()
     282
     283    def clearRequests(self):
     284        """Clear the outstanding one-shot requests."""
     285        with self._requestCondition:
     286            self._requests = []
    271287
    272288    def run(self):
     
    286302                self._requestCondition.wait()
    287303           
    288     def _connect(self):
     304    def _connect(self, autoReconnection = False):
    289305        """Try to connect to the flight simulator via FSUIPC
    290306
     
    292308        not due to no longer requested.
    293309        """
     310        attempts = 0
    294311        while self._connectionRequested:
    295312            try:
     313                attempts += 1
    296314                pyuipc.open(pyuipc.SIM_ANY)
    297315                description = "(FSUIPC version: 0x%04x, library version: 0x%04x, FS version: %d)" % \
    298316                    (pyuipc.fsuipc_version, pyuipc.lib_version,
    299317                     pyuipc.fs_version)
    300                 Handler._callSafe(lambda:     
    301                                   self._connectionListener.connected(const.SIM_MSFS9,
    302                                                                      description))
     318                if not autoReconnection:
     319                    Handler._callSafe(lambda:     
     320                                      self._connectionListener.connected(const.SIM_MSFS9,
     321                                                                         description))
    303322                self._connected = True
    304323                return True
    305324            except Exception, e:
    306325                print "fsuipc.Handler._connect: connection failed: " + str(e)
    307                 time.sleep(0.1)
     326                if attempts<self.NUM_CONNECTATTEMPTS:
     327                    time.sleep(self.CONNECT_INTERVAL)
     328                else:
     329                    self._connectionRequested = False
     330                    if autoReconnection:
     331                        Handler._callSafe(lambda:     
     332                                          self._connectionListener.disconnected())
     333                    else:
     334                        Handler._callSafe(lambda:     
     335                                          self._connectionListener.connectionFailed())
    308336
    309337        return False
     
    337365        if self._connected:
    338366            pyuipc.close()
    339             Handler._callSafe(lambda: self._connectionListener.disconnected())
    340367            self._connected = False
    341 
    342     def _failRequests(self, request):
    343         """Fail the outstanding, single-shot requuests."""
    344         request.fail()
    345         with self._requestCondition:
    346             for request in self._requests:
    347                 try:
    348                     self._requestCondition.release()
    349                     request.fail()
    350                 finally:
    351                     self._requestCondition.acquire()
    352             self._requests = []       
    353 
     368           
    354369    def _processRequest(self, request, time):
    355370        """Process the given request.
     
    377392
    378393            if needReconnect:
     394                with self._requestCondition:
     395                    self._requests.insert(0, request)
    379396                self._disconnect()
    380                 self._failRequests(request)
    381                 self._connect()
     397                self._connect(autoReconnection = True)
    382398        finally:
    383399            self._requestCondition.acquire()
     
    437453                   (0x0580, "d") ]           # Heading
    438454
    439     def __init__(self, connectionListener):
     455    def __init__(self, connectionListener, connectAttempts = -1,
     456                 connectInterval = 0.2):
    440457        """Construct the simulator.
    441458       
     
    460477        self._aircraft = None
    461478
    462         self._handler = Handler(connectionListener)
     479        self._handler = Handler(connectionListener,
     480                                connectAttempts = connectAttempts,
     481                                connectInterval = connectInterval)
    463482        self._handler.start()
    464483
     
    484503        self._aircraftModel = None
    485504        self._handler.connect()
    486         self._startDefaultNormal()
     505        if self._normalRequestID is None:
     506            self._startDefaultNormal()
     507
     508    def reconnect(self):
     509        """Initiate a reconnection to the simulator.
     510
     511        It does not reset already set up data, just calls connect() on the
     512        handler."""
     513        self._handler.connect()
     514
     515    def requestZFW(self, callback):
     516        """Send a request for the ZFW."""
     517        self._handler.requestRead([(0x3bfc, "d")], self._handleZFW, extra = callback)
    487518                                                           
    488519    def startMonitoring(self):
     
    683714        else:
    684715            self._addFlareRate(data[2])
     716
     717    def _handleZFW(self, data, callback):
     718        """Callback for a ZFW retrieval request."""
     719        zfw = data[0] * const.LBSTOKG / 256.0
     720        callback(zfw)
    685721                                                 
    686722#------------------------------------------------------------------------------
  • src/mlx/gui/flight.py

    r58 r59  
    494494#-----------------------------------------------------------------------------
    495495
     496class PayloadPage(Page):
     497    """Page to allow setting up the payload."""
     498    def __init__(self, wizard):
     499        """Construct the page."""
     500        help = "The briefing contains the weights below.\n" \
     501               "Setup the cargo weight and check if the simulator\n" \
     502               "reports the expected Zero Fuel Weight."
     503        super(PayloadPage, self).__init__(wizard, "Payload", help)
     504
     505        button = gtk.Button("_Query ZFW")
     506        button.connect("clicked", self._zfwRequested)
     507        self.setMainWidget(button)
     508
     509    def _zfwRequested(self, button):
     510        """Called when the ZFW is requested from the simulator."""
     511        self._wizard.gui.simulator.requestZFW(self._handleZFW)
     512
     513    def _handleZFW(self, zfw):
     514        """Called when the ZFW value is retrieved."""
     515        print "ZFW", zfw
     516
     517#-----------------------------------------------------------------------------
     518
    496519class Wizard(gtk.VBox):
    497520    """The flight wizard."""
     
    509532        self._pages.append(GateSelectionPage(self))
    510533        self._pages.append(ConnectPage(self))
     534        self._pages.append(PayloadPage(self))
    511535
    512536        maxWidth = 0
     
    523547        self.set_size_request(maxWidth, maxHeight)
    524548
    525         self._fleet = None
    526         self._fleetCallback = None
    527         self._updatePlaneCallback = None
    528        
    529         self._loginResult = None
    530         self._bookedFlight = None
    531         self._departureGate = "-"
    532 
    533         self._logger = Logger(output = gui)
    534         self._flight = None
    535         self._simulator = None
    536        
    537         self.setCurrentPage(0)
     549        self._initialize()
    538550       
    539551    @property
     
    567579        self._pages[self._currentPage].grabDefault()
    568580
     581    def connected(self, fsType, descriptor):
     582        """Called when the connection could be made to the simulator."""
     583        self.nextPage()
     584
     585    def connectionFailed(self):
     586        """Called when the connection could not be made to the simulator."""
     587        self._initialize()
     588
     589    def disconnected(self):
     590        """Called when we have disconnected from the simulator."""
     591        self._initialize()
     592
     593    def _initialize(self):
     594        """Initialize the wizard."""
     595        self._fleet = None
     596        self._fleetCallback = None
     597        self._updatePlaneCallback = None
     598       
     599        self._loginResult = None
     600        self._bookedFlight = None
     601        self._departureGate = "-"
     602
     603        self.setCurrentPage(0)
     604       
    569605    def _getFleet(self, callback, force = False):
    570606        """Get the fleet, if needed.
     
    634670    def _connectSimulator(self):
    635671        """Connect to the simulator."""
    636         self._logger.reset()
    637         self._flight = Flight(self.gui._logger, self.gui)
    638 
    639         self._flight.aircraftType = self._bookedFlight.aircraftType
    640         aircraft = self._flight.aircraft = Aircraft.create(self._flight)
    641         self._flight.aircraft._checkers.append(self.gui)
    642        
    643         self._flight.cruiseAltitude = -1       
    644         self._flight.zfw = -1
    645 
    646         if self._simulator is None:
    647             self._simulator = fs.createSimulator(const.SIM_MSFS9, self.gui)
    648 
    649         self._flight.simulator = self._simulator
    650         self._simulator.connect(aircraft)
    651         #self._simulator.startMonitoring()
     672        self.gui.connectSimulator(self._bookedFlight.aircraftType)
    652673   
    653674#-----------------------------------------------------------------------------
  • src/mlx/gui/gui.py

    r51 r59  
    4343        self.config = config
    4444        self._connecting = False
     45        self._reconnecting = False
    4546        self._connected = False
    4647        self._logger = logger.Logger(output = self)
    4748        self._flight = None
    4849        self._simulator = None
     50        self._monitoring = False
    4951
    5052        self._stdioLock = threading.Lock()
     
    127129                                      else gdk.WATCH)
    128130
     131    @property
     132    def simulator(self):
     133        """Get the simulator used by us."""
     134        return self._simulator
     135       
    129136    def run(self):
    130137        """Run the GUI."""
     
    140147        if self._flight is not None:
    141148            simulator = self._flight.simulator
    142             simulator.stopMonitoring()
    143             simulator.disconnect()           
     149            if self._monitoring:
     150                simulator.stopMonitoring()
     151                self._monitoring = False
     152            simulator.disconnect()                       
    144153
    145154    def connected(self, fsType, descriptor):
     
    147156        self._connected = True
    148157        self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
    149         gobject.idle_add(self._statusbar.updateConnection,
    150                          self._connecting, self._connected)
     158        gobject.idle_add(self._handleConnected, fsType, descriptor)
     159
     160    def _handleConnected(self, fsType, descriptor):
     161        """Called when the connection to the simulator has succeeded."""
     162        self._statusbar.updateConnection(self._connecting, self._connected)
     163        self.endBusy()
     164        if not self._reconnecting:
     165            self._wizard.connected(fsType, descriptor)
     166        self._reconnecting = False
     167
     168    def connectionFailed(self):
     169        """Called when the connection failed."""
     170        self._logger.untimedMessage("Connection to the simulator failed")
     171        gobject.idle_add(self._connectionFailed)
     172
     173    def _connectionFailed(self):
     174        """Called when the connection failed."""
     175        self.endBusy()
     176        self._statusbar.updateConnection(self._connecting, self._connected)
     177
     178        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
     179                                   message_format =
     180                                   "Cannot connect to the simulator.",
     181                                   parent = self._mainWindow)
     182        dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
     183                                       "to try the connection again, "
     184                                       "or <b>Cancel</b> to cancel the flight.")
     185       
     186        dialog.add_button("_Cancel", 0)
     187        dialog.add_button("_Try again", 1)
     188        dialog.set_default_response(1)
     189       
     190        result = dialog.run()
     191        dialog.hide()
     192        if result == 1:
     193            self.beginBusy("Connecting to the simulator.")
     194            self._simulator.reconnect()
     195        else:
     196            self._connecting = False
     197            self._reconnecting = False
     198            self._statusbar.updateConnection(self._connecting, self._connected)
     199            self._wizard.connectionFailed()
    151200       
    152201    def disconnected(self):
     
    154203        self._connected = False
    155204        self._logger.untimedMessage("Disconnected from the simulator")
    156         gobject.idle_add(self._statusbar.updateConnection,
    157                          self._connecting, self._connected)
     205
     206        gobject.idle_add(self._disconnected)
     207
     208    def _disconnected(self):
     209        """Called when we have disconnected from the simulator unexpectedly."""       
     210        self._statusbar.updateConnection(self._connecting, self._connected)
     211
     212        dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
     213                                   message_format =
     214                                   "The connection to the simulator failed unexpectedly.",
     215                                   parent = self._mainWindow)
     216        dialog.format_secondary_markup("If the simulator has crashed, restart it "
     217                                       "and restore your flight as much as possible "
     218                                       "to the state it was in before the crash.\n"
     219                                       "Then press <b>Reconnect</b> to reconnect.\n\n"
     220                                       "If you want to cancel the flight, press <b>Cancel</b>.")
     221
     222        dialog.add_button("_Cancel", 0)
     223        dialog.add_button("_Reconnect", 1)
     224        dialog.set_default_response(1)
     225
     226        result = dialog.run()
     227        dialog.hide()
     228        if result == 1:
     229            self.beginBusy("Connecting to the simulator.")
     230            self._reconnecting = True
     231            self._simulator.reconnect()
     232        else:
     233            self._connecting = False
     234            self._reconnecting = False
     235            self._statusbar.updateConnection(self._connecting, self._connected)
     236            self._wizard.disconnected()
    158237
    159238    def write(self, msg):
     
    285364            self._stdioAfterNewLine = False
    286365
     366    def connectSimulator(self, aircraftType):
     367        """Connect to the simulator for the first time."""
     368        self._logger.reset()
     369
     370        self._flight = flight.Flight(self._logger, self)
     371        self._flight.aircraftType = aircraftType
     372        self._flight.aircraft = acft.Aircraft.create(self._flight)
     373        self._flight.aircraft._checkers.append(self)
     374       
     375        if self._simulator is None:
     376            self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
     377
     378        self._flight.simulator = self._simulator
     379
     380        self.beginBusy("Connecting to the simulator...")
     381        self._statusbar.updateConnection(self._connecting, self._connected)
     382
     383        self._connecting = True
     384        self._simulator.connect(self._flight.aircraft)       
     385
    287386    def _connectToggled(self, button):
    288387        """Callback for the connection button."""
    289388        if self._connectButton.get_active():
    290             self._logger.reset()
    291             self._flight = flight.Flight(self._logger, self)
    292 
    293389            acftListModel = self._acftList.get_model()
    294             self._flight.aircraftType = \
    295                 acftListModel[self._acftList.get_active()][1]
    296             self._flight.aircraft = acft.Aircraft.create(self._flight)
    297             self._flight.aircraft._checkers.append(self)
    298 
     390            self.connectSimulator(acftListModel[self._acftList.get_active()][1])
     391           
    299392            self._flight.cruiseAltitude = self._flSpinButton.get_value_as_int() * 100
    300393
    301394            self._flight.zfw = self._zfwSpinButton.get_value_as_int()
    302 
    303             if self._simulator is None:
    304                 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
    305 
    306             self._flight.simulator = self._simulator
    307 
    308             self._connecting = True
    309             self._simulator.connect(self._flight.aircraft)
     395           
    310396            self._simulator.startMonitoring()
     397            self._monitoring = True
    311398        else:
    312399            self.resetFlightStatus()
    313400            self._connecting = False
     401            self._connected = False
     402
    314403            self._simulator.stopMonitoring()
     404            self._monitoring = False
     405
    315406            self._simulator.disconnect()
    316407            self._flight = None
  • src/mlx/gui/statusbar.py

    r49 r59  
    8787   
    8888    def _drawConnState(self, connStateArea, eventOrContext):
    89         """Draw the connection state."""
     89        """Draw the connection state."""       
    9090        context = eventOrContext if pygobject else connStateArea.window.cairo_create()
    9191
  • src/mlx/pyuipc_sim.py

    r57 r59  
    637637#------------------------------------------------------------------------------
    638638
     639failOpen = False
     640
     641opened = False
     642
     643#------------------------------------------------------------------------------
     644
    639645def open(request):
    640646    """Open the connection."""
    641     return True
     647    global opened
     648    if failOpen:
     649        raise FSUIPCException(ERR_NOFS)
     650    elif opened:
     651        raise FSUIPCException(ERR_OPEN)
     652    else:
     653        time.sleep(0.5)
     654        opened = True
     655        return True
    642656
    643657#------------------------------------------------------------------------------
     
    645659def prepare_data(pattern, forRead = True):
    646660    """Prepare the given pattern for reading and/or writing."""
    647     return pattern
    648 
     661    if opened:
     662        return pattern
     663    else:
     664        raise FSUIPCException(ERR_OPEN)
     665       
    649666#------------------------------------------------------------------------------
    650667
    651668def read(data):
    652669    """Read the given data."""
    653     return [values.read(offset) for (offset, type) in data]
     670    print "opened", opened
     671    if opened:
     672        return [values.read(offset) for (offset, type) in data]
     673    else:
     674        raise FSUIPCException(ERR_OPEN)
    654675           
    655676#------------------------------------------------------------------------------
     
    657678def write(data):
    658679    """Write the given data."""
    659     for (offset, type, value) in data:
    660         values.write(offset, value)
     680    if opened:
     681        for (offset, type, value) in data:
     682            values.write(offset, value)
     683    else:
     684        raise FSUIPCException(ERR_OPEN)
    661685           
    662686#------------------------------------------------------------------------------
     
    664688def close():
    665689    """Close the connection."""
    666     pass
     690    global opened
     691    opened = False
    667692
    668693#------------------------------------------------------------------------------
     
    673698CALL_WRITE=2
    674699CALL_CLOSE=3
     700CALL_FAILOPEN=4
     701CALL_QUIT = 99
    675702
    676703RESULT_RETURNED=1
     
    715742                    elif call==CALL_WRITE:
    716743                        result = write(args[0])
     744                    elif call==CALL_CLOSE:
     745                        global opened
     746                        opened = False
     747                        result = None
     748                    elif call==CALL_FAILOPEN:
     749                        global failOpen
     750                        failOpen = args[0]
     751                        result = None
    717752                    else:
    718753                        break
     
    753788        """Write the given data."""
    754789        return self._call(CALL_WRITE, data)
     790
     791    def close(self):
     792        """Close the connection currently opened in the simulator."""
     793        return self._call(CALL_CLOSE, None)
     794
     795    def failOpen(self, really):
     796        """Enable/disable open failure in the simulator."""
     797        return self._call(CALL_FAILOPEN, really)
     798
     799    def quit(self):
     800        """Quit from the simulator."""
     801        data = cPickle.dumps((CALL_QUIT, None))
     802        self._socket.send(struct.pack("I", len(data)) + data)       
    755803
    756804    def _call(self, command, data):
     
    10241072        if line=="EOF":
    10251073            print
    1026             return True
     1074            return self.do_quit("")
    10271075        else:
    10281076            return super(CLI, self).default(line)
     
    11021150            return [key + "=" for key in self._valueHandlers if key.startswith(text)]
    11031151
     1152    def do_close(self, args):
     1153        """Close an existing connection so that FS will fail."""
     1154        try:
     1155            self._client.close()
     1156            print "Connection closed"
     1157        except Exception, e:
     1158            print >> sys.stderr, "Failed to close the connection: " + str(e)       
     1159       
     1160    def do_failopen(self, args):
     1161        """Enable/disable the failing of opens."""
     1162        try:
     1163            value = self.str2bool(args)
     1164            self._client.failOpen(value)
     1165            print "Opening will%s fail" % ("" if value else " not",)
     1166        except Exception, e:
     1167            print >> sys.stderr, "Failed to set open failure: " + str(e)       
     1168
     1169    def help_failopen(self, usage = False):
     1170        """Help for the failopen close"""
     1171        if usage: print "Usage:",
     1172        print "failopen yes|no"
     1173
     1174    def complete_failopen(self, text, line, begidx, endidx):
     1175        if text:
     1176            if "yes".startswith(text): return ["yes"]
     1177            elif "no".startswith(text): return ["no"]
     1178            else: return []
     1179        else:
     1180            return ["yes", "no"]
     1181       
    11041182    def do_quit(self, args):
    11051183        """Handle the quit command."""
     1184        self._client.quit()
    11061185        return True
    11071186
Note: See TracChangeset for help on using the changeset viewer.