From 04076fd1b900fefb73ee2ef46c873c373562fe81 Mon Sep 17 00:00:00 2001 From: Rodrigo Bertoldo de Sousa Martins Date: Mon, 30 Mar 2026 22:06:13 -0300 Subject: [PATCH] diagnosticar: fix porta ethernet; sinal; OLT Datacom Signed-off-by: Rodrigo Bertoldo de Sousa Martins --- src/bot/handler.js | 116 +++++++++++++++------- src/fiberhome/manager.js | 201 +++++++++++++++++++++++---------------- 2 files changed, 199 insertions(+), 118 deletions(-) diff --git a/src/bot/handler.js b/src/bot/handler.js index 66c372a..2388718 100644 --- a/src/bot/handler.js +++ b/src/bot/handler.js @@ -1045,14 +1045,18 @@ Digite /help para ver todos os comandos disponíveis! const nomePlano = servico.descricao || servico.nome || 'N/A'; const endereco = servico.instalacao_endereco || servico.end_rua || null; const cidade = servico.instalacao_cidade || null; + // Campo base/AP usado para rotear para a OLT correta + const baseInfo = servico.base || (servico.id_ap ? `AP ${servico.id_ap}` : null); // Executar consultas em paralelo: Juniper + ONU + // No concentrador Juniper usa-se só o usuário (antes do @) + const pppoeUser = pppoeLogin ? pppoeLogin.split('@')[0] : null; const [juniperResult, onuResult] = await Promise.all([ - pppoeLogin - ? this.ssh.getJuniperSubscriberInfo('juniper', pppoeLogin).catch(e => ({ success: false, error: e.message })) + pppoeUser + ? this.ssh.getJuniperSubscriberInfo('juniper', pppoeUser).catch(e => ({ success: false, error: e.message })) : Promise.resolve({ success: false, skipped: true }), serial - ? this._diagnosticarONU(serial).catch(e => ({ success: false, error: e.message })) + ? this._diagnosticarONU(serial, baseInfo).catch(e => ({ success: false, error: e.message })) : Promise.resolve({ success: false, skipped: true }) ]); @@ -1106,7 +1110,7 @@ Digite /help para ver todos os comandos disponíveis! msg += '\n'; if (onuResult.portaEthernet) { - const ethOk = /forward|up|1gbit|100mbit/i.test(onuResult.portaEthernet); + const ethOk = /forward|up|active|1gbit|100mbit/i.test(onuResult.portaEthernet); msg += ` ${ethOk ? '🟢' : '🔴'} Porta Ethernet: ${onuResult.portaEthernet}\n`; } } @@ -1120,37 +1124,66 @@ Digite /help para ver todos os comandos disponíveis! } /** - * Diagnóstico de ONU: detecta tipo (Datacom/FiberHome) e retorna dados estruturados + * Mapeia o campo base/AP do TopSapp para o ID da OLT Datacom (ou 'fiberhome') */ - async _diagnosticarONU(serial) { - const serialUpper = serial.toUpperCase(); - - if (serialUpper.startsWith('DACM')) { - return await this._diagnosticarONUDatacom(serial); - } else { - return await this._diagnosticarONUFiberhome(serial); - } + _resolverOLTporBase(baseInfo) { + if (!baseInfo) return null; + const b = String(baseInfo).toUpperCase(); + if (/INTERNET\s+FIBRA\s+OPTICA/i.test(b)) return 'fiberhome'; + if (/COLMEIA/i.test(b)) return 'olt_colmeia'; + if (/DITO/i.test(b)) return 'olt_dito'; + if (/CANAA\s*2/i.test(b)) return 'olt_canaa2'; + if (/CANAA/i.test(b)) return 'olt_canaa'; + return null; } - async _diagnosticarONUDatacom(serial) { - const olts = this.ssh.listOLTs(); - if (olts.length === 0) { - return { success: false, error: 'Nenhuma OLT Datacom configurada' }; + /** + * Diagnóstico de ONU: detecta tipo (Datacom/FiberHome) e retorna dados estruturados + * @param {string} serial - Serial da ONU + * @param {string|null} baseInfo - Campo base/AP do TopSapp para rotear para OLT correta + */ + async _diagnosticarONU(serial, baseInfo) { + const serialUpper = serial.toUpperCase(); + const oltHint = this._resolverOLTporBase(baseInfo); + + // Usar base description para decidir se é FiberHome + if (oltHint === 'fiberhome' || + (!serialUpper.startsWith('DACM') && (serialUpper.startsWith('FHTT') || serialUpper.startsWith('FHGT')))) { + return await this._diagnosticarONUFiberhome(serial); } - // Buscar ONU em todas as OLTs em paralelo - const searches = olts.map(olt => - this.ssh.findOnuBySerial(olt.id, serial) - .then(r => ({ ...r, oltId: olt.id, oltName: olt.name })) - .catch(() => ({ success: false })) - ); - const results = await Promise.allSettled(searches); + return await this._diagnosticarONUDatacom(serial, oltHint); + } + async _diagnosticarONUDatacom(serial, oltIdHint) { let found = null; - for (const r of results) { - if (r.status === 'fulfilled' && r.value.success) { - found = r.value; - break; + + // 1. Tentar na OLT indicada pelo campo base/AP do cadastro + if (oltIdHint && this.ssh.equipamentos.has(oltIdHint)) { + const r = await this.ssh.findOnuBySerial(oltIdHint, serial).catch(() => null); + if (r && r.success) { + const olt = this.ssh.equipamentos.get(oltIdHint); + found = { ...r, oltId: oltIdHint, oltName: olt.name }; + } + } + + // 2. Fallback: buscar em todas as OLTs Datacom + if (!found) { + const olts = this.ssh.listOLTs(); + if (olts.length === 0) { + return { success: false, error: 'Nenhuma OLT Datacom configurada' }; + } + const searches = olts.map(olt => + this.ssh.findOnuBySerial(olt.id, serial) + .then(r => ({ ...r, oltId: olt.id, oltName: olt.name })) + .catch(() => ({ success: false })) + ); + const results = await Promise.allSettled(searches); + for (const r of results) { + if (r.status === 'fulfilled' && r.value.success) { + found = r.value; + break; + } } } @@ -1207,21 +1240,34 @@ Digite /help para ver todos os comandos disponíveis! return { success: false, error: resultado.error || 'ONU não encontrada na FiberHome' }; } - const portaPrincipal = resultado.portasEthernet && resultado.portasEthernet.length > 0 - ? resultado.portasEthernet[0] - : null; + // Preferir porta com dados reais de velocidade; cair para primeira disponível + const portas = resultado.portasEthernet || []; + const portaComDados = portas.find(p => p.status !== 'unknown' && p.speed); + const portaAtiva = portas.find(p => p.status === 'active'); + const portaBase = portaComDados || portaAtiva || (portas.length > 0 ? portas[0] : null); + + let portaEthernetStr = null; + if (portaBase && portaBase.status !== 'unknown') { + const statusLabel = portaBase.status === 'active' ? 'up' : 'down'; + portaEthernetStr = portaBase.speed + ? `${statusLabel} — ${portaBase.speed}` + : statusLabel; + } + + // rxPower: retornar null se for 'N/A' para que a UI mostre 'N/A' via fallback + const rxPower = resultado.rxPower && resultado.rxPower !== 'N/A' ? resultado.rxPower : null; return { success: true, type: 'fiberhome', - oltName: 'FiberHome', + oltName: 'FiberHome AN5516', slot: resultado.slot, pon: resultado.pon, onuId: resultado.onuId, online: resultado.online, - rxPower: resultado.rxPower, - txPower: resultado.txPower, - portaEthernet: portaPrincipal ? `${portaPrincipal.status}` : null + rxPower, + txPower: resultado.txPower !== 'N/A' ? resultado.txPower : null, + portaEthernet: portaEthernetStr }; } diff --git a/src/fiberhome/manager.js b/src/fiberhome/manager.js index 71074e8..3ebb4c7 100644 --- a/src/fiberhome/manager.js +++ b/src/fiberhome/manager.js @@ -383,14 +383,14 @@ class FiberhomeManager { // Extrair número da ONU da posição encontrada const numeroONU = onuEncontrada.posicao.onuno; - // Usar comandos TL1 que sabemos que funcionam baseados no manual + // Usar comandos TL1 conforme manual (ONUIDTYPE=ONU_NUMBER,ONUID=numero) const comandosDetalhados = [ - // Para informações de distância e estado (comando válido do manual) - `LST-ONUSTATE::OLTID=${oltId},PONID=${ponEncontrada},ONUNO=${numeroONU}:CTAG::;`, - // Para informações DDM/potência ótica (comando válido do manual) - `LST-OMDDM::OLTID=${oltId},PONID=${ponEncontrada},ONUNO=${numeroONU}:CTAG::;`, - // Para informações das portas LAN (comando válido do manual) - `LST-ONULANINFO::OLTID=${oltId},PONID=${ponEncontrada},ONUNO=${numeroONU}:CTAG::;` + // 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:', comandosDetalhados); @@ -644,97 +644,132 @@ class FiberhomeManager { const dados = resposta.response; - // Processar LST-ONUSTATE (distância e estado) + // Processar LST-ONUSTATE (estado operacional) if (resposta.comando && resposta.comando.includes('LST-ONUSTATE')) { - // Extrair distância - const distMatch = dados.match(/DISTANCE\s*=\s*(\d+)/i) || - dados.match(/RANGING\s*=\s*(\d+)/i); - if (distMatch) { - resultado.distance = `${distMatch[1]} m`; - } - - // Verificar estado online/offline - if (dados.includes('ONLINE') || dados.includes('ACTIVE')) { + // Verificar estado online/offline — OperState values: UP, DOWN, Power-Off, LOS + if (/\bUP\b/.test(dados)) { resultado.online = true; - } else if (dados.includes('OFFLINE') || dados.includes('INACTIVE')) { + } 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')) { - // Verificar se a resposta é válida (deve conter "Optical Power" ou campos de potência) - if (!dados.includes('RxPower') && !dados.includes('Optical Power')) { - this.logger.warn('⚠️ LST-OMDDM retornou formato inesperado, pulando...'); - continue; - } - - // Encontrar o índice da linha do header - const linhas = dados.split('\n'); - const headerIndex = linhas.findIndex(l => l.includes('ONUID') && l.includes('RxPower')); - - if (headerIndex === -1) { - this.logger.warn('⚠️ Header ONUID/RxPower não encontrado'); - continue; - } - - // Pegar as linhas APÓS o header que contenham dados válidos - const linhasDados = linhas.slice(headerIndex + 1).filter(linha => - linha.trim() && - !linha.includes('---') && - linha.match(/[+-]?\d+[,.]?\d+/) && // Deve ter pelo menos um número decimal - !linha.includes('FH_') && // Não é cabeçalho da resposta - !linha.includes('COMPLD') && // Não é status - !linha.includes('total_blocks') // Não é metadado + this.logger.debug('LST-OMDDM raw:', dados.substring(0, 500)); + + // Normalizar CRLF e dividir em linhas + const linhas = dados.replace(/\r/g, '').split('\n'); + + // Encontrar header: linha que contém 'ONUID' (pode não ter 'RxPower' se quebrou) + const headerIndex = linhas.findIndex(l => + /\bONUID\b/i.test(l) && (/\bRxPower\b/i.test(l) || /\bTxPower\b/i.test(l)) ); - - this.logger.debug(`📋 LST-OMDDM: ${linhasDados.length} linhas de dados encontradas`); - - if (linhasDados.length > 0) { - // Split por múltiplos espaços ou tabs - const campos = linhasDados[0].split(/\s+/).filter(c => c); - this.logger.debug(`📊 LST-OMDDM ${campos.length} campos: [${campos.join('] [')}]`); - - // Formato: ONUID RxPower RxPowerR TxPower TxPowerR CurrTxBias CurrTxBiasR - // Temperature TemperatureR Voltage VoltageR PTxPower PRxPower - - // RxPower = sinal que a OLT recebe da ONU (uplink) - campo 1 - const rxPower = campos[1]?.replace(',', '.').trim(); - // TxPower = potência transmitida pela OLT - campo 3 - const txPower = campos[3]?.replace(',', '.').trim(); - - // Compensação observada entre UNM2000 e valores brutos TL1 - const COMPENSACAO_RX = 2.7; // RX: +2.7 dBm - const COMPENSACAO_TX = -2.7; // TX: -2.7 dBm - - if (rxPower && rxPower !== '--' && !isNaN(parseFloat(rxPower))) { - const rxCompensado = (parseFloat(rxPower) + COMPENSACAO_RX).toFixed(2); - resultado.rxPower = `${rxCompensado} dBm`; - this.logger.info(`✅ RX Power: ${rxPower} dBm (bruto) → ${rxCompensado} dBm (compensado +${COMPENSACAO_RX})`); + + if (headerIndex !== -1) { + // Encontrar linha de dados (após o header, ignora traços e metadados) + 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) // linha de dados tem número decimal + ); + + if (linhaDados) { + // Parsear header e dados para mapear colunas por nome + const headers = linhas[headerIndex].trim().split(/\t|\s{2,}/).map(h => h.trim()); + const valores = linhaDados.trim().split(/\t|\s{2,}/).map(v => v.trim()); + this.logger.debug(`📊 LST-OMDDM headers: [${headers.join('][')}]`); + this.logger.debug(`📊 LST-OMDDM valores: [${valores.join('][')}]`); + + const colIdx = (name) => headers.findIndex(h => h.toLowerCase() === name.toLowerCase()); + const rxIdx = colIdx('RxPower'); + const txIdx = colIdx('TxPower'); + + // Fallback: se header parse falhou, usar posição fixa (ONUID=0, RxPower=1, TxPower=3) + const rxRaw = (rxIdx >= 0 ? valores[rxIdx] : valores[1])?.replace(',', '.'); + const txRaw = (txIdx >= 0 ? valores[txIdx] : valores[3])?.replace(',', '.'); + + const COMP_RX = 2.7; + const COMP_TX = -2.7; + + if (rxRaw && rxRaw !== '--' && !isNaN(parseFloat(rxRaw))) { + resultado.rxPower = `${(parseFloat(rxRaw) + COMP_RX).toFixed(2)} dBm`; + this.logger.info(`✅ RxPower bruto=${rxRaw} → ${resultado.rxPower}`); + } + if (txRaw && txRaw !== '--' && !isNaN(parseFloat(txRaw))) { + resultado.txPower = `${(parseFloat(txRaw) + COMP_TX).toFixed(2)} dBm`; + } + } else { + this.logger.warn('⚠️ LST-OMDDM: header encontrado mas sem linha de dados numéricos'); } - - if (txPower && txPower !== '--' && !isNaN(parseFloat(txPower))) { - const txCompensado = (parseFloat(txPower) + COMPENSACAO_TX).toFixed(2); - resultado.txPower = `${txCompensado} dBm`; - this.logger.info(`✅ TX Power: ${txPower} dBm (bruto) → ${txCompensado} dBm (compensado ${COMPENSACAO_TX})`); + } else { + // Header não encontrado — tentar regex direto no buffer + const rxMatch = dados.match(/RxPower[=\s:]+([+-]?\d+[,.]\d+)/i); + if (rxMatch) { + const v = parseFloat(rxMatch[1].replace(',', '.')) + 2.7; + resultado.rxPower = `${v.toFixed(2)} dBm`; + this.logger.info(`✅ RxPower via regex fallback → ${resultado.rxPower}`); + } else { + this.logger.warn('⚠️ LST-OMDDM: não foi possível extrair RxPower da resposta'); + this.logger.debug('LST-OMDDM resposta completa:', dados); } } } - // Processar LST-ONULANINFO (portas ethernet) + // Processar LST-ONULANINFO (porta ethernet) + // Resposta: AdminStatus OperStatus DUPLEX PVID VLANPRIORITY SPEED if (resposta.comando && resposta.comando.includes('LST-ONULANINFO')) { - // Extrair informações das portas - const portMatches = dados.matchAll(/PORT\s*=\s*(\d+).*?STATUS\s*=\s*(\w+)/gi); - for (const match of portMatches) { - const portNum = parseInt(match[1]); - const status = match[2].toLowerCase(); - - if (portNum >= 1 && portNum <= 4) { - if (!resultado.portasEthernet[portNum-1]) { - resultado.portasEthernet[portNum-1] = { porta: portNum, status: 'unknown' }; - } - resultado.portasEthernet[portNum-1].status = status === 'up' ? 'active' : - status === 'down' ? 'inactive' : 'unknown'; + this.logger.debug('LST-ONULANINFO raw:', dados.substring(0, 400)); + 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) + ); + + if (headerLanIdx !== -1) { + const headers = linhasLan[headerLanIdx].trim().split(/\t|\s{2,}/).map(h => h.trim()); + const linhaDados = linhasLan.slice(headerLanIdx + 1).find(l => + l.trim() && !/^[-\s]+$/.test(l) && + !/COMPLD|total_blocks|block_number/i.test(l) + ); + + if (linhaDados) { + const valores = linhaDados.trim().split(/\t|\s{2,}/).map(v => v.trim()); + const hLower = headers.map(h => h.toLowerCase()); + const idxAdm = hLower.findIndex(h => h === 'adminstatus'); + const idxOper = hLower.findIndex(h => h === 'operstatus'); + const idxSpeed = hLower.findIndex(h => h === 'speed'); + const idxDuplex= hLower.findIndex(h => h === 'duplex'); + + const operVal = idxOper >= 0 ? valores[idxOper] : valores[1]; + const speedVal = idxSpeed >= 0 ? valores[idxSpeed] : valores[5]; + const duplexVal= idxDuplex>= 0 ? valores[idxDuplex]: valores[2]; + + const isUp = /^up$/i.test(operVal); + const speedClean = speedVal && !/^(--|auto)$/i.test(speedVal) ? speedVal : null; + const duplexClean = duplexVal && !/^(--|auto|full|half)$/i.test(duplexVal) + ? null : (/full/i.test(duplexVal) ? 'Full-Duplex' : duplexVal && /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 + }; + this.logger.info(`✅ Porta LAN 1: ${isUp ? 'UP' : 'DOWN'} ${speedLabel || ''}`); + } + } else { + // Fallback: tentar extrair OperStatus via regex + const upMatch = dados.match(/OperStatus[\s\t]+UP/i) || + dados.match(/(?:^|\t)UP(?:\t|$)/m); + if (upMatch !== null) { + resultado.portasEthernet[0] = { porta: 1, status: 'active', speed: null }; } } }