Fácil acceso web a aplicaciones PHP LabVIEW VI a través del servidor ActiveX

Durante muchos años, LabVIEW ha sido capaz de "atornillar" la Web a dispositivos VI sin ninguna configuración complicada de publicación y servidor de LabVIEW, utilizando solo un servidor ActiveX incorporado. La edición de LabVIEW 2020 Community no es una excepción.

Para LabVIEW en este momento hay varias formas de publicar dispositivos virtuales en la Web, que requieren diferentes niveles de conocimiento y proporcionan diferentes capacidades. En este artículo no voy a describirlos, pero quiero presentarles el uso no estándar del servidor ActiveX / COM integrado en LabVIEW para organizar el acceso web a VI, así como para administrar el entorno de LabVIEW. Aunque ActiveX / COM ya es una tecnología antigua que continúa viviendo en Windows, es a través del servidor ActiveX integrado que puede administrar fácilmente dispositivos LabVIEW y VI, incluso a través de la Web.

Lo primero que debe hacer es incluir este mismo servidor ActiveX en LabVIEW, esto se hace en la configuración del entorno: Herramientas-> Opciones-> Servidor VI, la casilla de verificación ActiveX.


Puede verificar que el servidor esté encendido y tenga acceso a él, utilizando un script simple en VBScript. Debe crear el archivo de texto labview_test.vbs en el escritorio y llenarlo con el siguiente contenido:

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

Antes de ejecutar el script, ejecute el entorno LabVIEW. Sin embargo, funcionará sin primero lanzar el entorno. Durante la ejecución del script, la instancia de LabVIEW se lanzará como un servidor ActiveX / COM, y al completar el script, la instancia se cerrará, por lo que tendrá que esperar hasta que todo esto "se cargue y descargue". El resultado de labview_test.vbs será el nombre de la aplicación raíz y su versión.


A continuación, creé un dispositivo VI simple "ActiveX Server.vi". Contiene varios controles y funciones auxiliares. Este VI lo cargaremos y gestionaremos.


De LabVIEW no necesitamos nada más. Ahora puede comenzar con las capas web.

Camino espinoso


Al principio experimenté un poco con el servidor web estándar de Windows Microsoft IIS. Intenté crear páginas ASP en VBScript con los siguientes contenidos:

<% @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>

El método GetVIReference () carga el VI en la memoria y establece una conexión con él. Parámetro principal: ruta absoluta al VI seleccionado.

Salida de script al navegador:

LabVIEW.exe ver: 20.0
123 

Es cierto que tuve que jugar un poco con la configuración del grupo de aplicaciones IIS y la verificación de usuarios anónimos, que configuré para el usuario actual de Windows.

Decidí no profundizar en ASP y me cambié a PHP. Para IIS, configuré el demonio PHP FastCGI. No le doy las configuraciones; no son importantes para la parte principal de este artículo. PHP también logró acceder al objeto LabVIEW COM por tipo:

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

En ambos casos, cuando LabVIEW se está ejecutando y VI está abierto en él (ActiveX Server.vi). Al solicitar un script PHP (ASP), se lanzó una nueva instancia de LabVIEW.exe en paralelo (y durante un tiempo considerable), luego, utilizando el método GetVIReference (), cargó su propia instancia de ActiveX Server.vi. Al completar el script PHP, la instancia de LabVIEW se cerró. Aquellos. no hubo intersección con una instancia ya en ejecución del entorno de LabVIEW. Usando la conocida herramienta Process Explorer, esto se observa bien. El "juego" con la configuración del grupo de aplicaciones IIS tampoco dio un resultado especial. Por mi parte, concluí que IIS funciona como un demonio del sistema en nombre del sistema y, por lo tanto, se crea una instancia separada de LabVIEW.exe que está vinculada al contexto del sistema, y ​​no puedo volver a utilizar una instancia ya abierta como usuario de Windows.

Entonces surgió la idea de probar un servidor web de terceros que se ejecuta en el modo de aplicación simple en nombre del usuario actual. La elección recayó en NGINX, además, ya la usé como proxy inverso para los servicios web de LabVIEW.

Nginx


Tomamos la versión actual disponible de nginx para Windows. Actualmente nginx-1.17.10. Para conectar PHP con NGINX, utilicé la siguiente descripción .

Configuración mínima simple de NGINX. Tengo un archivo: c: \ nginx-1.17.10 \ conf \ nginx.conf
Agregando la lista del directorio raíz al navegador:

nginx.conf:

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

Habilitación de PHP a través de FastCGI en la raíz del servidor:

# 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


Tomamos la versión actual de PHP para Windows . Usé php-7.4.5-nts-Win32-vc15-x64.zip, lo tendré ubicado en c: \ php-7.4.5-nts-Win32-vc15-x64

Rename y configure php.ini (que es php. desarrollo inicial desde el archivo). Realice los siguientes cambios:

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

Aquí está conectada la biblioteca GD para trabajar con imágenes (si necesita obtener algunas imágenes de LabVIEW) y el módulo php_com_dotnet.dll para trabajar con objetos ActiveX / COM en PHP.

Mientras trabajaba con COM en PHP, se descubrió un error desagradable al trabajar con cadenas (VT_BSTR) que contenían 0x0 caracteres en el cuerpo. Se resuelve reemplazando php_com_dotnet.dll con recompilado con la solución. La descripción del error y el parche con la solución se pueden encontrar aquí . Desafortunadamente, todavía no está oficialmente arreglado en PHP. Reconstruí php_com_dotnet.dll (para php-7.4.5-nts-Win32-vc15-x64), el php_com_dotnet.dll fijo se puede encontrar en el enlace . Puede encontrar una guía para PHP de autoconstrucción y extensiones aquí .

De forma predeterminada, NGINX se ejecutará en el puerto TCP 80, el demonio PHP FastCGI en el puerto 9000, verifique que no haya otras aplicaciones que funcionen con estos puertos.

Iniciar y detener los demonios NGINX y PHP FastCGI se puede organizar de muchas maneras. Estos scripts de cmd tomaron forma para mis necesidades de depuración: start-restart-all.cmd que se inicia / reinicia en segundo plano (sin ventanas demonio abiertas) y detiene kill-all.cmd que puse en el directorio NGINX. Utilidad Run Hidden Console usada, tomada de la descripción .

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

Quiero prestar atención a la variable de entorno PHP_FCGI_MAX_REQUESTS. Por defecto, son 500. Y después de 500 solicitudes, el demonio PHP FastCGI completará su trabajo, por lo que deshabilité este contador para la depuración. Aquí hay una cita de la documentación para la reflexión:

Este comportamiento de PHP se puede deshabilitar estableciendo PHP_FCGI_MAX_REQUESTS en 0, pero eso puede ser un problema si la aplicación PHP pierde recursos. Alternativamente, PHP_FCGI_MAX_REQUESTS se puede establecer en un valor mucho más alto que el predeterminado para reducir la frecuencia de este problema.

Escribí 2 scripts de prueba PHP labview.php, labview_png.php, que deben colocarse en la raíz del servidor web C: \ nginx-1.17.10 \ html
labview.php - este es el script de ejemplo principal
labview_png.php - devuelve una imagen PNG de una cadena del tipo VT_BSTR leída del servidor 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;
?>


Es mejor ejecutar scripts cuando se ejecuta el entorno de LabVIEW, en cuyo caso los scripts reutilizarán una instancia ya abierta de LabVIEW. En lugar de crear y cerrar una instancia COM con cada llamada de script. Mi script usa un poco de AJAX y "reinicio", y no reutilizar LabVIEW resultará en un "maratón de tortugas" de inicios y terminaciones secuenciales de labview.exe.

Revisión de video:


Apéndice. Configuración de NGINX como proxy inverso con autenticación de acceso básico HTTP para trabajar con WebServices LabVIEW


Hace algún tiempo experimenté un poco con WebServices LabVIEW (para decir la verdad en una versión bastante antigua de LabVIEW). Luego descubrí que las páginas (recursos de WebServices) no tienen un control de acceso simple. Se propuso configurar usuarios en el Servidor de aplicaciones y usar el Microsoft Silverlight "muerto". Y necesitaba alguna opción simple, como la verificación de contraseña de autenticación de acceso básico HTTP.

Usé NGINX y lo configuré como un proxy web inverso con auth_basic habilitado. Usando la configuración a continuación, al acceder a la dirección http: // nombre_servidor: 5500 después de ingresar la contraseña, el usuario obtiene acceso a la aplicación WebService que se ejecuta en http://127.0.0.1:8001/webservice1/.
Todos los recursos de la aplicación webservice1 están protegidos.

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;
    }
}

y el archivo htpasswd con contraseñas de usuario:

admin:{PLAIN}1

Desarrollando aún más esta idea, puede habilitar el acceso al proxy NGINX a través de HTTPS y dejar HTTP de NGINX a LabVIEW.

All Articles