Scripts LUA

A linguagem de programação LUA

O projeto HIstudio adota Lua como linguagem de script para automação interna. Lua é uma escolha adequada pelas seguintes razões:

  • É simples, possuindo uma suave curva de aprendizado.

  • É projetada para ser embarcada.

  • É compacta. Consome poucos recursos do hardware onde executa.

  • É rápida. Sua execução é mais rápida do que outras linguagens interpretadas alternativas.

  • É auditável. O código-fonte da linguagem está livremente disponível, sem custo, na Internet.

  • É tecnologia nacional.

A título de exemplificação e referência rápida, provemos alguns exemplos de Lua aplicados às tarefas recorrentes de programação. Para um tutorial mais detalhado sobre a linguagem siga as referências no final desta página.

Modelo de Execução e Recursos

Diversas funcionalidades internas do HIstudio foram expostas através de uma interface de programação (API) na linguagem Lua.

O IDE permite a criação de scripts Lua e sua associação a eventos internos do HIstudio. Os eventos servem como gatilhos de disparo destes scripts. Cada script executa de forma preemptiva, ou seja, independente da execução dos demais scripts. Isso permite que um script execute operações de longa duração (cálculos, acesso a banco de dados, etc.) sem bloquear a execução dos demais scripts.

É importante notar que um script é uma unidade isolada de execução, sem compartilhar memória (variáveis) com os demais scripts. Portanto, scripts trocam dados entre si por intermédio de módulos providos na API do HIstudio .

A linguagem Lua faz distinção entre variáveis locais e globais. Na implementação do HIstudio , o escopo global é igual ao contexto de um único script, ou seja, variáveis globais definidas em um script não estão visíveis em outro script. A distinção entre variável global e local é útil quando um script define funções aninhadas, onde cada uma define um escopo próprio. O escopo global é compartilhado por todas as funções definidas em um mesmo script.

obs

As variáveis em Lua são globais por default Diferentemente da convenção de outras linguagens de programação, em Lua, uma variável é implicitamente global. Para definir variáveis locais é necessário utilizar explicitamente o modificador local ao declarar a variável.

Criação de Scripts

Dentro de um projeto qualquer do HIstudio os scripts podem ser criados para serem executados.

../../_images/scripts_location.png

Fig. 27 Localização dos itens para criação de scripts

O IDE disponibiliza no gerenciador de projeto um item Scripts dentro do item Kernel e outro dentro do item Viewer, conforme apresentado na figura acima. Abaixo do item Scripts o usuário pode criar grupos organizando funcionalmente os scripts utilizados. Não existe limite estabelecido para a quantidade de scripts criada dentro de um grupo, nem tão pouco para a quantidade de grupos de scripts necessários na aplicação. Os nomes dos scripts criados devem ser únicos dentro de um mesmo grupo, podendo ser repetido em grupos distintos, independentemente se os mesmos estão no KERNEL ou no VIEWER. Até a versão 1.3.03 do ambiente, ao clicar com o botão direito do mouse no item ScriptGroup era apresentada a opção Script que, quando selecionada criava um item Script vazio. Com um clique duplo sobre o item criado, o editor de scripts é apresentado no painel de trabalho abrindo o conteúdo do script selecionado. A partir da versão 1.3.04 foi incorporado um assistente de scripts ao IDE. Com este assistente, ao selecionar com o mouse o item ScriptGroup é apresentado um submenu com uma lista de templates de scripts, provendo ao usuário scripts pré-definidos para as operações mais comuns utilizadas no ambiente.

../../_images/scripts_criation.png

Fig. 28 Criação de scripts

Templates de Scripts

A partir de versão 1.3.04 do IDE o ambiente incorporou um assistente de scripts permitindo que templates de scripts sejam criados e modificados para serem utilizados como scripts iniciais no processo de criação dos mesmos. Este recurso està associado ao item ScriptTemplates, disponível dentro do item IdeConfig conforme ilustrado na figura a seguir.

../../_images/scripts_templates.jpg

Fig. 29 Gerência de templates de scripts

A lista de scripts disponível no item ScriptTemplates é automaticamente apresentada no submenu criado quando o usuário vai criar um novo script na aplicação. Ao ser criado o novo script o conteudo do template selecionado pelo usuário é transferido para o script criado e o mesmo é automaticamente apresentado no editor de scripts para visualização e edição.

Macros dos Scripts de Templates

Os scripts criados para template podem utilizar um recurso de macros para permitir a inserção de informações sobre o projeto corrente no script associado. As macros são palavras chave delimitadas pelos caracteres {@ e @}. São as seguintes as macros disponíveis para utilização nos scripts de template.

Macros dos

scripts de template

Macro

Descrição

{@VER@}

Insere o texto definido no campo versão do projeto do item projeto. Este campo está disponível no item project.

{@ENV@}

Insere o nome do ambiente HIstudio

{@PROJ@}

Insere o texto definido no campo Título do item de projeto. Este campo está disponível no item project.

{@COMP@}

nsere o texto definido no campo empresa do item de projeto. Estecampo está disponível no item project.

{@RESP@}

Insere o texto definido no campo responsável do item projeto. Este campo está disponível no item project.

{@DATE@}

Insere a data atual.

Ambiente de Edição e Depuração de Scripts

O HIstudio provê um ambiente de edição e depuração de Scripts Lua que permite a escrita de scripts, verificação sintática, depuração passo-a-passo e inspeção de variáveis em tempo de execução. A partir da versão 1.0.05 também é possível usar a função breakpoint() no corpo de scripts que ao ser executada implicará na abertura automática da tela de depuração.

Variáveis

Variáveis

Variável

K

V

I

Descrição

Sender

K

V

I

Objeto que disparou a execução do Script (se pertencer a VCL) ou nil.

Param

K

V

I

Parâmetros passados para a execução do script (qualquer valor válido em Lua).

ProjectName

K

V

I

Nome do projeto em edição ou execução.

ProjectsFolder

K

V

I

Diretório do projeto em edição ou execução.

ScriptName

K

V

I

Nome do script em execução. Compativel HIscada versão >= 1.0.15

ScriptPath

K

V

I

Caminho do script que está em execução. Compativel HIscada versão >= 1.0.15

Tipos de dados

Tipos

Tipo

Descrição.

DateTime

Tipo com suporte a aritmética de data e hora.

Funções

Função

Descrição

print()

Impressão de texto na tela de depuração do Kernel, Viewer e IDE.

error(msg)

Encerra a execução do script e envia mensagem (msg) de erro para a tela de depuração.

Sleep(timeout)

Coloca o script (thread) em execução para dormir pelo período especificado pelo parâmetro timeout (ms).

Yield()

Comunica com o processador para agendar a execução de outro script (thread).

Log()

Gera string de mensagem em arquivo de log com nível de criticidade (vide exemplo abaixo ou através do link).

Code2Msg(error_code)

Traduz código de erro em mensagem associada.

MessageBox()

Caixa-de-diálogo customizada.

InputBox()

Caixa de entrada customizada. Compativel HIscada versão >= 1.3.16

RunProgram()

Execução de aplicativos externos.

breakpoint()

Função sem parâmetros que introduz um breakpoint no código abrindo a tela de depuração passo-a-passo.

FileDialog()

Caixa-de-diálogo para seleção de arquivo.

DirDialog()

Caixa-de-diálogo para seleção de diretório.

TranslateString()

Separa palavras ou frases com separador padrão baseado no índice. Compativel HIscada versão >= 1.4.00

Bibliotecas de Funções Externas

Quando um script A executa outro script B através da chamada Scripts.Run, todo o escopo global de A estará visível e disponível em B (ex.: variáveis e funções globais). Essa propriedade é transitiva, ou seja, se B chamar C então este último terá todas variáveis e funções globais acumuladas. Este recurso pode ser utilizado para compartilhamento de variáveis e funções globais. Basta criar um Script com os recursos e executá-lo no início de outro script que deseja obter acesso a estes recursos.

Isso vale quando o atributo exec_parallel de Scripts.Run() é falso, se este parâmetro não for explicitamente especificado assume o valor default que é falso. Se exec_parallel for verdadeiro, B é executado em paralelo e independentemente de A, no caso de execução em paralelo não há compartilhamento de dados globais. Quando scripts são independentes não é possível compartilhar nem funções nem variáveis definidas em um script com outro script. Porém, é possível criar bibliotecas de funções (ou módulos) que podem ser importados para o contexto de um script, servindo assim de elementos de compartilhamento de código entre scripts (independentes ou não).

O roteiro de criação de módulos externos documenta e exemplifica como criar módulos de funções.

O portal http://luaforge.net é um repositório de módulos livremente disponíveis e escritos em Lua. Entretanto, não existe garantia de compatibilidade entre módulos obtidos de terceiros e o HIstudio .

Referências Úteis

Para aprender a programar em Lua, recomendamos a leitura da Referência Oficial da Linguagem Lua. Outra boa opção é ler Primeira Versão (on-line) do Livro **Programming in Lua**

Lua possui uma série de módulos nativos que realizam operações básicas. Estes módulos estão disponíveis para scripts sem que haja necessidade de importação explícita. Para maiores detalhes remetemos à documentação original e oficial da linguagem.

Documentação de Módulos Nativos de LUA

Módulo

Descrição

string

Manipulação de Strings.

table

Manipulação de Tabelas.

math

Funções Matemáticas.

bit32

Operações bit-a-bit (bitwise).

io

Manipulação de Entrada e Saída (I/O).

os

Funções Globais do Sistema Operacional.

debug

Funções Avançadas de Depuração.

————————————————————- ————————————————————————— Documentação de Módulos Externos Incorporados ============================================================ ============================================================================ Módulo Descrição struct Funções de manipulação de estruturas binárias compativel HIscada >= 1.0.05 LuaFileSystem Funções de manipulação de arquivos e diretórios compativel HIscada >= 1.0.05 LuaSocket Funções para acesso TCP, UDP, DNS, SMTP, FTP, HTTP, MIME e muito mais. ============================================================ ============================================================================

Documentação Complementar

Referência

Descrição

Quickreference

Guia de Referência Rápida

Sample Code

Exemplos de código-fonte.

Tutorial Directory

Tutoriais Oficiais de Lua.

Wiki Lua

Portal de documentação Wiki de Lua.

Referências para Módulos e Bibliotecas disponíveis na Internet

Como Lua é uma plataforma aberta de desenvolvimento, existem diversos recursos disponíveis para uso na Internet.

Módulos e Bibliotecas

Nome

Descrição

Lua Dist

Agregador de projetos e bibliotecas complementares em Lua no GitHub

<https://github.com/LuaDist>`_

Biblioteca PenLight - Biblioteca com funções úteis

<http://stevedonovan.github.io/Penlight/api/index.html>`_

Bibliotecas complementares disponibilizadas por LHF (um dos criadores de Lua)

<http://webserver2.tecgraf.puc-rio.br/~lhf/ftp/lua/>`_

Editor de Scripts

A documentação do Editor de Scripts descreve como criar e depurar scripts, tanto no IDE (tempo de projeto) quanto no Kernel e no Viewer (tempo de execução).

Scripts úteis

Impressão de todas variáveis definidas em um script

Todas as variáveis globais são armazenadas na tabela _ENV. Para inspecioná-las bastaria:

for k,v in pairs(_ENV) do
print(type(v) .. " " .. tostring(k) ..  " " .. tostring(v))
end

Conversão de valor texto para valor ponto-flutuante

Em Lua os valores ponto-flutuante são expressos com “.” como separador dos decimais. Todavia, no HIscada Pro a interface gráfica apresenta valores com ponto-flutuante separados por vírgulas. Portanto, é necessário converter vírgulas (convenção HIscada Pro ) para ponto (convenção de Lua) antes de aplicar a conversão de texto para número (função tonumber() em Lua). O código abaixo exemplifica esta situação.

-- só a título de informação
print("12.7" .. type(tonumber("12.7")))
print("12,7" .. type(tonumber("12,7")))

-- conversão de número ponto-flutuante formatado com vírgulas
valor_HIscada Pro _str = "12.7"

-- troca , por .
local valor_lua_str = string.gsub(valor_HIscada Pro _str, ",", ".")

-- converte string em Lua number
local valor_lua_number = tonumber(valor_lua_str)
print("Valor " .. valor_lua_number)
print("Tipo " .. type(valor_lua_number))

-- função de conversão

function HIscada Pro _str2number(value_str)
local convertido, n = string.gsub(value_str, ",", ".")
return tonumber(convertido)
end

-- exemplo de uso da função
print("Convertidos:")
print(HIscada Pro _str2number("12,7"))
print(HIscada Pro _str2number("12.7"))
print(HIscada Pro _str2number("12"))

-- saídas
--[[
Convertidos:
12.7
12.7
12
--

Conversão de valor ponto-flutuante para valor texto

Em Lua os valores ponto-flutuante são expressos com “.” como separador dos decimais. Todavia, no HIstudio a interface gráfica apresenta valores com ponto-flutuante separados por vírgulas. Portanto, é necessário converter ponto (convenção de Lua) para vírgulas (convenção HIscada Pro ) durante a conversão de número para texto. O código abaixo exemplifica esta situação.

-- só a título de informação
print("12.7" .. type(tonumber("12.7")))
print("12,7" .. type(tonumber("12,7")))

-- conversão de número ponto-flutuante formatado com vírgulas
valor_HIscada Pro _num = 12.7

-- transforma em string
local valor_lua_str = tostring(valor_HIscada Pro _num)

-- troca . por ,
valor_lua_str = string.gsub(valor_lua_str, "%.", ",")
print("Texto " .. valor_lua_str)
print("Tipo " .. type(valor_lua_str))

-- função de conversão

function HIscada Pro _number2str(number)
local convertido = tostring(number)
local n = 0
convertido,n = string.gsub(convertido ,"%.",",")
return convertido
end

-- exemplo de uso da função
print("Convertidos:")
print(HIscada Pro _number2str("12,7"))
print(HIscada Pro _number2str("12.7"))
print(HIscada Pro _number2str("12"))

-- saídas
--[[
Convertidos:
12,7
12,7
12
]]--

Redução da precisão de um valor ponto-flutuante

Existem dois cenários de redução de precisão de um número ponto-flutuante. O primeiro cenário é apenas para apresentação na tela, não sendo necessário fazer contas com o valor de precisão reduzida.

print(string.format("%.3f", 1.12345678))

No segundo cenário, a redução de precisão visa gerar um valor e não apenas um texto.

x1 = 1.123456789

function reduz(valor, casas)
local q = math.pow(10,casas)
return math.floor(valor * q)/ q
end

x3 = reduz(x1,3)
print(x3)
print(tostring(x1 - x3))

-- x3 == 1.123
-- x1 - x3 == 0.00045678900000001

Conversão de dados entre banco e tags

Quando é feita uma leitura em banco de dados, deve-se atentar ao tipo da variável retornada, isto pode ser feito utilizando a função type, como no exemplo a seguir:

-- Após a leitura no banco de dados, a variável v1 será uma string de valor 1,234
print(type(v1))
-- v1 == string

Para conversão da variável retornada do banco para um tag local numérico, deve-se primeiro converter a variável para o tipo number. Deve-se atentar também caso a conversão da string for de um número do tipo ponto flutuante, se este utiliza vírgula ou ponto como separador. Sendo vírgula, deve-se primeiramente converter para ponto, como no exemplo a seguir:

-- A variável v1 é uma string de valor 1,234
print("v1 == " .. v1)
print("tipo == " .. type(v1))
-- v1 == 1,234
-- tipo == string

-- Para substituir vírgula por ponto, pode-se usar a função string.gsub
v1 = string.gsub(v1, ",", ".")
-- v1 == 1.234

-- A conversão para o tipo number será feita utilizando a função "tonumber"
v1 = tonumber(v1)
print("v1 == " .. v1)
print("tipo == " .. type(v1))
-- v1 == 1.234
-- tipo == number

Conversão de vetor de floats em vetor de inteiros

O exemplo abaixo utiliza o módulo struct, disponibilizado a partir da versão 1.0.05, para armazenar um trecho de variáveis do tipo D em um segmento de variáveis do tipo M, e vice-versa.

-- Vetor de D c/ 3 posições
local d1 = Tags.Get("Kernel.Tags.Opc.TagOpcGroup_001.TagOpc_001")
print("Antes " .. d1.Value[1] .." ".. d1.Value[2] .." ".. d1.Value[3])

-- Vetor de M c/ 6 posições
local m = Tags.Get("Kernel.Tags.Opc.TagOpcGroup_001.TagOpc_002")

-- Serialização do vetor D
local buffer = struct.pack("<fff", d1.Value[1], d1.Value[2], d1.Value[3])

-- Conversão para memórias M (inteiro 16-bits)
local m_1, m_2, m_3, m_4, m_5, m_6 = struct.unpack("<I2I2I2I2I2I2", buffer)

-- Armazenamento das Ms
m.Value[1] = m_1
m.Value[2] = m_2
m.Value[3] = m_3
m.Value[4] = m_4
m.Value[5] = m_5
m.Value[6] = m_6
m:WriteValue()

-- Recuperação das Ms
local m2 = Tags.Get("Kernel.Tags.Opc.TagOpcGroup_001.TagOpc_002")
-- IMPORTANTE: Fazer leitura síncrona no dispositivo para recuperar valor escrito
m2:ReadDevice()

local buffer2 = struct.pack("<I2I2I2I2I2I2", m2.Value[1], m2.Value[2], m2.Value[3],
                                          m2.Value[4], m2.Value[5], m2.Value[6])

-- Conversão para memórias D (float 32-bits)
local d_1, d_2, d_3 = struct.unpack("<fff", buffer2)
print("Depois " .. d_1 .." ".. d_2 .." ".. d_3)

-- Armazenamento das D
local d2 = Tags.Get("Kernel.Tags.Opc.TagOpcGroup_001.TagOpc_003")
d2.Value[1] = d_1
d2.Value[2] = d_2
d2.Value[3] = d_3
d2:WriteValue()

Exemplo de conversão de duas variáveis D (vetor D[2]) em 4 memórias M (M[4]). Considerando que as 4 memórias M possuem um formato de Data e Hora associada a algum evento, ilustramos as funções de conversão e extração da data e hora.

-- Vetor D[2]
local D = Tags.Get("Kernel.Tags.Opc.PLC.DateTime_D")

-- Serialização do vetor D[2] em um buffer
local buffer = struct.pack("<ff", D.Value[1], D.Value[2])

-- Conversão do vetor D[2] para 4 memórias M (inteiro 16-bits)
local m_1, m_2, m_3, m_4 = struct.unpack("<I2I2I2I2", buffer)

-- Formata os valores serializados em M em valores hexadecimal
m0 = string.format("%04X %04X", m_2, m_1)
m1 = string.format("%04X %04X", m_4, m_3)
print(m0 .. "-" ..  m1)

-- Considerando o seguinte formato do timestamp a ser extraido :
--   m_1 (1º byte)   Segundos        [0..59]
--   m_1 (2º byte)   Minutos         [0..59]
--   m_2 (1º byte)   Horas           [0..23]
--   m_2 (2º byte)   Dia do mês      [1..31]
--   m_3 (1º byte)   Mês do ano      [1..12]
--   m_3 (2º byte)   Offset do Ano em relação a 1990 [0..255] + 1990

-- Extrai data e hora
local seg = bit32.band(m_1, 0x00ff)
local min = bit32.rshift(bit32.band(m_1, 0xff00), 8)
local hor = bit32.band(m_2, 0x00ff)
local dia = bit32.rshift(bit32.band(m_2, 0xff00), 8)
local mes = bit32.band(m_3, 0x00ff)
local ano = bit32.rshift(bit32.band(m_3, 0xff00), 8) + 1990

-- salva os valores de data e hora em um vetor de inteiros
local DH = Tags.Get("Viewers.Tags.ViewGroup_001.DateTime_Final")
DH.Value[1] = seg
DH.Value[2] = min
DH.Value[3] = hor
DH.Value[4] = mes
DH.Value[5] = dia
DH.Value[6] = ano
DH:WriteValue()

Montagem do diretório lua a partir do caminho do projeto

As variáveis ProjectsFolder e ProjectName que podem ser usadas para tornar os scripts Lua independentes do diretório de instalação.

-- Obtém o path de acesso ao projeto corrente
local path = ProjectsFolder
print(path)

-- Obtém o nome do projeto corrente
local prj = ProjectName
print(prj)

Exemplo de montagem do path do sub-diretório lua:

local path = ProjectsFolder
path = path:gsub("Projects","lua")
print(path)

Uso da propriedade Arguments para passar parâmetros para scripts

Diversos itens no IDE possuem a propriedade Arguments, onde o projetista da aplicação pode colocar um texto que será acessível via scripts. Usaremos um item de botão para ilustrar o uso da propriedade Argument, com um script associado ao evento OnClick. Colocando o texto “Oi mundo!” em Argument, no script esta mensagem pode ser obtida da seguinte forma:

print(Sender.Argument)

Colocando o texto “Oi mundo!” em Argument, no script esta mensagem pode ser obtida da seguinte forma:

print(Sender.Argument)

Colocando o texto “12” em Argument, no script o valor inteiro pode ser obtido da seguinte forma:

local x = tonumber(Sender.Argument)
print(2*x)

Colocando o código Lua “{1,2,3,bla=4}” em Argument, no script a tabela Lua pode ser obtida da seguinte forma:

-- load transforma o texto em uma função anônima Lua, e pcall executa esta função
-- Note que x, não pode ser local, senão não será visível fora do escopo da função anônima
pcall(load("x = " .. Sender.Argument))
print(x[1])
print(x[2])
print(x[3])
print(x.bla)