00001
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
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
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
00047
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
00061
00062
00063
00064
00065
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
00228 item = MasterURIItem( master_uri, valid )
00229 self.table.setItem( row, 0, item )
00230 items.append( item )
00231
00232
00233 item = QTableWidgetItem()
00234 self.table.setItem( row, 1, item )
00235 items.append( item )
00236
00237
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
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
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
00280 items[0].setFlags( Qt.NoItemFlags )
00281
00282
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
00321
00322
00323
00324
00325
00326
00327
00328
00329 timer = QTimer()
00330 timer.start(250)
00331 timer.timeout.connect(lambda: None)
00332
00333 dialog = ChooserDialog( program, os.environ[ 'ROS_MASTER_URI' ])
00334
00335 app.exec_()