choose-master.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 
00003 import sys
00004 import os
00005 import os.path
00006 import xmlrpclib
00007 import signal
00008 import subprocess
00009 
00010 from PySide.QtGui import *
00011 from PySide.QtCore import *
00012 
00013 def sigintHandler( signal, frame ):
00014     QApplication.quit()
00015 
00016 # Return an array of node names, or None on error (like if the master doesn't exist).
00017 def getNodesOnMaster( master_uri ):
00018     try:
00019         proxy = xmlrpclib.ServerProxy( master_uri )
00020         state = proxy.getSystemState("")
00021         if state[0] != 1:
00022             return None
00023         nodes = []
00024         for s in state[2]:
00025             for t, l in s:
00026                 nodes.extend(l)
00027         return list(set(nodes))
00028     except:
00029         return None
00030 
00031 # Return an array of recently-used master URIs read from a file, or empty array if none.
00032 def readRecentMasters():
00033     masters = set()
00034     try:
00035         filename = os.path.expanduser('~/.ros/recent_masters')
00036         if os.path.exists( filename ):
00037             with open( filename ) as file:
00038                 for line in file:
00039                     line = line.strip()
00040                     if len( line ) > 0:
00041                         masters.add( line )
00042     except:
00043         pass
00044     return masters
00045 
00046 # Replace the recent_masters file with the contents of master_uris,
00047 # which should be a set of strings.
00048 def writeRecentMasters( master_uris ):
00049     try:
00050         filename = os.path.expanduser('~/.ros/recent_masters')
00051         dirname = os.path.dirname( filename );
00052         if not os.path.isdir( dirname ):
00053             os.makedirs( dirname )
00054         with open( filename, 'w' ) as file:
00055             for master_uri in master_uris:
00056                 file.write( master_uri + '\n' )
00057     except:
00058         pass
00059 
00060 # Thread continually runs, checking for a given master over and over
00061 # while self.scan is True.  When self.scan is False, keeps looping but
00062 # does not check for the master.
00063 #
00064 # When it gets an answer for a master, emits signal foundValidMaster
00065 # or foundInvalidMaster.
00066 class ScannerThread( QThread ):
00067     foundValidMaster = Signal( str, object )
00068     foundInvalidMaster = Signal( str )
00069 
00070     def __init__( self, master_uri ):
00071         super(ScannerThread, self).__init__()
00072         self.master_uri = master_uri
00073         self.scan = True
00074 
00075     def run( self ):
00076         while True:
00077             if self.scan:
00078                 nodes = getNodesOnMaster( self.master_uri )
00079                 if( nodes ):
00080                     self.foundValidMaster.emit( self.master_uri, nodes )
00081                 else:
00082                     self.foundInvalidMaster.emit( self.master_uri )
00083             QThread.msleep( 500 )
00084 
00085 class MasterURIItem(QTableWidgetItem):
00086     def __init__( self, text, valid ):
00087         super(MasterURIItem, self).__init__( text )
00088         self.valid = valid
00089         self.my_text = text
00090 
00091     def __lt__( self, other ):
00092         if self.valid == other.valid:
00093             return (self.my_text < other.my_text)
00094         elif self.valid:
00095             return True
00096         else:
00097             return False
00098 
00099 class DeleteButton( QToolButton ):
00100     masterClicked = Signal( str )
00101 
00102     def __init__( self, master_uri, parent=None ):
00103         super( DeleteButton, self ).__init__( parent )
00104         self.setIcon( QIcon( ":/trolltech/styles/commonstyle/images/standardbutton-close-32.png" ))
00105         self.master_uri = master_uri
00106         self.clicked.connect( self.onClick )
00107 
00108     def onClick( self ):
00109         self.masterClicked.emit( self.master_uri )
00110 
00111 class ChooserDialog(QDialog):
00112     def __init__(self, program, master_from_environ, parent=None):
00113         super(ChooserDialog, self).__init__(parent)
00114 
00115         self.program = program
00116         program_name = os.path.basename( program )
00117 
00118         main_layout = QVBoxLayout()
00119 
00120         main_layout.addWidget( QLabel( "Choose a ROS master to start " + program_name ))
00121 
00122         self.table = QTableWidget( 0, 3, self )
00123         self.table.setHorizontalHeaderLabels( ["Master URI", "Nodes", ""] )
00124         self.table.horizontalHeader().setResizeMode( 0, QHeaderView.Stretch )
00125         self.table.horizontalHeader().setResizeMode( 1, QHeaderView.Fixed )
00126         self.table.horizontalHeader().resizeSection( 1, 60 )
00127         self.table.horizontalHeader().setResizeMode( 2, QHeaderView.Fixed )
00128         self.table.horizontalHeader().resizeSection( 2, 30 )
00129         self.table.itemSelectionChanged.connect( self.onSelectionChange )
00130         self.table.itemActivated.connect( self.start )
00131         main_layout.addWidget( self.table )
00132 
00133         start_program_layout = QHBoxLayout()
00134         start_program_layout.addStretch( 1000 )
00135         self.start_button = QPushButton( "Start " + program_name )
00136         self.start_button.setEnabled( False )
00137         self.start_button.clicked.connect( self.start )
00138         start_program_layout.addWidget( self.start_button )
00139         main_layout.addLayout( start_program_layout )
00140 
00141         bottom_layout = QHBoxLayout()
00142         add_label = QLabel( "http://" )
00143         self.host_entry = QLineEdit("localhost")
00144         self.host_entry.setMinimumSize( 150, 10 )
00145         self.host_entry.returnPressed.connect( self.addMaster )
00146         colon_label = QLabel( ":" )
00147         self.port_entry = QLineEdit("11311")
00148         self.port_entry.setMinimumSize( 100, 10 )
00149         self.port_entry.returnPressed.connect( self.addMaster )
00150         bottom_layout.addWidget( add_label )
00151         bottom_layout.addWidget( self.host_entry )
00152         bottom_layout.addWidget( colon_label )
00153         bottom_layout.addWidget( self.port_entry )
00154         add_button = QPushButton( "Add Master" )
00155         add_button.clicked.connect( self.addMaster )
00156         bottom_layout.addWidget( add_button )
00157 
00158         main_layout.addLayout( bottom_layout )
00159         self.setLayout( main_layout )
00160 
00161         self.master_from_env = master_from_environ
00162         masters = readRecentMasters()
00163         self.master_to_scanner_map = {}
00164         for master in masters:
00165             self.addScanner( master )
00166 
00167         self.show_timer = QTimer()
00168         self.show_timer.setSingleShot( True )
00169         self.show_timer.timeout.connect( self.show )
00170         self.show_timer.start( 200 )
00171 
00172     def addScanner( self, master_uri ):
00173         if master_uri not in self.master_to_scanner_map:
00174             thread = ScannerThread( master_uri )
00175             thread.foundValidMaster.connect( self.insertValidMaster, Qt.QueuedConnection )
00176             thread.foundInvalidMaster.connect( self.insertInvalidMaster, Qt.QueuedConnection )
00177             thread.start()
00178             self.master_to_scanner_map[ master_uri ] = thread
00179 
00180     def removeScanner( self, master_uri ):
00181         if master_uri in self.master_to_scanner_map:
00182             thread = self.master_to_scanner_map[ master_uri ]
00183             thread.scan = False
00184             thread.foundValidMaster.disconnect( self.insertValidMaster )
00185             thread.foundInvalidMaster.disconnect( self.insertInvalidMaster )
00186             del self.master_to_scanner_map[ master_uri ]
00187             print "waiting for thread", thread.master_uri, "to die."
00188             thread.wait(0)
00189 
00190     def setScanning( self, scanning ):
00191         for master, scanner in self.master_to_scanner_map.iteritems():
00192             scanner.scan = scanning
00193 
00194     def onSelectionChange( self ):
00195         selection = self.table.selectedItems()
00196         self.start_button.setEnabled( len( selection ) == 1 )
00197 
00198     def start( self ):
00199         selection = self.table.selectedItems()
00200         if len( selection ):
00201             self.setScanning( False )
00202             self.hide()
00203             master = selection[0].text()
00204             print "running", self.program, "with master", master
00205             program_result = subprocess.call( [self.program, "--in-mc-wrapper", "__master:=" + master] )
00206             if program_result == 255:
00207                 selection[0].setSelected( False )
00208                 self.master_from_env = None
00209                 self.show()
00210                 self.setScanning( True )
00211             else:
00212                 self.close()
00213 
00214     def addMaster( self ):
00215         host = self.host_entry.text()
00216         port = self.port_entry.text()
00217         if len( host ) and len( port ):
00218             self.addScanner( 'http://' + host + ':' + port )
00219             writeRecentMasters( self.master_to_scanner_map.keys() )
00220 
00221     def addRow( self, master_uri, valid ):
00222         row = self.table.rowCount()
00223         self.table.insertRow( row )
00224 
00225         items = []
00226 
00227         # master URI item
00228         item = MasterURIItem( master_uri, valid )
00229         self.table.setItem( row, 0, item )
00230         items.append( item )
00231 
00232         # node count item
00233         item = QTableWidgetItem()
00234         self.table.setItem( row, 1, item )
00235         items.append( item )
00236 
00237         # delete master item
00238         item = QTableWidgetItem()
00239         item.setFlags( Qt.NoItemFlags )
00240         self.table.setItem( row, 2, item )
00241         delete_button = DeleteButton( master_uri )
00242         self.table.setCellWidget( row, 2, delete_button )
00243         delete_button.masterClicked.connect( self.deleteMaster )
00244 
00245         return items
00246 
00247     def insertValidMaster( self, master_uri, nodes ):
00248         old_item = self.itemForMaster( master_uri )
00249         if old_item:
00250             if old_item.valid:
00251                 return
00252             else:
00253                 self.removeMasterFromTable( master_uri )
00254 
00255         items = self.addRow( master_uri, True )
00256         # master URI item
00257         items[0].setFlags( Qt.ItemIsEnabled | Qt.ItemIsSelectable )
00258         items[0].setSelected( self.master_from_env == master_uri )
00259 
00260         if self.master_from_env == master_uri and not self.isVisible():
00261             self.start()
00262 
00263         # node count item
00264         items[1].setFlags( Qt.NoItemFlags )
00265         items[1].setBackground( Qt.white )
00266         items[1].setText( str( len( nodes )))
00267 
00268         self.table.sortItems( 0 )
00269 
00270     def insertInvalidMaster( self, master_uri ):
00271         old_item = self.itemForMaster( master_uri )
00272         if old_item:
00273             if old_item.valid:
00274                 self.removeMasterFromTable( master_uri )
00275             else:
00276                 return
00277 
00278         items = self.addRow( master_uri, False )
00279         # master URI item
00280         items[0].setFlags( Qt.NoItemFlags )
00281 
00282         # node count item
00283         items[1].setFlags( Qt.NoItemFlags )
00284 
00285         self.table.sortItems( 0 )
00286 
00287     def itemForMaster( self, master_uri ):
00288         row = 0
00289         while row < self.table.rowCount():
00290             item = self.table.item( row, 0 )
00291             if item.text() == master_uri:
00292                 return item
00293             row += 1
00294         return None
00295 
00296     def removeMasterFromTable( self, master_uri ):
00297         row = 0
00298         while row < self.table.rowCount():
00299             if self.table.item( row, 0 ).text() == master_uri:
00300                 self.table.removeRow( row )
00301             else:
00302                 row += 1
00303 
00304     def deleteMaster( self, master_uri ):
00305         self.removeScanner( master_uri )
00306         writeRecentMasters( self.master_to_scanner_map.keys() )
00307         self.removeMasterFromTable( master_uri )
00308 
00309 if __name__ == '__main__':
00310     signal.signal(signal.SIGINT, sigintHandler)
00311 
00312     app = QApplication( sys.argv )
00313 
00314     if len( sys.argv ) < 2:
00315         print "USAGE: choose-master.py <program>"
00316         sys.exit( 1 )
00317 
00318     program = sys.argv[1]
00319 
00320     # Borrowed from rxlaunch:
00321     # Sets up signal handling so SIGINT closes the application,
00322     # following the solution given at [1].  Sets up a custom signal
00323     # handler, and ensures that the Python interpreter runs
00324     # occasionally so the signal is handled.  The email thread at [2]
00325     # explains why this is necessary.
00326     #
00327     # [1] http://stackoverflow.com/questions/4938723/#4939113
00328     # [2] http://www.mail-archive.com/pyqt@riverbankcomputing.com/msg13757.html
00329     timer = QTimer()
00330     timer.start(250)
00331     timer.timeout.connect(lambda: None)  # Forces the interpreter to run every 250ms
00332 
00333     dialog = ChooserDialog( program, os.environ[ 'ROS_MASTER_URI' ])
00334 
00335     app.exec_()


rviz
Author(s): Dave Hershberger, Josh Faust
autogenerated on Mon Jan 6 2014 11:54:32