This wiki has been archived and made read-only.
For up-to-date information about TkkrLab and it's projects please visit our main website at tkkrlab.nl.

Mini Arduino python script

From

Revision as of 16:08, 20 October 2012 by Frankf (Talk | contribs) (Using threads and timers)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

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.

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

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.