class BusMonitorWidget(QGroupBox): DEFAULT_PLOT_X_RANGE = 120 BUS_LOAD_PLOT_MAX_SAMPLES = 5000 def __init__(self, parent, node, iface_name): super(BusMonitorWidget, self).__init__(parent) self.setTitle('CAN bus activity (%s)' % iface_name.split(os.path.sep)[-1]) self._node = node self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook) self._columns = [ BasicTable.Column('Dir', lambda e: (e[0].upper()), searchable=False), BasicTable.Column('Local Time', TimestampRenderer(), searchable=False), BasicTable.Column('CAN ID', lambda e: (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8), colorize_can_id(e[1]))), BasicTable.Column('Data Hex', lambda e: (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[1].MAX_DATA_LENGTH), colorize_transfer_id(e))), BasicTable.Column('Data ASCII', lambda e: (''.join([(chr(x) if 32 <= x <= 126 else '.') for x in e[1].data]), colorize_transfer_id(e))), BasicTable.Column('Src', lambda e: render_node_id_with_color(e[1], 'src')), BasicTable.Column('Dst', lambda e: render_node_id_with_color(e[1], 'dst')), BasicTable.Column('Data Type', lambda e: render_data_type_with_color(e[1]), resize_mode=QHeaderView.Stretch), ] self._log_widget = RealtimeLogWidget(self, columns=self._columns, font=get_monospace_font(), post_redraw_hook=self._redraw_hook) self._log_widget.on_selection_changed = self._update_measurement_display def flip_row_mark(row, col): if col == 0: item = self._log_widget.table.item(row, col) if item.icon().isNull(): item.setIcon(get_icon('circle')) flash(self, 'Row %d was marked, click again to unmark', row, duration=3) else: item.setIcon(QIcon()) self._log_widget.table.cellPressed.connect(flip_row_mark) self._stat_update_timer = QTimer(self) self._stat_update_timer.setSingleShot(False) self._stat_update_timer.timeout.connect(self._update_stat) self._stat_update_timer.start(500) self._traffic_stat = TrafficStatCounter() self._stat_frames_tx = QLabel('N/A', self) self._stat_frames_rx = QLabel('N/A', self) self._stat_traffic = QLabel('N/A', self) self._load_plot = PlotWidget(background=(0, 0, 0)) self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE), padding=0) self._load_plot.setMaximumHeight(150) self._load_plot.setMinimumHeight(100) self._load_plot.setMinimumWidth(100) self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._load_plot.showGrid(x=True, y=True, alpha=0.4) self._load_plot.setToolTip('Frames per second') self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True, y=False) self._load_plot.enableAutoRange() self._bus_load_plot = self._load_plot.plot(name='Frames per second', pen=mkPen(QColor(Qt.lightGray), width=1)) self._bus_load_samples = [], [] self._started_at_mono = time.monotonic() layout = QVBoxLayout(self) layout.addWidget(self._log_widget, 1) stat_vars_layout = QGridLayout(self) stat_layout_next_row = 0 def add_stat_row(label, value): nonlocal stat_layout_next_row stat_vars_layout.addWidget(QLabel(label, self), stat_layout_next_row, 0) stat_vars_layout.addWidget(value, stat_layout_next_row, 1) value.setMinimumWidth(75) stat_layout_next_row += 1 add_stat_row('Frames transmitted:', self._stat_frames_tx) add_stat_row('Frames received:', self._stat_frames_rx) add_stat_row('Frames per second:', self._stat_traffic) stat_vars_layout.setRowStretch(stat_layout_next_row, 1) stat_layout = QHBoxLayout(self) stat_layout.addLayout(stat_vars_layout) stat_layout.addWidget(self._load_plot, 1) layout.addLayout(stat_layout, 0) self.setLayout(layout) def close(self): self._hook_handle.remove() def _update_stat(self): bus_load, ts_mono = self._traffic_stat.get_frames_per_second() self._stat_traffic.setText(str(int(bus_load + 0.5))) if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES: self._bus_load_samples[0].pop(0) self._bus_load_samples[1].pop(0) self._bus_load_samples[1].append(bus_load) self._bus_load_samples[0].append(ts_mono - self._started_at_mono) self._bus_load_plot.setData(*self._bus_load_samples) (xmin, xmax), _ = self._load_plot.viewRange() diff = xmax - xmin xmax = self._bus_load_samples[0][-1] xmin = self._bus_load_samples[0][-1] - diff self._load_plot.setRange(xRange=(xmin, xmax), padding=0) def _redraw_hook(self): self._stat_frames_tx.setText(str(self._traffic_stat.tx)) self._stat_frames_rx.setText(str(self._traffic_stat.rx)) def _frame_hook(self, direction, frame): self._traffic_stat.add_frame(direction, frame) self._log_widget.add_item_async((direction, frame)) def _update_measurement_display(self, selected_rows_cols): if not selected_rows_cols: return min_row = min([row for row, _ in selected_rows_cols]) max_row = max([row for row, _ in selected_rows_cols]) def get_row_ts(row): return TimestampRenderer.parse_timestamp(self._log_widget.table.item(row, 1).text()) def get_load_str(num_frames, dt): if dt >= 1e-6: return 'average load %.1f FPS' % (num_frames / dt) return 'average load is unknown' if min_row == max_row: num_frames = min_row first_ts = get_row_ts(0) current_ts = get_row_ts(min_row) dt = current_ts - first_ts flash(self, '%d frames from beginning, %.3f sec since first frame, %s', num_frames, dt, get_load_str(num_frames, dt)) else: num_frames = max_row - min_row + 1 first_ts = get_row_ts(min_row) last_ts = get_row_ts(max_row) dt = last_ts - first_ts flash(self, '%d frames, timedelta %.6f sec, %s', num_frames, dt, get_load_str(num_frames, dt))
class BusMonitorWidget(QGroupBox): DEFAULT_PLOT_X_RANGE = 120 BUS_LOAD_PLOT_MAX_SAMPLES = 5000 def __init__(self, parent, node, iface_name): super(BusMonitorWidget, self).__init__(parent) self.setTitle('CAN bus activity (%s)' % iface_name.split(os.path.sep)[-1]) self._node = node self._hook_handle = self._node.can_driver.add_io_hook(self._frame_hook) self._columns = [ BasicTable.Column('Dir', lambda e: (e[0].upper()), searchable=False), BasicTable.Column('Local Time', TimestampRenderer(), searchable=False), BasicTable.Column( 'CAN ID', lambda e: (('%0*X' % (8 if e[1].extended else 3, e[1].id)).rjust(8), colorize_can_id(e[1]))), BasicTable.Column( 'Data Hex', lambda e: (' '.join(['%02X' % x for x in e[1].data]).ljust(3 * e[ 1].MAX_DATA_LENGTH), colorize_transfer_id(e))), BasicTable.Column( 'Data ASCII', lambda e: (''.join([(chr(x) if 32 <= x <= 126 else '.') for x in e[1].data]), colorize_transfer_id(e))), BasicTable.Column( 'Src', lambda e: render_node_id_with_color(e[1], 'src')), BasicTable.Column( 'Dst', lambda e: render_node_id_with_color(e[1], 'dst')), BasicTable.Column('Data Type', lambda e: render_data_type_with_color(e[1]), resize_mode=QHeaderView.Stretch), ] self._log_widget = RealtimeLogWidget( self, columns=self._columns, font=get_monospace_font(), post_redraw_hook=self._redraw_hook) self._log_widget.on_selection_changed = self._update_measurement_display def flip_row_mark(row, col): if col == 0: item = self._log_widget.table.item(row, col) if item.icon().isNull(): item.setIcon(get_icon('circle')) flash(self, 'Row %d was marked, click again to unmark', row, duration=3) else: item.setIcon(QIcon()) self._log_widget.table.cellPressed.connect(flip_row_mark) self._stat_update_timer = QTimer(self) self._stat_update_timer.setSingleShot(False) self._stat_update_timer.timeout.connect(self._update_stat) self._stat_update_timer.start(500) self._traffic_stat = TrafficStatCounter() self._stat_frames_tx = QLabel('N/A', self) self._stat_frames_rx = QLabel('N/A', self) self._stat_traffic = QLabel('N/A', self) self._load_plot = PlotWidget(background=(0, 0, 0)) self._load_plot.setRange(xRange=(0, self.DEFAULT_PLOT_X_RANGE), padding=0) self._load_plot.setMaximumHeight(150) self._load_plot.setMinimumHeight(100) self._load_plot.setMinimumWidth(100) self._load_plot.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum) self._load_plot.showGrid(x=True, y=True, alpha=0.4) self._load_plot.setToolTip('Frames per second') self._load_plot.getPlotItem().getViewBox().setMouseEnabled(x=True, y=False) self._load_plot.enableAutoRange() self._bus_load_plot = self._load_plot.plot(name='Frames per second', pen=mkPen(QColor( Qt.lightGray), width=1)) self._bus_load_samples = [], [] self._started_at_mono = time.monotonic() layout = QVBoxLayout(self) layout.addWidget(self._log_widget, 1) stat_vars_layout = QGridLayout(self) stat_layout_next_row = 0 def add_stat_row(label, value): nonlocal stat_layout_next_row stat_vars_layout.addWidget(QLabel(label, self), stat_layout_next_row, 0) stat_vars_layout.addWidget(value, stat_layout_next_row, 1) value.setMinimumWidth(75) stat_layout_next_row += 1 add_stat_row('Frames transmitted:', self._stat_frames_tx) add_stat_row('Frames received:', self._stat_frames_rx) add_stat_row('Frames per second:', self._stat_traffic) stat_vars_layout.setRowStretch(stat_layout_next_row, 1) stat_layout = QHBoxLayout(self) stat_layout.addLayout(stat_vars_layout) stat_layout.addWidget(self._load_plot, 1) layout.addLayout(stat_layout, 0) self.setLayout(layout) def close(self): self._hook_handle.remove() def _update_stat(self): bus_load, ts_mono = self._traffic_stat.get_frames_per_second() self._stat_traffic.setText(str(int(bus_load + 0.5))) if len(self._bus_load_samples[0]) >= self.BUS_LOAD_PLOT_MAX_SAMPLES: self._bus_load_samples[0].pop(0) self._bus_load_samples[1].pop(0) self._bus_load_samples[1].append(bus_load) self._bus_load_samples[0].append(ts_mono - self._started_at_mono) self._bus_load_plot.setData(*self._bus_load_samples) (xmin, xmax), _ = self._load_plot.viewRange() diff = xmax - xmin xmax = self._bus_load_samples[0][-1] xmin = self._bus_load_samples[0][-1] - diff self._load_plot.setRange(xRange=(xmin, xmax), padding=0) def _redraw_hook(self): self._stat_frames_tx.setText(str(self._traffic_stat.tx)) self._stat_frames_rx.setText(str(self._traffic_stat.rx)) def _frame_hook(self, direction, frame): self._traffic_stat.add_frame(direction, frame) self._log_widget.add_item_async((direction, frame)) def _update_measurement_display(self, selected_rows_cols): if not selected_rows_cols: return min_row = min([row for row, _ in selected_rows_cols]) max_row = max([row for row, _ in selected_rows_cols]) def get_row_ts(row): return TimestampRenderer.parse_timestamp( self._log_widget.table.item(row, 1).text()) def get_load_str(num_frames, dt): if dt >= 1e-6: return 'average load %.1f FPS' % (num_frames / dt) return 'average load is unknown' if min_row == max_row: num_frames = min_row first_ts = get_row_ts(0) current_ts = get_row_ts(min_row) dt = current_ts - first_ts flash(self, '%d frames from beginning, %.3f sec since first frame, %s', num_frames, dt, get_load_str(num_frames, dt)) else: num_frames = max_row - min_row + 1 first_ts = get_row_ts(min_row) last_ts = get_row_ts(max_row) dt = last_ts - first_ts flash(self, '%d frames, timedelta %.6f sec, %s', num_frames, dt, get_load_str(num_frames, dt))