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\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" />
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 (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)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue