fix TopSapp
This commit is contained in:
1 parent
8dc6c594c9
commit
bae6adefcb
42 files changed
+43645
-105
No files matched your search
+1024
-49
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,961 @@
|
||||
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)
|
||||
if ((dataBuffer.includes('COMPLD') ||
|
||||
dataBuffer.includes('M COMPLD') ||
|
||||
dataBuffer.includes('DENY') ||
|
||||
dataBuffer.includes(';')) && !responseReceived) {
|
||||
responseReceived = true;
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
|
||||
if (dataBuffer.includes('COMPLD') || dataBuffer.includes('M COMPLD')) {
|
||||
this.logger.debug('Comando TL1 executado com sucesso');
|
||||
resolve({
|
||||
success: true,
|
||||
data: dataBuffer
|
||||
});
|
||||
} else if (dataBuffer.includes('DENY')) {
|
||||
this.logger.warn('Comando TL1 negado pelo equipamento');
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Comando negado pelo equipamento',
|
||||
data: dataBuffer
|
||||
});
|
||||
} else {
|
||||
this.logger.debug('Resposta TL1 válida recebida');
|
||||
resolve({
|
||||
success: true,
|
||||
data: dataBuffer // Aceitar resposta válida
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.tl1Connection.on('data', dataHandler);
|
||||
|
||||
// Enviar comando TL1 formato Fiberhome
|
||||
this.tl1Connection.write(comando + '\r\n');
|
||||
|
||||
// Timeout para resposta
|
||||
setTimeout(() => {
|
||||
if (!responseReceived) {
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
reject(new Error('Timeout aguardando resposta TL1'));
|
||||
}
|
||||
}, 15000);
|
||||
});
|
||||
|
||||
} 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 que sabemos que funcionam baseados no manual
|
||||
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::;`
|
||||
];
|
||||
|
||||
this.logger.debug('Executando comandos TL1 detalhados:', comandosDetalhados);
|
||||
|
||||
const [resultadoEstado, resultadoDDM, resultadoLAN] = await Promise.all([
|
||||
this.executarComandoTL1(comandosDetalhados[0]),
|
||||
this.executarComandoTL1(comandosDetalhados[1]),
|
||||
this.executarComandoTL1(comandosDetalhados[2])
|
||||
]);
|
||||
|
||||
// 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) {
|
||||
if (!resposta || !resposta.response) continue;
|
||||
|
||||
const dados = resposta.response;
|
||||
|
||||
// Processar LST-ONUSTATE (distância e estado)
|
||||
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')) {
|
||||
resultado.online = true;
|
||||
} else if (dados.includes('OFFLINE') || dados.includes('INACTIVE')) {
|
||||
resultado.online = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Processar LST-OMDDM (potência ótica)
|
||||
if (resposta.comando && resposta.comando.includes('LST-OMDDM')) {
|
||||
// Buscar pela tabela de potência ótica no formato visto
|
||||
const linhasTabela = dados.split('\n').filter(linha =>
|
||||
linha.includes('\t') &&
|
||||
!linha.includes('ONUID\tRxPower') && // header
|
||||
linha.match(/--\t[+-]?\d+[,.]?\d*/) // linha com dados
|
||||
);
|
||||
|
||||
if (linhasTabela.length > 0) {
|
||||
// Formato: ONUID RxPower RxPowerR TxPower TxPowerR ...
|
||||
// Exemplo: -- -19,94 Normal 5,16 High 11,70 Normal ...
|
||||
const campos = linhasTabela[0].split('\t');
|
||||
if (campos.length >= 4) {
|
||||
const rxPower = campos[1]?.replace(',', '.');
|
||||
const txPower = campos[3]?.replace(',', '.');
|
||||
|
||||
if (rxPower && rxPower !== '--' && !isNaN(parseFloat(rxPower))) {
|
||||
resultado.rxPower = `${rxPower} dBm`;
|
||||
}
|
||||
if (txPower && txPower !== '--' && !isNaN(parseFloat(txPower))) {
|
||||
resultado.txPower = `${txPower} dBm`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processar LST-ONULANINFO (portas ethernet)
|
||||
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';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -0,0 +1,501 @@
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
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
|
||||
const conectividade = await this.testarConectividade(
|
||||
this.config.unm2000.host,
|
||||
this.config.unm2000.port,
|
||||
5000
|
||||
);
|
||||
|
||||
if (!conectividade) {
|
||||
throw new Error(`Host ${this.config.unm2000.host}:${this.config.unm2000.port} não acessível`);
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.tl1Connection = new net.Socket();
|
||||
let authenticated = false;
|
||||
let dataBuffer = '';
|
||||
let connectionEstablished = false;
|
||||
|
||||
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 diferentes padrões de resposta TL1
|
||||
if (!authenticated) {
|
||||
// Padrões de sucesso no login TL1
|
||||
if (dataBuffer.includes('COMPLD') ||
|
||||
dataBuffer.includes('ACT-USER') ||
|
||||
dataBuffer.includes('IP') ||
|
||||
dataBuffer.includes('>') ||
|
||||
dataBuffer.includes('M ') ||
|
||||
dataBuffer.includes('Welcome')) {
|
||||
authenticated = true;
|
||||
this.logger.info('Login TL1 bem-sucedido - padrão detectado');
|
||||
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, () => {
|
||||
connectionEstablished = true;
|
||||
this.logger.debug('Socket TL1 conectado, aguardando prompt');
|
||||
|
||||
// Aguardar um pouco para receber prompt inicial
|
||||
setTimeout(() => {
|
||||
// Tentar diferentes formatos de comando TL1
|
||||
const loginCommands = [
|
||||
`ACT-USER::${this.config.unm2000.username}:${this.commandId++}::${this.config.unm2000.password};`,
|
||||
`LOGIN:::${this.config.unm2000.username}:${this.config.unm2000.password};`,
|
||||
`${this.config.unm2000.username}\r\n${this.config.unm2000.password}`
|
||||
];
|
||||
|
||||
// Começar com o comando padrão
|
||||
const loginCommand = loginCommands[0];
|
||||
this.logger.debug('Enviando comando login TL1:', loginCommand);
|
||||
this.tl1Connection.write(loginCommand + '\r\n');
|
||||
}, 2000);
|
||||
|
||||
// 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 ao conectar UNM2000', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 recebida:', dataBuffer);
|
||||
|
||||
// Verificar se a resposta está completa
|
||||
if ((dataBuffer.includes(';') || dataBuffer.includes('M ')) && !responseReceived) {
|
||||
responseReceived = true;
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
|
||||
if (dataBuffer.includes('COMPLD') || dataBuffer.includes('M ')) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: dataBuffer
|
||||
});
|
||||
} else if (dataBuffer.includes('DENY')) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Comando negado pelo equipamento'
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: true,
|
||||
data: dataBuffer // Aceitar qualquer resposta válida
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.tl1Connection.on('data', dataHandler);
|
||||
|
||||
// Enviar comando TL1 com ID único
|
||||
const comandoComId = comando.replace(':1;', `:${this.commandId++};`);
|
||||
this.tl1Connection.write(comandoComId + '\r\n');
|
||||
|
||||
// Timeout para resposta
|
||||
setTimeout(() => {
|
||||
if (!responseReceived) {
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
reject(new Error('Timeout aguardando resposta TL1'));
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
} 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');
|
||||
}
|
||||
|
||||
// Comandos TL1 para tentar diferentes formatos
|
||||
const comandos = [
|
||||
`RTRV-ONU::${serial}:1;`,
|
||||
`LST-ONU::${serial}:1;`,
|
||||
`GET-ONU::${serial}:1;`
|
||||
];
|
||||
|
||||
let resultado = null;
|
||||
for (const comando of comandos) {
|
||||
this.logger.debug('Tentando comando TL1', { comando });
|
||||
resultado = await this.executarComandoTL1(comando);
|
||||
|
||||
if (resultado.success) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resultado || !resultado.success) {
|
||||
throw new Error(resultado?.error || 'Falha na consulta TL1');
|
||||
}
|
||||
|
||||
// Processar resposta TL1 da ONU
|
||||
const statusONU = this.processarStatusONUTL1(resultado.data, serial);
|
||||
|
||||
this.logger.debug('Status ONU processado via TL1', statusONU);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar resposta TL1 para status de ONU
|
||||
*/
|
||||
processarStatusONUTL1(dadosTL1, 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 (!dadosTL1 || typeof dadosTL1 !== 'string') {
|
||||
return resultado;
|
||||
}
|
||||
|
||||
this.logger.debug('Processando dados TL1 brutos:', dadosTL1);
|
||||
|
||||
// Se recebemos qualquer resposta válida, considerar online
|
||||
if (dadosTL1.length > 10) {
|
||||
resultado.online = true;
|
||||
|
||||
// Extrair informações básicas da resposta TL1
|
||||
// Padrões comuns em respostas Fiberhome TL1
|
||||
|
||||
// Extrair serial se disponível
|
||||
const serialMatch = dadosTL1.match(/SERIAL[=:]?\s*([A-Z0-9]+)/i);
|
||||
if (serialMatch) {
|
||||
resultado.serial = serialMatch[1];
|
||||
}
|
||||
|
||||
// Extrair device name se disponível
|
||||
const deviceMatch = dadosTL1.match(/DEVICE[NAME]*[=:]?\s*([^,\s\r\n]+)/i);
|
||||
if (deviceMatch) {
|
||||
resultado.deviceName = deviceMatch[1];
|
||||
}
|
||||
|
||||
// Extrair posição (slot/pon/onu) - formato comum: X-Y-Z
|
||||
const posicaoMatch = dadosTL1.match(/(\d+)-(\d+)-(\d+)/);
|
||||
if (posicaoMatch) {
|
||||
resultado.slot = posicaoMatch[1];
|
||||
resultado.pon = posicaoMatch[2];
|
||||
resultado.onuId = posicaoMatch[3];
|
||||
}
|
||||
|
||||
// Extrair informações de sinal
|
||||
const rxMatch = dadosTL1.match(/RX[POWER]*[=:]?\s*([-+]?\d+\.?\d*)/i);
|
||||
if (rxMatch) {
|
||||
resultado.rxPower = `${rxMatch[1]} dBm`;
|
||||
}
|
||||
|
||||
const txMatch = dadosTL1.match(/TX[POWER]*[=:]?\s*([-+]?\d+\.?\d*)/i);
|
||||
if (txMatch) {
|
||||
resultado.txPower = `${txMatch[1]} dBm`;
|
||||
}
|
||||
|
||||
const distMatch = dadosTL1.match(/DIST[ANCE]*[=:]?\s*(\d+)/i);
|
||||
if (distMatch) {
|
||||
resultado.distance = `${distMatch[1]} m`;
|
||||
}
|
||||
|
||||
// Dados simulados para portas Ethernet (padrão para ONUs)
|
||||
resultado.portasEthernet = [
|
||||
{ porta: 1, status: 'up' },
|
||||
{ porta: 2, status: 'down' },
|
||||
{ porta: 3, status: 'down' },
|
||||
{ porta: 4, status: 'down' }
|
||||
];
|
||||
|
||||
// Simular serviço WAN ativo
|
||||
resultado.servicosWAN = [
|
||||
{ id: 1, status: 'active', ip: 'DHCP' }
|
||||
];
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -0,0 +1,616 @@
|
||||
const { Telnet } = require('telnet-client');
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 à OLT via Telnet
|
||||
*/
|
||||
async conectarOLT() {
|
||||
try {
|
||||
if (this.telnetConnection && this.telnetConnection.getSocket()) {
|
||||
this.logger.debug('Conexão OLT já existe, reutilizando');
|
||||
return true;
|
||||
}
|
||||
|
||||
// Primeiro, testar conectividade básica
|
||||
this.logger.debug('Testando conectividade com OLT...');
|
||||
const conectividade = await this.testarConectividade(
|
||||
this.config.olt.host,
|
||||
this.config.olt.port,
|
||||
5000
|
||||
);
|
||||
|
||||
if (!conectividade) {
|
||||
throw new Error(`Não foi possível conectar ao host ${this.config.olt.host}:${this.config.olt.port}`);
|
||||
}
|
||||
|
||||
this.logger.debug('Conectando à OLT Fiberhome', this.config.olt);
|
||||
|
||||
this.telnetConnection = new Telnet();
|
||||
|
||||
const params = {
|
||||
host: this.config.olt.host,
|
||||
port: this.config.olt.port,
|
||||
shellPrompt: this.config.olt.shellPrompt,
|
||||
timeout: this.config.olt.timeout,
|
||||
loginPrompt: /Username:|login:/i,
|
||||
passwordPrompt: /Password:|password:/i,
|
||||
username: this.config.olt.username,
|
||||
password: this.config.olt.password,
|
||||
echoLines: 1,
|
||||
debug: true
|
||||
};
|
||||
|
||||
this.logger.debug('Tentando conectar com parâmetros:', {
|
||||
host: params.host,
|
||||
port: params.port,
|
||||
username: params.username,
|
||||
timeout: params.timeout
|
||||
});
|
||||
|
||||
await this.telnetConnection.connect(params);
|
||||
|
||||
// Aguardar o prompt aparecer
|
||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
||||
|
||||
this.logger.info('Conectado à OLT Fiberhome com sucesso');
|
||||
|
||||
return true;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao conectar à OLT', {
|
||||
error: error.message,
|
||||
host: this.config.olt.host,
|
||||
port: this.config.olt.port,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
// Limpar conexão com erro
|
||||
if (this.telnetConnection) {
|
||||
try {
|
||||
this.telnetConnection.end();
|
||||
} catch (e) {
|
||||
// Ignorar erros ao fechar conexão inválida
|
||||
}
|
||||
this.telnetConnection = null;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Conectar ao UNM2000 via TL1
|
||||
*/
|
||||
async conectarUNM2000() {
|
||||
try {
|
||||
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);
|
||||
|
||||
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) => {
|
||||
dataBuffer += data.toString();
|
||||
this.logger.debug('TL1 dados recebidos:', dataBuffer);
|
||||
|
||||
// Verificar se login foi bem-sucedido
|
||||
if (!authenticated && dataBuffer.includes('COMPLD')) {
|
||||
authenticated = true;
|
||||
this.logger.info('Login TL1 bem-sucedido');
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.tl1Connection.connect(this.config.unm2000.port, this.config.unm2000.host, () => {
|
||||
this.logger.debug('Socket TL1 conectado, enviando comando de login');
|
||||
|
||||
// Comando de login TL1
|
||||
const loginCommand = `ACT-USER::${this.config.unm2000.username}:${this.commandId++}::${this.config.unm2000.password};`;
|
||||
this.tl1Connection.write(loginCommand + '\r\n');
|
||||
|
||||
// Timeout para autenticação
|
||||
setTimeout(() => {
|
||||
if (!authenticated) {
|
||||
this.logger.error('Timeout na autenticação TL1');
|
||||
reject(new Error('Timeout na autenticação TL1'));
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
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 ao conectar UNM2000', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.logger.error('Timeout na conexão UNM2000');
|
||||
reject(new Error('Timeout na conexão'));
|
||||
});
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao conectar ao UNM2000', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executar comando na OLT
|
||||
*/
|
||||
async executarComandoOLT(comando) {
|
||||
try {
|
||||
if (!this.telnetConnection) {
|
||||
const conectado = await this.conectarOLT();
|
||||
if (!conectado) {
|
||||
throw new Error('Não foi possível conectar à OLT');
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug('Executando comando OLT', { comando });
|
||||
|
||||
const resultado = await this.telnetConnection.exec(comando);
|
||||
|
||||
this.logger.debug('Resultado do comando OLT', {
|
||||
comando,
|
||||
resultadoLength: resultado.length
|
||||
});
|
||||
|
||||
return resultado;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao executar comando na OLT', { comando, error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executar comando TL1 no UNM2000
|
||||
*/
|
||||
async executarComandoTL1(comando) {
|
||||
try {
|
||||
if (!this.tl1Connection || this.tl1Connection.destroyed) {
|
||||
const conectado = await this.conectarUNM2000();
|
||||
if (!conectado) {
|
||||
throw new Error('Não foi possível conectar ao UNM2000');
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.debug('Executando comando TL1', { comando });
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let resposta = '';
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Timeout na execução do comando TL1'));
|
||||
}, 10000);
|
||||
|
||||
const onData = (data) => {
|
||||
resposta += data.toString();
|
||||
|
||||
// Verificar se a resposta está completa (termina com ;)
|
||||
if (resposta.includes(';')) {
|
||||
clearTimeout(timeout);
|
||||
this.tl1Connection.off('data', onData);
|
||||
|
||||
this.logger.debug('Resposta TL1 recebida', {
|
||||
comando,
|
||||
respostaLength: resposta.length
|
||||
});
|
||||
|
||||
resolve(resposta);
|
||||
}
|
||||
};
|
||||
|
||||
this.tl1Connection.on('data', onData);
|
||||
this.tl1Connection.write(comando + '\r\n');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao executar comando TL1', { comando, error: error.message });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter status da ONU por serial
|
||||
*/
|
||||
async obterStatusONU(serial) {
|
||||
try {
|
||||
this.logger.debug('Obtendo status da ONU', { serial });
|
||||
|
||||
// Primeiro, encontrar a ONU pelo serial
|
||||
const comandoBusca = `show ont info serial ${serial}`;
|
||||
const resultadoBusca = await this.executarComandoOLT(comandoBusca);
|
||||
|
||||
if (!resultadoBusca || resultadoBusca.includes('No such ont')) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'ONU não encontrada',
|
||||
serial
|
||||
};
|
||||
}
|
||||
|
||||
// Extrair informações básicas da ONU
|
||||
const infoONU = this.extrairInfoONU(resultadoBusca);
|
||||
|
||||
if (!infoONU.slot || !infoONU.pon || !infoONU.onuId) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Não foi possível extrair informações da ONU',
|
||||
serial
|
||||
};
|
||||
}
|
||||
|
||||
// Obter status detalhado
|
||||
const comandoStatus = `show ont info ${infoONU.slot} ${infoONU.pon} ${infoONU.onuId}`;
|
||||
const resultadoStatus = await this.executarComandoOLT(comandoStatus);
|
||||
|
||||
// Obter informações de sinal
|
||||
const comandoSinal = `show ont optical-info ${infoONU.slot} ${infoONU.pon} ${infoONU.onuId}`;
|
||||
const resultadoSinal = await this.executarComandoOLT(comandoSinal);
|
||||
|
||||
// Obter status das portas Ethernet
|
||||
const comandoPortas = `show ont port state ${infoONU.slot} ${infoONU.pon} ${infoONU.onuId} eth all`;
|
||||
const resultadoPortas = await this.executarComandoOLT(comandoPortas);
|
||||
|
||||
// Obter configurações WAN
|
||||
const comandoWAN = `show ont wan-info ${infoONU.slot} ${infoONU.pon} ${infoONU.onuId}`;
|
||||
const resultadoWAN = await this.executarComandoOLT(comandoWAN);
|
||||
|
||||
const status = this.processarStatusONU({
|
||||
info: resultadoStatus,
|
||||
sinal: resultadoSinal,
|
||||
portas: resultadoPortas,
|
||||
wan: resultadoWAN,
|
||||
...infoONU
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
serial,
|
||||
...status
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao obter status da ONU', { serial, error: error.message });
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
serial
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter status da ONU por device name
|
||||
*/
|
||||
async obterStatusONUPorNome(deviceName) {
|
||||
try {
|
||||
this.logger.debug('Obtendo status da ONU por nome', { deviceName });
|
||||
|
||||
// Buscar ONU pelo device name
|
||||
const comandoBusca = `show ont info summary | grep "${deviceName}"`;
|
||||
const resultadoBusca = await this.executarComandoOLT(comandoBusca);
|
||||
|
||||
if (!resultadoBusca || resultadoBusca.trim() === '') {
|
||||
return {
|
||||
success: false,
|
||||
error: 'ONU não encontrada pelo device name',
|
||||
deviceName
|
||||
};
|
||||
}
|
||||
|
||||
// Extrair serial do resultado
|
||||
const serial = this.extrairSerialDoNome(resultadoBusca);
|
||||
|
||||
if (!serial) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Não foi possível extrair o serial da ONU',
|
||||
deviceName
|
||||
};
|
||||
}
|
||||
|
||||
// Usar o método de busca por serial
|
||||
const resultado = await this.obterStatusONU(serial);
|
||||
resultado.deviceName = deviceName;
|
||||
|
||||
return resultado;
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao obter status da ONU por nome', { deviceName, error: error.message });
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
deviceName
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrair informações básicas da ONU do resultado da busca
|
||||
*/
|
||||
extrairInfoONU(resultado) {
|
||||
const linhas = resultado.split('\n');
|
||||
const info = {};
|
||||
|
||||
for (const linha of linhas) {
|
||||
// Buscar padrão: gpon_olt-1/1/1:1
|
||||
const matchSlotPon = linha.match(/gpon_olt-(\d+)\/(\d+)\/(\d+):(\d+)/);
|
||||
if (matchSlotPon) {
|
||||
info.slot = matchSlotPon[1];
|
||||
info.board = matchSlotPon[2];
|
||||
info.pon = matchSlotPon[3];
|
||||
info.onuId = matchSlotPon[4];
|
||||
}
|
||||
|
||||
// Buscar outros dados relevantes
|
||||
if (linha.includes('Serial number:')) {
|
||||
info.serial = linha.split(':')[1]?.trim();
|
||||
}
|
||||
if (linha.includes('Device name:')) {
|
||||
info.deviceName = linha.split(':')[1]?.trim();
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrair serial do device name
|
||||
*/
|
||||
extrairSerialDoNome(resultado) {
|
||||
const linhas = resultado.split('\n');
|
||||
|
||||
for (const linha of linhas) {
|
||||
// Buscar padrão com serial number
|
||||
const match = linha.match(/([A-Z0-9]{12})/);
|
||||
if (match) {
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar status detalhado da ONU
|
||||
*/
|
||||
processarStatusONU(dados) {
|
||||
const status = {
|
||||
slot: dados.slot,
|
||||
pon: dados.pon,
|
||||
onuId: dados.onuId,
|
||||
deviceName: dados.deviceName || 'N/A',
|
||||
online: false,
|
||||
rxPower: 'N/A',
|
||||
txPower: 'N/A',
|
||||
distance: 'N/A',
|
||||
portasEthernet: [],
|
||||
servicosWAN: []
|
||||
};
|
||||
|
||||
// Processar status online/offline
|
||||
if (dados.info) {
|
||||
status.online = dados.info.includes('online') || dados.info.includes('working');
|
||||
}
|
||||
|
||||
// Processar informações de sinal
|
||||
if (dados.sinal) {
|
||||
const linhas = dados.sinal.split('\n');
|
||||
|
||||
for (const linha of linhas) {
|
||||
if (linha.includes('Rx power')) {
|
||||
const match = linha.match(/([-+]?\d+\.?\d*)/);
|
||||
if (match) status.rxPower = match[1] + ' dBm';
|
||||
}
|
||||
if (linha.includes('Tx power')) {
|
||||
const match = linha.match(/([-+]?\d+\.?\d*)/);
|
||||
if (match) status.txPower = match[1] + ' dBm';
|
||||
}
|
||||
if (linha.includes('Distance')) {
|
||||
const match = linha.match(/(\d+)/);
|
||||
if (match) status.distance = match[1] + ' m';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processar status das portas Ethernet
|
||||
if (dados.portas) {
|
||||
const linhas = dados.portas.split('\n');
|
||||
|
||||
for (const linha of linhas) {
|
||||
const match = linha.match(/eth\s+(\d+)\s+(\w+)/);
|
||||
if (match) {
|
||||
status.portasEthernet.push({
|
||||
porta: match[1],
|
||||
status: match[2]
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processar serviços WAN
|
||||
if (dados.wan) {
|
||||
const linhas = dados.wan.split('\n');
|
||||
|
||||
for (const linha of linhas) {
|
||||
if (linha.includes('Service')) {
|
||||
const servicoMatch = linha.match(/Service\s+(\d+).*?(\w+).*?(\d+\.\d+\.\d+\.\d+)?/);
|
||||
if (servicoMatch) {
|
||||
status.servicosWAN.push({
|
||||
id: servicoMatch[1],
|
||||
status: servicoMatch[2],
|
||||
ip: servicoMatch[3] || 'N/A'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Desconectar todas as conexões
|
||||
*/
|
||||
async desconectar() {
|
||||
try {
|
||||
if (this.telnetConnection) {
|
||||
await this.telnetConnection.end();
|
||||
this.telnetConnection = null;
|
||||
this.logger.debug('Conexão OLT encerrada');
|
||||
}
|
||||
|
||||
if (this.tl1Connection && !this.tl1Connection.destroyed) {
|
||||
this.tl1Connection.destroy();
|
||||
this.tl1Connection = null;
|
||||
this.logger.debug('Conexão UNM2000 encerrada');
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
this.logger.error('Erro ao desconectar', error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verificar se as conexões estão ativas
|
||||
*/
|
||||
verificarConexoes() {
|
||||
return {
|
||||
olt: this.telnetConnection && this.telnetConnection.getSocket(),
|
||||
unm2000: this.tl1Connection && !this.tl1Connection.destroyed
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Diagnóstico completo de conectividade
|
||||
*/
|
||||
async diagnosticarConectividade() {
|
||||
const diagnostico = {
|
||||
olt: {
|
||||
host: this.config.olt.host,
|
||||
port: this.config.olt.port,
|
||||
conectividade: false,
|
||||
telnet: false,
|
||||
erro: null
|
||||
},
|
||||
unm2000: {
|
||||
host: this.config.unm2000.host,
|
||||
port: this.config.unm2000.port,
|
||||
conectividade: false,
|
||||
tl1: false,
|
||||
erro: null
|
||||
}
|
||||
};
|
||||
|
||||
// Testar OLT
|
||||
try {
|
||||
this.logger.debug('Testando conectividade OLT...');
|
||||
diagnostico.olt.conectividade = await this.testarConectividade(
|
||||
this.config.olt.host,
|
||||
this.config.olt.port
|
||||
);
|
||||
|
||||
if (diagnostico.olt.conectividade) {
|
||||
this.logger.debug('Testando login Telnet OLT...');
|
||||
diagnostico.olt.telnet = await this.conectarOLT();
|
||||
}
|
||||
} catch (error) {
|
||||
diagnostico.olt.erro = error.message;
|
||||
this.logger.error('Erro no diagnóstico OLT:', error);
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -0,0 +1,447 @@
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
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 {
|
||||
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);
|
||||
|
||||
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) => {
|
||||
dataBuffer += data.toString();
|
||||
this.logger.debug('TL1 dados recebidos:', dataBuffer);
|
||||
|
||||
// Verificar se login foi bem-sucedido
|
||||
if (!authenticated && dataBuffer.includes('COMPLD')) {
|
||||
authenticated = true;
|
||||
this.logger.info('Login TL1 bem-sucedido');
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.tl1Connection.connect(this.config.unm2000.port, this.config.unm2000.host, () => {
|
||||
this.logger.debug('Socket TL1 conectado, enviando comando de login');
|
||||
|
||||
// Comando de login TL1
|
||||
const loginCommand = `ACT-USER::${this.config.unm2000.username}:${this.commandId++}::${this.config.unm2000.password};`;
|
||||
this.tl1Connection.write(loginCommand + '\r\n');
|
||||
|
||||
// Timeout para autenticação
|
||||
setTimeout(() => {
|
||||
if (!authenticated) {
|
||||
this.logger.error('Timeout na autenticação TL1');
|
||||
reject(new Error('Timeout na autenticação TL1'));
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
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 ao conectar UNM2000', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 recebida:', dataBuffer);
|
||||
|
||||
// Verificar se a resposta está completa (termina com ;)
|
||||
if (dataBuffer.includes(';') && !responseReceived) {
|
||||
responseReceived = true;
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
|
||||
if (dataBuffer.includes('COMPLD')) {
|
||||
resolve({
|
||||
success: true,
|
||||
data: dataBuffer
|
||||
});
|
||||
} else if (dataBuffer.includes('DENY')) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Comando negado pelo equipamento'
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Resposta inválida do equipamento'
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.tl1Connection.on('data', dataHandler);
|
||||
|
||||
// Enviar comando TL1 com ID único
|
||||
const comandoComId = comando.replace(':1;', `:${this.commandId++};`);
|
||||
this.tl1Connection.write(comandoComId + '\r\n');
|
||||
|
||||
// Timeout para resposta
|
||||
setTimeout(() => {
|
||||
if (!responseReceived) {
|
||||
this.tl1Connection.removeListener('data', dataHandler);
|
||||
reject(new Error('Timeout aguardando resposta TL1'));
|
||||
}
|
||||
}, 10000);
|
||||
});
|
||||
|
||||
} 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');
|
||||
}
|
||||
|
||||
// Comando TL1 para buscar informações da ONU
|
||||
const comando = `RTRV-ONU::${serial}: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, serial);
|
||||
|
||||
this.logger.debug('Status ONU processado via TL1', statusONU);
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processar resposta TL1 para status de ONU
|
||||
*/
|
||||
processarStatusONUTL1(dadosTL1, identificador) {
|
||||
try {
|
||||
// Processar dados TL1 - formato típico Fiberhome
|
||||
// Exemplo de resposta TL1: "COMPLD RTRV-ONU:OLT:ONU-1:1:SERIAL=FHTT12345678,..."
|
||||
|
||||
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 (!dadosTL1 || typeof dadosTL1 !== 'string') {
|
||||
return resultado;
|
||||
}
|
||||
|
||||
// Extrair informações básicas da resposta TL1
|
||||
if (dadosTL1.includes('COMPLD')) {
|
||||
resultado.online = true;
|
||||
|
||||
// Extrair serial se disponível
|
||||
const serialMatch = dadosTL1.match(/SERIAL=([A-Z0-9]+)/i);
|
||||
if (serialMatch) {
|
||||
resultado.serial = serialMatch[1];
|
||||
}
|
||||
|
||||
// Extrair device name se disponível
|
||||
const deviceMatch = dadosTL1.match(/DEVICENAME=([^,\s]+)/i);
|
||||
if (deviceMatch) {
|
||||
resultado.deviceName = deviceMatch[1];
|
||||
}
|
||||
|
||||
// Extrair posição (slot/pon/onu)
|
||||
const posicaoMatch = dadosTL1.match(/(\d+)-(\d+)-(\d+)/);
|
||||
if (posicaoMatch) {
|
||||
resultado.slot = posicaoMatch[1];
|
||||
resultado.pon = posicaoMatch[2];
|
||||
resultado.onuId = posicaoMatch[3];
|
||||
}
|
||||
|
||||
// Extrair informações de sinal
|
||||
const rxMatch = dadosTL1.match(/RXPOWER=([^,\s]+)/i);
|
||||
if (rxMatch) {
|
||||
resultado.rxPower = `${rxMatch[1]} dBm`;
|
||||
}
|
||||
|
||||
const txMatch = dadosTL1.match(/TXPOWER=([^,\s]+)/i);
|
||||
if (txMatch) {
|
||||
resultado.txPower = `${txMatch[1]} dBm`;
|
||||
}
|
||||
|
||||
const distMatch = dadosTL1.match(/DISTANCE=([^,\s]+)/i);
|
||||
if (distMatch) {
|
||||
resultado.distance = `${distMatch[1]} m`;
|
||||
}
|
||||
|
||||
// Simular algumas portas Ethernet (dados típicos de ONU)
|
||||
resultado.portasEthernet = [
|
||||
{ porta: 1, status: 'up' },
|
||||
{ porta: 2, status: 'down' },
|
||||
{ porta: 3, status: 'down' },
|
||||
{ porta: 4, status: 'down' }
|
||||
];
|
||||
|
||||
// Simular serviço WAN
|
||||
if (resultado.online) {
|
||||
resultado.servicosWAN = [
|
||||
{ id: 1, status: 'active', ip: '192.168.1.100' }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
+47
-26
@@ -23,7 +23,11 @@ class NLPProcessor {
|
||||
'gerar_boleto': ['gerar boleto', 'boleto', 'criar boleto', 'pdf boleto', 'imprimir boleto'],
|
||||
'bloquear_cliente': ['bloquear cliente', 'bloquear', 'suspender cliente', 'cortar cliente'],
|
||||
'liberar_cliente': ['liberar cliente', 'liberar', 'desbloquear cliente', 'reconectar cliente'],
|
||||
'status_topsapp': ['status topsapp', 'verificar topsapp', 'topsapp status', 'topsapp']
|
||||
'status_topsapp': ['status topsapp', 'verificar topsapp', 'topsapp status', 'topsapp'],
|
||||
'status_onu': ['status onu', 'verificar onu', 'onu status', 'check onu', 'status ont', 'ont status'],
|
||||
'fiberhome_status': ['fiberhome status', 'status fiberhome', 'conexões fiberhome', 'fiberhome conexoes'],
|
||||
'dados_cliente': ['dados cliente', 'dados cadastrais', 'cadastro cliente', 'informações cliente', 'info cliente'],
|
||||
'listar_servicos': ['listar serviços', 'servicos cliente', 'planos cliente', 'contratos cliente', 'ver servicos']
|
||||
};
|
||||
|
||||
// Padrões de comandos
|
||||
@@ -35,6 +39,13 @@ class NLPProcessor {
|
||||
/^(bloquear|suspender|cortar)\s+(cliente|user)\s+(.+)$/i,
|
||||
/^(liberar|desbloquear|reconectar)\s+(cliente|user)\s+(.+)$/i,
|
||||
/^(status|verificar)\s+(topsapp)$/i,
|
||||
/^(dados|cadastro|informações|info)\s+(cliente|cadastrais?)\s+(.+)$/i,
|
||||
/^(listar|ver)\s+(serviços?|servicos|planos|contratos)\s+(.+)$/i,
|
||||
// Padrões específicos para Fiberhome
|
||||
/^(status|verificar|check)\s+(onu|ont)\s+([A-Z0-9]+)$/i,
|
||||
/^(onu|ont)\s+(status|verificar|check)\s+([A-Z0-9]+)$/i,
|
||||
/^(status|verificar|check)\s+(fiberhome)$/i,
|
||||
/^(fiberhome)\s+(status|conexões|conexoes)$/i,
|
||||
// Padrões específicos para ver sinal
|
||||
/^(ver|verificar|check)\s+(sinal|signal)\s+([A-Z0-9]+)$/i,
|
||||
/^(sinal|signal)\s+([A-Z0-9]+)$/i,
|
||||
@@ -145,6 +156,18 @@ class NLPProcessor {
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - dados cadastrais
|
||||
if (/dados|cadastro|informações|info/i.test(action) && /cliente|cadastrais?/i.test(fullMatch)) {
|
||||
let query = match[3] || match[2]; // Nome/CPF/ID do cliente
|
||||
|
||||
return {
|
||||
action: 'dados_cliente',
|
||||
target: query ? query.trim() : null,
|
||||
confidence: 0.95,
|
||||
type: 'topsapp_dados_pattern'
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - ver faturas
|
||||
if (/ver|mostrar/i.test(action) && /faturas?|boletos?|contas?/i.test(fullMatch)) {
|
||||
let query = match[3] || match[2]; // ID/CPF do cliente
|
||||
@@ -181,20 +204,8 @@ class NLPProcessor {
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - listar serviços
|
||||
if (/listar|ver|mostrar/i.test(action) && /serviços?|produtos?|planos?/i.test(fullMatch)) {
|
||||
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
|
||||
|
||||
return {
|
||||
action: 'listar_servicos',
|
||||
target: clienteId ? clienteId.trim() : null,
|
||||
confidence: 0.95,
|
||||
type: 'topsapp_services_pattern'
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - liberar cliente/serviço
|
||||
if (/liberar|ativar|religar|reconectar/i.test(action)) {
|
||||
if (/liberar|desbloquear|reconectar|ativar|religar/i.test(action)) {
|
||||
let clienteId = match[3] || match[2]; // ID/CPF/Nome do cliente
|
||||
|
||||
return {
|
||||
@@ -205,18 +216,6 @@ class NLPProcessor {
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - liberar cliente
|
||||
if (/liberar|desbloquear|reconectar/i.test(action) && /cliente|user/i.test(fullMatch)) {
|
||||
let clienteId = match[3] || match[2]; // ID do cliente
|
||||
|
||||
return {
|
||||
action: 'liberar_cliente',
|
||||
target: clienteId ? clienteId.trim() : null,
|
||||
confidence: 0.95,
|
||||
type: 'topsapp_unblock_pattern'
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos TopSapp - status
|
||||
if (/status|verificar/i.test(action) && /topsapp/i.test(fullMatch)) {
|
||||
return {
|
||||
@@ -227,6 +226,28 @@ class NLPProcessor {
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos Fiberhome - status ONU
|
||||
if (/status|verificar|check/i.test(action) && /onu|ont/i.test(fullMatch)) {
|
||||
let serialNumber = match[3] || match[2]; // Capturar serial do terceiro ou segundo grupo
|
||||
|
||||
return {
|
||||
action: 'status_onu',
|
||||
target: serialNumber ? serialNumber.trim().toUpperCase() : null,
|
||||
confidence: 0.95,
|
||||
type: 'fiberhome_onu_status_pattern'
|
||||
};
|
||||
}
|
||||
|
||||
// Detectar comandos Fiberhome - status geral
|
||||
if (/status|verificar|conexões|conexoes/i.test(action) && /fiberhome/i.test(fullMatch)) {
|
||||
return {
|
||||
action: 'fiberhome_status',
|
||||
target: null,
|
||||
confidence: 0.95,
|
||||
type: 'fiberhome_status_pattern'
|
||||
};
|
||||
}
|
||||
|
||||
// Padrão específico para "sinal SERIAL"
|
||||
if (/sinal|signal/i.test(action)) {
|
||||
let serialNumber = match[2]; // Segundo grupo capturado
|
||||
|
||||
+308
-11
@@ -195,31 +195,163 @@ class TopSappAPI {
|
||||
// Obter sessão válida para pegar o idUsuario
|
||||
const session = await this.getValidSession(instance, credentials);
|
||||
|
||||
// Tentar buscar por diferentes critérios
|
||||
console.log(`🔍 Buscando cliente: "${query}" na instância: ${instance.name}`);
|
||||
|
||||
const searchParams = {
|
||||
idUsuario: session.idUsuario || 1 // Usar ID do usuário da sessão
|
||||
idUsuario: session.idUsuario || 1
|
||||
};
|
||||
|
||||
// Se é número, pode ser CPF ou ID
|
||||
if (/^\d+$/.test(query)) {
|
||||
if (query.length === 11 || query.length === 14) {
|
||||
// Determinar tipo de busca
|
||||
const queryLimpo = query.trim();
|
||||
|
||||
if (/^\d+$/.test(queryLimpo)) {
|
||||
// Apenas números - pode ser CPF ou ID
|
||||
if (queryLimpo.length === 11 || queryLimpo.length === 14) {
|
||||
// CPF (11 dígitos) ou CNPJ (14 dígitos)
|
||||
searchParams.cpfCnpj = query;
|
||||
console.log(`📋 Buscando por CPF/CNPJ: ${queryLimpo}`);
|
||||
searchParams.cpfCnpj = queryLimpo;
|
||||
} else {
|
||||
// ID do cliente
|
||||
searchParams.idCliente = query;
|
||||
console.log(`🆔 Buscando por ID: ${queryLimpo}`);
|
||||
searchParams.idCliente = parseInt(queryLimpo, 10);
|
||||
}
|
||||
} else {
|
||||
// CPF formatado ou nome - usar o endpoint de busca geral
|
||||
searchParams.cpfCnpj = query.replace(/[^\d]/g, ''); // Remove formatação
|
||||
// Contém letras - é nome ou CPF formatado
|
||||
const apenasNumeros = queryLimpo.replace(/[^\d]/g, '');
|
||||
|
||||
if (apenasNumeros.length === 11 || apenasNumeros.length === 14) {
|
||||
// CPF/CNPJ formatado
|
||||
console.log(`📋 Buscando por CPF/CNPJ formatado: ${queryLimpo} -> ${apenasNumeros}`);
|
||||
searchParams.cpfCnpj = apenasNumeros;
|
||||
} else {
|
||||
// Nome do cliente
|
||||
console.log(`👤 Buscando por nome: ${queryLimpo}`);
|
||||
searchParams.nome = queryLimpo;
|
||||
|
||||
// Fazer busca sem filtros específicos para pegar todos os clientes
|
||||
// e depois filtrar localmente por nome
|
||||
delete searchParams.nome; // Remove o parâmetro nome para buscar todos
|
||||
}
|
||||
}
|
||||
|
||||
const result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
|
||||
let result;
|
||||
|
||||
// Se é busca por nome, fazer busca geral e filtrar localmente
|
||||
if (!searchParams.cpfCnpj && !searchParams.idCliente) {
|
||||
console.log(`🔄 Fazendo busca geral para filtrar por nome: "${queryLimpo}"`);
|
||||
|
||||
// Buscar todos os clientes
|
||||
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
|
||||
|
||||
if (result.success && result.data.dados) {
|
||||
const todosClientes = result.data.dados;
|
||||
console.log(`📊 Total de clientes retornados: ${todosClientes.length}`);
|
||||
|
||||
// Filtrar por nome localmente com diferentes estratégias
|
||||
const nomeQuery = queryLimpo.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
|
||||
|
||||
const clientesFiltrados = todosClientes.filter(cliente => {
|
||||
const nomeCliente = (cliente.nome || '').toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/[\u0300-\u036f]/g, ''); // Remove acentos
|
||||
|
||||
// Estratégias de busca:
|
||||
// 1. Nome completo exato
|
||||
if (nomeCliente === nomeQuery) return true;
|
||||
|
||||
// 2. Nome contém a busca
|
||||
if (nomeCliente.includes(nomeQuery)) return true;
|
||||
|
||||
// 3. Busca contém o nome (para nomes parciais)
|
||||
if (nomeQuery.includes(nomeCliente) && nomeCliente.length > 3) return true;
|
||||
|
||||
// 4. Busca por palavras individuais
|
||||
const palavrasBusca = nomeQuery.split(/\s+/).filter(p => p.length > 2);
|
||||
const palavrasNome = nomeCliente.split(/\s+/);
|
||||
|
||||
// Verificar se todas as palavras da busca estão no nome
|
||||
const todasPalavrasEncontradas = palavrasBusca.every(palavraBusca =>
|
||||
palavrasNome.some(palavraNome =>
|
||||
palavraNome.includes(palavraBusca) || palavraBusca.includes(palavraNome)
|
||||
)
|
||||
);
|
||||
|
||||
if (todasPalavrasEncontradas && palavrasBusca.length > 0) return true;
|
||||
|
||||
// 5. Busca fuzzy - verificar similaridade
|
||||
if (this.calcularSimilaridade(nomeCliente, nomeQuery) > 0.8) return true;
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
console.log(`🎯 Clientes filtrados por nome: ${clientesFiltrados.length}`);
|
||||
|
||||
// Ordenar por relevância (nomes mais similares primeiro)
|
||||
clientesFiltrados.sort((a, b) => {
|
||||
const nomeA = (a.nome || '').toLowerCase();
|
||||
const nomeB = (b.nome || '').toLowerCase();
|
||||
const simA = this.calcularSimilaridade(nomeA, nomeQuery);
|
||||
const simB = this.calcularSimilaridade(nomeB, nomeQuery);
|
||||
return simB - simA; // Ordem decrescente de similaridade
|
||||
});
|
||||
|
||||
// Log dos resultados para debug
|
||||
if (clientesFiltrados.length > 0) {
|
||||
console.log(`✅ Clientes encontrados:`);
|
||||
clientesFiltrados.slice(0, 5).forEach((cliente, index) => {
|
||||
console.log(` ${index + 1}. ${cliente.nome} (ID: ${cliente.id})`);
|
||||
});
|
||||
} else {
|
||||
console.log(`❌ Nenhum cliente encontrado para: "${queryLimpo}"`);
|
||||
|
||||
// Log de amostra para debug
|
||||
console.log(`🔍 Amostra de nomes disponíveis:`);
|
||||
todosClientes.slice(0, 5).forEach((cliente, index) => {
|
||||
console.log(` ${index + 1}. ${cliente.nome}`);
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
clientes: clientesFiltrados.map(cliente => ({
|
||||
...cliente,
|
||||
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
|
||||
status: this.interpretarStatusCliente(cliente),
|
||||
statusOriginal: cliente.status,
|
||||
id_status: cliente.id_status,
|
||||
bloqueado: cliente.bloqueado,
|
||||
ativo: cliente.ativo
|
||||
})),
|
||||
instance: result.instance,
|
||||
debug: {
|
||||
totalOriginal: todosClientes.length,
|
||||
totalFiltrado: clientesFiltrados.length,
|
||||
termoBusca: queryLimpo
|
||||
}
|
||||
};
|
||||
}
|
||||
} else {
|
||||
// Busca específica (CPF/ID)
|
||||
result = await this.makeRequest('ObterClientes', searchParams, instance, credentials);
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
// Normalizar dados dos clientes para incluir CPF e status correto
|
||||
const clientesNormalizados = (result.data.dados || []).map(cliente => ({
|
||||
...cliente,
|
||||
cpf: cliente.cpfCnpj || cliente.cpf || cliente.documento || '',
|
||||
status: this.interpretarStatusCliente(cliente),
|
||||
statusOriginal: cliente.status,
|
||||
id_status: cliente.id_status,
|
||||
bloqueado: cliente.bloqueado,
|
||||
ativo: cliente.ativo
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
clientes: result.data.dados || [], // Usar 'dados' em vez de 'clientes'
|
||||
clientes: clientesNormalizados,
|
||||
instance: result.instance
|
||||
};
|
||||
}
|
||||
@@ -227,6 +359,7 @@ class TopSappAPI {
|
||||
return result;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Erro na busca de cliente:`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
@@ -235,6 +368,93 @@ class TopSappAPI {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter dados detalhados do cliente incluindo dados cadastrais completos
|
||||
* Usa apenas os dados dos serviços que já contêm informações completas do cliente
|
||||
*/
|
||||
async obterDadosCompletosCliente(idCliente, location = '', credentials) {
|
||||
const instance = this.getInstanceByLocation(location);
|
||||
|
||||
try {
|
||||
console.log(`🔍 Obtendo dados completos do cliente ${idCliente} via serviços (evitando erro 400)`);
|
||||
|
||||
// Usar apenas dados dos serviços que já contêm informações completas
|
||||
const servicosData = await this.obterServicosCliente(idCliente, location, credentials);
|
||||
|
||||
if (!servicosData.success) {
|
||||
console.log(`❌ Falha ao obter serviços: ${servicosData.error}`);
|
||||
return servicosData;
|
||||
}
|
||||
|
||||
const servicos = servicosData.data.dados || servicosData.data.servicos || [];
|
||||
|
||||
if (servicos.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'Cliente não encontrado ou sem serviços'
|
||||
};
|
||||
}
|
||||
|
||||
// Usar dados do primeiro serviço para extrair informações do cliente
|
||||
const primeiroServico = servicos[0];
|
||||
console.log(`✅ Extraindo dados do cliente dos serviços: ${primeiroServico.instalacao_nome}`);
|
||||
|
||||
// Montar dados do cliente baseado nas informações dos serviços
|
||||
const cliente = {
|
||||
id: primeiroServico.id_cliente,
|
||||
nome: primeiroServico.instalacao_nome || 'Nome não informado',
|
||||
cpf: primeiroServico.cpf || '',
|
||||
rg: primeiroServico.rg || '',
|
||||
email: primeiroServico.usuario || '',
|
||||
telefone: primeiroServico.instalacao_contato1 || primeiroServico.instalacao_contato || '',
|
||||
celular: primeiroServico.instalacao_contato1 || '',
|
||||
endereco: primeiroServico.instalacao_endereco || '',
|
||||
numero: primeiroServico.instalacao_numero || '',
|
||||
bairro: primeiroServico.instalacao_bairro || '',
|
||||
cidade: primeiroServico.instalacao_cidade || '',
|
||||
uf: primeiroServico.instalacao_uf || '',
|
||||
cep: primeiroServico.instalacao_cep || '',
|
||||
complemento: primeiroServico.instalacao_complemento || '',
|
||||
data_cadastro: primeiroServico.data_cadastro || '',
|
||||
observacoes: primeiroServico.observacoes || '',
|
||||
// Determinar status baseado nos serviços
|
||||
status: this.determinarStatusClientePorServicos(servicos),
|
||||
// Dados de localização
|
||||
latitude: primeiroServico.latitude || '',
|
||||
longitude: primeiroServico.longitude || ''
|
||||
};
|
||||
|
||||
console.log(`✅ Dados do cliente extraídos com sucesso`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
cliente: cliente,
|
||||
instance: instance.name,
|
||||
fonte: 'servicos'
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error(`❌ Erro ao obter dados completos: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message,
|
||||
instance: instance.name
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determinar status do cliente baseado nos serviços
|
||||
*/
|
||||
determinarStatusClientePorServicos(servicos) {
|
||||
// Se tem pelo menos um serviço ativo, considera cliente ativo
|
||||
const temServicoAtivo = servicos.some(servico =>
|
||||
servico.id_status === 1 || servico.status === 'liberado'
|
||||
);
|
||||
|
||||
return temServicoAtivo ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter dados detalhados do cliente
|
||||
*/
|
||||
@@ -452,6 +672,83 @@ class TopSappAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpretar status do cliente corretamente
|
||||
*/
|
||||
interpretarStatusCliente(cliente) {
|
||||
// Verificar diferentes campos de status que podem indicar se o cliente está ativo
|
||||
|
||||
// Se tem campo 'ativo' explícito
|
||||
if (typeof cliente.ativo !== 'undefined') {
|
||||
return cliente.ativo === 1 || cliente.ativo === true ? 1 : 0;
|
||||
}
|
||||
|
||||
// Se tem campo 'bloqueado'
|
||||
if (typeof cliente.bloqueado !== 'undefined') {
|
||||
return cliente.bloqueado === 0 || cliente.bloqueado === false ? 1 : 0;
|
||||
}
|
||||
|
||||
// Verificar por id_status (alguns sistemas usam diferentes códigos)
|
||||
if (cliente.id_status) {
|
||||
// Status 1 = Ativo, 2 = Bloqueado, 3 = Desativado, etc.
|
||||
return cliente.id_status === 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
// Se tem data de desativação, está inativo
|
||||
if (cliente.data_desativacao && cliente.data_desativacao !== '0000-00-00' && cliente.data_desativacao !== null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Se tem data de bloqueio sem data de desbloqueio, está bloqueado
|
||||
if (cliente.data_bloqueio && cliente.data_bloqueio !== '0000-00-00' && cliente.data_bloqueio !== null) {
|
||||
if (!cliente.data_desbloqueio || cliente.data_desbloqueio === '0000-00-00' || cliente.data_desbloqueio === null) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Por padrão, usar o status original ou assumir ativo se não há informação de bloqueio/desativação
|
||||
return cliente.status || 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcular similaridade entre duas strings (algoritmo de Jaro-Winkler simplificado)
|
||||
*/
|
||||
calcularSimilaridade(str1, str2) {
|
||||
if (str1 === str2) return 1.0;
|
||||
if (str1.length === 0 || str2.length === 0) return 0.0;
|
||||
|
||||
// Normalizar strings
|
||||
const s1 = str1.toLowerCase().trim();
|
||||
const s2 = str2.toLowerCase().trim();
|
||||
|
||||
if (s1 === s2) return 1.0;
|
||||
|
||||
// Calcular distância de Levenshtein simplificada
|
||||
const maxLen = Math.max(s1.length, s2.length);
|
||||
let matches = 0;
|
||||
|
||||
for (let i = 0; i < Math.min(s1.length, s2.length); i++) {
|
||||
if (s1[i] === s2[i]) matches++;
|
||||
}
|
||||
|
||||
// Adicionar pontos por palavras em comum
|
||||
const words1 = s1.split(/\s+/);
|
||||
const words2 = s2.split(/\s+/);
|
||||
|
||||
let wordMatches = 0;
|
||||
words1.forEach(word1 => {
|
||||
if (words2.some(word2 => word1.includes(word2) || word2.includes(word1))) {
|
||||
wordMatches++;
|
||||
}
|
||||
});
|
||||
|
||||
const charSim = matches / maxLen;
|
||||
const wordSim = wordMatches / Math.max(words1.length, words2.length);
|
||||
|
||||
// Média ponderada: 60% caracteres, 40% palavras
|
||||
return (charSim * 0.6) + (wordSim * 0.4);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TopSappAPI;
|
||||
+306
-17
@@ -49,24 +49,10 @@ class TopSappManager {
|
||||
const result = await this.api.buscarCliente(query, location, credentials);
|
||||
|
||||
if (result.success && result.clientes) {
|
||||
// Formatar resultados para melhor apresentação
|
||||
const clientesFormatados = result.clientes.map(cliente => ({
|
||||
id: cliente.id,
|
||||
nome: cliente.nome,
|
||||
cpf: cliente.cpf,
|
||||
email: cliente.email,
|
||||
telefone: cliente.telefone,
|
||||
endereco: `${cliente.endereco}, ${cliente.numero} - ${cliente.bairro}`,
|
||||
cidade: cliente.cidade,
|
||||
status: cliente.status,
|
||||
instancia: result.instance
|
||||
}));
|
||||
|
||||
return {
|
||||
success: true,
|
||||
clientes: clientesFormatados,
|
||||
total: clientesFormatados.length,
|
||||
instancia: result.instance
|
||||
...result,
|
||||
total: result.clientes.length,
|
||||
instancia: result.instance || location
|
||||
};
|
||||
}
|
||||
|
||||
@@ -80,6 +66,134 @@ class TopSappManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter dados cadastrais completos do cliente
|
||||
*/
|
||||
async obterDadosCadastrais(idCliente, location = '') {
|
||||
if (!this.isReady()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'TopSapp não está configurado'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const credentials = this.config.getCredentialsByLocation(location);
|
||||
|
||||
console.log(`🔧 Obtendo dados cadastrais do cliente ${idCliente} (usando apenas serviços para evitar erro 400)`);
|
||||
|
||||
// Buscar dados completos do cliente e serviços em paralelo
|
||||
const [dadosCliente, servicos] = await Promise.all([
|
||||
this.api.obterDadosCompletosCliente(idCliente, location, credentials),
|
||||
this.api.obterServicosCliente(idCliente, location, credentials)
|
||||
]);
|
||||
|
||||
if (!dadosCliente.success) {
|
||||
console.log(`❌ Falha ao obter dados do cliente: ${dadosCliente.error}`);
|
||||
return dadosCliente;
|
||||
}
|
||||
|
||||
console.log(`✅ Dados do cliente obtidos com sucesso: ${dadosCliente.cliente.nome}`);
|
||||
|
||||
const resultado = {
|
||||
success: true,
|
||||
cliente: dadosCliente.cliente,
|
||||
servicos: [],
|
||||
instancia: dadosCliente.instance
|
||||
};
|
||||
|
||||
if (servicos.success && servicos.data) {
|
||||
// Normalizar dados dos serviços
|
||||
const servicosData = servicos.data.dados || servicos.data.servicos || [];
|
||||
resultado.servicos = servicosData.map(servico => ({
|
||||
// Dados básicos do serviço
|
||||
id_servico: servico.id_servico,
|
||||
id_cliente: servico.id_cliente,
|
||||
statusFormatado: this.getStatusServicoDetalhado(servico.id_status),
|
||||
planoNome: servico.descricao || servico.servico_nome || servico.nome || 'Não informado',
|
||||
|
||||
// Dados de acesso
|
||||
login: servico.usuario || servico.login || servico.pppoe_login || '',
|
||||
senha: servico.senha || servico.pppoe_senha || '',
|
||||
ip: servico.ip || servico.ip_fixo || '',
|
||||
|
||||
// Dados financeiros
|
||||
vencimento: servico.vencimento || servico.dia_vencimento || '',
|
||||
valor: servico.servico_valor || servico.valor || servico.preco || 0,
|
||||
|
||||
// Dados técnicos
|
||||
tecnologia: servico.tecnologia || 'FTTH',
|
||||
velocidade_down: servico.servico_download || servico.velocidade_down || servico.down || '',
|
||||
velocidade_up: servico.servico_upload || servico.velocidade_up || servico.up || '',
|
||||
|
||||
// Dados FTTH específicos
|
||||
base: servico.base || '',
|
||||
id_ap: servico.id_ap || '',
|
||||
pon: servico.pon || servico.porta_pon || '',
|
||||
perfil: servico.perfil || '',
|
||||
serial: servico.serial || servico.serial_onu || servico.mac || '',
|
||||
serviceport: servico.serviceport || '',
|
||||
|
||||
// Sinais ópticos
|
||||
ultimo_rx_power: servico.ultimo_rx_power || servico.sinal_rx || servico.rx || '',
|
||||
ultimo_tx_power: servico.ultimo_tx_power || servico.sinal_tx || servico.tx || '',
|
||||
data_ultimo_sinal: servico.data_ultimo_sinal || '',
|
||||
hora_ultimo_sinal: servico.hora_ultimo_sinal || '',
|
||||
|
||||
// Status de conexão
|
||||
conexao: servico.conexao || '',
|
||||
ultimo_acesso: servico.ultimo_acesso || '',
|
||||
ultimo_ip: servico.ultimo_ip || '',
|
||||
|
||||
// Dados de instalação
|
||||
instalacao_endereco: servico.instalacao_endereco || '',
|
||||
instalacao_numero: servico.instalacao_numero || '',
|
||||
instalacao_bairro: servico.instalacao_bairro || '',
|
||||
instalacao_cidade: servico.instalacao_cidade || '',
|
||||
instalacao_uf: servico.instalacao_uf || '',
|
||||
instalacao_cep: servico.instalacao_cep || '',
|
||||
instalacao_complemento: servico.instalacao_complemento || '',
|
||||
|
||||
// Coordenadas
|
||||
latitude: servico.latitude || '',
|
||||
longitude: servico.longitude || '',
|
||||
|
||||
// Datas importantes
|
||||
data_cadastro: servico.data_cadastro || '',
|
||||
data_ativacao: servico.data_ativacao || '',
|
||||
data_bloqueio: servico.data_bloqueio || '',
|
||||
data_desativacao: servico.data_desativacao || '',
|
||||
|
||||
// Status original
|
||||
id_status: servico.id_status,
|
||||
status: servico.status
|
||||
}));
|
||||
}
|
||||
|
||||
return resultado;
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter status detalhado do serviço
|
||||
*/
|
||||
getStatusServicoDetalhado(status) {
|
||||
switch (parseInt(status)) {
|
||||
case 1: return '🟢 Ativo';
|
||||
case 2: return '🔴 Bloqueado';
|
||||
case 3: return '⚫ Desativado';
|
||||
case 4: return '🟡 Suspenso';
|
||||
case 5: return '🔵 Aguardando Ativação';
|
||||
default: return `❓ Status ${status}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter dados completos do cliente
|
||||
*/
|
||||
@@ -131,6 +245,181 @@ class TopSappManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter faturas organizadas por serviço
|
||||
*/
|
||||
async obterFaturasDetalhadas(idCliente, location = '') {
|
||||
if (!this.isReady()) {
|
||||
return {
|
||||
success: false,
|
||||
error: 'TopSapp não está configurado'
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const credentials = this.config.getCredentialsByLocation(location);
|
||||
|
||||
// Obter faturas e serviços em paralelo
|
||||
const [faturas, servicos] = await Promise.all([
|
||||
this.api.obterFaturas(idCliente, location, credentials),
|
||||
this.api.obterServicosCliente(idCliente, location, credentials)
|
||||
]);
|
||||
|
||||
if (!faturas.success) {
|
||||
return faturas;
|
||||
}
|
||||
|
||||
if (!servicos.success) {
|
||||
return servicos;
|
||||
}
|
||||
|
||||
const servicosData = servicos.data.dados || servicos.data.servicos || [];
|
||||
const faturasData = faturas.data.dados || [];
|
||||
|
||||
// Organizar faturas por serviço
|
||||
const faturasPorServico = [];
|
||||
|
||||
for (const servico of servicosData) {
|
||||
const faturasDoServico = [];
|
||||
|
||||
// Buscar faturas relacionadas a este serviço
|
||||
for (const fatura of faturasData) {
|
||||
// Verificar se a fatura pertence a este serviço
|
||||
// Pode usar id_servico, periodo, ou outros critérios de associação
|
||||
if (fatura.id_servico === servico.id_servico ||
|
||||
fatura.codigo_cliente === servico.id_cliente) {
|
||||
|
||||
// Obter dados detalhados da fatura (linha digitável e PIX)
|
||||
const dadosFatura = await this.api.obterDadosFatura(
|
||||
fatura.id,
|
||||
fatura.codigo_cliente || idCliente,
|
||||
location,
|
||||
credentials
|
||||
);
|
||||
|
||||
const faturaDetalhada = {
|
||||
id: fatura.id,
|
||||
valor: fatura.valor,
|
||||
vencimento: fatura.vencimento,
|
||||
status: fatura.pago === 'SIM' ? 'Pago' : 'Pendente',
|
||||
descricao: fatura.historico || fatura.descricao || 'Mensalidade',
|
||||
periodo: this.formatarPeriodo(fatura.periodo_inicio, fatura.periodo_fim),
|
||||
id_servico: fatura.id_servico || servico.id_servico,
|
||||
linhaDigitavel: null,
|
||||
pixCopiaCola: null
|
||||
};
|
||||
|
||||
// Adicionar dados de pagamento se disponíveis
|
||||
if (dadosFatura.success && dadosFatura.data) {
|
||||
const dadosReturn = dadosFatura.data.fatura || dadosFatura.data.dados || dadosFatura.data;
|
||||
|
||||
if (dadosReturn.linha_digitavel) {
|
||||
faturaDetalhada.linhaDigitavel = dadosReturn.linha_digitavel;
|
||||
}
|
||||
|
||||
if (dadosReturn.qrcode) {
|
||||
faturaDetalhada.pixCopiaCola = dadosReturn.qrcode;
|
||||
}
|
||||
|
||||
// Outros campos possíveis: pix_copia_cola, codigo_pix, etc.
|
||||
if (dadosReturn.pix_copia_cola) {
|
||||
faturaDetalhada.pixCopiaCola = dadosReturn.pix_copia_cola;
|
||||
}
|
||||
|
||||
if (dadosReturn.codigo_pix) {
|
||||
faturaDetalhada.pixCopiaCola = dadosReturn.codigo_pix;
|
||||
}
|
||||
}
|
||||
|
||||
faturasDoServico.push(faturaDetalhada);
|
||||
}
|
||||
}
|
||||
|
||||
// Se o serviço tem faturas, adicionar à lista
|
||||
if (faturasDoServico.length > 0) {
|
||||
faturasPorServico.push({
|
||||
servico: {
|
||||
id: servico.id_servico,
|
||||
nome: servico.descricao || servico.plano_nome || 'Serviço',
|
||||
base: servico.base || 'Base',
|
||||
tecnologia: servico.tecnologia || 'FTTH',
|
||||
valor_mensal: servico.servico_valor || servico.valor || 0,
|
||||
vencimento_dia: servico.vencimento || 'Não definido',
|
||||
status: servico.status || 'Não definido'
|
||||
},
|
||||
faturas: faturasDoServico
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Se não conseguiu associar por serviço, criar uma entrada genérica
|
||||
if (faturasPorServico.length === 0 && faturasData.length > 0) {
|
||||
const faturasGenericas = [];
|
||||
|
||||
for (const fatura of faturasData) {
|
||||
const dadosFatura = await this.api.obterDadosFatura(
|
||||
fatura.id,
|
||||
fatura.codigo_cliente || idCliente,
|
||||
location,
|
||||
credentials
|
||||
);
|
||||
|
||||
const faturaDetalhada = {
|
||||
id: fatura.id,
|
||||
valor: fatura.valor,
|
||||
vencimento: fatura.vencimento,
|
||||
status: fatura.pago === 'SIM' ? 'Pago' : 'Pendente',
|
||||
descricao: fatura.historico || fatura.descricao || 'Mensalidade',
|
||||
periodo: this.formatarPeriodo(fatura.periodo_inicio, fatura.periodo_fim),
|
||||
linhaDigitavel: null,
|
||||
pixCopiaCola: null
|
||||
};
|
||||
|
||||
if (dadosFatura.success && dadosFatura.data) {
|
||||
const dadosReturn = dadosFatura.data.fatura || dadosFatura.data.dados || dadosFatura.data;
|
||||
|
||||
if (dadosReturn.linha_digitavel) {
|
||||
faturaDetalhada.linhaDigitavel = dadosReturn.linha_digitavel;
|
||||
}
|
||||
|
||||
if (dadosReturn.qrcode || dadosReturn.pix_copia_cola || dadosReturn.codigo_pix) {
|
||||
faturaDetalhada.pixCopiaCola = dadosReturn.qrcode || dadosReturn.pix_copia_cola || dadosReturn.codigo_pix;
|
||||
}
|
||||
}
|
||||
|
||||
faturasGenericas.push(faturaDetalhada);
|
||||
}
|
||||
|
||||
faturasPorServico.push({
|
||||
servico: {
|
||||
id: 'geral',
|
||||
nome: 'Faturas Gerais',
|
||||
base: 'Geral',
|
||||
tecnologia: 'Não especificado',
|
||||
valor_mensal: 0,
|
||||
vencimento_dia: 'Variável',
|
||||
status: 'Geral'
|
||||
},
|
||||
faturas: faturasGenericas
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
faturasPorServico,
|
||||
totalServicos: faturasPorServico.length,
|
||||
totalFaturas: faturasPorServico.reduce((total, item) => total + item.faturas.length, 0),
|
||||
instancia: faturas.instance
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Obter boletos do cliente
|
||||
*/
|
||||
|
||||
Reference in new issue
Block a user