Преобразование последовательного интерфейса приложения Python-CAN QT в потоковый или AsyncIO

Мое приложение PyQT не успевает за обновлениями сообщений по 4 каналам CAN с устройства Kvaser.

В настоящее время я

  1. загрузить окно PyQT
  2. инициализировать 4 шины CAN (на одном устройстве USB)
  3. Запустите таймер 20 мс в приложении PyQT.

Таймер запускает функцию обновления, которая обрабатывает до 50 сообщений банка за цикл (это корень моей проблемы):

  1. Обработка сообщений BUS 0 и сохранение соответствующей информации в переменных
  2. Обработка сообщений BUS 1 и сохранение соответствующей информации в переменных
  3. Обработка сообщений BUS 2 и сохранение соответствующей информации в переменных
  4. Обработка сообщений BUS 3 и сохранение соответствующей информации в переменных
  5. Обновите приложение 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;

0 ответов

Другие вопросы по тегам