class SyncDialog(QDialog): ''' A dialog to set the sync options. ''' def __init__(self, parent=None): QDialog.__init__(self, parent) # self.host = host self.setWindowIcon(QIcon(":/icons/irondevil_sync.png")) self.setWindowTitle('Sync') self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.resize(350, 190) self.toolButton_SyncAll = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(2) sizePolicy.setHeightForWidth(self.toolButton_SyncAll.sizePolicy().hasHeightForWidth()) self.toolButton_SyncAll.setSizePolicy(sizePolicy) self.toolButton_SyncAll.setObjectName("toolButton_SyncAll") self.verticalLayout.addWidget(self.toolButton_SyncAll) self.toolButton_SyncAll.setText(self._translate("Sync All")) self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked) # self.toolButton_SyncAllAnyMsg = QToolButton(self) # sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # sizePolicy.setHorizontalStretch(0) # sizePolicy.setVerticalStretch(1) # sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth()) # self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy) # self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg") # self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg) # self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)")) # self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked) self.toolButton_SyncTopicOnDemand = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth()) self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy) self.toolButton_SyncTopicOnDemand.setObjectName("toolButton_SyncTopicOnDemand") self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand) self.toolButton_SyncTopicOnDemand.setText(self._translate("Sync only topics on demand")) self.toolButton_SyncTopicOnDemand.clicked.connect(self._on_sync_topics_on_demand_clicked) self.toolButton_SelectInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth()) self.toolButton_SelectInterface.setSizePolicy(sizePolicy) self.toolButton_SelectInterface.setObjectName("toolButton_SelectInterface") self.verticalLayout.addWidget(self.toolButton_SelectInterface) self.toolButton_SelectInterface.setText(self._translate("Select an interface")) self.toolButton_SelectInterface.clicked.connect(self._on_select_interface_clicked) self.interface_field = QComboBox(self) self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically) self.interface_field.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.interface_field.setEditable(True) self.interface_field.setVisible(False) self.interface_field.setObjectName("interface_field") self.verticalLayout.addWidget(self.interface_field) self.interface_field.currentIndexChanged[str].connect(self._on_interface_selected) self.toolButton_EditInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.toolButton_EditInterface.sizePolicy().hasHeightForWidth()) self.toolButton_EditInterface.setSizePolicy(sizePolicy) self.toolButton_EditInterface.setObjectName("toolButton_EditInterface") self.verticalLayout.addWidget(self.toolButton_EditInterface) self.toolButton_EditInterface.setText(self._translate("Edit selected interface")) self.toolButton_EditInterface.clicked.connect(self._on_edit_interface_clicked) self.toolButton_EditInterface.setVisible(False) self.toolButton_CreateInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth(self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth()) self.toolButton_CreateInterface.setSizePolicy(sizePolicy) self.toolButton_CreateInterface.setObjectName("toolButton_CreateInterface") self.verticalLayout.addWidget(self.toolButton_CreateInterface) self.toolButton_CreateInterface.setText(self._translate("Create an interface")) self.toolButton_CreateInterface.clicked.connect(self._on_create_interface_clicked) self.toolButton_CreateInterface.setVisible(False) self.textedit = TextEdit('', self) self.hl = SyncHighlighter(self.textedit.document()) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.textedit.setSizePolicy(sizePolicy) self.textedit.setObjectName("syncedit") self.verticalLayout.addWidget(self.textedit) self.textedit.setVisible(False) self._fill_interface_thread = None self._interfaces_files = None self._sync_args = [] self._interface_filename = None self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setObjectName("buttonBox") self.verticalLayout.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self._new_iface = True def _translate(self, text): if hasattr(QApplication, "UnicodeUTF8"): return QApplication.translate("Form", text, None, QApplication.UnicodeUTF8) else: return QApplication.translate("Form", text, None) @property def sync_args(self): return self._sync_args @property def interface_filename(self): return self._interface_filename def _on_sync_all_clicked(self): self.setResult(QDialog.Accepted) self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', "'.'"])) self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False'])) self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) self._sync_args.append(''.join(['_sync_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_services:=', '[]'])) self._sync_args.append(''.join(['_sync_services:=', '[]'])) self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) self._interface_filename = None self.accept() # def _on_sync_all_anymsg_clicked(self): # self._sync_args = [] # self._sync_args.append(''.join(['_interface_url:=', "'.'"])) # self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) # self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) # self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) # self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) # self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) # self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) # self._sync_args.append(''.join(['_sync_topics:=', '[/*]'])) # self._sync_args.append(''.join(['_ignore_services:=', '[]'])) # self._sync_args.append(''.join(['_sync_services:=', '[]'])) # self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) # self._interface_filename = None # self.accept() def _on_sync_topics_on_demand_clicked(self): self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', "'.'"])) self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) self._sync_args.append(''.join(['_sync_topics:=', '[/only_on_demand]'])) self._sync_args.append(''.join(['_ignore_services:=', '[/*]'])) self._sync_args.append(''.join(['_sync_services:=', '[]'])) self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) self._interface_filename = None self.accept() def _on_select_interface_clicked(self): self.toolButton_SyncAll.setVisible(False) # self.toolButton_SyncAllAnyMsg.setVisible(False) self.toolButton_SyncTopicOnDemand.setVisible(False) self.toolButton_SelectInterface.setVisible(False) self.interface_field.setVisible(True) self.toolButton_CreateInterface.setVisible(True) self.toolButton_EditInterface.setVisible(True) self.toolButton_EditInterface.setEnabled(False) self.textedit.setVisible(False) # # fill the interfaces if self._interfaces_files is None: self.interface_field.addItems(['interface searching...']) self.interface_field.setCurrentIndex(0) self._fill_interface_thread = InterfacesThread() self._fill_interface_thread.interfaces.connect(self._fill_interfaces) self._fill_interface_thread.start() else: self.toolButton_EditInterface.setEnabled(self.interface_field.currentText() in self._interfaces_files) self.buttonBox.clear() self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.interface_field.setFocus(Qt.TabFocusReason) self.resize(350, 80) def _fill_interfaces(self, interfaces_files): self._interfaces_files = interfaces_files self.interface_field.clear() self.interface_field.clearEditText() self.interface_field.addItems(self._interfaces_files.keys()) def _on_interface_selected(self, interface): if self._interfaces_files and interface in self._interfaces_files: self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', interface])) self.toolButton_EditInterface.setEnabled(True) else: self.toolButton_EditInterface.setEnabled(False) def accept(self): if self.textedit.isVisible(): try: tmp_file = os.path.join(nm.screen().LOG_PATH, 'tmp_sync_interface.sync') with open(tmp_file, 'w+') as f: f.write(self.textedit.toPlainText()) from master_discovery_fkie.common import read_interface read_interface(tmp_file) if not self._new_iface and self.interface_field.currentText() in self._interfaces_files: fileName = self._interfaces_files[self.interface_field.currentText()] else: fileName, _ = QFileDialog.getSaveFileName(self, 'Save sync interface', '/home', "Sync Files (*.sync)") if fileName: with open(fileName, 'w+') as f: self._interface_filename = fileName f.write(self.textedit.toPlainText()) if self._new_iface: self.interface_field.clear() self._interfaces_files = None self._on_select_interface_clicked() # QDialog.accept(self) # self.resetView() except Exception as e: MessageBox.warning(self, "Create sync interface", "Error while create interface", utf8(e)) elif self.interface_field.isVisible(): interface = self.interface_field.currentText() if self._interfaces_files and interface in self._interfaces_files: self._interface_filename = self._interfaces_files[interface] self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', interface])) QDialog.accept(self) self.resetView() else: QDialog.accept(self) self.resetView() def reject(self): if self.textedit.isVisible(): self._on_select_interface_clicked() else: QDialog.reject(self) self.resetView() def _on_create_interface_clicked(self): self._new_iface = True self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(True) self.textedit.setText("# The ignore_* lists will be processed first.\n" "# For ignore/sync nodes, topics or services\n" "# use follow declaration:\n" "#{param name}: \n" "# - {ros name}\n" "# or for selected hosts:\n" "# - {host name}:\n" "# - {ros name}\n\n" "# you can use follow wildcard: '*', but not as a first character\n" "ignore_hosts:\n" "sync_hosts:\n\n" "ignore_nodes:\n" "sync_nodes:\n\n" "ignore_topics:\n" "ignore_publishers:\n" "ignore_subscribers:\n" "sync_topics:\n\n" "ignore_services:\n" " - /*get_loggers\n" " - /*set_logger_level\n" "sync_services:\n\n" "# If sync_topics_on_demand is True the local subscribed and published topics\n" "# are synchronized with remote even if they are not in the sync_* list.\n" "sync_topics_on_demand: False\n\n" "# The nodes which are running not at the same host as the ROS master are not\n" "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n" "sync_remote_nodes: False\n\n" ) self.resize(350, 300) def _on_edit_interface_clicked(self): self._new_iface = False self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(True) if self.interface_field.currentText() in self._interfaces_files: try: with open(self._interfaces_files[self.interface_field.currentText()], 'rw') as f: iface = f.read() self.textedit.setText(iface) except Exception as e: MessageBox.warning(self, "Edit sync interface", "Error while open interface", utf8(e)) self.resize(350, 300) def resetView(self): self.toolButton_SyncAll.setVisible(True) # self.toolButton_SyncAllAnyMsg.setVisible(True) self.toolButton_SyncTopicOnDemand.setVisible(True) self.toolButton_SelectInterface.setVisible(True) self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(False) self.buttonBox.clear() self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.resize(350, 160)
def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None): ''' Creates an input dialog. @param topic: the name of the topic @type topic: C{str} @param msg_type: the type of the topic @type msg_type: C{str} @raise Exception: if no topic class was found for the given type ''' QDialog.__init__(self, parent=parent) self._masteruri = masteruri masteruri_str = '' if masteruri is None else '[%s]' % masteruri self.setObjectName(' - '.join(['EchoDialog', topic, masteruri_str])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.setWindowTitle('%s %s %s' % ('Echo --- ' if not show_only_rate else 'Hz --- ', topic, masteruri_str)) self.resize(728, 512) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png") self.setWindowIcon(self.mIcon) self.topic = topic self.show_only_rate = show_only_rate self.lock = threading.RLock() self.last_printed_count = 0 self.msg_t0 = -1. self.msg_tn = 0 self.times = [] self.message_count = 0 self._rate_message = '' self._scrapped_msgs = 0 self._scrapped_msgs_sl = 0 self._last_received_ts = 0 self.receiving_hz = self.MESSAGE_HZ_LIMIT self.line_limit = self.MESSAGE_LINE_LIMIT self.field_filter_fn = None options = QWidget(self) if not show_only_rate: hLayout = QHBoxLayout(options) hLayout.setContentsMargins(1, 1, 1, 1) self.no_str_checkbox = no_str_checkbox = QCheckBox('Hide strings') no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled) hLayout.addWidget(no_str_checkbox) self.no_arr_checkbox = no_arr_checkbox = QCheckBox('Hide arrays') no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled) hLayout.addWidget(no_arr_checkbox) self.combobox_reduce_ch = QComboBox(self) self.combobox_reduce_ch.addItems( [str(self.MESSAGE_LINE_LIMIT), '0', '80', '256', '1024']) self.combobox_reduce_ch.activated[str].connect( self.combobox_reduce_ch_activated) self.combobox_reduce_ch.setEditable(True) self.combobox_reduce_ch.setToolTip( "Set maximum line width. 0 disables the limit.") hLayout.addWidget(self.combobox_reduce_ch) # reduce_ch_label = QLabel('ch', self) # hLayout.addWidget(reduce_ch_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) # add combobox for displaying frequency of messages self.combobox_displ_hz = QComboBox(self) self.combobox_displ_hz.addItems([ str(self.MESSAGE_HZ_LIMIT), '0', '0.1', '1', '50', '100', '1000' ]) self.combobox_displ_hz.activated[str].connect( self.on_combobox_hz_activated) self.combobox_displ_hz.setEditable(True) self.combobox_displ_hz.setToolTip( "Set maximum displayed message rate in Hz. 0 disables the limit." ) hLayout.addWidget(self.combobox_displ_hz) displ_hz_label = QLabel('Hz', self) hLayout.addWidget(displ_hz_label) # add combobox for count of displayed messages self.combobox_msgs_count = QComboBox(self) self.combobox_msgs_count.addItems( [str(self.MAX_DISPLAY_MSGS), '0', '50', '100']) self.combobox_msgs_count.activated[str].connect( self.on_combobox_count_activated) self.combobox_msgs_count.setEditable(True) self.combobox_msgs_count.setToolTip( "Set maximum displayed message count. 0 disables the limit.") hLayout.addWidget(self.combobox_msgs_count) displ_count_label = QLabel('#', self) hLayout.addWidget(displ_count_label) # add topic control button for unsubscribe and subscribe self.topic_control_button = QToolButton(self) self.topic_control_button.setText('stop') self.topic_control_button.setIcon( QIcon(':/icons/deleket_deviantart_stop.png')) self.topic_control_button.clicked.connect( self.on_topic_control_btn_clicked) hLayout.addWidget(self.topic_control_button) # add clear button clearButton = QToolButton(self) clearButton.setText('clear') clearButton.clicked.connect(self.on_clear_btn_clicked) hLayout.addWidget(clearButton) self.verticalLayout.addWidget(options) self.display = QTextBrowser(self) self.display.setReadOnly(True) self.verticalLayout.addWidget(self.display) self.display.document().setMaximumBlockCount(500) self.max_displayed_msgs = self.MAX_DISPLAY_MSGS self._blocks_in_msg = None self.display.setOpenLinks(False) self.display.anchorClicked.connect(self._on_display_anchorClicked) self.status_label = QLabel('0 messages', self) self.verticalLayout.addWidget(self.status_label) # subscribe to the topic errmsg = '' try: self.__msg_class = message.get_message_class(msg_type) if not self.__msg_class: errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type # raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type) except Exception as e: self.__msg_class = None errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % ( msg_type, e) # raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e)) # variables for Subscriber self.msg_signal.connect(self._append_message) self.sub = None # vairables for SSH connection self.ssh_output_file = None self.ssh_error_file = None self.ssh_input_file = None self.text_signal.connect(self._append_text) self.text_hz_signal.connect(self._append_text_hz) self._current_msg = '' self._current_errmsg = '' self.text_error_signal.connect(self._append_error_text) # decide, which connection to open if use_ssh: self.__msg_class = None self._on_display_anchorClicked(QUrl(self._masteruri)) elif self.__msg_class is None: errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % ( errmsg) self.display.setText('<a href="%s">open using SSH</a>' % (masteruri)) self.display.append(errtxt) else: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self.print_hz_timer = QTimer() self.print_hz_timer.timeout.connect(self._on_calc_hz) self.print_hz_timer.start(1000)
class EchoDialog(QDialog): MESSAGE_LINE_LIMIT = 128 MESSAGE_HZ_LIMIT = 10 MAX_DISPLAY_MSGS = 25 STATISTIC_QUEUE_LEN = 5000 ''' This dialog shows the output of a topic. ''' finished_signal = Signal(str) ''' finished_signal has as parameter the name of the topic and is emitted, if this dialog was closed. ''' msg_signal = Signal(object, bool) ''' msg_signal is a signal, which is emitted, if a new message was received. ''' text_hz_signal = Signal(str) text_signal = Signal(str) ''' text_signal is a signal, which is emitted, if a new text to display was received. ''' text_error_signal = Signal(str) ''' text_error_signal is a signal, which is emitted, if a new error text to display was received. ''' request_pw = Signal(object) def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None): ''' Creates an input dialog. @param topic: the name of the topic @type topic: C{str} @param msg_type: the type of the topic @type msg_type: C{str} @raise Exception: if no topic class was found for the given type ''' QDialog.__init__(self, parent=parent) self._masteruri = masteruri masteruri_str = '' if masteruri is None else '[%s]' % masteruri self.setObjectName(' - '.join(['EchoDialog', topic, masteruri_str])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.setWindowTitle('%s %s %s' % ('Echo --- ' if not show_only_rate else 'Hz --- ', topic, masteruri_str)) self.resize(728, 512) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png") self.setWindowIcon(self.mIcon) self.topic = topic self.show_only_rate = show_only_rate self.lock = threading.RLock() self.last_printed_count = 0 self.msg_t0 = -1. self.msg_tn = 0 self.times = [] self.message_count = 0 self._rate_message = '' self._scrapped_msgs = 0 self._scrapped_msgs_sl = 0 self._last_received_ts = 0 self.receiving_hz = self.MESSAGE_HZ_LIMIT self.line_limit = self.MESSAGE_LINE_LIMIT self.field_filter_fn = None options = QWidget(self) if not show_only_rate: hLayout = QHBoxLayout(options) hLayout.setContentsMargins(1, 1, 1, 1) self.no_str_checkbox = no_str_checkbox = QCheckBox('Hide strings') no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled) hLayout.addWidget(no_str_checkbox) self.no_arr_checkbox = no_arr_checkbox = QCheckBox('Hide arrays') no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled) hLayout.addWidget(no_arr_checkbox) self.combobox_reduce_ch = QComboBox(self) self.combobox_reduce_ch.addItems( [str(self.MESSAGE_LINE_LIMIT), '0', '80', '256', '1024']) self.combobox_reduce_ch.activated[str].connect( self.combobox_reduce_ch_activated) self.combobox_reduce_ch.setEditable(True) self.combobox_reduce_ch.setToolTip( "Set maximum line width. 0 disables the limit.") hLayout.addWidget(self.combobox_reduce_ch) # reduce_ch_label = QLabel('ch', self) # hLayout.addWidget(reduce_ch_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) # add combobox for displaying frequency of messages self.combobox_displ_hz = QComboBox(self) self.combobox_displ_hz.addItems([ str(self.MESSAGE_HZ_LIMIT), '0', '0.1', '1', '50', '100', '1000' ]) self.combobox_displ_hz.activated[str].connect( self.on_combobox_hz_activated) self.combobox_displ_hz.setEditable(True) self.combobox_displ_hz.setToolTip( "Set maximum displayed message rate in Hz. 0 disables the limit." ) hLayout.addWidget(self.combobox_displ_hz) displ_hz_label = QLabel('Hz', self) hLayout.addWidget(displ_hz_label) # add combobox for count of displayed messages self.combobox_msgs_count = QComboBox(self) self.combobox_msgs_count.addItems( [str(self.MAX_DISPLAY_MSGS), '0', '50', '100']) self.combobox_msgs_count.activated[str].connect( self.on_combobox_count_activated) self.combobox_msgs_count.setEditable(True) self.combobox_msgs_count.setToolTip( "Set maximum displayed message count. 0 disables the limit.") hLayout.addWidget(self.combobox_msgs_count) displ_count_label = QLabel('#', self) hLayout.addWidget(displ_count_label) # add topic control button for unsubscribe and subscribe self.topic_control_button = QToolButton(self) self.topic_control_button.setText('stop') self.topic_control_button.setIcon( QIcon(':/icons/deleket_deviantart_stop.png')) self.topic_control_button.clicked.connect( self.on_topic_control_btn_clicked) hLayout.addWidget(self.topic_control_button) # add clear button clearButton = QToolButton(self) clearButton.setText('clear') clearButton.clicked.connect(self.on_clear_btn_clicked) hLayout.addWidget(clearButton) self.verticalLayout.addWidget(options) self.display = QTextBrowser(self) self.display.setReadOnly(True) self.verticalLayout.addWidget(self.display) self.display.document().setMaximumBlockCount(500) self.max_displayed_msgs = self.MAX_DISPLAY_MSGS self._blocks_in_msg = None self.display.setOpenLinks(False) self.display.anchorClicked.connect(self._on_display_anchorClicked) self.status_label = QLabel('0 messages', self) self.verticalLayout.addWidget(self.status_label) # subscribe to the topic errmsg = '' try: self.__msg_class = message.get_message_class(msg_type) if not self.__msg_class: errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type # raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type) except Exception as e: self.__msg_class = None errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % ( msg_type, e) # raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e)) # variables for Subscriber self.msg_signal.connect(self._append_message) self.sub = None # vairables for SSH connection self.ssh_output_file = None self.ssh_error_file = None self.ssh_input_file = None self.text_signal.connect(self._append_text) self.text_hz_signal.connect(self._append_text_hz) self._current_msg = '' self._current_errmsg = '' self.text_error_signal.connect(self._append_error_text) # decide, which connection to open if use_ssh: self.__msg_class = None self._on_display_anchorClicked(QUrl(self._masteruri)) elif self.__msg_class is None: errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % ( errmsg) self.display.setText('<a href="%s">open using SSH</a>' % (masteruri)) self.display.append(errtxt) else: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self.print_hz_timer = QTimer() self.print_hz_timer.timeout.connect(self._on_calc_hz) self.print_hz_timer.start(1000) # print "======== create", self.objectName() # # def __del__(self): # print "******* destroy", self.objectName() # def hideEvent(self, event): # self.close() def closeEvent(self, event): if self.sub is not None: self.sub.unregister() del self.sub try: self.ssh_output_file.close() self.ssh_error_file.close() # send Ctrl+C to remote process self.ssh_input_file.write('%s\n' % chr(3)) self.ssh_input_file.close() except: pass self.finished_signal.emit(self.topic) if self.parent() is None: QApplication.quit() # else: # self.setParent(None) def create_field_filter(self, echo_nostr, echo_noarr): def field_filter(val): try: # fields = val.__slots__ # field_types = val._slot_types for f, t in zip(val.__slots__, val._slot_types): if echo_noarr and '[' in t: continue elif echo_nostr and 'string' in t: continue yield f except: pass return field_filter def on_no_str_checkbox_toggled(self, state): self.field_filter_fn = self.create_field_filter( state, self.no_arr_checkbox.isChecked()) def on_no_arr_checkbox_toggled(self, state): self.field_filter_fn = self.create_field_filter( self.no_str_checkbox.isChecked(), state) def combobox_reduce_ch_activated(self, ch_txt): try: self.line_limit = int(ch_txt) except ValueError: try: self.line_limit = float(ch_txt) except ValueError: self.combobox_reduce_ch.setEditText(str(self.line_limit)) def on_combobox_hz_activated(self, hz_txt): try: self.receiving_hz = int(hz_txt) except ValueError: try: self.receiving_hz = float(hz_txt) except ValueError: self.combobox_displ_hz.setEditText(str(self.receiving_hz)) def on_combobox_count_activated(self, count_txt): try: self.max_displayed_msgs = int(count_txt) self._blocks_in_msg = None except ValueError: self.combobox_msgs_count.setEditText(str(self.max_displayed_msgs)) def on_clear_btn_clicked(self): self.display.clear() with self.lock: self.message_count = 0 self._scrapped_msgs = 0 del self.times[:] def on_topic_control_btn_clicked(self): try: if self.sub is None and self.ssh_output_file is None: if self.__msg_class: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) else: self._on_display_anchorClicked(QUrl(self._masteruri)) self.topic_control_button.setText('stop') self.topic_control_button.setIcon( QIcon(':/icons/deleket_deviantart_stop.png')) else: if self.sub is not None: self.sub.unregister() self.sub = None elif self.ssh_output_file is not None: self.ssh_output_file.close() self.ssh_error_file.close() self.ssh_output_file = None self.topic_control_button.setText('play') self.topic_control_button.setIcon( QIcon(':/icons/deleket_deviantart_play.png')) self.no_str_checkbox.setEnabled(True) self.no_arr_checkbox.setEnabled(True) except Exception as e: rospy.logwarn('Error while stop/play echo for topic %s: %s' % (self.topic, e)) def _msg_handle(self, data): self.msg_signal.emit(data, (data._connection_header['latching'] != '0')) def _append_message(self, msg, latched): ''' Adds a label to the dialog's layout and shows the given text. @param msg: the text to add to the dialog @type msg: message object ''' current_time = time.time() self._count_messages(current_time) # skip messages, if they are received often then MESSAGE_HZ_LIMIT if self._last_received_ts != 0 and self.receiving_hz != 0: if not latched and current_time - self._last_received_ts < 1.0 / self.receiving_hz: self._scrapped_msgs += 1 self._scrapped_msgs_sl += 1 return self._last_received_ts = current_time if not self.show_only_rate: # convert message to string and reduce line width to current limit msg = message.strify_message(msg, field_filter=self.field_filter_fn) if isinstance(msg, tuple): msg = msg[0] msg = self._trim_width(msg) msg = msg.replace('<', '<').replace('>', '>') # create a notification about scrapped messages if self._scrapped_msgs_sl > 0: txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">scrapped %s message because of Hz-settings</pre>' % self._scrapped_msgs_sl self.display.append(txt) self._scrapped_msgs_sl = 0 txt = '<pre style="background-color:#FFFCCC; color:#000000;font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % ( datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), msg) # set the count of the displayed messages on receiving the first message self._update_max_msg_count(txt) self.display.append(txt) self._print_status() def _count_messages(self, ts=time.time()): ''' Counts the received messages. Call this method only on receive message. ''' current_time = ts with self.lock: # time reset if self.msg_t0 < 0 or self.msg_t0 > current_time: self.msg_t0 = current_time self.msg_tn = current_time self.times = [] else: self.times.append(current_time - self.msg_tn) self.msg_tn = current_time # keep only statistics for the last 5000 messages so as not to run out of memory if len(self.times) > self.STATISTIC_QUEUE_LEN: self.times.pop(0) self.message_count += 1 def _trim_width(self, msg): ''' reduce line width to current limit :param msg: the message :type msg: str :return: trimmed message ''' result = msg if self.line_limit != 0: a = '' for l in msg.splitlines(): a = a + (l if len(l) <= self.line_limit else l[0:self.line_limit - 3] + '...') + '\n' result = a return result def _update_max_msg_count(self, txt): ''' set the count of the displayed messages on receiving the first message :param txt: text of the message, which will be added to the document :type txt: str ''' if self._blocks_in_msg is None: td = QTextDocument(txt) self._blocks_in_msg = td.blockCount() self.display.document().setMaximumBlockCount( self._blocks_in_msg * self.max_displayed_msgs) def _on_calc_hz(self): if rospy.is_shutdown(): self.close() return if self.message_count == self.last_printed_count: return with self.lock: # the code from ROS rostopic n = len(self.times) if n < 2: return mean = sum(self.times) / n rate = 1. / mean if mean > 0. else 0 # std dev std_dev = math.sqrt(sum((x - mean)**2 for x in self.times) / n) # min and max max_delta = max(self.times) min_delta = min(self.times) self.last_printed_count = self.message_count self._rate_message = "average rate: %.3f\tmin: %.3fs max: %.3fs std dev: %.5fs window: %s" % ( rate, min_delta, max_delta, std_dev, n + 1) if self._scrapped_msgs > 0: self._rate_message += " --- scrapped msgs: %s" % self._scrapped_msgs self._print_status() if self.show_only_rate: self.display.append(self._rate_message) def _print_status(self): self.status_label.setText('%s messages %s' % (self.message_count, self._rate_message)) def _append_text(self, text): ''' Append echo text received through the SSH. ''' with self.lock: self._current_msg += text if self._current_msg.find('---') != -1: messages = self._current_msg.split('---') for m in messages[:-1]: current_time = time.time() self._count_messages(current_time) # limit the displayed text width m = self._trim_width(m) txt = '<pre style="background-color:#FFFCCC; color:#000000;font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % ( datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), m) # set the count of the displayed messages on receiving the first message self._update_max_msg_count(txt) self.display.append(txt) self._current_msg = messages[-1] self._print_status() def _append_error_text(self, text): ''' Append error text received through the SSH. ''' with self.lock: self._current_errmsg += text if self._current_errmsg.find('\n') != -1: messages = self._current_errmsg.split('\n') for m in messages[:-1]: txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">%s</pre>' % m self.display.append(txt) self._current_errmsg = messages[-1] def _append_text_hz(self, text): ''' Append text received through the SSH for hz view. ''' with self.lock: self._current_msg += text if self._current_msg.find('\n') != -1: messages = self._current_msg.split('\n') for m in messages[:-1]: txt = '<div style="font-family:Fixedsys,Courier;">%s</div>' % ( m) self.display.append(txt) self._current_msg = messages[-1] def _on_display_anchorClicked(self, url, user=None, pw=None): try: ok = False if self.show_only_rate: self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh( ).ssh_exec(url.host(), ['rostopic hz %s' % (self.topic)], user, pw, auto_pw_request=True, get_pty=True) self.status_label.setText('connected to %s over SSH' % url.host()) else: self.combobox_displ_hz.setEnabled(False) nostr = '--nostr' if self.no_str_checkbox.isChecked() else '' noarr = '--noarr' if self.no_arr_checkbox.isChecked() else '' self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh( ).ssh_exec( url.host(), ['rostopic echo %s %s %s' % (nostr, noarr, self.topic)], user, pw, auto_pw_request=True, get_pty=True) if ok: self.display.clear() target = self._read_output_hz if self.show_only_rate else self._read_output thread = threading.Thread(target=target, args=((self.ssh_output_file, ))) thread.setDaemon(True) thread.start() thread = threading.Thread(target=self._read_error, args=((self.ssh_error_file, ))) thread.setDaemon(True) thread.start() elif self.ssh_output_file: self.ssh_output_file.close() self.ssh_error_file.close() except Exception as e: self._append_error_text('%s\n' % e) # import traceback # print traceback.format_exc() def _read_output_hz(self, output_file): try: while not output_file.closed: text = output_file.read(1) if text: self.text_hz_signal.emit(text) except: pass # import traceback # print traceback.format_exc() def _read_output(self, output_file): while not output_file.closed: text = output_file.read(1) if text: self.text_signal.emit(text) def _read_error(self, error_file): try: while not error_file.closed: text = error_file.read(1) if text: self.text_error_signal.emit(text) except: pass
class SyncDialog(QDialog): ''' A dialog to set the sync options. ''' def __init__(self, parent=None): QDialog.__init__(self, parent) # self.host = host self.setWindowIcon(QIcon(":/icons/irondevil_sync.png")) self.setWindowTitle('Sync') self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.resize(350, 190) self.toolButton_SyncAll = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(2) sizePolicy.setHeightForWidth( self.toolButton_SyncAll.sizePolicy().hasHeightForWidth()) self.toolButton_SyncAll.setSizePolicy(sizePolicy) self.toolButton_SyncAll.setObjectName("toolButton_SyncAll") self.verticalLayout.addWidget(self.toolButton_SyncAll) self.toolButton_SyncAll.setText(self._translate("Sync All")) self.toolButton_SyncAll.clicked.connect(self._on_sync_all_clicked) # self.toolButton_SyncAllAnyMsg = QToolButton(self) # sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) # sizePolicy.setHorizontalStretch(0) # sizePolicy.setVerticalStretch(1) # sizePolicy.setHeightForWidth(self.toolButton_SyncAllAnyMsg.sizePolicy().hasHeightForWidth()) # self.toolButton_SyncAllAnyMsg.setSizePolicy(sizePolicy) # self.toolButton_SyncAllAnyMsg.setObjectName("toolButton_SyncAllAnyMsg") # self.verticalLayout.addWidget(self.toolButton_SyncAllAnyMsg) # self.toolButton_SyncAllAnyMsg.setText(self._translate("Sync all (+AnyMsg)")) # self.toolButton_SyncAllAnyMsg.clicked.connect(self._on_sync_all_anymsg_clicked) self.toolButton_SyncTopicOnDemand = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.toolButton_SyncTopicOnDemand.sizePolicy().hasHeightForWidth()) self.toolButton_SyncTopicOnDemand.setSizePolicy(sizePolicy) self.toolButton_SyncTopicOnDemand.setObjectName( "toolButton_SyncTopicOnDemand") self.verticalLayout.addWidget(self.toolButton_SyncTopicOnDemand) self.toolButton_SyncTopicOnDemand.setText( self._translate("Sync only topics on demand")) self.toolButton_SyncTopicOnDemand.clicked.connect( self._on_sync_topics_on_demand_clicked) self.toolButton_SelectInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.toolButton_SelectInterface.sizePolicy().hasHeightForWidth()) self.toolButton_SelectInterface.setSizePolicy(sizePolicy) self.toolButton_SelectInterface.setObjectName( "toolButton_SelectInterface") self.verticalLayout.addWidget(self.toolButton_SelectInterface) self.toolButton_SelectInterface.setText( self._translate("Select an interface")) self.toolButton_SelectInterface.clicked.connect( self._on_select_interface_clicked) self.interface_field = QComboBox(self) self.interface_field.setInsertPolicy(QComboBox.InsertAlphabetically) self.interface_field.setSizePolicy( QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)) self.interface_field.setEditable(True) self.interface_field.setVisible(False) self.interface_field.setObjectName("interface_field") self.verticalLayout.addWidget(self.interface_field) self.interface_field.currentIndexChanged[str].connect( self._on_interface_selected) self.toolButton_EditInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.toolButton_EditInterface.sizePolicy().hasHeightForWidth()) self.toolButton_EditInterface.setSizePolicy(sizePolicy) self.toolButton_EditInterface.setObjectName("toolButton_EditInterface") self.verticalLayout.addWidget(self.toolButton_EditInterface) self.toolButton_EditInterface.setText( self._translate("Edit selected interface")) self.toolButton_EditInterface.clicked.connect( self._on_edit_interface_clicked) self.toolButton_EditInterface.setVisible(False) self.toolButton_CreateInterface = QToolButton(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(1) sizePolicy.setHeightForWidth( self.toolButton_CreateInterface.sizePolicy().hasHeightForWidth()) self.toolButton_CreateInterface.setSizePolicy(sizePolicy) self.toolButton_CreateInterface.setObjectName( "toolButton_CreateInterface") self.verticalLayout.addWidget(self.toolButton_CreateInterface) self.toolButton_CreateInterface.setText( self._translate("Create an interface")) self.toolButton_CreateInterface.clicked.connect( self._on_create_interface_clicked) self.toolButton_CreateInterface.setVisible(False) self.textedit = TextEdit('', self) self.hl = SyncHighlighter(self.textedit.document()) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.textedit.setSizePolicy(sizePolicy) self.textedit.setObjectName("syncedit") self.verticalLayout.addWidget(self.textedit) self.textedit.setVisible(False) self._fill_interface_thread = None self._interfaces_files = None self._sync_args = [] self._interface_filename = None self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.buttonBox.setOrientation(Qt.Horizontal) self.buttonBox.setObjectName("buttonBox") self.verticalLayout.addWidget(self.buttonBox) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self._new_iface = True def _translate(self, text): if hasattr(QApplication, "UnicodeUTF8"): return QApplication.translate("Form", text, None, QApplication.UnicodeUTF8) else: return QApplication.translate("Form", text, None) @property def sync_args(self): return self._sync_args @property def interface_filename(self): return self._interface_filename def _on_sync_all_clicked(self): self.setResult(QDialog.Accepted) self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', "'.'"])) self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'False'])) self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) self._sync_args.append(''.join(['_sync_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_services:=', '[]'])) self._sync_args.append(''.join(['_sync_services:=', '[]'])) self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) self._interface_filename = None self.accept() # def _on_sync_all_anymsg_clicked(self): # self._sync_args = [] # self._sync_args.append(''.join(['_interface_url:=', "'.'"])) # self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) # self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) # self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) # self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) # self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) # self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) # self._sync_args.append(''.join(['_sync_topics:=', '[/*]'])) # self._sync_args.append(''.join(['_ignore_services:=', '[]'])) # self._sync_args.append(''.join(['_sync_services:=', '[]'])) # self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) # self._interface_filename = None # self.accept() def _on_sync_topics_on_demand_clicked(self): self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', "'.'"])) self._sync_args.append(''.join(['_sync_topics_on_demand:=', 'True'])) self._sync_args.append(''.join(['_ignore_hosts:=', '[]'])) self._sync_args.append(''.join(['_sync_hosts:=', '[]'])) self._sync_args.append(''.join(['_ignore_nodes:=', '[]'])) self._sync_args.append(''.join(['_sync_nodes:=', '[]'])) self._sync_args.append(''.join(['_ignore_topics:=', '[]'])) self._sync_args.append(''.join(['_ignore_publishers:=', '[]'])) self._sync_args.append(''.join(['_ignore_subscribers:=', '[]'])) self._sync_args.append(''.join(['_sync_topics:=', '[/only_on_demand]'])) self._sync_args.append(''.join(['_ignore_services:=', '[/*]'])) self._sync_args.append(''.join(['_sync_services:=', '[]'])) self._sync_args.append(''.join(['_sync_remote_nodes:=', 'False'])) self._interface_filename = None self.accept() def _on_select_interface_clicked(self): self.toolButton_SyncAll.setVisible(False) # self.toolButton_SyncAllAnyMsg.setVisible(False) self.toolButton_SyncTopicOnDemand.setVisible(False) self.toolButton_SelectInterface.setVisible(False) self.interface_field.setVisible(True) self.toolButton_CreateInterface.setVisible(True) self.toolButton_EditInterface.setVisible(True) self.toolButton_EditInterface.setEnabled(False) self.textedit.setVisible(False) # # fill the interfaces if self._interfaces_files is None: self.interface_field.addItems(['interface searching...']) self.interface_field.setCurrentIndex(0) self._fill_interface_thread = InterfacesThread() self._fill_interface_thread.interfaces.connect( self._fill_interfaces) self._fill_interface_thread.start() else: self.toolButton_EditInterface.setEnabled( self.interface_field.currentText() in self._interfaces_files) self.buttonBox.clear() self.buttonBox.setStandardButtons(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) self.interface_field.setFocus(Qt.TabFocusReason) self.resize(350, 80) def _fill_interfaces(self, interfaces_files): self._interfaces_files = interfaces_files self.interface_field.clear() self.interface_field.clearEditText() self.interface_field.addItems(self._interfaces_files.keys()) def _on_interface_selected(self, interface): if self._interfaces_files and interface in self._interfaces_files: self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', interface])) self.toolButton_EditInterface.setEnabled(True) else: self.toolButton_EditInterface.setEnabled(False) def accept(self): if self.textedit.isVisible(): try: tmp_file = os.path.join(screen.LOG_PATH, 'tmp_sync_interface.sync') with open(tmp_file, 'w+') as f: f.write(self.textedit.toPlainText()) from fkie_master_discovery.common import read_interface read_interface(tmp_file) if not self._new_iface and self.interface_field.currentText( ) in self._interfaces_files: fileName = self._interfaces_files[ self.interface_field.currentText()] else: fileName, _ = QFileDialog.getSaveFileName( self, 'Save sync interface', '/home', "Sync Files (*.sync)") if fileName: with open(fileName, 'w+') as f: self._interface_filename = fileName f.write(self.textedit.toPlainText()) if self._new_iface: self.interface_field.clear() self._interfaces_files = None self._on_select_interface_clicked() # QDialog.accept(self) # self.resetView() except Exception as e: MessageBox.warning(self, "Create sync interface", "Error while create interface", utf8(e)) elif self.interface_field.isVisible(): interface = self.interface_field.currentText() if self._interfaces_files and interface in self._interfaces_files: self._interface_filename = self._interfaces_files[interface] self._sync_args = [] self._sync_args.append(''.join(['_interface_url:=', interface])) QDialog.accept(self) self.resetView() else: QDialog.accept(self) self.resetView() def reject(self): if self.textedit.isVisible(): self._on_select_interface_clicked() else: QDialog.reject(self) self.resetView() def _on_create_interface_clicked(self): self._new_iface = True self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(True) self.textedit.setText( "# The ignore_* lists will be processed first.\n" "# For ignore/sync nodes, topics or services\n" "# use follow declaration:\n" "#{param name}: \n" "# - {ros name}\n" "# or for selected hosts:\n" "# - {host name}:\n" "# - {ros name}\n\n" "# you can use follow wildcard: '*', but not as a first character\n" "ignore_hosts:\n" "sync_hosts:\n\n" "ignore_nodes:\n" "sync_nodes:\n\n" "ignore_topics:\n" "ignore_publishers:\n" "ignore_subscribers:\n" "sync_topics:\n\n" "ignore_services:\n" " - /*get_loggers\n" " - /*set_logger_level\n" "sync_services:\n\n" "# If sync_topics_on_demand is True the local subscribed and published topics\n" "# are synchronized with remote even if they are not in the sync_* list.\n" "sync_topics_on_demand: False\n\n" "# The nodes which are running not at the same host as the ROS master are not\n" "# synchronized by default. Use sync_remote_nodes to sync these nodes also.\n" "sync_remote_nodes: False\n\n") self.resize(350, 300) def _on_edit_interface_clicked(self): self._new_iface = False self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(True) if self.interface_field.currentText() in self._interfaces_files: try: with open( self._interfaces_files[ self.interface_field.currentText()], 'rw') as f: iface = f.read() self.textedit.setText(iface) except Exception as e: MessageBox.warning(self, "Edit sync interface", "Error while open interface", utf8(e)) self.resize(350, 300) def resetView(self): self.toolButton_SyncAll.setVisible(True) # self.toolButton_SyncAllAnyMsg.setVisible(True) self.toolButton_SyncTopicOnDemand.setVisible(True) self.toolButton_SelectInterface.setVisible(True) self.interface_field.setVisible(False) self.toolButton_CreateInterface.setVisible(False) self.toolButton_EditInterface.setVisible(False) self.textedit.setVisible(False) self.buttonBox.clear() self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.resize(350, 160)
def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None): """ Creates an input dialog. @param topic: the name of the topic @type topic: C{str} @param msg_type: the type of the topic @type msg_type: C{str} @raise Exception: if no topic class was found for the given type """ QDialog.__init__(self, parent=parent) self._masteruri = masteruri masteruri_str = "" if masteruri is None else "[%s]" % masteruri self.setObjectName(" - ".join(["EchoDialog", topic, masteruri_str])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.setWindowTitle("%s %s %s" % ("Echo --- " if not show_only_rate else "Hz --- ", topic, masteruri_str)) self.resize(728, 512) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png") self.setWindowIcon(self.mIcon) self.topic = topic self.show_only_rate = show_only_rate self.lock = threading.RLock() self.last_printed_count = 0 self.msg_t0 = -1.0 self.msg_tn = 0 self.times = [] self.message_count = 0 self._rate_message = "" self._scrapped_msgs = 0 self._scrapped_msgs_sl = 0 self._last_received_ts = 0 self.receiving_hz = self.MESSAGE_HZ_LIMIT self.line_limit = self.MESSAGE_LINE_LIMIT self.field_filter_fn = None options = QWidget(self) if not show_only_rate: hLayout = QHBoxLayout(options) hLayout.setContentsMargins(1, 1, 1, 1) self.no_str_checkbox = no_str_checkbox = QCheckBox("Hide strings") no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled) hLayout.addWidget(no_str_checkbox) self.no_arr_checkbox = no_arr_checkbox = QCheckBox("Hide arrays") no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled) hLayout.addWidget(no_arr_checkbox) self.combobox_reduce_ch = QComboBox(self) self.combobox_reduce_ch.addItems([str(self.MESSAGE_LINE_LIMIT), "0", "80", "256", "1024"]) self.combobox_reduce_ch.activated[str].connect(self.combobox_reduce_ch_activated) self.combobox_reduce_ch.setEditable(True) self.combobox_reduce_ch.setToolTip("Set maximum line width. 0 disables the limit.") hLayout.addWidget(self.combobox_reduce_ch) # reduce_ch_label = QLabel('ch', self) # hLayout.addWidget(reduce_ch_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) # add combobox for displaying frequency of messages self.combobox_displ_hz = QComboBox(self) self.combobox_displ_hz.addItems([str(self.MESSAGE_HZ_LIMIT), "0", "0.1", "1", "50", "100", "1000"]) self.combobox_displ_hz.activated[str].connect(self.on_combobox_hz_activated) self.combobox_displ_hz.setEditable(True) hLayout.addWidget(self.combobox_displ_hz) displ_hz_label = QLabel("Hz", self) hLayout.addWidget(displ_hz_label) # add combobox for count of displayed messages self.combobox_msgs_count = QComboBox(self) self.combobox_msgs_count.addItems([str(self.MAX_DISPLAY_MSGS), "0", "50", "100"]) self.combobox_msgs_count.activated[str].connect(self.on_combobox_count_activated) self.combobox_msgs_count.setEditable(True) self.combobox_msgs_count.setToolTip("Set maximum displayed message count. 0 disables the limit.") hLayout.addWidget(self.combobox_msgs_count) displ_count_label = QLabel("#", self) hLayout.addWidget(displ_count_label) # add topic control button for unsubscribe and subscribe self.topic_control_button = QToolButton(self) self.topic_control_button.setText("stop") self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png")) self.topic_control_button.clicked.connect(self.on_topic_control_btn_clicked) hLayout.addWidget(self.topic_control_button) # add clear button clearButton = QToolButton(self) clearButton.setText("clear") clearButton.clicked.connect(self.on_clear_btn_clicked) hLayout.addWidget(clearButton) self.verticalLayout.addWidget(options) self.display = QTextBrowser(self) self.display.setReadOnly(True) self.verticalLayout.addWidget(self.display) self.display.document().setMaximumBlockCount(500) self.max_displayed_msgs = self.MAX_DISPLAY_MSGS self._blocks_in_msg = None self.display.setOpenLinks(False) self.display.anchorClicked.connect(self._on_display_anchorClicked) self.status_label = QLabel("0 messages", self) self.verticalLayout.addWidget(self.status_label) # subscribe to the topic errmsg = "" try: self.__msg_class = message.get_message_class(msg_type) if not self.__msg_class: errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type # raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type) except Exception as e: self.__msg_class = None errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (msg_type, e) # raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e)) # variables for Subscriber self.msg_signal.connect(self._append_message) self.sub = None # vairables for SSH connection self.ssh_output_file = None self.ssh_error_file = None self.ssh_input_file = None self.text_signal.connect(self._append_text) self.text_hz_signal.connect(self._append_text_hz) self._current_msg = "" self._current_errmsg = "" self.text_error_signal.connect(self._append_error_text) # decide, which connection to open if use_ssh: self.__msg_class = None self._on_display_anchorClicked(QUrl(self._masteruri)) elif self.__msg_class is None: errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % ( errmsg ) self.display.setText('<a href="%s">open using SSH</a>' % (masteruri)) self.display.append(errtxt) else: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self.print_hz_timer = QTimer() self.print_hz_timer.timeout.connect(self._on_calc_hz) self.print_hz_timer.start(1000)
class EchoDialog(QDialog): MESSAGE_LINE_LIMIT = 128 MESSAGE_HZ_LIMIT = 10 MAX_DISPLAY_MSGS = 25 STATISTIC_QUEUE_LEN = 5000 """ This dialog shows the output of a topic. """ finished_signal = Signal(str) """ finished_signal has as parameter the name of the topic and is emitted, if this dialog was closed. """ msg_signal = Signal(object, bool) """ msg_signal is a signal, which is emitted, if a new message was received. """ text_hz_signal = Signal(str) text_signal = Signal(str) """ text_signal is a signal, which is emitted, if a new text to display was received. """ text_error_signal = Signal(str) """ text_error_signal is a signal, which is emitted, if a new error text to display was received. """ request_pw = Signal(object) def __init__(self, topic, msg_type, show_only_rate=False, masteruri=None, use_ssh=False, parent=None): """ Creates an input dialog. @param topic: the name of the topic @type topic: C{str} @param msg_type: the type of the topic @type msg_type: C{str} @raise Exception: if no topic class was found for the given type """ QDialog.__init__(self, parent=parent) self._masteruri = masteruri masteruri_str = "" if masteruri is None else "[%s]" % masteruri self.setObjectName(" - ".join(["EchoDialog", topic, masteruri_str])) self.setAttribute(Qt.WA_DeleteOnClose, True) self.setWindowFlags(Qt.Window) self.setWindowTitle("%s %s %s" % ("Echo --- " if not show_only_rate else "Hz --- ", topic, masteruri_str)) self.resize(728, 512) self.verticalLayout = QVBoxLayout(self) self.verticalLayout.setObjectName("verticalLayout") self.verticalLayout.setContentsMargins(1, 1, 1, 1) self.mIcon = QIcon(":/icons/crystal_clear_prop_run_echo.png") self.setWindowIcon(self.mIcon) self.topic = topic self.show_only_rate = show_only_rate self.lock = threading.RLock() self.last_printed_count = 0 self.msg_t0 = -1.0 self.msg_tn = 0 self.times = [] self.message_count = 0 self._rate_message = "" self._scrapped_msgs = 0 self._scrapped_msgs_sl = 0 self._last_received_ts = 0 self.receiving_hz = self.MESSAGE_HZ_LIMIT self.line_limit = self.MESSAGE_LINE_LIMIT self.field_filter_fn = None options = QWidget(self) if not show_only_rate: hLayout = QHBoxLayout(options) hLayout.setContentsMargins(1, 1, 1, 1) self.no_str_checkbox = no_str_checkbox = QCheckBox("Hide strings") no_str_checkbox.toggled.connect(self.on_no_str_checkbox_toggled) hLayout.addWidget(no_str_checkbox) self.no_arr_checkbox = no_arr_checkbox = QCheckBox("Hide arrays") no_arr_checkbox.toggled.connect(self.on_no_arr_checkbox_toggled) hLayout.addWidget(no_arr_checkbox) self.combobox_reduce_ch = QComboBox(self) self.combobox_reduce_ch.addItems([str(self.MESSAGE_LINE_LIMIT), "0", "80", "256", "1024"]) self.combobox_reduce_ch.activated[str].connect(self.combobox_reduce_ch_activated) self.combobox_reduce_ch.setEditable(True) self.combobox_reduce_ch.setToolTip("Set maximum line width. 0 disables the limit.") hLayout.addWidget(self.combobox_reduce_ch) # reduce_ch_label = QLabel('ch', self) # hLayout.addWidget(reduce_ch_label) # add spacer spacerItem = QSpacerItem(515, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) hLayout.addItem(spacerItem) # add combobox for displaying frequency of messages self.combobox_displ_hz = QComboBox(self) self.combobox_displ_hz.addItems([str(self.MESSAGE_HZ_LIMIT), "0", "0.1", "1", "50", "100", "1000"]) self.combobox_displ_hz.activated[str].connect(self.on_combobox_hz_activated) self.combobox_displ_hz.setEditable(True) hLayout.addWidget(self.combobox_displ_hz) displ_hz_label = QLabel("Hz", self) hLayout.addWidget(displ_hz_label) # add combobox for count of displayed messages self.combobox_msgs_count = QComboBox(self) self.combobox_msgs_count.addItems([str(self.MAX_DISPLAY_MSGS), "0", "50", "100"]) self.combobox_msgs_count.activated[str].connect(self.on_combobox_count_activated) self.combobox_msgs_count.setEditable(True) self.combobox_msgs_count.setToolTip("Set maximum displayed message count. 0 disables the limit.") hLayout.addWidget(self.combobox_msgs_count) displ_count_label = QLabel("#", self) hLayout.addWidget(displ_count_label) # add topic control button for unsubscribe and subscribe self.topic_control_button = QToolButton(self) self.topic_control_button.setText("stop") self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png")) self.topic_control_button.clicked.connect(self.on_topic_control_btn_clicked) hLayout.addWidget(self.topic_control_button) # add clear button clearButton = QToolButton(self) clearButton.setText("clear") clearButton.clicked.connect(self.on_clear_btn_clicked) hLayout.addWidget(clearButton) self.verticalLayout.addWidget(options) self.display = QTextBrowser(self) self.display.setReadOnly(True) self.verticalLayout.addWidget(self.display) self.display.document().setMaximumBlockCount(500) self.max_displayed_msgs = self.MAX_DISPLAY_MSGS self._blocks_in_msg = None self.display.setOpenLinks(False) self.display.anchorClicked.connect(self._on_display_anchorClicked) self.status_label = QLabel("0 messages", self) self.verticalLayout.addWidget(self.status_label) # subscribe to the topic errmsg = "" try: self.__msg_class = message.get_message_class(msg_type) if not self.__msg_class: errmsg = "Cannot load message class for [%s]. Did you build messages?" % msg_type # raise Exception("Cannot load message class for [%s]. Did you build messages?"%msg_type) except Exception as e: self.__msg_class = None errmsg = "Cannot load message class for [%s]. Did you build messagest?\nError: %s" % (msg_type, e) # raise Exception("Cannot load message class for [%s]. Did you build messagest?\nError: %s"%(msg_type, e)) # variables for Subscriber self.msg_signal.connect(self._append_message) self.sub = None # vairables for SSH connection self.ssh_output_file = None self.ssh_error_file = None self.ssh_input_file = None self.text_signal.connect(self._append_text) self.text_hz_signal.connect(self._append_text_hz) self._current_msg = "" self._current_errmsg = "" self.text_error_signal.connect(self._append_error_text) # decide, which connection to open if use_ssh: self.__msg_class = None self._on_display_anchorClicked(QUrl(self._masteruri)) elif self.__msg_class is None: errtxt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">\n%s</pre>' % ( errmsg ) self.display.setText('<a href="%s">open using SSH</a>' % (masteruri)) self.display.append(errtxt) else: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) self.print_hz_timer = QTimer() self.print_hz_timer.timeout.connect(self._on_calc_hz) self.print_hz_timer.start(1000) # print "======== create", self.objectName() # # def __del__(self): # print "******* destroy", self.objectName() # def hideEvent(self, event): # self.close() def closeEvent(self, event): if self.sub is not None: self.sub.unregister() del self.sub try: self.ssh_output_file.close() self.ssh_error_file.close() # send Ctrl+C to remote process self.ssh_input_file.write("%s\n" % chr(3)) self.ssh_input_file.close() except: pass self.finished_signal.emit(self.topic) if self.parent() is None: QApplication.quit() # else: # self.setParent(None) def create_field_filter(self, echo_nostr, echo_noarr): def field_filter(val): try: # fields = val.__slots__ # field_types = val._slot_types for f, t in zip(val.__slots__, val._slot_types): if echo_noarr and "[" in t: continue elif echo_nostr and "string" in t: continue yield f except: pass return field_filter def on_no_str_checkbox_toggled(self, state): self.field_filter_fn = self.create_field_filter(state, self.no_arr_checkbox.isChecked()) def on_no_arr_checkbox_toggled(self, state): self.field_filter_fn = self.create_field_filter(self.no_str_checkbox.isChecked(), state) def combobox_reduce_ch_activated(self, ch_txt): try: self.line_limit = int(ch_txt) except ValueError: try: self.line_limit = float(ch_txt) except ValueError: self.combobox_reduce_ch.setEditText(str(self.line_limit)) def on_combobox_hz_activated(self, hz_txt): try: self.receiving_hz = int(hz_txt) except ValueError: try: self.receiving_hz = float(hz_txt) except ValueError: self.combobox_displ_hz.setEditText(str(self.receiving_hz)) def on_combobox_count_activated(self, count_txt): try: self.max_displayed_msgs = int(count_txt) self._blocks_in_msg = None except ValueError: self.combobox_msgs_count.setEditText(str(self.max_displayed_msgs)) def on_clear_btn_clicked(self): self.display.clear() with self.lock: self.message_count = 0 self._scrapped_msgs = 0 del self.times[:] def on_topic_control_btn_clicked(self): try: if self.sub is None and self.ssh_output_file is None: if self.__msg_class: self.sub = rospy.Subscriber(self.topic, self.__msg_class, self._msg_handle) else: self._on_display_anchorClicked(QUrl(self._masteruri)) self.topic_control_button.setText("stop") self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_stop.png")) else: if self.sub is not None: self.sub.unregister() self.sub = None elif self.ssh_output_file is not None: self.ssh_output_file.close() self.ssh_error_file.close() self.ssh_output_file = None self.topic_control_button.setText("play") self.topic_control_button.setIcon(QIcon(":/icons/deleket_deviantart_play.png")) self.no_str_checkbox.setEnabled(True) self.no_arr_checkbox.setEnabled(True) except Exception as e: rospy.logwarn("Error while stop/play echo for topic %s: %s" % (self.topic, e)) def _msg_handle(self, data): self.msg_signal.emit(data, (data._connection_header["latching"] != "0")) def _append_message(self, msg, latched): """ Adds a label to the dialog's layout and shows the given text. @param msg: the text to add to the dialog @type msg: message object """ current_time = time.time() self._count_messages(current_time) # skip messages, if they are received often then MESSAGE_HZ_LIMIT if self._last_received_ts != 0 and self.receiving_hz != 0: if not latched and current_time - self._last_received_ts < 1.0 / self.receiving_hz: self._scrapped_msgs += 1 self._scrapped_msgs_sl += 1 return self._last_received_ts = current_time if not self.show_only_rate: # convert message to string and reduce line width to current limit msg = message.strify_message(msg, field_filter=self.field_filter_fn) if isinstance(msg, tuple): msg = msg[0] msg = self._trim_width(msg) msg = msg.replace("<", "<").replace(">", ">") # create a notification about scrapped messages if self._scrapped_msgs_sl > 0: txt = ( '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">scrapped %s message because of Hz-settings</pre>' % self._scrapped_msgs_sl ) self.display.append(txt) self._scrapped_msgs_sl = 0 txt = ( '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % (datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), msg) ) # set the count of the displayed messages on receiving the first message self._update_max_msg_count(txt) self.display.append(txt) self._print_status() def _count_messages(self, ts=time.time()): """ Counts the received messages. Call this method only on receive message. """ current_time = ts with self.lock: # time reset if self.msg_t0 < 0 or self.msg_t0 > current_time: self.msg_t0 = current_time self.msg_tn = current_time self.times = [] else: self.times.append(current_time - self.msg_tn) self.msg_tn = current_time # keep only statistics for the last 5000 messages so as not to run out of memory if len(self.times) > self.STATISTIC_QUEUE_LEN: self.times.pop(0) self.message_count += 1 def _trim_width(self, msg): """ reduce line width to current limit :param msg: the message :type msg: str :return: trimmed message """ result = msg if self.line_limit != 0: a = "" for l in msg.splitlines(): a = a + (l if len(l) <= self.line_limit else l[0 : self.line_limit - 3] + "...") + "\n" result = a return result def _update_max_msg_count(self, txt): """ set the count of the displayed messages on receiving the first message :param txt: text of the message, which will be added to the document :type txt: str """ if self._blocks_in_msg is None: td = QTextDocument(txt) self._blocks_in_msg = td.blockCount() self.display.document().setMaximumBlockCount(self._blocks_in_msg * self.max_displayed_msgs) def _on_calc_hz(self): if rospy.is_shutdown(): self.close() return if self.message_count == self.last_printed_count: return with self.lock: # the code from ROS rostopic n = len(self.times) if n < 2: return mean = sum(self.times) / n rate = 1.0 / mean if mean > 0.0 else 0 # std dev std_dev = math.sqrt(sum((x - mean) ** 2 for x in self.times) / n) # min and max max_delta = max(self.times) min_delta = min(self.times) self.last_printed_count = self.message_count self._rate_message = "average rate: %.3f\tmin: %.3fs max: %.3fs std dev: %.5fs window: %s" % ( rate, min_delta, max_delta, std_dev, n + 1, ) if self._scrapped_msgs > 0: self._rate_message += " --- scrapped msgs: %s" % self._scrapped_msgs self._print_status() if self.show_only_rate: self.display.append(self._rate_message) def _print_status(self): self.status_label.setText("%s messages %s" % (self.message_count, self._rate_message)) def _append_text(self, text): """ Append echo text received through the SSH. """ with self.lock: self._current_msg += text if self._current_msg.find("---") != -1: messages = self._current_msg.split("---") for m in messages[:-1]: current_time = time.time() self._count_messages(current_time) # limit the displayed text width m = self._trim_width(m) txt = ( '<pre style="background-color:#FFFCCC; font-family:Fixedsys,Courier; padding:10px;">---------- %s --------------------\n%s</pre>' % (datetime.now().strftime("%d.%m.%Y %H:%M:%S.%f"), m) ) # set the count of the displayed messages on receiving the first message self._update_max_msg_count(txt) self.display.append(txt) self._current_msg = messages[-1] self._print_status() def _append_error_text(self, text): """ Append error text received through the SSH. """ with self.lock: self._current_errmsg += text if self._current_errmsg.find("\n") != -1: messages = self._current_errmsg.split("\n") for m in messages[:-1]: txt = '<pre style="color:red; font-family:Fixedsys,Courier,monospace; padding:10px;">%s</pre>' % m self.display.append(txt) self._current_errmsg = messages[-1] def _append_text_hz(self, text): """ Append text received through the SSH for hz view. """ with self.lock: self._current_msg += text if self._current_msg.find("\n") != -1: messages = self._current_msg.split("\n") for m in messages[:-1]: txt = '<div style="font-family:Fixedsys,Courier;">%s</div>' % (m) self.display.append(txt) self._current_msg = messages[-1] def _on_display_anchorClicked(self, url, user=None, pw=None): try: ok = False if self.show_only_rate: self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh().ssh_exec( url.host(), ["rostopic hz %s" % (self.topic)], user, pw, auto_pw_request=True, get_pty=True ) self.status_label.setText("connected to %s over SSH" % url.host()) else: self.combobox_displ_hz.setEnabled(False) nostr = "--nostr" if self.no_str_checkbox.isChecked() else "" noarr = "--noarr" if self.no_arr_checkbox.isChecked() else "" self.ssh_input_file, self.ssh_output_file, self.ssh_error_file, ok = nm.ssh().ssh_exec( url.host(), ["rostopic echo %s %s %s" % (nostr, noarr, self.topic)], user, pw, auto_pw_request=True, get_pty=True, ) if ok: self.display.clear() target = self._read_output_hz if self.show_only_rate else self._read_output thread = threading.Thread(target=target, args=((self.ssh_output_file,))) thread.setDaemon(True) thread.start() thread = threading.Thread(target=self._read_error, args=((self.ssh_error_file,))) thread.setDaemon(True) thread.start() elif self.ssh_output_file: self.ssh_output_file.close() self.ssh_error_file.close() except Exception as e: self._append_error_text("%s\n" % e) # import traceback # print traceback.format_exc() def _read_output_hz(self, output_file): try: while not output_file.closed: text = output_file.read(1) if text: self.text_hz_signal.emit(text) except: pass # import traceback # print traceback.format_exc() def _read_output(self, output_file): while not output_file.closed: text = output_file.read(1) if text: self.text_signal.emit(text) def _read_error(self, error_file): try: while not error_file.closed: text = error_file.read(1) if text: self.text_error_signal.emit(text) except: pass