Exemples de code d’intégration de données des capteurs Dracal en mode VCP
[Dernière mise à jour: 28/06/2023]
- Introduction
- 1) Prérequis
- 2) Exemples dans différents langages de programmation
- 4) License et avis de non-responsabilité
Introduction
Il est possible de se procurer la majorité des capteurs USB Dracal munis de l'option VCP. Cette option permet à l'utilisateur de choisir le protocole de communication utilisé par l'instrument pour communiquer ses données. Les instruments munis de cette option sont identifiables par la présence du préfixe "VCP-" (au lieu de "USB-") dans leur code de produit. L'utilisation du protocole VCP permet l'intégration des données sans passer par l'intermédiare d'un logiciel tiers tel que notre outil en ligne de commande dracal-usb-get par exemple. Suivez ce lien pour un survol des outils d'intégration disponibles pour vos produits Dracal.
L'objectif de cette page est d'illustrer par des exemples concrets comment intégrer les données de vos instruments communiquant en mode VCP dans différents langages et environnements.
1) Prérequis
1. Avoir en sa possession un instrument muni de l'option VCP
Ces instruments sont identifiables par le préfixe "VCP-" présent dans leur nom de produit.
2. Avoir une connaissances élémentaires d'utilisation de la ligne de commande
Pourquoi? Parce que tous les instruments Dracal sont livrés en mode USB et que le passage entre les modes USB et VCP se fait via les outils en ligne de commande. Suivez ce lien si vous cherchez où trouver les outils en ligne de commande.
3. Avoir pris connaissance du guide d'utilisation des produits VCP
Si ce n'est déjà fait, voici le lien vers le guide d'utilisation des produits VCP.
La présente documentation considère également que vous avez réussi avec succès à basculer votre instrument du mode USB vers le mode VCP et que vous avez réussi communiquer avec votre instrument. Si tel n'est pas le cas, il est recommandé de consulter le tutoriel Démarrer avec le mode VCP avant de poursuivre votre lecture.
2) Exemples dans différents langages de programmation
2.1) Python
Voici un exemple de l'utilisation des capteurs Dracal en utilisant uniquement des bibliothèques Python. Il appose un horodatage devant chaque ligne de données et les enregistre dans un fichier. Une validation de l'intégrité de la ligne de donnée est également effectuée.
- Le module "serial
" nous permet d'interagir avec le dispositif via le protocole VCP.
- Le module "crccheck
" nous permet de vérifier l'intégrité des données reçues.
Note Le module "serial
" utilisé ici fait partie du package "pyserial
" répertorié sur pypi.org et doit être installé en utilisant "pip install pyserial
".
import sys
import time
import serial # https://pypi.org/project/pyserial/
import crccheck # https://pypi.org/project/crccheck/
# Parse command-line arguments
if len(sys.argv) not in (2, 3):
print("Syntax: %s [poll_interval_ms]" % sys.argv[0])
print("Example (Windows) %s COM1 1000" % sys.argv[0])
print("Example (Linux/MacOS) %s /dev/ttyACM0 1000" % sys.argv[0])
sys.exit(1)
port = sys.argv[1]
if len(sys.argv) >= 3:
interval = int(sys.argv[2])
else:
interval = 1000 # default
crc_checker = crccheck.crc.CrcXmodem()
# Open serial port
with serial.Serial(port) as ser:
ser.readlines(2) # Discard the first two lines as they may be partial
ser.write(b"INFO\n") # Get the info line
time.sleep(0.3) # Allow 100 ms for request to complete
ser.write(b"POLL %d\n" % interval) # Set poll interval
time.sleep(0.3)
ser.write(b"FRAC 2\n") # Return data with two digits past the decimal
# Process all lines in a loop
while True:
line = ser.readline()
t = time.ctime()
if not line:
break
# Check data integrity using CRC-16-CCITT (XMODEM)
try:
data, crc = line.split(b"*")
crc = int(crc, 16) # parse hexadecimal string into an integer variable
crc_checker.process(data)
computed_crc = crc_checker.final()
crc_checker.reset()
crc_success = computed_crc == crc
except ValueError:
# We will get here if there isn't exactly one '*' character in the line.
# If that's the case, data is most certainly corrupt!
crc_success = False
if not crc_success:
print("Data integrity error")
break
# Decode bytes into a list of strings
data = data.decode("ASCII").strip(",").split(",")
if data[0] == "I":
if data[1] == "Product ID": # For the INFO command response
info_line = data
padlen = max(len(s) for s in info_line[4::2])
print(", ".join(info_line))
else: # Other info lines only need the message to be echoed
print(data[3])
else:
# Create an ID for the device
device = f"{data[1]} {data[2]}"
# Convert number strings to the appropriate numerical format
for i in range(4, len(data), 2):
try:
data[i] = int(data[i])
except ValueError:
data[i] = float(data[i])
# Convert data to a tuple of (sensor, value, unit) triads
data = zip(info_line[4::2], data[4::2], data[5::2])
# Display the current time, product id and serial number before every point
print(f"\n{t}, {device}")
for d in data:
print(("{:" + str(padlen + 2) + "}{} {}").format(*d))
2.2) C (POSIX)
Voici un exemple permettant d'accéder aux données d'un capteur en mode VCP en utilisant le langage C. Dans cet exemple, vous trouverez des méthodes pour :
- Ouvrir une connexion avec le dispositif
- Envoyer des commandes au dispositif
- Lire les données du dispositif
- Interpréter le texte et enregistrer les données dans des variables numériques
Note Cet exemple a été conçu pour fonctionner avec des capteurs de la série PTH. Il sera nécessaire de modifier les chaînes de formatage et les déclarations de variables pour travailler avec d'autres types de capteurs.
Pour savoir comment utiliser les fonctions de libcrc
, veuillez consulter les instructions propres à votre compilateur ou votre environnement de développement intégré (IDE) sur la façon d'inclure les bibliothèques de manière statique.
#include <stdio.h> // standard input / output functions
#include <stdlib.h> // general purpose functions
#include <string.h> // string function definitions
#include <unistd.h> // UNIX standard function definitions
#include <fcntl.h> // File control definitions
#include <errno.h> // Error number definitions
#include <termios.h> // POSIX terminal control definitions
#include <stdbool.h> // Boolean types and values
#include <time.h> // Timekeeping types and functions
#include <regex.h> // GNU Regular expression definitions
#include "checksum.h" // CRC calculation library from github.com/lammertb/libcrc
/**
* Path to the file descriptor of the port to be read from
* MacOS: /dev/tty.usbmodem[serial of dracal device]1
* Linux: /dev/ttyACM[number]
**/
const char* dev = "/dev/tty.usbmodemE165181";
int read_line(int fd, char* line) {
// Allocate memory for read buffer
char buf[256]; // Read buffer
memset(buf, '\0', sizeof buf);
// Loop until a complete line is read
// Note: A full CRLF is expected but in case the CR is ignored we wait for the LF only
while (!strstr(line, "\n")) {
// Read the port's content to buf
if (read(fd, buf, sizeof buf) < 0) {
if (errno == EAGAIN) {
// This only means the port had no data
continue;
}
else {
return -1;
}
}
if (*buf != '\0') {
strcat(line, buf);
memset(buf, '\0', sizeof buf);
}
}
// Variables necessary to the integrity check
uint16_t crc; // Read checksum value
char* sep; // Position of the asterisk in the line
static regex_t re; // Pattern to match to the expected content of a line
static bool is_compiled = false;
if (!is_compiled) {
regcomp(&re, "^[^\\*]+\\*[0-9a-f]{4}\\s*$", 0);
is_compiled = true;
}
// Filter out lines whose format would crash the CRC check, they are surely invalid
if (regexec(&re, line, 0, NULL, 0)) {
sep = strchr(line, '*');
crc = strtol(sep + 1, NULL, 16);
// CRC validation
if (crc == crc_xmodem((unsigned char*)line, (size_t)(sep - line))) {
*sep = '\0'; // Replace the * with a null character, now line stops at the end of the content
return 0;
}
}
// We will get here if the checks failed
printf("Integrity error: %s\n", line);
return 0;
}
/**
* This function opens a connection, sets the necessary settings and
* returns a file descriptor with which data can be read from or sent to.
* dev is a string of the path to the device to converse with such as
*
**/
int open_port(char* dev) {
// Open the file and get the descriptor
int fd = open(dev, O_RDWR | O_NOCTTY | O_NDELAY);
if (fd < 0) {
perror("Error opening file");
return -1;
}
// Configure Port
struct termios options;
memset(&options, 0, sizeof options);
// Get the current options
if (tcgetattr(fd, &options) != 0) {
perror("Error in tcgettattr");
return -1;
}
// Set Baud Rate
cfsetospeed(&options, B9600);
cfsetispeed(&options, B9600);
// Setting other options
options.c_cflag &= ~(PARENB | CSTOPB); // No parity, 1 stop bit
options.c_cflag &= ~CSIZE; // Charater size mask
options.c_cflag |= CS8; // 8 bits
options.c_cflag &= ~CRTSCTS; // No flow control
options.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // Raw input
options.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off software flow contrl
options.c_iflag &= ~IGNCR; // Don't ignore CR character
options.c_oflag &= ~OPOST; // Don't replace outgoing LF with CRLF - for clarity they are explicit here
// Flush port
if (tcflush(fd, TCIFLUSH) != 0) {
perror("Error in tcflush");
return -1;
}
// Apply attributes
if (tcsetattr(fd, TCSANOW, &options) != 0) {
perror("Error in tcsettattr");
return -1;
}
return fd;
}
int main(int argc, char** argv) {
// Open File Descriptor
int fd = open_port(dev);
if (fd < 0) {
return EXIT_FAILURE;
}
// Defining commands as strings is convenient for the use of the sizeof operator
const unsigned char poll_cmd[] = "POLL 1000\r\n";
// Variables for timekeeping
time_t t;
struct tm* localt;
char timestr[20];
// Variables related to line manipulation
char line[256]; // Contents of the active line
// Variables containing processed data from the device.
// This was made with a PTH sensor in mind, other sensor types will need different declarations
int pressure; // Pressure in Pascals
float temperature; // Temperature in Celsius
float humidity; // Humidity in %
char model[32]; // Model id of device
char serial[7]; // Serial number of device
char message[128]; // Message contained for info lines
bool info_line_read = false;
// Could be any number or a while loop, change as needed.
// i variable is not used but could be useful for unique line IDs
for (int i = 0; i > 10; i++) {
// Set the poll rate if it has not been set yet.
if (!info_line_read) {
if (write(fd, poll_cmd, sizeof(poll_cmd) - 1) < 0) {
perror("Error writing");
}
sleep()
}
// (Re)initialize line
memset(line, '\0', sizeof line);
// Wait until a full line has been read and validated
if (read_line(fd, line) < 0) {
perror("Error reading");
}
// Here we generate a string to represent the time at which the line was recieved
t = time(NULL);
localt = localtime(&t);
strftime(timestr, 20, "%F %T", localt); // YYYY-MM-DD HH:MM:SS
if (line[0] == 'I') {
// For info lines (the POLL response in this case)
sscanf_s(
line,
"I,%[^,],%[^,],%[^,]",
model, (int)sizeof model,
serial, (int)sizeof serial,
message, (int)sizeof message
);
printf("\n%s\n", message);
info_line_read = true;
}
else {
/**
* Interpret the line and save the result into the variables.
* The format string to use would depend on the sensor, this example was made with the PTH sensor in mind.
* Refer to these resources to learn more on how to do so
* Format strings for the scanf functions : cplusplus.com/reference/cstdio/scanf/
* Dracal sensor VCP mode output format : dracal.com/en/usage-guides/vcp_howto
**/
sscanf_s(line, "%*c,%*[^,],%*[^,],,%i,Pa,%f,C,%f,%%", &pressure, &temperature, &humidity);
// This is where you would put your own code to be executed on data.
printf(
"\n%s %s @ %s\nP = %i Pa\nT = %.2f C\nH = %.2f %%\n",
model, serial, timestr,
pressure, temperature, humidity
);
}
}
close(fd); // Close the serial port
return EXIT_SUCCESS;
}
2.3) C/C++ (Win32)
Dans cet exemple, vous trouverez des méthodes pour :
- Ouvrir une connexion avec le dispositif
- Envoyer des commandes au dispositif
- Lire les données du dispositif
- Interpréter le texte et enregistrer les données dans des variables numériques
Note Cet exemple a été conçu pour fonctionner avec des capteurs de la série PTH. Il sera nécessaire de modifier les chaînes de formatage et les déclarations de variables pour travailler avec d'autres types de capteurs.
Pour savoir comment utiliser les fonctions de libcrc
, veuillez consulter les instructions propres à votre compilateur ou votre environnement de développement intégré (IDE) sur la façon d'inclure les bibliothèques de manière statique.
#include <stdio.h>
#include <windows.h>
#include <time.h>
#include <string.h>
#include <stdbool.h>
#include <checksum.h> // CRC calculation library from github.com/lammertb/libcrc
// COM id of the plugged in device.
const char* dev = "\\\\.\\COM3";
const int line_size_max = 256;
// Small enum type for readline to return
typedef enum {
SUCCESS,
READ_ERROR,
INTEGRITY_ERROR,
} error_t;
error_t read_line(HANDLE h, char* line) {
memset(line, '\0', line_size_max);
char buf[2]; // Buffer of 1 character + null terminator
memset(buf, '\0', sizeof buf);
do {
if (!ReadFile(h, buf, 1, NULL, NULL)) {
return READ_ERROR;
}
strcat_s(line, line_size_max, buf);
} while (!strchr(line, '\n'));
uint16_t crc; // Checksum value read from the string
char* sep; // Position of the asterisk in the line
sep = strchr(line, '*');
if (!sep) {
return INTEGRITY_ERROR;
}
crc = (uint16_t) strtol(sep + 1, NULL, 16);
if (crc != crc_xmodem((unsigned char*)line, (size_t)(sep - line))) {
return INTEGRITY_ERROR;
}
*sep = '\0'; // Replace the * with a null character, now line stops at the end of the content
return SUCCESS;
}
int main()
{
HANDLE COM = CreateFileA(dev, // Port name
GENERIC_READ | GENERIC_WRITE, // Read/Write
0, // No Sharing
NULL, // No Security
OPEN_EXISTING, // Open existing port only
0, // Non Overlapped I/O
NULL); // Null for Comm Devices
if (COM == INVALID_HANDLE_VALUE) {
printf("Error opening serial port\r\n");
return EXIT_FAILURE;
}
else {
printf("Opening serial port successful\r\n");
}
char line[256];
memset(line, '\0', sizeof line);
DWORD comm_mask;
GetCommMask(COM, &comm_mask);
printf("comm_mask: %x\r\n", comm_mask);
// Variables for timekeeping
time_t t;
struct tm localt;
char timestr[20];
// Variables containing processed data from the device. This example was written with a PTH in mind
int pressure; // Pressure in Pascals
float temperature; // Temperature in Celsius
float humidity; // Humidity in %
char model[32] = ""; // Model id of device
char serial[7] = ""; // Serial number of device
char message[128]; // Message contained for info lines
char poll_cmd[] = "POLL 1000\r\n";
// Whether an info line has been read yet
bool info_line_read = false;
for (int i = 0;; i++) {
if (!info_line_read) {
//PurgeComm(COM, PURGE_TXCLEAR);
if (!WriteFile(COM, poll_cmd, sizeof poll_cmd -1, NULL, NULL)) {
printf("Write error = %i", GetLastError());
}
}
switch (read_line(COM, line)) {
case SUCCESS: // Here, the code that runs when everything is fine
if (line[0] == 'I') {
// For info lines (the POLL response in this case)
sscanf_s(
line,
"I,%[^,],%[^,],%[^,]",
model, (int) sizeof model,
serial, (int) sizeof serial,
message, (int) sizeof message
);
printf("\n%s\n", message);
info_line_read = true;
}
else {
t = time(NULL);
localtime_s(&localt, &t);
strftime(timestr, 20, "%F %T", &localt); // YYYY-MM-DD HH:MM:SS
/**
* Interpret the line and save the result into the variables.
* The format string to use would depend on the sensor, this example was made with the PTH sensor in mind.
* Refer to these resources to learn more on how to do so
* Format strings for the scanf functions : cplusplus.com/reference/cstdio/scanf/
* Dracal sensor VCP mode output format : dracal.com/en/usage-guides/vcp_howto
**/
sscanf_s(line, "%*c,%*[^,],%*[^,],,%i,%*2c,%f,%*c,%f", &pressure, &temperature, &humidity);
// This is where you would put your code to be executed on data.
printf(
"\n%s %s @ %s\nP = %i Pa\nT = %.2f C\nH = %.2f %%\n",
model, serial, timestr,
pressure, temperature, humidity
);
}
break;
case READ_ERROR: // This may happen if eg. the device is unplugged
return EXIT_FAILURE;
case INTEGRITY_ERROR: // When the integrity check failed
if (i != 0) {
// First line is likely to be garbage, no need to warn us about it
printf("Integrity error on line %i: \"%s\"", i, line);
}
break;
}
}
CloseHandle(COM); // Close the serial port
return EXIT_SUCCESS;
}
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).