# Ações

> O editor de ação: identidade e localização, o bloco de script, o que um script pode ler e escrever, todas as mensagens de validação, o verbo Executar e o diário dele, quem disparou a última execução, o que executa a ação, Habilitar e Desabilitar, Copiar e Excluir, e os limites de execução.

Uma ação é um bloco de instruções JavaScript que lê o espaço de endereços e escreve variáveis de memória e tags. As ações são o caminho de escrita da Logic: elas rodam sob demanda (Executar), por um [gatilho](logic-triggers), a partir de um [Botão](component-button) de dashboard ligado a elas, ou no Início ou na Conclusão de uma execução do Process, por um [comando de ação da Logic](process-actions#logic-action-commands). Selecione uma ação sob a seção Actions da coluna da Logic, ou pressione **Abrir** na linha dela na tabela da pasta ou da raiz da seção, para abrir o editor dela (uma ação feita com Novo… > Nova ação não é aberta, e sim destacada nessa tabela, veja [Criar e copiar](logic#creating-and-copying)); ler exige **Ver a lógica**, editar exige **Configurar a lógica**, executar exige **Operar a lógica**.

O cabeçalho mostra a marca de ação, o nome, a frase "Um script que você executa sob demanda, ou que um gatilho executa por você, escrevendo em variáveis e tags." e uma pílula **Desabilitada** enquanto a ação está desligada. A coluna do formulário contém IDENTIDADE, SCRIPT DA AÇÃO e **Usada por**; o trilho contém ÚLTIMA EXECUÇÃO.

## Identidade {#identity}

| Campo | O que é | Valores / padrão | Efeito |
| --- | --- | --- | --- |
| Nome | O nome de exibição e o rótulo sob o qual uma execução é registrada no diário. | Texto livre; uma ação nova é `ActionN`; um nome em branco salva como `Action`. | Salva sozinho a cada tecla. Ações são referenciadas por identidade, então renomear não reescreve nada e não quebra nada. |
| Linha de localização | Onde a ação está arquivada, em palavras: `Actions / Startup / Walk setpoint`. | Somente leitura. | Uma ação não tem endereço; a linha é um lugar, nunca algo a digitar numa expressão. Mude-a com o **Mover para** da árvore. |
| Descrição (opcional) | Texto livre. | Vazia por padrão. | Nenhum. |

## Script da ação {#script}

O cartão SCRIPT DA AÇÃO é o [editor de script](logic-expressions#the-script-editor) com a dica "Instruções JavaScript (let, if, for, funções). Escreva em variáveis de memória por atribuição (Variables.Setpoint = 21.5;) e em tags com Connector.Driver.Device.Tag.Write(valor). As leituras não passam por filtro de qualidade: consulte .State() e proteja as lacunas com ?? antes de fazer contas." O script salva sozinho a cada tecla e é validado a cada meio segundo; a mensagem aparece abaixo da dica. A validação nunca executa o script.

Um script é JavaScript comum em modo estrito (variáveis locais, `if`, laços, funções, `Math`, `Date`, `JSON`) sobre o mesmo [espaço de endereços](logic-address-space) e a mesma [superfície de leitura](logic-expressions#the-read-surface-of-a-point) das expressões, com duas diferenças. As leituras **não** passam pela porteira da qualidade: uma lacuna lê como `null`, então confira o `.State()` e proteja as contas com `??`; lidar com uma leitura ruim é para isso que as ações servem. E um script pode ler variáveis de expressão (o valor publicado delas); só uma expressão é que não pode referenciá-las.

Um ponto ao vivo (uma tag, uma variável, um alarme, um canal do Process) é o ponto, não o valor dele, então leia o valor por um membro: `if (Variables.Start.ValueToBool())`, `Variables.Level.ValueToReal() > 80`, `if (Alarms.High.IsActive)`. Escrito nu no teste de `if`, `while`, `do`, `for` ou `?:`, como operando de `!`, `&&` ou `||`, numa comparação ou na aritmética (com `+=` e as outras atribuições compostas), ele sempre contaria como verdadeiro ou nunca leria o valor: `if (Variables.Start) { … }` executaria o bloco em toda execução, seja qual for o valor da variável. A validação o recusa e nomeia o membro a ler no lugar ([O que a validação recusa](#what-validation-refuses)). Um ponto passado a uma função, e qualquer membro de um ponto, fica como foi escrito. O mesmo vale para um `switch` sobre um ponto nu ou um `case` que nomeia um (leia `.Value`), para o lado esquerdo de `??` (leia `.Value`) e para um marcador `${…}` de um template literal (leia `.ValueToString()`). Uma atribuição composta a uma variável (`Variables.Total += 1`, `-=`, `||=` e as outras) lê a variável como o ponto antes de escrever, então é recusada com a atribuição simples que funciona: `Variables.Total = Variables.Total.ValueToReal() + 1`.

### O que uma ação pode escrever {#what-an-action-may-write}

Exatamente duas construções alteram a planta, e as duas passam pelo [funil de escrita](logic#writing-values) registrado no diário:

| Escrita | Forma | Alvo |
| --- | --- | --- |
| Atribuição de memória | `Variables.Setpoint = 21.5;` ou `Variables.Forno.Setpoint = 21.5;` | Uma variável de memória, níveis de pasta incluídos. O valor é convertido para o tipo de saída da variável e marcado para retenção quando a variável é retentiva. Ela vale na hora, então a linha seguinte do mesmo script lê o que acabou de escrever. |
| Escrita de tag | `Connector.Simulated.Pump.Cmd.Write(true);` ou `…Cmd.WriteAndWait(true);` | Uma tag ao vivo. O valor é um valor de engenharia: ele viaja de trás para a frente pela [cadeia de conversão](connector-value-pipeline) da tag até o valor bruto que o dispositivo recebe, e o funil o entrega à fila de entrega daquele dispositivo, onde os valores devidos a ele saem um de cada vez, na ordem em que chegaram. |

Uma tag carrega dois verbos de escrita, e o script escolhe conforme precise ou não da resposta:

| Verbo | Argumentos | Deixado de fora | Devolve | Espera o equipamento |
| --- | --- | --- | --- | --- |
| `Write(value)` | O valor. | Não há o que deixar de fora. | Nada. | Não. O valor é entregue à fila do dispositivo e a linha seguinte roda. |
| `WriteAndWait(value, timeoutMs, continueOnFailure)` | O valor, quantos milissegundos esperar por ele, e se deve seguir quando ele falha. | `timeoutMs`: o tempo de espera do próprio driver é usado no lugar. `continueOnFailure`: `true`. | `true` só quando o equipamento aceitou o valor; `false` para qualquer outra coisa, um valor ultrapassado incluído. | Sim, até o recibo chegar ou o tempo de espera terminar. |

A espera acontece na faixa das ações, então um dispositivo lento segura só as ações atrás desta, e ela nunca retira a escrita: um valor que não é confirmado a tempo continua viajando, e o recibo dele é registrado no diário quando chega. É isso que a resposta diz, com todas as letras: "'Cmd' não foi confirmado em 2000 ms; ainda está a caminho do equipamento." Com o `continueOnFailure` deixado de fora ou `true`, o verbo responde `false` e o script segue; com `false`, a execução termina naquela linha, nomeando o alvo e o motivo.

**Nenhuma escrita aborta um script por si só.** Uma escrita recusada antes de qualquer coisa sair e uma escrita que o equipamento recusa ou nunca responde falham sozinhas: o resto do script roda, e a execução termina como uma execução que deu certo com escritas que falharam. Nada é desfeito tampouco, então o que foi aplicado antes de uma escrita que falhou continua aplicado, e o diário mostra cada escrita como valor antigo para valor novo, sob a origem Ação. Os motivos em que uma recusa é lida:

- "A escrita em 'X' foi rejeitada: o valor não é finito." (NaN ou infinito)
- "'Tag' não está materializada no connector." (um dispositivo desabilitado, ou uma tag que o Connector não carrega)
- "Não foi possível converter 'valor' para 'Tag'."
- "'X' está protegida contra escrita por '`<holder>`'." (uma execução de receita ou um intertravamento segura a origem)
- "'Cmd' não foi escrito: o equipamento não está conectado." (o dispositivo já é sabido estar fora do ar, então nada é enfileirado para ele)
- "A escrita em 'Tag' falhou: o Connector não está rodando."
- "A variável 'X' não foi encontrada." e "'Variables.X' é uma variável de expressão: ela é calculada, nunca escrita."
- O que o equipamento ou o driver dele respondeu, nas palavras dele, quando a recusa veio do fio.

Um valor que uma escrita mais nova na mesma tag ultrapassou na fila do dispositivo **não** é uma falha: "'Cmd' foi substituído por um valor mais novo antes de ser enviado." é exatamente o que aconteceu, o valor mais novo é o que o equipamento recebe, e nada é relatado em lugar nenhum, tirando a um `WriteAndWait`, que responde `false` porque este valor não é o que saiu.

O que termina uma execução: um erro do próprio script (um erro de sintaxe, um endereço que não nomeia nada, uma exceção), um `WriteAndWait(…, false)` cuja escrita falhou, e os limites de execução em [Executar](#run).

Quais falhas a própria execução consegue relatar decorre de quem esperou. Um `Write` recusado antes de qualquer coisa sair é recusado enquanto o script ainda roda, então a execução o conta e o feed diz isso; um `Write` que o equipamento recusa depois é respondido muito tempo após o fim da execução, então é o console de [Eventos](events), sob o nome da ação, que o carrega, e nunca a execução. Um `WriteAndWait` ficou pela resposta, então ele conta de qualquer jeito que a resposta venha.

## O que a validação recusa {#what-validation-refuses}

| Mensagem | Causa |
| --- | --- |
| "O script é obrigatório." | O editor está vazio. |
| "Erro no script: …" | O texto não é interpretável. |
| "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 '…'." | Uma referência curta demais para nomear um ponto. |
| "Não foi possível encontrar 'X'." | Uma leitura, ou um alvo de atribuição, que não resolve para nada. |
| "'X' é um valor: leia-o sem .Value." | Um membro de ponto ao vivo (`.Value`, `.ValueToBool()`, `.State()`, …) lido num ponto que entra no script como o próprio valor: `System.StartTime.Value` ou `Process.Oven_soak.Oven_1.Procedure.Running.ValueToBool()`. A mensagem nomeia o membro que foi escrito. |
| "'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 `if`, `while`, `do`, `for` ou `?:`, ou um operando de `!`, `&&` ou `\|\|` (`if (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`, `total += Variables.Level`). Para `==`, `!=`, `===` e `!==` a mensagem nomeia `.Value`, e num alarme `.IsActive`. Um `switch` sobre um ponto nu, ou um `case` que nomeia um, recebe a mensagem com `.Value`. |
| "'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`. |
| "'+=' lê X como o próprio ponto, não o valor dele, antes de escrever. Use X = X.ValueToReal() + 1." | Uma atribuição composta a uma variável: `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, as bit a bit, `&&=`, `\|\|=` e `??=`. A mensagem nomeia o operador escrito e a atribuição simples que faz o que ele lê, com o lado direito como foi escrito: `.ValueToReal()` para a aritmética, `.ValueToInteger()` para as bit a bit, `.ValueToBool()` para `&&=` e `\|\|=`, `.Value` para `??=`. |
| "Atribua à própria variável: Variables.Nome = valor." | O lado esquerdo do `=` nomeia um membro, ou vai mais fundo do que o endereço de uma variável permite. |
| "'X' é uma variável de expressão: ela é calculada, nunca escrita." | Atribuir a uma variável de expressão. |
| "Os pontos de 'Connector' são somente leitura. Escreva em tags com Connector.Driver.Device.Tag.Write(valor)." | Atribuir a qualquer raiz que não seja Variables (o Process tem a mensagem própria dele). |
| "Os pontos do Process são somente leitura nas Ações; comandos de execução e escritas de preparação não são suportados por scripts." | Atribuir a um ponto do Process, ou chamar `Write` nele. |
| "Os comandos de execução do Process não podem ser chamados de Ações; os pontos de comando expõem valores de prontidão somente leitura." | Chamar `Process.<Model>.<Unit>.Commands.Start()` ou qualquer outro ponto de comando; um ponto de `Commands` só lê se o comando poderia executar agora. |
| "Os operadores ++ e -- não são suportados em pontos. Use Variables.Nome = Variables.Nome.ValueToReal() + 1." | `Variables.N++`. |
| "Write(valor) é a escrita da tag: Connector.Driver.Device.Tag.Write(valor)." | `Write` chamado em algo que não é um endereço de tag. A frase nomeia o verbo que foi usado, então o `WriteAndWait` recebe a mesma frase com o nome dele nos dois lugares. |
| "O tempo de espera de WriteAndWait precisa ser um número de milissegundos: WriteAndWait(valor, 2000)." | O segundo argumento está escrito por extenso e não é um número. |
| "O continuar em caso de falha de WriteAndWait precisa ser true ou false: WriteAndWait(valor, 2000, false)." | O terceiro argumento está escrito por extenso e não é `true` nem `false`. |
| "WriteAndWait aceita no máximo três argumentos: o valor, o tempo de espera em milissegundos e se continua em caso de falha." | Um quarto argumento. |

As três mensagens do `WriteAndWait` julgam o que o próprio texto diz. Um argumento que o editor não consegue resolver só de ler (uma variável, uma expressão calculada) é julgado na hora em que a chamada acontece de verdade, e a mesma frase então termina a execução naquela linha.

Variáveis locais do JavaScript (`let x = 1; x = x + 1;`) não são tocadas por essas regras.

## Executar {#run}

**Executar** é o verbo inicial da barra sobre uma ação aberta; ele é desenhado só para um papel com **Operar a lógica** e fica esmaecido com "Runtime parado" enquanto a estação está em somente leitura (a trava de engenharia não o esmaece: executar é operar, não configurar). Quando o papel também tem Configurar a lógica, o rascunho do editor é salvo primeiro e a execução roda a definição salva; sem ele, a execução roda a definição durável (os campos são somente leitura, então as duas nunca diferem).

Toda execução roda na **faixa das ações** com um motor de script novo, então nenhum estado vaza entre execuções e dois scripts nunca rodam ao mesmo tempo: eles rodam um de cada vez, na ordem em que foram pedidos. A faixa não é a varredura de 100 ms. Expressões, alarmes, reconhecimentos e escritas que chegam de fora seguem andando enquanto um script roda, então uma varredura pode cair no meio de um e duas leituras do mesmo script podem ver ciclos diferentes; em troca, uma ação que para para esperar um dispositivo atrasa só as ações enfileiradas atrás dela e mais nada na estação.

Uma execução é abortada além de 500 ms ("O script foi abortado: ele passou do limite de 500 ms (laço infinito?)."), 100.000 instruções ou 4 MB de memória, cada caso com uma mensagem que nomeia o limite. Os 500 ms medem o script rodando: a contagem fica parada enquanto um `WriteAndWait` espera um dispositivo e é retomada depois, então um script correto nunca leva a culpa por equipamento lento. Uma ação desabilitada falha sem rodar ("A ação está desabilitada."); um runtime parado responde "O runtime da lógica não está em execução."

O feed relata "Execução OK em 12 ms." quando toda escrita passou, "Execução OK em 12 ms, 1 escrita falhou: Cmd: 'Cmd' não foi escrito: o equipamento não está conectado." quando o próprio script terminou e uma escrita não (com "Execução OK em 12 ms, 3 escritas falharam, a primeira: Cmd: 'Cmd' não foi escrito: o equipamento não está conectado." a partir da segunda falha), ou "A execução falhou: `<motivo>`" quando a própria execução terminou mal.

A execução que falha chega ao console de [Eventos](events), sob o nome da ação junto com o que a invocou: Manual pelo verbo, Dashboard por um Botão, MCP pelo agente, o nome do gatilho numa execução agendada, e `Process · <unit> · <action>` numa execução do Process. Ela é registrada no diário como aviso na primeira vez, resumida enquanto segue falhando pela mesma causa, e anotada mais uma vez quando se recupera, e essa linha de recuperação é a única entrada que uma execução bem-sucedida escreve ali. Uma escrita recusada chega ao console do mesmo jeito e sob o mesmo nome, e pelo mesmo motivo é a ação que é nomeada, e não a execução: a resposta do equipamento pode chegar segundos depois do fim da execução, e não há mais execução para falhar. Uma execução que simplesmente dá certo, e todo valor que ela escreve, é escrita só na [captura detalhada](events-console#detailed-capture): um script que escreve a cada varredura produz valores aos milhares, e um console cheio deles é um console em que ninguém consegue ler as notícias da estação.

O trilho ÚLTIMA EXECUÇÃO mostra quando a ação rodou pela última vez (ou "Nunca executada."), "Duração: N ms", quem a disparou (abaixo), o último erro numa nota de atenção, as escritas que falharam naquela execução numa nota de atenção própria (a mesma frase que o feed carregou, então uma execução que deu certo e ainda assim deixou um setpoint onde estava diz isso aqui também), e o lembrete "As execuções acontecem na faixa das ações, uma de cada vez. Só uma execução que falha chega ao console de Eventos; uma execução bem-sucedida, e cada valor que ela escreve, fica guardada para a captura detalhada. Os gatilhos chamam as ações na hora marcada." O trilho pertence à ação ao vivo: um salvamento automático nunca o esvazia; um reinício do runtime esvazia.

### Disparada por {#fired-by}

Sob a duração, a linha "Disparada por: …" diz o que executou a ação pela última vez:

| O que a executou | A linha diz |
| --- | --- |
| **Executar** nesta página | "Disparada por: Execução nesta página" |
| Um Botão de dashboard | "Disparada por: Um botão de dashboard" |
| O agente (`action_run`) | "Disparada por: Agente" |
| Um gatilho | O nome do gatilho: "Disparada por: Heater off when too hot". |
| Um comando de ação da Logic de uma execução do Process | A unidade e o grupo de ações: "Disparada por: Process · Oven 1 · After soak". |

## Usada por {#used-by}

O cartão **Usada por** lista tudo na estação que executa a ação ("Tudo na estação que executa esta ação."), lido quando a ação é aberta: uma linha por comando do Process que a dispara, por gatilho ligado a ela e por Botão de dashboard que a executa.

| Tipo | Local | Nome |
| --- | --- | --- |
| **Comando do Process** | O modelo. | O grupo de ações e o momento: `After soak · Conclusão`. |
| **Gatilho** | Vazio. | O gatilho, sob as pastas dele. |
| **Botão** | O dashboard. | A legenda do Botão. |

Sem nada, ele diz "Nada na estação executa esta ação."; quando as referências não podem ser lidas, "Esta ação ainda pode estar em uso. Tente de novo antes de excluí-la ou renomeá-la." com **Tentar de novo**. O cartão só lê, então todo papel o vê, também enquanto a estação está travada na engenharia. Leia-o antes de desabilitar ou excluir uma ação: um comando do Process cuja ação sumiu não executa nada e diz isso na página dele.

## Ler a execução que a disparou {#reading-the-run-that-fired-it}

Uma ação que uma execução do Process dispara na Conclusão lê essa execução sob o ramo [LastRun](process-addresses#lastrun) da unidade, que já a guarda quando a ação roda; uma disparada no Início lê os metadados da execução que começa sob `Procedure.Metadata`. Os pontos do Process se leem como valores puros, sem `.Value` depois deles, ao contrário das variáveis, e um ponto que a execução não guarda se lê como `null`, então proteja-o:

```js
if (Process.Oven_soak.Oven_1.LastRun.Verdict === "Ok") {
  Variables.Last_serial = Process.Oven_soak.Oven_1.LastRun.Metadata.Serial;
}
const peak = Process.Oven_soak.Oven_1.LastRun.Evaluations.Soak_check.Temperature_in_range.MaxValue;
if (peak !== null) {
  Variables.Last_peak = peak;
}
```

Os endereços são escritos por inteiro, como todo endereço de um script: um endereço não pode ser montado a partir de uma variável.

## Comandos {#commands}

| Comando | O que faz | Esmaecido quando | Não aparece quando |
| --- | --- | --- | --- |
| Executar | Salva o rascunho (com Configurar a lógica) e enfileira uma execução. | O runtime está parado. | O papel não tem Operar a lógica. |
| Habilitar / Desabilitar | Vira o Habilitado; uma ação desabilitada recusa o Executar e deixa ocioso todo gatilho ligado a ela, e um comando do Process que a dispara é recusado a cada vez e fica em Revisar. | A estação está travada ou em somente leitura. | O papel não tem Configurar a lógica. |
| Copiar | Duplica a ação na raiz Actions, com um nome livre, e destaca a cópia na tabela dessa raiz em vez de abri-la ([Criar e copiar](logic#creating-and-copying)); a cópia não tem execução registrada nem gatilho ligado a ela. | A estação está travada ou em somente leitura. | O papel não tem Configurar a lógica. |
| Excluir a ação | Pergunta "Excluir 'X'? Isso remove a ação. Isso não pode ser desfeito." Um gatilho ligado a ela fica sem ligação, e não é excluído; um comando do Process que a executa continua lá, não executa nada e fica em Revisar. | A estação está travada ou em somente leitura. | O papel não tem Configurar a lógica. |

O painel da raiz Actions lista toda ação com o estado dela na coluna Valor: "Habilitada · última execução `<quando>`", com "· erro" quando a última execução falhou, "· escrita falhou" quando ela deu certo e uma escrita dela não, ou "Desabilitada".

## O que as ações não fazem {#what-actions-do-not-do}

Elas não despacham comandos de execução do Process, não preparam um procedimento nem acrescentam um comentário de execução (esses pontos são valores de prontidão e de preparação, somente leitura); elas não reconhecem alarmes; elas não rodam em paralelo; elas não guardam estado entre execuções e não compartilham funções; e elas não são disparadas por mudança de valor nem por condição, apenas pelo Executar, pelo intervalo de um gatilho, por um botão de dashboard, pelo agente ou pelo comando de uma execução do Process. Uma execução do Process nunca espera a ação que dispara, e o resultado da ação nunca é escrito na execução.
