revisado conexão can
This commit is contained in:
parent
1a170c10b3
commit
3b15a2473c
|
|
@ -1267,7 +1267,7 @@ namespace AgroBase.Forms.IHM
|
||||||
idx = PreencheDadosLinha(grid, Testes, idx, Corrente12V);
|
idx = PreencheDadosLinha(grid, Testes, idx, Corrente12V);
|
||||||
|
|
||||||
var SImu = Variaveis.OperacaoEmAndamento.Sensoriamento.IMU;
|
var SImu = Variaveis.OperacaoEmAndamento.Sensoriamento.IMU;
|
||||||
double Orientacao = SImu.Iniciado ? SImu.Rotacao : -1;
|
double Orientacao = SImu.Iniciado ? SImu.YawSeguro : -1;
|
||||||
idx = PreencheDadosLinha(grid, Testes, idx, Orientacao);
|
idx = PreencheDadosLinha(grid, Testes, idx, Orientacao);
|
||||||
|
|
||||||
bool GpsConectado = SerialService.DispositivosMapeados.Any(x => x.Dispositivo == Enums.T_Code.Gps);
|
bool GpsConectado = SerialService.DispositivosMapeados.Any(x => x.Dispositivo == Enums.T_Code.Gps);
|
||||||
|
|
|
||||||
|
|
@ -217,7 +217,7 @@ namespace AgroBase.Forms.IHM
|
||||||
txtDentroCorredor.Text = _Sensoriamento.Trajetoria.CorredorAtual.Dentro ? "Sim" : "Não";
|
txtDentroCorredor.Text = _Sensoriamento.Trajetoria.CorredorAtual.Dentro ? "Sim" : "Não";
|
||||||
txtMargemCorredor.Text = _Sensoriamento.Trajetoria.NaMargemDoCorredor ? "Sim" : "Não";
|
txtMargemCorredor.Text = _Sensoriamento.Trajetoria.NaMargemDoCorredor ? "Sim" : "Não";
|
||||||
txtStatusCarro.Text = _Sensoriamento.Trajetoria.StatusCarro.ToString();
|
txtStatusCarro.Text = _Sensoriamento.Trajetoria.StatusCarro.ToString();
|
||||||
txtInclinacao.Text = _Sensoriamento.IMU.InclinacaoLateral.ToString("0.00") + "/" + _Sensoriamento.IMU.InclinacaoFrontal.ToString("0.00");
|
txtInclinacao.Text = _Sensoriamento.IMU.RollSeguro.ToString("0.00") + "/" + _Sensoriamento.IMU.PitchSeguro.ToString("0.00");
|
||||||
|
|
||||||
var ModET = Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.FirstOrDefault(x => x.Modulo_ID == "ET");
|
var ModET = Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.FirstOrDefault(x => x.Modulo_ID == "ET");
|
||||||
txtET.Text = (ModET?.MovMotor?.Corrente_Motor ?? 0).ToString("0.00");
|
txtET.Text = (ModET?.MovMotor?.Corrente_Motor ?? 0).ToString("0.00");
|
||||||
|
|
|
||||||
|
|
@ -840,7 +840,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
|
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
|
||||||
|
|
||||||
RequisitarDadosModulo(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, true);
|
RequisitarDadosDescoberta();
|
||||||
|
|
||||||
DateTime Inicio = DateTime.Now;
|
DateTime Inicio = DateTime.Now;
|
||||||
bool Respondido() => Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.UltimoComandoRespondido > Inicio;
|
bool Respondido() => Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.UltimoComandoRespondido > Inicio;
|
||||||
|
|
@ -872,6 +872,11 @@ namespace AgroBase.Models.Modules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RequisitarDadosDescoberta()
|
||||||
|
{
|
||||||
|
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta)
|
public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta)
|
||||||
{
|
{
|
||||||
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
||||||
|
|
|
||||||
|
|
@ -530,7 +530,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
public void RequisitarDadosSensoresExternos()
|
public void RequisitarDadosSensoresExternos()
|
||||||
{
|
{
|
||||||
if (SensorAnguloReal != null)
|
if (SensorAnguloReal?.Inicializado == true && SensorAnguloReal?.Aferir == true)
|
||||||
{
|
{
|
||||||
Variaveis.OperacaoEmAndamento.DispSen.Dados.RequisitarDadosModulo(SensorAnguloReal.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
|
Variaveis.OperacaoEmAndamento.DispSen.Dados.RequisitarDadosModulo(SensorAnguloReal.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -458,7 +458,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
public void RequisitarDadosSensoresExternos()
|
public void RequisitarDadosSensoresExternos()
|
||||||
{
|
{
|
||||||
if (SensorTemperaturaMotor != null)
|
if (SensorTemperaturaMotor?.Inicializado == true && SensorTemperaturaMotor?.Aferir == true)
|
||||||
{
|
{
|
||||||
Variaveis.OperacaoEmAndamento.DispSen.Dados.RequisitarDadosModulo(SensorTemperaturaMotor.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
|
Variaveis.OperacaoEmAndamento.DispSen.Dados.RequisitarDadosModulo(SensorTemperaturaMotor.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2100,7 +2100,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _SenHandler);
|
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _SenHandler);
|
||||||
|
|
||||||
RequisitarDadosModulo(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, true);
|
RequisitarDadosDescoberta();
|
||||||
|
|
||||||
DateTime Inicio = DateTime.Now;
|
DateTime Inicio = DateTime.Now;
|
||||||
bool Respondido() => Variaveis.OperacaoEmAndamento.DispSen.Dados.DadosLeitura.UltimoComandoRespondido > Inicio;
|
bool Respondido() => Variaveis.OperacaoEmAndamento.DispSen.Dados.DadosLeitura.UltimoComandoRespondido > Inicio;
|
||||||
|
|
@ -2132,6 +2132,11 @@ namespace AgroBase.Models.Modules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RequisitarDadosDescoberta()
|
||||||
|
{
|
||||||
|
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta)
|
public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta)
|
||||||
{
|
{
|
||||||
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using AgroBase.Models;
|
using AgroBase.Models;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO.Ports;
|
using System.IO.Ports;
|
||||||
using static AgroBase.Models.Enums;
|
using static AgroBase.Models.Enums;
|
||||||
|
|
||||||
|
|
@ -148,6 +149,9 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
public string ChaveInterna { get; set; }
|
public string ChaveInterna { get; set; }
|
||||||
public bool IsExtended => IdRx > 0x7FF || IdTx > 0x7FF;
|
public bool IsExtended => IdRx > 0x7FF || IdTx > 0x7FF;
|
||||||
|
|
||||||
|
public bool ContabilizarTimeoutHealth { get; set; } = true;
|
||||||
|
public bool MensagemDescoberta { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CanCommandFreqModel<T>
|
public class CanCommandFreqModel<T>
|
||||||
|
|
@ -262,7 +266,7 @@ namespace AgroBase.Services
|
||||||
bool PortaIsCan(SerialPort Porta = null);
|
bool PortaIsCan(SerialPort Porta = null);
|
||||||
void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler);
|
void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler);
|
||||||
bool Inicializar(int BitRate = 250);
|
bool Inicializar(int BitRate = 250);
|
||||||
void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true);
|
void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
||||||
void Fechar();
|
void Fechar();
|
||||||
|
|
||||||
string _portName { get; set; }
|
string _portName { get; set; }
|
||||||
|
|
@ -278,6 +282,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
int ErrosCriticos { get; set; }
|
int ErrosCriticos { get; set; }
|
||||||
double UltimoErroCritico { get; set; }
|
double UltimoErroCritico { get; set; }
|
||||||
|
string UltimoErroCriticoMotivo { get; }
|
||||||
|
|
||||||
bool FilaLiberada { get; }
|
bool FilaLiberada { get; }
|
||||||
|
|
||||||
|
|
@ -286,6 +291,11 @@ namespace AgroBase.Services
|
||||||
long UltimoRxUnixMs { get; }
|
long UltimoRxUnixMs { get; }
|
||||||
long UltimoErroCriticoUnixMs { get; }
|
long UltimoErroCriticoUnixMs { get; }
|
||||||
|
|
||||||
|
Dictionary<string, long> TimeoutsRespostaPorId { get; }
|
||||||
|
Dictionary<string, long> TimeoutsRespostaPorDispositivo { get; }
|
||||||
|
Dictionary<string, long> TimeoutsRespostaPorChave { get; }
|
||||||
|
Dictionary<string, int> PendentesAguardandoRespostaPorId { get; }
|
||||||
|
|
||||||
long TxTotal { get; }
|
long TxTotal { get; }
|
||||||
long RxTotal { get; }
|
long RxTotal { get; }
|
||||||
long TxFalhaTotal { get; }
|
long TxFalhaTotal { get; }
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ namespace AgroBase.Services
|
||||||
private readonly ConcurrentDictionary<uint, bool> _roteadoresRegistrados = new ConcurrentDictionary<uint, bool>();
|
private readonly ConcurrentDictionary<uint, bool> _roteadoresRegistrados = new ConcurrentDictionary<uint, bool>();
|
||||||
private uint? _ultimoIdEnviado = null;
|
private uint? _ultimoIdEnviado = null;
|
||||||
|
|
||||||
private List<CanMessage> Mensagens = new List<CanMessage>();
|
private List<CanMessage> MensagensPendentes = new List<CanMessage>();
|
||||||
private readonly object _LockMensagens = new object();
|
private readonly object _LockMensagens = new object();
|
||||||
|
|
||||||
public bool FilaLiberada
|
public bool FilaLiberada
|
||||||
|
|
@ -42,9 +42,9 @@ namespace AgroBase.Services
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
bool liberado =
|
bool liberado =
|
||||||
Mensagens == null ||
|
MensagensPendentes == null ||
|
||||||
Mensagens.Count == 0 ||
|
MensagensPendentes.Count == 0 ||
|
||||||
!Mensagens.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
|
!MensagensPendentes.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
|
||||||
return liberado;
|
return liberado;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -58,7 +58,37 @@ namespace AgroBase.Services
|
||||||
public double FreqRx{ get; set; } = 0;
|
public double FreqRx{ get; set; } = 0;
|
||||||
public int ErrosCriticos { get; set; } = 0;
|
public int ErrosCriticos { get; set; } = 0;
|
||||||
public double UltimoErroCritico { get; set; } = 0;
|
public double UltimoErroCritico { get; set; } = 0;
|
||||||
|
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||||
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
||||||
|
private DateTime _portaIndisponivelDesde = DateTime.MinValue;
|
||||||
|
private DateTime _reaberturaSolicitadaDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorId = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorDispositivo = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorChave = new ConcurrentDictionary<string, long>();
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorId =>
|
||||||
|
_timeoutsRespostaPorId.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorDispositivo =>
|
||||||
|
_timeoutsRespostaPorDispositivo.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorChave =>
|
||||||
|
_timeoutsRespostaPorChave.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, int> PendentesAguardandoRespostaPorId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes
|
||||||
|
.Where(x => x.ComResposta && x.Enviado && !x.Respondido)
|
||||||
|
.GroupBy(x => IdKey(x.IdRx))
|
||||||
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
|
|
@ -94,7 +124,7 @@ namespace AgroBase.Services
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
return Mensagens?.Count ?? 0;
|
return MensagensPendentes?.Count ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -103,7 +133,7 @@ namespace AgroBase.Services
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
return Mensagens?.Count(x => !x.Enviado) ?? 0;
|
return MensagensPendentes?.Count(x => !x.Enviado) ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,14 +142,14 @@ namespace AgroBase.Services
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
return Mensagens?.Count(x => x.ComResposta && x.Enviado && !x.Respondido) ?? 0;
|
return MensagensPendentes?.Count(x => x.ComResposta && x.Enviado && !x.Respondido) ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public int FramesRxNaFila => FramesRecebidos?.Count ?? 0;
|
public int FramesRxNaFila => FramesRecebidos?.Count ?? 0;
|
||||||
|
|
||||||
private bool DebugMode = false;
|
private bool DebugMode = false;
|
||||||
private void MostrarLog(string message, uint id)
|
private void MostrarLog(string message, uint id = 0)
|
||||||
{
|
{
|
||||||
if (DebugMode) // && new List<uint>() { 0x18100140 }.Contains(id)
|
if (DebugMode) // && new List<uint>() { 0x18100140 }.Contains(id)
|
||||||
{
|
{
|
||||||
|
|
@ -492,7 +522,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
mensagem = Mensagens.FirstOrDefault(x =>
|
mensagem = MensagensPendentes.FirstOrDefault(x =>
|
||||||
{
|
{
|
||||||
if (!x.Enviado) return false;
|
if (!x.Enviado) return false;
|
||||||
if (x.Respondido) return false;
|
if (x.Respondido) return false;
|
||||||
|
|
@ -567,7 +597,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true)
|
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (_PortaCAN?.IsOpen != true) return;
|
if (_PortaCAN?.IsOpen != true) return;
|
||||||
|
|
||||||
|
|
@ -604,10 +634,10 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
mensagemNaFila = Mensagens.Any(x => (!x.Enviado || (x.ComResposta && x.Enviado && !x.Respondido)) && x.IdTx == idTx && x.IdRx == idRx && x.DataTx.SequenceEqual(dados.ToArray()));
|
mensagemNaFila = MensagensPendentes.Any(x => (!x.Enviado || (x.ComResposta && x.Enviado && !x.Respondido)) && x.IdTx == idTx && x.IdRx == idRx && x.DataTx.SequenceEqual(dados.ToArray()));
|
||||||
if (!mensagemNaFila)
|
if (!mensagemNaFila)
|
||||||
{
|
{
|
||||||
Mensagens.Add(novaMensagem);
|
MensagensPendentes.Add(novaMensagem);
|
||||||
MostrarLog($"Mensagem adicionada na fila. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
MostrarLog($"Mensagem adicionada na fila. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
||||||
//if (dados[1] == Variaveis.ID_Num_sLRA)
|
//if (dados[1] == Variaveis.ID_Num_sLRA)
|
||||||
//{
|
//{
|
||||||
|
|
@ -627,7 +657,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
mensagensPendentes = Mensagens
|
mensagensPendentes = MensagensPendentes
|
||||||
.Where(x => !x.Enviado)
|
.Where(x => !x.Enviado)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
@ -806,7 +836,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
var timeouts = Mensagens
|
var timeouts = MensagensPendentes
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.ComResposta &&
|
x.ComResposta &&
|
||||||
x.Enviado &&
|
x.Enviado &&
|
||||||
|
|
@ -816,11 +846,11 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
foreach (var item in timeouts)
|
foreach (var item in timeouts)
|
||||||
{
|
{
|
||||||
TimeoutsRespostaTotal++;
|
RegistrarTimeoutResposta(item);
|
||||||
Mensagens.Remove(item);
|
MensagensPendentes.Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
var remover = Mensagens
|
var remover = MensagensPendentes
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
(!x.ComResposta && x.Enviado) ||
|
(!x.ComResposta && x.Enviado) ||
|
||||||
(x.ComResposta && x.Respondido) ||
|
(x.ComResposta && x.Respondido) ||
|
||||||
|
|
@ -828,7 +858,7 @@ namespace AgroBase.Services
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
foreach (var item in remover)
|
foreach (var item in remover)
|
||||||
Mensagens.Remove(item);
|
MensagensPendentes.Remove(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -841,17 +871,6 @@ namespace AgroBase.Services
|
||||||
var portaExiste = !string.IsNullOrEmpty(_portName) &&
|
var portaExiste = !string.IsNullOrEmpty(_portName) &&
|
||||||
portas.Contains(_portName, StringComparer.OrdinalIgnoreCase);
|
portas.Contains(_portName, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (!portaExiste || _PortaCAN == null || !_PortaCAN.IsOpen)
|
|
||||||
{
|
|
||||||
IsConnected = false;
|
|
||||||
RegistrarErroCritico("porta_serial_indisponivel");
|
|
||||||
|
|
||||||
Task.Run(() => SerialService.RealizarVarreduraPortasUSB());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool existeDemanda = ExisteDemandaCan();
|
|
||||||
|
|
||||||
double ageTx = _ultimoTx == DateTime.MinValue
|
double ageTx = _ultimoTx == DateTime.MinValue
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoTx).TotalSeconds;
|
: (agora - _ultimoTx).TotalSeconds;
|
||||||
|
|
@ -860,36 +879,76 @@ namespace AgroBase.Services
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoRx).TotalSeconds;
|
: (agora - _ultimoRx).TotalSeconds;
|
||||||
|
|
||||||
if (IsConnected && _precisaReabrir)
|
bool portaOk = portaExiste && _PortaCAN != null && _PortaCAN.IsOpen;
|
||||||
|
|
||||||
|
if (!portaOk)
|
||||||
{
|
{
|
||||||
_precisaReabrir = false;
|
if (_portaIndisponivelDesde == DateTime.MinValue)
|
||||||
|
_portaIndisponivelDesde = agora;
|
||||||
|
|
||||||
RegistrarErroCritico("serial_solicitou_reabertura");
|
double tempoSuspeito = (agora - _portaIndisponivelDesde).TotalSeconds;
|
||||||
|
bool semTrafegoRecente = ageTx > 2.0 && ageRx > 2.0;
|
||||||
|
|
||||||
MostrarLog("Reabrindo conexão CAN por evento da serial...", 0);
|
if (tempoSuspeito >= 1.0 && semTrafegoRecente)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
RegistrarErroCritico("porta_serial_indisponivel_confirmada");
|
||||||
|
|
||||||
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
Task.Run(() => SerialService.RealizarVarreduraPortasUSB());
|
||||||
T_Code.Can,
|
}
|
||||||
StatusModulo.Alerta,
|
|
||||||
50,
|
|
||||||
"Reabrindo conexão CAN por evento da serial..."
|
|
||||||
);
|
|
||||||
|
|
||||||
ReabrirPortaSegura();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsConnected && existeDemanda && ageTx > 2.0 && ageRx > 2.0)
|
_portaIndisponivelDesde = DateTime.MinValue;
|
||||||
{
|
|
||||||
RegistrarErroCritico("sem_trafego_com_demanda");
|
|
||||||
|
|
||||||
MostrarLog("Muito tempo sem tráfego CAN Serial com demanda pendente, reiniciando conexão...", 0);
|
bool existeDemanda = ExisteDemandaCan();
|
||||||
|
|
||||||
|
if (IsConnected && _precisaReabrir)
|
||||||
|
{
|
||||||
|
if (_reaberturaSolicitadaDesde == DateTime.MinValue)
|
||||||
|
_reaberturaSolicitadaDesde = agora;
|
||||||
|
|
||||||
|
double tempoSolicitado = (agora - _reaberturaSolicitadaDesde).TotalSeconds;
|
||||||
|
|
||||||
|
// Evento explícito de serial é mais forte que watchdog de silêncio,
|
||||||
|
// mas ainda dá uma pequena janela para evitar falso positivo espirrado.
|
||||||
|
if (tempoSolicitado >= 0.5)
|
||||||
|
{
|
||||||
|
_precisaReabrir = false;
|
||||||
|
_reaberturaSolicitadaDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
RegistrarErroCritico("serial_solicitou_reabertura");
|
||||||
|
|
||||||
|
MostrarLog("Reabrindo conexão CAN por evento da serial...", 0);
|
||||||
|
|
||||||
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
||||||
|
T_Code.Can,
|
||||||
|
StatusModulo.Alerta,
|
||||||
|
50,
|
||||||
|
"Reabrindo conexão CAN por evento da serial..."
|
||||||
|
);
|
||||||
|
|
||||||
|
ReabrirPortaSegura();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_reaberturaSolicitadaDesde = DateTime.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsConnected && existeDemanda && ageTx > 10.0 && ageRx > 10.0)
|
||||||
|
{
|
||||||
|
RegistrarErroCritico("sem_trafego_prolongado_com_demanda");
|
||||||
|
|
||||||
|
MostrarLog("Sem tráfego prolongado no CAN Serial com demanda, reiniciando conexão...", 0);
|
||||||
|
|
||||||
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
||||||
T_Code.Can,
|
T_Code.Can,
|
||||||
StatusModulo.Alerta,
|
StatusModulo.Alerta,
|
||||||
50,
|
50,
|
||||||
"Muito tempo sem tráfego CAN Serial com demanda pendente, reiniciando conexão..."
|
"Sem tráfego prolongado no CAN Serial com demanda, reiniciando conexão..."
|
||||||
);
|
);
|
||||||
|
|
||||||
ReabrirPortaSegura();
|
ReabrirPortaSegura();
|
||||||
|
|
@ -911,7 +970,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
Mensagens.Clear();
|
MensagensPendentes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
SerialCANBuffer.Clear();
|
SerialCANBuffer.Clear();
|
||||||
|
|
@ -1001,7 +1060,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
Mensagens.Clear();
|
MensagensPendentes.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (_lock)
|
lock (_lock)
|
||||||
|
|
@ -1088,7 +1147,11 @@ namespace AgroBase.Services
|
||||||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||||
UltimoErroCriticoUnixMs = NowUnixMs();
|
UltimoErroCriticoUnixMs = NowUnixMs();
|
||||||
|
|
||||||
MostrarLog($"Erro crítico: {motivo}", 0);
|
UltimoErroCriticoMotivo = motivo;
|
||||||
|
|
||||||
|
MostrarLog($"Erro crítico: {motivo}");
|
||||||
|
var saude = Variaveis.OperacaoEmAndamento.Sensoriamento.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Can);
|
||||||
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Can, saude?.status ?? StatusModulo.Desconectado, saude?.saude ?? 0, $"Erro crítico: {motivo}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegistrarLatenciaResposta(double latenciaMs)
|
private void RegistrarLatenciaResposta(double latenciaMs)
|
||||||
|
|
@ -1139,11 +1202,45 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
return Mensagens.Any(x =>
|
return MensagensPendentes.Any(x =>
|
||||||
!x.Enviado ||
|
!x.Enviado ||
|
||||||
(x.ComResposta && x.Enviado && !x.Respondido));
|
(x.ComResposta && x.Enviado && !x.Respondido));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string IdKey(uint id)
|
||||||
|
{
|
||||||
|
return $"0x{id:X}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TimeoutChave(CanMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
return "desconhecido";
|
||||||
|
|
||||||
|
return $"{msg.Dispositivo}|tx:{IdKey(msg.IdTx)}|rx:{IdKey(msg.IdRx)}|fc_rx:0x{msg.funcCodeRx:X2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncrementarContador(ConcurrentDictionary<string, long> dict, string key)
|
||||||
|
{
|
||||||
|
dict.AddOrUpdate(key, 1, (_, atual) => atual + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegistrarTimeoutResposta(CanMessage msg)
|
||||||
|
{
|
||||||
|
TimeoutsRespostaTotal++;
|
||||||
|
|
||||||
|
string idRx = IdKey(msg.IdRx);
|
||||||
|
string dispositivo = msg.Dispositivo.ToString();
|
||||||
|
string chave = TimeoutChave(msg);
|
||||||
|
|
||||||
|
IncrementarContador(_timeoutsRespostaPorId, idRx);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorDispositivo, dispositivo);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorChave, chave);
|
||||||
|
|
||||||
|
MostrarLog($"Timeout resposta CAN | {chave}", msg.IdRx);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +55,36 @@ namespace AgroBase.Services
|
||||||
public double FreqRx { get; set; } = 0;
|
public double FreqRx { get; set; } = 0;
|
||||||
public int ErrosCriticos { get; set; } = 0;
|
public int ErrosCriticos { get; set; } = 0;
|
||||||
public double UltimoErroCritico { get; set; } = 0;
|
public double UltimoErroCritico { get; set; } = 0;
|
||||||
|
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||||
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
||||||
|
private DateTime _connectorIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorId = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorDispositivo = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorChave = new ConcurrentDictionary<string, long>();
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorId =>
|
||||||
|
_timeoutsRespostaPorId.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorDispositivo =>
|
||||||
|
_timeoutsRespostaPorDispositivo.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorChave =>
|
||||||
|
_timeoutsRespostaPorChave.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, int> PendentesAguardandoRespostaPorId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes
|
||||||
|
.Where(x => x.ComResposta && x.Enviado && !x.Respondido)
|
||||||
|
.GroupBy(x => IdKey(x.IdRx))
|
||||||
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
|
|
@ -115,7 +144,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
public int FramesRxNaFila => FramesRecebidos.Count;
|
public int FramesRxNaFila => FramesRecebidos.Count;
|
||||||
|
|
||||||
private void MostrarLog(string message, uint id)
|
private void MostrarLog(string message, uint id = 0)
|
||||||
{
|
{
|
||||||
if (DebugMode/* && new List<uint>() { 0x01 }.Contains(id)*/)
|
if (DebugMode/* && new List<uint>() { 0x01 }.Contains(id)*/)
|
||||||
{
|
{
|
||||||
|
|
@ -379,7 +408,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true)
|
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (!Connector.IsOpen) return;
|
if (!Connector.IsOpen) return;
|
||||||
|
|
||||||
|
|
@ -630,7 +659,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
foreach (var item in timeouts)
|
foreach (var item in timeouts)
|
||||||
{
|
{
|
||||||
TimeoutsRespostaTotal++;
|
RegistrarTimeoutResposta(item);
|
||||||
MensagensPendentes.Remove(item);
|
MensagensPendentes.Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -652,16 +681,6 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
bool connectorOk = Connector != null && Connector.IsOpen;
|
bool connectorOk = Connector != null && Connector.IsOpen;
|
||||||
|
|
||||||
if (!connectorOk)
|
|
||||||
{
|
|
||||||
IsConnected = false;
|
|
||||||
_iniciado = false;
|
|
||||||
RegistrarErroCritico("connector_waveshare_fechado");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool existeDemanda = ExisteDemandaCan();
|
|
||||||
|
|
||||||
double ageTx = _ultimoTx == DateTime.MinValue
|
double ageTx = _ultimoTx == DateTime.MinValue
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoTx).TotalSeconds;
|
: (agora - _ultimoTx).TotalSeconds;
|
||||||
|
|
@ -670,17 +689,41 @@ namespace AgroBase.Services
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoRx).TotalSeconds;
|
: (agora - _ultimoRx).TotalSeconds;
|
||||||
|
|
||||||
if (IsConnected && existeDemanda && ageTx > 2.0 && ageRx > 2.0)
|
if (!connectorOk)
|
||||||
{
|
{
|
||||||
RegistrarErroCritico("sem_trafego_com_demanda");
|
if (_connectorIndisponivelDesde == DateTime.MinValue)
|
||||||
|
_connectorIndisponivelDesde = agora;
|
||||||
|
|
||||||
MostrarLog("Muito tempo sem tráfego CAN Waveshare com demanda pendente, reiniciando conexão...", 0);
|
double tempoSuspeito = (agora - _connectorIndisponivelDesde).TotalSeconds;
|
||||||
|
bool semTrafegoRecente = ageTx > 2.0 && ageRx > 2.0;
|
||||||
|
|
||||||
|
if (tempoSuspeito >= 1.0 && semTrafegoRecente)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
_iniciado = false;
|
||||||
|
RegistrarErroCritico("connector_waveshare_fechado_confirmado");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_connectorIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
bool existeDemanda = ExisteDemandaCan();
|
||||||
|
|
||||||
|
// Se existe demanda, mas TX/RX estão sem tráfego por bastante tempo,
|
||||||
|
// aí sim parece travamento real do serviço/conector.
|
||||||
|
if (IsConnected && existeDemanda && ageTx > 10.0 && ageRx > 10.0)
|
||||||
|
{
|
||||||
|
RegistrarErroCritico("sem_trafego_prolongado_com_demanda");
|
||||||
|
|
||||||
|
MostrarLog("Sem tráfego prolongado no CAN Waveshare com demanda, reiniciando conexão...", 0);
|
||||||
|
|
||||||
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(
|
||||||
T_Code.Can,
|
T_Code.Can,
|
||||||
StatusModulo.Alerta,
|
StatusModulo.Alerta,
|
||||||
50,
|
50,
|
||||||
"Muito tempo sem tráfego CAN Waveshare com demanda pendente, reiniciando conexão..."
|
"Sem tráfego prolongado no CAN Waveshare com demanda, reiniciando conexão..."
|
||||||
);
|
);
|
||||||
|
|
||||||
ReabrirConexao();
|
ReabrirConexao();
|
||||||
|
|
@ -809,7 +852,11 @@ namespace AgroBase.Services
|
||||||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||||
UltimoErroCriticoUnixMs = NowUnixMs();
|
UltimoErroCriticoUnixMs = NowUnixMs();
|
||||||
|
|
||||||
MostrarLog($"Erro crítico: {motivo}", 0);
|
UltimoErroCriticoMotivo = motivo;
|
||||||
|
|
||||||
|
MostrarLog($"Erro crítico: {motivo}");
|
||||||
|
var saude = Variaveis.OperacaoEmAndamento.Sensoriamento.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Can);
|
||||||
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Can, saude?.status ?? StatusModulo.Desconectado, saude?.saude ?? 0, $"Erro crítico: {motivo}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegistrarLatenciaResposta(double latenciaMs)
|
private void RegistrarLatenciaResposta(double latenciaMs)
|
||||||
|
|
@ -866,5 +913,38 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string IdKey(uint id)
|
||||||
|
{
|
||||||
|
return $"0x{id:X}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TimeoutChave(CanMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
return "desconhecido";
|
||||||
|
|
||||||
|
return $"{msg.Dispositivo}|tx:{IdKey(msg.IdTx)}|rx:{IdKey(msg.IdRx)}|fc_rx:0x{msg.funcCodeRx:X2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncrementarContador(ConcurrentDictionary<string, long> dict, string key)
|
||||||
|
{
|
||||||
|
dict.AddOrUpdate(key, 1, (_, atual) => atual + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegistrarTimeoutResposta(CanMessage msg)
|
||||||
|
{
|
||||||
|
TimeoutsRespostaTotal++;
|
||||||
|
|
||||||
|
string idRx = IdKey(msg.IdRx);
|
||||||
|
string dispositivo = msg.Dispositivo.ToString();
|
||||||
|
string chave = TimeoutChave(msg);
|
||||||
|
|
||||||
|
IncrementarContador(_timeoutsRespostaPorId, idRx);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorDispositivo, dispositivo);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorChave, chave);
|
||||||
|
|
||||||
|
MostrarLog($"Timeout resposta CAN | {chave}", msg.IdRx);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,36 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
public int ErrosCriticos { get; set; } = 0;
|
public int ErrosCriticos { get; set; } = 0;
|
||||||
public double UltimoErroCritico { get; set; } = 0;
|
public double UltimoErroCritico { get; set; } = 0;
|
||||||
|
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||||
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
private DateTime _ultimoErroCritico = DateTime.MinValue;
|
||||||
|
private DateTime _socketIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorId = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorDispositivo = new ConcurrentDictionary<string, long>();
|
||||||
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorChave = new ConcurrentDictionary<string, long>();
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorId =>
|
||||||
|
_timeoutsRespostaPorId.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorDispositivo =>
|
||||||
|
_timeoutsRespostaPorDispositivo.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorChave =>
|
||||||
|
_timeoutsRespostaPorChave.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
|
||||||
|
public Dictionary<string, int> PendentesAguardandoRespostaPorId
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes
|
||||||
|
.Where(x => x.ComResposta && x.Enviado && !x.Respondido)
|
||||||
|
.GroupBy(x => IdKey(x.IdRx))
|
||||||
|
.ToDictionary(g => g.Key, g => g.Count());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
|
|
@ -107,6 +136,8 @@ namespace AgroBase.Services
|
||||||
private long _txTotalUltimaJanela = 0;
|
private long _txTotalUltimaJanela = 0;
|
||||||
private long _rxTotalUltimaJanela = 0;
|
private long _rxTotalUltimaJanela = 0;
|
||||||
private DateTime _ultimaJanelaFreq = DateTime.UtcNow;
|
private DateTime _ultimaJanelaFreq = DateTime.UtcNow;
|
||||||
|
private DateTime _inicioJanelaLatencia = DateTime.UtcNow;
|
||||||
|
private double _latenciaMaxJanelaMs = 0;
|
||||||
|
|
||||||
private static long NowUnixMs()
|
private static long NowUnixMs()
|
||||||
{
|
{
|
||||||
|
|
@ -285,7 +316,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true)
|
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (!IsConnected) return;
|
if (!IsConnected) return;
|
||||||
|
|
||||||
|
|
@ -319,7 +350,9 @@ namespace AgroBase.Services
|
||||||
funcCodeRx = funcCodeRx,
|
funcCodeRx = funcCodeRx,
|
||||||
DataTx = dados.ToArray(),
|
DataTx = dados.ToArray(),
|
||||||
ComResposta = get,
|
ComResposta = get,
|
||||||
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray())
|
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray()),
|
||||||
|
ContabilizarTimeoutHealth = contabilizarTimeoutHealth,
|
||||||
|
MensagemDescoberta = mensagemDescoberta
|
||||||
};
|
};
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
|
|
@ -728,7 +761,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
foreach (var item in timeouts)
|
foreach (var item in timeouts)
|
||||||
{
|
{
|
||||||
TimeoutsRespostaTotal++;
|
RegistrarTimeoutResposta(item);
|
||||||
MensagensPendentes.Remove(item);
|
MensagensPendentes.Remove(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -752,15 +785,6 @@ namespace AgroBase.Services
|
||||||
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
|
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
|
||||||
: (_udpClient != null);
|
: (_udpClient != null);
|
||||||
|
|
||||||
if (!socketOk)
|
|
||||||
{
|
|
||||||
IsConnected = false;
|
|
||||||
RegistrarErroCritico("socket_indisponivel");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool existeDemanda = ExisteDemandaCan();
|
|
||||||
|
|
||||||
double ageTx = _ultimoTx == DateTime.MinValue
|
double ageTx = _ultimoTx == DateTime.MinValue
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoTx).TotalSeconds;
|
: (agora - _ultimoTx).TotalSeconds;
|
||||||
|
|
@ -769,6 +793,28 @@ namespace AgroBase.Services
|
||||||
? 999999
|
? 999999
|
||||||
: (agora - _ultimoRx).TotalSeconds;
|
: (agora - _ultimoRx).TotalSeconds;
|
||||||
|
|
||||||
|
if (!socketOk)
|
||||||
|
{
|
||||||
|
if (_socketIndisponivelDesde == DateTime.MinValue)
|
||||||
|
_socketIndisponivelDesde = agora;
|
||||||
|
|
||||||
|
double tempoSuspeito = (agora - _socketIndisponivelDesde).TotalSeconds;
|
||||||
|
|
||||||
|
bool semTrafegoRecente = ageTx > 2.0 && ageRx > 2.0;
|
||||||
|
|
||||||
|
if (tempoSuspeito >= 1.0 && semTrafegoRecente)
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
RegistrarErroCritico("socket_indisponivel_confirmado");
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_socketIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
|
bool existeDemanda = ExisteDemandaCan();
|
||||||
|
|
||||||
// Se existe demanda, mas houve TX recente e não houve RX,
|
// Se existe demanda, mas houve TX recente e não houve RX,
|
||||||
// isso parece mais "dispositivo CAN não respondeu" do que "socket morreu".
|
// isso parece mais "dispositivo CAN não respondeu" do que "socket morreu".
|
||||||
// Deixa o timeout da fila e o health tratarem.
|
// Deixa o timeout da fila e o health tratarem.
|
||||||
|
|
@ -912,7 +958,11 @@ namespace AgroBase.Services
|
||||||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||||
UltimoErroCriticoUnixMs = NowUnixMs();
|
UltimoErroCriticoUnixMs = NowUnixMs();
|
||||||
|
|
||||||
|
UltimoErroCriticoMotivo = motivo;
|
||||||
|
|
||||||
MostrarLog($"Erro crítico: {motivo}");
|
MostrarLog($"Erro crítico: {motivo}");
|
||||||
|
var saude = Variaveis.OperacaoEmAndamento.Sensoriamento.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Can);
|
||||||
|
Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Can, saude?.status ?? StatusModulo.Desconectado, saude?.saude ?? 0, $"Erro crítico: {motivo}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RegistrarLatenciaResposta(double latenciaMs)
|
private void RegistrarLatenciaResposta(double latenciaMs)
|
||||||
|
|
@ -926,8 +976,21 @@ namespace AgroBase.Services
|
||||||
? latenciaMs
|
? latenciaMs
|
||||||
: (alpha * latenciaMs) + ((1.0 - alpha) * LatenciaMediaMs);
|
: (alpha * latenciaMs) + ((1.0 - alpha) * LatenciaMediaMs);
|
||||||
|
|
||||||
if (latenciaMs > LatenciaMaxMs)
|
var now = DateTime.UtcNow;
|
||||||
LatenciaMaxMs = latenciaMs;
|
|
||||||
|
if ((now - _inicioJanelaLatencia).TotalSeconds >= 10)
|
||||||
|
{
|
||||||
|
LatenciaMaxMs = _latenciaMaxJanelaMs;
|
||||||
|
_latenciaMaxJanelaMs = latenciaMs;
|
||||||
|
_inicioJanelaLatencia = now;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (latenciaMs > _latenciaMaxJanelaMs)
|
||||||
|
_latenciaMaxJanelaMs = latenciaMs;
|
||||||
|
|
||||||
|
LatenciaMaxMs = _latenciaMaxJanelaMs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AtualizarJanelaFrequencia()
|
private void AtualizarJanelaFrequencia()
|
||||||
|
|
@ -969,5 +1032,59 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string IdKey(uint id)
|
||||||
|
{
|
||||||
|
return $"0x{id:X}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string TimeoutChave(CanMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
return "desconhecido";
|
||||||
|
|
||||||
|
string chave = $"{msg.Dispositivo}|tx:{IdKey(msg.IdTx)}|rx:{IdKey(msg.IdRx)}|fc_rx:0x{msg.funcCodeRx:X2}";
|
||||||
|
|
||||||
|
if (new List<T_Code>() { T_Code.Atu, T_Code.Sen }.Contains(msg.Dispositivo) && msg.DataTx != null && msg.DataTx.Length > 1)
|
||||||
|
{
|
||||||
|
chave += $"|id_num:{msg.DataTx[1]}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg.Dispositivo != T_Code.Bat && msg.DataTx != null && msg.DataTx.Length > 0)
|
||||||
|
{
|
||||||
|
chave += $"|fc_tx:0x{msg.DataTx[0]:X2}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return chave;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void IncrementarContador(ConcurrentDictionary<string, long> dict, string key)
|
||||||
|
{
|
||||||
|
dict.AddOrUpdate(key, 1, (_, atual) => atual + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegistrarTimeoutResposta(CanMessage msg)
|
||||||
|
{
|
||||||
|
if (msg == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!msg.ContabilizarTimeoutHealth)
|
||||||
|
{
|
||||||
|
MostrarLog($"Timeout ignorado pelo health | {TimeoutChave(msg)}", msg.IdRx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeoutsRespostaTotal++;
|
||||||
|
|
||||||
|
string idRx = IdKey(msg.IdRx);
|
||||||
|
string dispositivo = msg.Dispositivo.ToString();
|
||||||
|
string chave = TimeoutChave(msg);
|
||||||
|
|
||||||
|
IncrementarContador(_timeoutsRespostaPorId, idRx);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorDispositivo, dispositivo);
|
||||||
|
IncrementarContador(_timeoutsRespostaPorChave, chave);
|
||||||
|
|
||||||
|
MostrarLog($"Timeout resposta CAN | {chave}", msg.IdRx);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ namespace AgroBase.Services
|
||||||
CanManager.CanService.RegistrarHandler(CanManager.ConverterIdCan(MakeResponseID(f)), _BatHandler);
|
CanManager.CanService.RegistrarHandler(CanManager.ConverterIdCan(MakeResponseID(f)), _BatHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
RequisitarDado(BatFuncCode.SOC);
|
EnviarComandoDescoberta();
|
||||||
|
|
||||||
DateTime Inicio = DateTime.Now;
|
DateTime Inicio = DateTime.Now;
|
||||||
bool Respondido() => DadosLeitura.UltimoComandoRecebido > Inicio;
|
bool Respondido() => DadosLeitura.UltimoComandoRecebido > Inicio;
|
||||||
|
|
@ -94,6 +94,12 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
#region Helpers de protocolo (ID, DataID, etc.)
|
#region Helpers de protocolo (ID, DataID, etc.)
|
||||||
|
|
||||||
|
private static void EnviarComandoDescoberta()
|
||||||
|
{
|
||||||
|
BatFuncCode dado = BatFuncCode.SOC;
|
||||||
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, MakeRequestID(dado), MakeResponseID(dado), (byte)dado, (byte)dado, payload: new byte[8], get: true, contabilizarTimeoutHealth: false, mensagemDescoberta: true);
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnviarComando(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCode, byte[] payload = null)
|
private static void EnviarComando(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCode, byte[] payload = null)
|
||||||
{
|
{
|
||||||
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, idTx, idRx, funcCode, funcCode, payload, get: true);
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, idTx, idRx, funcCode, funcCode, payload, get: true);
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ namespace AgroBase.Comum
|
||||||
VariaveisOperacao.RegistrarLogDispositivo(Logs, Dispositivo, ".json", 100);
|
VariaveisOperacao.RegistrarLogDispositivo(Logs, Dispositivo, ".json", 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdicionarMensagemFila(int ID_Num, CanMessagePosicaoDados PosicaoTx, CanMessagePosicaoDados PosicaoRx, byte[] dados = null, bool get = true)
|
public void AdicionarMensagemFila(int ID_Num, CanMessagePosicaoDados PosicaoTx, CanMessagePosicaoDados PosicaoRx, byte[] dados = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
List<byte> payload = new List<byte>()
|
List<byte> payload = new List<byte>()
|
||||||
{
|
{
|
||||||
|
|
@ -74,7 +74,7 @@ namespace AgroBase.Comum
|
||||||
{
|
{
|
||||||
payload.AddRange(dados);
|
payload.AddRange(dados);
|
||||||
}
|
}
|
||||||
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { Dados._EnderecoCAN_Tx }, new byte[] { Dados._EnderecoCAN_Rx }, (byte)PosicaoTx, (byte)PosicaoRx, payload.ToArray(), get);
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { Dados._EnderecoCAN_Tx }, new byte[] { Dados._EnderecoCAN_Rx }, (byte)PosicaoTx, (byte)PosicaoRx, payload.ToArray(), get, contabilizarTimeoutHealth, mensagemDescoberta);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Control InstanciarDispositivo(int index)
|
public Control InstanciarDispositivo(int index)
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ namespace AgroBase.Services
|
||||||
Control InstanciarDispositivo(int index);
|
Control InstanciarDispositivo(int index);
|
||||||
void AtualizaIndex(int index);
|
void AtualizaIndex(int index);
|
||||||
void SalvarParametros(DispositivoBaseModel dados);
|
void SalvarParametros(DispositivoBaseModel dados);
|
||||||
void AdicionarMensagemFila(int ID_Num, CanMessagePosicaoDados PosicaoTx, CanMessagePosicaoDados PosicaoRx, byte[] dados = null, bool get = true);
|
void AdicionarMensagemFila(int ID_Num, CanMessagePosicaoDados PosicaoTx, CanMessagePosicaoDados PosicaoRx, byte[] dados = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
||||||
void EnviarProtocolosConfiguracao(bool Conectar = true);
|
void EnviarProtocolosConfiguracao(bool Conectar = true);
|
||||||
void CarregarParametrosModulos();
|
void CarregarParametrosModulos();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
CanManager.CanService.RegistrarHandler(Modulo.DirMotor._EnderecoCAN_Rx, _MksHandler);
|
CanManager.CanService.RegistrarHandler(Modulo.DirMotor._EnderecoCAN_Rx, _MksHandler);
|
||||||
|
|
||||||
RequisitarDado(Modulo.DirMotor._EnderecoCAN_Tx, Modulo.DirMotor._EnderecoCAN_Rx, MksFuncCode.Versao);
|
EnviarComandoDescoberta(Modulo.DirMotor._EnderecoCAN_Tx, Modulo.DirMotor._EnderecoCAN_Rx);
|
||||||
|
|
||||||
DateTime Inicio = DateTime.Now;
|
DateTime Inicio = DateTime.Now;
|
||||||
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.DirMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.DirMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
||||||
|
|
@ -124,6 +124,11 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
#region COMANDOS
|
#region COMANDOS
|
||||||
|
|
||||||
|
private static void EnviarComandoDescoberta(byte idTx, byte idRx)
|
||||||
|
{
|
||||||
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, (byte)MksFuncCode.Versao, (byte)MksFuncCode.Versao, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnviarComando(T_Code Dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true)
|
private static void EnviarComando(T_Code Dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true)
|
||||||
{
|
{
|
||||||
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, funcCode, funcCode, payload, get);
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, funcCode, funcCode, payload, get);
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
CanManager.CanService.RegistrarHandler(Modulo.MovMotor._EnderecoCAN_Rx, _OidHandler);
|
CanManager.CanService.RegistrarHandler(Modulo.MovMotor._EnderecoCAN_Rx, _OidHandler);
|
||||||
|
|
||||||
RequisitarDado(Modulo.MovMotor._EnderecoCAN_Tx, Modulo.MovMotor._EnderecoCAN_Rx, OidParametros.CodErro);
|
EnviarComandoDescoberta(Modulo.MovMotor._EnderecoCAN_Tx, Modulo.MovMotor._EnderecoCAN_Rx);
|
||||||
|
|
||||||
DateTime Inicio = DateTime.Now;
|
DateTime Inicio = DateTime.Now;
|
||||||
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.MovMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.MovMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
||||||
|
|
@ -107,6 +107,11 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
#region COMANDOS
|
#region COMANDOS
|
||||||
|
|
||||||
|
private static void EnviarComandoDescoberta(byte idTx, byte idRx)
|
||||||
|
{
|
||||||
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, (byte)OidParametros.CodErro, (byte)OidParametros.CodErro, null, true, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
private static void EnviarComando(T_Code Dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true)
|
private static void EnviarComando(T_Code Dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true)
|
||||||
{
|
{
|
||||||
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, funcCode, funcCode, payload, get);
|
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, new byte[] { idTx }, new byte[] { idRx }, funcCode, funcCode, payload, get);
|
||||||
|
|
|
||||||
|
|
@ -872,6 +872,12 @@ namespace AgroBase.Services.Operadores
|
||||||
("timeouts_resposta_total", DadosCan.TimeoutsRespostaTotal),
|
("timeouts_resposta_total", DadosCan.TimeoutsRespostaTotal),
|
||||||
("reconexoes_total", DadosCan.ReconexoesTotal),
|
("reconexoes_total", DadosCan.ReconexoesTotal),
|
||||||
|
|
||||||
|
// Timeouts por origem
|
||||||
|
("timeouts_por_id_total", DadosCan.TimeoutsRespostaPorId),
|
||||||
|
("timeouts_por_dispositivo_total", DadosCan.TimeoutsRespostaPorDispositivo),
|
||||||
|
("timeouts_por_chave_total", DadosCan.TimeoutsRespostaPorChave),
|
||||||
|
("pendentes_resp_por_id", DadosCan.PendentesAguardandoRespostaPorId),
|
||||||
|
|
||||||
// Filas
|
// Filas
|
||||||
("pendentes_total", DadosCan.PendentesTotal),
|
("pendentes_total", DadosCan.PendentesTotal),
|
||||||
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
||||||
|
|
@ -886,7 +892,8 @@ namespace AgroBase.Services.Operadores
|
||||||
("erros_criticos", DadosCan.ErrosCriticos),
|
("erros_criticos", DadosCan.ErrosCriticos),
|
||||||
("last_tx", DadosCan.UltimoTx),
|
("last_tx", DadosCan.UltimoTx),
|
||||||
("last_rx", DadosCan.UltimoRx),
|
("last_rx", DadosCan.UltimoRx),
|
||||||
("last_ec", DadosCan.UltimoErroCritico)
|
("last_ec", DadosCan.UltimoErroCritico),
|
||||||
|
("last_ec_motivo", DadosCan.UltimoErroCriticoMotivo)
|
||||||
);
|
);
|
||||||
|
|
||||||
double MinOrDefault(List<double> items)
|
double MinOrDefault(List<double> items)
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,35 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
self._last_tx_fail = None
|
self._last_tx_fail = None
|
||||||
self._last_rx_invalid = None
|
self._last_rx_invalid = None
|
||||||
self._last_sample_ts = None
|
self._last_sample_ts = None
|
||||||
|
self._last_reconexoes = None
|
||||||
|
|
||||||
|
self._last_timeouts_por_id = {}
|
||||||
|
self._last_timeouts_por_dispositivo = {}
|
||||||
|
self._last_timeouts_por_chave = {}
|
||||||
|
|
||||||
|
self._saude_suavizada = None
|
||||||
|
self._status_atual = StatusModulo.OPERANTE
|
||||||
|
self._ciclos_alerta = 0
|
||||||
|
self._ciclos_ok = 0
|
||||||
|
|
||||||
|
def _delta_dict(self, atual, anterior):
|
||||||
|
if not isinstance(atual, dict):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
anterior = anterior or {}
|
||||||
|
deltas = {}
|
||||||
|
|
||||||
|
for k, v in atual.items():
|
||||||
|
try:
|
||||||
|
atual_v = int(v or 0)
|
||||||
|
anterior_v = int(anterior.get(k, 0) or 0)
|
||||||
|
d = max(0, atual_v - anterior_v)
|
||||||
|
if d > 0:
|
||||||
|
deltas[k] = d
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
return deltas
|
||||||
|
|
||||||
def _delta(self, atual, anterior):
|
def _delta(self, atual, anterior):
|
||||||
if anterior is None:
|
if anterior is None:
|
||||||
|
|
@ -32,6 +61,11 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
last_rx_ms = float(m.get("last_rx_ms", 0.0) or 0.0)
|
last_rx_ms = float(m.get("last_rx_ms", 0.0) or 0.0)
|
||||||
last_ec_ms = float(m.get("last_ec_ms", 0.0) or 0.0)
|
last_ec_ms = float(m.get("last_ec_ms", 0.0) or 0.0)
|
||||||
|
|
||||||
|
last_ec_motivo = m.get("last_ec_motivo", "") or ""
|
||||||
|
reconexoes_total = int(m.get("reconexoes_total", 0) or 0)
|
||||||
|
delta_reconexoes = self._delta(reconexoes_total, self._last_reconexoes)
|
||||||
|
self._last_reconexoes = reconexoes_total
|
||||||
|
|
||||||
age_tx = ((now_ms - last_tx_ms) / 1000.0) if last_tx_ms > 0 else 999999.0
|
age_tx = ((now_ms - last_tx_ms) / 1000.0) if last_tx_ms > 0 else 999999.0
|
||||||
age_rx = ((now_ms - last_rx_ms) / 1000.0) if last_rx_ms > 0 else 999999.0
|
age_rx = ((now_ms - last_rx_ms) / 1000.0) if last_rx_ms > 0 else 999999.0
|
||||||
age_ec = ((now_ms - last_ec_ms) / 1000.0) if last_ec_ms > 0 else 999999.0
|
age_ec = ((now_ms - last_ec_ms) / 1000.0) if last_ec_ms > 0 else 999999.0
|
||||||
|
|
@ -45,6 +79,10 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
frames_rx_fila = int(m.get("frames_rx_fila", 0) or 0)
|
frames_rx_fila = int(m.get("frames_rx_fila", 0) or 0)
|
||||||
|
|
||||||
timeouts_total = int(m.get("timeouts_resposta_total", 0) or 0)
|
timeouts_total = int(m.get("timeouts_resposta_total", 0) or 0)
|
||||||
|
timeouts_por_id_total = m.get("timeouts_por_id_total", {}) or {}
|
||||||
|
timeouts_por_dispositivo_total = m.get("timeouts_por_dispositivo_total", {}) or {}
|
||||||
|
timeouts_por_chave_total = m.get("timeouts_por_chave_total", {}) or {}
|
||||||
|
pendentes_resp_por_id = m.get("pendentes_resp_por_id", {}) or {}
|
||||||
tx_falha_total = int(m.get("tx_falha_total", 0) or 0)
|
tx_falha_total = int(m.get("tx_falha_total", 0) or 0)
|
||||||
rx_invalid_total = int(m.get("rx_invalido_total", 0) or 0)
|
rx_invalid_total = int(m.get("rx_invalido_total", 0) or 0)
|
||||||
|
|
||||||
|
|
@ -57,6 +95,25 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
delta_tx_fail = self._delta(tx_falha_total, self._last_tx_fail)
|
delta_tx_fail = self._delta(tx_falha_total, self._last_tx_fail)
|
||||||
delta_rx_invalid = self._delta(rx_invalid_total, self._last_rx_invalid)
|
delta_rx_invalid = self._delta(rx_invalid_total, self._last_rx_invalid)
|
||||||
|
|
||||||
|
delta_timeouts_por_id = self._delta_dict(
|
||||||
|
timeouts_por_id_total,
|
||||||
|
self._last_timeouts_por_id
|
||||||
|
)
|
||||||
|
|
||||||
|
delta_timeouts_por_dispositivo = self._delta_dict(
|
||||||
|
timeouts_por_dispositivo_total,
|
||||||
|
self._last_timeouts_por_dispositivo
|
||||||
|
)
|
||||||
|
|
||||||
|
delta_timeouts_por_chave = self._delta_dict(
|
||||||
|
timeouts_por_chave_total,
|
||||||
|
self._last_timeouts_por_chave
|
||||||
|
)
|
||||||
|
|
||||||
|
self._last_timeouts_por_id = dict(timeouts_por_id_total)
|
||||||
|
self._last_timeouts_por_dispositivo = dict(timeouts_por_dispositivo_total)
|
||||||
|
self._last_timeouts_por_chave = dict(timeouts_por_chave_total)
|
||||||
|
|
||||||
self._last_timeouts = timeouts_total
|
self._last_timeouts = timeouts_total
|
||||||
self._last_tx_fail = tx_falha_total
|
self._last_tx_fail = tx_falha_total
|
||||||
self._last_rx_invalid = rx_invalid_total
|
self._last_rx_invalid = rx_invalid_total
|
||||||
|
|
@ -96,6 +153,16 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
saude -= p
|
saude -= p
|
||||||
motivos.append(f"{delta_timeouts} timeout(s) de resposta no ciclo (-{p}%)")
|
motivos.append(f"{delta_timeouts} timeout(s) de resposta no ciclo (-{p}%)")
|
||||||
|
|
||||||
|
if delta_timeouts_por_id:
|
||||||
|
top_ids = sorted(
|
||||||
|
delta_timeouts_por_id.items(),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)[:3]
|
||||||
|
|
||||||
|
texto_ids = ", ".join([f"{k}: +{v}" for k, v in top_ids])
|
||||||
|
motivos.append(f"timeouts por ID no ciclo: {texto_ids}")
|
||||||
|
|
||||||
if delta_tx_fail > 0:
|
if delta_tx_fail > 0:
|
||||||
p = min(30, delta_tx_fail * 10)
|
p = min(30, delta_tx_fail * 10)
|
||||||
saude -= p
|
saude -= p
|
||||||
|
|
@ -120,13 +187,15 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
# Latência.
|
# Latência.
|
||||||
if lat_media > 150:
|
if lat_media > 150:
|
||||||
p = min(25, int((lat_media - 150) / 20))
|
p = min(25, int((lat_media - 150) / 20))
|
||||||
saude -= p
|
if p > 0:
|
||||||
motivos.append(f"latência média alta: {lat_media:.0f}ms (-{p}%)")
|
saude -= p
|
||||||
|
motivos.append(f"latência média alta: {lat_media:.0f}ms (-{p}%)")
|
||||||
|
|
||||||
if lat_max > 500:
|
if lat_max > 500:
|
||||||
p = min(15, int((lat_max - 500) / 100) + 5)
|
p = min(15, int((lat_max - 500) / 100) + 5)
|
||||||
saude -= p
|
if p > 0:
|
||||||
motivos.append(f"pico de latência: {lat_max:.0f}ms (-{p}%)")
|
saude -= p
|
||||||
|
motivos.append(f"pico de latência: {lat_max:.0f}ms (-{p}%)")
|
||||||
|
|
||||||
# Erro crítico recente, com decaimento.
|
# Erro crítico recente, com decaimento.
|
||||||
if erros_criticos > 0 and age_ec <= 600:
|
if erros_criticos > 0 and age_ec <= 600:
|
||||||
|
|
@ -134,25 +203,69 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
p = int(round(min(erros_criticos * 10, 40) * fator))
|
p = int(round(min(erros_criticos * 10, 40) * fator))
|
||||||
if p > 0:
|
if p > 0:
|
||||||
saude -= p
|
saude -= p
|
||||||
motivos.append(f"{erros_criticos} erro(s) crítico(s), último há {age_ec:.0f}s (-{p}%)")
|
motivo_ec = f"{erros_criticos} erro(s) crítico(s)"
|
||||||
|
if last_ec_motivo:
|
||||||
|
motivo_ec += f" ({last_ec_motivo})"
|
||||||
|
motivo_ec += f", último há {age_ec:.0f}s (-{p}%)"
|
||||||
|
motivos.append(motivo_ec)
|
||||||
|
|
||||||
saude = max(0, min(100, saude))
|
saude = max(0, min(100, saude))
|
||||||
|
|
||||||
status = StatusModulo.OPERANTE
|
if delta_reconexoes > 0:
|
||||||
|
motivos.append(f"{delta_reconexoes} reconexão(ões) CAN no ciclo")
|
||||||
|
|
||||||
if not conectado:
|
saude_instantanea = max(0, min(100, saude))
|
||||||
status = StatusModulo.DESCONECTADO
|
if self._saude_suavizada is None:
|
||||||
elif saude <= 20:
|
self._saude_suavizada = saude_instantanea
|
||||||
status = StatusModulo.FALHA
|
else:
|
||||||
elif saude < 80:
|
alpha = 0.35
|
||||||
status = StatusModulo.ALERTA
|
self._saude_suavizada = (
|
||||||
|
alpha * saude_instantanea +
|
||||||
|
(1.0 - alpha) * self._saude_suavizada
|
||||||
|
)
|
||||||
|
|
||||||
|
saude = int(round(self._saude_suavizada))
|
||||||
|
status = self._atualizar_status_suavizado(conectado, saude)
|
||||||
|
|
||||||
|
saude_individual = []
|
||||||
|
for id_can, delta in sorted(delta_timeouts_por_id.items(), key=lambda x: x[1], reverse=True)[:10]:
|
||||||
|
total_id = int(timeouts_por_id_total.get(id_can, 0) or 0)
|
||||||
|
pend_id = int(pendentes_resp_por_id.get(id_can, 0) or 0)
|
||||||
|
|
||||||
|
p_id = min(80, delta * 25)
|
||||||
|
saude_id = max(0, 100 - p_id)
|
||||||
|
|
||||||
|
if saude_id <= 20:
|
||||||
|
status_id = StatusModulo.FALHA
|
||||||
|
elif saude_id < 80:
|
||||||
|
status_id = StatusModulo.ALERTA
|
||||||
|
else:
|
||||||
|
status_id = StatusModulo.OPERANTE
|
||||||
|
|
||||||
|
motivos_id = [
|
||||||
|
f"{delta} timeout(s) recente(s)",
|
||||||
|
f"{total_id} timeout(s) acumulado(s)"
|
||||||
|
]
|
||||||
|
|
||||||
|
if pend_id > 0:
|
||||||
|
motivos_id.append(f"{pend_id} resposta(s) pendente(s)")
|
||||||
|
|
||||||
|
saude_individual.append({
|
||||||
|
"id": id_can,
|
||||||
|
"label": id_can,
|
||||||
|
"status": status_id.value,
|
||||||
|
"saude": saude_id,
|
||||||
|
"motivos": motivos_id,
|
||||||
|
"condicoes_operacionais": [],
|
||||||
|
})
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"conectado": conectado,
|
"conectado": conectado,
|
||||||
"status": status.value,
|
"status": status.value,
|
||||||
"saude": saude,
|
"saude": saude,
|
||||||
|
"saude_instantanea": saude_instantanea,
|
||||||
"motivos": motivos,
|
"motivos": motivos,
|
||||||
"saude_individual": [],
|
"saude_individual": saude_individual,
|
||||||
"condicoes_operacionais": [],
|
"condicoes_operacionais": [],
|
||||||
"detalhes": {
|
"detalhes": {
|
||||||
"age_rx_s": age_rx,
|
"age_rx_s": age_rx,
|
||||||
|
|
@ -165,10 +278,21 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
"pendentes_aguardando_resposta": pend_resp,
|
"pendentes_aguardando_resposta": pend_resp,
|
||||||
"frames_rx_fila": frames_rx_fila,
|
"frames_rx_fila": frames_rx_fila,
|
||||||
"timeouts_resposta_total": timeouts_total,
|
"timeouts_resposta_total": timeouts_total,
|
||||||
|
"timeouts_por_id_total": timeouts_por_id_total,
|
||||||
|
"timeouts_por_dispositivo_total": timeouts_por_dispositivo_total,
|
||||||
|
"timeouts_por_chave_total": timeouts_por_chave_total,
|
||||||
|
"delta_timeouts_por_id": delta_timeouts_por_id,
|
||||||
|
"delta_timeouts_por_dispositivo": delta_timeouts_por_dispositivo,
|
||||||
|
"delta_timeouts_por_chave": delta_timeouts_por_chave,
|
||||||
|
"pendentes_resp_por_id": pendentes_resp_por_id,
|
||||||
"tx_falha_total": tx_falha_total,
|
"tx_falha_total": tx_falha_total,
|
||||||
"rx_invalido_total": rx_invalid_total,
|
"rx_invalido_total": rx_invalid_total,
|
||||||
"latencia_media_ms": lat_media,
|
"latencia_media_ms": lat_media,
|
||||||
"latencia_max_ms": lat_max,
|
"latencia_max_ms": lat_max,
|
||||||
|
"erros_criticos": erros_criticos,
|
||||||
|
"last_ec_motivo": last_ec_motivo,
|
||||||
|
"reconexoes_total": reconexoes_total,
|
||||||
|
"delta_reconexoes": delta_reconexoes,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,4 +302,38 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")
|
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")
|
||||||
|
|
||||||
|
def _atualizar_status_suavizado(self, conectado, saude):
|
||||||
|
if not conectado:
|
||||||
|
self._status_atual = StatusModulo.DESCONECTADO
|
||||||
|
self._ciclos_alerta = 0
|
||||||
|
self._ciclos_ok = 0
|
||||||
|
return self._status_atual
|
||||||
|
|
||||||
|
if saude <= 20:
|
||||||
|
self._status_atual = StatusModulo.FALHA
|
||||||
|
self._ciclos_alerta = 0
|
||||||
|
self._ciclos_ok = 0
|
||||||
|
return self._status_atual
|
||||||
|
|
||||||
|
if saude < 80:
|
||||||
|
self._ciclos_alerta += 1
|
||||||
|
self._ciclos_ok = 0
|
||||||
|
elif saude >= 85:
|
||||||
|
self._ciclos_ok += 1
|
||||||
|
self._ciclos_alerta = 0
|
||||||
|
else:
|
||||||
|
# Zona neutra: não troca status.
|
||||||
|
self._ciclos_alerta = 0
|
||||||
|
self._ciclos_ok = 0
|
||||||
|
|
||||||
|
# Entra em alerta só depois de 2 ciclos ruins.
|
||||||
|
if self._ciclos_alerta >= 2:
|
||||||
|
self._status_atual = StatusModulo.ALERTA
|
||||||
|
|
||||||
|
# Volta para operante só depois de 3 ciclos bons.
|
||||||
|
if self._ciclos_ok >= 3:
|
||||||
|
self._status_atual = StatusModulo.OPERANTE
|
||||||
|
|
||||||
|
return self._status_atual
|
||||||
Loading…
Reference in New Issue