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:
1 parent
0dbab52aa1
commit
04076fd1b9
2 files changed
+190
-109
No files matched your search
+81
-35
@@ -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
@@ -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 };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user