Saturday, 26 March 2011

Controlling a serial device with Python

This program to sends a command to serial device, receives data from the device, converts it to numeric format, and graphs the data in real time. It is written in Python using wxPython for the display.

The device is connected to /dev/ttyS0 with a baud rate of 19200, 8-bit data, no parity, and 1 stopbit. You need to edit the line 'serial.Serial' with the relevant parameters to use this code for your device. Editing 'DATA_LENGTH' will change the rate at which the graph scrolls forward. Also, you'd need to change the 'command' that the device understands as 'send me data' (here it is the s character - line 'input'). Knowledge of the format of the returned data is also essential (here I turn ascii into 2 integers) line 'struct.unpack' - see here for help). Otherwise this should work with any device. Tested on Ubuntu 9.10 and 10.4 using serial port and USB-serial converters.
import os
import pprint
import sys
import wx
# use wx with mpl is with the WXAgg backend
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas, NavigationToolbar2WxAgg as NavigationToolbar
import numpy as npy
import pylab
import serial
import time
import struct
# set connection:
ser = serial.Serial('/dev/ttyUSB0', 19200, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=1, timeout=0.1, xonxoff=1, rtscts=0)
ser.open() # open connection
ser.isOpen() # should be true
DATA_LENGTH = 120 #seconds
REDRAW_TIMER_MS = 60 # milliseconds
def getData():
input = 's'
# send 's' the character to the device
ser.write(input)
out = ''
# give device time to answer
time.sleep(0.02)
while ser.inWaiting() > 0:
out += ser.read(1) #request data (serial.read() only returns 8 bit ASCII strings)
#out += ser.read(ser.inWaiting())
if out != '':
# read in a length-2 string of 8-bit characters and then use the struct module to convert the strings into integers
response=struct.unpack(">bb", out)
# use only first integer in tuple (2nd is empty)
print response[0]
return response[0]
class GraphFrame(wx.Frame):
# the main frame of the application
def __init__(slf):
wx.Frame.__init__(slf, None, -1, "Sensor response", size=(800,600))
slf.Centre()
slf.data = []
slf.create_main_panel()
slf.redraw_timer = wx.Timer(slf)
slf.Bind(wx.EVT_TIMER, slf.on_redraw_timer, slf.redraw_timer)
slf.redraw_timer.Start(REDRAW_TIMER_MS)
def create_main_panel(slf):
slf.panel = wx.Panel(slf)
slf.init_plot()
slf.canvas = FigCanvas(slf.panel, -1, slf.fig)
slf.hbox1 = wx.BoxSizer(wx.HORIZONTAL)
slf.vbox = wx.BoxSizer(wx.VERTICAL)
slf.vbox.Add(slf.canvas, 1, flag=wx.LEFT | wx.TOP | wx.GROW)
slf.panel.SetSizer(slf.vbox)
def init_plot(slf):
slf.dpi = 100
slf.fig = Figure((3.0, 3.0), dpi=slf.dpi)
slf.axes = slf.fig.add_subplot(111)
slf.axes.set_axis_bgcolor('black')
slf.axes.set_title('My data', size=12)
slf.ymin = -50000 # initalise with large values
slf.ymax = 50000
pylab.setp(slf.axes.get_xticklabels(), fontsize=8)
pylab.setp(slf.axes.get_yticklabels(), fontsize=8)
# plot the data as a line series, and save the reference
# to the plotted line series
slf.plot_data = slf.axes.plot(
slf.data,
linewidth=2,
color="white",
)[0]
#count=count+1
#if count>120:
# slf.Destroy()
def draw_plot(slf):
# redraws the plot
xmax = len(slf.data) if len(slf.data) > DATA_LENGTH else DATA_LENGTH
xmin = xmax - DATA_LENGTH
ymin = slf.ymin # ymin and ymax set based on previous entries
ymax = slf.ymax #50000
slf.axes.set_xbound(lower=xmin, upper=xmax)
slf.axes.set_ybound(lower=ymin, upper=ymax)
pylab.setp(slf.axes.get_xticklabels(), visible=True)
slf.plot_data.set_xdata(npy.arange(len(slf.data)))
slf.plot_data.set_ydata(npy.array(slf.data))
slf.canvas.draw()
def on_redraw_timer(slf, event):
newData = getData()
slf.data.append(newData)
slf.draw_plot()
data=slf.data
if len(data)>=6:
# get last 5 values
datanew=[data[len(slf.data)-5],data[len(slf.data)-4],data[len(slf.data)-3],data[len(slf.data)-2],data[len(slf.data)-1]]
slf.ymin = (min(datanew))-(min(datanew)*10)/100 # min of last 5, minus 10%
slf.ymax = (max(datanew))+(max(datanew)*10)/100 # max of last 5, plus 10%
else:
slf.ymin = (min(data))-(min(data)*10)/100 # min minus 10%
slf.ymax = (max(data))+(max(data)*10)/100 # max plus 10%
def on_exit(slf, event):
slf.Destroy()
def flash_status_message(slf, msg, flash_len_ms=1500):
slf.statusbar.SetStatusText(msg)
slf.timeroff = wx.Timer(slf)
slf.Bind(
wx.EVT_TIMER,
slf.on_flash_status_off,
slf.timeroff)
slf.timeroff.Start(flash_len_ms, oneShot=True)
def on_flash_status_off(slf, event):
slf.statusbar.SetStatusText('')
if __name__ == '__main__':
app = wx.PySimpleApp()
app.frame = GraphFrame()
app.frame.Show()
app.MainLoop()

No comments: