Chamadas GET/POST com o ScadaBR 1.2

Neste tutorial veremos como fazer chamadas HTTP GET ou POST utilizando os recursos de script do ScadaBR 1.2 (scripting, data point meta, etc). Este tipo de chamada pode ser utilizado para, por exemplo, enviar mensagens através da API do Telegram e de outros softwares.

Ainda não está usando o ScadaBR 1.2? Está esperando o que? Baixe já!

Funcionamento dos scripts no ScadaBR

O ScadaBR é um software construído, em seu núcleo, utilizando a linguagem Java. Entretanto, alguns recursos como o data point meta e a página de Scripting utilizam-se de um sistema de programação via scripts Javascript.

Sim, até os nomes Java e Javascript são parecidos, mas trata-se de duas linguagens diferentes. Então, como é que um programa escrito em Java pode executar código escrito em outra linguagem (Javascript)? É aí que entra uma biblioteca chamada Rhino, que é responsável por interpretar o código Javascript e converter para instruções que possam ser executadas pela Máquina Java.

Assim, os scripts do ScadaBR utilizam a sintaxe definida pela Rhino, que é uma espécie de híbrido entre Javascript e Java. Você pode escrever a maioria das instruções utilizando uma sintaxe normal de Javascript, mas existe a possibilidade de acessar e instanciar classes Java como se fossem objetos Javascript. Essa é uma parte mais técnica e complexa, então, não entrarei em detalhes aqui para não perder o foco do tutorial.

O mais importante a entender agora é que, como a Rhino é executada no lado do servidor (server side), você não terá acesso a algumas coisas que são implementadas apenas no lado do cliente, pelo DOM do navegador. Entre elas estão os objetos XMLHttpRequest, o que nos obriga a realizar chamadas GET/POST por outros métodos.

Chamadas GET e POST

Uma vez que não temos como utilizar XMLHttpRequest, utilizaremos a biblioteca Apache HttpClient, que já vem incluída no ScadaBR 1.2. Para facilitar, eu já criei duas funções que fazem esse processo automaticamente. Então, para implementar chamadas GET ou POST no ScadaBR 1.2, basta copiar no seu script a função simpleGet(URL, parameters) ou simplePost(URL, parameters). O código de ambas está logo abaixo.

Para invocar essas funções no seu script, a sintaxe é bastante simples. Você deve fornecer um parâmetro URL com uma string contendo a URL do site a ser requisitado. Além disso, para especificar quais parâmetros serão incluídos na requisição, você pode criar um array contendo objetos. Cada objeto desse array representa um parâmetro da sua requisição GET/POST e deve incluir uma entrada key, contendo o parâmetro requisitado, e uma entrada value, contendo seu respectivo valor.

Por exemplo, se quiséssemos fazer uma chamada GET para o site httbin.org/get passando como parâmetros name=ScadaBR e tutorial=GET, poderíamos escrever o código assim:

   // Definindo os parâmetros da requisição
   var params = [ { key: "name", value: "ScadaBR" }, { key: "tutorial", value: "GET" } ];

   // Salvar resposta num objeto
   var obj = simpleGet("httbin.org/get", params);

Como você pode ter observado, as funções simpleGet() e simplePost() retornam um objeto. Este objeto pode ser utilizado para recuperar dados no seu código e possui as seguintes propriedades:

   // Salvar resposta num objeto
   var obj = simpleGet("httbin.org/get", params);

   // Para acessar o código da requisição HTTP, use .status
   obj.status; // Valor numérico

   // Para acessar a resposta do servidor, use .response
   obj.response; // String. Cuidado: pode conter null!

   // Para acessar mensagens de erro (se houver), use .errors
   obj.errors; // String. Cuidado: pode conter null!

Uma lógica simples para testar se a requisição deu certo poderia ser assim:

    // Salvar resposta num objeto
    var obj = simpleGet("httbin.org/get", params);

    // Verificar se o status é OK (200)
    if (obj.status == 200) {
        // Pegar resposta
        return obj.response;
    } else {
        // Pegar mensagens de erro
        return obj.errors;
    }
Código da função simpleGet()

Copie o seguinte código para implementar a função simpleGet() e fazer chamadas GET nos seus scripts:

function simpleGet(url, parameters) {
 
    var respStatus = null;
    var respString = null;
    var respErrors = null;

    // Add parameters to the GET Url
    var getUrl = url.replace(/\/$/, "") + "?";
    for (var i in parameters) {
        var paramKey = encodeURIComponent(parameters[i].key);
        var paramValue = encodeURIComponent(parameters[i].value);
        getUrl += paramKey + "=" + paramValue + "&";
    }

    // Java classes to import
    var imports =  JavaImporter(org.apache.commons.httpclient.HttpClient,
          org.apache.commons.httpclient.methods.GetMethod,
          org.apache.commons.httpclient.DefaultHttpMethodRetryHandler,
          org.apache.commons.httpclient.params.HttpMethodParams
        );

    // Use Rhino's "with" feature
    with (imports) {
        var client = new HttpClient();

        // Create a method instance.
        var method = new GetMethod(getUrl);

        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false));

        try {
            // Execute the method.
            var statusCode = client.executeMethod(method);
            // Save the HTTP status to the response
            respStatus = statusCode;

            if (statusCode != 200) {
                // Add error messages to the response
                respErrors = "Method failed: " + String(method.getStatusLine());
            } else {
                // Read the response body.
                var responseBody = method.getResponseBodyAsString();

                // Deal with the response.
                // Use caution: ensure correct character encoding and is not binary data
                respString = String(responseBody);
            }

        } catch (e) {
            // Add error messages to the response
            respErrors = "Error occurred: " + String(e);
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }

    // Return a Javascript object with the response data
    return { status: respStatus, response: respString, errors: respErrors };
}

Código da função simplePost()

Copie o seguinte código para implementar a função simplePost() e fazer chamadas POST nos seus scripts:

function simplePost(url, parameters) {
 
    var respStatus = null;
    var respString = null;
    var respErrors = null;

    // Java classes to import
    var imports =  JavaImporter(org.apache.commons.httpclient.HttpClient,
          org.apache.commons.httpclient.methods.PostMethod,
          org.apache.commons.httpclient.NameValuePair,
          org.apache.commons.httpclient.DefaultHttpMethodRetryHandler,
          org.apache.commons.httpclient.params.HttpMethodParams
        );
 
    // Use Rhino's "with" feature
    with (imports) {
        var client = new HttpClient();

        // Create a method instance.
        var method = new PostMethod(url);
      
        // Add parameters to the POST request
        var getUrl = url + "?";
        for (var i in parameters) {
            var paramKey = parameters[i].key;
            var paramValue = parameters[i].value;
            method.addParameter(paramKey, paramValue);
        }
      
        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
            new DefaultHttpMethodRetryHandler(3, false));

        try {
            // Execute the method.
            var statusCode = client.executeMethod(method);
            // Save the HTTP status to the response
            respStatus = statusCode;
       
            if (statusCode != 200) {
                // Add error messages to the response
                respErrors = "Method failed: " + String(method.getStatusLine());
            } else {
                // Read the response body.
                var responseBody = method.getResponseBodyAsString();

                // Deal with the response.
                // Use caution: ensure correct character encoding and is not binary data
                respString = String(responseBody);
        }

        } catch (e) {
            // Add error messages to the response
            respErrors = "Error occurred: " + String(e);
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }

    // Return a Javascript object with the response data
    return { status: respStatus, response: respString, errors: respErrors };
}

Tutorial em vídeo

Se dizem que uma imagem vale mais do que mil palavras, então um vídeo deve ter um valor ainda maior. Para finalizar deixarei aqui um excelente tutorial feito pelo Fernando José, da empresa Branqs, em que ele ensina como utilizar essas funções para integrar o ScadaBR ao Telegram. E de quebra você ainda pode aprender a criar uma instância do ScadaBR no Linux!

3 curtidas

great work , working perfectly, god bless you Celsus and Fernando Jose.
Celsus could you be so kind and make an example for massaging to multi telegram users.
many thanks in advance.

Unfortunately I don’t often use the Telegram API to show you how to do this. But I think it’s possible to add a bot to a group and configure the bot to send the messages to the group chat.

Greetings,
Celso.

Funcionou perfeitamente! Obrigado! :grinning:

Para aqueles que querem usar este recurso especificamente para enviar mensagens no Telegram, aqui está um código pronto que faz isso. Você apenas precisa trocar os valores do BOT_TOKEN e do CHAT_ID. Veja como encontrar estes valores neste vídeo.

// Alterar pelo token do seu bot do Telegram
var BOT_TOKEN = "aaaaaaaaaa";
// Alterar pelo id do chat/grupo/supergrupo para o qual será enviada a mensagem
var CHAT_ID = "-2222222";

// A mensagem que será enviada, você pode personalizar este texto
var mensagem = "Mensagem de teste, bot do Telegram";

var params = [{key: "chat_id", value: CHAT_ID},
              {key: "text", value: mensagem},
              {key: "parse_mode", value: "MarkdownV2"}];
var url = "https://api.telegram.org/bot" + BOT_TOKEN + "/sendMessage";

// Faz a requisição HTTP POST:
simplePost(url, params);


function simplePost(url, parameters) {
 
    var respStatus = null;
    var respString = null;
    var respErrors = null;

    // Java classes to import
    var imports =  JavaImporter(org.apache.commons.httpclient.HttpClient,
          org.apache.commons.httpclient.methods.PostMethod,
          org.apache.commons.httpclient.NameValuePair,
          org.apache.commons.httpclient.DefaultHttpMethodRetryHandler,
          org.apache.commons.httpclient.params.HttpMethodParams
        );
 
    // Use Rhino's "with" feature
    with (imports) {
        var client = new HttpClient();

        // Create a method instance.
        var method = new PostMethod(url);
      
        // Add parameters to the POST request
        var getUrl = url + "?";
        for (var i in parameters) {
            var paramKey = parameters[i].key;
            var paramValue = parameters[i].value;
            method.addParameter(paramKey, paramValue);
        }
      
        // Provide custom retry handler is necessary
        method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
            new DefaultHttpMethodRetryHandler(3, false));

        try {
            // Execute the method.
            var statusCode = client.executeMethod(method);
            // Save the HTTP status to the response
            respStatus = statusCode;
       
            if (statusCode != 200) {
                // Add error messages to the response
                respErrors = "Method failed: " + String(method.getStatusLine());
            } else {
                // Read the response body.
                var responseBody = method.getResponseBodyAsString();

                // Deal with the response.
                // Use caution: ensure correct character encoding and is not 
                //binary data
                respString = String(responseBody);
        }

        } catch (e) {
            // Add error messages to the response
            respErrors = "Error occurred: " + String(e);
        } finally {
            // Release the connection.
            method.releaseConnection();
        }
    }
    // Return a Javascript object with the response data
    return { status: respStatus, response: respString, errors: respErrors };
}
1 curtida

Esse script funciona perfeitamente no ScadaBR 1.2, mas
apresenta erro no ScadaLTS 2.7.6.

Eu estou ciente desta limitação. Ela ocorre porque os desenvolvedores do Scada-LTS estão adicionando novos recursos de segurança para evitar que os scripts acessem recursos do servidor de forma irrestrita.

Eu ainda não sei exatamente como resolver esta questão, mas assim que eu descobrir atualizarei este tópico.

Dear all
I rise on ubuntu 22.04 sever scada-lts
Basicaly it work OK but we experiance error when user want to login from outside network
“Connect error:Whoops! Lost connection to http://79.101.37.215:8080/Scada-LTS/ws-scada/alarmLevel
Im not expert, by googling problem is located in GET/POST also http https
Looking forward for some help
I noticed when remove all acitve alarms on server (ACK ALL) than clients can login without problem
Looking forward for help if any

Atualizando sobre como alterar a política de segurança do Scada-LTS para habilitar o envio de chamadas GET/POST:

  1. Editar o arquivo env.properties do Scada-LTS, adicionando na chave scadalts.security.js.access.granted.class.regexes o valor org.apache.commons.* . A linha completa deve se parecer com isso:
scadalts.security.js.access.granted.class.regexes=java.lang.*;java.util.*;java.math.*;java.text.*;java.time.*;br.org.scadabr.*;cc.radiuino.scadabr.*;com.serotonin.*;org.scada_lts.*;org.apache.commons.*
  1. Reiniciar o serviço do Scada-LTS

Testado em novembro de 2023.

Celso, excelentes scripts e excelente explicação. Parabéns e obrigado.

Tive que fazer uma ligeira alteração, para funcionar com um end point (mais restrito na “formatação do url”) de um fornecedor, que retornava erro. Basicamente não colocar um separador de parâmetros depois do último “key=value”

Ficou assim no cliente:
 // Add parameters to the GET Url
    var getUrl = url.replace(/\/$/, "") + "?";
   
    var bFirstParam = true;
    for (var i in parameters) {
        if(!bFirstParam)
            getUrl += '&';
        var paramKey = encodeURIComponent(parameters[i].key);
        var paramValue = encodeURIComponent(parameters[i].value);
        getUrl += paramKey + "=" + paramValue;
        bFirstParam = false;
    }

(eu poderia tb ter posto o “bFirstParam = false;” no else em vez de repetir a instrução em cada ciclo)

Olhando para o código original parece-me também que o script não deve funcionar se o URL original já incluir params (o que pode acontecer). E, para casos mais estritos como o endpoint do meu fornecedor, podia-se apenas adicionar o “?” caso necessário (se não houver params não vale a pena adicionar).

Não tive estas últimas necessidade, por isso não alterei, deixei para os prós, ou para quando eu necessitar :slight_smile:

António