Code examples for integrating data from Dracal sensors using command-line tools

[Last update: 26/04/2024]

Introduction

All the products supported by our DracalView data logger, in fact all our USB sensors, can be readily used from a variety of programming language by programmatically invoking a simple command-line tool and processing its output. In the following, examples are built on the use of dracal-usb-get command-line tool. However, note that in each of these examples, a simple search-and-replace is required to convert them into examples using the SensGate command-line tool dracal-sensgate-get.

This page demonstrates how this can be done in a variety of programming languages.

1. Prerequisites

- Have installed DracalView (special instructions for users under Linux)

- Have skimmed the Getting Started Guide with command-line tools guide

- Have a basic command-line usage knowledge

Where to get the command-line tools?

Windows and Mac OS X users

The dracal-usb-get and dracal-sensgate-get command-line tools are included in DracalView. Under Windows, simply install DracalView. After installation, navigate to the installation directory (typically, this will be "C:\Program files\DracalView" or "C:\Program files (x86)\DracalView"). The files dracal-usb-get and dracal-sensgate-get will be found in this location.

Linux users

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

2. Using dracal-usb-get and dracal-sensgate-get

You will find hereafter a summary of the basic commands available with our command line tool dracal-usb-get. The tool dracal-sensgate-get is used in exactly the same way. For more in-depth content of the tool, please visit the dracal-usb-get 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:

dracal-usb-get -l
Found: 'VCP-PTH450-CAL', Serial: 'E24638', Version 2.10, Channels: 7
    Channel 0: MS5611 Pressure [Pressure]
    Channel 1: SHT31 Temperature [Temperature]
    Channel 2: SHT31 Relative Humidity [Relative Humidity]
    Virtual Channel 256: Dew point [Dew point]
    Virtual Channel 257: Humidex [Humidex]
    Virtual Channel 258: Heat index [Heat index]
    Virtual Channel 262: Altitude [Height]

Only one sensor (Dracal VCP-PTH450-CAL) with serial number E24638 was connected when the above command was executed. This sensor has 3 real channels (Measured data) and 4 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 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:

dracal-usb-get -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 dracal-usb-get 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:

dracal-usb-get -s E24638 -i 0,1,256
22.46, 39.55, 8.02

This covers the dracal-usb-get usage basics.

3. Examples in different programming languages

Bash Icon 3.1. Bash

Github Icon  Fully-functional Bash code sample GitHub repository

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

#!/bin/bash

# Note: dracal-usb-get 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 dracal-usb-get exits with a non-zero values, the exit status will be checked.
if ! output=$(dracal-usb-get -i 0,1,2 2>&1); then
  echo "dracal-usb-get error"
  exit 1
fi

fields=$(echo $output | tr "," "\n")

# This example expects the following output:
#
# 100.40, 24.48, 61.56
#
# Where fields are pressure, temperature and rh (humidity).
#
# Detect errors by checking if the exact expected number
# fields was returned.
if [[ $(echo $fields | wc -w) -lt 3 ]]; then
  echo "Error reading sensor"
  exit 2
fi

# Convert the fields from strings to floating point values
# This step is necessary to perform math on values.
pressure=$(echo $fields | awk '{print $1}')
temperature=$(echo $fields | awk '{print $2}')
rh=$(echo $fields | awk '{print $3}')

# Display values
echo "Temperature (C): $temperature"
echo "RH......... (%): $rh"
echo "Pressure..(kPa): $pressure"
echo "Temperature (F): $(echo "scale=2; $temperature*9/5+32" | bc)"

producing the following output:

C Icon 3.2. C (POSIX)

Github Icon  Fully-functional C/POSIX code sample GitHub repository

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

#include 

int getMeasurements(float *pres, float *temp, float *hum)
{
	FILE *fptr;
	float p, t, h;
	int n, res;

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

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

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

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

	return 0;
}

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

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

  printf("Pressure. (kPa): %.2f\n", pressure);
  printf("Temperature (C): %.2f\n", temperature);
  printf("RH......... (%%): %.2f\n", humidity);
  printf("Temperature (F): %.2f\n", temperature * 9 / 5 + 32);

	return 0;
}

generating the following output:

C Icon 3.3. C/C++ (Win32)

Github Icon  Fully-functional C++/WIN32 code sample GitHub repository

If using the Win32 API, dracal-usb-get can be executed using the CreateProcess function. The output of the child process (dracal-usb-get) is redirected to a pipe. The parent (the example code) reads from the pipe to receive the output of dracal-usb-get, 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 
#include 
#include 
#include 
#include 

#define BUFSIZE 4096

/* Execute dracal-usb-get 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("dracal-usb-get -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 pipe\n");
		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 dracal-usb-get\n");
		return false;
	}

	// Wait until dracal-usb-get 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 dracal-usb-get\n");
		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 values\n");
		return false;
	}
	float* dst_value = *values;
	for (char* c = chBuf; c; )
	{
		int i = strlen(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("dracal-usb-get -i 0,1,2"), &values, &n_values);
	if (!bSuccess) {
		return -1;
	}

	if (bSuccess) {
		// We run dracal-usb-get 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, "dracal-usb-get returned wrong number of fields\n");
			return -1;
		}
		else {
			printf("Pressure. (kPa): %.2f\n", values[0]);
			printf("Temperature (C): %.2f\n", values[1]);
			printf("RH......... (%%): %.2f\n", values[2]);
			printf("Temperature (F): %.2f\n", values[1] * 9 / 5 + 32);
		}
	}

	if (values) {
		free(values);
	}

	return 0;
}

producing the following input:

C# Icon 3.4 C#

Github Icon  Fully-functional C# code sample GitHub repository

In C# with the .Net framework, dracal-usb-get can be executed by the Process class. The output from dracal-usb-get 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.Diagnostics;

class App
{
    static void Main(string[] args)
    {
        Process usbtenki = new Process();
        usbtenki.StartInfo.FileName = "dracal-usb-get";
        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 dracal-usb-get: " + e);
            return;
        }

        usbtenki.WaitForExit();

        string? output = usbtenki.StandardOutput.ReadLine();

        if (output == null)
        {
            Console.Error.WriteLine("dracal-usb-get did not return data");
            return;
        }

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

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

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

generating the following output:

A C# example where dracal-usb-get keeps running (using log mode) instead of being restarted for each sample is also available: AppRunMode.cs

C++ Icon 3.5. C++/CLI (.NET)

Github Icon  Fully-functional C++/CLI (.NET) code sample GitHub repository

In C++/CLI targeting the .NET Framework, dracal-usb-get can be executed by the Process class. The output from dracal-usb-get 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.

#using <System.dll>
#include <limits>
using namespace System;
using namespace System::Diagnostics;
using namespace System::ComponentModel;

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

	usbtenki->StartInfo->FileName = "dracal-usb-get";
	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 dracal-usb-get: " + e);
		return 1;
	}

	usbtenki->WaitForExit();

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

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

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

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

	return 0;
}

producing the following output:

Java Icon 3.6. Java

Github Icon  Fully-functional Java code sample GitHub repository

package src.main.java;
import java.io.*;

public class Main {

  public static void main(String[] args)
  {
    Process usbGet;

    // Note: dracal-usb-get 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 {
      usbGet = Runtime.getRuntime().exec("dracal-usb-get -i 0,1,2");
    } catch(IOException e) {
      System.err.println("could not run dracal-usb-get: " + e);
      return;
    }

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

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

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

    // dracal-usb-get 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 dracal-usb-get -i 0,1,2 argument requests 3 fields).
    if (values.length != 3) {
      System.err.println("Incorrect number of fields received: " + values.length);
      return;
    }

    float pressure = Float.parseFloat(values[0]);
    float temperature = Float.parseFloat(values[1]);
    float rh = 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));
  }
}

generating the following output:

Node Icon 3.7. Node.js

Github Icon  Fully-functional Node.js code sample GitHub repository

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

// if not using Babel, you'll want to replace `import` statements with ES5 format, e.g. `const { SerialPort } = require('SerialPort')...`
import { execFile } from 'child_process';

const child = execFile('dracal-usb-get', ['-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("Pressure..(kPa): " + fields[0]);
	console.log("Temperature.(C): " + fields[1]);
	console.log("RH..........(%): " + fields[2]);
	console.log("Temperature.(F): " + (fields[1] * 9 / 5 + 32));

});

generating the following output:

Python Icon 3.8. Python

Github Icon  Fully-functional Python code sample GitHub repository

In Python, dracal-usb-get 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: dracal-usb-get 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 dracal-usb-get exits with a non-zero values, the subprocess.CalledProcessError
# exception will be raised. Catch it.
try:
    p = subprocess.check_output(["dracal-usb-get","-i","0,1,2"]).decode('utf-8')
except subprocess.CalledProcessError:
    print("dracal-usb-get 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) < 2:
    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.
pressure = float(fields[0])
temperature = float(fields[1])
rh = 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)

 

generating the following output:

VB Icon 3.9. VB.Net

Github Icon  Fully-functional VB.NET code sample GitHub repository

With VB.Net, dracal-usb-get can be executed by using the Process class. The output from dracal-usb-get 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 dracal-usb-get.
        ' This assumes that dracal-usb-get is in your PATH, or
        ' that dracal-usb-get.exe is present in the same directory
        ' as this program.
        Dim usbtenki As New Process()
        usbtenki.StartInfo.FileName = "dracal-usb-get"
        usbtenki.StartInfo.Arguments = "-i 0,1,2"
        usbtenki.StartInfo.UseShellExecute = False
        usbtenki.StartInfo.RedirectStandardOutput = True

        ' Run dracal-usb-get and wait until it exits
        usbtenki.Start()
        usbtenki.WaitForExit()

        ' Read one line of what was output by dracal-usb-get
        Dim output = usbtenki.StandardOutput.ReadLine()

        If output Is Nothing Then
            Console.Error.WriteLine("dracal-usb-get 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 dracal-usb-get
        ' argument, exactly 3 are expected.
        If fields.Length <> 3 Then
            Console.Error.WriteLine("dracal-usb-get returned an incorrect number of fields")
            Return
        End If

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

    End Sub
End Module

producing the following output:

3.10. Visual Basic 6

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

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

dracal-usb-get %* > tmpfile.txt

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

As explained in the comments of the getAndRefreshValues() function, the batch file (and dracal-usb-get) 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 dracal-usb-get 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) dracal-usb-get 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() & "rundracal-usb-get.bat"
    getWrapperPath = wrapper
End Function
Public Function getTempFilePath()
    getTempFilePath = getTempDir() & "dracalsensordata.txt"
End Function
Public Function getdracal-usb-getPath()
    getdracal-usb-getPath = """c:program Files (x86)DracalViewdracal-usb-get"""
End Function

' Create the batch file wrapping dracal-usb-get if it does not already exist
Private Sub createWrapper()
    tmpfile = getTempFilePath()
    dracal-usb-getpath = getdracal-usb-getPath()
    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 dracal-usb-get.
        Print #1, dracal-usb-getpath & " %* < " & tmpfile
        Close #1
    End If
End Sub

' Run the batch file which will run dracal-usb-get, 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 dracal-usb-get 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 responsibility, and YOU assume the entire risk as to the quality and performance of the code.

Dracal Technologies Inc. does not accept any responsibility 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).