Mini Arduino python script
From
Interfacing an Arduino using a Python script is easy? If you want a good script which just works, you may find yourself in need of some pointers.
Below are two solutions. Both do not require you to unplug an Arduino in order to communicate with it before restarting your program. The first solution makes good use of the serial interface provided by the operating system. Furthermore, it uses a non-blocking connection, so you can use your CPU time efficiently with minimal coding effort. Unfortunately, this requires calls to some potent libraries. Installing and activating a complete gtk toolkit for a graphical user interface may not be to everyone's liking.
The second bit of sample code only depends on the serial library for Python, pySerial. But this approach requires resetting the Arduino by your program, meticulously closing the serial connection when the program ends, and be very conscious about the methods and options you use, perhaps including threads, or accept your program will do nothing while waiting for incoming data after a read command.
Contents
Using a non-blocking serial connection
The serial library for Python does not allow a non-blocking connection. Using, in this example, gtk and glib you can still set up such a connection. The solution below requires you to install python-gtk2 and perhaps python-gi.
import os import sys import gtk import glib scanned = '' def callback(fd, condition): global scanned try: s = os.read(fd, 1024) except OSError: return True pos = 0 while pos<len(s): scanned+=s[pos] return True def main(): global status read_all() status = 'welcome' if len(sys.argv)>1 and sys.argv[1]=='test': filename = "data" else: filename = '/dev/ttyUSB0' os.system('stty -F '+filename+' 19200') fd = os.open(filename, os.O_RDONLY | os.O_NONBLOCK) glib.io_add_watch(fd, glib.IO_IN | glib.IO_PRI, callback) gtk.main() return 0
Under Unix you can build a similar solution using the pySerial module. Use the nonblocking()
method, which should cooperate well with the select module. See the pySerial documentation for more information.
See the Links section for more Qt oriented solutions (which also uses pySerial.)
Contacting the Arduino using the serial library pySerial
This second example does not require juggling a complicated GUI interface, but your code may frequently have to wait until the next data batch arrives from your Arduino. Solutions to this are discussed below. You need the pySerial library (python-serial package) for the code to work.
import serial import time import sys def myprogram(): # put some code here which either ends nicely or crashes the program # if you put a sys.exit() call here, don't forget to first close the input: ser.close(), # otherwise you probably can't restart the script successfully without detaching and # reattaching the Arduino pass if __name__ == "__main__" : try: # turns on fatal error catching ser = serial.Serial('/dev/ttyACM0', 115200) # set the correct device name and baudrate except: # do the following if a fatal error was detected # please give the user a suitable error message sys.exit(1) # quit and tell the OS the program closed due to an error # Reset the Arduino to make sure it's working properly ser.setDTR( level=False ) # set the reset signal time.sleep(2) # wait two seconds, an Arduino needs some time to really reset # don't do anything here which might overwrite the Arduino's program ser.setDTR( level=True ) # remove the reset signal, the Arduino will restart try: myprogram() except: ser.close() raise # show any traceback messages and quit ser.close() # make sure to always close the serial connection
In the discussion below, reading from an Arduino is assumed to be sensitive to buffer overflow. Your computer's serial device is equipped with a buffer to collect data from your Arduino. But this buffer has a finite capacity. A buffer overflow occurs when the Arduino sends out more data than is collected from the serial device by your program. The overflowing data is lost, as in deleted. A buffer overflow will not stop your program, but the data loss will corrupt the structure of the data the Arduino sends.
If writing instead of reading is a limiting action, similar reasoning and solutions can be applied as described below.
The final assumption in the following discussion is that your application has to be efficient. For example, if your program requires lots of computations, produces complicated visual representations, produces sound, or a combination of these.
Simulating non-blocking
To limit or avoid the time spend waiting for new data would require non-blocking. Using the timeout option when setting the serial connection, you can get a similar effect.
Using the code example above, the serial connection, ser.read()
will block until data is present. With the timeout option set to zero, ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0)
, your code mimics non blocking behaviour.
Similarly, you can give timeout
a value of a few milliseconds (a value of 0.001 represents a millisecond.) Combined with threads (see below), an optimised timeout setting provides a very resource friendly solution [Kurniawan, Links section].
Using threads and timers
To keep the serial buffer from overflowing, it has to be emptied on a regular basis. One solution is using threads. Running code in separate threads is similar to running programs in a multi-tasking operating system, where multiple program seem to run at the same time, while peacefully sharing the computer's resources. However, Python threads can only run on a single processor. The thread responsible for reading from serial may still have to wait, but the other threads can continue to do something useful in their time slots. At the same time the frequently reactivated reading thread prevents buffer overflow.
Communication between threads is not straightforward, but quite doable. In a program without threads, functions return well formed data and you don't need to worry (too much) about side effects. When using threads, you don't know in what state its functions are, and side effects which change global variables are a nightmare. So the problem is to make your threads communicate safely. Python's Queue package makes this easy by providing a thread safe communication channel. Dough Hellmann (Links section) provides good general tutorials on both.
A variation on a threaded solution is to use a timer. The timer regularly calls a reading function which empties serial device's buffer. The trick is to set a decent time interval between calls; call your function too often and your application will be sluggish, or your CPU has to work hard to keep up, call too infrequently and you'll have buffer overflows anyway. As in the threaded solution, the function called by the timer has to use thread safe communication with the rest of the program. All GUI toolkits provide timers. Python provides a timer through its threading.Timer class.
(Python's file system watcher capabilities seem too limited to be able to tell if data is available from the serial device. Using glib, this method was used successfully in the first example of this page. If such a method would exist, again, a thread safe connection would be needed to get the data to the rest of the program.)
Links
- The pySerial documentation
- Kurniawan's slides on Qt and python serial (pdf), although a bit hard to interpret due to the slide format, this provides a lot of background on how to structure your code to use pySerial effectively.
- Dough Hellman's tutorials on Python threads and queues
Mentioned package names are for Python 2.x in Debian and Debian related distributions. For Python 3.x you need the packages which start with python3.