Search code examples
pythonmatplotlibpython-can

Live waveform with python-can


I'm trying to plot can-data using the python-can and matplotlib library. I have a basic example on how to plot live data with matplotlib:

import numpy as np
import matplotlib.pyplot as plt

plt.axis([0, 10, 0, 1])


for i in range(10):
    y = np.random.random()
    plt.scatter(i, y)
    # plt.pause(0.05)
    plt.draw()

plt.show()

And I used a basic python-can example and added the matplotlib:

import sys
import argparse
import socket
from datetime import datetime

import can
from can import Bus, BusState, Logger
import numpy as np
import matplotlib.pyplot as plt



def main():

    plt.axis([0, 10, 0, 1])

    bus = Bus(bustype ='pcan')
    i = 0

    print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}")
    print(f"Can Logger (Started on {datetime.now()})")

    plt.show()

    try:
        while True:
            msg = bus.recv(1)
            if msg is not None:
                y = np.random.random()
                plt.scatter(i, y)
                plt.draw()
                i += 1
                print('Message id {id}'.format(id=msg.arbitration_id))

    except KeyboardInterrupt:
        pass
    finally:
        plt.show()
        bus.shutdown()


if __name__ == "__main__":
    main()

However as soon as I add the plt commands the script stops working, it just doesn't output the print statement anymore. When I Debug into it and step manually it works. Anything I'm doing wrong? Is there a better way to solve this? I'm not set on matplotlib, just the fastest way to plot something :)


Solution

  • I found a working solution using the blit function, its not perfect yet but its a good starting point:

    from datetime import datetime
    import can
    from can import Bus, BusState, Logger
    import numpy as np
    import matplotlib.pyplot as plt
    import struct
    
    
    
    def main():
        bus = Bus(bustype ='pcan')
        i = 0
    
        print(f"Connected to {bus.__class__.__name__}: {bus.channel_info}")
        print(f"Can Logger (Started on {datetime.now()})")
    
        x = np.linspace(0, 1000, num=1000)
        y = np.zeros(1000)
    
        fig = plt.figure()
        ax1 = fig.add_subplot(1, 1, 1)
        line, = ax1.plot([], lw=3)
        text = ax1.text(0.8, 0.5, "")
    
        ax1.set_xlim(x.min(), x.max())
        ax1.set_ylim([-10, 10])
    
        fig.canvas.draw()  # note that the first draw comes before setting data
    
        ax2background = fig.canvas.copy_from_bbox(ax1.bbox)
    
        plt.show(block=False)
        k = 0
    
    
        try:
            while True:
                msg = bus.recv(1)
                if msg is not None:
                    y = np.roll(y, 1)
                    value = struct.unpack('f', msg.data)
                    y[0] = np.int8(value)
                    line.set_data(x, y)
                    fig.canvas.restore_region(ax2background)
                    ax1.draw_artist(line)
                    ax1.draw_artist(text)
                    fig.canvas.blit(ax1.bbox)
                    fig.canvas.flush_events()
                    # print('Message data {data}, value {value}, counter {counter}'.format(data=msg.data, value=value, counter=k))
                    k += 1
                    if k > 100:
                        k = 0
    
        except KeyboardInterrupt:
            pass
        finally:
            plt.show()
            bus.shutdown()
    
    
    if __name__ == "__main__":
        main()
    

    The script assumes that the can message carries one single float over the first four uint8 and just plots it.