Expressões

A única linguagem de expressão atrás das variáveis e das condições de alarme, o que ela pode referenciar, a superfície de leitura de cada tipo de ponto, as regras que o avaliador impõe, as mensagens que a validação dá, e como o editor de script e o seletor Procurar… funcionam.

Ver como Markdown

Uma única linguagem de expressão serve a dois lugares da página Logic: o que uma variável de expressão calcula e a condição que um alarme vigia. As duas são uma expressão JavaScript avaliada em modo estrito dentro de uma caixa isolada, sem acesso à máquina, e as duas são editadas no mesmo editor de script. Scripts de ação usam o mesmo vocabulário, mas são blocos de instruções; as diferenças deles estão em Ações.

O que é uma expressão

Uma expressão é exatamente uma expressão JavaScript: literais, aritmética, comparações, operadores lógicos, o condicional a ? b : c, operações de texto, cadeias de modelo e chamadas aos embutidos do JavaScript Math, Date e JSON. Instruções (let, if, laços) pertencem às ações: uma segunda instrução é recusada com "Uma variável guarda uma única expressão. Instruções pertencem às Ações." Atribuições e ++/-- em qualquer lugar de uma expressão são recusados com "As expressões são somente leitura. As escritas pertencem às Ações."

A expressão referencia pontos pelo endereço enraizado deles. O que cada raiz entrega à expressão:

Raiz O que atravessa até a expressão
Tags de Connector, Variables, Alarms, os Channels de uma unidade do Process Um objeto ao vivo com a superfície de leitura tipada abaixo. Uma referência nua (Connector.Simulated.Pump.Flow sozinha) avalia para o valor do ponto; um alarme nu, para o IsActive dele.
System, Server, Assets, e o estado da execução, a produtividade, os comandos e a preparação de uma unidade do Process O valor atual em si: um número, um texto, um booleano ou um instante. Use-o diretamente; não há membro a acrescentar. Um membro de ponto ao vivo acrescentado a ele é recusado: System.StartTime.Value dá "'System.StartTime' é um valor: leia-o sem .Value."

Um ponto ao vivo escrito nu é o ponto, não o valor dele, e só vale pelo valor quando é a expressão inteira. Onde um operador o toma pelo valor, leia-o pelo membro que diz qual valor: .ValueToBool() onde ele é testado como verdadeiro ou falso (Variables.Start.ValueToBool() ? 1 : 0), .Value onde é comparado por igualdade, .ValueToReal() onde é ordenado ou entra numa conta (Variables.Level.ValueToReal() > 80), e .IsActive num alarme. A validação recusa o ponto nu no teste de ?:, como operando de !, && ou ||, numa comparação (==, !=, ===, !==, <, >, <=, >=) e na aritmética (+, -, *, /, %, ou um sinal na frente dele), porque ali ele sempre contaria como verdadeiro ou nunca leria o valor: Variables.Level > 80 dá "'Variables.Level' é o próprio ponto, não o valor dele: uma comparação ou um cálculo nunca lê o valor nele. Leia o valor dele com Variables.Level.ValueToReal()." Um ponto passado a uma função, e qualquer membro de um ponto, fica como foi escrito. A mesma recusa vale para o lado esquerdo de ??, do qual um ponto nunca sai (leia .Value), e para um marcador ${…} de um template literal, que nunca escreve o valor do ponto no texto (leia .ValueToString()).

System.Now é o instante da avaliação: toda referência a ele dentro de uma avaliação lê a mesma hora. Ele atravessa como um Date do JavaScript, como todo carimbo de tempo, então instantes comparam e subtraem naturalmente: (System.Now - Connector.Simulated.Pump.Run.SourceTimeStamp()) / 1000 são segundos.

Uma variável de expressão pode referenciar tags, variáveis de memória, alarmes, pontos do Process, pontos de Assets e os diagnósticos de System e Server, mas nunca outra variável de expressão: o editor a recusa ("'Variables.X' é uma variável de expressão. Expressões não podem referenciar outras variáveis de expressão; referencie variáveis de memória ou os pontos por baixo delas.") para que os resultados nunca dependam da ordem de avaliação. Uma condição de alarme pode referenciar tudo, menos Alarms.* ("As condições de alarme não podem referenciar outros alarmes.") e precisa produzir um booleano.

A superfície de leitura de um ponto

Uma tag ao vivo oferece:

Membro Devolve Significado
.Value valor tipado O valor de engenharia, depois da cadeia de conversão inteira da tag
.ValueRaw valor tipado O valor bruto normalizado para o tipo declarado da tag, antes da escala
.ValueObject qualquer coisa O valor exatamente como o driver o produziu
.ValueScaled, .ValueCalibrated, .ValueLimited, .ValueRounded número O valor depois dos estágios de Escala, Calibração, Limites e Decimais
.ValueToBool(), .ValueToInteger(), .ValueToReal(), .ValueToString(), .ValueToDateTime() valor convertido O valor de engenharia convertido para aquela categoria; nenhum valor continua nenhum valor
.State() booleano Qualidade: verdadeiro enquanto a leitura é boa
.SourceTimeStamp() Date Quando o dispositivo produziu a leitura, se o protocolo dele a data (OPC UA); senão, o instante em que o Connector a recebeu, o mesmo de .ApplicationTimeStamp()
.ApplicationTimeStamp() Date Quando o Connector a recebeu
.Name, .EngineeringUnit texto O nome e a unidade da tag

Uma variável oferece .Value, as cinco conversões .ValueTo*(), .State(), .ApplicationTimeStamp(), .Name e .EngineeringUnit. Um alarme oferece .IsActive, .IsAcked, .CurrentState, .State() (se a condição dele pôde ser avaliada na última varredura), .ActiveSince(), .LastTransitionAt() e .Name. Um canal do Process oferece a superfície de tag quando está vinculado a uma tag, e a superfície de variável quando está vinculado a uma variável; um canal sem vínculo não oferece nada. Nada mais num objeto ao vivo é alcançável: um membro fora desta superfície lê como indefinido, e chamar um falha na validação. O seletor Procurar… lista esses mesmos membros sob cada ponto, então nenhum deles precisa ser digitado de memória.

As regras que o avaliador impõe

Tanto no editor, enquanto você digita, quanto de novo a cada varredura de 100 ms:

  • A porteira da qualidade. Antes de avaliar, todo ponto referenciado é resolvido e atualizado. Qualquer ponto ausente, Ruim ou sem valor faz o resultado ser uma lacuna de qualidade ruim sem rodar a expressão, então uma tag offline nunca consegue fabricar uma amostra e null nunca chega ao motor para virar 0. Uma tag cuja leitura calou, sem leitura nova durante o intervalo de leitura mais 10 segundos, vale aqui como uma leitura que falhou: Ruim, a menos que a sua política para um dispositivo que não respondeu apresente um substituto.
  • Resultados não finitos são lacunas. NaN ou infinito (divisão por zero, aritmética indefinida) nunca vira uma amostra.
  • Proteção contra descontrole. Uma avaliação é abortada além de 250 ms, 5.000 instruções ou 4 MB de memória de script, e conta como lacuna de qualidade ruim. Um motor é mantido por texto de expressão, então nada vaza de outra expressão.
  • Conversão. Um resultado bem-sucedido é convertido para o tipo de saída da variável; uma condição de alarme é convertida para booleano, e um resultado que não converte mantém o estado do alarme.
  • Erros são lacunas, não exceções. Um erro de execução no momento da varredura produz uma lacuna e uma entrada de diário (a primeira falha, resumos enquanto ela se repete, uma linha quando ela se recupera); a varredura segue com a próxima variável.

O que a validação recusa

O editor valida o rascunho a cada meio segundo e mostra o primeiro problema abaixo do valor ou da condição:

Mensagem Causa
A expressão é obrigatória. / A condição é obrigatória. O editor está vazio.
Erro na expressão: … O texto não é interpretável.
Uma variável guarda uma única expressão. Instruções pertencem às Ações. Mais de uma instrução, ou uma instrução que não é uma expressão.
As expressões são somente leitura. As escritas pertencem às Ações. Uma atribuição ou ++/--.
Os endereços precisam ser estáticos: 'Root' é acessado com um segmento calculado. Variables[name] ou qualquer segmento que não seja literal.
Endereço incompleto 'Connector.Simulated'. Uma cadeia curta demais para nomear um ponto sob a raiz dela.
Não foi possível encontrar 'X'. O endereço não resolve para nada.
'X' é uma variável de expressão. … Uma referência a outra variável de expressão.
'X' é um valor: leia-o sem .Value. Um membro de ponto ao vivo (.Value, .State(), …) acrescentado a um ponto que entra como o próprio valor (System.StartTime.Value).
'X' é o próprio ponto, não o valor dele: como condição ele sempre conta como verdadeiro. Leia o valor dele com X.ValueToBool(). Um ponto ao vivo escrito nu onde é testado como verdadeiro ou falso: o teste de ?:, ou um operando de !, && ou \|\| (!Variables.Start). Num alarme a mensagem nomeia .IsActive.
'X' é o próprio ponto, não o valor dele: uma comparação ou um cálculo nunca lê o valor nele. Leia o valor dele com X.ValueToReal(). Um ponto ao vivo escrito nu numa comparação ou na aritmética (Variables.Level > 80, Variables.Level * 2). Para ==, !=, === e !== a mensagem nomeia .Value, e num alarme .IsActive.
'X' é o próprio ponto, não o valor dele: ele nunca está vazio, então ?? sempre fica com ele. Leia o valor dele com X.Value. Um ponto ao vivo escrito nu à esquerda de ?? (Variables.Level ?? 0). Num alarme a mensagem nomeia .IsActive.
'X' é o próprio ponto, não o valor dele: escrito dentro de um texto ele nunca mostra o valor. Leia o valor dele com X.ValueToString(). Um ponto ao vivo escrito nu num marcador ${…} de um template literal. Num alarme a mensagem nomeia .IsActive.
As condições de alarme não podem referenciar outros alarmes. Uma referência a Alarms.* numa condição.
A condição precisa resultar em um booleano, mas ela produziu 'x'. A avaliação de teste de uma condição produziu um valor presente que não converte para booleano.
Erro na expressão: um ponto ao vivo não pode ser usado direto aqui. Leia-o pelo membro .Value dele (por exemplo Connector.Driver.Device.Tag.Value). Um objeto ao vivo usado onde um primitivo é necessário (Math.round(Variables.Level)).
Erro na expressão: <mensagem do motor> A avaliação de teste lançou um erro: um identificador desconhecido, um membro bloqueado chamado como função.

A avaliação de teste roda com os valores atuais, seja qual for a qualidade, então uma mensagem reflete a expressão, e não uma entrada Ruim; uma entrada Ruim aparece no cartão PONTOS REFERENCIADOS.

O editor de script

Toda expressão, condição e script da página é editado no mesmo cartão: um rótulo de grupo (EXPRESSÃO, CONDIÇÃO ou SCRIPT DA AÇÃO), um botão Procurar…, o editor, uma dica e a mensagem de validação. O editor é o CodeMirror com realce de JavaScript, um histórico de desfazer e autocompletar de símbolos de endereço: nomes de raiz num símbolo isolado, depois os filhos do caminho digitado até ali, com os membros incluídos assim que o símbolo nomeia um ponto (digitar um endereço). O texto é empurrado para o rascunho com atraso e salvo sozinho. As caixas de expressão e de condição têm no mínimo 56 px de altura, e a caixa de script de ação tem 160 px. Quando o motor do editor não consegue iniciar, a caixa se degrada para uma área de texto simples, que ainda lê e escreve, sem realce, sem completar e sem histórico; o Procurar… então acrescenta o endereço escolhido no fim, em vez de no cursor.

O Procurar… abre um diálogo ("Procurar: inserir em Expressão") com a árvore do espaço de endereços inteiro, até o nível de membro; selecione uma linha e pressione Inserir, ou clique duas vezes nela, para pôr o endereço dela no cursor. Cada editor é dono do próprio diálogo, então dois editores numa página nunca compartilham uma seleção.

O que as expressões não fazem

Elas não escrevem nada, não chamam comandos de execução do Process, não guardam estado entre avaliações, não conseguem definir funções compartilhadas entre expressões, e não são avaliadas sob demanda: a varredura as publica a cada 100 ms e o editor mostra a prévia do rascunho duas vezes por segundo.