Application Note 7

Getting DACS Data with Python and Perl


Introduction

The BOBZ Data Acquisition and Control System (DACS) provides an easy way to monitor and control using a serial connection and terminal. However, DACS can also be used effectively when data acquisition and control is performed automatically with the use of scripts, programs or a control microprocessor.

Application Note 1 shows how to log DACS data using a Bash shell script that is attached to a cron task. The following shows how to use a Python program to do the same thing. The last part of this note provides a Perl script that reads DACS data by passing requests on the command line.

Before launching into a description of the Python program, it may be useful to ask: "why use a Python program?" For our purposes, Python provides a second example on how to log DACS data and it uses existing resources.

Python is an increasingly popular language and is pre-installed on the Raspberry Pi. But, its use is not limited to small systems: it is readily loaded on almost any Linux platform and on Windows and the MAC. Python basics are relatively easy to learn. Chances are that already have or can easily load Python. With access to a serial port you are ready to log data with little modification of the example program.

The case for Perl is similar to that for Python: it is readily loaded on a variety of platforms and is more powerful and versitile than shell scripting, especially for string processing.

Python is emphasized in this note mostly because it is being promoted as the language of choice for processors such as the BeagleBone Black and Raspberry Pi (the "Pi" stands for "Python"). Because of this preferred use on these platforms, there are a large number of Python examples and tutorials to help new users. It is also likely that many of our customers are also learning Python for applications other than DACS data acquisition.


Page 2

Serial Connection

In the following, it is assumed that pyserial, a serial port module for Python, has been installed. Similarly, serial support may have to be loaded for Perl.

If pyserial is not installed, you should be able to easily install it on any Debian-based, network-connected Linux system using the following statements:



   sudo apt-get update
   sudo apt-cache search python2-pyserial
   sudo apt-get install python2-pyserial

You will have to edit the Python program given below to specify the serial port to use. The Perl program also requires the serial port designation as a command line parameter.

To see what serial devices are mapped, use a command such as: dmesg | grep "ttyUSB" or dmesg | grep "tty". If you are using the hardware port on a Raspberry Pi, the serial device is: /dev/ttyAMA0.

For controlling multiple DACS devices with a Raspberry Pi or BeagleBone, you can plug in a small USB Hub into one of the Pi or BeagleBone USB connectors to expand the available USB ports. With this expansion, you can use several USB to serial adapters. Two Hub-connected USB adapters might map, for example, into /dev/ttyUSB0 and /dev/ttyUSB1.


Page 3

Example Python Program

Figure 1 lists a Python program that acquires DACS data whenever it is executed at a command line. The following sections refer to this program and to individual numbered lines within it. Note: the numbered listing was generated and put in a temporary file with the following statement: cat -n dacs_v1.py >> temp.txt.

The downloadable file app_note7.tgz contains the source code for both the Python and Perl programs. To uncompress the tar archive, enter: tar -xvf app_note7.tgz at a command prompt. Note that this command line assumes that the archive file is in the current directory and that the extracted files will also reside in the current directory.

We suggest moving the archive file to your local "bin" directory (i.e., "~/bin"). Before extracting files from it. After extraction, you may have to change the permissions on the Python file using a command such as sudo chmod 755 ./cron_dacs_v1.py. You may also have to use sudo with the Perl script when executing it from a command line.

The following describes the program statements in Figure 1 by presenting a few lines and then discussing them.



   1   #!/usr/bin/python
   2	
   3	# cron_dacs_v1.py -- 140711rjn
   4	# aquire DACS data, parse it and append to plot file

The first few lines of the program include the usual boilerplate comments describing the name, date, author and functionality of the program.

Line 1 has the usual "#!" prefix that specifies that the following statements are to be executed by Python. The Python version used with the Figure 1 script is 2.7.3, but it should work for any version of Python from 2.6 and above (also, probably, with Python 3 with a slight change to the print statement).

To reveal the version of Python that your system uses, enter python at a command prompt. Exit the Python interpreter with a "control D" (EOF) or enter exit() at the Python command line.


Page 4

Example Python Program (Cont.)



    1     #!/usr/bin/python
    2	
    3     # cron_dacs_v1.py -- 140711rjn
    4     # aquire DACS data, parse it and append to plot file
    5	
    6     import serial, time
    7     from datetime import datetime
    8	
    9     logfile = '/home/bob/logs/dacs_v1.txt'
   10    dacs_port = '/dev/ttyUSB0'
   11	
   12
   13	
   14    ser = serial.Serial(
   15       port=dacs_port,
   16       baudrate=19200,
   17       bytesize=serial.EIGHTBITS,
   18       parity=serial.PARITY_EVEN,
   19       stopbits=serial.STOPBITS_ONE,
   20    )
   21	
   22
   23    if ser.isOpen():
   24       ser.write('s ')
   25       time.sleep(0.2)
   26       s = ser.readline()
   27       ser.close()
   28       v = s.split('|')[3].split()
   29	
   30    logtime = datetime.now()
   31    log = datetime.strftime(logtime, '%y%m%d-%H:%M:%S') + ' ' + v[1]
   32    # print log
   33	
   34    with open(logfile, 'a') as f:
   35       f.write(log + '\n')
   36	
   37    exit() 
    

Figure 1. Python Program to Log DACS Data


Page 5

Example Python Program (Cont.)



   6	import serial, time
   7	from datetime import datetime

Lines 6 and 7 import Python modules needed for this example. Only datetime is exported from the datetime module.



    9	logfile = '/home/bob/logs/dacs_v1.txt'
   10	dacs_port = '/dev/ttyUSB0'

Line 9 assigns the log file string to logfile. If you need to change the location of the DACS log file, this is the place to do it. This file is typically directed to the user's local logs directory (e.g., ~/logs).

The statement in Line 10 specifies the serial port to use. Again, if this needs to be changed, this is the place to do it.



   14    ser = serial.Serial(
   15    port=dacs_port,
   16    baudrate=19200,
   17    bytesize=serial.EIGHTBITS,
   18    parity=serial.PARITY_EVEN,
   19    stopbits=serial.STOPBITS_ONE,
   20    )

Lines 14 to 20 establish a serial port instance, ser, and set up port parameters such as baud rate and parity. Note that, in Line 14, it is necessary to capitalize the second "serial." These statements also open the serial port so a ser.open() statement is not needed.


Page 6

Example Python Program (Cont.)



    23   if ser.isOpen():
    24      ser.write('s ')
    25      time.sleep(0.2)
    26      s = ser.readline()
    27      ser.close()
    28      v = s.split('|')[3].split()

Lines 23 to 28 form the "meat" of the program. Line 23 performs the actions of Line 24 through 28 if the serial port was successfully opened.

The serial port opening and data processing are not included within a "try: ... except:" statement, as is normal practice. This is because the exception messages may not be displayed at a command line. Normally, additional code would be add to capture errors and route them to the standard error file (e.g., when the program is executed as a "cron" task). Experienced Python and Linux users may want to use exception processing to return error numbers so that they can be handled by the operating system.

If the port has been opened without an exception, Line 24 writes an "s" character, followed by a space, to query the DACS for a snapshot of its I/O. Line 25 pauses the program for 200 milliseconds to allow the DACS response to accumulate in the serial buffer. The serial response buffer is not cleared because the serial port is opened and closed each time a line of DACS data is captured.

Line 26 assigns the response line to s, for further processing. The "readline" command terminates at the first "newline" character received in the response. In our case, this will be the newline character following the data response. At this point, s contains data such as: s | 1 1 1 1 1 1 | 0 0 0 0 | 2.099 2.187 0.848 2.983 | 25.8 78.1 | , followed by a carriage return and a "newline" (line feed). The DACS response also includes the "> " prompt for the next command but this is not included in s.

The data of interest is the second voltage reading of 2.187 Volts. Note that DACS echos characters sent to it so the leading s and one of the following spaces is a command echo.

Line 27 closes the serial port because the data we want to process is now in s and the serial port is no longer needed. On the following line, the parsed data from s is processed and put into v.


Page 7

Example Python Program (Cont.)

As noted, Line 28 parses s to get the data we want. The data in s is first split using the "|" character. If v was displayed at the end of the first split, it would be a list: v = ['s ', ' 1 1 1 1 1 1 ', ' 0 0 0 0', ' 2.099 2.187 0.848 2.983 ', ' 25.8 78.1 ']. The "[3]" following the split specifies that we want to slice out the fourth item in the list, which is the field containing the four DACS voltages.

Next, the voltages are split using the default "whitespace" (blank) character for parsing. At this point, the v is: v = ['2.099', '2.187', '0.848', '2.983'], a list containing the four DACS voltages.



    30   logtime = datetime.now()
    31   log = datetime.strftime(logtime, '%y%m%d-%H:%M:%S') + ' ' + v[1]

Now that we have the voltages squared away in a list, we turn to the date and time field that will precede the DACS voltage in the log file.

Line 30 assigns the current date and time to logtime. Line 31 uses the statement: datetime.strftime(logtime, '%y%m%d-%H:%M:%S') to create a string containing the current date and time in a format specified by the second parameter in the strftime method. Note that the format specification is the same as that used for Bash shell scripting.

Once the date and time string is generated, it is concantenated with a space character and the second item in v, the voltage reading of 2.187. Note that an index of "1" is used to get the second item because Python uses zero indexing. At this point, log contains the data to be written to the log file.



    34   with open(logfile, 'a') as f:
    35         f.write(log + '\n')
    36	
    37         exit() 

Line 34 opens our previously-specified logging file, logfile, in the append mode with an instance name of f. Line 35 writes the log string to logfile followed by a "newline" character. Line 37 exits the program with a normal return value. Again, advanced users may want to improve the error handling and passing of status information to system resources (e.g., stderr). There are many examples of how to do this on the Web.


Page 8

Example Python Program (Cont.)

Figure 2 shows the contents of a typical output file generated by the Python program (it is identical to that generated by the Bash shell script).



  140530-16:00:01 2.951
  140530-17:00:01 2.942
  140530-18:00:01 2.938
  140530-19:00:01 2.939
  140530-20:00:01 2.925
  140530-21:00:01 2.930
  140530-22:00:01 2.923
  140530-23:00:01 2.922
  140531-00:00:01 2.919
  140531-01:00:01 2.914
 

Figure 2. Voltage Data in logfile


CRON Task

Application Note 1 describes how to attach the Python program to a "cron" task. Note that, as written, the Python program does not direct any error messages to stderr, so the redirection of errors described in Note 1 has no effect. Python programmers can easily change this situation using a raise instruction in an exception statement.


Page 9

Example PERL Script

Figure 3 shows a PERL script that gets DACS data with a request at the command line. For example, entering: get_dac.pl /dev/ttyUSB0 s would produce a snapshot of DACS data for a DACS connected to the device /dev/ttyUSB0.

The PERL program is not explained in detail here, but it should be relatively easy to follow after the Python example. Note that, in the do_print subroutine, the response from the DACS is received a character at a time until a ">" character is received. The accumulated response is then displayed with a print statement.


       1    #!/usr/bin/perl
       2    #
       3    # get_dac.pl -- 140601 by Bill Kibler
       4    #
       5    # Usag   
       6    # perl ./get_dac.pl /dev/ttyUSB0 tf
       7    # tf   80.1
       8	
       9    # util to pass command to dac board
      10    use Device::SerialPort;
      11	
      12    # get command to use
      13    ($device, $cmmd, $other) = @ARGV;
      14    if($device eq "") {
      15    print "usage: get_dac.pl device command ( /dev/tty??? for dac )\n";
      16    exit(0);
      17    }
      18    if($cmmd eq "") {
      19    print "usage: get_dac.pl device command ( command to send to dac )\n";
      20    exit(0);
      21    }
   

Figure 3. PERL Script to Show DACS Data (Part 1)


Page 10


      22	
      23    # get start time of run
      24    $date = `date`;
      25	
      26    my $port = Device::SerialPort->new("$device");
      27    $port->user_msg(ON);
      28    $port->databits(8);
      29    $port->baudrate(19200);
      30    $port->parity("none");
      31    $port->stopbits(1);
      32    $port->write_settings;
      33	
      34    # needed to clear buffer
      35    $port->lookclear;
      36	
      37    # do send command - get analog 0
      38    $port->write("$cmmd\n");
      39    # get results and print it
      40    do_print () ;
      41	
      42    exit;
      43	
      44    ###### subs ########
      45    sub do_print {
      46       while(1) {
      47          my $byte=$port->read(1);
      48          if ( $byte eq ">") {
      49          last;
      50          }
      51       print "$byte";
      52       }
      53    }

Figure 3. PERL Script to Show DACS Data (Part 2)


Page 11





(BLANK PAGE)


Page 12

Revision Summary

Revision
Date
Description
1
17Jul14
Initial Release

email for BOBZ product support

Copyright © July 17, 2014, Bob Nash.