Adicionado conversor CAN Ethernet
This commit is contained in:
parent
fec398d5a4
commit
42fbf5924b
|
|
@ -575,6 +575,7 @@
|
|||
<Compile Include="Services\BLD300RService.cs" />
|
||||
<Compile Include="Services\CameraService.cs" />
|
||||
<Compile Include="Services\CANService.cs" />
|
||||
<Compile Include="Services\CanServiceEthernet.cs" />
|
||||
<Compile Include="Services\CANServiceWaveshare.cs" />
|
||||
<Compile Include="Services\CoolerControlService.cs" />
|
||||
<Compile Include="Services\CoulombCounterService.cs" />
|
||||
|
|
|
|||
|
|
@ -2651,7 +2651,7 @@ namespace AgroBase.Models
|
|||
public List<ManagerWorkerMessageResponseModulosPendentesModel> ModulosSaude { get; set; }
|
||||
public List<DispositivoDetalhesModel> DispositivosMapeados { 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 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(),
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue