Accès Web facile aux applications LabVIEW VI PHP via le serveur ActiveX

Pendant de nombreuses années, LabVIEW a été en mesure de «lier» le Web aux périphériques VI sans aucun paramètre de publication et de serveur compliqué de LabVIEW, en utilisant uniquement un serveur ActiveX intégré. L'édition communautaire LabVIEW 2020 ne fait pas exception.

Pour LabVIEW, il existe actuellement plusieurs façons de publier des périphériques virtuels sur le Web, nécessitant différents niveaux de connaissances et offrant différentes capacités. Dans cet article, je ne vais pas les décrire, mais je veux vous présenter l'utilisation non standard du serveur ActiveX / COM intégré à LabVIEW pour organiser l'accès Web au VI, ainsi que pour gérer l'environnement LabVIEW lui-même. Bien qu'ActiveX / COM soit déjà une ancienne technologie qui continue de vivre sous Windows, c'est grâce au serveur ActiveX intégré que vous pouvez facilement gérer les appareils LabVIEW et VI, y compris via le Web.

La première chose à faire est d'inclure ce même serveur ActiveX dans LabVIEW, cela se fait dans les paramètres d'environnement: Outils-> Options-> VI Server, la case à cocher ActiveX.


Vous pouvez vérifier que le serveur est allumé et y a accès, à l'aide d'un simple script dans VBScript. Vous devez créer le fichier texte labview_test.vbs sur le bureau et le remplir avec le contenu suivant:

Dim obj
Set obj = CreateObject("LabVIEW.Application")
'Dim vi
'Set vi = obj.GetVIReference("C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi")
WScript.Echo(obj.AppName & " ver: " & obj.Version)
'WScript.Echo(vi.GetControlValue("Count"))
'Set vi = Nothing
Set obj = Nothing

Avant d'exécuter le script, exécutez l'environnement LabVIEW. Cependant, cela fonctionnera sans lancer l'environnement au préalable. Pendant l'exécution du script, l'instance LabVIEW sera lancée en tant que serveur ActiveX / COM, et à la fin du script, l'instance sera fermée, vous devrez donc attendre jusqu'à ce que tous ces «chargements et déchargements». La sortie de labview_test.vbs sera le nom de l'application racine et sa version.


Ensuite, j'ai créé un simple périphérique VI «ActiveX Server.vi». Il contient plusieurs commandes et fonctions auxiliaires. Ce VI, nous allons le charger et le gérer.


De LabVIEW, nous n'avons besoin de rien d'autre. Vous pouvez maintenant commencer avec les couches Web.

Chemin épineux


Au début, j'ai expérimenté un peu avec le serveur Web Windows standard Microsoft IIS. J'ai essayé de créer des pages ASP sur VBScript avec le contenu suivant:

<% @language = "vbscript" %>
<html><body>
<p>ASP can output HTML tags as well as plain text</p>
<%
	Dim obj
	Set obj = CreateObject("LabVIEW.Application")
	response.write(obj.AppName & " ver: " & obj.Version & "<br>" & vbCr)
	Dim vi
	Set vi = obj.GetVIReference("C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi")
	response.write(vi.GetControlValue("Count") & vbCr)
	set vi = Nothing
	set obj = Nothing
%>
</body></html>

La méthode GetVIReference () charge le VI en mémoire et établit une connexion avec lui. Paramètre principal: chemin absolu vers le VI sélectionné.

Sortie du script vers le navigateur:

LabVIEW.exe ver: 20.0
123 

Certes, j'ai dû bricoler un peu avec les paramètres du pool d'applications IIS et la vérification des utilisateurs anonymes, que j'ai configurés pour l'utilisateur Windows actuel.

J'ai décidé de ne pas approfondir ASP et je suis passé à PHP. Pour IIS, j'ai configuré le démon PHP FastCGI. Je ne vous donne pas les paramètres, ils ne sont pas importants pour la partie principale de cet article. PHP a également réussi à accéder à l'objet LabVIEW COM par type:

$obj = new COM('LabVIEW.Application');

Dans les deux cas, lorsque LabVIEW est en cours d'exécution et que le VI y est ouvert (ActiveX Server.vi). Lors de la demande d'un script PHP (ASP), une nouvelle instance de LabVIEW.exe a été lancée en parallèle (et pendant une durée considérable), puis en utilisant la méthode GetVIReference (), elle a chargé sa propre instance d'ActiveX Server.vi. Une fois le script PHP terminé, l'instance LabVIEW a été fermée. Ceux. il n'y avait aucune intersection avec une instance déjà en cours d'exécution de l'environnement LabVIEW. En utilisant l'utilitaire Process Explorer bien connu, cela est bien observé. Le "jeu" avec les paramètres du pool d'applications IIS n'a pas non plus donné de résultat spécial. Pour ma part, j'ai conclu que IIS fonctionne comme un démon système au nom du système, et donc une instance distincte de LabVIEW.exe est créée qui est liée au contexte du système, et je ne peux pas réutiliser une instance déjà ouverte en tant qu'utilisateur Windows.

L'idée est alors venue d'essayer un serveur Web tiers fonctionnant en mode application simple pour le compte de l'utilisateur actuel. Le choix s'est porté sur NGINX, d'ailleurs, je l'ai déjà utilisé comme proxy inverse pour LabVIEW WebServices.

Nginx


Nous prenons la version actuelle disponible de nginx pour Windows. Actuellement nginx-1.17.10. Pour connecter PHP avec NGINX, j'ai utilisé la description suivante .

Configuration NGINX minimale simple. J'ai un fichier: c: \ nginx-1.17.10 \ conf \ nginx.conf
Ajout de la liste du répertoire racine au navigateur:

nginx.conf:

location / {
	root   html;
	index  index.html index.htm;
	autoindex on;
}

Activer PHP via FastCGI à la racine du serveur:

# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
location ~ \.php$ {
	root           html;
	fastcgi_pass   127.0.0.1:9000;
	fastcgi_index  index.php;
	fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
	include        fastcgi_params;
}

Php


Nous prenons la version actuelle de PHP pour Windows . J'ai utilisé php-7.4.5-nts-Win32-vc15-x64.zip, je l'aurai situé dans c: \ php-7.4.5-nts-Win32-vc15-x64

Renommer et configurer php.ini (qui est php. ini-development à partir des archives). Apportez les modifications suivantes:

php.ini:

short_open_tag = On
html_errors = On
error_reporting = E_ALL & ~E_NOTICE
extension_dir = "ext"
extension=gd2
extension=php_com_dotnet.dll

Ici, la bibliothèque GD pour travailler avec des images est connectée (si vous avez besoin d'obtenir des images de LabVIEW) et le module php_com_dotnet.dll pour travailler avec des objets ActiveX / COM en PHP.

En travaillant avec COM en PHP, un bug désagréable a été découvert lors du travail avec des chaînes (VT_BSTR) contenant 0x0 caractères dans le corps. Il est résolu en remplaçant php_com_dotnet.dll par recompilé avec le correctif. La description du bogue et le correctif avec le correctif peuvent être trouvés ici . Malheureusement, il n'est toujours pas officiellement corrigé en PHP. J'ai reconstruit php_com_dotnet.dll (pour php-7.4.5-nts-Win32-vc15-x64), le php_com_dotnet.dll fixe peut être trouvé sur le lien . Un guide pour PHP auto-construit et les extensions peut être trouvé ici .

Par défaut, NGINX s'exécutera sur le port TCP 80, le démon PHP FastCGI sur le port 9000, vérifiez qu'aucune autre application fonctionnelle n'utilise ces ports.

Le démarrage et l'arrêt des démons NGINX et PHP FastCGI peuvent être organisés de plusieurs manières. Ces scripts cmd ont pris forme pour mes besoins de débogage: start-restart-all.cmd qui démarre / redémarre en arrière-plan (sans fenêtre de démon ouverte) et arrête kill-all.cmd que j'ai mis dans le répertoire NGINX. Utilitaire Run Hidden Console utilisé, tiré de la description .

start-restart-all.cmd:

rem @echo off
set PHP_FCGI_MAX_REQUESTS=0
@echo Shutting down servers...
taskkill /f /IM nginx.exe
taskkill /f /IM php-cgi.exe
@timeout 1
@echo Starting servers...
@rem start /b /D "C:\php-7.4.5-nts-Win32-vc15-x64" php-cgi.exe -b 127.0.0.1:9000
RunHiddenConsole.exe "C:\php-7.4.5-nts-Win32-vc15-x64\php-cgi.exe" -b 127.0.0.1:9000
start /b /D "c:\nginx-1.17.10\" nginx.exe
@timeout 3

kill-all.cmd:

taskkill /f /IM nginx.exe
taskkill /f /IM php-cgi.exe
pause

Je veux faire attention à la variable d'environnement PHP_FCGI_MAX_REQUESTS. Par défaut, il est de 500. Et après 500 requêtes, le démon PHP FastCGI terminera son travail, j'ai donc désactivé ce compteur pour le débogage. Voici une citation de la documentation pour réflexion:

Ce comportement PHP peut être désactivé en définissant PHP_FCGI_MAX_REQUESTS à 0, mais cela peut être un problème si l'application PHP perd des ressources. Alternativement, PHP_FCGI_MAX_REQUESTS peut être défini sur une valeur beaucoup plus élevée que la valeur par défaut pour réduire la fréquence de ce problème.

J'ai écrit 2 scripts de test PHP labview.php, labview_png.php, qui doivent être placés à la racine du serveur Web C: \ nginx-1.17.10 \ html
labview.php - c'est le principal exemple de script
labview_png.php - retourne une image PNG à partir d'une chaîne de type VT_BSTR lue depuis le serveur LabVIEW ActiveX.

labview.php
<?php
if(strpos(exec('tasklist /FI "IMAGENAME eq LabVIEW.exe" /NH'), 'LabVIEW.exe') === false)
	exit("  LabVIEW.exe");?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
	<title>LabVIEW PHP COM example</title>
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
	<script>
		// setTimeout(function(){
		//	window.location.reload(1);
		// }, 3000);
		// setInterval(function() {
		//	var myImageElement = document.getElementById('myImage');
		// 	myImageElement.src = 'labview_png.php?rand=' + Math.random();
		//}, 200);
	 
		$(document).ready(function(){
			setInterval(function(){
				$("#png").attr('src', 'labview_png.php?rand=' + Math.random());
				$("#auto").load(location.href + " #auto");
			}, 1000);
		});
</script>
	
</head> 
<body>
<?php
//phpinfo();
echo '_GET val: ';
foreach ($_GET as $key => $value)
	echo "$key=$value, ";
echo '<br>', PHP_EOL;

echo '_POST val: ';
foreach ($_POST as $key => $value)
	echo "$key=$value, ";
echo '<br>', PHP_EOL;

define('FPStateInfo', ['Invalid', 'Standard', 'Closed', 'Hidden', 'Minimized', 'Maximized']);
define('ExecStateInfo', ['eBad 0 VI has errors; it cannot run', 'eIdle 1 VI is not running, but the VI is in memory.', 'eRunTopLevel 2 VI is running as a top-level VI', 'eRunning 3 VI is running as a subV']);

$obj = new COM('LabVIEW.Application');
//com_print_typeinfo($obj);

$vi = $obj->GetVIReference('C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi');

//$vi->OpenFrontPanel();

echo '<form action="" method="post">';
echo '<input type="button" value="Refresh page" onClick=\'window.location.href=window.location.href\'>', PHP_EOL;

$fpstate = $vi->FPState();
$vistate = $vi->ExecState();

if ($_POST['action']==='run_vi' && $vistate <= 1) {
	$vi->Run(true); // async Boolean If TRUE, you do not need to wait for the VI to finish running. The default is FALSE.
} elseif ($_POST['action']==='stop_vi' && $vistate > 1) {
	//$vi->SetControlValue('stop', true);
	//sleep(1);
	$vi->Abort();
} elseif ($_POST['action']==='open_fp' && $fpstate==2) {
	$vi->OpenFrontPanel();
} elseif ($_POST['action']==='close_fp' && $fpstate!=2) {
	$vi->CloseFrontPanel();
}

if ($_POST['Count2']) {
	$vi->SetControlValue('Count2', $_POST['Count2']);
}

echo '<h3>SetControlValue(\'Count2\'):</h3>', PHP_EOL;
echo '<input onchange="this.form.submit()" type="number" name="Count2" value="', $vi->GetControlValue('Count2'), '">', PHP_EOL;

echo '<div id="auto">';

echo '<h3>AppName / Version:</h3>', PHP_EOL;
echo $obj->AppName(), ' / ', $obj->Version(), '<br>', PHP_EOL;

echo '<h3>ExportedVIs:</h3>', PHP_EOL;
foreach ($obj->ExportedVIs() as $value)
	echo $value, '<br>', PHP_EOL;

echo '<h3>FPState:</h3>', PHP_EOL;
$fpstate = $vi->FPState();
echo $fpstate, ', ', FPStateInfo[$fpstate], PHP_EOL;

echo '<button name="action" type="submit" value="open_fp">OpenFrontPanel</button>', PHP_EOL;
echo '<button name="action" type="submit" value="close_fp">CloseFrontPanel</button>', PHP_EOL;

echo '<h3>ExecState:</h3>', PHP_EOL;
$vistate = $vi->ExecState();

if ($vistate > 1) {
	echo '<font color="blue">', $vistate, ', ', ExecStateInfo[$vistate], '</font>', PHP_EOL;
} else {
	echo $vistate, ', ', ExecStateInfo[$vistate], PHP_EOL;
}

echo '<button name="action" type="submit" value="run_vi">Run VI</button>', PHP_EOL;
echo '<button name="action" type="submit" value="stop_vi">Abort VI</button>', PHP_EOL;
echo '</form>', PHP_EOL;

echo '<h3>GetControlValue(\'Count\') / GetControlValue(\'Count2\'):</h3>', PHP_EOL;
echo $vi->GetControlValue('Count'), ' / ', $vi->GetControlValue('Count2'), PHP_EOL;
//echo $vi->SetControlValue('Count2', $vi->GetControlValue('Count')+1), PHP_EOL;

echo '<h3>Array1:</h3>', PHP_EOL;
foreach ($vi->GetControlValue('Array1') as $value)
	echo $value, '<br>', PHP_EOL;

//$png_data = new variant(null, VT_UI1);
//$png_data = variant_set_type($vi->GetControlValue('png data'), VT_UI1);
//echo variant_cast($vi->GetControlValue('png1'), VT_BSTR), PHP_EOL;
//echo mb_strlen($vi->GetControlValue('String1')), PHP_EOL;
//echo variant_get_type($vi->GetControlValue('png1')), PHP_EOL;

echo '<h3>PNG data:</h3>', PHP_EOL;
$png_data = $vi->GetControlValue('PNG data');
echo 'PNG size:' , strlen($png_data), '<br>', PHP_EOL;


echo '</div>';

if ($vistate > 1 && $fpstate!=2) {
	echo '<img src="labview_png.php" id="png">';
}

// variant_set_type($variant, VT_BSTR)
//$png_data = variant_cast($vi->GetControlValue('png data'), VT_U1);


//echo  variant_get_type($png_data), PHP_EOL;
echo $vi->SetControlValue('String1', "123\x00555321");
//com_print_typeinfo($vi);
$obj = null;
?>
</body>
</html>


labview_png.php
<?php
if(strpos(exec('tasklist /FI "IMAGENAME eq LabVIEW.exe" /NH'), 'LabVIEW.exe') === false)
	exit("  LabVIEW.exe");
$obj = new COM('LabVIEW.Application');
$vi = $obj->GetVIReference('C:\Users\Dell\Desktop\LabVIEW Web ActiveX\ActiveX Server Executable _LV2012_NI Verified\Executable as ActiveX Server\ActiveX Server.vi');

$data = $vi->GetControlValue('PNG data');

$im = imagecreatefromstring($data);
if ($im !== false) {
    header('Content-Type: image/png');
    imagepng($im);
    imagedestroy($im);
}
else {
    echo ' .';
}
$obj = null;
?>


Il est préférable d'exécuter des scripts lorsque l'environnement LabVIEW est en cours d'exécution, auquel cas les scripts réutilisent une instance déjà ouverte de LabVIEW. Au lieu de créer et de fermer une instance COM à chaque appel de script. Mon script utilise un peu AJAX et un "redémarrage", et ne pas réutiliser LabVIEW entraînera un "marathon de tortue" de démarrages et de terminaisons séquentiels de labview.exe.

Revue vidéo:


Annexe. Configuration de NGINX en tant que proxy inverse avec l'authentification d'accès HTTP de base pour fonctionner avec WebServices LabVIEW


Il y a quelque temps, j'ai expérimenté un peu avec WebServices LabVIEW (pour dire la vérité sur une version assez ancienne de LabVIEW). Puis j'ai découvert que les pages (ressources WebServices) n'ont pas de contrôle d'accès simple. Il a été proposé de configurer les utilisateurs dans le serveur d'applications et d'utiliser le Microsoft Silverlight «mort». Et j'avais besoin d'une option simple, comme la vérification du mot de passe d'authentification d'accès de base HTTP.

J'ai utilisé NGINX et je l'ai configuré comme proxy Web inversé avec auth_basic activé. En utilisant les paramètres ci-dessous, lors de l'accès à l'adresse http: // nom_serveur: 5500 après avoir entré le mot de passe, l'utilisateur obtient l'accès à l'application WebService en cours d'exécution à http://127.0.0.1:8001/webservice1/.
Toutes les ressources de l'application webservice1 sont protégées.

nginx.conf:

server {
    listen       5500;
    server_name  localhost;
    location / {
        auth_basic "Unauthorized";
        auth_basic_user_file htpasswd;
        root html;
        #autoindex on;
        #index index.html index.htm;
        proxy_pass http://127.0.0.1:8001/webservice1/;
    }
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   html;
    }
}

et le fichier htpasswd avec les mots de passe des utilisateurs:

admin:{PLAIN}1

En développant cette idée, vous pouvez activer l'accès au proxy NGINX via HTTPS et laisser HTTP de NGINX à LabVIEW.

All Articles