wow-another-ethernet-ip
Version:
Uma biblioteca para comunicação com dispositivos com o protocolo CIP via Ethernet/IP
1,200 lines (1,020 loc) • 65.3 kB
JavaScript
import { EmissorEvento } from "../../../Utils/EmissorEvento.js";
import { CompactLogixRockwell } from "./CompactLogix.js";
/**
* Gerencia a conexão com um CompactLogix e permite ler/escrever tags, além de observar alterações de tags.
** Essa classe é uma camada de abstração com melhorias no desempenho de leitura e escritas de tags, além de permitir observações de tags. (Coisa que a classe base não tem)
** Se preferir ter mais controle sobre lógica de leituras/escritas/observações, utilize a classe `CompactLogix.js` diretamente.
*/
export class CompactLogixV2 {
#configuracoes = {
/**
* Se os logs devem ser mostrados no console
*/
isMostrarConsoleLogs: false,
/**
* Se em caso de perda de conexão, será tentado reconectar automaticamente
*/
isAutoReconectar: false,
/**
* Endereço IP do CompactLogix
*/
ip: '',
/**
* Porta de comunicação com o CompactLogix
*/
porta: 44818,
/**
* Configuraçoes do comportamento da leitura de tags
*/
leitura: {
/**
* Tempo em millisegundos que deve ocorrer as leituras das tags pendentes de leituras
*/
tempoMinimoEntreLeituras: 1400
},
/**
* Configurações do comportamento das escritas de tags
*/
escrita: {
/**
* Tempo em millisegundos que deve ocorrer as escritas das tags pendentes de escritas
*/
tempoMinimoEntreEscritas: 1400
},
/**
* Configurações do comportamento de observações das tags
*/
observacoes: {
/**
* O tempo em millisegundos que as leituras das tags observadas devem ser realizadas
*/
tempoEntreLeituras: 1000
}
}
/**
* @callback CallbackConfirmaLeituraTag
* @param {String} tag - Tag que foi lido
* @param {DataTypeTag} dataType - Informações do Data Type da tag
* @param {DataValorTag} valor - Informações do valor lido da tag
* @param {Boolean} isLeituraSucesso - Se o retorno da tag retornou sucesso. Caso contrario, objeto erro deve conter os detalhes do erro
* @param {Object} erro - Se ocorreu algum erro, contém detalhes do erro
* @param {String} erro.descricao - Descrição ocorrida do erro
*/
/**
* Informações do Data Type de uma tag
* @typedef DataTypeTag
* @property {Boolean} dataType.isAtomico - Se o Data Type é atomico
* @property {Object} dataType.atomico - Se o Data Type for atomico, contém os detalhes do Data Type
* @property {Number} dataType.atomico.codigoAtomico - O código especifico desse tipo de tag atomico
* @property {String} dataType.atomico.descricao - Descrição do tipo atomico
* @property {Boolean} dataType.atomico.isSigned - Se o valor é um número com sinal
* @property {Number} dataType.atomico.tamanho - Tamanho em bytes do número atómico
* @property {Boolean} dataType.isStruct - Se o Data Type é uma Struct
* @property {Object} dataType.struct - Se o Data Type for uma Struct, contém os detalhes da Struct
* @property {Number} dataType.struct.codigoStruct - O código especifico desse tipo de tag Struct
* @property {String} dataType.struct.descricao - Descrição do tipo Struct
*/
/**
* Informações do valor de uma tag
* @typedef DataValorTag
* @property {Boolean} valor.isAtomico - Se o valor for atomico, contém o valor atomico
* @property {Object} valor.atomico - Detalhes do tipo atomico
* @property {Number} valor.atomico.numero - Valor atomico da tag(no caso um número)
* @property {Boolean} valor.isStruct - Se o valor for uma struct
* @property {Object} valor.struct - Se o valor for uma struct, contém os detalhes do Struct
* @property {Boolean} valor.struct.isASCIIString82 - Se o valor for uma Struct do tipo ASCII String 82
* @property {Object} valor.struct.ASCIIString82 - Detalhes do valor da Struct do tipo ASCII String 82
* @property {String} valor.struct.ASCIIString82.string - String que foi lida na tag
*/
/**
* @typedef LeituraTagResultado
* @property {String} tag - Tag que foi lido
* @property {DataTypeTag} dataType - Detalhes do Data Type da tag
* @property {DataValorTag} valor - Valor lido da tag. Esse valor é dinamico dependendo do Data Type da tag.
* @property {Object} erro - Se ocorreu algum erro, contém detalhes do erro
* @property {String} erro.descricao - Descrição ocorrida do erro
* @property {Boolean} isLeituraRecebida - Se essa tag já foi confirmado o recebimento da leitura(sendo ele sucesso ou não)
* @property {Boolean} isTagValida - Se a leitura da tag é valida e não ocorreu nenhum erro.
*/
/**
* @typedef PromiseLeituraTag
* @property {Number} id - ID unico incremental
* @property {LeituraTagResultado[]} tags - Tags da leitura
* @property {CallbackConfirmaLeituraTag} confirmaTag - Função a ser chamada para confirmar a leitura de uma tag
* @property {Number} idTimeout - ID do setTimeout caso demore demais para a leitura das tags
*/
/**
* @callback CallbackConfirmaEscritaTag
* @param {String} tag - A tag que foi escrita
* @param {Boolean} isEscritaSucesso - Se a escrita foi confirmada no CompactLogix
* @param {Object} erro - Se ocorreu um erro, contém os detalhes do erro
* @param {String} erro.descricao - A descrição do erro
*/
/**
* @typedef EscritaTagResultado
* @property {String} tag - Tag no CompactLogix
* @property {*} valor - Valor a ser escrito na tag
* @property {Boolean} isEscritoSucesso - Se a escrita foi confirmada no CompactLogix
* @property {Boolean} isRecebidoConfirmacao - Se essa tag já foi confirmado o recebimento pelo dispositivo(sendo ele sucesso ou não)
* @property {Object} erro - Se não foi possível escrever a tag, contém os detalhes do erro
* @property {String} erro.descricao - Descrição do erro
*/
/**
* @typedef PromiseEscritaTag
* @property {Number} id - ID unico incremental
* @property {EscritaTagResultado[]} tags - Tags a serem escritas
* @property {CallbackConfirmaEscritaTag} confirmaEscrita - Confirma a escrita de uma tag
* @property {Number} idTimeout - ID do setTimeout caso demore demais para a escrita das tags
*/
/**
* @typedef CacheDataType
* @property {String} tag - Nome da tag
* @property {Boolean} isAtomico - Se o Data Type é atomico
* @property {Object} atomico - Se o Data Type for atomico, contém os detalhes do Data Type
* @property {Number} atomico.codigoAtomico - O código especifico desse tipo de tag atomico
* @property {Boolean} isStruct - Se o Data Type é uma Struct
* @property {Object} struct - Se o Data Type for uma Struct, contém os detalhes da Struct
* @property {Number} struct.codigoStruct - O código especifico desse tipo de tag Struct
*/
/**
* @callback CallbackObservaTagValorAlterado
* @param {DataValorTag} valorAntigo - O valor anterior da tag
* @param {DataValorTag} valorNovo - O novo valor da tag
*/
/**
* @typedef CallbackObservacaoTag
* @property {Number} id - ID unico desse callback
* @property {CallbackObservaTagValorAlterado} onAlterado - Função a ser chamada quando o valor da tag mudar
*/
/**
* @typedef ObservacaoDeTag
* @property {String} tag - Nome da tag
* @property {DataTypeTag} dataType - Informações do Data Type da tag
* @property {DataValorTag} valor - Valor atual da tag
* @property {CallbackObservacaoTag[]} callbacks - Callbacks que desejam receber notificações de alterações dessa tag
*/
/**
* Controle de estado com o CompactLogix
*/
#estado = {
/**
* Instancia do controlador CompactLogix da minha biblioteca que comunica via EtherNet IP
* @type {CompactLogixRockwell}
*/
controlador: undefined,
/**
* Estado de leituras de tags
*/
leituras: {
/**
* Tags que estão na fila de pendentes para serem lidas
* @type {PromiseLeituraTag[]}
*/
pendentesDeLeitura: [],
/**
* O totalizador de lote é um ID incremental que é utilizado para identificar um lote de leituras.
*/
totalizadorLoteId: 0,
/**
* Se existe uma chamada de leitura pendente para executar
*/
isLeituraPendente: false,
/**
* Se a leitura está sendo executada, ou seja aguardando a resposta do CompactLogix
*/
isLeituraExecutando: false,
/**
* Data da última vez que foi executado a leitura das tags no CompactLogix
* @type {Date}
*/
dataUltimaExecucao: undefined
},
/**
* Estado de escrita de tags
*/
escritas: {
/**
* Tags que estão na fila para serem escritas
* @type {PromiseEscritaTag[]}
*/
pendentesDeEscrita: [],
/**
* O totalizador de lote é um ID incremental que é utilizado para identificar um lote de escritas.
*/
totalizadorLoteId: 0,
/**
* Se já existe uma chamada pendente que será executada
*/
isEscritaPendente: false,
/**
* Se esta atualmente executando a escrita das tags no CompactLogix
*/
isEscritaExecutando: false,
/**
* Data em que ocorreu a ultima execução de escrita de tags
*/
dataUltimaExecucao: undefined
},
/**
* Estado de observações de tags
*/
observacoes: {
/**
* Tags observadas
* @type {ObservacaoDeTag[]}
*/
tags: [],
/**
* Se a leitura das tags está sendo realizada para disparar as observações
*/
isRealizandoLeitura: false,
/**
* ID do setInterval criado que realiza o trigger de leitura das tags observadas
*/
IDSetInterval: -1
},
/**
* O cache de Data Types armazena os tipos das últimas tags que foram escritas/lidas.
*
* O comportamento desse cache é definido assim:
* - Se a tag não estiver no cache e for uma escrita, a chamada da função ao Compact informara para auto-reconhecer a tag, o que resultará em duas requisições (busca tipo da tag e depois escreve). Se retornar sucesso, a tag será adicionada ao cache.
* - Se a tag não estiver no cache e for uma leitura, se o retorno da leitura for sucesso, a tag será adicionada ao cache.
* - Se a tag existir no cache, o tipo dela será informado na requisição ao Compact se necessario(no caso de escritas), evitando overhead de 2 requisições.
* - Se por uma rara chance o controlador retornar que o Data Type tá incorreto, é provavél que a tag no controlador foi alterado o tipo dela. Nesses casos, o cache identificara que esse erro ocorreu e atualizará o cache com o novo Data Type.
*
* @type {CacheDataType[]}
*/
cacheDataTypes: [],
/**
* Emissor de eventos customizado
* @type {EmissorEvento}
*/
emissorEventos: undefined
}
/**
* Instanciar uma nova conexão com um CompactLogix
* @param {Object} construtorOpcoes - Propriedades de conexão
* @param {Boolean} construtorOpcoes.isMostrarConsoleLogs - Se deve mostrar logs no console
* @param {Object} construtorOpcoes.conexao - Propriedades de conexão com o controlador CompactLogix
* @param {String} construtorOpcoes.conexao.ip - Endereço IP do CompactLogix
* @param {Number} construtorOpcoes.conexao.porta - Porta de comunicação com o CompactLogix
* @param {Boolean} construtorOpcoes.isAutoReconectar - Se em caso de perca de conexão, será tentado reconectar automaticamente
*/
constructor(construtorOpcoes) {
if (construtorOpcoes == undefined) throw new Error('As propriedades do construtor precisam ser informadas.');
if (construtorOpcoes.isMostrarConsoleLogs) this.#configuracoes.isMostrarConsoleLogs = construtorOpcoes.isMostrarConsoleLogs;
if (construtorOpcoes.conexao == undefined) throw new Error('As propriedades de conexão com o CompactLogix precisam ser informadas.');
if (construtorOpcoes.conexao.ip == undefined) throw new Error('O endereço IP do CompactLogix precisa ser informado.');
if (construtorOpcoes.isAutoReconectar) this.#configuracoes.isAutoReconectar = construtorOpcoes.isAutoReconectar;
// Se informado a porta opcional, eu utilizo ela, senão eu utilizo a padrão 44818
if (construtorOpcoes.conexao.porta != undefined) this.#configuracoes.porta = construtorOpcoes.conexao.porta;
this.#configuracoes.ip = construtorOpcoes.conexao.ip;
this.#estado.emissorEventos = new EmissorEvento(`CompactLogix ${this.#configuracoes.ip}`);
this.#estado.controlador = new CompactLogixRockwell({
ip: this.#configuracoes.ip,
porta: this.#configuracoes.porta,
habilitaLogs: false,
autoReconectar: this.#configuracoes.isAutoReconectar
})
this.#estado.controlador.onLog((msgLog) => {
this.log(msgLog);
})
}
/**
* Iniciar a conexão com o CompactLogix
*/
async conectar() {
const retornoConexao = {
/**
* Se conectou com sucesso
*/
isConectado: false,
/**
* Se não conectou, contém detalhes do erro
*/
erro: {
descricao: ''
}
}
const retornoConectar = await this.#estado.controlador.conectar();
if (retornoConectar.isConectou) {
retornoConexao.isConectado = true;
} else {
retornoConexao.erro.descricao = retornoConectar.erro.descricao;
}
return retornoConexao;
}
/**
* Encerrar a conexão com o CompactLogix. Independente se está conectado ou não, o Socket será destruido, leituras e escritas pendentes serão removidas.
*/
desconectar() {
this.#estado.controlador.desconectar();
}
getParametrosConexao() {
return {
ip: this.#configuracoes.ip,
porta: this.#configuracoes.porta
}
}
/**
* @typedef RetLerTags
* @property {Boolean} isSucesso - Se a leitura foi sucesso
* @property {Object} sucesso - Tags lidas com sucesso
* @property {LeituraTagResultado[]} sucesso.tags - Tags lidas com sucesso
* @property {Object} erro - Descrição do erro ocorrido
* @property {String} erro.descricao - Descrição do erro ocorrido
*/
/**
* Ler tags no CompactLogix
* @param {Array<String>} tags - Array de tags string a serem lidas
* @returns {Promise<RetLerTags>}
*/
async lerTags(tags) {
/**
* @type {RetLerTags}
*/
let retornoLeitura = {
isSucesso: false,
sucesso: {
tags: []
},
erro: {
descricao: ''
}
}
// Precisa ser um array de tags
if (tags == undefined || Array.isArray(tags) == false) {
throw new Error('As tags a serem lidas não foram informadas ou não são um array.');
}
// Tags que vão ser adicionados na leitura
let tagsDesejadas = [];
for (const tagString of tags) {
if (typeof tagString != 'string') {
throw new Error(`As tags a serem lidas precisam ser strings. Foi informado um valor ${tagString} do tipo ${typeof tagString}`);
}
// Não adicionar tags repetidas
if (tagsDesejadas.indexOf(tagString) != -1) continue
tagsDesejadas.push(tagString);
}
/**
* @type {PromiseLeituraTag}
*/
let novaLeitura = {
id: this.#estado.leituras.totalizadorLoteId++,
tags: [],
confirmaTag: () => { },
idTimeout: -1
}
// Adicionar o status das tags pro retorno
for (const tagString of tagsDesejadas) {
novaLeitura.tags.push({
tag: tagString,
valor: {},
erro: {
descricao: ''
},
isLeituraRecebida: false,
isTagValida: false
})
}
const logLeitura = (string) => {
this.log(`[Leitura Tag] Lote #${novaLeitura.id}: ${string}`);
}
const promiseAguarda = new Promise((resolve) => {
// Essa função é utilizada para confirmar a leitura de uma tag
/**
* @type {CallbackConfirmaLeituraTag}
*/
const confirmaLeituraTag = (tag, datatype, valor, isLeituraSucesso, detalhesErro) => {
// Encontrar a tag confirmada na lista de tags pendentes
const tagObj = novaLeitura.tags.find(t => t.tag == tag);
if (tagObj != undefined) {
// Se a tag já foi recebida, eu só ignoro
if (tagObj.isLeituraRecebida) {
logLeitura(`A tag ${tag} já foi confirmada anteriormente. Ignorando...`);
isTodosConfirmados();
return;
}
// Se teve sucesso na leitura, algumErro será nullo
if (isLeituraSucesso) {
logLeitura(`Tag ${tag} confirmada a leitura com sucesso.`);
tagObj.isTagValida = true;
// Adicionar o valor e o Data Type da tag
tagObj.valor = valor;
tagObj.dataType = datatype;
} else {
if (detalhesErro == undefined) {
detalhesErro = {
descricao: 'Erro da leitura não informado.'
}
}
logLeitura(`Tag ${tag} confirmada a leitura com erro: ${detalhesErro.descricao}`);
tagObj.erro.descricao = detalhesErro.descricao;
}
tagObj.isLeituraRecebida = true;
// Verificar se todas as tags já foram confirmadas e se sim, resolver a promise
isTodosConfirmados();
} else {
logLeitura(`Tag ${tag} não foi encontrada na lista de tags pendentes.`);
}
}
// Valida se todas as tags foram lidas
const isTodosConfirmados = () => {
const tagsAguardandoConfirmacao = novaLeitura.tags.filter(t => t.isLeituraRecebida == false);
if (tagsAguardandoConfirmacao.length == 0) {
retornoLeitura.isSucesso = true;
// Cancelar o timeout de leitura
clearTimeout(novaLeitura.idTimeout);
// Remover da lista de leituras pendentes
this.#estado.leituras.pendentesDeLeitura = this.#estado.leituras.pendentesDeLeitura.filter(l => l.id != novaLeitura.id);
logLeitura(`Todas as tags foram confirmadas. Resolvendo a promise...`);
retornoLeitura.sucesso.tags = novaLeitura.tags;
return resolve(retornoLeitura);
} else {
logLeitura(`Aguardando confirmações das tags ${tagsAguardandoConfirmacao.map(t => t.tag).join(', ')}...`);
}
}
// Inicia um setTimeout para retornar caso demore na leitura das tags
novaLeitura.idTimeout = setTimeout(() => {
const tagsAguardandoConfirmacao = novaLeitura.tags.filter(t => t.isLeituraRecebida == false);
tagsAguardandoConfirmacao.forEach(t => {
t.erro.descricao = 'O CompactLogix demorou para retornar a leitura da tag.';
t.isLeituraRecebida = true;
})
// Se pelo menos alguma tag ocorreu sucesso, eu considero que a leitura foi sucesso
if (tagsAguardandoConfirmacao.length != novaLeitura.tags.length) {
retornoLeitura.isSucesso = true;
} else {
// Se todas as tags deram erro de leitura, não devo considerar como sucesso.
retornoLeitura.erro.descricao = `Timeout ocorrido para todas as leituras de tags`;
}
logLeitura(`Timeout de leitura ocorrido para as tags: ${tagsAguardandoConfirmacao.map(t => t.tag).join(', ')}. Resolvendo promise...`);
retornoLeitura.sucesso.tags = novaLeitura.tags.map(t => {
return {
valor: t.valor,
erro: t.erro,
isLeituraRecebida: t.isLeituraRecebida,
tag: t.tag,
isTagValida: t.isTagValida
}
})
return resolve(retornoLeitura);
}, 5000);
novaLeitura.confirmaTag = confirmaLeituraTag;
})
// Adicionar essa leitura pendente a lista para ser lida quando possível
this.#estado.leituras.pendentesDeLeitura.push(novaLeitura);
this.#triggerLeituraTags();
return promiseAguarda;
}
/**
* @typedef RetEscreverTags
* @property {Boolean} isSucesso - Se a solicitação de escrita foi enviada e recebido com sucesso(não garante que tags individuais tenham sido sucesso, apenas que o dispositivo remoto retornou alguma resposta)
* @property {Object} sucesso - Se a solicitação deu sucesso, contém os detalhes das tags solicitadas para escritas
* @property {EscritaTagResultado[]} sucesso.tags - Tags que foram solicitadas para escrita com seus status
* @property {Object} erro - Descrição do erro ocorrido
* @property {String} erro.descricao - Descrição do erro ocorrido
*/
/**
* Escrever tags no CompactLogix
* @param {Object[]} tags - Tags para escrever no CompactLogix
* @param {String} tags[].tag - Tag a ser escrita
* @param {*} tags[].valor - Valor a ser escrito na tag
* @returns {Promise<RetEscreverTags>}
*/
async escreverTags(tags) {
/**
* @type {RetEscreverTags}
*/
let retornoEscrita = {
isSucesso: false,
sucesso: {
tags: []
},
erro: {
descricao: ''
}
}
// Se não foi informado um array válido de tags, retornar erro
if (tags == undefined || Array.isArray(tags) == false) {
throw new Error('As tags a serem escritas não foram informadas ou não são um array.');
}
/**
* @type {PromiseEscritaTag}
*/
let novaEscrita = {
id: this.#estado.escritas.totalizadorLoteId++,
tags: [],
confirmaEscrita: () => { }
}
// Adicionar as tags a serem escritas
for (const tag of tags) {
novaEscrita.tags.push({
tag: tag.tag,
valor: tag.valor,
erro: {
descricao: ''
},
isEscritoSucesso: false,
isRecebidoConfirmacao: false
})
}
const logEscrita = (string) => {
this.log(`[Escrita Tag] Lote #${novaEscrita.id} (${novaEscrita.tags.map(t => t.tag).join(', ')}) ${string}`);
}
const promiseAguardaEscrita = new Promise((resolve) => {
// Vincular a função de confirmação de escrita de tag
novaEscrita.confirmaEscrita = (tag, isEscritaSucesso, erro) => {
const tagObj = novaEscrita.tags.find(t => t.tag == tag);
if (tagObj != undefined) {
// Se a tag já foi recebida
if (tagObj.isRecebidoConfirmacao) {
// logEscrita(`A tag ${tag} já foi confirmada anteriormente. Ignorando...`);
isTudoOk();
return;
}
if (isEscritaSucesso) {
// logEscrita(`Tag '${tag}' confirmada a escrita com sucesso.`);
tagObj.isEscritoSucesso = true;
} else {
if (erro == undefined) {
erro = {
descricao: 'Erro da escrita não informado.'
}
}
// logEscrita(`Tag '${tag}' confirmada com erro. Erro: ${erro.descricao}`);
tagObj.erro.descricao = erro.descricao;
}
tagObj.isRecebidoConfirmacao = true;
isTudoOk();
} else {
// logEscrita(`Tag ${tag} não foi encontrada na lista de tags pendentes de escrita.`);
}
}
const isTudoOk = () => {
// Valida se todas as escritas foram realizadas com sucesso pra retornar a mensagem
const escritasAguardando = novaEscrita.tags.filter(t => t.isRecebidoConfirmacao == false);
if (escritasAguardando.length == 0) {
retornoEscrita.isSucesso = true;
// Cancelar o timeout de escrita
clearTimeout(novaEscrita.idTimeout);
// Remover da lista pendente de escritas
this.#estado.escritas.pendentesDeEscrita = this.#estado.escritas.pendentesDeEscrita.filter(e => e.id != novaEscrita.id);
// logEscrita(`Todas as tags foram confirmadas. Resolvendo a promise...`);
retornoEscrita.sucesso.tags = novaEscrita.tags.map(t => {
return {
tag: t.tag,
valor: t.valor,
isEscritoSucesso: t.isEscritoSucesso,
erro: t.erro,
isRecebidoConfirmacao: t.isRecebidoConfirmacao
}
})
resolve(retornoEscrita);
} else {
// logEscrita(`Aguardando confirmações das tags ${escritasAguardando.map(t => t.tag).join(', ')}...`);
}
}
// Definir o timeout se demorar para receber as confirmações
novaEscrita.idTimeout = setTimeout(() => {
const escritasAguardando = novaEscrita.tags.filter(t => t.isRecebidoConfirmacao == false);
escritasAguardando.forEach(t => {
t.erro.descricao = 'O CompactLogix demorou para retornar a escrita da tag.';
t.isRecebidoConfirmacao = true;
})
// Se pelo menos alguma tag ocorreu sucesso, eu considero que a leitura foi sucesso
if (escritasAguardando.length != novaEscrita.tags.length) {
retornoEscrita.isSucesso = true;
} else {
// Se todas as tags deram erro de leitura, não devo considerar com sucesso.
retornoEscrita.erro.descricao = `Timeout ocorrido para todas as escritas de tags`;
}
// logEscrita(`Timeout de escrita ocorrido para as tags: ${escritasAguardando.map(t => t.tag).join(', ')}. Resolvendo promise...`);
retornoEscrita.sucesso.tags = novaEscrita.tags.map(t => {
return {
tag: t.tag,
valor: t.valor,
isEscritoSucesso: t.isEscritoSucesso,
erro: t.erro,
isRecebidoConfirmacao: t.isRecebidoConfirmacao,
}
})
return resolve(retornoEscrita);
}, 5000);
})
// Adicionar ao array de escritas pendentes
this.#estado.escritas.pendentesDeEscrita.push(novaEscrita);
// Triggar a escritura de tags se possível
this.#triggerEscreverTags();
return promiseAguardaEscrita;
}
/**
* Adicionar uma tag para ser observada e invocar seu callback quando houver alteração
*
* **A tag precisa exitir e ser suportada pela blblioteca.**
* @param {String} tag - Nome da tag
* @param {Object} callbacks - Definir seus callbacks para executar quando certos eventos ocorrerem
* @param {CallbackObservaTagValorAlterado} callbacks.onAlterado - Função a ser chamada quando o valor da tag mudar
*/
async observarTag(tag, callbacks) {
if (tag == undefined || typeof tag != 'string') {
throw new Error('A tag a ser observada precisa ser informada e ser uma string.');
}
const retObservar = {
/**
* Se ocorreu sucesso em iniciar o processo de observação da tag. Sucesso é retornado se:
*
* - A tag existe
* - É suportada pela biblioteca
*/
isSucesso: false,
/**
* Se sucesso em observar, contém detalhes do observador gerado
*/
sucesso: {
/**
* ID de callback unico para permitir cancelar depois
*/
idCallback: undefined
},
/**
* Se ocorreu algum erro, contém detalhes adicionais do erro
*/
erro: {
descricao: ''
}
}
const requisitaLeituraTag = await this.lerTags([tag]);
if (!requisitaLeituraTag.isSucesso) {
retObservar.erro.descricao = requisitaLeituraTag.erro.descricao;
return retObservar;
}
const tagEncontrada = requisitaLeituraTag.sucesso.tags.find(t => t.tag == tag);
if (tagEncontrada == undefined) {
retObservar.erro.descricao = `A tag ${tag} não foi encontrada na solicitação de leitura ao controlador, não será possível observar.`;
return retObservar;
}
if (!tagEncontrada.isTagValida) {
retObservar.erro.descricao = `A tag ${tag} não foi lida com sucesso, motivo: ${tagEncontrada.erro.descricao}.`;
return retObservar;
}
// Se for valida, adicionar as observações
let tagObservada = this.#estado.observacoes.tags.find(t => t.tag == tag);
if (tagObservada == undefined) {
tagObservada = {
tag: tag,
dataType: tagEncontrada.dataType,
valor: tagEncontrada.valor,
callbacks: [],
}
this.#estado.observacoes.tags.push(tagObservada);
}
/**
* @type {CallbackObservacaoTag}
*/
const novoCallback = {
id: tagObservada.callbacks.length + 1,
onAlterado: callbacks.onAlterado
}
tagObservada.callbacks.push(novoCallback);
retObservar.isSucesso = true;
retObservar.sucesso.idCallback = novoCallback.id;
if (this.#estado.observacoes.IDSetInterval == -1) {
this.iniciarObservadorInterval();
}
return retObservar;
}
/**
* Excluir um callback adicionado de observação para uma tag
* @param {String} tag - Tag original que foi observada
* @param {Number} id - ID do callback a ser removido
*/
pararCallbackObservacaoTag(tag, id) {
const exclusaoCallback = {
isExcluido: false,
erro: {
descricao: ''
}
}
const tagAlvo = this.#estado.observacoes.tags.find(t => t.tag == tag);
if (tagAlvo == undefined) {
exclusaoCallback.erro.descricao = `A tag ${tag} não está sendo observada.`;
return exclusaoCallback;
}
const callbackAlvo = tagAlvo.callbacks.find(c => c.id == id);
if (callbackAlvo == undefined) {
exclusaoCallback.erro.descricao = `O callback com ID ${id} não foi encontrado na tag ${tag}.`;
return exclusaoCallback;
}
tagAlvo.callbacks = tagAlvo.callbacks.filter(c => c.id != id);
if (tagAlvo.callbacks.length == 0) {
this.pararObservacaoTag(tag);
}
exclusaoCallback.isExcluido = true;
return exclusaoCallback;
}
/**
* Excluir todos os callbacks de uma tag observada
* @param {String} tag
*/
pararObservacaoTag(tag) {
const exclusaoTag = {
isExcluido: false,
erro: {
descricao: ''
}
}
const tagAlvo = this.#estado.observacoes.tags.find(t => t.tag == tag);
if (tagAlvo == undefined) {
exclusaoTag.erro.descricao = `A tag ${tag} não está sendo observada.`;
return exclusaoTag;
}
tagAlvo.callbacks = [];
exclusaoTag.isExcluido = true;
this.#estado.observacoes.tags = this.#estado.observacoes.tags.filter(t => t.tag != tag);
return exclusaoTag;
}
/**
* Verificar se uma tag está sendo observada
* @param {String} tag - Nome da tag a ser verificada
*/
isTagObservada(tag) {
return this.#estado.observacoes.tags.find(t => t.tag == tag) != undefined
}
/**
* Inicia o setInterval que verifica por alterações nas tags
*/
iniciarObservadorInterval() {
if (this.#estado.observacoes.IDSetInterval != -1) {
clearInterval(this.#estado.observacoes.IDSetInterval);
this.#estado.observacoes.IDSetInterval = -1;
}
this.#estado.observacoes.IDSetInterval = setInterval(() => {
this.#triggerObservacaoTags();
}, this.#configuracoes.observacoes.tempoEntreLeituras);
this.log(`Iniciado observador de tags com intervalo de ${this.#configuracoes.observacoes.tempoEntreLeituras}ms.`);
}
/**
* Inicia a leitura das tags que estão pendentes
*/
async #triggerLeituraTags() {
// Se já tiver uma chamada de leitura pendente, não deixar iniciar outra
if (this.#estado.leituras.isLeituraPendente) {
this.log(`Já existe uma chamada de leitura pendente. Ignorando...`);
return;
}
// Se já tiver uma chamada de leitura em execução e aguardando a resposta do CompactLogix
if (this.#estado.leituras.isLeituraExecutando) {
this.log(`Já existe uma chamada de leitura em execução no CompactLogix. Ignorando...`);
return;
}
if (this.#estado.leituras.pendentesDeLeitura.length == 0) {
this.log(`Não existem leituras pendentes para serem lidas.`);
return;
}
let dataAgra = new Date();
const diffMsUltimaExecucao = dataAgra.getTime() - (this.#estado.leituras.dataUltimaExecucao != undefined ? this.#estado.leituras.dataUltimaExecucao.getTime() : this.#configuracoes.leitura.tempoMinimoEntreLeituras + 1);
// Se o tempo desde a ultima leitura ainda não atingiu o tempo minimo de espera
if (this.#estado.leituras.dataUltimaExecucao != undefined && (diffMsUltimaExecucao < this.#configuracoes.leitura.tempoMinimoEntreLeituras)) {
this.#estado.leituras.isLeituraPendente = true;
let diffRestante = this.#configuracoes.leitura.tempoMinimoEntreLeituras - diffMsUltimaExecucao;
setTimeout(() => {
this.#estado.leituras.isLeituraPendente = false;
this.#triggerLeituraTags();
}, diffRestante);
return;
}
this.#estado.leituras.dataUltimaExecucao = dataAgra;
this.log(`Iniciando leitura de tags pendentes...`);
this.#estado.leituras.isLeituraExecutando = true;
this.#estado.emissorEventos.disparaEvento('trigger-leitura-tags-executando');
/**
* Array de string contendo as tags para ler
* @type {String[]}
*/
const listaDeTagsParaLer = [];
// Pra cada leitura pendente, coletar as tags que preciso adicionar no TagGroup pra solicitar
for (const leituraTag of this.#estado.leituras.pendentesDeLeitura) {
leituraTag.tags.forEach(tag => {
if (listaDeTagsParaLer.indexOf(tag.tag) == -1) {
listaDeTagsParaLer.push(tag.tag);
}
});
}
// Iniciar a solicitação de leitura
const retornoLeituraTags = await this.#estado.controlador.lerMultiplasTags(listaDeTagsParaLer);
// Passar por todas as tags solicitadas e verificar no retorno da leitura multipla
for (const leituraDeTag of listaDeTagsParaLer) {
// Encontrar os lotes de leituras pendentes que possuem essa tag no meio
const loteLeiturasDessaTag = this.#estado.leituras.pendentesDeLeitura.filter(l => l.tags.find(t => t.tag == leituraDeTag) != undefined);
this.log(`Leitura da tag ${leituraDeTag} tem ${loteLeiturasDessaTag.length} callbacks pendentes.`);
// Se a leitura múltipla não deu certo, nenhuma tag foi lida. Retorno o erro
if (!retornoLeituraTags.isSucesso) {
// Passar em cada lote, e confirmar para o lote que a leitura retornou erro
for (const loteLeitura of loteLeiturasDessaTag) {
loteLeitura.confirmaTag(leituraDeTag, undefined, undefined, false, {
descricao: retornoLeituraTags.erro.descricao
})
}
continue;
}
// Beleza, se a leitura multipla deu certo, isso me diz que pelo menos o dispositivo retornou o resultado das tags lidas
const resultadoTagLida = retornoLeituraTags.sucesso.tags.find(t => t.tag == leituraDeTag);
// Por algum motivo a tag lida não foi retornado no retorno da leitura multipla(não devia acontecer)
if (resultadoTagLida == undefined) {
for (const loteLeitura of loteLeiturasDessaTag) {
loteLeitura.confirmaTag(leituraDeTag, undefined, undefined, false, {
descricao: `Não foi retornado nenhuma informação da tag na solicitação de leitura do controlador.`
})
}
continue;
}
// Se a leitura da tag retornado não deu sucesso
if (!resultadoTagLida.isSucesso) {
for (const loteLeitura of loteLeiturasDessaTag) {
loteLeitura.confirmaTag(leituraDeTag, undefined, undefined, false, {
descricao: resultadoTagLida.erro.descricao
})
}
continue;
}
// Atualizar o cache com as informações do Data Type da tag lida
if (resultadoTagLida.sucesso.isAtomico) {
this.atualizarCacheTagDataType(leituraDeTag, {
isAtomico: true,
atomico: {
codigoAtomico: resultadoTagLida.sucesso.atomico.dataType.codigo
}
})
} else if (resultadoTagLida.sucesso.isStruct) {
this.atualizarCacheTagDataType(leituraDeTag, {
isStruct: true,
struct: {
codigoStruct: resultadoTagLida.sucesso.struct.dataTypeStruct.codigoTipoStruct
}
})
}
// Leitura confirmada com sucesso
for (const loteLeitura of loteLeiturasDessaTag) {
if (resultadoTagLida.sucesso.isAtomico) {
loteLeitura.confirmaTag(leituraDeTag, {
isAtomico: true,
atomico: {
codigoAtomico: resultadoTagLida.sucesso.atomico.dataType.codigo,
descricao: resultadoTagLida.sucesso.atomico.dataType.descricao,
isSigned: resultadoTagLida.sucesso.atomico.dataType.isSigned,
tamanho: resultadoTagLida.sucesso.atomico.dataType.tamanho
}
}, {
isAtomico: true,
atomico: {
numero: resultadoTagLida.sucesso.atomico.valor
}
}, true);
} else if (resultadoTagLida.sucesso.isStruct) {
// Verificar o tipo da Struct
switch (resultadoTagLida.sucesso.struct.dataTypeStruct.codigoTipoStruct) {
case this.#estado.controlador.getDataTypes().structs.ASCIISTRING82.codigoTipoStruct: {
loteLeitura.confirmaTag(leituraDeTag, {
isStruct: true,
struct: {
codigoStruct: resultadoTagLida.sucesso.struct.dataTypeStruct.codigoTipoStruct,
descricao: resultadoTagLida.sucesso.struct.dataTypeStruct.descricao
}
}, {
isStruct: true,
struct: {
isASCIIString82: true,
ASCIIString82: {
string: resultadoTagLida.sucesso.struct.valor.stringConteudo
}
}
}, true)
break;
}
// Se for um Data Type Struct não suportado, retorno como erro
default: {
loteLeitura.confirmaTag(leituraDeTag, undefined, undefined, false, {
descricao: `Data Type Struct não suportado para leitura: ${resultadoTagLida.sucesso.struct.dataTypeStruct}.`
})
break;
}
}
} else {
loteLeitura.confirmaTag(leituraDeTag, undefined, undefined, false, {
descricao: `Data Type não suportado para leitura da tag`
})
}
}
}
this.#estado.leituras.isLeituraExecutando = false;
this.#estado.emissorEventos.disparaEvento('trigger-leitura-tags-finalizado');
}
/**
* Inicia a escrita das tags que estão pendentes
*/
async #triggerEscreverTags() {
if (this.#estado.escritas.isEscritaPendente) {
this.log(`Já existe uma chamada de escrita pendente. Ignorando...`);
return;
}
if (this.#estado.escritas.isEscritaExecutando) {
this.log(`Já existe uma chamada de escrita em execução no CompactLogix. Ignorando...`);
return;
}
if (this.#estado.escritas.pendentesDeEscrita.length == 0) {
this.log(`Não existem escritas pendentes para executar o trigger de escrita.`);
return;
}
let dataAgra = new Date();
const diffMsUltimaExecucao = dataAgra.getTime() - (this.#estado.escritas.dataUltimaExecucao != undefined ? this.#estado.escritas.dataUltimaExecucao.getTime() : this.#configuracoes.escrita.tempoMinimoEntreEscritas + 1);
// Se o tempo desde a ultima escrita ainda não atingiu o tempo minimo de espera
if (this.#estado.escritas.dataUltimaExecucao != undefined && (diffMsUltimaExecucao < this.#configuracoes.escrita.tempoMinimoEntreEscritas)) {
this.#estado.escritas.isEscritaPendente = true;
let diffRestante = this.#configuracoes.escrita.tempoMinimoEntreEscritas - diffMsUltimaExecucao;
setTimeout(() => {
this.#estado.escritas.isEscritaPendente = false;
this.#triggerEscreverTags();
}, diffRestante);
return;
}
this.#estado.escritas.dataUltimaExecucao = dataAgra;
this.log(`Iniciando escrita de tags pendentes...`);
this.#estado.escritas.isEscritaExecutando = true;
this.#estado.emissorEventos.disparaEvento('trigger-escreve-tags-executando')
/**
* @typedef TagParaEscrever
* @property {String} tag - Nome da tag
* @property {*} valor - Valor a ser escrito na tag
*/
/**
* Tags para adicionar no grupo
* @type {TagParaEscrever[]}
*/
const tagsParaEscrever = []
for (const tagEscrever of this.#estado.escritas.pendentesDeEscrita) {
tagEscrever.tags.forEach(tag => {
tagsParaEscrever.push({
tag: tag.tag,
valor: tag.valor
})
});
}
this.log(`Iniciando escrita de ${tagsParaEscrever.length} tags...`);
const retornoEscritaTags = await this.#estado.controlador.escreveMultiplasTags(tagsParaEscrever.map(t => {
// Verificar a existencia do DataType no cache
const existeDataTypeCache = this.#estado.cacheDataTypes.find(cT => cT.tag == t.tag);
let dataTypePadrao = {
isResolverAutomaticamente: true,
resolverAutomaticamente: {
valor: t.valor
}
}
// Se existir, definir no objeto o data type com seu valor correto
if (existeDataTypeCache != undefined) {
this.log(`Tag ${t.tag} encontrada no cache de Data Types.`);
// Tipo atomicos
if (existeDataTypeCache.isAtomico) {
dataTypePadrao = {
isAtomico: true,
atomico: {
codigoAtomico: existeDataTypeCache.atomico.codigoAtomico,
valor: t.valor
}
}
} else if (existeDataTypeCache.isStruct) {
// Structs
dataTypePadrao = {
isStruct: true,
struct: {
codigoStruct: existeDataTypeCache.struct.codigoStruct,
classeStruct: {}
}
}
// Identificar o tipo da Struct
switch (existeDataTypeCache.struct.codigoStruct) {
// Se for uma String ASCII 82
case this.#estado.controlador.getDataTypes().structs.ASCIISTRING82.codigoTipoStruct: {
const classeStructASCIIString82 = {
string: t.valor
}
dataTypePadrao.struct.classeStruct = classeStructASCIIString82;
break;
}
default: {
break;
}
}
}
}
return {
tag: t.tag,
dataType: dataTypePadrao
}
}))
// Passar por cada tag solicitada e verificar se houve retorno na resposta da solicitação de escrita
for (const tagParaEscrever of tagsParaEscrever) {
// Encontrar os lotes de escritas pendentes que possuem essa tag no meio
const loteEscritasDessaTag = this.#estado.escritas.pendentesDeEscrita.filter(l => l.tags.find(t => t.tag == tagParaEscrever.tag) != undefined);
this.log(`Escrita da tag ${tagParaEscrever.tag} tem ${loteEscritasDessaTag.length} callbacks pendentes.`);
// Se a escrita múltipla não deu certo, nenhuma tag foi escrita. Retorno o erro