CAN Bus example¶
The example sends and receives CAN bus frames. The example sends and receives CAN bus frames. Incoming frames are ordered according to their type. A connect dialog is provided to adjust the CAN Bus connection parameters.
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import sys
from PySide6.QtCore import QCoreApplication, QLoggingCategory
from PySide6.QtWidgets import QApplication
from mainwindow import MainWindow
"""PySide6 port of the CAN example from Qt v6.x"""
if __name__ == "__main__":
QLoggingCategory.setFilterRules("qt.canbus* = true")
a = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(QCoreApplication.exec())
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtWidgets import QComboBox
from PySide6.QtGui import QIntValidator
from PySide6.QtCore import Slot
class BitRateBox(QComboBox):
def __init__(self, parent):
super().__init__(parent)
self.m_isFlexibleDataRateEnabled = False
self.m_customSpeedValidator = None
self.m_customSpeedValidator = QIntValidator(0, 1000000, self)
self.fill_bit_rates()
self.currentIndexChanged.connect(self.check_custom_speed_policy)
def bit_rate(self):
index = self.currentIndex()
if index == self.count() - 1:
return int(self.currentText)
return int(self.itemData(index))
def is_flexible_data_rate_enabled(self):
return self.m_isFlexibleDataRateEnabled
def set_flexible_date_rate_enabled(self, enabled):
self.m_isFlexibleDataRateEnabled = enabled
self.m_customSpeedValidator.setTop(10000000 if enabled else 1000000)
self.fill_bit_rates()
@Slot(int)
def check_custom_speed_policy(self, idx):
is_custom_speed = not self.itemData(idx)
self.setEditable(is_custom_speed)
if is_custom_speed:
self.clearEditText()
self.lineEdit().setValidator(self.m_customSpeedValidator)
def fill_bit_rates(self):
rates = [10000, 20000, 50000, 100000, 125000, 250000, 500000,
800000, 1000000]
data_rates = [2000000, 4000000, 8000000]
self.clear()
for rate in rates:
self.addItem(f"{rate}", rate)
if self.is_flexible_data_rate_enabled():
for rate in data_rates:
self.addItem(f"{rate}", rate)
self.addItem("Custom")
self.setCurrentIndex(6) # default is 500000 bits/sec
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QGroupBox
from ui_canbusdeviceinfobox import Ui_CanBusDeviceInfoBox
def _set_readonly_and_compact(box):
box.setAttribute(Qt.WA_TransparentForMouseEvents)
box.setFocusPolicy(Qt.NoFocus)
box.setStyleSheet("margin-top:0; margin-bottom:0;")
class CanBusDeviceInfoBox(QGroupBox):
def __init__(self, parent):
super().__init__(parent)
self.m_ui = Ui_CanBusDeviceInfoBox()
self.m_ui.setupUi(self)
_set_readonly_and_compact(self.m_ui.isVirtual)
_set_readonly_and_compact(self.m_ui.isFlexibleDataRateCapable)
def clear(self):
self.m_ui.pluginLabel.clear()
self.m_ui.nameLabel.clear()
self.m_ui.descriptionLabel.clear()
self.m_ui.serialNumberLabel.clear()
self.m_ui.aliasLabel.clear()
self.m_ui.channelLabel.clear()
self.m_ui.isVirtual.setChecked(False)
self.m_ui.isFlexibleDataRateCapable.setChecked(False)
def set_device_info(self, info):
self.m_ui.pluginLabel.setText(f"Plugin: {info.plugin()}")
self.m_ui.nameLabel.setText(f"Name: {info.name()}")
self.m_ui.descriptionLabel.setText(info.description())
serial_number = info.serialNumber()
if not serial_number:
serial_number = "n/a"
self.m_ui.serialNumberLabel.setText(f"Serial: {serial_number}")
alias = info.alias()
if not alias:
alias = "n/a"
self.m_ui.aliasLabel.setText(f"Alias: {alias}")
self.m_ui.channelLabel.setText(f"Channel: {info.channel()}")
self.m_ui.isVirtual.setChecked(info.isVirtual())
self.m_ui.isFlexibleDataRateCapable.setChecked(info.hasFlexibleDataRate())
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CanBusDeviceInfoBox</class>
<widget class="QGroupBox" name="CanBusDeviceInfoBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>319</width>
<height>257</height>
</rect>
</property>
<property name="windowTitle">
<string>CAN Interface Properties</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="pluginLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="nameLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="descriptionLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="serialNumberLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="aliasLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="channelLabel">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="isFlexibleDataRateCapable">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Flexible Data Rate</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="isVirtual">
<property name="text">
<string>Virtual</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtWidgets import QDialog
from ui_canbusdeviceinfodialog import Ui_CanBusDeviceInfoDialog
class CanBusDeviceInfoDialog(QDialog):
def __init__(self, info, parent):
super().__init__(parent)
self.m_ui = Ui_CanBusDeviceInfoDialog()
self.m_ui.setupUi(self)
self.m_ui.deviceInfoBox.set_device_info(info)
self.m_ui.okButton.pressed.connect(self.close)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CanBusDeviceInfoDialog</class>
<widget class="QDialog" name="CanBusDeviceInfoDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>237</width>
<height>225</height>
</rect>
</property>
<property name="windowTitle">
<string>CAN Interface Properties</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="CanBusDeviceInfoBox" name="deviceInfoBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>CAN Interface Properties</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>Ok</string>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>CanBusDeviceInfoBox</class>
<extends>QGroupBox</extends>
<header location="global">canbusdeviceinfobox.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import QSettings, Qt, Slot
from PySide6.QtGui import QIntValidator
from PySide6.QtWidgets import QDialog
from PySide6.QtSerialBus import QCanBus, QCanBusDevice
from ui_connectdialog import Ui_ConnectDialog
class Settings():
def __init__(self):
self.plugin_name = ""
self.device_interface_name = ""
self.configurations = []
self.use_configuration_enabled = False
self.use_model_ring_buffer = True
self.model_ring_buffer_size = 1000
self.use_autoscroll = False
class ConnectDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.m_ui = Ui_ConnectDialog()
self.m_currentSettings = Settings()
self.m_interfaces = []
self.m_settings = QSettings("QtProject", "CAN example")
self.m_ui.setupUi(self)
self.m_ui.errorFilterEdit.setValidator(QIntValidator(0, 0x1FFFFFFF, self))
self.m_ui.loopbackBox.addItem("unspecified")
self.m_ui.loopbackBox.addItem("False", False)
self.m_ui.loopbackBox.addItem("True", True)
self.m_ui.receiveOwnBox.addItem("unspecified")
self.m_ui.receiveOwnBox.addItem("False", False)
self.m_ui.receiveOwnBox.addItem("True", True)
self.m_ui.canFdBox.addItem("False", False)
self.m_ui.canFdBox.addItem("True", True)
self.m_ui.dataBitrateBox.set_flexible_date_rate_enabled(True)
self.m_ui.okButton.clicked.connect(self.ok)
self.m_ui.cancelButton.clicked.connect(self.cancel)
self.m_ui.useConfigurationBox.toggled.connect(self.m_ui.configurationBox.setEnabled)
self.m_ui.pluginListBox.currentTextChanged.connect(self.plugin_changed)
self.m_ui.interfaceListBox.currentTextChanged.connect(self.interface_changed)
self.m_ui.ringBufferBox.checkStateChanged.connect(self._ring_buffer_changed)
self.m_ui.rawFilterEdit.hide()
self.m_ui.rawFilterLabel.hide()
self.m_ui.pluginListBox.addItems(QCanBus.instance().plugins())
self.restore_settings()
@Slot(int)
def _ring_buffer_changed(self, state):
self.m_ui.ringBufferLimitBox.setEnabled(state == Qt.CheckState.Checked)
def settings(self):
return self.m_currentSettings
def save_settings(self):
qs = self.m_settings
cur = self.m_currentSettings
qs.beginGroup("LastSettings")
qs.setValue("PluginName", self.m_currentSettings.plugin_name)
qs.setValue("DeviceInterfaceName", cur.device_interface_name)
qs.setValue("UseAutoscroll", cur.use_autoscroll)
qs.setValue("UseRingBuffer", cur.use_model_ring_buffer)
qs.setValue("RingBufferSize", cur.model_ring_buffer_size)
qs.setValue("UseCustomConfiguration", cur.use_configuration_enabled)
if cur.use_configuration_enabled:
qs.setValue("Loopback",
self.configuration_value(QCanBusDevice.LoopbackKey))
qs.setValue("ReceiveOwn",
self.configuration_value(QCanBusDevice.ReceiveOwnKey))
qs.setValue("ErrorFilter",
self.configuration_value(QCanBusDevice.ErrorFilterKey))
qs.setValue("BitRate",
self.configuration_value(QCanBusDevice.BitRateKey))
qs.setValue("CanFd",
self.configuration_value(QCanBusDevice.CanFdKey))
qs.setValue("DataBitRate",
self.configuration_value(QCanBusDevice.DataBitRateKey))
qs.endGroup()
def restore_settings(self):
qs = self.m_settings
cur = self.m_currentSettings
qs.beginGroup("LastSettings")
cur.plugin_name = qs.value("PluginName", "", str)
cur.device_interface_name = qs.value("DeviceInterfaceName", "", str)
cur.use_autoscroll = qs.value("UseAutoscroll", False, bool)
cur.use_model_ring_buffer = qs.value("UseRingBuffer", False, bool)
cur.model_ring_buffer_size = qs.value("RingBufferSize", 0, int)
cur.use_configuration_enabled = qs.value("UseCustomConfiguration", False, bool)
self.revert_settings()
if cur.use_configuration_enabled:
self.m_ui.loopbackBox.setCurrentText(qs.value("Loopback"))
self.m_ui.receiveOwnBox.setCurrentText(qs.value("ReceiveOwn"))
self.m_ui.errorFilterEdit.setText(qs.value("ErrorFilter"))
self.m_ui.bitrateBox.setCurrentText(qs.value("BitRate"))
self.m_ui.canFdBox.setCurrentText(qs.value("CanFd"))
self.m_ui.dataBitrateBox.setCurrentText(qs.value("DataBitRate"))
qs.endGroup()
self.update_settings()
@Slot(str)
def plugin_changed(self, plugin):
self.m_ui.interfaceListBox.clear()
interfaces, error_string = QCanBus.instance().availableDevices(plugin)
self.m_interfaces = interfaces
for info in self.m_interfaces:
self.m_ui.interfaceListBox.addItem(info.name())
@Slot(str)
def interface_changed(self, interface):
for info in self.m_interfaces:
if interface == info.name():
self.m_ui.deviceInfoBox.set_device_info(info)
return
self.m_ui.deviceInfoBox.clear()
@Slot()
def ok(self):
self.update_settings()
self.save_settings()
self.accept()
@Slot()
def cancel(self):
self.revert_settings()
self.reject()
def configuration_value(self, key):
result = None
for k, v in self.m_currentSettings.configurations:
if k == key:
result = v
break
if not result and (key == QCanBusDevice.LoopbackKey or key == QCanBusDevice.ReceiveOwnKey):
return "unspecified"
return str(result)
def revert_settings(self):
self.m_ui.pluginListBox.setCurrentText(self.m_currentSettings.plugin_name)
self.m_ui.interfaceListBox.setCurrentText(self.m_currentSettings.device_interface_name)
self.m_ui.useConfigurationBox.setChecked(self.m_currentSettings.use_configuration_enabled)
self.m_ui.ringBufferBox.setChecked(self.m_currentSettings.use_model_ring_buffer)
self.m_ui.ringBufferLimitBox.setValue(self.m_currentSettings.model_ring_buffer_size)
self.m_ui.autoscrollBox.setChecked(self.m_currentSettings.use_autoscroll)
value = self.configuration_value(QCanBusDevice.LoopbackKey)
self.m_ui.loopbackBox.setCurrentText(value)
value = self.configuration_value(QCanBusDevice.ReceiveOwnKey)
self.m_ui.receiveOwnBox.setCurrentText(value)
value = self.configuration_value(QCanBusDevice.ErrorFilterKey)
self.m_ui.errorFilterEdit.setText(value)
value = self.configuration_value(QCanBusDevice.BitRateKey)
self.m_ui.bitrateBox.setCurrentText(value)
value = self.configuration_value(QCanBusDevice.CanFdKey)
self.m_ui.canFdBox.setCurrentText(value)
value = self.configuration_value(QCanBusDevice.DataBitRateKey)
self.m_ui.dataBitrateBox.setCurrentText(value)
def update_settings(self):
self.m_currentSettings.plugin_name = self.m_ui.pluginListBox.currentText()
self.m_currentSettings.device_interface_name = self.m_ui.interfaceListBox.currentText()
self.m_currentSettings.use_configuration_enabled = self.m_ui.useConfigurationBox.isChecked()
self.m_currentSettings.use_model_ring_buffer = self.m_ui.ringBufferBox.isChecked()
self.m_currentSettings.model_ring_buffer_size = self.m_ui.ringBufferLimitBox.value()
self.m_currentSettings.use_autoscroll = self.m_ui.autoscrollBox.isChecked()
if self.m_currentSettings.use_configuration_enabled:
self.m_currentSettings.configurations.clear()
# process LoopBack
if self.m_ui.loopbackBox.currentIndex() != 0:
item = (QCanBusDevice.LoopbackKey, self.m_ui.loopbackBox.currentData())
self.m_currentSettings.configurations.append(item)
# process ReceiveOwnKey
if self.m_ui.receiveOwnBox.currentIndex() != 0:
item = (QCanBusDevice.ReceiveOwnKey, self.m_ui.receiveOwnBox.currentData())
self.m_currentSettings.configurations.append(item)
# process error filter
error_filter = self.m_ui.errorFilterEdit.text()
if error_filter:
ok = False
try:
int(error_filter) # check if value contains a valid integer
ok = True
except ValueError:
pass
if ok:
item = (QCanBusDevice.ErrorFilterKey, error_filter)
self.m_currentSettings.configurations.append(item)
# process raw filter list
if self.m_ui.rawFilterEdit.text():
pass # TODO current ui not sufficient to reflect this param
# process bitrate
bitrate = self.m_ui.bitrateBox.bit_rate()
if bitrate > 0:
item = (QCanBusDevice.BitRateKey, bitrate)
self.m_currentSettings.configurations.append(item)
# process CAN FD setting
fd_item = (QCanBusDevice.CanFdKey, self.m_ui.canFdBox.currentData())
self.m_currentSettings.configurations.append(fd_item)
# process data bitrate
data_bitrate = self.m_ui.dataBitrateBox.bit_rate()
if data_bitrate > 0:
item = (QCanBusDevice.DataBitRateKey, data_bitrate)
self.m_currentSettings.configurations.append(item)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>ConnectDialog</class>
<widget class="QDialog" name="ConnectDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>542</width>
<height>558</height>
</rect>
</property>
<property name="windowTitle">
<string>Connect</string>
</property>
<layout class="QGridLayout" name="gridLayout_6">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout_5">
<item row="0" column="0">
<widget class="QGroupBox" name="selectPluginBox">
<property name="title">
<string>Select CAN plugin</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QComboBox" name="pluginListBox"/>
</item>
</layout>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>GUI Settings</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QCheckBox" name="ringBufferBox">
<property name="toolTip">
<string><html><head/><body><p>Use ring buffer in table view model</p></body></html></string>
</property>
<property name="text">
<string>Use ring buffer</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="ringBufferLimitBox">
<property name="toolTip">
<string><html><head/><body><p>Limit of ring buffer in table view model</p></body></html></string>
</property>
<property name="minimum">
<number>10</number>
</property>
<property name="maximum">
<number>10000000</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="stepType">
<enum>QAbstractSpinBox::StepType::AdaptiveDecimalStepType</enum>
</property>
<property name="value">
<number>1000</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="autoscrollBox">
<property name="toolTip">
<string><html><head/><body><p>Scroll to bottom table view on each portion of received frames</p></body></html></string>
</property>
<property name="text">
<string>Autoscroll</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item row="3" column="0">
<widget class="QCheckBox" name="useConfigurationBox">
<property name="text">
<string>Custom configuration</string>
</property>
</widget>
</item>
<item row="0" column="1" rowspan="4">
<widget class="QGroupBox" name="configurationBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="title">
<string>Specify Configuration</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QLabel" name="rawFilterLabel">
<property name="text">
<string>RAW Filter</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="rawFilterEdit">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="errorFilterLabel">
<property name="text">
<string>Error Filter</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="errorFilterEdit">
<property name="alignment">
<set>Qt::AlignmentFlag::AlignRight|Qt::AlignmentFlag::AlignTrailing|Qt::AlignmentFlag::AlignVCenter</set>
</property>
<property name="placeholderText">
<string>FrameError bits</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="loopbackLabel">
<property name="text">
<string>Loopback</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="loopbackBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="receiveOwnLabel">
<property name="text">
<string>Receive Own</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QComboBox" name="receiveOwnBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="bitrateLabel">
<property name="text">
<string>Bitrate</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="BitRateBox" name="bitrateBox"/>
</item>
<item row="5" column="0">
<widget class="QLabel" name="canFdLabel">
<property name="text">
<string>CAN FD</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="canFdBox"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="dataBitrateLabel">
<property name="text">
<string>Data Bitrate</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="BitRateBox" name="dataBitrateBox"/>
</item>
</layout>
</widget>
</item>
<item row="5" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>96</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="cancelButton">
<property name="text">
<string>Cancel</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="okButton">
<property name="text">
<string>OK</string>
</property>
<property name="autoDefault">
<bool>false</bool>
</property>
<property name="default">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QGroupBox" name="specifyInterfaceNameBox">
<property name="title">
<string>Specify CAN interface name</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QComboBox" name="interfaceListBox">
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="2" column="0">
<widget class="CanBusDeviceInfoBox" name="deviceInfoBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="title">
<string>CAN Interface Properties</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>BitRateBox</class>
<extends>QComboBox</extends>
<header>bitratebox.h</header>
</customwidget>
<customwidget>
<class>CanBusDeviceInfoBox</class>
<extends>QGroupBox</extends>
<header location="global">canbusdeviceinfobox.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import QTimer, QUrl, Slot
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import QLabel, QMainWindow
from PySide6.QtSerialBus import QCanBus, QCanBusDevice, QCanBusFrame
from connectdialog import ConnectDialog
from canbusdeviceinfodialog import CanBusDeviceInfoDialog
from ui_mainwindow import Ui_MainWindow
from receivedframesmodel import ReceivedFramesModel
def frame_flags(frame):
result = " --- "
if frame.hasBitrateSwitch():
result[1] = 'B'
if frame.hasErrorStateIndicator():
result[2] = 'E'
if frame.hasLocalEcho():
result[3] = 'L'
return result
def show_help():
url = "http://doc.qt.io/qt-6/qtcanbus-backends.html#can-bus-plugins"
QDesktopServices.openUrl(QUrl(url))
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.m_ui = Ui_MainWindow()
self.m_number_frames_written = 0
self.m_number_frames_received = 0
self.m_written = None
self.m_received = None
self.m_can_device = None
self.m_busStatusTimer = QTimer(self)
self.m_ui.setupUi(self)
self.m_connect_dialog = ConnectDialog(self)
self.m_status = QLabel()
self.m_ui.statusBar.addPermanentWidget(self.m_status)
self.m_written = QLabel()
self.m_ui.statusBar.addWidget(self.m_written)
self.m_received = QLabel()
self.m_ui.statusBar.addWidget(self.m_received)
self.m_model = ReceivedFramesModel(self)
self.m_model.set_queue_limit(1000)
self.m_ui.receivedFramesView.set_model(self.m_model)
self.init_actions_connections()
QTimer.singleShot(50, self.m_connect_dialog.show)
self.m_busStatusTimer.timeout.connect(self.bus_status)
self.m_appendTimer = QTimer(self)
self.m_appendTimer.timeout.connect(self.onAppendFramesTimeout)
self.m_appendTimer.start(350)
def init_actions_connections(self):
self.m_ui.actionDisconnect.setEnabled(False)
self.m_ui.actionDeviceInformation.setEnabled(False)
self.m_ui.sendFrameBox.setEnabled(False)
self.m_ui.sendFrameBox.send_frame.connect(self.send_frame)
self.m_ui.actionConnect.triggered.connect(self._action_connect)
self.m_connect_dialog.accepted.connect(self.connect_device)
self.m_ui.actionDisconnect.triggered.connect(self.disconnect_device)
self.m_ui.actionResetController.triggered.connect(self._reset_controller)
self.m_ui.actionQuit.triggered.connect(self.close)
self.m_ui.actionAboutQt.triggered.connect(qApp.aboutQt) # noqa: F821
self.m_ui.actionClearLog.triggered.connect(self.m_model.clear)
self.m_ui.actionPluginDocumentation.triggered.connect(show_help)
self.m_ui.actionDeviceInformation.triggered.connect(self._action_device_information)
@Slot()
def _action_connect(self):
if self.m_can_device:
self.m_can_device.deleteLater()
self.m_can_device = None
self.m_connect_dialog.show()
@Slot()
def _reset_controller(self):
self.m_can_device.resetController()
@Slot()
def _action_device_information(self):
info = self.m_can_device.deviceInfo()
dialog = CanBusDeviceInfoDialog(info, self)
dialog.exec()
@Slot(QCanBusDevice.CanBusError)
def process_errors(self, error):
if error != QCanBusDevice.NoError:
self.m_status.setText(self.m_can_device.errorString())
@Slot()
def connect_device(self):
p = self.m_connect_dialog.settings()
if p.use_model_ring_buffer:
self.m_model.set_queue_limit(p.model_ring_buffer_size)
else:
self.m_model.set_queue_limit(0)
device, error_string = QCanBus.instance().createDevice(
p.plugin_name, p.device_interface_name)
if not device:
self.m_status.setText(
f"Error creating device '{p.plugin_name}', reason: '{error_string}'")
return
self.m_number_frames_written = 0
self.m_can_device = device
self.m_can_device.errorOccurred.connect(self.process_errors)
self.m_can_device.framesReceived.connect(self.process_received_frames)
self.m_can_device.framesWritten.connect(self.process_frames_written)
if p.use_configuration_enabled:
for k, v in p.configurations:
self.m_can_device.setConfigurationParameter(k, v)
if not self.m_can_device.connectDevice():
e = self.m_can_device.errorString()
self.m_status.setText(f"Connection error: {e}")
self.m_can_device = None
else:
self.m_ui.actionConnect.setEnabled(False)
self.m_ui.actionDisconnect.setEnabled(True)
self.m_ui.actionDeviceInformation.setEnabled(True)
self.m_ui.sendFrameBox.setEnabled(True)
config_bit_rate = self.m_can_device.configurationParameter(QCanBusDevice.BitRateKey)
if config_bit_rate is not None and config_bit_rate > 0:
is_can_fd = bool(self.m_can_device.configurationParameter(QCanBusDevice.CanFdKey))
config_data_bit_rate = self.m_can_device.configurationParameter(
QCanBusDevice.DataBitRateKey)
bit_rate = config_bit_rate / 1000
if is_can_fd and config_data_bit_rate > 0:
data_bit_rate = config_data_bit_rate / 1000
m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} "
f"at {bit_rate} / {data_bit_rate} kBit/s")
self.m_status.setText(m)
else:
m = (f"Plugin: {p.plugin_name}, connected to {p.device_interface_name} "
f"at {bit_rate} kBit/s")
self.m_status.setText(m)
else:
self.m_status.setText(
f"Plugin: {p.plugin_name}, connected to {p.device_interface_name}")
if self.m_can_device.hasBusStatus():
self.m_busStatusTimer.start(2000)
else:
self.m_ui.busStatus.setText("No CAN bus status available.")
def bus_status(self):
if not self.m_can_device or not self.m_can_device.hasBusStatus():
self.m_ui.busStatus.setText("No CAN bus status available.")
self.m_busStatusTimer.stop()
return
state = self.m_can_device.busStatus()
if state == QCanBusDevice.CanBusStatus.Good:
self.m_ui.busStatus.setText("CAN bus status: Good.")
elif state == QCanBusDevice.CanBusStatus.Warning:
self.m_ui.busStatus.setText("CAN bus status: Warning.")
elif state == QCanBusDevice.CanBusStatus.Error:
self.m_ui.busStatus.setText("CAN bus status: Error.")
elif state == QCanBusDevice.CanBusStatus.BusOff:
self.m_ui.busStatus.setText("CAN bus status: Bus Off.")
else:
self.m_ui.busStatus.setText("CAN bus status: Unknown.")
@Slot()
def disconnect_device(self):
if not self.m_can_device:
return
self.m_busStatusTimer.stop()
self.m_can_device.disconnectDevice()
self.m_ui.actionConnect.setEnabled(True)
self.m_ui.actionDisconnect.setEnabled(False)
self.m_ui.actionDeviceInformation.setEnabled(False)
self.m_ui.sendFrameBox.setEnabled(False)
self.m_status.setText("Disconnected")
@Slot(int)
def process_frames_written(self, count):
self.m_number_frames_written += count
self.m_written.setText(f"{self.m_number_frames_written} frames written")
def closeEvent(self, event):
self.m_connect_dialog.close()
event.accept()
@Slot()
def process_received_frames(self):
if not self.m_can_device:
return
while self.m_can_device.framesAvailable():
self.m_number_frames_received = self.m_number_frames_received + 1
frame = self.m_can_device.readFrame()
data = ""
if frame.frameType() == QCanBusFrame.ErrorFrame:
data = self.m_can_device.interpretErrorFrame(frame)
else:
data = frame.payload().toHex(' ').toUpper()
secs = frame.timeStamp().seconds()
microsecs = frame.timeStamp().microSeconds() / 100
time = f"{secs:>10}.{microsecs:0>4}"
flags = frame_flags(frame)
id = f"{frame.frameId():x}"
dlc = f"{frame.payload().size()}"
frame = [f"{self.m_number_frames_received}", time, flags, id, dlc, data]
self.m_model.append_frame(frame)
@Slot(QCanBusFrame)
def send_frame(self, frame):
if self.m_can_device:
self.m_can_device.writeFrame(frame)
@Slot()
def onAppendFramesTimeout(self):
if not self.m_can_device:
return
if self.m_model.need_update():
self.m_model.update()
if self.m_connect_dialog.settings().use_autoscroll:
self.m_ui.receivedFramesView.scrollToBottom()
self.m_received.setText(f"{self.m_number_frames_received} frames received")
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>634</width>
<height>527</height>
</rect>
</property>
<property name="windowTitle">
<string>CAN Example</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="SendFrameBox" name="sendFrameBox">
<property name="title">
<string>Send CAN frame</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="receivedMessagesBox">
<property name="title">
<string>Received CAN messages</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="ReceivedFramesView" name="receivedFramesView">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="busStatus">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>634</width>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuCalls">
<property name="title">
<string>&Calls</string>
</property>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="actionDeviceInformation"/>
<addaction name="separator"/>
<addaction name="actionResetController"/>
<addaction name="separator"/>
<addaction name="actionClearLog"/>
<addaction name="separator"/>
<addaction name="actionQuit"/>
</widget>
<widget class="QMenu" name="menuHelp">
<property name="title">
<string>&Help</string>
</property>
<addaction name="actionPluginDocumentation"/>
<addaction name="actionAboutQt"/>
</widget>
<addaction name="menuCalls"/>
<addaction name="menuHelp"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
<addaction name="actionConnect"/>
<addaction name="actionDisconnect"/>
<addaction name="separator"/>
<addaction name="actionClearLog"/>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionConnect">
<property name="icon">
<iconset resource="can.qrc">
<normaloff>:/images/connect.png</normaloff>:/images/connect.png</iconset>
</property>
<property name="text">
<string>&Connect</string>
</property>
</action>
<action name="actionDisconnect">
<property name="icon">
<iconset resource="can.qrc">
<normaloff>:/images/disconnect.png</normaloff>:/images/disconnect.png</iconset>
</property>
<property name="text">
<string>&Disconnect</string>
</property>
</action>
<action name="actionQuit">
<property name="icon">
<iconset resource="can.qrc">
<normaloff>:/images/application-exit.png</normaloff>:/images/application-exit.png</iconset>
</property>
<property name="text">
<string>&Quit</string>
</property>
</action>
<action name="actionAboutQt">
<property name="text">
<string>&About Qt</string>
</property>
</action>
<action name="actionClearLog">
<property name="icon">
<iconset resource="can.qrc">
<normaloff>:/images/clear.png</normaloff>:/images/clear.png</iconset>
</property>
<property name="text">
<string>Clear &Log</string>
</property>
</action>
<action name="actionPluginDocumentation">
<property name="text">
<string>Plugin Documentation</string>
</property>
<property name="toolTip">
<string>Open plugin documentation in Webbrowser</string>
</property>
</action>
<action name="actionResetController">
<property name="text">
<string>&Reset CAN Controller</string>
</property>
<property name="toolTip">
<string>Reset CAN Controller</string>
</property>
</action>
<action name="actionDeviceInformation">
<property name="text">
<string>Device &Information...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>SendFrameBox</class>
<extends>QGroupBox</extends>
<header location="global">sendframebox.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>ReceivedFramesView</class>
<extends>QTableView</extends>
<header location="global">receivedframesview.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="can.qrc"/>
</resources>
<connections/>
</ui>
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from enum import IntEnum
from PySide6.QtCore import QAbstractTableModel, QModelIndex, QSize, Qt
class ReceivedFramesModelColumns(IntEnum):
number = 0
timestamp = 1
flags = 2
can_id = 3
DLC = 4
data = 5
count = 6
clipboard_text_role = Qt.ItemDataRole.UserRole + 1
column_alignment = [Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
Qt.AlignmentFlag.AlignCenter,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter,
Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter]
class ReceivedFramesModel(QAbstractTableModel):
def __init__(self, parent=None):
super().__init__(parent)
self.m_framesQueue = [] # QQueue()
self.m_framesAccumulator = []
self.m_queueLimit = 0
def remove_rows(self, row, count, parent):
self.beginRemoveRows(parent, row, row + count - 1)
self.m_framesQueue = self.m_framesQueue[0:row] + self.m_framesQueue[row + count:]
self.endRemoveRows()
return True
def headerData(self, section, orientation, role):
if role == Qt.ItemDataRole.DisplayRole and orientation == Qt.Orientation.Horizontal:
if section == ReceivedFramesModelColumns.number:
return "#"
if section == ReceivedFramesModelColumns.timestamp:
return "Timestamp"
if section == ReceivedFramesModelColumns.flags:
return "Flags"
if section == ReceivedFramesModelColumns.can_id:
return "CAN-ID"
if section == ReceivedFramesModelColumns.DLC:
return "DLC"
if section == ReceivedFramesModelColumns.data:
return "Data"
if role == Qt.ItemDataRole.SizeHintRole and orientation == Qt.Orientation.Horizontal:
if section == ReceivedFramesModelColumns.number:
return QSize(80, 25)
if section == ReceivedFramesModelColumns.timestamp:
return QSize(130, 25)
if section == ReceivedFramesModelColumns.flags:
return QSize(25, 25)
if section == ReceivedFramesModelColumns.can_id:
return QSize(50, 25)
if section == ReceivedFramesModelColumns.DLC:
return QSize(25, 25)
if section == ReceivedFramesModelColumns.data:
return QSize(200, 25)
return None
def data(self, index, role):
if not self.m_framesQueue:
return None
row = index.row()
column = index.column()
if role == Qt.ItemDataRole.TextAlignmentRole:
return column_alignment[index.column()]
if role == Qt.ItemDataRole.AlignmentFlag.DisplayRole:
return self.m_framesQueue[row][column]
if role == clipboard_text_role:
f = self.m_framesQueue[row][column]
return f"[{f}]" if column == ReceivedFramesModelColumns.DLC else f
return None
def rowCount(self, parent=QModelIndex()):
return 0 if parent.isValid() else len(self.m_framesQueue)
def columnCount(self, parent=QModelIndex()):
return 0 if parent.isValid() else ReceivedFramesModelColumns.count
def append_frames(self, slvector):
self.m_framesAccumulator.extend(slvector)
def need_update(self):
return self.m_framesAccumulator
def update(self):
if not self.m_framesAccumulator:
return
if self.m_queueLimit:
self.append_frames_ring_buffer(self.m_framesAccumulator)
else:
self.append_frames_unlimited(self.m_framesAccumulator)
self.m_framesAccumulator.clear()
def append_frames_ring_buffer(self, slvector):
slvector_len = len(slvector)
row_count = self.rowCount()
if self.m_queueLimit <= row_count + slvector_len:
if slvector_len < self.m_queueLimit:
self.remove_rows(0, row_count + slvector_len - self.m_queueLimit + 1)
else:
self.clear()
self.beginInsertRows(QModelIndex(), row_count, row_count + slvector_len - 1)
if slvector_len < self.m_queueLimit:
self.m_framesQueue.extend(slvector)
else:
self.m_framesQueue.extend(slvector[slvector_len - self.m_queueLimit:])
self.endInsertRows()
def append_frame(self, slist):
self.append_frames([slist])
def append_frames_unlimited(self, slvector):
row_count = self.rowCount()
self.beginInsertRows(QModelIndex(), row_count, row_count + len(slvector) - 1)
self.m_framesQueue.extend(slvector)
self.endInsertRows()
def clear(self):
if self.m_framesQueue:
self.beginResetModel()
self.m_framesQueue.clear()
self.endResetModel()
def set_queue_limit(self, limit):
self.m_queueLimit = limit
frame_queue_len = len(self.m_framesQueue)
if limit and frame_queue_len > limit:
self.remove_rows(0, frame_queue_len - limit)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
from PySide6.QtCore import QPoint, Qt, Slot
from PySide6.QtGui import QAction, QKeySequence
from PySide6.QtWidgets import QApplication, QMenu, QTableView
from receivedframesmodel import clipboard_text_role
class ReceivedFramesView(QTableView):
def __init__(self, parent):
super().__init__(parent)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self._context_menu)
@Slot(QPoint)
def _context_menu(self, pos):
context_menu = QMenu("Context menu", self)
if self.selectedIndexes():
copy_action = QAction("Copy", self)
copy_action.triggered.connect(self.copy_row)
context_menu.addAction(copy_action)
select_all_action = QAction("Select all", self)
select_all_action.triggered.connect(self.selectAll)
context_menu.addAction(select_all_action)
context_menu.exec(self.mapToGlobal(pos))
def set_model(self, model):
super().setModel(model)
for i in range(0, model.columnCount()):
size = model.headerData(i, Qt.Orientation.Horizontal, Qt.ItemDataRole.SizeHintRole)
self.setColumnWidth(i, size.width())
def keyPressEvent(self, event):
if event.matches(QKeySequence.Copy):
self.copy_row()
elif event.matches(QKeySequence.SelectAll):
self.selectAll()
else:
super().keyPressEvent(event)
@Slot()
def copy_row(self):
clipboard = QApplication.clipboard()
str_row = ""
last_column = self.model().columnCount() - 1
for index in self.selectedIndexes():
str_row += index.data(clipboard_text_role) + " "
if index.column() == last_column:
str_row += "\n"
clipboard.setText(str_row)
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
from __future__ import annotations
import re
from PySide6.QtGui import QValidator
from PySide6.QtCore import QByteArray, Signal, Slot
from PySide6.QtWidgets import QGroupBox
from PySide6.QtSerialBus import QCanBusFrame
from ui_sendframebox import Ui_SendFrameBox
THREE_HEX_DIGITS_PATTERN = re.compile("[0-9a-fA-F]{3}")
HEX_NUMBER_PATTERN = re.compile("^[0-9a-fA-F]+$")
MAX_STANDARD_ID = 0x7FF
MAX_EXTENDED_ID = 0x10000000
MAX_PAYLOAD = 8
MAX_PAYLOAD_FD = 64
def is_even_hex(input):
return len(input.replace(" ", "")) % 2 == 0
def insert_space(string, pos):
return string[0:pos] + " " + string[pos:]
# Formats a string of hex characters with a space between every byte
# Example: "012345" -> "01 23 45"
def format_hex_data(input):
out = input.strip()
while True:
match = THREE_HEX_DIGITS_PATTERN.search(out)
if match:
out = insert_space(out, match.end(0) - 1)
else:
break
return out.strip().upper()
class HexIntegerValidator(QValidator):
def __init__(self, parent):
super().__init__(parent)
self.m_maximum = MAX_STANDARD_ID
def validate(self, input, pos):
result = QValidator.Intermediate
if input:
result = QValidator.Invalid
try:
value = int(input, base=16)
if value < self.m_maximum:
result = QValidator.Acceptable
except ValueError:
pass
return result
def set_maximum(self, maximum):
self.m_maximum = maximum
class HexStringValidator(QValidator):
def __init__(self, parent):
super().__init__(parent)
self.m_maxLength = MAX_PAYLOAD
def validate(self, input, pos):
max_size = 2 * self.m_maxLength
data = input.replace(" ", "")
if not data:
return QValidator.Intermediate
# limit maximum size
if len(data) > max_size:
return QValidator.Invalid
# check if all input is valid
if not HEX_NUMBER_PATTERN.match(data):
return QValidator.Invalid
# insert a space after every two hex nibbles
while True:
match = THREE_HEX_DIGITS_PATTERN.search(input)
if not match:
break
start = match.start(0)
end = match.end()
if pos == start + 1:
# add one hex nibble before two - Abc
input = insert_space(input, pos)
elif pos == start + 2:
# add hex nibble in the middle - aBc
input = insert_space(input, end - 1)
pos = end
else:
# add one hex nibble after two - abC
input = insert_space(input, end - 1)
pos = end + 1
return (QValidator.Acceptable, input, pos)
def set_max_length(self, maxLength):
self.m_maxLength = maxLength
class SendFrameBox(QGroupBox):
send_frame = Signal(QCanBusFrame)
def __init__(self, parent):
super().__init__(parent)
self.m_ui = Ui_SendFrameBox()
self.m_ui.setupUi(self)
self.m_hexIntegerValidator = HexIntegerValidator(self)
self.m_ui.frameIdEdit.setValidator(self.m_hexIntegerValidator)
self.m_hexStringValidator = HexStringValidator(self)
self.m_ui.payloadEdit.setValidator(self.m_hexStringValidator)
self.m_ui.dataFrame.toggled.connect(self._data_frame)
self.m_ui.remoteFrame.toggled.connect(self._remote_frame)
self.m_ui.errorFrame.toggled.connect(self._error_frame)
self.m_ui.extendedFormatBox.toggled.connect(self._extended_format)
self.m_ui.flexibleDataRateBox.toggled.connect(self._flexible_datarate)
self.m_ui.frameIdEdit.textChanged.connect(self._frameid_or_payload_changed)
self.m_ui.payloadEdit.textChanged.connect(self._frameid_or_payload_changed)
self._frameid_or_payload_changed()
self.m_ui.sendButton.clicked.connect(self._send)
@Slot(bool)
def _data_frame(self, value):
if value:
self.m_ui.flexibleDataRateBox.setEnabled(True)
@Slot(bool)
def _remote_frame(self, value):
if value:
self.m_ui.flexibleDataRateBox.setEnabled(False)
self.m_ui.flexibleDataRateBox.setChecked(False)
@Slot(bool)
def _error_frame(self, value):
if value:
self.m_ui.flexibleDataRateBox.setEnabled(False)
self.m_ui.flexibleDataRateBox.setChecked(False)
@Slot(bool)
def _extended_format(self, value):
m = MAX_EXTENDED_ID if value else MAX_STANDARD_ID
self.m_hexIntegerValidator.set_maximum(m)
@Slot(bool)
def _flexible_datarate(self, value):
len = MAX_PAYLOAD_FD if value else MAX_PAYLOAD
self.m_hexStringValidator.set_max_length(len)
self.m_ui.bitrateSwitchBox.setEnabled(value)
if not value:
self.m_ui.bitrateSwitchBox.setChecked(False)
@Slot()
def _frameid_or_payload_changed(self):
has_frame_id = bool(self.m_ui.frameIdEdit.text())
self.m_ui.sendButton.setEnabled(has_frame_id)
tt = "" if has_frame_id else "Cannot send because no Frame ID was given."
self.m_ui.sendButton.setToolTip(tt)
if has_frame_id:
is_even = is_even_hex(self.m_ui.payloadEdit.text())
self.m_ui.sendButton.setEnabled(is_even)
tt = "" if is_even else "Cannot send because Payload hex string is invalid."
self.m_ui.sendButton.setToolTip(tt)
@Slot()
def _send(self):
frame_id = int(self.m_ui.frameIdEdit.text(), base=16)
data = self.m_ui.payloadEdit.text().replace(" ", "")
self.m_ui.payloadEdit.setText(format_hex_data(data))
payload = QByteArray.fromHex(bytes(data, encoding='utf8'))
frame = QCanBusFrame(frame_id, payload)
frame.setExtendedFrameFormat(self.m_ui.extendedFormatBox.isChecked())
frame.setFlexibleDataRateFormat(self.m_ui.flexibleDataRateBox.isChecked())
frame.setBitrateSwitch(self.m_ui.bitrateSwitchBox.isChecked())
if self.m_ui.errorFrame.isChecked():
frame.setFrameType(QCanBusFrame.ErrorFrame)
elif self.m_ui.remoteFrame.isChecked():
frame.setFrameType(QCanBusFrame.RemoteRequestFrame)
self.send_frame.emit(frame)
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SendFrameBox</class>
<widget class="QGroupBox" name="SendFrameBox">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>505</width>
<height>219</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<property name="title">
<string/>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
</property>
<item>
<widget class="QGroupBox" name="frameTypeBox">
<property name="title">
<string>Frame Type</string>
</property>
<property name="checkable">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QRadioButton" name="dataFrame">
<property name="toolTip">
<string>Sends a CAN data frame.</string>
</property>
<property name="text">
<string>D&ata Frame</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="remoteFrame">
<property name="toolTip">
<string>Sends a CAN remote request frame.</string>
</property>
<property name="text">
<string>Re&mote Request Frame</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="errorFrame">
<property name="toolTip">
<string>Sends an error frame.</string>
</property>
<property name="text">
<string>&Error Frame</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="frameOptionsBox">
<property name="title">
<string>Frame Options</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="topMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QCheckBox" name="extendedFormatBox">
<property name="toolTip">
<string>Allows extended frames with 29 bit identifier.</string>
</property>
<property name="text">
<string>E&xtended Format</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="flexibleDataRateBox">
<property name="toolTip">
<string>Allows up to 64 byte payload data.</string>
</property>
<property name="text">
<string>&Flexible Data-Rate</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="bitrateSwitchBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>Sends payload at higher data rate.</string>
</property>
<property name="text">
<string>&Bitrate Switch</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetMinimumSize</enum>
</property>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="frameIdLabel">
<property name="text">
<string>Frame &ID (hex)</string>
</property>
<property name="buddy">
<cstring>frameIdEdit</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="frameIdEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>123</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QLabel" name="payloadLabel">
<property name="text">
<string>&Payload (hex)</string>
</property>
<property name="buddy">
<cstring>payloadEdit</cstring>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="payloadEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>2</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="placeholderText">
<string>12 34 AB CE</string>
</property>
<property name="clearButtonEnabled">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="sendButton">
<property name="text">
<string>&Send</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
<RCC>
<qresource prefix="/">
<file>images/connect.png</file>
<file>images/disconnect.png</file>
<file>images/application-exit.png</file>
<file>images/clear.png</file>
</qresource>
</RCC>