Adicionado conversor CAN Ethernet

This commit is contained in:
Diego Freitas 2026-06-08 09:36:40 -03:00
parent fec398d5a4
commit 42fbf5924b
8 changed files with 788 additions and 14 deletions

View File

@ -575,6 +575,7 @@
<Compile Include="Services\BLD300RService.cs" /> <Compile Include="Services\BLD300RService.cs" />
<Compile Include="Services\CameraService.cs" /> <Compile Include="Services\CameraService.cs" />
<Compile Include="Services\CANService.cs" /> <Compile Include="Services\CANService.cs" />
<Compile Include="Services\CanServiceEthernet.cs" />
<Compile Include="Services\CANServiceWaveshare.cs" /> <Compile Include="Services\CANServiceWaveshare.cs" />
<Compile Include="Services\CoolerControlService.cs" /> <Compile Include="Services\CoolerControlService.cs" />
<Compile Include="Services\CoulombCounterService.cs" /> <Compile Include="Services\CoulombCounterService.cs" />

View File

@ -2651,7 +2651,7 @@ namespace AgroBase.Models
public List<ManagerWorkerMessageResponseModulosPendentesModel> ModulosSaude { get; set; } public List<ManagerWorkerMessageResponseModulosPendentesModel> ModulosSaude { get; set; }
public List<DispositivoDetalhesModel> DispositivosMapeados { get; set; } public List<DispositivoDetalhesModel> DispositivosMapeados { get; set; }
public Dictionary<T_Code, Dictionary<string, bool>> ModsCalibragem { get; set; } public Dictionary<T_Code, Dictionary<string, bool>> ModsCalibragem { get; set; }
public bool CanSerialAtivado { get; set; } public CanServiceTipo TipoCanAtivo { get; set; }
public List<OperacaoSensoriamentoLogErrosModel> Logs { get; set; } public List<OperacaoSensoriamentoLogErrosModel> Logs { get; set; }
public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue; public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue;
public CoolerControlDataModel CoolerControl { get; set; } public CoolerControlDataModel CoolerControl { get; set; }
@ -2928,7 +2928,7 @@ namespace AgroBase.Models
DataFim = Variaveis.OperacaoEmAndamento.DataFim, DataFim = Variaveis.OperacaoEmAndamento.DataFim,
TempoAguardandoSeg = Variaveis.OperacaoEmAndamento.TempoAguardandoSegs, TempoAguardandoSeg = Variaveis.OperacaoEmAndamento.TempoAguardandoSegs,
TempoDecorridoSeg = Variaveis.OperacaoEmAndamento.TempoDecorridoSegs, TempoDecorridoSeg = Variaveis.OperacaoEmAndamento.TempoDecorridoSegs,
CanSerialAtivado = CanManager.UseCanSerial, TipoCanAtivo = CanManager.TipoServico,
IMU = dadosIMU, IMU = dadosIMU,
Atuador = dadosAtuador, Atuador = dadosAtuador,
Movimentacao = dadosMovimentacao, Movimentacao = dadosMovimentacao,
@ -2979,7 +2979,7 @@ namespace AgroBase.Models
DataFim = DataFim, DataFim = DataFim,
TempoAguardandoSeg = TempoAguardandoSeg, TempoAguardandoSeg = TempoAguardandoSeg,
TempoDecorridoSeg = TempoDecorridoSeg, TempoDecorridoSeg = TempoDecorridoSeg,
CanSerialAtivado = CanSerialAtivado, TipoCanAtivo = TipoCanAtivo,
Atuador = Atuador?.Clone(), Atuador = Atuador?.Clone(),
Movimentacao = Movimentacao?.Clone(), Movimentacao = Movimentacao?.Clone(),
Direcional = Direcional?.Clone(), Direcional = Direcional?.Clone(),

View File

@ -504,7 +504,7 @@ namespace AgroBase.Models.Operacoes
{ {
public DateTime Momento { get; set; } public DateTime Momento { get; set; }
public StatusModulo StatusRover { get; set; } public StatusModulo StatusRover { get; set; }
public bool CanSerial { get; set; } public CanServiceTipo TipoCanAtivo { get; set; }
public OperacaoParametrosDadosOperacaoModel Operacao { get; set; } public OperacaoParametrosDadosOperacaoModel Operacao { get; set; }
public OperacaoParametrosDadosTrajetoriaModel Trajetoria { get; set; } public OperacaoParametrosDadosTrajetoriaModel Trajetoria { get; set; }
public OperacaoParametrosDadosControleModel Controle { get; set; } public OperacaoParametrosDadosControleModel Controle { get; set; }
@ -537,7 +537,7 @@ namespace AgroBase.Models.Operacoes
Momento = DateTime.Now; Momento = DateTime.Now;
StatusRover = op.Status; StatusRover = op.Status;
CanSerial = CanManager.UseCanSerial; TipoCanAtivo = CanManager.TipoServico;
Operacao = new OperacaoParametrosDadosOperacaoModel() Operacao = new OperacaoParametrosDadosOperacaoModel()
{ {
Iniciada = op.Iniciado, Iniciada = op.Iniciado,
@ -806,7 +806,7 @@ namespace AgroBase.Models.Operacoes
{ {
Momento = Momento, Momento = Momento,
StatusRover = StatusRover, StatusRover = StatusRover,
CanSerial = CanSerial, TipoCanAtivo = TipoCanAtivo,
Operacao = Operacao, Operacao = Operacao,
Trajetoria = Trajetoria?.Clone(), Trajetoria = Trajetoria?.Clone(),
Controle = Controle?.Clone(), Controle = Controle?.Clone(),

View File

@ -4,24 +4,44 @@ using static AgroBase.Models.Enums;
namespace AgroBase.Services namespace AgroBase.Services
{ {
public enum CanServiceTipo
{
Serial,
Waveshare,
Ethernet
}
public class CanManager public class CanManager
{ {
public static bool UseCanSerial = true; public static CanServiceTipo TipoServico = CanServiceTipo.Ethernet;
private static CanServiceWaveshare CanWaveshare = new CanServiceWaveshare(); private static CanServiceWaveshare CanWaveshare = new CanServiceWaveshare();
private static CanServiceSerial CanSerial = new CanServiceSerial(); private static CanServiceSerial CanSerial = new CanServiceSerial();
private static CanServiceEthernet CanEthernet = new CanServiceEthernet
{
IpAddress = "192.168.0.7",
Port = 8235,
TransportMode = CanEthernetTransportMode.Tcp
};
public static ICanService CanService public static ICanService CanService
{ {
get get
{ {
if (UseCanSerial) switch (TipoServico)
{ {
return CanSerial; case CanServiceTipo.Serial:
} return CanSerial;
else
{ case CanServiceTipo.Waveshare:
return CanWaveshare; return CanWaveshare;
case CanServiceTipo.Ethernet:
return CanEthernet;
default:
return CanSerial;
} }
} }
} }

View File

@ -237,6 +237,8 @@ namespace AgroBase.Services
tmrProcessarCAN.Start(); tmrProcessarCAN.Start();
IsConnected = true; IsConnected = true;
DefinirDispositivo();
} }
return IsConnected; return IsConnected;
@ -252,6 +254,23 @@ namespace AgroBase.Services
} }
} }
private void DefinirDispositivo()
{
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
{
if (IsConnected && Iniciado)
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Can,
Endereco = _portName,
Versao = ("1").ToString()
});
}
}
}
private async Task ProcessarCAN() private async Task ProcessarCAN()
{ {
const int MAX_BATCH = 256; // teto de itens por tick const int MAX_BATCH = 256; // teto de itens por tick

View File

@ -131,6 +131,8 @@ namespace AgroBase.Services
else else
{ {
MostrarLog($"Conversor CAN FD iniciado com sucesso no canal {Connector.Canal}.", 0); MostrarLog($"Conversor CAN FD iniciado com sucesso no canal {Connector.Canal}.", 0);
DefinirDispositivo();
} }
if (canIniciado || !IsConnected) if (canIniciado || !IsConnected)
@ -154,6 +156,23 @@ namespace AgroBase.Services
return canIniciado; return canIniciado;
} }
private void DefinirDispositivo()
{
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
{
if (IsConnected && Iniciado)
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Can,
Endereco = _portName,
Versao = ("1").ToString()
});
}
}
}
private async Task ProcessarCAN() private async Task ProcessarCAN()
{ {
const int MAX_BATCH = 256; // teto de itens por tick const int MAX_BATCH = 256; // teto de itens por tick

View File

@ -0,0 +1,715 @@
using AgroBase.Models;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO.Ports;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using static AgroBase.Models.Enums;
namespace AgroBase.Services
{
public enum CanEthernetTransportMode
{
Tcp = 0,
Udp = 1
}
/// <summary>
/// Serviço CAN para conversores Ethernet-CAN no modo Standard Protocol Conversion do USR-CAN115.
/// Formato por frame: 13 bytes
/// [0] FrameInfo: bit7=Extended, bit6=RTR, bits3..0=DLC
/// [1..4] CAN ID big-endian
/// [5..12] Data CAN, sempre 8 bytes, preenchendo com 0x00 quando DLC menor.
/// </summary>
public class CanServiceEthernet : ICanService
{
public string _portName { get; set; } = "CAN-ETH";
public string IpAddress { get; set; } = "192.168.0.7";
public int Port { get; set; } = 8235;
public CanEthernetTransportMode TransportMode { get; set; } = CanEthernetTransportMode.Tcp;
private int _bitRate;
private TcpClient _tcpClient;
private NetworkStream _tcpStream;
private UdpClient _udpClient;
private IPEndPoint _remoteEndPoint;
private readonly object _socketLock = new object();
private readonly object _LockMensagens = new object();
private AsyncTaskTimerModel tmrOuvirCAN;
private AsyncTaskTimerModel tmrEnviarCAN;
private AsyncTaskTimerModel tmrProcessarCAN;
private readonly ConcurrentQueue<(uint id, byte[] data)> FramesRecebidos = new ConcurrentQueue<(uint, byte[])>();
private readonly List<byte> TcpRxBuffer = new List<byte>(4096);
private readonly ConcurrentDictionary<uint, ICanMessageHandler> _roteadores = new ConcurrentDictionary<uint, ICanMessageHandler>();
private readonly ConcurrentDictionary<uint, bool> _roteadoresRegistrados = new ConcurrentDictionary<uint, bool>();
private readonly List<CanMessage> MensagensPendentes = new List<CanMessage>();
private uint? _ultimoIdEnviado = null;
private bool DebugMode = false;
public bool IsConnected { get; private set; }
public bool Iniciado { get; private set; }
public bool FilaLiberada
{
get
{
lock (_LockMensagens)
{
return MensagensPendentes == null ||
MensagensPendentes.Count == 0 ||
!MensagensPendentes.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
}
}
}
public double UltimoTx { get; set; } = 0;
private DateTime _ultimoTx = DateTime.MinValue;
public double FreqTx { get; set; } = 0;
public double UltimoRx { get; set; } = 0;
private DateTime _ultimoRx = DateTime.MinValue;
public double FreqRx { get; set; } = 0;
public int ErrosCriticos { get; set; } = 0;
public double UltimoErroCritico { get; set; } = 0;
private DateTime _ultimoErroCritico = DateTime.MinValue;
private const int USR_FRAME_SIZE = 13;
private void MostrarLog(string message, uint id = 0)
{
if (DebugMode)
Console.WriteLine($"[CAN-ETH] {message}");
}
public bool PortaIsCan(SerialPort Porta = null)
{
return true;
}
public void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler)
{
_roteadoresRegistrados.TryGetValue(endereco_rx, out bool registrado);
if (!registrado)
{
_roteadoresRegistrados.TryAdd(endereco_rx, true);
_roteadores.TryAdd(endereco_rx, handler);
MostrarLog($"Roteador registrado: 0x{endereco_rx:X}", endereco_rx);
}
}
public bool Inicializar(int BitRate = 250)
{
_bitRate = BitRate;
try
{
FecharConexaoSemLimparHandlers();
if (TransportMode == CanEthernetTransportMode.Tcp)
IsConnected = ConectarTcp();
else
IsConnected = ConectarUdp();
Iniciado = IsConnected;
if (!IsConnected)
return false;
_ultimoRx = DateTime.Now;
UltimoRx = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
_ultimoTx = DateTime.Now;
UltimoTx = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
tmrOuvirCAN?.Dispose();
tmrOuvirCAN = new AsyncTaskTimerModel("tmrOuvirCANEthernet", OuvirCAN, 1);
tmrOuvirCAN.Start();
tmrEnviarCAN?.Dispose();
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCANEthernet", EnviarCAN, 3);
tmrEnviarCAN.Start();
tmrProcessarCAN?.Dispose();
tmrProcessarCAN = new AsyncTaskTimerModel("tmrProcessarCANEthernet", ProcessarCAN, 5);
tmrProcessarCAN.Start();
MostrarLog($"Inicializado via {TransportMode} em {IpAddress}:{Port}");
DefinirDispositivo();
return true;
}
catch (Exception ex)
{
MostrarLog($"Erro ao inicializar: {ex.Message}");
IsConnected = false;
Iniciado = false;
return false;
}
}
private bool ConectarTcp()
{
try
{
_tcpClient = new TcpClient();
_tcpClient.NoDelay = true;
_tcpClient.ReceiveBufferSize = 64 * 1024;
_tcpClient.SendBufferSize = 64 * 1024;
var task = _tcpClient.ConnectAsync(IpAddress, Port);
if (!task.Wait(1200))
return false;
if (!_tcpClient.Connected)
return false;
_tcpStream = _tcpClient.GetStream();
return true;
}
catch (Exception ex)
{
MostrarLog($"Erro TCP: {ex.Message}");
return false;
}
}
private bool ConectarUdp()
{
try
{
_remoteEndPoint = new IPEndPoint(IPAddress.Parse(IpAddress), Port);
_udpClient = new UdpClient();
_udpClient.Client.ReceiveBufferSize = 64 * 1024;
_udpClient.Client.SendBufferSize = 64 * 1024;
_udpClient.Client.Blocking = false;
_udpClient.Connect(_remoteEndPoint);
return true;
}
catch (Exception ex)
{
MostrarLog($"Erro UDP: {ex.Message}");
return false;
}
}
private void DefinirDispositivo()
{
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
{
if (IsConnected && Iniciado)
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Can,
Endereco = IpAddress,
Versao = ("1").ToString()
});
}
}
}
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true)
{
if (!IsConnected) return;
uint idTx = CanManager.ConverterIdCan(_idTx);
uint idRx = CanManager.ConverterIdCan(_idRx);
var dados = new List<byte>();
if (Dispositivo != T_Code.Bat)
dados.Add(funcCodeTx);
if (payload != null)
dados.AddRange(payload);
if (Dispositivo == T_Code.Mks)
{
byte crc = CanManager.CalcularChecksum(idTx, dados.ToArray());
dados.Add(crc);
}
if (dados.Count > 8)
throw new InvalidOperationException($"Payload CAN maior que 8 bytes. ID=0x{idTx:X}, Len={dados.Count}");
var novaMensagem = new CanMessage
{
Momento = DateTime.Now,
Dispositivo = Dispositivo,
IdTx = idTx,
IdRx = idRx,
funcCodeTx = funcCodeTx,
funcCodeRx = funcCodeRx,
DataTx = dados.ToArray(),
ComResposta = get,
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray())
};
lock (_LockMensagens)
{
bool mensagemNaFila = MensagensPendentes.Any(x =>
(!x.Enviado || (x.ComResposta && x.Enviado && !x.Respondido)) &&
x.IdTx == idTx &&
x.IdRx == idRx &&
x.DataTx != null &&
x.DataTx.SequenceEqual(dados.ToArray()));
if (!mensagemNaFila)
{
MensagensPendentes.Add(novaMensagem);
MostrarLog($"Mensagem adicionada. ID=0x{idTx:X}, Data={BitConverter.ToString(novaMensagem.DataTx)}", idTx);
}
}
}
private CanMessage ObterProximaMensagem()
{
List<CanMessage> mensagensPendentes;
lock (_LockMensagens)
{
mensagensPendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
}
if (!mensagensPendentes.Any())
return null;
var prioritarias = mensagensPendentes.Where(x => !x.ComResposta).ToList();
var baseLista = prioritarias.Any() ? prioritarias : mensagensPendentes;
var idsDisponiveis = baseLista.Select(x => x.IdTx).Distinct().OrderBy(x => x).ToList();
uint proximoId;
if (!_ultimoIdEnviado.HasValue || !idsDisponiveis.Contains(_ultimoIdEnviado.Value))
proximoId = idsDisponiveis.First();
else
{
int index = idsDisponiveis.IndexOf(_ultimoIdEnviado.Value);
proximoId = (index + 1 < idsDisponiveis.Count) ? idsDisponiveis[index + 1] : idsDisponiveis.First();
}
return baseLista.Where(x => x.IdTx == proximoId).OrderBy(x => x.Momento).FirstOrDefault();
}
private async Task EnviarCAN()
{
if (!IsConnected) return;
RefillTokens();
const int MAX_BURST = 8;
int sent = 0;
while (_tokens >= 1 && sent < MAX_BURST)
{
var msg = ObterProximaMensagem();
if (msg == null) break;
byte[] packet = CriarPacoteUsrStandard(msg);
if (!EnviarPacote(packet, msg.IdTx))
{
MostrarLog($"Erro ao enviar frame ID=0x{msg.IdTx:X}", msg.IdTx);
IsConnected = false;
break;
}
msg.Enviado = true;
msg.EnviadoEm = DateTime.Now;
_ultimoIdEnviado = msg.IdTx;
_tokens -= 1;
sent++;
}
if (sent == 0)
await Task.Delay(1);
}
private byte[] CriarPacoteUsrStandard(CanMessage msg)
{
bool isExtended = msg.IdTx > 0x7FF;
byte[] data = msg.DataTx ?? new byte[0];
int dlc = Math.Min(data.Length, 8);
byte frameInfo = (byte)(dlc & 0x0F);
if (isExtended)
frameInfo |= 0x80;
var packet = new byte[USR_FRAME_SIZE];
packet[0] = frameInfo;
packet[1] = (byte)((msg.IdTx >> 24) & 0xFF);
packet[2] = (byte)((msg.IdTx >> 16) & 0xFF);
packet[3] = (byte)((msg.IdTx >> 8) & 0xFF);
packet[4] = (byte)(msg.IdTx & 0xFF);
for (int i = 0; i < dlc; i++)
packet[5 + i] = data[i];
return packet;
}
private bool EnviarPacote(byte[] packet, uint id)
{
try
{
lock (_socketLock)
{
if (TransportMode == CanEthernetTransportMode.Tcp)
{
if (_tcpStream == null || !_tcpStream.CanWrite)
return false;
_tcpStream.Write(packet, 0, packet.Length);
_tcpStream.Flush();
}
else
{
if (_udpClient == null)
return false;
_udpClient.Send(packet, packet.Length);
}
}
var agora = DateTime.Now;
FreqTx = _ultimoTx == DateTime.MinValue ? 0 : 1.0 / Math.Max(0.001, (agora - _ultimoTx).TotalSeconds);
_ultimoTx = agora;
UltimoTx = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
MostrarLog($"TX ID=0x{id:X}, Packet={BitConverter.ToString(packet)}", id);
return true;
}
catch (Exception ex)
{
MostrarLog($"Erro TX: {ex.Message}", id);
return false;
}
}
private async Task OuvirCAN()
{
if (!IsConnected) return;
try
{
if (TransportMode == CanEthernetTransportMode.Tcp)
OuvirTcp();
else
OuvirUdp();
}
catch (Exception ex)
{
MostrarLog($"Erro RX: {ex.Message}");
IsConnected = false;
}
await Task.CompletedTask;
}
private void OuvirTcp()
{
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
{
IsConnected = false;
return;
}
while (_tcpStream.DataAvailable)
{
byte[] buffer = new byte[4096];
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
if (lidos <= 0)
break;
for (int i = 0; i < lidos; i++)
TcpRxBuffer.Add(buffer[i]);
}
while (TcpRxBuffer.Count >= USR_FRAME_SIZE)
{
byte[] packet = TcpRxBuffer.Take(USR_FRAME_SIZE).ToArray();
TcpRxBuffer.RemoveRange(0, USR_FRAME_SIZE);
ProcessarPacoteUsrStandard(packet);
}
}
private void OuvirUdp()
{
if (_udpClient == null)
{
IsConnected = false;
return;
}
while (_udpClient.Available > 0)
{
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
byte[] pacote = _udpClient.Receive(ref ep);
if (pacote == null || pacote.Length == 0)
continue;
int offset = 0;
while (offset + USR_FRAME_SIZE <= pacote.Length)
{
byte[] frame = new byte[USR_FRAME_SIZE];
Buffer.BlockCopy(pacote, offset, frame, 0, USR_FRAME_SIZE);
ProcessarPacoteUsrStandard(frame);
offset += USR_FRAME_SIZE;
}
}
}
private void ProcessarPacoteUsrStandard(byte[] packet)
{
if (packet == null || packet.Length != USR_FRAME_SIZE)
return;
byte frameInfo = packet[0];
bool isExtended = (frameInfo & 0x80) != 0;
bool isRemote = (frameInfo & 0x40) != 0;
int dlc = frameInfo & 0x0F;
if (dlc < 0 || dlc > 8 || isRemote)
return;
uint id = ((uint)packet[1] << 24) |
((uint)packet[2] << 16) |
((uint)packet[3] << 8) |
packet[4];
if (!isExtended)
id &= 0x7FF;
byte[] data = new byte[dlc];
if (dlc > 0)
Buffer.BlockCopy(packet, 5, data, 0, dlc);
FramesRecebidos.Enqueue((id, data));
while (FramesRecebidos.Count > 4096)
FramesRecebidos.TryDequeue(out _);
var agora = DateTime.Now;
FreqRx = _ultimoRx == DateTime.MinValue ? 0 : 1.0 / Math.Max(0.001, (agora - _ultimoRx).TotalSeconds);
_ultimoRx = agora;
UltimoRx = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
MostrarLog($"RX {(isExtended ? "EXT" : "STD")} ID=0x{id:X}, Data={BitConverter.ToString(data)}", id);
}
private async Task ProcessarCAN()
{
const int MAX_BATCH = 256;
const int TIME_BUDGET_US = 1000;
var sw = Stopwatch.StartNew();
int processed = 0;
while (processed < MAX_BATCH && FramesRecebidos.TryDequeue(out var frame))
{
ProcessarFrameCAN(frame);
processed++;
if (sw.ElapsedTicks * (1_000_000.0 / Stopwatch.Frequency) > TIME_BUDGET_US)
break;
}
LimparMensagensAntigas();
VerificaSaudeConexao();
await Task.CompletedTask;
}
private void ProcessarFrameCAN((uint id, byte[] data) frame)
{
uint id = frame.id;
byte[] data = frame.data ?? new byte[0];
try
{
_roteadores.TryGetValue(id, out ICanMessageHandler handler);
T_Code dispositivo = handler?.Dispositivo ?? T_Code.Vzo;
bool db = dispositivo == T_Code.Bat;
byte fCodeRx = db ? (byte)DalyBMSService.GetDataId(id) : data.Length > 0 ? data[0] : (byte)0x00;
string chave = CanManager.GerarChaveMensagem(dispositivo, id, data);
CanMessage mensagem = null;
lock (_LockMensagens)
{
mensagem = MensagensPendentes.FirstOrDefault(x =>
x.Enviado &&
!x.Respondido &&
x.Dispositivo == dispositivo &&
x.IdRx == id &&
x.DataTx != null &&
(db || data.Length < 2 || x.DataTx.Length < 2 || x.DataTx[1] == data[1]));
}
if (mensagem == null)
{
mensagem = new CanMessage
{
Dispositivo = dispositivo,
IdRx = id,
funcCodeRx = fCodeRx,
DataRx = data,
ChaveInterna = chave
};
}
mensagem.DataRx = data;
mensagem.Respondido = true;
mensagem.RespondidoEm = DateTime.Now;
if (handler != null && dispositivo != T_Code.Vzo)
handler.ProcessarMensagem(mensagem);
else
MostrarLog($"Roteador não registrado. ID=0x{id:X}, Data={BitConverter.ToString(data)}", id);
}
catch (Exception ex)
{
MostrarLog($"Erro ao processar frame ID=0x{id:X}: {ex.Message}", id);
}
}
private void LimparMensagensAntigas()
{
var agora = DateTime.Now;
lock (_LockMensagens)
{
var remover = MensagensPendentes
.Where(x =>
(!x.ComResposta && x.Enviado) ||
(x.ComResposta && x.Respondido) ||
(x.ComResposta && x.Enviado && !x.Respondido && x.EnviadoEm < agora.AddSeconds(-2)) ||
(x.Momento < agora.AddSeconds(-30)))
.ToList();
foreach (var item in remover)
MensagensPendentes.Remove(item);
}
}
private void VerificaSaudeConexao()
{
DateTime agora = DateTime.Now;
bool socketOk = TransportMode == CanEthernetTransportMode.Tcp
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
: (_udpClient != null);
if (!socketOk)
{
IsConnected = false;
return;
}
// UDP não tem conexão real. Este watchdog só reinicia se o sistema ficou tempo demais sem TX e RX.
if (IsConnected && ((agora - _ultimoTx).TotalSeconds > 2 && (agora - _ultimoRx).TotalSeconds > 2))
{
ErrosCriticos++;
_ultimoErroCritico = agora;
UltimoErroCritico = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
MostrarLog("Muito tempo sem tráfego CAN-ETH, reiniciando conexão...");
ReabrirConexao();
}
if ((agora - _ultimoErroCritico).TotalMinutes > 10)
ErrosCriticos = 0;
}
private void ReabrirConexao()
{
try
{
FecharConexaoSemLimparHandlers();
if (TransportMode == CanEthernetTransportMode.Tcp)
IsConnected = ConectarTcp();
else
IsConnected = ConectarUdp();
Iniciado = IsConnected;
_ultimoRx = DateTime.Now;
_ultimoTx = DateTime.Now;
}
catch
{
IsConnected = false;
Iniciado = false;
}
}
private void FecharConexaoSemLimparHandlers()
{
lock (_socketLock)
{
try { _tcpStream?.Close(); } catch { }
try { _tcpClient?.Close(); } catch { }
try { _udpClient?.Close(); } catch { }
_tcpStream = null;
_tcpClient = null;
_udpClient = null;
_remoteEndPoint = null;
TcpRxBuffer.Clear();
}
IsConnected = false;
Iniciado = false;
}
public void Fechar()
{
tmrOuvirCAN?.Dispose();
tmrEnviarCAN?.Dispose();
tmrProcessarCAN?.Dispose();
lock (_LockMensagens)
MensagensPendentes.Clear();
while (FramesRecebidos.TryDequeue(out _)) { }
_ultimoIdEnviado = null;
FecharConexaoSemLimparHandlers();
}
public void Dispose()
{
Fechar();
}
// alvo: ~200 fps, igual ao serviço USB atual
private const double TARGET_FPS = 200;
private double _tokens = TARGET_FPS;
private const double _capacity = TARGET_FPS;
private const double _refillPerMs = TARGET_FPS / 1000.0;
private DateTime _lastRefill = DateTime.UtcNow;
private void RefillTokens()
{
var now = DateTime.UtcNow;
var dtMs = (now - _lastRefill).TotalMilliseconds;
if (dtMs > 0)
{
_tokens = Math.Min(_capacity, _tokens + dtMs * _refillPerMs);
_lastRefill = now;
}
}
}
}

View File

@ -232,7 +232,7 @@ namespace AgroBase.Services
if (Variaveis.IsAgroMonitor) continue; if (Variaveis.IsAgroMonitor) continue;
if (CanManager.UseCanSerial && !CanManager.CanService.Iniciado) if (CanManager.TipoServico == CanServiceTipo.Serial && !CanManager.CanService.Iniciado)
{ {
bool dispositivoCan = await ProcurarDispositivosCAN(_Porta); bool dispositivoCan = await ProcurarDispositivosCAN(_Porta);
if (dispositivoCan) if (dispositivoCan)