diagnosticar: fix porta ethernet; sinal; OLT Datacom

Signed-off-by: Rodrigo Bertoldo de Sousa Martins <rodrigo.sousa.577@gmail.com>
This commit is contained in:
rodrigo committed 2026-03-30 22:16:57 -03:00
1 parent 0dbab52aa1
commit 04076fd1b9
2 files changed
+190 -109

No files matched your search

+81 -35
View File
@@ -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
};
}
+109 -74
View File
@@ -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;
}
this.logger.debug('LST-OMDDM raw:', dados.substring(0, 500));
// Encontrar o índice da linha do header
const linhas = dados.split('\n');
const headerIndex = linhas.findIndex(l => l.includes('ONUID') && l.includes('RxPower'));
// Normalizar CRLF e dividir em linhas
const linhas = dados.replace(/\r/g, '').split('\n');
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
// 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 (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 (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('] [')}]`);
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('][')}]`);
// Formato: ONUID RxPower RxPowerR TxPower TxPowerR CurrTxBias CurrTxBiasR
// Temperature TemperatureR Voltage VoltageR PTxPower PRxPower
const colIdx = (name) => headers.findIndex(h => h.toLowerCase() === name.toLowerCase());
const rxIdx = colIdx('RxPower');
const txIdx = colIdx('TxPower');
// 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();
// 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(',', '.');
// 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
const COMP_RX = 2.7;
const COMP_TX = -2.7;
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 (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();
this.logger.debug('LST-ONULANINFO raw:', dados.substring(0, 400));
const linhasLan = dados.replace(/\r/g, '').split('\n');
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';
// 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 };
}
}
}