Using Dracal Sensors with Almost any Programming Language

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.

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

  • 1) Prerequisites
  • 2) Using dracal-usb-get
  • 3) Examples in different programming languages
  • 4) License and disclaimer

    1) Prerequisites

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

    Windows users: Where to get dracal-usb-get.exe

    The dracal-usb-get command-line tool is 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 file dracal-usb-get.exe will be found in this location.

    Linux users: Compile dracal-usb-get

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

    2) Using dracal-usb-get

    You will find hereafter a summary of the basic commands available with our command line tool dracal-usb-get. 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: '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 USBTenki) 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 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 -i 0,1,256 -s B10004
    22.46, 39.55, 8.02
    

    This covers the dracal-usb-get usage basics.

    3) Examples in different programming languages

    3.1) Bash

    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 path.
    # You may need to use -s to read from a specific sensor if you have more than one.
    VALUES=`dracal-usb-get -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 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 <stdio.h>
    int getTempAndHumidity(float *temp, float *hum)
    {
    	FILE *fptr;
    	float t, h;
    	int n, res;
    
    	fptr = popen("dracal-usb-get -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, 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 <windows.h>
    #include <tchar.h>
    #include <stdio.h>
    #include <strsafe.h>
    #include <math.h>
    
    #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 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 dracal-usb-getn");
    		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-getn");
    		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("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 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, 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;
    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 = "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("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 dracal-usb-get 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, 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.

    #include "stdafx.h"
    #include 
    
    using namespace System;
    using namespace System::Diagnostics; // for Process
    
    int main(array ^args)
    {
    	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;
    	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 dracal-usb-get;
    
    		// 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 {
    			dracal-usb-get = 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 {
    				dracal-usb-get.waitFor();
    			} catch (InterruptedException e) {
    				continue;
    			}
    			break;
    		}
    
    		// Check if dracal-usb-get exited with an error code
    		if (dracal-usb-get.exitValue() != 0) {
    			System.err.println("dracal-usb-get error. Exit value=" + dracal-usb-get.exitValue());
    			return;
    		}
    
    		BufferedReader reader = new BufferedReader(new InputStreamReader(dracal-usb-get.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 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, 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.

    const execFile = require('child_process').execFile;
    
    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("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, 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"])
    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) < 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, 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("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 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).