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);
|
||||
|
||||
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);
|
||||
|
||||
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";
|
||||
txtMargemCorredor.Text = _Sensoriamento.Trajetoria.NaMargemDoCorredor ? "Sim" : "Não";
|
||||
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");
|
||||
txtET.Text = (ModET?.MovMotor?.Corrente_Motor ?? 0).ToString("0.00");
|
||||
|
|
|
|||
|
|
@ -840,7 +840,7 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
|
||||
|
||||
RequisitarDadosModulo(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, true);
|
||||
RequisitarDadosDescoberta();
|
||||
|
||||
DateTime Inicio = DateTime.Now;
|
||||
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)
|
||||
{
|
||||
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
||||
|
|
|
|||
|
|
@ -530,7 +530,7 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,7 +458,7 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2100,7 +2100,7 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _SenHandler);
|
||||
|
||||
RequisitarDadosModulo(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, true);
|
||||
RequisitarDadosDescoberta();
|
||||
|
||||
DateTime Inicio = DateTime.Now;
|
||||
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)
|
||||
{
|
||||
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using AgroBase.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO.Ports;
|
||||
using static AgroBase.Models.Enums;
|
||||
|
||||
|
|
@ -148,6 +149,9 @@ namespace AgroBase.Services
|
|||
|
||||
public string ChaveInterna { get; set; }
|
||||
public bool IsExtended => IdRx > 0x7FF || IdTx > 0x7FF;
|
||||
|
||||
public bool ContabilizarTimeoutHealth { get; set; } = true;
|
||||
public bool MensagemDescoberta { get; set; } = false;
|
||||
}
|
||||
|
||||
public class CanCommandFreqModel<T>
|
||||
|
|
@ -262,7 +266,7 @@ namespace AgroBase.Services
|
|||
bool PortaIsCan(SerialPort Porta = null);
|
||||
void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler);
|
||||
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();
|
||||
|
||||
string _portName { get; set; }
|
||||
|
|
@ -278,6 +282,7 @@ namespace AgroBase.Services
|
|||
|
||||
int ErrosCriticos { get; set; }
|
||||
double UltimoErroCritico { get; set; }
|
||||
string UltimoErroCriticoMotivo { get; }
|
||||
|
||||
bool FilaLiberada { get; }
|
||||
|
||||
|
|
@ -286,6 +291,11 @@ namespace AgroBase.Services
|
|||
long UltimoRxUnixMs { 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 RxTotal { get; }
|
||||
long TxFalhaTotal { get; }
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ namespace AgroBase.Services
|
|||
private readonly ConcurrentDictionary<uint, bool> _roteadoresRegistrados = new ConcurrentDictionary<uint, bool>();
|
||||
private uint? _ultimoIdEnviado = null;
|
||||
|
||||
private List<CanMessage> Mensagens = new List<CanMessage>();
|
||||
private List<CanMessage> MensagensPendentes = new List<CanMessage>();
|
||||
private readonly object _LockMensagens = new object();
|
||||
|
||||
public bool FilaLiberada
|
||||
|
|
@ -42,9 +42,9 @@ namespace AgroBase.Services
|
|||
lock (_LockMensagens)
|
||||
{
|
||||
bool liberado =
|
||||
Mensagens == null ||
|
||||
Mensagens.Count == 0 ||
|
||||
!Mensagens.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
|
||||
MensagensPendentes == null ||
|
||||
MensagensPendentes.Count == 0 ||
|
||||
!MensagensPendentes.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
|
||||
return liberado;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,37 @@ namespace AgroBase.Services
|
|||
public double FreqRx{ get; set; } = 0;
|
||||
public int ErrosCriticos { get; set; } = 0;
|
||||
public double UltimoErroCritico { get; set; } = 0;
|
||||
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||
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 UltimoRxUnixMs { get; private set; } = 0;
|
||||
|
|
@ -94,7 +124,7 @@ namespace AgroBase.Services
|
|||
get
|
||||
{
|
||||
lock (_LockMensagens)
|
||||
return Mensagens?.Count ?? 0;
|
||||
return MensagensPendentes?.Count ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +133,7 @@ namespace AgroBase.Services
|
|||
get
|
||||
{
|
||||
lock (_LockMensagens)
|
||||
return Mensagens?.Count(x => !x.Enviado) ?? 0;
|
||||
return MensagensPendentes?.Count(x => !x.Enviado) ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -112,14 +142,14 @@ namespace AgroBase.Services
|
|||
get
|
||||
{
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
|
|
@ -492,7 +522,7 @@ namespace AgroBase.Services
|
|||
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
mensagem = Mensagens.FirstOrDefault(x =>
|
||||
mensagem = MensagensPendentes.FirstOrDefault(x =>
|
||||
{
|
||||
if (!x.Enviado) 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;
|
||||
|
||||
|
|
@ -604,10 +634,10 @@ namespace AgroBase.Services
|
|||
|
||||
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)
|
||||
{
|
||||
Mensagens.Add(novaMensagem);
|
||||
MensagensPendentes.Add(novaMensagem);
|
||||
MostrarLog($"Mensagem adicionada na fila. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
||||
//if (dados[1] == Variaveis.ID_Num_sLRA)
|
||||
//{
|
||||
|
|
@ -627,7 +657,7 @@ namespace AgroBase.Services
|
|||
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
mensagensPendentes = Mensagens
|
||||
mensagensPendentes = MensagensPendentes
|
||||
.Where(x => !x.Enviado)
|
||||
.ToList();
|
||||
}
|
||||
|
|
@ -806,7 +836,7 @@ namespace AgroBase.Services
|
|||
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
var timeouts = Mensagens
|
||||
var timeouts = MensagensPendentes
|
||||
.Where(x =>
|
||||
x.ComResposta &&
|
||||
x.Enviado &&
|
||||
|
|
@ -816,11 +846,11 @@ namespace AgroBase.Services
|
|||
|
||||
foreach (var item in timeouts)
|
||||
{
|
||||
TimeoutsRespostaTotal++;
|
||||
Mensagens.Remove(item);
|
||||
RegistrarTimeoutResposta(item);
|
||||
MensagensPendentes.Remove(item);
|
||||
}
|
||||
|
||||
var remover = Mensagens
|
||||
var remover = MensagensPendentes
|
||||
.Where(x =>
|
||||
(!x.ComResposta && x.Enviado) ||
|
||||
(x.ComResposta && x.Respondido) ||
|
||||
|
|
@ -828,7 +858,7 @@ namespace AgroBase.Services
|
|||
.ToList();
|
||||
|
||||
foreach (var item in remover)
|
||||
Mensagens.Remove(item);
|
||||
MensagensPendentes.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -841,17 +871,6 @@ namespace AgroBase.Services
|
|||
var portaExiste = !string.IsNullOrEmpty(_portName) &&
|
||||
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
|
||||
? 999999
|
||||
: (agora - _ultimoTx).TotalSeconds;
|
||||
|
|
@ -860,36 +879,76 @@ namespace AgroBase.Services
|
|||
? 999999
|
||||
: (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(
|
||||
T_Code.Can,
|
||||
StatusModulo.Alerta,
|
||||
50,
|
||||
"Reabrindo conexão CAN por evento da serial..."
|
||||
);
|
||||
Task.Run(() => SerialService.RealizarVarreduraPortasUSB());
|
||||
}
|
||||
|
||||
ReabrirPortaSegura();
|
||||
return;
|
||||
}
|
||||
|
||||
if (IsConnected && existeDemanda && ageTx > 2.0 && ageRx > 2.0)
|
||||
{
|
||||
RegistrarErroCritico("sem_trafego_com_demanda");
|
||||
_portaIndisponivelDesde = DateTime.MinValue;
|
||||
|
||||
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(
|
||||
T_Code.Can,
|
||||
StatusModulo.Alerta,
|
||||
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();
|
||||
|
|
@ -911,7 +970,7 @@ namespace AgroBase.Services
|
|||
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
Mensagens.Clear();
|
||||
MensagensPendentes.Clear();
|
||||
}
|
||||
|
||||
SerialCANBuffer.Clear();
|
||||
|
|
@ -1001,7 +1060,7 @@ namespace AgroBase.Services
|
|||
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
Mensagens.Clear();
|
||||
MensagensPendentes.Clear();
|
||||
}
|
||||
|
||||
lock (_lock)
|
||||
|
|
@ -1088,7 +1147,11 @@ namespace AgroBase.Services
|
|||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||
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)
|
||||
|
|
@ -1139,11 +1202,45 @@ namespace AgroBase.Services
|
|||
{
|
||||
lock (_LockMensagens)
|
||||
{
|
||||
return Mensagens.Any(x =>
|
||||
return MensagensPendentes.Any(x =>
|
||||
!x.Enviado ||
|
||||
(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 int ErrosCriticos { get; set; } = 0;
|
||||
public double UltimoErroCritico { get; set; } = 0;
|
||||
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||
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 UltimoRxUnixMs { get; private set; } = 0;
|
||||
|
|
@ -115,7 +144,7 @@ namespace AgroBase.Services
|
|||
|
||||
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)*/)
|
||||
{
|
||||
|
|
@ -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;
|
||||
|
||||
|
|
@ -630,7 +659,7 @@ namespace AgroBase.Services
|
|||
|
||||
foreach (var item in timeouts)
|
||||
{
|
||||
TimeoutsRespostaTotal++;
|
||||
RegistrarTimeoutResposta(item);
|
||||
MensagensPendentes.Remove(item);
|
||||
}
|
||||
|
||||
|
|
@ -652,16 +681,6 @@ namespace AgroBase.Services
|
|||
|
||||
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
|
||||
? 999999
|
||||
: (agora - _ultimoTx).TotalSeconds;
|
||||
|
|
@ -670,17 +689,41 @@ namespace AgroBase.Services
|
|||
? 999999
|
||||
: (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(
|
||||
T_Code.Can,
|
||||
StatusModulo.Alerta,
|
||||
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();
|
||||
|
|
@ -809,7 +852,11 @@ namespace AgroBase.Services
|
|||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||
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)
|
||||
|
|
@ -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 double UltimoErroCritico { get; set; } = 0;
|
||||
public string UltimoErroCriticoMotivo { get; private set; } = "";
|
||||
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 UltimoRxUnixMs { get; private set; } = 0;
|
||||
|
|
@ -107,6 +136,8 @@ namespace AgroBase.Services
|
|||
private long _txTotalUltimaJanela = 0;
|
||||
private long _rxTotalUltimaJanela = 0;
|
||||
private DateTime _ultimaJanelaFreq = DateTime.UtcNow;
|
||||
private DateTime _inicioJanelaLatencia = DateTime.UtcNow;
|
||||
private double _latenciaMaxJanelaMs = 0;
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -319,7 +350,9 @@ namespace AgroBase.Services
|
|||
funcCodeRx = funcCodeRx,
|
||||
DataTx = dados.ToArray(),
|
||||
ComResposta = get,
|
||||
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray())
|
||||
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray()),
|
||||
ContabilizarTimeoutHealth = contabilizarTimeoutHealth,
|
||||
MensagemDescoberta = mensagemDescoberta
|
||||
};
|
||||
|
||||
lock (_LockMensagens)
|
||||
|
|
@ -728,7 +761,7 @@ namespace AgroBase.Services
|
|||
|
||||
foreach (var item in timeouts)
|
||||
{
|
||||
TimeoutsRespostaTotal++;
|
||||
RegistrarTimeoutResposta(item);
|
||||
MensagensPendentes.Remove(item);
|
||||
}
|
||||
|
||||
|
|
@ -752,15 +785,6 @@ namespace AgroBase.Services
|
|||
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
|
||||
: (_udpClient != null);
|
||||
|
||||
if (!socketOk)
|
||||
{
|
||||
IsConnected = false;
|
||||
RegistrarErroCritico("socket_indisponivel");
|
||||
return;
|
||||
}
|
||||
|
||||
bool existeDemanda = ExisteDemandaCan();
|
||||
|
||||
double ageTx = _ultimoTx == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora - _ultimoTx).TotalSeconds;
|
||||
|
|
@ -769,6 +793,28 @@ namespace AgroBase.Services
|
|||
? 999999
|
||||
: (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,
|
||||
// isso parece mais "dispositivo CAN não respondeu" do que "socket morreu".
|
||||
// Deixa o timeout da fila e o health tratarem.
|
||||
|
|
@ -912,7 +958,11 @@ namespace AgroBase.Services
|
|||
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
|
||||
UltimoErroCriticoUnixMs = NowUnixMs();
|
||||
|
||||
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)
|
||||
|
|
@ -926,8 +976,21 @@ namespace AgroBase.Services
|
|||
? latenciaMs
|
||||
: (alpha * latenciaMs) + ((1.0 - alpha) * LatenciaMediaMs);
|
||||
|
||||
if (latenciaMs > LatenciaMaxMs)
|
||||
LatenciaMaxMs = latenciaMs;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
if ((now - _inicioJanelaLatencia).TotalSeconds >= 10)
|
||||
{
|
||||
LatenciaMaxMs = _latenciaMaxJanelaMs;
|
||||
_latenciaMaxJanelaMs = latenciaMs;
|
||||
_inicioJanelaLatencia = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (latenciaMs > _latenciaMaxJanelaMs)
|
||||
_latenciaMaxJanelaMs = latenciaMs;
|
||||
|
||||
LatenciaMaxMs = _latenciaMaxJanelaMs;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
RequisitarDado(BatFuncCode.SOC);
|
||||
EnviarComandoDescoberta();
|
||||
|
||||
DateTime Inicio = DateTime.Now;
|
||||
bool Respondido() => DadosLeitura.UltimoComandoRecebido > Inicio;
|
||||
|
|
@ -94,6 +94,12 @@ namespace AgroBase.Services
|
|||
|
||||
#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)
|
||||
{
|
||||
CanManager.CanService.AdicionarMensagemNaFila(Dispositivo, idTx, idRx, funcCode, funcCode, payload, get: true);
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ namespace AgroBase.Comum
|
|||
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>()
|
||||
{
|
||||
|
|
@ -74,7 +74,7 @@ namespace AgroBase.Comum
|
|||
{
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ namespace AgroBase.Services
|
|||
Control InstanciarDispositivo(int index);
|
||||
void AtualizaIndex(int index);
|
||||
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 CarregarParametrosModulos();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ namespace AgroBase.Services
|
|||
{
|
||||
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;
|
||||
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.DirMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
||||
|
|
@ -124,6 +124,11 @@ namespace AgroBase.Services
|
|||
|
||||
#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)
|
||||
{
|
||||
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);
|
||||
|
||||
RequisitarDado(Modulo.MovMotor._EnderecoCAN_Tx, Modulo.MovMotor._EnderecoCAN_Rx, OidParametros.CodErro);
|
||||
EnviarComandoDescoberta(Modulo.MovMotor._EnderecoCAN_Tx, Modulo.MovMotor._EnderecoCAN_Rx);
|
||||
|
||||
DateTime Inicio = DateTime.Now;
|
||||
bool Respondido() => DadosLeitura.Any(x => x.EnderecoRx == Modulo.MovMotor._EnderecoCAN_Rx && x.UltimoComandoRecebido > Inicio);
|
||||
|
|
@ -107,6 +107,11 @@ namespace AgroBase.Services
|
|||
|
||||
#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)
|
||||
{
|
||||
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),
|
||||
("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
|
||||
("pendentes_total", DadosCan.PendentesTotal),
|
||||
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
||||
|
|
@ -886,7 +892,8 @@ namespace AgroBase.Services.Operadores
|
|||
("erros_criticos", DadosCan.ErrosCriticos),
|
||||
("last_tx", DadosCan.UltimoTx),
|
||||
("last_rx", DadosCan.UltimoRx),
|
||||
("last_ec", DadosCan.UltimoErroCritico)
|
||||
("last_ec", DadosCan.UltimoErroCritico),
|
||||
("last_ec_motivo", DadosCan.UltimoErroCriticoMotivo)
|
||||
);
|
||||
|
||||
double MinOrDefault(List<double> items)
|
||||
|
|
|
|||
|
|
@ -13,6 +13,35 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
self._last_tx_fail = None
|
||||
self._last_rx_invalid = 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):
|
||||
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_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_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
|
||||
|
|
@ -45,6 +79,10 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
frames_rx_fila = int(m.get("frames_rx_fila", 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)
|
||||
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_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_tx_fail = tx_falha_total
|
||||
self._last_rx_invalid = rx_invalid_total
|
||||
|
|
@ -96,6 +153,16 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
saude -= 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:
|
||||
p = min(30, delta_tx_fail * 10)
|
||||
saude -= p
|
||||
|
|
@ -120,13 +187,15 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
# Latência.
|
||||
if lat_media > 150:
|
||||
p = min(25, int((lat_media - 150) / 20))
|
||||
saude -= p
|
||||
motivos.append(f"latência média alta: {lat_media:.0f}ms (-{p}%)")
|
||||
if p > 0:
|
||||
saude -= p
|
||||
motivos.append(f"latência média alta: {lat_media:.0f}ms (-{p}%)")
|
||||
|
||||
if lat_max > 500:
|
||||
p = min(15, int((lat_max - 500) / 100) + 5)
|
||||
saude -= p
|
||||
motivos.append(f"pico de latência: {lat_max:.0f}ms (-{p}%)")
|
||||
if p > 0:
|
||||
saude -= p
|
||||
motivos.append(f"pico de latência: {lat_max:.0f}ms (-{p}%)")
|
||||
|
||||
# Erro crítico recente, com decaimento.
|
||||
if erros_criticos > 0 and age_ec <= 600:
|
||||
|
|
@ -134,25 +203,69 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
p = int(round(min(erros_criticos * 10, 40) * fator))
|
||||
if p > 0:
|
||||
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))
|
||||
|
||||
status = StatusModulo.OPERANTE
|
||||
if delta_reconexoes > 0:
|
||||
motivos.append(f"{delta_reconexoes} reconexão(ões) CAN no ciclo")
|
||||
|
||||
if not conectado:
|
||||
status = StatusModulo.DESCONECTADO
|
||||
elif saude <= 20:
|
||||
status = StatusModulo.FALHA
|
||||
elif saude < 80:
|
||||
status = StatusModulo.ALERTA
|
||||
saude_instantanea = max(0, min(100, saude))
|
||||
if self._saude_suavizada is None:
|
||||
self._saude_suavizada = saude_instantanea
|
||||
else:
|
||||
alpha = 0.35
|
||||
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 = {
|
||||
"conectado": conectado,
|
||||
"status": status.value,
|
||||
"saude": saude,
|
||||
"saude_instantanea": saude_instantanea,
|
||||
"motivos": motivos,
|
||||
"saude_individual": [],
|
||||
"saude_individual": saude_individual,
|
||||
"condicoes_operacionais": [],
|
||||
"detalhes": {
|
||||
"age_rx_s": age_rx,
|
||||
|
|
@ -165,10 +278,21 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
|||
"pendentes_aguardando_resposta": pend_resp,
|
||||
"frames_rx_fila": frames_rx_fila,
|
||||
"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,
|
||||
"rx_invalido_total": rx_invalid_total,
|
||||
"latencia_media_ms": lat_media,
|
||||
"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:
|
||||
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