Reading Real Time Values With Pyserial While Plotting
Solution 1:
I know the code below seems long and overly complex for your simple problem, but manual optimization is usually more code and more potential bugs. That's why premature optimization is almost always a mistake.
In __init__
it sets up the plot, setting references for the axis, canvas, line (it starts with a line drawn off screen), and the pre-animation background. Additionally __init__
registers callbacks to handle resizing and shutdown. The on_resize
callback is needed to update the background (used for the blit) when the window is resized. The on_close
callback uses a lock to update the running status. I haven't eliminated all race conditions with this, but this works to prevent a _tkinter.TclError
caused by trying to blit to a terminated Tk app. I only tested with Tk, and on only one machine. YMMV, and I'm open to suggestions.
In the run
method I've added a call to canvas.flush_events()
. This should keep the plot window from hanging if you try to drag the window around and resize it. The while loop in this method calls self.get_new_values()
to set the data in the plot. It then updates the plot using the configured method. If self.blit
is true, it uses canvas.blit
, else it uses pyplot.draw
.
The variable spf
(samples per frame) controls how many samples are plotted in a frame. You can use it in your implementation of get_new_values
to determine the number of bytes to read (e.g. 2 * self.spf
for 2 bytes per sample). I've set the default to 120, which is 5 frames per second if your data rate is 600 samples per second. You have to find the sweet spot that maximizes throughput vs time resolution in the graph while also keeping up with the incoming data.
Reading your data into a NumPy array instead of using a Python list will likely speed up processing. Also, it would give you easy access to tools to downsample and analyze the signal. You can read into a NumPy array directly from a byte string, but make sure you get the endianess right:
>>>data = b'\x01\xff'#big endian 256 + 255 = 511>>>np.little_endian #my machine is little endian
True
>>>y = np.fromstring(data, dtype=np.uint16); y #so this is wrong
array([65281], dtype=uint16)
>>>if np.little_endian: y = y.byteswap()>>>y #fixed
array([511], dtype=uint16)
Code:
from __future__ import division
from matplotlib import pyplot
import threading
classMain(object):
def__init__(self, samples_per_frame=120, blit=True):
self.blit = blit
self.spf = samples_per_frame
pyplot.ion()
self.ax = pyplot.subplot(111)
self.line, = self.ax.plot(range(self.spf), [-1] * self.spf)
self.ax.axis([0, self.spf, 0, 65536])
pyplot.draw()
self.canvas = self.ax.figure.canvas
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
self.canvas.mpl_connect('resize_event', self.on_resize)
self.canvas.mpl_connect('close_event', self.on_close)
self.lock = threading.Lock()
self.run()
defget_new_values(self):
import time
import random
#simulate receiving data at 9600 bps (no cntrl/crc)
time.sleep(2 * self.spf * 8 / 9600)
y = [random.randrange(65536) for i inrange(self.spf)]
self.line.set_ydata(y)
defon_resize(self, event):
self.line.set_ydata([-1] * self.spf)
pyplot.draw()
self.background = self.canvas.copy_from_bbox(self.ax.bbox)
defon_close(self, event):
with self.lock:
self.running = Falsedefrun(self):
with self.lock:
self.running = Truewhile self.running:
self.canvas.flush_events()
with self.lock:
self.get_new_values()
if self.running:
if self.blit:
#blit for a higher frame rate
self.canvas.restore_region(self.background)
self.ax.draw_artist(self.line)
self.canvas.blit(self.ax.bbox)
else:
#versus a regular draw
pyplot.draw()
if __name__ == '__main__':
Main(samples_per_frame=120, blit=True)
Post a Comment for "Reading Real Time Values With Pyserial While Plotting"