I’m working on a simple data logger application called ComPlotter that opens a serial port and reads an incoming plain text data stream, plotting one or more signals in realtime in a scrolling window. The incoming data is assumed to be in plain text with the same number of integers (currently assumed to be three) on each line, separated by spaces. The incoming text is printed in a log window and the data values from each line are parsed and plotted in the graph area.
ComPlotter is still very much a work in progress, but it’s starting to come together and I have a reasonable looking screenshot, so I thought I’d post a little bit about it. Here’s the screenshot of the application as it currently looks. The signals displayed in the plot in this screenshot are from a simple accelerometer circuit that I had connected to my PC. The three signals visible in the plot are the x, y and z axes of the accelerometer.
ComPlotter tries serial port numbers in descending order, starting with COM30. The idea of this is that a USB-to-serial adapter may be assigned a different COM port number each time it is plugged into the PC, but it is likely to have the highest numbered port each time. In my experience, this is a pretty reliable way of accessing the type of USB-to-serial adapter that I usually use to connect my dsPIC microcontroller circuits to my PC.
ComPlotter is written in Python using the wxPython GUI toolkit. Here’s the complete source code (all 130 lines of it).
# # ComPlotter.py - wxPython data logger GUI # # Written by Ted Burke # Last updated 11-5-2012 # import serial import wx import numpy import matplotlib matplotlib.use('WXAgg') from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg from matplotlib.figure import Figure import matplotlib.pyplot as plt class DataLoggerWindow(wx.Frame): def __init__(self): wx.Frame.__init__(self, None, -1, "ComPlotter", (100,100), (640,480)) self.SetBackgroundColour('#ece9d8') # Flag variables self.isLogging = False # Create data buffers self.N = 100 self.n = range(self.N) self.M = 3 self.x =  for m in range(self.M): self.x.append(0 * numpy.ones(self.N, numpy.int)) # Create plot area and axes self.fig = Figure(facecolor='#ece9d8') self.canvas = FigureCanvasWxAgg(self, -1, self.fig) self.canvas.SetPosition((0,0)) self.canvas.SetSize((640,320)) self.ax = self.fig.add_axes([0.08,0.1,0.86,0.8]) self.ax.autoscale(False) self.ax.set_xlim(0, 99) self.ax.set_ylim(-100, 1100) for m in range(self.M): self.ax.plot(self.n,self.x[m]) # Create text box for event logging self.log_text = wx.TextCtrl( self, -1, pos=(140,320), size=(465,100), style=wx.TE_MULTILINE) self.log_text.SetFont( wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False)) # Create timer to read incoming data and scroll plot self.timer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.GetSample, self.timer) # Create start/stop button self.start_stop_button = wx.Button( self, label="Start", pos=(25,320), size=(100,100)) self.start_stop_button.SetFont( wx.Font(14, wx.DEFAULT, wx.NORMAL, wx.NORMAL, False)) self.start_stop_button.Bind( wx.EVT_BUTTON, self.onStartStopButton) def GetSample(self, event=None): # Get a line of text from the serial port sample_string = self.ser.readline() # Add the line to the log text box self.log_text.AppendText(sample_string) # If the line is the right length, parse it if len(sample_string) == 15: sample_string = sample_string[0:-1] sample_values = sample_string.split() for m in range(self.M): # get one value from sample value = int(sample_values[m]) self.x[m][0:99] = self.x[m][1:] self.x[m] = value # Update plot self.ax.cla() self.ax.autoscale(False) self.ax.set_xlim(0, self.N - 1) self.ax.set_ylim(-100, 1100) for m in range(self.M): self.ax.plot(self.n, self.x[m]) self.canvas.draw() def onStartStopButton(self, event): if not self.isLogging: self.isLogging = True self.ser = serial.Serial() self.ser.baudrate = 38400 self.ser.timeout=0.25 # Try serial ports one by one starting # with COM30 and working downwards for m in range(29, 0, -1): self.ser.port = m try: # Try this port number self.ser.open() # We only get to here if port opened self.log_text.AppendText( 'Opened COM' + str(m+1) + '...\n') break except: # We end up here if this port number # failed to open pass if self.ser.isOpen(): # We successfully opened a port, so start # a timer to read incoming data self.timer.Start(100) self.start_stop_button.SetLabel("Stop") else: self.timer.Stop() self.ser.close() self.isLogging = False self.start_stop_button.SetLabel("Start") if __name__ == '__main__': app = wx.PySimpleApp() window = DataLoggerWindow() window.Show() app.MainLoop()