0

I am working on a program that takes in high-speed CanBus Data and displays it to a speedometer-style display using PYQT (A digital Dash Board Display). I have run into the common problem of the speedometer display having some latency and I have not figured out a way to solve it.

The code below is split into two parts, the init is just setting up the display with the AnalogGaugeWidget being the Speedometer widget. The sorting function is a while loop that sends out a request for the CanBus data and decodes it based on its ID. I am only trying to get the RPM code to work for now, and then work my way to the others.

The code works fast whenever the "self.widget.updateVlue(self.RPM)" code is taken out, but when it is put in, there is roughtly a 1-second latency in the while loop.

If anyone knows how I can help reduce this latency either through better threading or a different code structure I'm all ears. Also if you need the "Speedometer" code let me know and I would be more than happy to provide it.

Thanks.

The important part of the code:

from PyQt5 import QtCore, QtGui, QtWidgets
from threading import Thread
from Speedometer import *

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.resize(1000, 600)
        self.widget = AnalogGaugeWidget(self)
        self.setCentralWidget(self.widget)

        t1 = Thread(target=self.sorting)
        t1.start()

    def sorting(self):
        while 1:
            for i in range(1, 1000):

                # Example of the data received for RPM
                data = "Timestamp: 1647910233.332248        ID: 0140    S Rx                DL:  8    f9 93 02 00 00 ff 02 02    Channel: can0"

                Timestamp = float(data.split(" ")[1])
                ID = data.split(" ")[10]
                B0 = data.split(" ")[36]
                B1 = data.split(" ")[37]
                B2 = data.split(" ")[38]
                B3 = data.split(" ")[39]
                B4 = data.split(" ")[40]
                B5 = data.split(" ")[41]
                B6 = data.split(" ")[42]
                B7 = data.split(" ")[43]

                if ID == '0140':
                    rpm2 = int((bin(int(B3, base=16))[2:][-5:]), 2)
                    rpm1 = (int(B2, 16))
                    self.RPM = (rpm2 * 256 + rpm1)
                    self.widget.updateValue(self.RPM)

This is the full code that only works when plugged into a CanBus module (To give an idea of the size and complexity of the while loop).

global bitrate
bitrate = 500000

global can_interface
can_interface = "can0"

class MainWindow(QtWidgets.QMainWindow):

    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)
        self.resize(1000, 600)
        self.widget = AnalogGaugeWidget(self)
        self.setCentralWidget(self.widget)

        t1 = Thread(target=self.sorting)
        t1.start()

    def sorting(self):
        SaveData = 0

        bus = can.interface.Bus(bustype='socketcan', channel=can_interface, bitrate=bitrate)

        while 1:
            for i in range(1, 1000):

                msg = can.Message(arbitration_id=0x7DF, data=[2, 1, 0, 0, 0, 0, 0, 0], is_extended_id=False)
                bus.send(msg)
                data = str(bus.recv(timeout=2))

                # Example of the data received for RPM
                #data = "Timestamp: 1647910233.332248        ID: 0140    S Rx                DL:  8    f9 93 02 00 00 ff 02 02    Channel: can0"

                Timestamp = float(data.split(" ")[1])
                ID = data.split(" ")[10]
                B0 = data.split(" ")[36]
                B1 = data.split(" ")[37]
                B2 = data.split(" ")[38]
                B3 = data.split(" ")[39]
                B4 = data.split(" ")[40]
                B5 = data.split(" ")[41]
                B6 = data.split(" ")[42]
                B7 = data.split(" ")[43]

                # if SaveData == 1:
                # with open('Can-Bus Data', 'a') as f:
                # f.write('{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6, B7))

                if ID == '0140':
                    rpm2 = int((bin(int(B3, base=16))[2:][-5:]), 2)
                    rpm1 = (int(B2, 16))
                    self.RPM = (rpm2 * 256 + rpm1)
                    self.widget.updateValue(self.RPM)

                    CP = int(B1, 16)
                    if CP >= 128 and CP <= 143:
                        CP = 1
                        # ("Clutch is engaged")
                    else:
                        CP = 0
                        # ("Clutch is disengaged")

                    # TP = round(((int(B0, 16) / 256) * 100), 1)
                    # TPD = round(((int(B4, 16) / 256) * 100), 1)

                    if SaveData == 1:
                        with open('ID 0140', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '0141':
                    EL = int(B3 + B2, 16)

                    if SaveData == 1:
                        with open('ID 0141', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '0360':
                    TO = int(B2, 16) - 40
                    TC = int(B3, 16) - 40

                    CC = int(B5, 16)
                    if CC == 16:
                        CC = 1
                        # ("Cruise Control Engaged")
                    else:
                        CC = 0
                        # ("CC off")

                    if SaveData == 1:
                        with open('ID 0360', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '0361':
                    GI = int(B0, 16)

                    if SaveData == 1:
                        with open('ID 0361', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '00d4':
                    FL = int(B1 + B0, 16) * 0.05747
                    FR = int(B3 + B2, 16) * 0.05747
                    BL = int(B5 + B4, 16) * 0.05747
                    BR = int(B7 + B6, 16) * 0.05747
                    Speed = round(((FL + FR + BL + BR) / 4), 1)

                    if SaveData == 1:
                        with open('ID 00d4', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '00d0':
                    SA = int(B1 + B0, 16)
                    if SA > 60000:
                        SA = SA - 65536
                    SA = round(SA * 0.1)

                    LatAcc = int(B6, 16) * 0.2
                    LonAcc = int(B7, 16) * 0.1
                    ComAcc = math.sqrt((LatAcc ** 2) + (LonAcc ** 2))

                    # Idle seems to give a value of 50 for lat and 25 for lon then it changes

                    if SaveData == 1:
                        with open('ID 00d0', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '00d1':
                    Speed1 = round((int(B1 + B0, 16) * 0.015694 * 4), 1)
                    BP = round(((int(B2, 16) / 77) * 100), 1)

                    if SaveData == 1:
                        with open('ID 00d1', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

                if ID == '00d3':
                    TRC = int(B0 + B1, 16)

                    if TRC == 2062:
                        TRC = ("Sport Mode Engaged")
                        # IDK maybe change the display
                    elif TRC == 10254:
                        TRC = ("Sport Mode Engaged and Traction Control Off")
                    elif TRC == 2006:
                        TRC = ("Drag Mode Engaged")
                    elif TRC == 8206:
                        TRC = ("Drift Mode Engaged")
                    elif TRC == 6:
                        TRC = ("Fully engaged")

                    if SaveData == 1:
                        with open('ID 00d3', 'a') as f:
                            f.write(
                                '{} {} {} {} {} {} {} {} {} {}\n'.format(Timestamp, ID, B0, B1, B2, B3, B4, B5, B6,
                                                                         B7))

if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    main = MainWindow()
    main.show()
    sys.exit(app.exec_())
4
  • 1
    I'm actually surprised that it even works without crashing. UI elements cannot be accessed by external threads, you must use QThread and signals. If the data rate is very high, you should also find a way to make intermediate computations and only emit average values at regular intervals (time/count based) to avoid excessive repaintings (remember that the human eye can rarely notice any difference above 25-30 fps). Besides, the code could be optimized a lot, as there are lots of unnecessary conversions, like the multiple data.split() and int conversions (just compare the hex string). Commented Jul 12, 2022 at 18:49
  • @musicamante, thank you for the detailed response and explanations, I will look into QThreading instead of python threading. I will implement an average value system to reduce the number of times the code writes to the display. For the last comment on the data split and compare hex strings, could you expand on what would be a more optimized version of this, or subjects to research to optimize these areas? Thank you again for the help! Commented Jul 12, 2022 at 19:24
  • 1
    For starters, assign a temporary variable for the split text, splitText = data.split() then directly access that: Timestamp = float(splitText[1]) B0, B1, B2, B3, B4, B5, B6, B7 = splitText[36:44]; for the comparison, assuming values are always lowercase, use the string hex: for instance, instead of converting B5 and check if it's equal to 16, just do `if B5 == '10:'. I also strongly suggest you to use more verbose names for variables than TRC, SA, TC, etc (unless you are extremely familiar with those abbreviations): they don't provide any benefit, they just make code less readable. Commented Jul 12, 2022 at 19:33
  • 1
    About the hex comparison, if you're not sure about the letter case, you can still compare using lower() (or upper()): suppose you have to check if a value is equal to 15: if value.lower() == '0f':. Commented Jul 12, 2022 at 19:35

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.