Launching iButton Thermochrons with the help of R

Maxim’s DS1921G iButton Thermochron temperature dataloggers are little silver doo-dads the size of a large watch battery that can record up to 2048 time-stamped temperature values. The internal battery is usually good for a few years of use. Maxim supplies a Java-based application for talking to iButtons to start recording or to download results. This program, coupled with a USB-based iButton adapter, works fine when you’re just dealing with a few iButtons. But I have more than a few iButtons, so I used R to write a script to launch multiple iButtons quickly.

An iButton (center) flanked by the DS9490B USB adapter and DS1402D-DR8 “blue-dot” iButton reader. Ignore the telephone wall jack in the upper left, that is just my solution for having bought the DS9490B instead of the DS9490R. You should buy the DS9490R.

In my case, I want to launch 200-300 iButtons at a go, and I want them to all start recording at the same time. This would take hours using the Java applet, since you have to click your preferred settings for every new iButton, and you need to do the math on how long to delay the start so that the iButtons sync up. Fortunately, Maxim also makes a software development kit freely available for iButtons, and they also supply pre-compiled binary versions of the various utilities for talking to iButtons (i.e. command-line tools). Below I’ve written some R code to use one of these pre-compiled .exe’s to repeatedly launch many iButtons using identical mission parameters and equivalent starting times. R isn’t necessarily the best tool for this sort of thing, but it certainly works well enough.

To start with, I used the USB drivers for my Windows 7 64-bit computer so it would talk to my DS9490B USB adapter for the iButtons. The instructions for getting this working are provided by Maxim Integrated here:

https://www.maximintegrated.com/en/app-notes/index.mvp/id/4373

and the link to download the actual drivers and OneWireViewer is here:

https://www.maximintegrated.com/en/products/ibutton/software/tmex/download_drivers.cfm

After installing the drivers for the USB adapter, I ensured that it was working and could talk to iButtons using the Java OneWireViewer applet.

Then I downloaded the 1-Wire Public Domain software development kit, in this case the pre-compiled binary version for Win64 USB Preliminary Version 3.11 Beta 2 that came as a zip file called winusb64vc311Beta2_r2.zip (downloaded 2012-03-15).

http://www.maxim-ic.com/products/ibutton/software/1wire/wirekit.cfm

There are several different potential versions of the kit listed on that page, and the only one that I have had success with thus far is the specific version named above. Scroll down near the bottom of that page to find the correct link, or use this direct link. It works with the WinUSB drivers, while other versions that work with libusb haven’t given me any success. If you’re running 32-bit Windows, you might try the version winusb32vc311Beta2_r2.zip.

Inside the winusb64vc311Beta2_r2.zip file, I found the necessary files thermoms.exe (for launching missions) and thermodl.exe (for downloading data, not covered here) in the \builds\winusb64vc\release folder. I copied these two .exe’s to the directory that I use as my R working directory, so that I could call them from within my R script using the system() function. You probably don’t keep your code in D:/R/ibuttons, so you should ignore the setwd("D:/R/ibuttons") call in the script below.

The thermoms.exe program is called from within R using R’s system() function. This function takes a string as the first argument, and passes this string to the OS command line. In this case I send thermoms.exe ds2490-0, exactly as I would type it if I was using thermoms.exe at the Windows command line. The ds2490-0 instructs thermoms.exe to look for a ds2490 USB adapter on the system, and the -0 says to use the first one it finds (this should work for you if you only have one USB adapter). The answers to the questions that thermoms.exe usually asks are stored in the mission.params character vector and supplied as part of the system() call. This syntax will only work on Windows. Linux/OSX will need different command line tools and syntax, and thus this script won’t work on Linux/OSX. Versions of the scripts below can be downloaded from my GitHub repository.

# The thermoms.exe program expects a series of inputs in order to establish the
# mission parameters. Rgui doesn't work all that well with interactive command 
# line programs (Rterm is just fine, but the goal is to not have to interact), 
# so instead we'll create a character vector of answers to thermoms's 
# queries and supply those via the input option of the system() function. 
# The parameters are as follows:
# Erase current mission (0) yes, (1) no. Answer: 0
# Enter start delay in minutes (0 to 65535). Answer: whatever you choose
# Enter sample rate in minutes (1 to 255). Answer: whatever you choose
# Enable roll-over (0) yes, (1) no. Answer: 1
# Enter high temperature threshold in Celsius (-40 to 70). Answer: 70
# Enter low temperature threshold in Celsius (-40 to 70). Answer: -40

mission.params = c('0', # erase current mission 
		'0', # start delay in minutes (0 to 65535)
		'255', # sample rate in minutes (1 to 255)
		'1', # 0 = enable roll-over, 1 = no roll over
		'70', # high temperature threshold
		'-40') # low temperature threshold

# Launch thermoms.exe
# The 1st argument supplied to thermoms needs to be the location of the ibutton 
# in the system. If using a DS9490B or DS9490R USB reader, you will probably get
# away with using ds2490-0. The DS9490 uses a ds2490 chip internally, hence the 
# given name.
out = system('thermoms.exe ds2490-0', intern = TRUE, wait = TRUE,
		input = mission.params)

# Display the output from the mission launch to show the user that launch was 
# successful. 
for (i in 73:90) {
	cat(out[i],'\n')
}
cat('Finished\a\n')

Created by Pretty R at inside-R.org

The script above is extremely basic, and just launches one iButton when executed. In order to launch multiple iButtons, I wrote the version below (link to GitHub repository) with a loop to send the same parameters to each new iButton that I plug in. The script below starts by asking the user for a time delay value, which can be up to 45.5 days in the future. The time delay must be entered as YYYY-mm-dd HH:MM, or the user can simply enter 0 to have the iButtons start recording immediately. If the user enters a non-zero time in the future, the script automatically adjusts the iButton start delay so that all iButtons will start at the user-specified time and date as time elapses while swapping iButtons in and out of the reader. The sampling frequency is also taken as an input, but all other mission parameters are hard-coded to my preferences.

There are also some error-handling routines, since the mission doesn’t always upload correctly on the first try. The results of each mission launch are returned to R, and parsed to make sure they’re set as desired. The results are shown at the command line so the user can check them directly. If you want to launch another iButton, simply unplug the current iButton, plug in a new one to the reader, and then hit Enter to repeat the mission-launch loop. This process can be repeated endlessly until the user enters “q” and hits Enter to quit the script.

# Filename: mission_launch_multi.R
# A script to upload new missions to iButton Thermochron temperature 
# dataloggers. See the comments below for the additional external software 
# needed to make this run. This version will loop continuously and upload the 
# same mission to multiple iButtons so that they all start at the same 
# absolute time and sample at the same frequency. 
# Author: Luke Miller Mar 23, 2012
###############################################################################
setwd('D:/R/ibuttons') # Alter this for your needs, thermoms.exe must be in the
# current R working directory.

# The thermoms.exe file was originally downloaded as part of the Maxim iButton 
# 1-Wire Public Domain Kit. 
# There are several versions of the Kit available, including
# versions with pre-compiled binaries (executables) for Windows/Linux/OSX.
# http://www.maxim-ic.com/products/ibutton/software/1wire/wirekit.cfm
# On my Windows 7 x64 computer using the DS9490B USB ibutton adapter, I used the
# precompiled binary build for Win64 USB (DS9490 + WinUSB) Preliminary Version 
# 3.11 Beta 2,
# filename: winusb64vc311Beta2_r2.zip, downloaded 2012-03-15
# Unzip this file and find the .exe files thermoms.exe (and thermodl.exe) in the
# builds\winusb64vc\release folder. Copy these to your R working directory.
# The drivers for the DS9490 USB iButton adapter must also be downloaded and 
# installed: 
# http://www.maxim-ic.com/products/ibutton/software/tmex/
# I downloaded and installed the file "install_1_wire_drivers_x64_v403.msi"
# The Java OneWireViewer app can also be downloaded and used to verify that your
# drivers work and that you can communicate with iButtons successfully through 
# the USB adapter. You can download this app here: 
# http://www.maxim-ic.com/products/ibutton/software/1wire/OneWireViewer.cfm

cat('Enter a desired mission start time YYYY-MM-DD HH:MM\n')
cat('Enter 0 for immediate start. Delay must be less than 45.5 days.\n')
time.delay = scan(file = '', what = character(), n = 1, sep = ',')
# The sep value in scan is necessary so that spaces are not interpreted as the
# default record delimiter.

if (time.delay == '0') {
	launch = TRUE
} else {
	# Convert the date/time into a POSIX time object
	time.delay = as.POSIXct(strptime(time.delay, format = '%Y-%m-%d %H:%M'))

	# Check to make sure that the delay time is usable
	if (is.na(time.delay)) { # If time.delay can't be interpreted, fail 
		cat('Time could not be interpreted\a\n')
		cat('Quitting now\n')
		launch = FALSE
	}
	# If time.delay is a valid POSIX time, check that it is within limits
	if (!is.na(time.delay)) {
		curr.time = as.POSIXct(Sys.time()) # get current time
		# Check time difference between user's delay and current computer time
		t.diff = as.numeric(time.delay) - as.numeric(curr.time)
		t.diff = t.diff / 60 # convert to minutes

		if (t.diff < 0) {
			cat('Time delay is less than zero. Check your watch.\a\n')
			cat('Quitting now\n')
			launch = FALSE
		} else if (t.diff > 65535) {
			cat('Time delay is longer than 45.5 days. You are bad at math.\a\n')
			cat('Quitting now\n')
			launch = FALSE
		} else if (t.diff > 0 & t.diff < 1) {
			cat('Time delay is being set to 0 for immediate launch.\a\n')
			launch = TRUE
		} else {
			# time.delay is a readable time, and t.diff is between 0 and 65535
			launch = TRUE
		}
	} # end of !is.na(time.delay) if-statement
} # end of time.delay if-statement

################################################################################
## Start the launch loop. 

if (launch) { #only do this part if launch == TRUE
	cat('Enter the desired sampling frequency in minutes (1 to 255):\n')
	freq = scan(file = '', what = numeric(), n = 1)
	freq = as.character(freq) # convert to character
	loop = TRUE # Set loop continuation variable to TRUE initially

	# This while loop will repeat continuously to launch multiple iButtons.
	# The same parameters will be used to launch every iButton, except that the 
	# start delay (if >0) will automatically adjust as time elapses so that each
	# iButton will start at the same absolute time. 
	while (loop) {

		if (as.character(time.delay) != '0') {
			curr.time = as.POSIXct(Sys.time()) # Get current time
			# Calculate difference between current time and time.delay value
			time.diff = as.numeric(time.delay) - as.numeric(curr.time)
			time.diff = time.diff / 60 # convert from seconds to minutes
			time.diff = floor(time.diff) # round down to nearest minute
			# iButtons only read out to the nearest minute. Rounding down to the
			# nearest minute should produce the proper delay to start at the 
			# desired time. 

			# If too much time elapses during this script, the time difference 
			# could eventually shrink to zero or less than zero. In that case, 
			# warn the user and set the iButton for immediate launch. 
			if (time.diff < 1) {
				time.diff = 0 # set for immediate launch.
				cat('*********************************\n')
				cat('Delay has shrunk to zero. Setting for immediate launch.')
				cat('*********************************\a\n')
			}
			time.diff = as.character(time.diff) # convert to character 
		} else { # time.delay was originally set to 0, so keep that.
			time.diff == '0'
		}
		cat('Calculated delay: ', time.diff, ' minutes\n')
# The thermoms.exe program expects a series of inputs in order to establish the
# mission parameters. Rgui doesn't work all that well with interactive command 
# line programs (Rterm is just fine, but our goal is to not have to interact), 
# so instead we'll create a character vector of answers to thermoms.exe's 
# queries and supply those via the input option of the system() function. 
# The parameters are as follows:
# Erase current mission (0) yes, (1) no. Answer: 0
# Enter start delay in minutes (0 to 65535). Answer: whatever you choose
# Enter sample rate in minutes (1 to 255). Answer: whatever you choose
# Enable roll-over (0) yes, (1) no. Answer: 1
# Enter high temperature threshold in Celsius (-40 to 70). Answer: 70
# Enter low temperature threshold in Celsius (-40 to 70). Answer: -40

		mission.params = c('0', # erase current mission 
				time.diff, # start delay in minutes (0 to 65535)
				freq, # sample rate in minutes (1 to 255)
				'1', # enable roll-over? 1 = no roll over
				'70', # high temperature threshold
				'-40') # low temperature threshold

# Launch thermoms.exe
# The 1st argument supplied to thermoms needs to be the location of the iButton 
# in the system. If using a DS9490B USB reader on Windows, you will probably get 
# away with using ds2490-0. The DS9490 USB reader uses a ds2490 chip internally, 
# so you need to tell thermoms.exe to look for a ds2490. 
		out = system('thermoms.exe ds2490-0', intern = TRUE, wait = TRUE,
				input = mission.params)

# Check the output from the mission launch to ensure that the correct parameters
# were registered. Occasionally the time delay will not be properly registered 
# on the first launch, so the loops below will immediately relaunch the mission
# to get the time delay to register properly.

		# If no iButton is plugged in, this should be the failure message
		if (out[7] == 'Thermochron not present on 1-Wire') {
			cat('******************************************\n')
			cat(out[7],'\n')
			cat('******************************************\n\a')
		} else { # if out[7] is blank, a mission was probably uploaded
			for (i in 73:90) { # Display read-back from mission upload
				cat(out[i],'\n')
			}

			# Make sure the delay was actually entered correctly if it's >0
			# This while loop will run a maximum of 3 times. Each time through 
			# it will compare the output in out[73] to make sure the correct 
			# delay was returned from the iButton. If not, it will re-launch the
			# mission up to two more times before sending a failure message
			retry = 0
			while (retry < 3) {
				setting = out[79]
				nums = gregexpr('[0-9]', setting) # Find numbers in the string
				digs = substr(setting, nums[[1]][1],
						nums[[1]][length(nums[[1]])]) # Extract delay value
				if (digs != time.diff & retry < 2) {
					# If delay value returned by iButton doesn't match the 
					# programmed delay, warn the user and re-launch the mission
					cat('*****************************************\n')
					cat('****Launch did not work, re-launching****\a\n')
					cat('*****************************************\n')
					out = system('thermoms.exe ds2490-0', intern = TRUE,
							wait = TRUE, input = mission.params)
					for (i in 73:90) { # Display the newly returned values
						cat(out[i],'\n')
					}
					retry = retry + 1 # increment the loop counter
					cat('---------------------------\n')
				} else if (digs != time.diff & retry == 2) {
					# If the returned delay still doesn't match the programmed
					# delay after two more iterations, send a failure message
					# to the user and let them deal with this issue. 
					# A common failure mode is due to a dead battery, which will
					# keep returning a clock time of 01/01/1970 00:00:00
					retry = 3
					cat('****************************************\n')
					cat('*****Uploaded failed, check iButton*****\n')
					cat('****************************************\n')
					for (i in 73:90) {
						cat(out[i],'\n')
					}

					answer = out[85] # Check the iButton's internal clock

					# Find the location of the date in this line (if present)
					d1 = regexpr('[0-9]{2}/[0-9]{2}/[0-9]{4}', answer)

					# Extract the date as a character string
					button.date = substr(answer, d1,
							d1 + attr(d1,'match.length') - 1)

					# If the iButton date returns 01/01/1970, the iButton 
					# battery is probably dead
					if (button.date == '01/01/1970') {
						cat('********************************\n')
						cat('The iButton battery may be dead.\n')
						cat('********************************\n')
					}

				} else if (digs == time.diff) { # iButton mission launch worked
					cat('\n----------Success---------\n')
					retry = 3
				}
			} # End of retry while-loop
		} # End of if (out[7]... if-else statements

		# Ask the user to load the next iButton or quit.
		cat('Swap next iButton and press Enter to launch. Enter q to quit.\a\n')
		user.input = scan(file = '', what = character(), n = 1)
		if (length(user.input) > 0) { # Allows user to not type anything
			if (user.input == 'q') loop = FALSE # Kills loop
		} else {loop = TRUE}
	} # End of 'loop' while-loop
} # End of 'launch' if-statement

cat('Finished \a\n')

Created by Pretty R at inside-R.org

The output from a typical launch cycle looks like the following:

Calculated delay:  42  minutes
Serial Number of DS1921: 4B0000002D1FCB21
Mission is in progress
Sample rate: 5 minute(s)
Roll-Over Enabled: no
Roll-Over Occurred: no
Mission Start time: na
Mission Start delay: 42 minute(s)
Mission Samples: 0
Device total samples: 1990
Temp displayed in: (Celsius)
High Threshold:   70.0
Low Threshold:  -40.0
Current Real-Time Clock from DS1921: 03/23/2012  11:47:52
Current PC Time: 03/23/2012  11:47:51 

Closing port DS2490-0.
End program normally
///////Success///////
Swap next iButton and press Enter to launch. Enter q to quit.
1:

The upshot of all of this is that I can now launch about 300 iButtons in roughly 20 minutes, and the limiting factor is how fast I can pop the buttons in and out of the reader (the repetitive stress injuries I’ll surely incur will slow things down). This script only handles launching a single iButton at a time. The blue-dot reader can hold two iButtons, and thermoms.exe will launch both simultaneously, but this script only looks at the results from the first iButton, so it could miss errors on the second iButton.

 

Updated 2015-10-07 with new working links to Maxim.