Using Dracal sensors from almost any programming language

1) Prerequisites

  • The usbtenkiget command-line tool (see where to get it below)
  • Basic command-line usage knowledge

Windows users: Where to get usbtenkiget.exe

The usbtenkiget command-line tool is included in QTenki. Under Windows, simply install QTenki. After installation, navigate to the installation directory (typically, this will be "c:program filesQTenki" or "c:program files (x86)QTenki"). The file usbtenkiget.exe will be found in this location.

Linux users: Compile usbtenkiget

The usbtenkiget command-line tool must be compiled from sources. Follow the instructions found on the Using Dracal sensors under Linux page.

2) Using usbtenkiget

You will find hereafter a summary of the basic commands available with our command line tool usbtenkiget. For more in-depth content of the tool, please visit the usbtenkiget documentation. As for most command-line tool, help information can be displayed using the -h option, please feel free to have a look. Most options are easy to understand so we will not review them here. But the two most important options are demonstrated below.

Listing sensors and channels

The currently connected USB sensors can be listed using the -l command as below:

usbtenkiget -l
Found: 'USBTenki', Serial: 'B10004', Version 1.2, Channels: 11
    Channel 0: Sensirion SHT1x/7x Temperature [Temperature]
    Channel 1: Sensirion SHT1x/7x Relative Humidity [Relative Humidity]
    Channel 2: MPX4115 Absolute air pressure sensor [Pressure]
    Virtual Channel 256: Dew point [Dew point]
    Virtual Channel 257: Humidex [Humidex]
    Virtual Channel 258: Heat index [Heat index]

Only one sensor (Dracal PTH-01-RH) with serial number B10004 was connected when the above command was executed. This sensor has 3 real channels (Measured data) and 3 virtual channels (Values computed from the measured data). Each channel, real or virtual, has an ID that can be used to query its value.

Note: Under Linux, if no sensors are found, a possible reason might be that the user you are running the tool as does not have the required access rights. Please consult the configuring access permissions section of the Using Dracal sensors under Linux page for more information.

Fetching values

The value for a channel is retrieved by passing the channel IDs to the -i argument. When more than one channel is required, IDs are separated by commas as below. The returned values are also separated by commas and are outputted in the order they were requested:

usbtenkiget -i 0,1,256
22.46, 39.55, 8.02

The above output is what your program will have to interpret. If you are just displaying the values, try -p (pretty) for a human-friendly output with units. Speaking of units, have a look to the help page (-h option) and the usbtenkiget documentation for how to change them.

Using multiple sensors

The command demonstrated above works fine if you have only a single sensor connected. But if you have several sensors connected, only the values from the first sensor the tool finds will be returned.

Values from a specific sensor can be read by specifying the serial number on the command-line using the -s option, as such:

usbtenkiget -i 0,1,256 -s B10004
22.46, 39.55, 8.02

This covers the usbtenkiget usage basics.

3) Examples in different programming languages

3.1) Bash

Usbtenkiget may be executed from bash like any other command, provided it is in the path. There are many ways to process the output of usbtenkiget. Here is an example that uses the cut tool to sepearate fields, and then uses bc to compare values. (Bash only does integer math).

#!/bin/bash

# Note: usbtenkiget assumed to be in path.
# You may need to use -s to read from a specific sensor if you have more than one.
VALUES=`usbtenkiget -i a`
if (( $? )); then
	echo "Sensor not found or error"
fi

# Use cut, specifying a comma delimiter, and requesting
# field 1 for temperature, field 2 for RH...
TEMPERATURE=`echo $VALUES | cut -d ',' -f 1`
RELATIVE_HUMIDITY=`echo $VALUES | cut -d ',' -f 2`
PRESSURE=`echo $VALUES | cut -d ',' -f 3`

# Display the separate values now stored in variables
echo "Temperature: $TEMPERATURE"
echo "RH: $RELATIVE_HUMIDITY"
echo "Pressure: $PRESSURE"

# Example using bc to check how temperature compares to 29.5
if (( $(echo "$TEMPERATURE > 29.5" | bc -l) )); then
	echo "Too hot!"
else
	echo "Comfortable"
fi

3.2) C (POSIX)

On operating systems where it is available, the popen function can be used to execute usbtenkiget and read its output as a stream (FILE*). Then fscanf can be used read the values and store them in variables.

#include <stdio.h>
int getTempAndHumidity(float *temp, float *hum)
{
	FILE *fptr;
	float t, h;
	int n, res;

	fptr = popen("usbtenkiget -i a", "r");
	if (!fptr) {
		perror("popen");
		return -1;
	}

	n = fscanf(fptr, "%f, %f", &t, &h);
	res = pclose(fptr);

	if (res==-1) { return -2; }
	if (n<2) { return -3; }

	if (temp) { *temp = t; }
	if (hum) { *hum = h; }

	return 0;
}

int main(void)
{
	float temperature, humidity;
	int res;

	res = getTempAndHumidity(&temperature, &humidity);
	if (res<0) {
		return res;
	}

	printf("Temperature: %.2fnHumidity: %.2fn", temperature, humidity);

	return 0;
}

3.3) C/C++ (Win32)

If using the Win32 API, usbtenkiget can be executed using the CreateProcess function. The output of the child process (usbtenkiget) is redirected to a pipe. The parent (the example code) reads from the pipe to receive the output of usbtenkiget, storing it in a buffer for processing and conversion to floating point values.

// win32Example.cpp
//
// Based on "Creating a Child Process with Redirected Input and Output"[1] from
// Microsoft Docs, but heavily modified and simplified for this specific use case.
//
// [1] https://docs.microsoft.com/en-us/windows/desktop/procthread/creating-a-child-process-with-redirected-input-and-output
//
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <strsafe.h>
#include <math.h>

#define BUFSIZE 4096

/* Execute usbtenkiget and return values, converted to float. Nan is used
 * for signaling illegal values (some sensors may return "err" or Nan when
 * a low level error occurs.
 *
 * param cmdline Pointer to a string for the command to execute. Eg: TEXT("usbtenkiget -i 1")
 * param values Pointer to float*. A properly sized array of floats will be allocated with malloc.
                 Must be freed using free() by the caller.
 * param n_values Pointer to an int where the number of received fields will be stored.
 * return false on error, true on success. values does not need to be freed if an error was returned.
 */
BOOL getUsbTenkiValues(const TCHAR *cmdline, float **values, int *n_values)
{
	PROCESS_INFORMATION piProcInfo;
	STARTUPINFO siStartInfo;
	BOOL bSuccess = FALSE;
	HANDLE g_hChildStd_OUT_Rd = NULL;
	HANDLE g_hChildStd_OUT_Wr = NULL;
	SECURITY_ATTRIBUTES saAttr;
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
	saAttr.bInheritHandle = TRUE;
	saAttr.lpSecurityDescriptor = NULL;
	CHAR chBuf[BUFSIZE];
	TCHAR *cmdline_copy = NULL;

	if (!values || !n_values) {
		return false;
	}

	// Create a pipe for the child process's STDOUT.
	if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) {
		fprintf(stderr, "Could not create pipen");
		return false;
	}

	// Set up members of the PROCESS_INFORMATION structure.
	ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
	ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
	siStartInfo.cb = sizeof(STARTUPINFO);
	siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
	siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

	/* Create a copy of the command-line passed in argument. CreateProcessW
	 * may modify the string, so we must be sure the string passed to CreateProcess
	 * is NOT in read-only memory (such as a const variable or a literal string) */
	cmdline_copy = _tcsdup(cmdline);
	if (!cmdline_copy) {
		fprintf(stderr, "Could not allocate memory");
		CloseHandle(g_hChildStd_OUT_Wr);
		CloseHandle(g_hChildStd_OUT_Rd);
		return false;
	}

	// Create the child process.
	bSuccess = CreateProcess(NULL,
		cmdline_copy,  // command line
		NULL,          // process security attributes
		NULL,          // primary thread security attributes
		TRUE,          // handles are inherited
		0,             // creation flags
		NULL,          // use parent's environment
		NULL,          // use parent's current directory
		&siStartInfo,  // STARTUPINFO pointer
		&piProcInfo);  // receives PROCESS_INFORMATION

	// Free the command-line copy as it won't be needed anymore
	free(cmdline_copy);

	// Close the writing end of the pipe now that the child has inherited it. Otherwise
	// the read loop below will never stop.
	CloseHandle(g_hChildStd_OUT_Wr);

	if (!bSuccess) {
		CloseHandle(g_hChildStd_OUT_Rd);
		fprintf(stderr, "Could not run usbtenkigetn");
		return false;
	}

	// Wait until usbtenkiget exits
	WaitForSingleObject(piProcInfo.hProcess, INFINITE);

	// Close handles to the child process
	CloseHandle(piProcInfo.hProcess);
	CloseHandle(piProcInfo.hThread);

	// Now read the output that was buffered in the pipe. Read at most
	// BUFSIZE-1 to be sure we always get a NUL-terminated string.
	ZeroMemory(chBuf, sizeof(chBuf));
	bSuccess = ReadFile(g_hChildStd_OUT_Rd, chBuf, BUFSIZE-1, NULL, NULL);

	// Close the read end of the pipe too, now that we are done.
	CloseHandle(g_hChildStd_OUT_Rd);

	if (!bSuccess) {
		fprintf(stderr, "Could not read from usbtenkigetn");
		return false;
	}

	/* Now we have comma-separated values in chBuf. Count how many fields. */
	int fields = 1;
	for (char *c = chBuf; *c; c++) {
		if (*c == ',')
			fields++;
	}

	/* Allocate memory for the array of floats, and convert fields to floats. */
	*n_values = fields;
	*values = (float*)calloc(fields, sizeof(float));
	if (!*values) {
		fprintf(stderr, "Could not allocate memory for array of valuesn");
		return false;
	}
	float *dst_value = *values;
	char *c = chBuf;
	for (char *c = chBuf; c; )
	{
		if (1 != sscanf_s(c, "%f", dst_value)) {
			*dst_value = nanf("");
		}
		dst_value++;
		c = strchr(c, ',');
		// skip , for next value, otherwise c is NULL and loop stops
		if (c)
			c++;
	}

	return true;
}

int main()
{
	BOOL bSuccess;
	float *values = NULL;
	int n_values;

	bSuccess = getUsbTenkiValues(TEXT("usbtenkiget -i 0,1,2"), &values, &n_values);
	if (!bSuccess) {
		return -1;
	}

	if (bSuccess) {
		// We run usbtenkiget with the -i 0,1,2 argument which requests
		// exactly 3 channels. Getting any other quantity is therefore an error.
		if (n_values != 3) {
			fprintf(stderr, "usbtenkiget returned wrong number of fieldsn");
			return -1;
		}
		else {
			printf("Temperature (C): %.2fn", values[0]);
			printf("RH......... (%%): %.2fn", values[1]);
			printf("Pressure. (kPa): %.2fn", values[2]);
			printf("Temperature (F): %.2fn", values[0] * 9 / 5 + 32);
		}
	}

	if (values) {
		free(values);
	}

	return 0;
}

3.4) C#

In C# with the .Net framework, usbtenkiget can be executed by the Process class. The output from usbtenkiget is redirected and converted to a string using ReadLine(). An array of individual values can then be built by splitting the string and converting the values to floats.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DracalCSexample
{
    class Program
    {
        static void Main(string[] args)
        {
            Process usbtenki = new Process();
            usbtenki.StartInfo.FileName = "usbtenkiget";
            usbtenki.StartInfo.Arguments = "-i 0,1,2";
            usbtenki.StartInfo.UseShellExecute = false;
            usbtenki.StartInfo.RedirectStandardOutput = true;

            try
            {
                usbtenki.Start();
            }
            catch (Exception e)
            {
                Console.Error.WriteLine("could not run usbtenkiget: " + e);
                return;
            }

            usbtenki.WaitForExit();

            String output = usbtenki.StandardOutput.ReadLine();

            if (output == null)
            {
                Console.Error.WriteLine("usbtenkiget did not return data");
                return;
            }

            float[] fields = output.Split(',').Select(field => float.Parse(field)).ToArray();

            if (fields.Length != 3)
            {
                Console.Error.WriteLine("usbtenkiget returned an incorrect number of fields");
                return;
            }

            Console.WriteLine("Temperature (C): " + fields[0]);
            Console.WriteLine("RH......... (%): " + fields[1]);
            Console.WriteLine("Pressure. (kPa): " + fields[2]);
            Console.WriteLine("Temperature (F): " + (fields[0] * 9 / 5 + 32));
        }
    }
}

A C# example where usbtenkiget keeps running (using log mode) instead of being restarted for each sample is also available: usingUsbtenkiRunMode.cs

3.5) C++/CLI (.NET)

In C++/CLI targeting the .NET Framework, usbtenkiget can be executed by the Process class. The output from usbtenkiget is redirected and converted to a String using ReadLine(). An array of individual values can then be built by splitting the string and converting the values to doubles.

#include "stdafx.h"
#include 

using namespace System;
using namespace System::Diagnostics; // for Process

int main(array ^args)
{
	Process^ usbtenki = gcnew Process;

	usbtenki->StartInfo->FileName = "usbtenkiget";
	usbtenki->StartInfo->Arguments = "-i 0,1,2";
	usbtenki->StartInfo->UseShellExecute = false;
	usbtenki->StartInfo->RedirectStandardOutput = true;

	try {
		usbtenki->Start();
	}
	catch (Exception^ e) {
		Console::Error->WriteLine("could not start usbtenkiget: " + e);
		return 1;
	}

	usbtenki->WaitForExit();

	String^ output = usbtenki->StandardOutput->ReadLine();
	if (!output) {
		Console::Error->WriteLine("usbtenkiget did not return data");
		return 1;
	}

	array<String^>^ fields = output->Split(',');
	array^ values = gcnew array(fields->Length);

	int i = 0;
	for each (String^ str in fields) {
		if (!System::Double::TryParse(str, values[i])) {
			values[i] = std::numeric_limits::quiet_NaN();
		}
		i++;
	}

	Console::WriteLine("Temperature (C): " + values[0]);
	Console::WriteLine("RH..........(%): " + values[1]);
	Console::WriteLine("Pressure..(kPa): " + values[2]);
	Console::WriteLine("Temperature (F): " + (values[0] * 9 / 5 + 32));

    return 0;
}

3.6) Java

import java.io.*;

public class JavaExample
{
	public static void main(String[] args)
	{
		Process usbtenkiget;

		// Note: usbtenkiget assumed to be in the path
		// Arguments passed to -i (0,1,2) here need to be updated to fit
		// your scenario. You may also specify a serial number by adding
		// the -s argument.
		try {
			usbtenkiget = Runtime.getRuntime().exec("usbtenkiget -i 0,1,2");
		} catch(IOException e) {
			System.err.println("could not run usbtenkiget: " + e);
			return;
		}

		// Wait until the process exits
		while(true) {
			try {
				usbtenkiget.waitFor();
			} catch (InterruptedException e) {
				continue;
			}
			break;
		}

		// Check if usbtenkiget exited with an error code
		if (usbtenkiget.exitValue() != 0) {
			System.err.println("usbtenkiget error. Exit value=" + usbtenkiget.exitValue());
			return;
		}

		BufferedReader reader = new BufferedReader(new InputStreamReader(usbtenkiget.getInputStream()));

		// usbtenkiget outputs the data on the first line. Read it to a string.
		String line;
		try {
			line = reader.readLine();
		} catch (IOException e) {
			System.err.println("Error reading data: " + e);
			return;
		}

		// Now split the line in an array of values.
		String[] values = line.split(",");

		// Check that we received the expected number of fields (in this case,
		// the usbtenkiget -i 0,1,2 argument requests 3 fields).
		if (values.length != 3) {
			System.err.println("Incorrect number of fields received: " + values.length);
			return;
		}

		float temperature = Float.parseFloat(values[0]);
		float rh = Float.parseFloat(values[1]);
		float pressure = Float.parseFloat(values[2]);

		System.out.println("Temperature (C):" + temperature);
		System.out.println("RH......... (%):" + rh);
		System.out.println("Pressure..(kPa):" + pressure);
		System.out.println("Temperature (C):" + (temperature*9/5+32));
	}
}

3.7) Node.js

With Node, usbtenkiget can be executed by execFile. When execution completes, the output from usbtenkiget is returned as a string which may then be cleaned up and split in separate fields.

const execFile = require('child_process').execFile;

const child = execFile('usbtenkiget', ['-i','0,1,2'], (error, stdout, stderr) => {
	if (error) {
		throw error;
	}

	// Remove everything following the first newline character, then
	// split using a comma separator, then trim each field.
	var fields = stdout.replace(/(rn|n|r)*/gm,"").split(",").map(s => s.trim());

	// Validate how many fields were read
	if (fields.length != 3) {
		throw "Wrong number of fields"
	}

	// Display individual values
	console.log("Temperature.(C): " + fields[0]);
	console.log("RH..........(%): " + fields[1]);
	console.log("Pressure..(kPa): " + fields[2]);
	console.log("Temperature.(F): " + (fields[0] * 9 / 5 + 32));

});

3.8) Python

In Python, usbtenkiget can be executed using subprocess.check_output. Splitting the line in an array of text fields can be accomplished by calling split, and the values can be converted to floats by calling float() on the fields.

#!/usr/bin/python
import sys,import subprocess

# Note: usbtenkiget assumed to be in the path
# Arguments passed to -i (0,1,2) here need to be updated to fit
# your scenario. You may also specify a serial number by adding
# the -s argument.

# If usbtenkiget exits with a non-zero values, the subprocess.CalledProcessError
# exception will be raised. Catch it.
try:
    p = subprocess.check_output(["usbtenkiget","-i","0,1,2"])
except subprocess.CalledProcessError:
    print "usbtenkiget error"
    sys.exit(1)

fields = p.split(",");

# This example expects the following output:
#
# 24.48, 61.56, 100.40
#
# Where fields are temperature, rh and pressure.
#
# Detect errors by checking if the exact expected number
# fields was returned.
if len(fields) < 3:
    print "Error reading sensor"
    sys.exit(2)

# Convert the fields from strings to floating point values
# This step is necessary, otherwise math on values will not
# be possible.
temperature = float(fields[0])
rh = float(fields[1])
pressure = float(fields[2])

# Display values
print "Temperature (C):", temperature
print "RH......... (%):", rh
print "Pressure..(kPa):", pressure
print "Temperature (F):",temperature*9/5+32

sys.exit(0)

3.9) VB.Net

With VB.Net, usbtenkiget can be executed by using the Process class. The output from usbtenkiget is redirected and can be converted to a string using ReadLine(). An array of individual values can then be built by splitting the string.

Module VBExample
    Sub Main()

        ' Prepare a Process object for running usbtenkiget.
        ' This assumes that usbtenkiget is in your PATH, or
        ' that usbtenkiget.exe is present in the same directory
        ' as this program.
        Dim usbtenki As New Process()
        usbtenki.StartInfo.FileName = "usbtenkiget"
        usbtenki.StartInfo.Arguments = "-i 0,1,2"
        usbtenki.StartInfo.UseShellExecute = False
        usbtenki.StartInfo.RedirectStandardOutput = True

        ' Run usbtenkiget and wait until it exits
        usbtenki.Start()
        usbtenki.WaitForExit()

        ' Read one line of what was output by usbtenkiget
        Dim output = usbtenki.StandardOutput.ReadLine()

        If output Is Nothing Then
            Console.Error.WriteLine("usbtenkiget did not return data")
            Return
        End If

        ' Split the line into fields stored in an array, trim individual fields
        ' to remove extra spaces before fields.
        Dim fields() As String
        fields = output.Split(",").Select(Function(s) s.Trim()).ToArray()

        ' Check that the expected number of fields were read.
        ' In this case, due to the use of the -i 0,1,2 usbtenkiget
        ' argument, exactly 3 are expected.
        If fields.Length <> 3 Then
            Console.Error.WriteLine("usbtenkiget returned an incorrect number of fields")
            Return
        End If

        Console.Out.WriteLine("Temperature (C): " & fields(0))
        Console.Out.WriteLine("RH..........(%): " & fields(1))
        Console.Out.WriteLine("Pressure..(kPa): " & fields(2))
        Console.Out.WriteLine("Tempearture.(F): " & fields(0) * 9 / 5 + 32)

    End Sub
End Module

3.9) Visual Basic 6

With Visual basic 6, the Shell function can be used to execute usbtenkiget, but there is no native way to redirect the output. The strategy here is to send the output of usbtenkiget to a file, and then read it in Visual Basic.

The output of usbtenkiget is redirected within a batch file since redirections do not seem to work directly with the Shell command (eg: Shell "usbtenkiget > tmpfile.txt" does not work). The createWrapper() Sub automatically generates this batch file if it does not already exist.

usbtenkiget %* > tmpfile.txt

In the batch file shown above, the %* characters forward the arguments passed to the batch file to usbtenkiget. The batch file can therefore be run with the appropriate -i and optionally -s arguments. The > character redirects the output from usbtenkiget to a file.

As explained in the comments of the getAndRefreshValues() function, the batch file (and usbtenkiget) are run in parallel to the VB application. Ideally one should wait until the batch file terminates before reading the file. But VB has no native way of doing this (it is however possible using an external API call to WaitForSingleObject, but this is not done here).

In this example, to avoid problems reading incomplete or missing data, the file is read first, and then usbtenkiget is run. This has the side effect that getAndRefreshValues() will return slightly older data. (For instance, if getAndRefreshValues() is called at 5 seconds intervals, the data will be 5 second old). A race condition still exists, but is not likely to cause issues if the sampling rate is not too high. 5 seconds seems safe, knowing than in normal circumstances (working sensor) usbtenkiget exists in less than a second.

It is if course up to you to improve and/or accept the limitations/risks/tradeoffs present in this example.

Private Declare Function getTempPath Lib "kernel32" Alias _
 "GetTempPathA" (ByVal nBufferLength As Long, ByVal lpBuffer As String) As Long
Const MAX_PATH = 255

' Return the path of a temporary directory this process has
' permission to access. A batch file and temporary file
' will be created there.
Public Function getTempDir() As String
    Dim sRet As String, lngLen As Long
    sRet = String(MAX_PATH, 0)
    lngLen = getTempPath(MAX_PATH, sRet)
    If lngLen = 0 Then Err.Raise Err.LastDllError
    getTempDir = Left$(sRet, lngLen)
End Function

Public Function getWrapperPath()
    wrapper = getTempDir() & "runusbtenkiget.bat"
    getWrapperPath = wrapper
End Function
Public Function getTempFilePath()
    getTempFilePath = getTempDir() & "dracalsensordata.txt"
End Function
Public Function getUsbtenkigetPath()
    getUsbtenkigetPath = """c:program Files (x86)qtenkiusbtenkiget"""
End Function

' Create the batch file wrapping usbtenkiget if it does not already exist
Private Sub createWrapper()
    tmpfile = getTempFilePath()
    usbtenkigetpath = getUsbtenkigetPath()
    wrapper = getWrapperPath()

    If Dir(wrapper) <> "" Then
        Exit Sub
    Else
        Open wrapper For Output Access Write As #1
        ' Note: %* means all arguments passed to the batch file will
        ' be forwarded to usbtenkiget.
        Print #1, usbtenkigetpath & " %* < " & tmpfile
        Close #1
    End If
End Sub

' Run the batch file which will run usbtenkiget, redirecting
' the output to a temporary file. The batch file will be
' created automatically if it does not already exist.
Public Function runWrapper(arguments As String)
    Dim returnedData As String

    createWrapper
    Shell getWrapperPath() & " " & arguments, vbHide
End Function

' Try to read the the last values written to the temporary files.
Public Function readLastValues() As String
    On Error GoTo nofile
    Open getTempFilePath() For Input As #1

    On Error GoTo nodata
    Line Input #1, returnedData
    Close #1
    readLastValues = returnedData

nofile:
    Exit Function
nodata:
    ' Line raised an error, but we must still close the file!
    Close #1
End Function

Public Function getAndRefreshValues(arguments As String) As Double()
    Dim result As String
    Dim values() As String
    Dim convertedValues() As Double

    ' The batch file will run in parallel with VB6. There is a race
    ' condition. To avoid issues, the last value is read first. But
    ' this creates a lag equal to the time between updates.
    result = readLastValues()

    ' Once the last values were read, run the batch file. Hopefully
    ' it exits BEFORE next time this function gets called... (do not
    ' call it too often... 5 second intervals are probably safe.
    runWrapper (arguments)

    ' Now try to split the comma-separated fields...
    values() = Split(result, ",")
    If UBound(values) < 0 Then Err.Raise 1000

    ' ...and convert them to floating point numbers
    ReDim convertedValues(UBound(values)) As Double
    For i = 0 To UBound(values)
        convertedValues(i) = CDbl(values(i))
    Next i

    getAndRefreshValues = convertedValues()
End Function

Rem *************** Example UI code ************

Private Sub updateDisplay()
    On Error GoTo getfailed

    ' Note: You may need to change the options passed to usbtenkiget here.
    values = getAndRefreshValues("-i 0,1,2")

    On Error GoTo baddata
    For i = 0 To UBound(values)
    valuesLabel(i).Caption = Format(values(i), "0.00")
    Next i

    ' Success. Show timestamp in status label.
    messageLabel.Caption = "Last update: " & Format(Now, "YYYY-MM-dd hh:mm:ss")

    Exit Sub

    ' Display errors in status label.
getfailed:
    If Err.Number = 1000 Then
        messageLabel.Caption = "No data or no sensor connected"
    Else
        messageLabel.Caption = "Could not update values: " & Err.Description
    End If
    Exit Sub
baddata:
    messageLabel.Caption = "No data: " & Err.Description
End Sub

Private Sub Form_Load()
    ' Start the application with fresh data
    updateDisplay
End Sub

Private Sub Timer1_Timer()
    ' Timer runs every 5 seconds
    updateDisplay
End Sub

4) License and disclaimer

The code on this page is in the Public domain and may be freely incorporated in any software project, commercial or otherwise.

The code examples on this page are provided "as is" in the hope that they will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Best practises, such as error checking and handling, input sanitization, and testing are YOUR responsability, and YOU assume the entire risk as to the quality and performance of the code.

Dracal technologies inc. does not accept any responsability of liability for the accuracy, completeness, or reliability of the code presented on this page. In no event will Dracal technologies inc. be liable to you for damages, including any general, special, incidental or consequential damages arising out of the use or inability to use the code (including but not limited to loss of data or data being rendered inaccurate or losses sustained by you or third parties).