diff --git a/AgroBase/AgroBase/AgroBase.csproj b/AgroBase/AgroBase/AgroBase.csproj index 4272cbe06..c852c9229 100644 --- a/AgroBase/AgroBase/AgroBase.csproj +++ b/AgroBase/AgroBase/AgroBase.csproj @@ -575,6 +575,7 @@ + diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index dee49133c..4f72b1a94 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -2651,7 +2651,7 @@ namespace AgroBase.Models public List ModulosSaude { get; set; } public List DispositivosMapeados { get; set; } public Dictionary> ModsCalibragem { get; set; } - public bool CanSerialAtivado { get; set; } + public CanServiceTipo TipoCanAtivo { get; set; } public List Logs { get; set; } public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue; public CoolerControlDataModel CoolerControl { get; set; } @@ -2928,7 +2928,7 @@ namespace AgroBase.Models DataFim = Variaveis.OperacaoEmAndamento.DataFim, TempoAguardandoSeg = Variaveis.OperacaoEmAndamento.TempoAguardandoSegs, TempoDecorridoSeg = Variaveis.OperacaoEmAndamento.TempoDecorridoSegs, - CanSerialAtivado = CanManager.UseCanSerial, + TipoCanAtivo = CanManager.TipoServico, IMU = dadosIMU, Atuador = dadosAtuador, Movimentacao = dadosMovimentacao, @@ -2979,7 +2979,7 @@ namespace AgroBase.Models DataFim = DataFim, TempoAguardandoSeg = TempoAguardandoSeg, TempoDecorridoSeg = TempoDecorridoSeg, - CanSerialAtivado = CanSerialAtivado, + TipoCanAtivo = TipoCanAtivo, Atuador = Atuador?.Clone(), Movimentacao = Movimentacao?.Clone(), Direcional = Direcional?.Clone(), diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs index 04931f4b2..8170ca09c 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoParametrosModel.cs @@ -504,7 +504,7 @@ namespace AgroBase.Models.Operacoes { public DateTime Momento { get; set; } public StatusModulo StatusRover { get; set; } - public bool CanSerial { get; set; } + public CanServiceTipo TipoCanAtivo { get; set; } public OperacaoParametrosDadosOperacaoModel Operacao { get; set; } public OperacaoParametrosDadosTrajetoriaModel Trajetoria { get; set; } public OperacaoParametrosDadosControleModel Controle { get; set; } @@ -537,7 +537,7 @@ namespace AgroBase.Models.Operacoes Momento = DateTime.Now; StatusRover = op.Status; - CanSerial = CanManager.UseCanSerial; + TipoCanAtivo = CanManager.TipoServico; Operacao = new OperacaoParametrosDadosOperacaoModel() { Iniciada = op.Iniciado, @@ -806,7 +806,7 @@ namespace AgroBase.Models.Operacoes { Momento = Momento, StatusRover = StatusRover, - CanSerial = CanSerial, + TipoCanAtivo = TipoCanAtivo, Operacao = Operacao, Trajetoria = Trajetoria?.Clone(), Controle = Controle?.Clone(), diff --git a/AgroBase/AgroBase/Services/CANService.cs b/AgroBase/AgroBase/Services/CANService.cs index a7da5278e..27c00fe25 100644 --- a/AgroBase/AgroBase/Services/CANService.cs +++ b/AgroBase/AgroBase/Services/CANService.cs @@ -4,24 +4,44 @@ using static AgroBase.Models.Enums; namespace AgroBase.Services { + public enum CanServiceTipo + { + Serial, + Waveshare, + Ethernet + } + public class CanManager { - public static bool UseCanSerial = true; + public static CanServiceTipo TipoServico = CanServiceTipo.Ethernet; private static CanServiceWaveshare CanWaveshare = new CanServiceWaveshare(); 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 { get { - if (UseCanSerial) + switch (TipoServico) { - return CanSerial; - } - else - { - return CanWaveshare; + case CanServiceTipo.Serial: + return CanSerial; + + case CanServiceTipo.Waveshare: + return CanWaveshare; + + case CanServiceTipo.Ethernet: + return CanEthernet; + + default: + return CanSerial; } } } diff --git a/AgroBase/AgroBase/Services/CANServiceSerial.cs b/AgroBase/AgroBase/Services/CANServiceSerial.cs index 1afd70e92..853394daf 100644 --- a/AgroBase/AgroBase/Services/CANServiceSerial.cs +++ b/AgroBase/AgroBase/Services/CANServiceSerial.cs @@ -237,6 +237,8 @@ namespace AgroBase.Services tmrProcessarCAN.Start(); IsConnected = true; + + DefinirDispositivo(); } 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() { const int MAX_BATCH = 256; // teto de itens por tick diff --git a/AgroBase/AgroBase/Services/CANServiceWaveshare.cs b/AgroBase/AgroBase/Services/CANServiceWaveshare.cs index 4aaf93b34..6136c072e 100644 --- a/AgroBase/AgroBase/Services/CANServiceWaveshare.cs +++ b/AgroBase/AgroBase/Services/CANServiceWaveshare.cs @@ -131,6 +131,8 @@ namespace AgroBase.Services else { MostrarLog($"Conversor CAN FD iniciado com sucesso no canal {Connector.Canal}.", 0); + + DefinirDispositivo(); } if (canIniciado || !IsConnected) @@ -154,6 +156,23 @@ namespace AgroBase.Services 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() { const int MAX_BATCH = 256; // teto de itens por tick diff --git a/AgroBase/AgroBase/Services/CanServiceEthernet.cs b/AgroBase/AgroBase/Services/CanServiceEthernet.cs new file mode 100644 index 000000000..0d8e404ed --- /dev/null +++ b/AgroBase/AgroBase/Services/CanServiceEthernet.cs @@ -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 + } + + /// + /// 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. + /// + 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 TcpRxBuffer = new List(4096); + + private readonly ConcurrentDictionary _roteadores = new ConcurrentDictionary(); + private readonly ConcurrentDictionary _roteadoresRegistrados = new ConcurrentDictionary(); + private readonly List MensagensPendentes = new List(); + + 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(); + + 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 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; + } + } + } +} diff --git a/AgroBase/AgroBase/Services/SerialService.cs b/AgroBase/AgroBase/Services/SerialService.cs index 3cb2b6814..adb9b9f39 100644 --- a/AgroBase/AgroBase/Services/SerialService.cs +++ b/AgroBase/AgroBase/Services/SerialService.cs @@ -232,7 +232,7 @@ namespace AgroBase.Services if (Variaveis.IsAgroMonitor) continue; - if (CanManager.UseCanSerial && !CanManager.CanService.Iniciado) + if (CanManager.TipoServico == CanServiceTipo.Serial && !CanManager.CanService.Iniciado) { bool dispositivoCan = await ProcurarDispositivosCAN(_Porta); if (dispositivoCan)