View the ADU series of USB based Data Acquisition Products
Introduction
Communicating with USB devices via software involves
a few simple steps. Unlike RS232 based devices which are
connected to physical COM ports, USB devices are
assigned a logical handle
by operating systems when they are first plugged in.
This process is known as enumeration.
Once a USB device has been
enumerated, it is ready for use by the host computer
software. For the host application software to
communicate with the USB device, it must first obtain
the handle assigned to the USB device during the
enumeration process. The handle can be obtained using an
open function along with some
specific information about the USB device. Information
that can be used to obtain a handle to a USB device
include, serial number,
product ID, or
vendor ID.
Once we obtain a USB device handle, we can read and
write information to and from the USB device via our
application. Once the application has finished with all
communication with the USB device, the handle is closed.
The handle is generally closed when the application
terminates.
The sample source code outlines
the basics of communicating directly with an ADU device
on Linux and OS X using Python and libhidapi.
Basics of opening a USB device handle, writing and
reading data, as well as closing the handle of the ADU
usb device is provided as an example. An alternate way
of working with ADU devices in Linux is to use the
adutux kernel driver to access the device as a file
descriptor (outlined here:
https://www.ontrak.net/Linux/APG.htm). The
recommended way of working with ADU devices via Python
on OS X is via the hidapi (this example).
All source code is provided so
that you may review details that are not highlighted
here.
NOTE: See also Python and LIBUSB with ADU Devices
for alternate method of USB communications using LIBUSB.
Lets have a look at the code......
This example illustrates the basics of reading and
writing to ADU devices using the hidapi library.
NOTE: When running the example, it
must be run with root privileges in order to access the
USB device.
hidapi is a library that
provides simple access to HID compliant USB devices (https://github.com/libusb/hidapi).
We will need a vendor ID and product ID
in order to open the USB device. The VENDOR_ID define
will always remain the same as this is OnTrak's USB
vendor ID, however, PRODUCT_ID must be set to match the
product that is connected via USB. See this link for a
list of OnTrack product IDs:
https://www.ontrak.net/Nodll.htm. A Python interface
to the C library is available here:
https://github.com/trezor/cython-hidapi.
First we'll import the hid library. If you haven't
yet installed it, you may do so in the command line via
pip install hidapi or by installing via
requirements.txt (pip install -r requirements.txt).
If you receive errors when installing the hidapi Python
module on Linux, check that udev is installed (on
Debian/Ubuntu: sudo apt-get install libudev1
libudev-dev)
We'll declare OnTrak's vendor ID and the product ID
for the ADU device we wish to use (in our case 200 for
ADU200).
import hid
VENDOR_ID = 0x0a07
PRODUCT_ID = 200
Next, we'll open the connected USB device that
matches our vendor and product ID. This device will be
used for all of our interactions with the ADU via hidapi
(opening, closing, reading and writing commands).
device = hid.device()
device.open(VENDOR_ID, PRODUCT_ID)
print('Connected to ADU{}\n'.format(PRODUCT_ID))
Now that we have successfully opened our device, we
can write commands to the ADU device and read the
result.
Two convenience functions have been written to help
properly format command packets to send to the ADU
device, as well as to read a result from the ADU device:
write_to_adu() and read_from_adu(). We'll cover the
internals of these functions at the end of this page.
bytes_written = write_to_adu(device, 'RK0')
bytes_written = write_to_adu(device, 'SK0')
In order to read from the ADU device, we can send a
command that requests a return value (as defined in our
product documentation). Such a command for the ADU200 is
RPK0. This requests the value of relay 0, which we
previously set with the RK0 and SK0 commands in the
above code block.
We can then use read_from_adu() to read the result.
read_from_adu() returns the data read in integer format
on success, and None on failure. A timeout is supplied
for the maximum amount of time that the host (computer)
will wait for data from the read request.
bytes_written = write_to_adu(device, 'RPA')
data = read_from_adu(device, 200)
if data != None:
print('Received string: {}'.format(data))
When we are finished communicating with the device,
we should close the device. This is generally donw when
the application is closed.
device.close()
Further Details
If you're interested in the internals of
write_to_adu() and read_from_adu() as well as how to
structure a command packet, the details are below.
All ADU commands have their first byte set to 0x01
and the following bytes contain the ASCII representation
of the command. The ADU command packet format is
described here:
https://www.ontrak.net/Nodll.htm. As described in
the link, the remaining bytes in the command buffer must
be null padded (0x00).
We'll use hid_write() to write our command to the
device. After sending, we check to result to make sure
the transfer succeeded. The number of bytes sent should
match our command buffer's size (8 bytes)
def write_to_adu(dev, msg_str):
print('Writing command: {}'.format(msg_str))
byte_str = chr(0x01) + msg_str + chr(0) * max(7 - len(msg_str), 0)
try:
num_bytes_written = dev.write(byte_str.encode())
except IOError as e:
print ('Error writing command: {}'.format(e))
return None
return num_bytes_written
If the write, we should now have a result to read
from the command we previously sent. We can use
hid.device.read() to read the value. The arguments are
the USB device and a timeout. read() should return the
data read from the device.
If reading from the device was successful, let's
extract the data we are interested in. It's important to
note that the data returned is 8 bytes in length. The
first byte of the data returned is 0x01 and is followed
by an ASCII representation of the number. The remainder
of the bytes are padded with 0x00 (NULL) values. We can
construct a string from the second byte to the eighth
byte and strip out the null '\x00' characters. The ASCII
string is then returned.
def read_from_adu(dev, timeout):
try:
data = dev.read(8, timeout)
except IOError as e:
print ('Error reading response: {}'.format(e))
return None
byte_str = ''.join(chr(n) for n in data[1:])
result_str = byte_str.split('\x00',1)[0]
if len(result_str) == 0:
return None
return result_str
If you wish to view a list of all connected devices,
you may use the enumerate() function of hidapi:
print('Connected ADU devices:')
for d in hid.enumerate(VENDOR_ID):
print(' ADU{}'.format(d['product_id']))
print('')
When newly connecting to a device, you may want to
read until there is nothing left to read from the
device. This is to clear any pending reads that was not
initiated by us. We'll review the read_adu function
later on.
while True:
if read_adu(device, 200) == None:
break
Download Linux &
OS Python Example File in ZIP Format
|