Picotech makes a good range of data loggers for logging pretty much any physical measurements. The commpany publishes the APIs for their devices, which means that they are easy to control from a program written in the Python programming language. This article shows an example temperature logger.
Requirements:
Programs written in Python can run on any widely-used operating system. Picotech data loggers are usually used on machines running Microsoft Windows, but Picotech does produce (unsupported) drivers for Linux. Visit the main download page. MacOS is not supported.
Save this code in a file, perhaps called usbtc08.py
#!/usr/bin/env python
"""Communication functions for Pico Technology USB TC-08 data logger.
See http://www.picotech.com/document/pdf/usbtc08_en.pdf
"""
import ctypes as ct
import unittest
__author__ = 'Tom Oakley'
# Try loading the dll
try:
usbtc08 = ct.windll.LoadLibrary('usbtc08')
except WindowsError:
raise ImportError('usbtc08.dll not found. Check driver is installed.')
# Cold junction is channel 0, plus 8 hot junctions
_CHANNELS_PER_TC08 = 9
class Usbtc08Exception(Exception):
"""Exception generated whilst using USB TC08 unit."""
# Function interface to dll
_usb_tc08_open_unit = usbtc08.usb_tc08_open_unit
_usb_tc08_open_unit.restype = ct.c_short
def usb_tc08_open_unit():
"""Opens unit.
If you wish to use more than one USB TC-08, call this routine once for
each unit connected to the PC. The function will return 0 if there are
no more units found. The driver is thread-safe and will not allow access
to a single unit from more than one application. If, therefore,
usb_tc08_open_unit does not find a unit, check that other applications
are not using the USB TC-08. This includes applications on other user
accounts on the same computer, where fast user switching is supported.
Note: The usb_tc08_open_unit function provides a simple way to open
USB TC-08 units. However, the function call locks up the calling
thread until the attached USB TC-08 unit has been fully enumerated.
If a single-threaded application needs to perform concurrent processing,
such as displaying a progress bar, use usb_tc08_open_unit_async.
Returns:
Positive short - the handle of the unit
0 - no units were found
-1 - unit failed to open. Call usb_tc08_get_last_error(0)
"""
return _usb_tc08_open_unit()
_usb_tc08_close_unit = usbtc08.usb_tc08_close_unit
_usb_tc08_close_unit.restype = ct.c_short
_usb_tc08_close_unit.argtypes = (ct.c_short,)
def usb_tc08_close_unit(handle=1):
"""Closes the TC08 unit.
Note: If you successfully open any USB TC-08 units, call
usb_tc08_close_unit for each handle before you exit from your program.
If you do not, there is a chance that the unit will not reopen until it
has been disconnected and reconnected.
Arguments:
handle - the TC08 unit
Returns:
0 - call usb_tc08_get_last_error(0)
1 - the unit closed successfully
"""
return _usb_tc08_close_unit(handle)
_usb_tc08_set_channel = usbtc08.usb_tc08_set_channel
_usb_tc08_set_channel.restype = ct.c_short
_usb_tc08_set_channel.argtypes = (ct.c_short, ct.c_short, ct.c_char)
def usb_tc08_set_channel(handle=1, channel=1, tc_type='K'):
"""Enables the given channel.
Call usb_tc08_open_unit() first.
Arguments:
handle - the USB TC-08 unit.
channel - which channel to set: 0 - 8 (0 denotes the cold junction.)
tc_type - type of thermocouple. Set to one of the following characters:
'B', 'E', 'J', 'K', 'N', 'R', 'S', 'T.' Use a space in quotes
to disable the channel. Voltage readings can be obtained by
passing 'X' as the character.
Returns:
0 - an error occurred. Call usb_tc08_get_last_error(0)
1 - channel was successfully set
"""
return _usb_tc08_set_channel(handle, channel, ct.c_char(tc_type))
_usb_tc08_get_single = usbtc08.usb_tc08_get_single
_usb_tc08_get_single.restype = ct.c_short
_usb_tc08_get_single.argtypes = (ct.c_short,
ct.POINTER(ct.c_float),
ct.POINTER(ct.c_short),
ct.c_short)
def usb_tc08_get_single(handle=1, units=0):
"""Reads a single temperature.
You must set up the channels before calling this function.
You must not have put the unit into Streaming mode with usb_tc08_run,
as this will cause usb_tc08_get_single to fail. The function will convert
all readings on demand. For more details and an example see the Get
Single mode section.
Arguments:
handle - specifies the USB TC-08 unit.
units - specifies the temperature units for returned data:
0: USBTC08_UNITS_CENTIGRADE
1: USBTC08_UNITS_FAHRENHEIT
2: USBTC08_UNITS_KELVIN
3: USBTC08_UNITS_RANKINE
Returns a tuple of:
ok 0 - an error occurred, use usb_tc08_get_last_error to get the code.
1 - the function succeeded.
temp - pointer to an array of length [9]. There are 9 channels
on the USB TC-08 (8 + cold junction) and the readings are
always placed in the array subscript corresponding to the
channel number. Channels which are not enabled are filled
with QNaN values.
Raises:
Usbtc08Exception if an overflow was reported by the TC08.
"""
# Set up the buffers to be passed as pointers
buffer = (ct.c_float * _CHANNELS_PER_TC08)()
overflow = 0
# Call the function
ok = _usb_tc08_get_single(handle, buffer, ct.byref(ct.c_short(overflow)), units)
if overflow:
raise Usbtc08Exception('Buffer overflow, code:%d' % overflow)
# Clean the buffer of QNAN values
buffer = list(buffer)
for index, temper in enumerate(buffer):
if 'QNAN' in str(temper):
buffer[index] = None
# Return a tuple as described in this function's docstring
return ok, tuple(buffer)
_usb_tc08_get_last_error = usbtc08.usb_tc08_get_last_error
_usb_tc08_get_last_error.restype = ct.c_short
_usb_tc08_get_last_error.argtypes = (ct.c_short,)
def usb_tc08_get_last_error(handle=1):
"""Returns the last error on the unit specified by the handle.
If zero is passed instead of a handle, the function returns the error
associated with the last call to usb_tc08_open_unit or
usb_tc08_open_unit_async
Note: If an invalid handle is passed to a function, the function will fail.
The error code, however, cannot be associated with a unit so
usb_tc08_get_last_error will not retain an error code in this instance.
usb_tc08_get_last_error will also fail if the invalid handle is passed to
it.
Returns:
-1 - invalid handle, otherwise...
0 USBTC08_ERROR_OK No error occurred.
1 USBTC08_ERROR_OS_NOT_SUPPORTED The driver supports Windows XP SP2 and Vista.
2 USBTC08_ERROR_NO_CHANNELS_SET A call to usb_tc08_set_channel is required.
3 USBTC08_ERROR_INVALID_PARAMETER One or more of the function arguments were invalid.
4 USBTC08_ERROR_VARIANT_NOT_SUPPORTED The hardware version is not supported.
Download the latest driver.
5 USBTC08_ERROR_INCORRECT_MODE An incompatible mix of legacy and non-legacy
functions was called (or usb_tc08_get_single
was called while in streaming mode.)
6 USBTC08_ERROR_ENUMERATION_INCOMPLETE usb_tc08_open_unit_async was called
again while a background enumeration
was already in progress.
"""
return _usb_tc08_get_last_error(handle)
_usb_tc08_set_mains = usbtc08.usb_tc08_set_mains
_usb_tc08_set_mains.restype = ct.c_short
_usb_tc08_set_mains.argtypes = (ct.c_short, ct.c_short)
def usb_tc08_set_mains(handle=1, sixty_hertz=1):
"""Sets the USB TC-08 to reject either 50 or 60 Hz [noise].
handle - the USB TC08 unit.
sixty_hertz - Specifies whether to reject 50 Hz or 60 Hz.
If set to 1, the unit will reject 60 Hz,
If set to 0, the unit will reject 50 Hz.
Returns:
0 if an error occurred. Use usb_tc08_get_last_error
1 if mains rejection was set
"""
return _usb_tc08_set_mains(handle, sixty_hertz)
_usb_tc08_get_minimum_interval_ms = usbtc08.usb_tc08_get_minimum_interval_ms
_usb_tc08_get_minimum_interval_ms.restype = ct.c_long
_usb_tc08_get_minimum_interval_ms.argtypes = (ct.c_short,)
def usb_tc08_get_minimum_interval_ms(handle=1):
"""Returns the minimum sampling interval (or fastest millisecond
interval) that the unit can achieve in its current configuration.
The configuration is defined by calling usb_tc08_set_channel().
Note: The USB TC-08 can sample, from a single channel, at a rate of 10
samples per second. The absolute minimum sampling interval,
with all 8 channels and the cold junction enabled, is 900 ms.
You must set up all the channels that you wish to use before
calling this routine.
handle - the USB TC08 unit.
Returns:
0 if an error occurred. Use usb_tc08_get_last_error
minimum sampling interval otherwise
"""
return _usb_tc08_get_minimum_interval_ms(handle)
class Testusbtc08(unittest.TestCase):
"""Unit tests that require USB TC08 hardware to be plugged in."""
def setUp(self):
self.openunits = []
def tearDown(self):
for handle in self.openunits:
usb_tc08_close_unit(handle)
def testopenunit(self):
while True:
handle = usb_tc08_open_unit()
if handle > 0:
self.openunits.append(handle)
else:
break
# Assert no errors
self.assertNotEqual(-1, handle, str(usb_tc08_get_last_error(0)))
# Assert that at least 1 TC08 was opened
self.assertTrue(self.openunits, 'No units opened. Check TC08 is plugged in.')
def testgettemp(self):
"""Tests single temperature retrieval."""
handle = usb_tc08_open_unit()
self.assertTrue(handle)
self.openunits.append(handle)
# Set channel 1
channelok = usb_tc08_set_channel(handle)
self.assertTrue(channelok)
# Read channel 1
ok, temps = usb_tc08_get_single(handle)
self.assertTrue(ok)
# Assert that the temperature is within expected limits in degrees C
MIN_TEMP = 17
MAX_TEMP = 25
self.assertTrue(MIN_TEMP < temps[1] < MAX_TEMP,
'Temperature should be %f to %f but is %f' % (MIN_TEMP, MAX_TEMP, temps[1]))
if __name__ == "__main__":
"""Runs the given unit tests.
Command line examples:
%python usbtc08.py Testusbtc08 # Runs Testusbtc08 test fixture
%python usbtc08.py # Runs all unit tests
"""
unittest.main()
Run the following program to use the library above. Perhaps save this code as tc08simpleread.py
#!/usr/bin/env python
"""Print one temperature reading from channel 1 of the first USBTC08 found."""
import usbtc08
def main():
# Open the first TC08 unit found
handle = usbtc08.usb_tc08_open_unit()
print 'Opened unit %d' % handle
# Set channel 1
usbtc08.usb_tc08_set_channel(handle, 1)
# Read all channels
ok, temps = usbtc08.usb_tc08_get_single(handle)
# Print results
for channel, temp in enumerate(temps):
if temp:
print 'Unit %d channel %d temp is %s degC' % (handle, channel, temp)
# Close the unit
usbtc08.usb_tc08_close_unit(handle)
print 'Closed unit %d' % handle
if __name__ == "__main__":
main()
usb_tc08_get_minimum_interval_ms()