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.
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
nullnunca chega ao motor para virar0. 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.