class RPCServer(object): def __init__(self,port,host=''): self.handler = RPCHandler(self) self.port = port self.host = host self.tcpserver = None self.serviceMap = {} def registerService(self, service): '''Register an RPC service.''' self.serviceMap[service.GetDescriptor().full_name] = service def run(self): log.info('Running server on port %d' % self.port) rpcprotocol = RPCProtocol() self.tcpserver = TCPServer(self.handler,rpcprotocol) self.tcpserver.bind(self.port, self.host) self.tcpserver.start(0) ioloop.IOLoop.instance().start() def stop(self): self.tcpserver.stop() ioloop.IOLoop.instance().stop()
class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.showMaximized() self.setWindowTitle("EVGP Race Control System") self.is_server_started = False teams_list_file_path = "racers_list.yaml" if not os.path.exists(teams_list_file_path): logging.warning("No racers_list.yaml found!") msgBox = QMessageBox() msgBox.setIcon(QMessageBox.Warning) msgBox.setText( "No racers_list.yaml found.\nPress Open and use the file explorer to select the racer list YAML file" ) msgBox.setWindowTitle("No racers_list.yaml found!") msgBox.setStandardButtons(QMessageBox.Open) returnValue = msgBox.exec() if returnValue == QMessageBox.Open: teams_list_file_path = QFileDialog.getOpenFileName( self, 'Open Racer List File', os.getcwd(), "Yaml files (*.yaml)")[0] self.model = RCSModel(teams_list_file_path) layout = QGridLayout() layout.setColumnStretch(0, 10) layout.setColumnStretch(1, 10) #FILTERED ACTIVE RACE TABLE self.activeRaceProxyModel = RCSSortFilterProxyModel(True) self.activeRaceProxyModel.setDynamicSortFilter(True) self.activeRaceProxyModel.setSourceModel(self.model) self.activeRaceTable = QtWidgets.QTableView() self.activeRaceTable.setModel(self.activeRaceProxyModel) self.activeRaceTable.setSizeAdjustPolicy( QtWidgets.QAbstractScrollArea.AdjustToContents) self.activeRaceTable.horizontalHeader().setStretchLastSection(True) self.activeRaceTable.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents) self.activeRaceTable.setSelectionBehavior( QtWidgets.QTableView.SelectRows) self.activeRaceTable.setSelectionMode( QtWidgets.QTableView.SingleSelection) layout.addWidget(self.activeRaceTable, 1, 0) self.standbyRaceProxyModel = RCSSortFilterProxyModel(False) self.standbyRaceProxyModel.setDynamicSortFilter(True) self.standbyRaceProxyModel.setSourceModel(self.model) self.standbyRaceTable = QtWidgets.QTableView() self.standbyRaceTable.setModel(self.standbyRaceProxyModel) self.standbyRaceTable.setSizeAdjustPolicy( QtWidgets.QAbstractScrollArea.AdjustToContents) self.standbyRaceTable.horizontalHeader().setSectionResizeMode( QtWidgets.QHeaderView.ResizeToContents) self.standbyRaceTable.horizontalHeader().setStretchLastSection(True) self.standbyRaceTable.setSelectionBehavior( QtWidgets.QTableView.SelectRows) self.standbyRaceTable.setSelectionMode( QtWidgets.QTableView.SingleSelection) layout.addWidget(self.standbyRaceTable, 1, 1) self.activeRaceTableLabel = QLabel("Active Race Table") self.activeRaceTableLabel.setAlignment(Qt.AlignCenter) self.standbyRaceTableLabel = QLabel("Other Teams Table") self.standbyRaceTableLabel.setAlignment(Qt.AlignCenter) layout.addWidget(self.activeRaceTableLabel, 0, 0) layout.addWidget(self.standbyRaceTableLabel, 0, 1) self.standbyRaceTable.setSortingEnabled(True) self.standbyRaceTable.sortByColumn(1, Qt.AscendingOrder) self.activeRaceTable.setSortingEnabled(True) self.activeRaceTable.sortByColumn(1, Qt.AscendingOrder) self.selectedIndex = None self.standbyRaceTable.selectionModel().selectionChanged.connect( self.standby_race_table_selection_handler) self.activeRaceTable.selectionModel().selectionChanged.connect( self.active_race_table_selection_handler) self.horizontalGroupBox = QGroupBox("") self.horizontalGroupBox.setLayout(layout) # All relevant buttons in sidebar self.button_sidebar_vBox = QVBoxLayout() layout.addLayout(self.button_sidebar_vBox, 1, 3) self.button_sidebar_vBox.setAlignment(Qt.AlignTop) # Move Racers Buttons self.button_container_stylesheet = "QWidget#ButtonContainer{background-color: rgb(200, 200, 200);\n border-radius: 5;\n}" move_racers_button_container = QWidget() move_racers_button_container.setObjectName("ButtonContainer") move_racers_button_container.setStyleSheet( self.button_container_stylesheet) move_racers_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) move_racers_layout = QVBoxLayout(move_racers_button_container) move_racers_layout.addWidget(QLabel("ADMIN CONTROLS")) move_to_active_race_button = QPushButton("Move to Active Race") move_to_active_race_button.clicked.connect(self.move_to_active_race) move_racers_layout.addWidget(move_to_active_race_button) remove_from_active_race_button = QPushButton("Remove from Active Race") remove_from_active_race_button.clicked.connect( self.remove_from_active_race) move_racers_layout.addWidget(remove_from_active_race_button) self.button_sidebar_vBox.addWidget(move_racers_button_container) # Team State Control Buttons team_state_button_container = QWidget() team_state_button_container.setObjectName("ButtonContainer") team_state_button_container.setStyleSheet( self.button_container_stylesheet) team_state_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) team_state_btns = self.create_team_state_buttons() team_state_layout = QVBoxLayout(team_state_button_container) team_state_layout.addWidget(QLabel("TEAM CONTROLS")) for btn in team_state_btns: team_state_layout.addWidget(btn) self.button_sidebar_vBox.addWidget(team_state_button_container) # Race State Control Buttons race_state_button_container = QWidget() race_state_button_container.setObjectName("ButtonContainer") race_state_button_container.setStyleSheet( self.button_container_stylesheet) race_state_button_container.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Maximum) race_state_btns = self.create_race_state_buttons() race_state_layout = QVBoxLayout(race_state_button_container) race_state_layout.addWidget(QLabel("RACE CONTROLS")) for btn in race_state_btns: race_state_layout.addWidget(btn) self.button_sidebar_vBox.addWidget(race_state_button_container) self.info_group_box = QGroupBox("Race Status Information") self.race_state_label = QLabel("Race State: IN_GARAGE") self.info_label = QLabel("Race Status: No Race running.") info_layout = QVBoxLayout() info_layout.addWidget(self.race_state_label) info_layout.addWidget(self.info_label) self.info_group_box.setLayout(info_layout) layout.addWidget(self.info_group_box, 3, 0) self.buttonController = ButtonStateController( race_state_btns[0], race_state_btns[1], race_state_btns[2], race_state_btns[3], race_state_btns[4], team_state_btns[0], move_to_active_race_button, remove_from_active_race_button, self.race_state_label, self.info_label) self.model.race_state_change_signal.connect( self.buttonController.race_state_updated) verticalSpacer = QtWidgets.QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) layout.addItem(verticalSpacer, 4, 0, rowSpan=1, columnSpan=3) self.create_menu_bar() # wait for start of server self.server_wait_label = QLabel( "Waiting for TCP Server to start. Please hold on.") self.server_wait_label.setAlignment(Qt.AlignCenter) self.setCentralWidget(self.server_wait_label) self.start_server() # Make sure we stop the server on window close def closeEvent(self, event): self.stop_server() event.accept() def create_menu_bar(self): menuBar = QtWidgets.QMenuBar(self) self.setMenuBar(menuBar) helpMenu = menuBar.addMenu("&Help") self.reset_gui_buttons_actions = QtWidgets.QAction("Reset GUI buttons") self.reset_gui_buttons_actions.triggered.connect( self.buttonController.enable_all_buttons) helpMenu.addAction(self.reset_gui_buttons_actions) self.about_action = QtWidgets.QAction("About") self.about_action.triggered.connect(self.show_about_message) helpMenu.addAction(self.about_action) def show_about_message(self): text = "The Electric Vehicle GrandPrix Autonomous Race Control System " \ "is brought to you by the RoboJackets at Georgia Tech.<br>" \ "Contribute to the RCS at <a href='https://github.com/RoboJackets/evgp-rcs'>https://github.com/RoboJackets/evgp-rcs</a>" QMessageBox.question(self, 'About the EVGP Race Control System', text, QMessageBox.Ok) def create_race_state_buttons(self): grid_active_race_button = QPushButton("GRID ACTIVE RACE") grid_active_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.GRID_ACTIVE)) start_race_button = QPushButton("START RACE") start_race_button.setEnabled(False) start_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.GREEN_GREEN)) red_flag_race_button = QPushButton("RED FLAG RACE") red_flag_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.RED_FLAG)) e_stop_race_button = QPushButton("E-STOP RACE") e_stop_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.RED_RED)) finish_race_button = QPushButton("FINISH RACE") finish_race_button.clicked.connect( lambda: self.race_state_change_callback(RaceState.IN_GARAGE)) return [ grid_active_race_button, start_race_button, red_flag_race_button, e_stop_race_button, finish_race_button ] def create_team_state_buttons(self): in_garage_team_button = QPushButton("IN GARAGE TEAM") in_garage_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.IN_GARAGE)) red_flag_team_button = QPushButton("RED FLAG TEAM") red_flag_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.RED_FLAG)) e_stop_team_button = QPushButton("E-STOP TEAM") e_stop_team_button.clicked.connect( lambda: self.team_state_change_callback(RaceState.RED_RED)) return [ in_garage_team_button, red_flag_team_button, e_stop_team_button ] def team_state_change_callback(self, state): if self.selectedIndex is not None: self.model.team_state_change(self.selectedIndex, state) def race_state_change_callback(self, state): self.model.race_state_change(state) def move_to_active_race(self): if self.selectedIndex is not None: changed = self.model.move_to_active_race(self.selectedIndex) if changed: self.clearAllSelections() def remove_from_active_race(self): if self.selectedIndex is not None: changed = self.model.move_to_standby_race(self.selectedIndex) if changed: self.clearAllSelections() def start_server(self): if not self.is_server_started: self.is_server_started = True port = 12017 server_backlog = 10 send_hz = 10 ip_list = self.model.teams_list.keys() self.server = TCPServer(port, server_backlog, whitelist=ip_list, hz=send_hz) self.server.new_connection.connect( self.model.new_connection_handler) self.server.lost_connection.connect( self.model.lost_connection_handler) self.server.new_response.connect(self.model.new_response_handler) self.server.server_ready.connect(self.server_ready_handler) self.model.team_state_change_signal.connect( self.server.on_race_state_change) self.server_thread = QThread() self.server.moveToThread(self.server_thread) self.server_thread.started.connect(self.server.run_server) self.server_thread.start() def stop_server(self): if self.is_server_started: self.server.stop() self.is_server_started = False self.server_thread.quit() @QtCore.pyqtSlot(bool) def server_ready_handler(self, isReady): if isReady: self.setCentralWidget(self.horizontalGroupBox) if not isReady: self.server_wait_label.setText( "Server Error: Please restart program.") QMessageBox.question( self, 'Server Error', "Server failed to start.\nPress \"Close\" to quit program, then fix your network issues and restart this program.", QMessageBox.Close) self.close() @QtCore.pyqtSlot(QItemSelection, QItemSelection) def standby_race_table_selection_handler(self, filterTableSelection, filterTableDeselected): if filterTableSelection.indexes(): self.activeRaceTable.selectionModel().clearSelection() self.selectedIndex = self.standbyRaceProxyModel.mapToSource( filterTableSelection.indexes()[0]).row() @QtCore.pyqtSlot(QItemSelection, QItemSelection) def active_race_table_selection_handler(self, tableSelection, tableDeselected): if tableSelection.indexes(): self.standbyRaceTable.selectionModel().clearSelection() self.selectedIndex = self.activeRaceProxyModel.mapToSource( tableSelection.indexes()[0]).row() def clearAllSelections(self): self.activeRaceTable.selectionModel().clearSelection() self.standbyRaceTable.selectionModel().clearSelection() self.selectedIndex = None