Преобразование последовательного интерфейса приложения Python-CAN QT в потоковый или AsyncIO
Мое приложение PyQT не успевает за обновлениями сообщений по 4 каналам CAN с устройства Kvaser.
В настоящее время я
- загрузить окно PyQT
- инициализировать 4 шины CAN (на одном устройстве USB)
- Запустите таймер 20 мс в приложении PyQT.
Таймер запускает функцию обновления, которая обрабатывает до 50 сообщений банка за цикл (это корень моей проблемы):
- Обработка сообщений BUS 0 и сохранение соответствующей информации в переменных
- Обработка сообщений BUS 1 и сохранение соответствующей информации в переменных
- Обработка сообщений BUS 2 и сохранение соответствующей информации в переменных
- Обработка сообщений BUS 3 и сохранение соответствующей информации в переменных
- Обновите приложение PyQT последней информацией.
Если я попытаюсь обработать больше сообщений, приложение перестанет отвечать.
Текущая установка полностью последовательна. Я ищу здесь направление. Должен ли я запускать отдельные потоки для каждого канала CAN и сохранять элементы в очереди? Стоит ли запускать 4 уведомителя и обрабатывать сообщения с обратными вызовами? Я не уверен, как интегрировать цикл таймера PyQT с кодом Python-CAN.
Спасибо за любой отзыв!
Минимальный код для графического интерфейса: gui.py
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'IMU_Monitor.ui'
#
# Created by: PyQt5 UI code generator 5.9.2
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(478, 321)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.txtbox_X = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_X.setGeometry(QtCore.QRect(150, 80, 61, 22))
self.txtbox_X.setObjectName("txtbox_X")
self.lbl_Accel = QtWidgets.QLabel(self.centralwidget)
self.lbl_Accel.setGeometry(QtCore.QRect(120, 50, 91, 20))
self.lbl_Accel.setObjectName("lbl_Accel")
self.lbl_X = QtWidgets.QLabel(self.centralwidget)
self.lbl_X.setGeometry(QtCore.QRect(50, 80, 91, 20))
self.lbl_X.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_X.setObjectName("lbl_X")
self.lbl_Y = QtWidgets.QLabel(self.centralwidget)
self.lbl_Y.setGeometry(QtCore.QRect(50, 110, 91, 20))
self.lbl_Y.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Y.setObjectName("lbl_Y")
self.lbl_Z = QtWidgets.QLabel(self.centralwidget)
self.lbl_Z.setGeometry(QtCore.QRect(50, 140, 91, 20))
self.lbl_Z.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Z.setObjectName("lbl_Z")
self.lbl_Gyro = QtWidgets.QLabel(self.centralwidget)
self.lbl_Gyro.setGeometry(QtCore.QRect(260, 50, 91, 20))
self.lbl_Gyro.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Gyro.setObjectName("lbl_Gyro")
self.lbl_Roll = QtWidgets.QLabel(self.centralwidget)
self.lbl_Roll.setGeometry(QtCore.QRect(230, 80, 51, 20))
self.lbl_Roll.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Roll.setObjectName("lbl_Roll")
self.lbl_Pitch = QtWidgets.QLabel(self.centralwidget)
self.lbl_Pitch.setGeometry(QtCore.QRect(230, 110, 51, 20))
self.lbl_Pitch.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Pitch.setObjectName("lbl_Pitch")
self.lbl_Yaw = QtWidgets.QLabel(self.centralwidget)
self.lbl_Yaw.setGeometry(QtCore.QRect(230, 140, 51, 20))
self.lbl_Yaw.setAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter)
self.lbl_Yaw.setObjectName("lbl_Yaw")
self.txtbox_Y = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_Y.setGeometry(QtCore.QRect(150, 110, 61, 22))
self.txtbox_Y.setObjectName("txtbox_Y")
self.txtbox_Z = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_Z.setGeometry(QtCore.QRect(150, 140, 61, 22))
self.txtbox_Z.setObjectName("txtbox_Z")
self.txtbox_Pitch = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_Pitch.setGeometry(QtCore.QRect(290, 110, 61, 22))
self.txtbox_Pitch.setObjectName("txtbox_Pitch")
self.txtbox_Roll = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_Roll.setGeometry(QtCore.QRect(290, 80, 61, 22))
self.txtbox_Roll.setObjectName("txtbox_Roll")
self.txtbox_Yaw = QtWidgets.QLineEdit(self.centralwidget)
self.txtbox_Yaw.setGeometry(QtCore.QRect(290, 140, 61, 22))
self.txtbox_Yaw.setObjectName("txtbox_Yaw")
self.btn_Send_Zero = QtWidgets.QPushButton(self.centralwidget)
self.btn_Send_Zero.setGeometry(QtCore.QRect(190, 200, 93, 28))
self.btn_Send_Zero.setObjectName("btn_Send_Zero")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 478, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.lbl_Accel.setText(_translate("MainWindow", "Accelerometers"))
self.lbl_X.setText(_translate("MainWindow", "X:"))
self.lbl_Y.setText(_translate("MainWindow", "Y:"))
self.lbl_Z.setText(_translate("MainWindow", "Z:"))
self.lbl_Gyro.setText(_translate("MainWindow", "Gyroscopes"))
self.lbl_Roll.setText(_translate("MainWindow", "Roll:"))
self.lbl_Pitch.setText(_translate("MainWindow", "Pitch:"))
self.lbl_Yaw.setText(_translate("MainWindow", "Yaw:"))
self.btn_Send_Zero.setText(_translate("MainWindow", "Send Zeros"))
Минимальный код для обработки данных: Serial_IMU_Monitor.py
from PyQt5 import QtWidgets, QtGui, QtCore
from gui import Ui_MainWindow # importing our generated file
import sys
# Items for CAN messaging
import can
import cantools # Required to process dbc file
#pyuic5 IMU_Monitor.ui -o gui.py
class mywindow(QtWidgets.QMainWindow):
def __init__(self):
super(mywindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
try:
self.ui.btn_Send_Zero.clicked.connect(self.sendFakeIMU)
except Exception as err:
print(str(err))
# Initialize the bus.
self.initialize_can()
# Create a timer to process CAN messages
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update)
self.timer.start(20) # 20 ms
def initialize_can(self):
self.can_sensor = CanDevices("IMU_Messages.dbc", channel=0)
# GUI Update loop
def update(self):
# Process sensor messages
try:
process_sensor_messages(self.can_sensor.bus, self.can_sensor.db)
except Exception as err:
print("We encountered a problem.")
print(str(err))
# Update GUI screen
self.update_gui_imu()
def update_gui_imu(self):
global IMU_Raw
x = "{0:.2f}".format(IMU_Raw["X"])
y = "{0:.2f}".format(IMU_Raw["Y"])
z = "{0:.2f}".format(IMU_Raw["Z"])
roll = "{0:.2f}".format(IMU_Raw["Roll"])
pitch = "{0:.2f}".format(IMU_Raw["Pitch"])
yaw = "{0:.2f}".format(IMU_Raw["Yaw"])
self.ui.txtbox_X.setText(x)
self.ui.txtbox_Y.setText(y)
self.ui.txtbox_Z.setText(z)
self.ui.txtbox_Roll.setText(roll)
self.ui.txtbox_Pitch.setText(pitch)
self.ui.txtbox_Yaw.setText(yaw)
def sendFakeIMU(self):
'''
Reads input text boxes to send a singular IMU message
:return:
'''
x = -1
y = -1
z = -1
roll = 1
pitch = 1
yaw = 1
try:
sendIMUSignal(self.can_sensor.bus,
self.can_sensor.db,
float(x), float(y), float(z),
float(roll), float(pitch), float(yaw))
# pass
except Exception as err:
print("Failed to send message.")
print(str(err))
class CanDevices:
def __init__(self, dbc_file, channel):
self.db = cantools.db.load_file(dbc_file)
self.interface = "kvaser"
self.channel = channel
self.bit_rate = 250000
#self.bus = can.interface.Bus(bustype=self.interface, channel=self.channel, bitrate=self.bit_rate)
self.bus = can.ThreadSafeBus(bustype=self.interface, channel=self.channel, bitrate=self.bit_rate)
def process_sensor_messages(bus, db):
'''
processor_sensor_messages identifies the message broadcast on the bus and updates a global variable with the latest
received message
:param bus: Takes a can.bus instance
:param db: Takes a can.db instance
:return:
'''
counter = int(0)
# print("Processing sensor messages.")
# This logic only works if messages are being received. Otherwise bus sits until a message is received. This is
# not thread safe.
# for i, msg in enumerate(bus):
while counter < 50:
msg = bus.recv(0.001)
counter = counter + 1
if counter > 50:
break # Don't delay for too long. Keep the UI going.
if msg is None:
break # No messages detected.
# IMU Sensor (Accelerations)
if msg.arbitration_id == 419364912:
decoded_msg = db.decode_message(msg.arbitration_id, msg.data)
update_imu_accel_raw(decoded_msg)
def sendIMUSignal(can_bus, db, x, y, z, roll, pitch, yaw):
IMU_Accel_Message = db.get_message_by_name("IMU_Accelerations")
X_Signal = IMU_Accel_Message.get_signal_by_name("Longitudinal_Acceleration")
Y_Signal = IMU_Accel_Message.get_signal_by_name("Lateral_Acceleration")
Z_Signal = IMU_Accel_Message.get_signal_by_name("Vertical_Acceleration")
accel_data = IMU_Accel_Message.encode(
{"Longitudinal_Acceleration": float(x), "Lateral_Acceleration": float(y), "Vertical_Acceleration": float(z)})
accel_message = can.Message(arbitration_id=IMU_Accel_Message.frame_id, data=accel_data)
IMU_Gyro_Message = db.get_message_by_name("IMU_ARI")
Roll_Signal = IMU_Gyro_Message.get_signal_by_name("Roll_Rate")
Pitch_Signal = IMU_Gyro_Message.get_signal_by_name("Pitch_Rate")
Yaw_Signal = IMU_Gyro_Message.get_signal_by_name("Yaw_Rate")
gyro_data = IMU_Gyro_Message.encode(
{"Roll_Rate": float(roll),
"Pitch_Rate": float(pitch),
"Yaw_Rate": float(yaw),
"AngRateMsrmntLatency": float(0)})
gyro_message = can.Message(arbitration_id=IMU_Gyro_Message.frame_id, data=gyro_data)
try:
can_bus.send(accel_message, timeout=0.01)
can_bus.send(gyro_message, timeout=0.01)
except Exception as err:
print(str(err))
print("Send fake IMU message timed out.")
def update_imu_accel_raw(msg):
global IMU_Raw
IMU_Raw["X"] = msg["Lateral_Acceleration"]
IMU_Raw["Y"] = msg["Longitudinal_Acceleration"]
IMU_Raw["Z"] = msg["Vertical_Acceleration"]
def update_imu_gyro_raw(msg):
global IMU_Raw
IMU_Raw["Roll"] = msg["Roll_Rate"]
IMU_Raw["Pitch"] = msg["Pitch_Rate"]
IMU_Raw["Yaw"] = msg["Yaw_Rate"]
def IMU():
dict = {
"X": 0,
"Y": 0,
"Z": 0,
"Roll": 0,
"Pitch": 0,
"Yaw": 0
}
return dict
if __name__ == "__main__":
# Create dictionaries to track variable data
global IMU_Raw
IMU_Raw = IMU()
app = QtWidgets.QApplication([])
application = mywindow()
application.show()
sys.exit(app.exec())
минимальный файл dbc: IMU_Messages.dbc
VERSION ""
NS_ :
NS_DESC_
CM_
BA_DEF_
BA_
VAL_
CAT_DEF_
CAT_
FILTER
BA_DEF_DEF_
EV_DATA_
ENVVAR_DATA_
SGTYPE_
SGTYPE_VAL_
BA_DEF_SGTYPE_
BA_SGTYPE_
SIG_TYPE_REF_
VAL_TABLE_
SIG_GROUP_
SIG_VALTYPE_
SIGTYPE_VALTYPE_
BO_TX_BU_
BA_DEF_REL_
BA_REL_
BA_DEF_DEF_REL_
BU_SG_REL_
BU_EV_REL_
BU_BO_REL_
SG_MUL_VAL_
BS_:
BU_: IMU_0x30 Datalogger
BO_ 2364549680 IMU_ARI: 8 IMU_0x30
SG_ AngRateMsrmntLatency : 56|8@1+ (0.5,0) [0|125] "ms" Datalogger
SG_ Yaw_Rate : 32|16@1+ (0.0078125,-250) [-250|250.992] "degps" Datalogger
SG_ Roll_Rate : 16|16@1+ (0.0078125,-250) [-250|250.992] "degps" Datalogger
SG_ Pitch_Rate : 0|16@1+ (0.0078125,-250) [-250|250.992] "degps" Datalogger
BO_ 2566848560 IMU_Accelerations: 8 IMU_0x30
SG_ Vertical_Acceleration : 32|16@1+ (0.000599,-19.62) [-19.62|19.63] "mps2" Datalogger
SG_ Longitudinal_Acceleration : 16|16@1+ (0.000599,-19.62) [-19.62|19.63] "mps2" Datalogger
SG_ Lateral_Acceleration : 0|16@1+ (0.000599,-19.62) [-19.62|19.63] "mps2" Datalogger
BA_DEF_ BO_ "FoundInModel" INT 0 0;
BA_DEF_ BU_ "NmStationAddress" INT 0 255;
BA_DEF_ "BusType" STRING ;
BA_DEF_ BO_ "GenMsgCycleTime" INT 0 65535;
BA_DEF_ SG_ "GenSigStartValue" FLOAT -3.4e+38 3.4e+38;
BA_DEF_ SG_ "SigType" ENUM "Default","Range","RangeSigned","ASCII","Discrete","Control","ReferencePGN","DTC","StringDelimiter","StringLength","StringLengthControl","MessageCounter","MessageChecksum";
BA_DEF_ SG_ "GenSigEVName" STRING ;
BA_DEF_ SG_ "GenSigILSupport" ENUM "No","Yes";
BA_DEF_ SG_ "GenSigSendType" ENUM "Cyclic","OnWrite","OnWriteWithRepetition","OnChange","OnChangeWithRepetition","IfActive","IfActiveWithRepetition","NoSigSendType";
BA_DEF_ BO_ "GenMsgFastOnStart" INT 0 100000;
BA_DEF_ SG_ "GenSigInactiveValue" INT 0 0;
BA_DEF_ BO_ "GenMsgCycleTimeFast" INT 0 3600000;
BA_DEF_ BO_ "GenMsgNrOfRepetition" INT 0 1000000;
BA_DEF_ BO_ "GenMsgDelayTime" INT 0 1000;
BA_DEF_ BO_ "GenMsgILSupport" ENUM "No","Yes";
BA_DEF_ BO_ "GenMsgStartDelayTime" INT 0 100000;
BA_DEF_ BU_ "NodeLayerModules" STRING ;
BA_DEF_ BU_ "ECU" STRING ;
BA_DEF_ BU_ "NmJ1939SystemInstance" INT 0 15;
BA_DEF_ BU_ "NmJ1939System" INT 0 127;
BA_DEF_ BU_ "NmJ1939ManufacturerCode" INT 0 2047;
BA_DEF_ BU_ "NmJ1939IndustryGroup" INT 0 7;
BA_DEF_ BU_ "NmJ1939IdentityNumber" INT 0 2097151;
BA_DEF_ BU_ "NmJ1939FunctionInstance" INT 0 7;
BA_DEF_ BU_ "NmJ1939Function" INT 0 255;
BA_DEF_ BU_ "NmJ1939ECUInstance" INT 0 3;
BA_DEF_ BU_ "NmJ1939AAC" INT 0 1;
BA_DEF_ BO_ "GenMsgSendType" ENUM "cyclic","NotUsed","IfActive","NotUsed","NotUsed","NotUsed","NotUsed","NotUsed","noMsgSendType";
BA_DEF_ BO_ "GenMsgRequestable" INT 0 1;
BA_DEF_ SG_ "SPN" INT 0 524287;
BA_DEF_ "DBName" STRING ;
BA_DEF_ "ProtocolType" STRING ;
BA_DEF_ BO_ "VFrameFormat" ENUM "StandardCAN","ExtendedCAN","reserved","J1939PG";
BA_DEF_DEF_ "FoundInModel" 0;
BA_DEF_DEF_ "NmStationAddress" 0;
BA_DEF_DEF_ "BusType" "CAN";
BA_DEF_DEF_ "GenMsgCycleTime" 0;
BA_DEF_DEF_ "GenSigStartValue" 0;
BA_DEF_DEF_ "SigType" "Default";
BA_DEF_DEF_ "GenSigEVName" "Env@Nodename_@Signame";
BA_DEF_DEF_ "GenSigILSupport" "Yes";
BA_DEF_DEF_ "GenSigSendType" "NoSigSendType";
BA_DEF_DEF_ "GenMsgFastOnStart" 0;
BA_DEF_DEF_ "GenSigInactiveValue" 0;
BA_DEF_DEF_ "GenMsgCycleTimeFast" 0;
BA_DEF_DEF_ "GenMsgNrOfRepetition" 0;
BA_DEF_DEF_ "GenMsgDelayTime" 0;
BA_DEF_DEF_ "GenMsgILSupport" "Yes";
BA_DEF_DEF_ "GenMsgStartDelayTime" 0;
BA_DEF_DEF_ "NodeLayerModules" "";
BA_DEF_DEF_ "ECU" "";
BA_DEF_DEF_ "NmJ1939SystemInstance" 0;
BA_DEF_DEF_ "NmJ1939System" 0;
BA_DEF_DEF_ "NmJ1939ManufacturerCode" 0;
BA_DEF_DEF_ "NmJ1939IndustryGroup" 0;
BA_DEF_DEF_ "NmJ1939IdentityNumber" 0;
BA_DEF_DEF_ "NmJ1939FunctionInstance" 0;
BA_DEF_DEF_ "NmJ1939Function" 0;
BA_DEF_DEF_ "NmJ1939ECUInstance" 0;
BA_DEF_DEF_ "NmJ1939AAC" 0;
BA_DEF_DEF_ "GenMsgSendType" "noMsgSendType";
BA_DEF_DEF_ "GenMsgRequestable" 1;
BA_DEF_DEF_ "SPN" 0;
BA_DEF_DEF_ "DBName" "";
BA_DEF_DEF_ "ProtocolType" "J1939";
BA_DEF_DEF_ "VFrameFormat" "J1939PG";
BA_ "BusType" "CAN";
BA_ "DBName" "Sensor_Bus";
BA_ "GenMsgCycleTime" BO_ 2364549680 10;
BA_ "GenMsgSendType" BO_ 2364549680 0;
BA_ "FoundInModel" BO_ 2364549680 1;
BA_ "VFrameFormat" BO_ 2364549680 3;
BA_ "GenMsgCycleTime" BO_ 2566848560 10;
BA_ "GenMsgSendType" BO_ 2566848560 0;
BA_ "FoundInModel" BO_ 2566848560 1;
BA_ "VFrameFormat" BO_ 2566848560 3;
BA_ "NmStationAddress" BU_ IMU_0x30 48;
BA_ "NmStationAddress" BU_ Datalogger 251;