Fácil acesso da Web a aplicativos PHP do LabVIEW VI via servidor ActiveX

Por muitos anos, o LabVIEW foi capaz de “amarrar” a Web a dispositivos VI sem nenhuma configuração complicada de publicação e servidor do LabVIEW, usando apenas um servidor ActiveX embutido. A edição comunitária do LabVIEW 2020 não é exceção.

No momento, para o LabVIEW, existem várias maneiras de publicar dispositivos virtuais na Web, exigindo diferentes níveis de conhecimento e fornecendo recursos diferentes. Neste artigo, não vou descrevê-los, mas quero apresentar a você o uso não padrão do servidor ActiveX / COM integrado no LabVIEW para organizar o acesso da Web ao VI, bem como para controlar o próprio ambiente do LabVIEW. Embora o ActiveX / COM já seja uma tecnologia antiga que continua funcionando no Windows, é através do servidor ActiveX integrado que você pode gerenciar facilmente os dispositivos LabVIEW e VI, inclusive via Web.

A primeira coisa a fazer é incluir esse mesmo servidor ActiveX no LabVIEW, isso é feito nas configurações do ambiente: Ferramentas-> Opções-> VI Server, a caixa de seleção ActiveX.


Você pode verificar se o servidor está ligado e tem acesso a ele, usando um script simples no VBScript. Você precisa criar o arquivo de texto labview_test.vbs na área de trabalho e preenchê-lo com o seguinte conteúdo:

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 executar o script, execute o ambiente LabVIEW. No entanto, ele funcionará sem primeiro iniciar o ambiente. Durante a execução do script, a instância do LabVIEW será lançada como um servidor ActiveX / COM e, após a conclusão do script, a instância será fechada, assim você terá que esperar até que tudo isso seja carregado e descarregado. A saída do labview_test.vbs será o nome do aplicativo raiz e sua versão.


Em seguida, criei um dispositivo VI simples, “ActiveX Server.vi”. Ele contém vários controles e funções auxiliares. Este VI iremos carregá-lo e gerenciá-lo.


No LabVIEW, não precisamos de mais nada. Agora você pode começar com as camadas da Web.

Caminho espinhoso


No começo, experimentei um pouco com o servidor Web padrão do Windows, o Microsoft IIS. Tentei criar páginas ASP no VBScript com o seguinte conteúdo:

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

O método GetVIReference () carrega o VI na memória e estabelece uma conexão com ele. Parâmetro principal: caminho absoluto para o VI selecionado.

Saída de script para o navegador:

LabVIEW.exe ver: 20.0
123 

É verdade que tive que mexer um pouco nas configurações do pool de aplicativos IIS e na verificação anônima de usuário, que configurei para o usuário atual do Windows.

Eu decidi não ir fundo no ASP e mudei para o PHP. Para o IIS, configurei o daemon PHP FastCGI. Não dou as configurações. Elas não são importantes para a parte principal deste artigo. O PHP também conseguiu acessar o objeto LabVIEW COM por tipo:

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

Nos dois casos, quando o LabVIEW está sendo executado e o VI é aberto (ActiveX Server.vi). Ao solicitar um script PHP (ASP), uma nova instância do LabVIEW.exe foi lançada em paralelo (e por um período considerável de tempo) e, usando o método GetVIReference (), carregou sua própria instância do ActiveX Server.vi. Após a conclusão do script PHP, a instância do LabVIEW foi fechada. Essa. não houve interseção com uma instância já em execução do ambiente LabVIEW. Usando o conhecido utilitário Process Explorer, isso é bem observado. O "jogo" com as configurações do pool de aplicativos IIS também não deu um resultado especial. Concluí que o IIS funciona como um daemon do sistema em nome do sistema e, portanto, é criada uma instância separada do LabVIEW.exe, vinculada ao contexto do sistema, e não consigo reutilizar uma instância já aberta como usuário do Windows.

Em seguida, surgiu a idéia de tentar um servidor da web de terceiros executando no modo de aplicativo simples em nome do usuário atual. A escolha recaiu sobre o NGINX; além disso, eu já o usei como proxy reverso para os LabVIEW WebServices.

Nginx


Tomamos a versão atual disponível do nginx para Windows. Atualmente nginx-1.17.10. Para conectar o PHP ao NGINX, usei a seguinte descrição .

Configuração mínima simples de NGINX. Eu tenho um arquivo: c: \ nginx-1.17.10 \ conf \ nginx.conf
Adicionando a listagem do diretório raiz ao navegador:

nginx.conf:

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

Ativando o PHP via FastCGI na raiz do 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 a versão atual do PHP para Windows . Eu usei o php-7.4.5-nts-Win32-vc15-x64.zip, vou tê-lo localizado em c: \ php-7.4.5-nts-Win32-vc15-x64

Renomeie e configure o php.ini (que é php. desenvolvimento do arquivo). Faça as seguintes alterações:

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

Aqui está conectada a biblioteca GD para trabalhar com imagens (se você precisar obter algumas imagens do LabVIEW) e o módulo php_com_dotnet.dll para trabalhar com objetos ActiveX / COM em PHP.

Ao trabalhar com COM no PHP, um bug desagradável foi descoberto ao trabalhar com seqüências de caracteres (VT_BSTR) contendo 0x0 caracteres no corpo. É resolvido substituindo php_com_dotnet.dll por recompilado com a correção. A descrição do bug e o patch com a correção podem ser encontrados aqui . Infelizmente, ainda não foi oficialmente corrigido no PHP. Eu reconstruí o php_com_dotnet.dll (para php-7.4.5-nts-Win32-vc15-x64), o php_com_dotnet.dll fixo pode ser encontrado no link . Um guia para criação automática de PHP e extensões pode ser encontrado aqui .

Por padrão, o NGINX estará em execução na porta TCP 80, o daemon PHP FastCGI na porta 9000, verifique se não há outros aplicativos ativos usando essas portas.

O início e a parada dos daemons NGINX e PHP FastCGI podem ser organizados de várias maneiras. Esses scripts cmd tomaram forma para minhas necessidades de depuração: start-restart-all.cmd que inicia / reinicia em segundo plano (sem as janelas demoníacas abertas) e para o kill-all.cmd que eu coloquei no diretório NGINX. Usado o utilitário Run Hidden Console, retirado da descrição .

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

Quero prestar atenção à variável de ambiente PHP_FCGI_MAX_REQUESTS. Por padrão, é 500. E após 500 solicitações, o daemon PHP FastCGI concluirá seu trabalho, então desabilitei esse contador para depuração. Aqui está uma citação da documentação para reflexão:

Esse comportamento do PHP pode ser desativado configurando PHP_FCGI_MAX_REQUESTS como 0, mas isso pode ser um problema se o aplicativo PHP vazar recursos. Como alternativa, PHP_FCGI_MAX_REQUESTS pode ser definido com um valor muito maior que o padrão para reduzir a frequência desse problema.

Escrevi 2 scripts de teste PHP labview.php, labview_png.php, que devem ser colocados na raiz do servidor Web C: \ nginx-1.17.10 \ html
labview.php - este é o principal exemplo de script
labview_png.php - retorna uma imagem PNG de uma sequência do tipo VT_BSTR lida no 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;
?>


É melhor executar scripts quando o ambiente do LabVIEW estiver em execução. Nesse caso, os scripts reutilizarão uma instância já aberta do LabVIEW. Em vez de criar e fechar uma instância COM com cada chamada de script. Meu script usa um pouco de AJAX e "reinicia", e não reutilizar o LabVIEW resultará em uma "maratona de tartaruga" de partidas e terminações seqüenciais do labview.exe.

Revisão de vídeo:


Apêndice. Configurando o NGINX como um proxy reverso com autenticação de acesso HTTP Basic para trabalhar com o WebServices LabVIEW


Há algum tempo, experimentei um pouco o WebServices LabVIEW (para dizer a verdade em uma versão antiga do LabVIEW). Descobri que as páginas (recursos de WebServices) não têm um controle de acesso simples. Foi proposto configurar usuários no servidor de aplicativos e usar o Microsoft Silverlight "morto". E eu precisava de uma opção simples, como a verificação de senha de autenticação de acesso HTTP Basic.

Eu usei o NGINX e o configurei como um proxy da Web reverso com o auth_basic ativado. Usando as configurações abaixo, ao acessar o endereço http: // nome_do_servidor: 5500 após digitar a senha, o usuário obtém acesso ao aplicativo WebService em execução em http://127.0.0.1:8001/webservice1/.
Todos os recursos do aplicativo webservice1 estão 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;
    }
}

e o arquivo htpasswd com senhas de usuário:

admin:{PLAIN}1

Desenvolvendo ainda mais essa idéia, você pode habilitar o acesso ao proxy NGINX via HTTPS e deixar o HTTP do NGINX para o LabVIEW.

All Articles