1087 lines
51 KiB
JavaScript
1087 lines
51 KiB
JavaScript
const net = require('net');
|
|
|
|
class FiberhomeManager {
|
|
constructor() {
|
|
this.tl1Connection = null;
|
|
this.commandId = 1;
|
|
|
|
// Configuração apenas para UNM2000 via TL1
|
|
this.config = {
|
|
unm2000: {
|
|
host: '131.0.192.9',
|
|
port: 3337,
|
|
username: '1',
|
|
password: '1',
|
|
timeout: 15000,
|
|
defaultOLT: 'AN5516-06_172.16.10.6' // Nome correto da OLT no sistema
|
|
}
|
|
};
|
|
|
|
this.logger = require('../utils/logger');
|
|
}
|
|
|
|
/**
|
|
* Testar conectividade de rede com um host
|
|
*/
|
|
async testarConectividade(host, port, timeout = 5000) {
|
|
return new Promise((resolve) => {
|
|
const socket = new net.Socket();
|
|
|
|
socket.setTimeout(timeout);
|
|
|
|
socket.connect(port, host, () => {
|
|
this.logger.debug(`Conectividade OK: ${host}:${port}`);
|
|
socket.destroy();
|
|
resolve(true);
|
|
});
|
|
|
|
socket.on('error', (error) => {
|
|
this.logger.debug(`Erro de conectividade ${host}:${port}:`, error.message);
|
|
resolve(false);
|
|
});
|
|
|
|
socket.on('timeout', () => {
|
|
this.logger.debug(`Timeout de conectividade ${host}:${port}`);
|
|
socket.destroy();
|
|
resolve(false);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Conectar ao UNM2000 via TL1
|
|
*/
|
|
async conectarUNM2000() {
|
|
try {
|
|
this.logger.debug('=== INICIO CONEXAO UNM2000 ===');
|
|
this.logger.debug('Configuração:', this.config.unm2000);
|
|
|
|
if (this.tl1Connection && !this.tl1Connection.destroyed) {
|
|
this.logger.debug('Conexão TL1 já existe e está ativa');
|
|
return true;
|
|
}
|
|
|
|
this.logger.debug('Conectando ao UNM2000 via TL1', this.config.unm2000);
|
|
|
|
// Primeiro testar conectividade básica
|
|
this.logger.debug('Testando conectividade básica...');
|
|
const conectividade = await this.testarConectividade(
|
|
this.config.unm2000.host,
|
|
this.config.unm2000.port,
|
|
5000
|
|
);
|
|
|
|
if (!conectividade) {
|
|
this.logger.error(`Host ${this.config.unm2000.host}:${this.config.unm2000.port} não acessível`);
|
|
throw new Error(`Host ${this.config.unm2000.host}:${this.config.unm2000.port} não acessível`);
|
|
}
|
|
|
|
this.logger.debug('Conectividade básica OK, iniciando conexão TL1...');
|
|
|
|
return new Promise((resolve, reject) => {
|
|
this.tl1Connection = new net.Socket();
|
|
let authenticated = false;
|
|
let dataBuffer = '';
|
|
|
|
this.tl1Connection.setTimeout(this.config.unm2000.timeout);
|
|
|
|
// Handler para dados recebidos
|
|
this.tl1Connection.on('data', (data) => {
|
|
const receivedData = data.toString();
|
|
dataBuffer += receivedData;
|
|
this.logger.debug('TL1 dados recebidos:', {
|
|
raw: receivedData,
|
|
buffer: dataBuffer
|
|
});
|
|
|
|
// Verificar padrões de sucesso no login TL1 Fiberhome
|
|
if (!authenticated) {
|
|
if (dataBuffer.includes('COMPLD') ||
|
|
dataBuffer.includes('M COMPLD') ||
|
|
dataBuffer.includes('IP') ||
|
|
dataBuffer.includes('>')) {
|
|
authenticated = true;
|
|
this.logger.info('Login TL1 Fiberhome bem-sucedido');
|
|
resolve(true);
|
|
}
|
|
// Padrões de erro
|
|
else if (dataBuffer.includes('DENY') ||
|
|
dataBuffer.includes('FAIL') ||
|
|
dataBuffer.includes('ERROR')) {
|
|
this.logger.error('Login TL1 negado:', dataBuffer);
|
|
reject(new Error('Login TL1 negado pelo equipamento'));
|
|
}
|
|
}
|
|
});
|
|
|
|
this.tl1Connection.connect(this.config.unm2000.port, this.config.unm2000.host, () => {
|
|
this.logger.debug('Socket TL1 conectado, enviando login Fiberhome');
|
|
|
|
// Aguardar um pouco para receber prompt inicial
|
|
setTimeout(() => {
|
|
// Comando de login TL1 formato Fiberhome
|
|
const loginCommand = `LOGIN:::CTAG::UN=${this.config.unm2000.username},PWD=${this.config.unm2000.password};`;
|
|
this.logger.debug('Enviando comando login TL1 Fiberhome:', loginCommand);
|
|
this.tl1Connection.write(loginCommand + '\r\n');
|
|
}, 1000);
|
|
|
|
// Timeout para autenticação
|
|
setTimeout(() => {
|
|
if (!authenticated) {
|
|
this.logger.error('Timeout na autenticação TL1 - dados recebidos:', dataBuffer);
|
|
reject(new Error(`Timeout na autenticação TL1. Dados: ${dataBuffer.substring(0, 200)}`));
|
|
}
|
|
}, 12000);
|
|
});
|
|
|
|
this.tl1Connection.on('error', (error) => {
|
|
this.logger.error('Erro na conexão TL1', error);
|
|
reject(error);
|
|
});
|
|
|
|
this.tl1Connection.on('timeout', () => {
|
|
this.logger.error('Timeout na conexão TL1');
|
|
reject(new Error('Timeout na conexão TL1'));
|
|
});
|
|
|
|
this.tl1Connection.on('close', () => {
|
|
this.logger.debug('Conexão TL1 fechada');
|
|
this.tl1Connection = null;
|
|
});
|
|
});
|
|
|
|
} catch (error) {
|
|
this.logger.error('=== ERRO NA CONEXAO UNM2000 ===');
|
|
this.logger.error('Erro detalhado:', {
|
|
message: error.message,
|
|
code: error.code,
|
|
errno: error.errno,
|
|
syscall: error.syscall,
|
|
address: error.address,
|
|
port: error.port
|
|
});
|
|
this.logger.error('Erro ao conectar UNM2000', error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Método de teste para verificar comandos TL1 básicos
|
|
*/
|
|
async testarComandoTL1(comando) {
|
|
try {
|
|
this.logger.debug('=== TESTE TL1 ===');
|
|
this.logger.debug('Comando:', comando);
|
|
|
|
const conectado = await this.conectarUNM2000();
|
|
if (!conectado) {
|
|
throw new Error('Não foi possível conectar ao UNM2000');
|
|
}
|
|
|
|
const resultado = await this.executarComandoTL1(comando);
|
|
|
|
this.logger.debug('=== RESULTADO TESTE TL1 ===');
|
|
this.logger.debug('Success:', resultado.success);
|
|
this.logger.debug('Error:', resultado.error);
|
|
this.logger.debug('Data length:', resultado.data ? resultado.data.length : 0);
|
|
this.logger.debug('Data:', resultado.data);
|
|
this.logger.debug('=== FIM TESTE TL1 ===');
|
|
|
|
return resultado;
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro no teste TL1:', error.message);
|
|
return { success: false, error: error.message };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Executar comando TL1
|
|
*/
|
|
async executarComandoTL1(comando) {
|
|
try {
|
|
this.logger.debug('Executando comando TL1', { comando });
|
|
|
|
if (!this.tl1Connection || this.tl1Connection.destroyed) {
|
|
const conectado = await this.conectarUNM2000();
|
|
if (!conectado) {
|
|
throw new Error('Não foi possível conectar ao UNM2000');
|
|
}
|
|
}
|
|
|
|
return new Promise((resolve, reject) => {
|
|
let dataBuffer = '';
|
|
let responseReceived = false;
|
|
|
|
// Handler temporário para esta consulta
|
|
const dataHandler = (data) => {
|
|
dataBuffer += data.toString();
|
|
this.logger.debug('Resposta TL1 acumulada:', {
|
|
comando,
|
|
responseLength: dataBuffer.length,
|
|
response: dataBuffer
|
|
});
|
|
|
|
// Verificar se a resposta está completa (formato Fiberhome)
|
|
//
|
|
// IMPORTANTE: respostas com muitas ONUs são enviadas em múltiplos blocos:
|
|
// bloco 1 → termina com ">" (mais dados a seguir), contém COMPLD
|
|
// bloco 2 → termina com ";" (fim real), também contém COMPLD
|
|
// Resolver apenas no ";" garante que todos os blocos foram recebidos.
|
|
// Resolver no primeiro COMPLD causaria leitura prematura, deixando os
|
|
// blocos restantes no buffer TCP para a próxima chamada consumir por engano.
|
|
const terminadorFinal = dataBuffer.includes(';');
|
|
const deny = dataBuffer.includes('DENY');
|
|
|
|
if ((terminadorFinal || deny) && !responseReceived) {
|
|
responseReceived = true;
|
|
this.tl1Connection.removeListener('data', dataHandler);
|
|
|
|
if (deny) {
|
|
this.logger.warn('Comando TL1 negado pelo equipamento');
|
|
resolve({
|
|
success: false,
|
|
error: 'Comando negado pelo equipamento',
|
|
data: dataBuffer
|
|
});
|
|
} else {
|
|
this.logger.debug('Comando TL1 executado com sucesso');
|
|
resolve({
|
|
success: true,
|
|
data: dataBuffer
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
this.tl1Connection.on('data', dataHandler);
|
|
|
|
// Enviar comando TL1 formato Fiberhome
|
|
this.tl1Connection.write(comando + '\r\n');
|
|
|
|
// Timeout para resposta (aumentado para suportar respostas multi-bloco)
|
|
setTimeout(() => {
|
|
if (!responseReceived) {
|
|
this.tl1Connection.removeListener('data', dataHandler);
|
|
reject(new Error('Timeout aguardando resposta TL1'));
|
|
}
|
|
}, 30000);
|
|
});
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro ao executar comando TL1', { comando, error: error.message });
|
|
return {
|
|
success: false,
|
|
error: error.message
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter status de ONU via TL1
|
|
*/
|
|
async obterStatusONU(serial) {
|
|
try {
|
|
this.logger.debug('Obtendo status da ONU via TL1', { serial });
|
|
|
|
// Conectar ao UNM2000
|
|
const conectado = await this.conectarUNM2000();
|
|
if (!conectado) {
|
|
throw new Error('Não foi possível conectar ao UNM2000');
|
|
}
|
|
|
|
const oltId = this.config.unm2000.defaultOLT; // Nome correto da OLT
|
|
|
|
// BUSCA AUTOMATIZADA: Estrutura correta da OLT
|
|
this.logger.debug('=== INICIANDO BUSCA AUTOMATIZADA POR SLOT/PON ===');
|
|
this.logger.debug(`OLTID: ${oltId}, Serial: ${serial}`);
|
|
|
|
// Definir estrutura correta da OLT
|
|
const estruturaOLT = [
|
|
// Slots 12, 13, 14 com 16 PONs cada
|
|
{ slot: 12, maxPons: 16 },
|
|
{ slot: 13, maxPons: 16 },
|
|
{ slot: 14, maxPons: 16 },
|
|
// Slots 15, 16 com 8 PONs cada
|
|
{ slot: 15, maxPons: 8 },
|
|
{ slot: 16, maxPons: 8 }
|
|
];
|
|
|
|
// Gerar lista de PONs para busca
|
|
const listaPons = [];
|
|
estruturaOLT.forEach(estrutura => {
|
|
for (let pon = 1; pon <= estrutura.maxPons; pon++) {
|
|
listaPons.push(`1-1-${estrutura.slot}-${pon}`);
|
|
}
|
|
});
|
|
|
|
this.logger.info(`📡 Iniciando busca em ${listaPons.length} PONs: slots 12-16`);
|
|
|
|
let onuEncontrada = null;
|
|
let ponEncontrada = null;
|
|
|
|
// Buscar em cada PON até encontrar a ONU
|
|
for (const ponid of listaPons) {
|
|
try {
|
|
this.logger.debug(`🔍 Verificando PON ${ponid}...`);
|
|
|
|
// Listar ONUs desta PON específica
|
|
const comandoListarPon = `LST-ONU::OLTID=${oltId},PONID=${ponid}:CTAG::;`;
|
|
const resultadoPon = await this.executarComandoTL1(comandoListarPon);
|
|
|
|
if (resultadoPon.success) {
|
|
// Verificar se a ONU está nesta PON (busca case-insensitive)
|
|
const serialUpperCase = serial.toUpperCase();
|
|
const dataUpperCase = resultadoPon.data ? resultadoPon.data.toUpperCase() : '';
|
|
|
|
if (resultadoPon.data && dataUpperCase.includes(serialUpperCase)) {
|
|
// Extrair informações da ONU na listagem primeiro
|
|
const posicaoONU = this.extrairPosicaoONU(resultadoPon.data, serial);
|
|
|
|
if (posicaoONU) {
|
|
// Usar o PONID correto extraído dos dados reais
|
|
this.logger.info(`🎯 ONU ${serial} ENCONTRADA NA PON ${posicaoONU.ponid}!`);
|
|
|
|
// Log dos dados da PON para debug
|
|
this.logger.debug('Dados da PON onde ONU foi encontrada:', {
|
|
ponidBuscada: ponid,
|
|
ponidReal: posicaoONU.ponid,
|
|
dataLength: resultadoPon.data.length,
|
|
preview: resultadoPon.data.substring(0, 1000)
|
|
});
|
|
|
|
onuEncontrada = {
|
|
posicao: posicaoONU,
|
|
listagem: resultadoPon.data,
|
|
ponid: posicaoONU.ponid // Usar o PONID correto extraído dos dados
|
|
};
|
|
ponEncontrada = posicaoONU.ponid; // Usar o PONID correto extraído dos dados
|
|
|
|
// Extrair slot e PON do ponid encontrado
|
|
const [, , slot, pon] = posicaoONU.ponid.split('-');
|
|
this.logger.info(`📍 Localização: Slot ${slot}, PON ${pon}`);
|
|
|
|
break; // Encontrou, pode parar a busca
|
|
}
|
|
}
|
|
} else {
|
|
this.logger.debug(`❌ Erro na PON ${ponid}: ${resultadoPon.error}`);
|
|
}
|
|
|
|
} catch (error) {
|
|
this.logger.debug(`⚠️ Erro ao verificar PON ${ponid}: ${error.message}`);
|
|
// Continuar tentando outras PONs
|
|
}
|
|
}
|
|
|
|
if (!onuEncontrada) {
|
|
throw new Error(`ONU ${serial} não encontrada em nenhuma PON da OLT ${oltId}. Verifique se o serial está correto e se a ONU está cadastrada.`);
|
|
}
|
|
|
|
// Agora buscar informações detalhadas da ONU
|
|
this.logger.info(`📊 Coletando informações detalhadas da ONU na PON ${ponEncontrada}...`);
|
|
|
|
// Extrair número da ONU da posição encontrada
|
|
const numeroONU = onuEncontrada.posicao.onuno;
|
|
|
|
// Usar comandos TL1 conforme manual (ONUIDTYPE=ONU_NUMBER,ONUID=numero)
|
|
const comandosDetalhados = [
|
|
// Estado da ONU (OperState: UP/DOWN/LOS)
|
|
`LST-ONUSTATE::OLTID=${oltId},PONID=${ponEncontrada},ONUIDTYPE=ONU_NUMBER,ONUID=${numeroONU}:CTAG::;`,
|
|
// DDM / potência ótica (RxPower, TxPower)
|
|
`LST-OMDDM::OLTID=${oltId},PONID=${ponEncontrada},ONUIDTYPE=ONU_NUMBER,ONUID=${numeroONU}:CTAG::;`,
|
|
// Porta LAN 1 (OperStatus, Speed, Duplex)
|
|
`LST-ONULANINFO::OLTID=${oltId},PONID=${ponEncontrada},ONUIDTYPE=ONU_NUMBER,ONUID=${numeroONU},ONUPORT=NA-NA-NA-1:CTAG::;`
|
|
];
|
|
|
|
this.logger.debug('Executando comandos TL1 detalhados (sequencial):', comandosDetalhados);
|
|
|
|
// IMPORTANTE: o socket TL1 é compartilhado — Promise.all faz os três handlers
|
|
// ouvirem o mesmo buffer e todos resolvem com o primeiro ';' recebido.
|
|
// Por isso os comandos DEVEM ser executados sequencialmente.
|
|
console.log('[TL1-DIAG] Iniciando sequência LST-ONUSTATE...');
|
|
const resultadoEstado = await this.executarComandoTL1(comandosDetalhados[0]);
|
|
console.log(`[TL1-DIAG] LST-ONUSTATE concluído: success=${resultadoEstado.success}`);
|
|
|
|
console.log('[TL1-DIAG] Iniciando sequência LST-OMDDM...');
|
|
const resultadoDDM = await this.executarComandoTL1(comandosDetalhados[1]);
|
|
console.log(`[TL1-DIAG] LST-OMDDM concluído: success=${resultadoDDM.success}`);
|
|
|
|
console.log('[TL1-DIAG] Iniciando sequência LST-ONULANINFO...');
|
|
const resultadoLAN = await this.executarComandoTL1(comandosDetalhados[2]);
|
|
console.log(`[TL1-DIAG] LST-ONULANINFO concluído: success=${resultadoLAN.success}`);
|
|
|
|
// Adicionar dados coletados
|
|
onuEncontrada.estadoGeral = resultadoEstado.success ? resultadoEstado.data : null;
|
|
onuEncontrada.detalhes = resultadoDDM.success ? resultadoDDM.data : null;
|
|
onuEncontrada.lan = resultadoLAN.success ? resultadoLAN.data : null;
|
|
|
|
// Adicionar respostas detalhadas para processamento
|
|
onuEncontrada.detalhados = [
|
|
{ comando: comandosDetalhados[0], response: resultadoEstado.data, success: resultadoEstado.success },
|
|
{ comando: comandosDetalhados[1], response: resultadoDDM.data, success: resultadoDDM.success },
|
|
{ comando: comandosDetalhados[2], response: resultadoLAN.data, success: resultadoLAN.success }
|
|
];
|
|
|
|
// Log dos resultados dos comandos detalhados
|
|
this.logger.debug('📊 Resultados dos comandos detalhados:', {
|
|
estado: { success: resultadoEstado.success, length: resultadoEstado.data?.length || 0 },
|
|
ddm: { success: resultadoDDM.success, length: resultadoDDM.data?.length || 0 },
|
|
lan: { success: resultadoLAN.success, length: resultadoLAN.data?.length || 0 }
|
|
});
|
|
|
|
// Tentar extrair mais informações dos dados da listagem original
|
|
this.logger.debug('📋 Processando dados da listagem original para extrair informações...');
|
|
onuEncontrada.dadosOriginais = onuEncontrada.listagem;
|
|
|
|
// Processar todos os dados coletados
|
|
const statusONU = this.processarStatusONUTL1(onuEncontrada, serial);
|
|
|
|
this.logger.info('🎉 Status final da ONU processado:', {
|
|
serial: statusONU.serial,
|
|
slot: statusONU.slot,
|
|
pon: statusONU.pon,
|
|
online: statusONU.online,
|
|
rxPower: statusONU.rxPower,
|
|
txPower: statusONU.txPower
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
...statusONU
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro ao obter status da ONU via TL1', { serial, error: error.message });
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
serial
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Obter status da ONU por device name via TL1
|
|
*/
|
|
async obterStatusONUPorNome(deviceName) {
|
|
try {
|
|
this.logger.debug('Obtendo status da ONU por nome via TL1', { deviceName });
|
|
|
|
// Conectar ao UNM2000
|
|
const conectado = await this.conectarUNM2000();
|
|
if (!conectado) {
|
|
throw new Error('Não foi possível conectar ao UNM2000');
|
|
}
|
|
|
|
// Comando TL1 para buscar ONU por nome
|
|
const comando = `RTRV-ONU::${deviceName}:1;`;
|
|
this.logger.debug('Executando comando TL1', { comando });
|
|
|
|
const resultado = await this.executarComandoTL1(comando);
|
|
|
|
if (!resultado.success) {
|
|
throw new Error(resultado.error || 'Falha na consulta TL1');
|
|
}
|
|
|
|
// Processar resposta TL1 da ONU
|
|
const statusONU = this.processarStatusONUTL1(resultado.data, deviceName);
|
|
|
|
this.logger.debug('Status ONU processado via TL1', statusONU);
|
|
|
|
return {
|
|
success: true,
|
|
...statusONU
|
|
};
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro ao obter status da ONU por nome via TL1', { deviceName, error: error.message });
|
|
return {
|
|
success: false,
|
|
error: error.message,
|
|
deviceName
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Extrair posição da ONU da lista de ONUs
|
|
*/
|
|
extrairPosicaoONU(dadosLista, serial) {
|
|
try {
|
|
if (!dadosLista || typeof dadosLista !== 'string') {
|
|
return null;
|
|
}
|
|
|
|
this.logger.debug('Extraindo posição da ONU da resposta TL1:', {
|
|
serial,
|
|
serialUpperCase: serial.toUpperCase(),
|
|
responseLength: dadosLista.length,
|
|
preview: dadosLista.substring(0, 500)
|
|
});
|
|
|
|
// Dividir resposta em linhas para processamento
|
|
const linhas = dadosLista.split('\n');
|
|
|
|
this.logger.debug('Total de linhas para processar:', linhas.length);
|
|
|
|
for (const linha of linhas) {
|
|
// Pular linhas de cabeçalho e vazias
|
|
if (linha.includes('OLTID') || linha.includes('----') || linha.trim() === '') {
|
|
continue;
|
|
}
|
|
|
|
// Log de cada linha processada (apenas as primeiras 3 para não poluir)
|
|
if (linhas.indexOf(linha) < 3) {
|
|
this.logger.debug('Processando linha:', linha.substring(0, 200));
|
|
}
|
|
|
|
// Procurar linha que contém o serial na coluna MAC (busca case-insensitive)
|
|
const serialUpperCase = serial.toUpperCase();
|
|
const linhaUpperCase = linha.toUpperCase();
|
|
|
|
if (linhaUpperCase.includes(serialUpperCase)) {
|
|
this.logger.debug('Serial encontrado na linha:', linha);
|
|
|
|
// Formato esperado baseado nos logs:
|
|
// OLTID PONID ONUNO NAME DESC ONUTYPE IP AUTHTYPE MAC LOID PWD SWVER
|
|
// 172.16.10.6 1-1-14-2 68 artenizamendes@clicknetguarai.com.br -- AN5506-02-B -- MAC FHTT92704618 -- -- RP2604
|
|
|
|
// Dividir por tabs
|
|
const campos = linha.split('\t');
|
|
this.logger.debug('Campos extraídos por TAB:', campos);
|
|
|
|
if (campos.length >= 9) {
|
|
const oltid = campos[0];
|
|
const ponid = campos[1];
|
|
const onuno = campos[2];
|
|
const name = campos[3];
|
|
const mac = campos[8]; // Posição da coluna MAC
|
|
|
|
// Verificar se o MAC corresponde ao serial procurado (case-insensitive)
|
|
if (mac && mac.toUpperCase().includes(serialUpperCase)) {
|
|
this.logger.debug('PONID encontrado:', ponid);
|
|
return {
|
|
ponid: ponid,
|
|
encontrado: true,
|
|
oltid: oltid,
|
|
onuno: onuno,
|
|
name: name,
|
|
mac: mac,
|
|
linhaOriginal: linha
|
|
};
|
|
}
|
|
}
|
|
|
|
// Fallback: se não conseguiu dividir por TAB, tentar por espaços
|
|
const camposEspaco = linha.trim().split(/\s+/);
|
|
this.logger.debug('Campos extraídos por espaço:', camposEspaco);
|
|
|
|
// Procurar o campo PONID (formato x-x-x-x) e verificar se serial está na linha
|
|
for (let i = 0; i < camposEspaco.length; i++) {
|
|
const campo = camposEspaco[i];
|
|
if (campo.match(/^\d+-\d+-\d+-\d+$/) && linhaUpperCase.includes(serialUpperCase)) {
|
|
this.logger.debug('PONID encontrado por regex:', campo);
|
|
return {
|
|
ponid: campo,
|
|
encontrado: true,
|
|
linhaOriginal: linha
|
|
};
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
this.logger.warn('ONU não encontrada na listagem TL1', { serial });
|
|
return null;
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro ao extrair posição da ONU', { error: error.message, serial });
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Processar resposta TL1 para status de ONU
|
|
*/
|
|
processarStatusONUTL1(dadosONU, identificador) {
|
|
try {
|
|
const resultado = {
|
|
serial: identificador,
|
|
deviceName: identificador,
|
|
slot: 'N/A',
|
|
pon: 'N/A',
|
|
onuId: 'N/A',
|
|
online: false,
|
|
rxPower: 'N/A',
|
|
txPower: 'N/A',
|
|
distance: 'N/A',
|
|
portasEthernet: [],
|
|
servicosWAN: []
|
|
};
|
|
|
|
if (!dadosONU || typeof dadosONU !== 'object') {
|
|
return resultado;
|
|
}
|
|
|
|
this.logger.debug('Processando dados ONU TL1:', dadosONU);
|
|
|
|
// Se encontramos a posição, a ONU existe
|
|
if (dadosONU.posicao && dadosONU.posicao.encontrado) {
|
|
resultado.online = true;
|
|
|
|
// Extrair informações da posição (formato NA-NA-slot-pon ou NA-shelf-slot-pon)
|
|
const ponid = dadosONU.posicao.ponid;
|
|
const partesPonid = ponid.split('-');
|
|
|
|
this.logger.debug('Analisando PONID:', { ponid, partes: partesPonid });
|
|
|
|
if (partesPonid.length >= 4) {
|
|
// Formato NA-NA-slot-pon ou NA-shelf-slot-pon
|
|
resultado.slot = partesPonid[2];
|
|
resultado.pon = partesPonid[3];
|
|
// Incluir o número da ONU no ID (slot-pon-numeroONU)
|
|
const numeroONU = dadosONU.posicao.onuno || '?';
|
|
resultado.onuId = `${partesPonid[2]}-${partesPonid[3]}-${numeroONU}`;
|
|
}
|
|
|
|
// Processar dados específicos dos comandos TL1 detalhados
|
|
if (dadosONU.detalhados) {
|
|
this.logger.debug('Processando respostas dos comandos TL1 detalhados');
|
|
|
|
for (const resposta of dadosONU.detalhados) {
|
|
// ── DIAGNÓSTICO TL1 (sempre visível no console) ──────────────────
|
|
const _cmdLabel = resposta?.comando?.split('::')[0] || '???';
|
|
console.log(`\n[TL1-DIAG] ════ ${_cmdLabel} ════`);
|
|
console.log(`[TL1-DIAG] success: ${resposta?.success}`);
|
|
if (resposta?.response) {
|
|
// Log da resposta completa, linha por linha, para facilitar análise
|
|
console.log('[TL1-DIAG] resposta completa:');
|
|
resposta.response.replace(/\r/g, '').split('\n').forEach((l, i) => {
|
|
console.log(`[TL1-DIAG] L${String(i).padStart(2,'0')}: ${JSON.stringify(l)}`);
|
|
});
|
|
} else {
|
|
console.log('[TL1-DIAG] (sem resposta/data)');
|
|
}
|
|
// ─────────────────────────────────────────────────────────────────
|
|
|
|
// Pular respostas com DENY ou sem dados
|
|
if (!resposta || !resposta.response || resposta.success === false) {
|
|
console.log(`[TL1-DIAG] ⚠️ PULANDO: success=false (DENY ou erro) para ${_cmdLabel}`);
|
|
continue;
|
|
}
|
|
|
|
const dados = resposta.response;
|
|
|
|
// Processar LST-ONUSTATE (estado operacional)
|
|
if (resposta.comando && resposta.comando.includes('LST-ONUSTATE')) {
|
|
// Verificar estado online/offline — OperState values: UP, DOWN, Power-Off, LOS
|
|
if (/\bUP\b/.test(dados)) {
|
|
resultado.online = true;
|
|
} else if (/\b(DOWN|Power-Off|LOS)\b/i.test(dados)) {
|
|
resultado.online = false;
|
|
}
|
|
}
|
|
|
|
// Processar LST-OMDDM (potência ótica)
|
|
// Resposta: ONUID RxPower RxPowerR TxPower TxPowerR CurrTxBias ...
|
|
if (resposta.comando && resposta.comando.includes('LST-OMDDM')) {
|
|
// Normalizar CRLF e dividir em linhas
|
|
const linhas = dados.replace(/\r/g, '').split('\n');
|
|
|
|
// Encontrar header: linha que contém 'ONUID' e 'RxPower' ou 'TxPower'
|
|
const headerIndex = linhas.findIndex(l =>
|
|
/\bONUID\b/i.test(l) && (/\bRxPower\b/i.test(l) || /\bTxPower\b/i.test(l))
|
|
);
|
|
console.log(`[TL1-DIAG] OMDDM headerIndex=${headerIndex}, totalLinhas=${linhas.length}`);
|
|
|
|
if (headerIndex !== -1) {
|
|
// Encontrar linha de dados (após header — tem pelo menos um número decimal)
|
|
const linhaDados = linhas.slice(headerIndex + 1).find(l =>
|
|
l.trim() &&
|
|
!/^[-\s]+$/.test(l) &&
|
|
!/COMPLD|total_blocks|block_number|FH_/i.test(l) &&
|
|
/[+-]?\d+[,.]\d+/.test(l)
|
|
);
|
|
console.log(`[TL1-DIAG] OMDDM linhaDados: ${JSON.stringify(linhaDados)}`);
|
|
|
|
if (linhaDados) {
|
|
const headers = linhas[headerIndex].trim().split(/\s+/);
|
|
const valores = linhaDados.trim().split(/\s+/);
|
|
console.log(`[TL1-DIAG] OMDDM headers(${headers.length}): [${headers.join('][')}]`);
|
|
console.log(`[TL1-DIAG] OMDDM valores(${valores.length}): [${valores.join('][')}]`);
|
|
|
|
const colIdx = (name) => headers.findIndex(h => h.toLowerCase() === name.toLowerCase());
|
|
const rxIdx = colIdx('RxPower');
|
|
const txIdx = colIdx('TxPower');
|
|
console.log(`[TL1-DIAG] OMDDM rxIdx=${rxIdx} txIdx=${txIdx}`);
|
|
|
|
// LST-OMDDM reporta valores na perspectiva da ONU:
|
|
// RxPower = sinal que a ONU recebe (downlink, da OLT para a ONU)
|
|
// TxPower = sinal que a ONU transmite (uplink, da ONU para a OLT)
|
|
// Posições fixas de fallback: ONUID=0, RxPower=1, TxPower=3
|
|
const rxRaw = (rxIdx >= 0 ? valores[rxIdx] : valores[1])?.replace(',', '.');
|
|
const txRaw = (txIdx >= 0 ? valores[txIdx] : valores[3])?.replace(',', '.');
|
|
console.log(`[TL1-DIAG] OMDDM rxRaw=${rxRaw} txRaw=${txRaw}`);
|
|
|
|
const COMP = 2.7; // compensação de calibração
|
|
|
|
if (rxRaw && rxRaw !== '--' && !isNaN(parseFloat(rxRaw))) {
|
|
resultado.rxPower = `${(parseFloat(rxRaw) + COMP).toFixed(2)} dBm`;
|
|
console.log(`[TL1-DIAG] ✅ RxPower ONU: ${rxRaw} → ${resultado.rxPower}`);
|
|
}
|
|
if (txRaw && txRaw !== '--' && !isNaN(parseFloat(txRaw))) {
|
|
resultado.txPower = `${(parseFloat(txRaw) + COMP).toFixed(2)} dBm`;
|
|
console.log(`[TL1-DIAG] ✅ TxPower ONU: ${txRaw} → ${resultado.txPower}`);
|
|
}
|
|
} else {
|
|
console.log('[TL1-DIAG] ⚠️ OMDDM: header encontrado mas nenhuma linha com número decimal após ele');
|
|
linhas.slice(headerIndex, headerIndex + 6).forEach((l, i) =>
|
|
console.log(`[TL1-DIAG] pós-header L${i}: ${JSON.stringify(l)}`)
|
|
);
|
|
}
|
|
} else {
|
|
// Header não encontrado — regex direto no buffer como último recurso
|
|
console.log('[TL1-DIAG] ⚠️ OMDDM: header ONUID+RxPower não encontrado, tentando regex');
|
|
const rxDireto = dados.match(/(-\d+\.\d+)/);
|
|
if (rxDireto) {
|
|
const v = parseFloat(rxDireto[1]) + 2.7;
|
|
resultado.rxPower = `${v.toFixed(2)} dBm`;
|
|
console.log(`[TL1-DIAG] RxPower ONU via regex último recurso → ${resultado.rxPower}`);
|
|
} else {
|
|
console.log('[TL1-DIAG] ⚠️ OMDDM: regex também falhou, rxPower permanece N/A');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Processar LST-ONULANINFO (porta ethernet)
|
|
// Resposta: AdminStatus OperStatus DUPLEX PVID VLANPRIORITY SPEED
|
|
if (resposta.comando && resposta.comando.includes('LST-ONULANINFO')) {
|
|
const linhasLan = dados.replace(/\r/g, '').split('\n');
|
|
|
|
// Localizar header: contém AdminStatus ou OperStatus
|
|
const headerLanIdx = linhasLan.findIndex(l =>
|
|
/AdminStatus/i.test(l) || /OperStatus/i.test(l)
|
|
);
|
|
console.log(`[TL1-DIAG] LAN headerLanIdx=${headerLanIdx}, totalLinhas=${linhasLan.length}`);
|
|
|
|
if (headerLanIdx !== -1) {
|
|
const headers = linhasLan[headerLanIdx].trim().split(/\s+/);
|
|
// Linha de dados: próxima linha não-vazia que contenha UP ou DOWN
|
|
const linhaDados = linhasLan.slice(headerLanIdx + 1).find(l =>
|
|
l.trim() &&
|
|
!/^[-\s;]+$/.test(l) &&
|
|
!/COMPLD|total_blocks|block_number/i.test(l) &&
|
|
/\b(UP|DOWN|DISABLE|ENABLE)\b/i.test(l)
|
|
);
|
|
console.log(`[TL1-DIAG] LAN linhaDados: ${JSON.stringify(linhaDados)}`);
|
|
|
|
if (linhaDados) {
|
|
const valores = linhaDados.trim().split(/\s+/);
|
|
const hLower = headers.map(h => h.toLowerCase());
|
|
console.log(`[TL1-DIAG] LAN headers(${headers.length}): [${headers.join('][')}]`);
|
|
console.log(`[TL1-DIAG] LAN valores(${valores.length}): [${valores.join('][')}]`);
|
|
|
|
const idxOper = hLower.findIndex(h => h === 'operstatus');
|
|
const idxSpeed = hLower.findIndex(h => h === 'speed');
|
|
const idxDuplex= hLower.findIndex(h => h === 'duplex');
|
|
console.log(`[TL1-DIAG] LAN idxOper=${idxOper} idxSpeed=${idxSpeed} idxDuplex=${idxDuplex}`);
|
|
|
|
// Fallback posições: AdminStatus=0, OperStatus=1, Duplex=2, PVID=3, VLANPRI=4, Speed=5
|
|
const operVal = idxOper >= 0 ? valores[idxOper] : valores[1];
|
|
const speedVal = idxSpeed >= 0 ? valores[idxSpeed] : valores[5];
|
|
const duplexVal= idxDuplex>= 0 ? valores[idxDuplex]: valores[2];
|
|
console.log(`[TL1-DIAG] LAN operVal=${operVal} speedVal=${speedVal} duplexVal=${duplexVal}`);
|
|
|
|
const isUp = /^up$/i.test(operVal);
|
|
const speedClean = speedVal && !/^(--|auto)$/i.test(speedVal) ? speedVal : null;
|
|
const duplexClean = /full/i.test(duplexVal) ? 'Full-Duplex'
|
|
: /half/i.test(duplexVal) ? 'Half-Duplex' : null;
|
|
const speedLabel = speedClean
|
|
? (duplexClean ? `${speedClean} ${duplexClean}` : speedClean)
|
|
: null;
|
|
|
|
resultado.portasEthernet[0] = {
|
|
porta: 1,
|
|
status: isUp ? 'active' : 'inactive',
|
|
speed: speedLabel
|
|
};
|
|
console.log(`[TL1-DIAG] ✅ Porta LAN 1: ${isUp ? 'UP' : 'DOWN'} speed=${speedLabel}`);
|
|
} else {
|
|
console.log('[TL1-DIAG] ⚠️ LAN: header encontrado mas nenhuma linha com UP/DOWN após ele');
|
|
linhasLan.slice(headerLanIdx, headerLanIdx + 6).forEach((l, i) =>
|
|
console.log(`[TL1-DIAG] pós-header L${i}: ${JSON.stringify(l)}`)
|
|
);
|
|
}
|
|
} else {
|
|
// Fallback: detectar UP/DOWN em qualquer lugar da resposta
|
|
console.log('[TL1-DIAG] ⚠️ LAN: header AdminStatus/OperStatus não encontrado, usando fallback');
|
|
const upMatch = /\bUP\b/.test(dados);
|
|
const downMatch = /\bDOWN\b/i.test(dados);
|
|
console.log(`[TL1-DIAG] LAN fallback: upMatch=${upMatch} downMatch=${downMatch}`);
|
|
if (upMatch || downMatch) {
|
|
const speedMatch = dados.match(/\b(\d+[MG])\b/i);
|
|
resultado.portasEthernet[0] = {
|
|
porta: 1,
|
|
status: upMatch ? 'active' : 'inactive',
|
|
speed: speedMatch ? speedMatch[1] : null
|
|
};
|
|
console.log(`[TL1-DIAG] LAN via fallback: ${upMatch ? 'UP' : 'DOWN'} speed=${speedMatch?.[1]}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tentar extrair distância dos dados da listagem (onde já funcionou)
|
|
if (dadosONU.listagem || dadosONU.dadosOriginais) {
|
|
const dadosCompletos = dadosONU.listagem || dadosONU.dadosOriginais;
|
|
|
|
// Análise específica do formato TL1 tabular
|
|
const linhasTabela = dadosCompletos.split('\r\n').filter(linha =>
|
|
linha.includes('\t') &&
|
|
linha.includes(dadosONU.posicao.mac.toLowerCase()) &&
|
|
!linha.includes('OLTID\tPONID')
|
|
);
|
|
|
|
if (linhasTabela.length > 0) {
|
|
const campos = linhasTabela[0].split('\t');
|
|
// Último campo geralmente contém código de ranging (ex: RP2610)
|
|
const ultimoCampo = campos[campos.length - 1];
|
|
|
|
// Extrair número do código RP (Ranging Profile)
|
|
const rpMatch = ultimoCampo.match(/RP(\d{2})(\d{2})/);
|
|
if (rpMatch) {
|
|
// RP2610 = distância baseada no perfil
|
|
// Converter baseado em padrões típicos dos equipamentos Fiberhome
|
|
const codigoDistancia = parseInt(rpMatch[2]);
|
|
if (codigoDistancia > 0) {
|
|
// Estimativa baseada no código (cada unidade pode representar ~100m)
|
|
const distanciaEstimada = codigoDistancia * 10;
|
|
resultado.distance = `${distanciaEstimada} m`;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Procurar por dados de distância explícitos na listagem
|
|
const distanciaMatches = [
|
|
dadosCompletos.match(/DISTANCE[=\s:]+(\d+)/gi),
|
|
dadosCompletos.match(/RANGING[=\s:]+(\d+)/gi),
|
|
dadosCompletos.match(/RANGE[=\s:]+(\d+)/gi),
|
|
// Buscar por padrões mais específicos de distância com metros (mínimo 3 dígitos)
|
|
dadosCompletos.match(/\b(\d{3,4})\s*m(?:etros?)?\b/gi)
|
|
];
|
|
|
|
for (const matches of distanciaMatches) {
|
|
if (matches && matches.length > 0) {
|
|
// Pegar o primeiro match válido
|
|
const match = matches[0].match(/(\d+)/);
|
|
if (match) {
|
|
resultado.distance = `${match[1]} m`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Extrair informações de potência se disponível na listagem
|
|
const rxMatches = [
|
|
dadosCompletos.match(/RX[=\s:]+([+-]?\d+\.?\d*)/gi),
|
|
dadosCompletos.match(/RXPOWER[=\s:]+([+-]?\d+\.?\d*)/gi)
|
|
];
|
|
|
|
for (const matches of rxMatches) {
|
|
if (matches && matches.length > 0) {
|
|
const match = matches[0].match(/([+-]?\d+\.?\d*)/);
|
|
if (match) {
|
|
resultado.rxPower = `${match[1]} dBm`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const txMatches = [
|
|
dadosCompletos.match(/TX[=\s:]+([+-]?\d+\.?\d*)/gi),
|
|
dadosCompletos.match(/TXPOWER[=\s:]+([+-]?\d+\.?\d*)/gi)
|
|
];
|
|
|
|
for (const matches of txMatches) {
|
|
if (matches && matches.length > 0) {
|
|
const match = matches[0].match(/([+-]?\d+\.?\d*)/);
|
|
if (match) {
|
|
resultado.txPower = `${match[1]} dBm`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Processar dados detalhados se disponível
|
|
if (dadosONU.detalhes) {
|
|
this.logger.debug('Processando dados detalhados da ONU:', dadosONU.detalhes);
|
|
|
|
// Extrair RX Power
|
|
const rxMatches = [
|
|
dadosONU.detalhes.match(/RXPOWER[=\s:]+([+-]?\d+\.?\d*)/i),
|
|
dadosONU.detalhes.match(/RX[=\s:]+([+-]?\d+\.?\d*)/i)
|
|
];
|
|
|
|
for (const match of rxMatches) {
|
|
if (match) {
|
|
resultado.rxPower = `${match[1]} dBm`;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Extrair TX Power
|
|
const txMatches = [
|
|
dadosONU.detalhes.match(/TXPOWER[=\s:]+([+-]?\d+\.?\d*)/i),
|
|
dadosONU.detalhes.match(/TX[=\s:]+([+-]?\d+\.?\d*)/i)
|
|
];
|
|
|
|
for (const match of txMatches) {
|
|
if (match) {
|
|
resultado.txPower = `${match[1]} dBm`;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Extrair distância
|
|
const distMatches = [
|
|
dadosONU.detalhes.match(/DISTANCE[=\s:]+(\d+)/i),
|
|
dadosONU.detalhes.match(/RANGINGTIME[=\s:]+(\d+)/i)
|
|
];
|
|
|
|
for (const match of distMatches) {
|
|
if (match) {
|
|
resultado.distance = `${match[1]} m`;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Processar dados de estado geral se disponível
|
|
if (dadosONU.estadoGeral) {
|
|
this.logger.debug('Processando dados de estado geral:', dadosONU.estadoGeral);
|
|
|
|
// Verificar se está online/offline
|
|
if (dadosONU.estadoGeral.includes('ONLINE') ||
|
|
dadosONU.estadoGeral.includes('ACTIVE') ||
|
|
dadosONU.estadoGeral.includes('UP')) {
|
|
resultado.online = true;
|
|
} else if (dadosONU.estadoGeral.includes('OFFLINE') ||
|
|
dadosONU.estadoGeral.includes('INACTIVE') ||
|
|
dadosONU.estadoGeral.includes('DOWN')) {
|
|
resultado.online = false;
|
|
}
|
|
}
|
|
|
|
// Se não temos dados específicos das portas, usar padrão
|
|
if (resultado.portasEthernet.length === 0) {
|
|
resultado.portasEthernet = [
|
|
{ porta: 1, status: 'unknown' },
|
|
{ porta: 2, status: 'unknown' },
|
|
{ porta: 3, status: 'unknown' },
|
|
{ porta: 4, status: 'unknown' }
|
|
];
|
|
}
|
|
|
|
// Se não temos dados WAN específicos, mas a ONU está online
|
|
if (resultado.servicosWAN.length === 0 && resultado.online) {
|
|
resultado.servicosWAN.push({
|
|
id: 1,
|
|
status: 'active',
|
|
ip: 'Configurado'
|
|
});
|
|
}
|
|
}
|
|
|
|
this.logger.debug('Dados TL1 processados:', resultado);
|
|
return resultado;
|
|
|
|
} catch (error) {
|
|
this.logger.error('Erro ao processar dados TL1', { error: error.message });
|
|
return {
|
|
serial: identificador,
|
|
deviceName: identificador,
|
|
slot: 'N/A',
|
|
pon: 'N/A',
|
|
onuId: 'N/A',
|
|
online: false,
|
|
rxPower: 'N/A',
|
|
txPower: 'N/A',
|
|
distance: 'N/A',
|
|
portasEthernet: [],
|
|
servicosWAN: []
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Desconectar TL1
|
|
*/
|
|
async desconectar() {
|
|
try {
|
|
if (this.tl1Connection && !this.tl1Connection.destroyed) {
|
|
this.logger.debug('Desconectando TL1');
|
|
this.tl1Connection.end();
|
|
this.tl1Connection = null;
|
|
}
|
|
} catch (error) {
|
|
this.logger.error('Erro ao desconectar TL1', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Verificar se as conexões estão ativas
|
|
*/
|
|
verificarConexoes() {
|
|
return {
|
|
olt: false, // Não usamos mais OLT diretamente
|
|
unm2000: this.tl1Connection && !this.tl1Connection.destroyed
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Diagnóstico completo de conectividade
|
|
*/
|
|
async diagnosticarConectividade() {
|
|
const diagnostico = {
|
|
olt: {
|
|
host: 'N/A',
|
|
port: 'N/A',
|
|
conectividade: false,
|
|
telnet: false,
|
|
erro: 'Não utilizado - consultas via TL1'
|
|
},
|
|
unm2000: {
|
|
host: this.config.unm2000.host,
|
|
port: this.config.unm2000.port,
|
|
conectividade: false,
|
|
tl1: false,
|
|
erro: null
|
|
}
|
|
};
|
|
|
|
// Testar UNM2000
|
|
try {
|
|
this.logger.debug('Testando conectividade UNM2000...');
|
|
diagnostico.unm2000.conectividade = await this.testarConectividade(
|
|
this.config.unm2000.host,
|
|
this.config.unm2000.port
|
|
);
|
|
|
|
if (diagnostico.unm2000.conectividade) {
|
|
this.logger.debug('Testando login TL1 UNM2000...');
|
|
diagnostico.unm2000.tl1 = await this.conectarUNM2000();
|
|
}
|
|
} catch (error) {
|
|
diagnostico.unm2000.erro = error.message;
|
|
this.logger.error('Erro no diagnóstico UNM2000:', error);
|
|
}
|
|
|
|
return diagnostico;
|
|
}
|
|
}
|
|
|
|
module.exports = FiberhomeManager;
|