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;