Exemples de code pour intégrer les données des capteurs Dracal avec des outils en ligne de commande
[Last update: 18/04/2024]
- Introduction
- 1. Prérequis
- 2. Utiliser dracal-usb-get et dracal-sensgate-get
- 3. Exemples dans différents langages de programmation
- 4. License et avis de non-responsabilité
Introduction
L'ensemble des produits Dracal USB supportés par l'enregistreur DracalView, et donc tous nos capteurs USB, peuvent être utilisés dans une multitude de langages de programmation avec une approche simple qui consiste à exécuter un outil en ligne de commande et en traiter la sortie. Notez que les exemples présentés ici sont construits à partir de l'outil dracal-usb-get.
Quant à l'intégration des données communiquées par Wi-Fi ou Ethernet via notre passerelle sans fil SensGate, un simple cherche-et-remplace (search and replace) suffit dans les exemples ci-dessous pour les rendre parfaitement fonctionnels avec l'outil d'accès au SensGate. Survoler la documentation de l'outil en ligne de commande dracal-sensgate-get pour constater la structure identique des commandes pour les deux CLI.
1. Prérequis
- Avoir installé DracalView (instructions spéciales pour les utilisateurs sous Linux)
- Avoir pris connaissance du guide de démarrage avec les outils en ligne de commande
- Avoir une connaissances élémentaires d'utilisation de la ligne de commande
2. Utiliser dracal-usb-get et dracal-sensgate-get
Vous trouverez ci-après un bref résumé des commandes de base de notre outil en ligne de commande dracal-usb-get. L'utilisation de dracal-sensgate-get est se fait de la même manière et les exemples peuvent être utilisés pour les 2 outils de manière interchangeable.
Pour en approfondir le contenu, consultez la Documentation de dracal-usb-get. Comme pour la plupart des outils en ligne de commandes, de l'aide peut être affichée en utilisant l'option -h
. Puisque les options sont généralement assez simples, elles ne seront pas expliquées en détail ici. Mais les deux plus importantes seront démontrées.
Lister les capteurs et leurs canaux
Des informations sur les capteurs USB présentement connectés peuvent être affichées via la commande -l
, tel que démontré ci-dessous:
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]
Dans l'exemple ci-haut, un seul capteur (Dracal VCP-PTH450-CAL) de numéro de série E24638 était présent lors de l'exécution de la commande. Ce capteur possède 3 canaux réels (des données mesurées) et 4 canaux virtuels (données calculées à partir des données mesurées). Chaque canal, soit-il réel ou virtuel, est identifié par un numéro.
Note: Sous Linux, si aucun capteur n'est détecté, il est probable que votre compte utilisateur n'ait pas les droits requis. Consultez la section sur la configuration des droits d'accès au besoin.
Lire les valeurs d'un capteur
La valeur d'un canal peut être récupérée en passant le numéro correspondant à l'argument -i
. Lorsque plus qu'un canal est requis, les identifiants sont séparés par des virgules, tel que ci-dessous. Les valeurs retournées sont elles aussi séparées par des virgules et sont affichées dans l'ordre demandé:
dracal-usb-get -i 0,1,256
22.46, 39.55, 8.02
Le résultat ci-dessus est ce que votre programme devra interpréter. Si vous affichez simplement les valeurs, essayez -p
("pretty output") pour un résultat convivial avec des unités. En parlant d'unités, consultez la page d'aide (option -h
) et la documentation de dracal-usb-get pour savoir comment les modifier.
Utiliser plusieurs capteurs
L'exemple précédent fonctionnera sans problèmes s'il n'y a qu'un capteur d'installé. Mais si plusieurs capteurs sont présents, seules les valeurs du premier capteur trouvé seront lues.
Les valeurs d'un capteur spécifique peuvent être obtenues en mentionnant son numéro de série en ligne de commande à l'aide de l'option -s
, comme ceci:
dracal-usb-get -s E24638 -i 0,1,256
22.46, 39.55, 8.02
Ceci couvre les bases de l'utilisation de l'outil dracal-usb-get.
3. Exemples dans différents langages de programmation
3.1. Bash
Dépôt GitHub contenant un exemple de code en Bash prêt à l'emploi
L'exécution de dracal-usb-get à partir d'un script bash se fait comme pour n'importe quelle autre commande, pourvu que l'exécutable dracal-usb-get soit dans le path. De nombreuses techniques peuvent être employées pour traiter le retour de dracal-usb-get. Voici un exemple utilisant cut
pour séparer les champs, et ensuite bc
pour comparer les valeurs.
#!/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)"
générant la sortie suivante:
3.2. C (POSIX)
Dépôt GitHub contenant un exemple de code en C/POSIX prêt à l'emploi
Dans les environnements où elle est disponible, la fonction popen
permet d'exécuter dracal-usb-get en redirigeant la sortie vers un flux de type FILE. La fonction fscanf
peut alors servir pour extraire les valeurs vers des 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;
}
produisant la sortie suivante:
3.3. C/C++ (Win32)
Dépôt GitHub contenant un exemple de code en C++/Win32 prêt à l'emploi
Avec l'API Win32, dracal-usb-get peut être exécuté avec la fonction CreateProcess
. La sortie de dracal-usb-get est dirigée vers un tuyau (pipe) duquel le parent (le code d'exemple) lira pour recevoir les valeurs retournées par dracal-usb-get, valeurs qui seront ensuite traitées et transformées en variables float.
// 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;
}
produisant la sortie suivante:
3.4. C#
Dépôt GitHub contenant un exemple de code en C# prêt à l'emploi
Avec C# et le Framework .NET, la classe Process permet d'exécuter dracal-usb-get. La sortie de dracal-usb-get est redirigée et transformée en chaîne de caractères par ReadLine(). Un tableau des valeurs individuelles peut alors être bâti en divisant la chaîne avec Split avant de convertir le tout en Float.
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));
}
}
Produisant la sortie suivante:
Un exemple en C# dans lequel dracal-usb-get est gardé en vie (via log mode) plutôt que d'être redémarré pour chaque lecture est aussi disponible: AppRunMode.cs
3.5. C++/CLI (.NET)
Dépôt GitHub contenant un exemple de code en C++/CLI (.NET) prêt à l'emploi
Avec C++/CLI et le Framework .NET, la classe Process permet d'exécuter dracal-usb-get. La sortie de dracal-usb-get est redirigée et transformée en chaîne de caractères par ReadLine(). Un tableau des valeurs individuelles peut alors être bâti en divisant la chaîne avec Split avant de convertir le tout en Double.
#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;
}
produisant la sortie suivante:
3.6. Java
Dépôt GitHub contenant un exemple de code en Java prêt à l'emploi
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));
}
}
produisant la sortie suivante:
3.7. Node.js
Dépôt GitHub contenant un exemple de code en Node.js prêt à l'emploi
En Node, l'exécution de dracal-usb-get peut se faire à l'aide de execFile
. L'exécution terminée, la sortie de dracal-usb-get est disponible dans une chaîne de caractères qui peut alors être traitée.
// 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));
});
produisant la sortie suivante:
3.8. Python
Dépôt GitHub contenant un exemple de code Python prêt à l'emploi
En Python, subprocess.check_output
se charge d'exécuter dracal-usb-get. Un appel à la méthode split permet de séparer les champs qui seront alors transformés en nombre à virgule flottante par float()
.
#!/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)
produisant la sortie suivante:
3.9. VB.Net
Dépôt GitHub contenant un exemple de code VB.NET prêt à l'emploi
Avec VB.NET, la classe Process permet d'exécuter dracal-usb-get. La sortie de dracal-usb-get est redirigée et transformée en chaîne de caractères par ReadLine(). Un tableau des valeurs individuelles peut alors être bâti en divisant la chaîne avec Split avant de convertir le tout en Double.
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
produisant la sortie suivante:
3.10. Visual Basic 6
Sous Visual Basic 6, la fonction Shell()
permet d'exécuter dracal-usb-get, mais sans permettre d'en rediriger la sortie pour la lire directement. La stratégie employée dans cet exemple est donc de rediriger la sortie vers un fichier, pour ensuite le lire depuis Visual Basic (VB).
Un fichier batch se charge d'exécuter dracal-usb-get et d'en rediriger la sortie car la redirection vers un fichier ne semble pas fonctionner avec Shell()
.. (c'est-à-dire que "dracal-usb-get > tmpfile.txt" ne fonctionne pas). Le sub createWrapper() génère automatiquement ce fichier batch s'il n'existe pas déjà.
dracal-usb-get %* > tmpfile.txt
Dans le fichier batch ci-dessus, les caractères %* permettent de transférer les arguments passés au fichier batch à dracal-usb-get. Les arguments -i
et -s
passés au fichier batch parviendront donc à dracal-usb-get. Le caractère > quant à lui redirige la sortie vers un fichier.
Tel que décrit dans les commentaires de la fonction getAndRefreshValues()
, l'exécution du fichier batch (et de dracal-usb-get) se déroule parallèlement à l'application VB. Idéalement, il faudrait attendre que l'exécution du fichier batch prenne fin avant de lire le fichier. Malheureusement, VB ne permet pas nativement de le faire. (Il est possible d'y arriver avec des appels à des API externes telles que WaitForSingleObject, mais cette technique n'est pas employée ici).
Dans cet exemple, afin d'éviter de lire des donnés incomplètes ou nulles, le fichier est lu d'abord, juste avant le lancement du fichier batch. Cela a comme effet que chaque appel à getAndRefreshValues()
retournera des valeurs passées. Par exemple, si la fonction est appelée aux 5 seconds, ce sera toujours des valeurs vieilles de 5 secondes qui seront retournées. Bien que la situation de compétition demeure, les chances de complications sont faibles si les appels à getAndRefreshValues
ne sont pas trop rapprochés. Un appel aux 5 seconds est probablement peu risqué, sachant que dans des circonstances normales (capteurs fonctionnels) dracal-usb-get s'exécute en moins d'une seconde.
Bien entendu, c'est à vous d'améliorer l'exemple pour vos besoin, sinon d'en comprendre et accepter les limites, les risques et les compromis...
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 et avis de non-responsabilité
Sauf indication contraire, les extraits de code sur cette page sont placés dans le domaine public et peuvent être incorporés dans tout logiciel, commercial ou non.
Les exemples sur cette page sont fournis "en l'état", dans l'espoir qu'ils puissent servir, mais sans AUCUNE GARANTIE de quelque nature que ce soit, expresse ou implicite, y compris, mais sans y être limité, les garanties implicites de commerciabilité et de la conformité à une utilisation particulière.
Les pratiques recommandées de programmation, telles que la gestion et détection d'erreurs, la validation des entrées et les tests, sont sous la responsabilité de l'utilisateur, qui assume également la totalité des risques liés à la qualité et aux performances du code.
Dracal Technologies Inc. n'accepte aucune responsabilité quant à l'exactitude, l'intégralité, la performance ou la fiabilité du code sur cette page. Dracal Technologies Inc. ne pourrait être tenue responsable à votre égard des dommages, incluant les dommages génériques, spécifiques, secondaires ou consécutifs, résultant de l'utilisation ou de l'incapacité d'utiliser le code (y compris, mais sans y être limité, la perte de données, ou le fait que des données soient rendues imprécises, ou les pertes éprouvées par vous ou par des tiers).