374 lines
12 KiB
C#
374 lines
12 KiB
C#
using AgroBase.Models;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.IO.Ports;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using static AgroBase.Models.Enums;
|
|
|
|
namespace AgroBase.Services
|
|
{
|
|
public class CanMessage
|
|
{
|
|
public DateTime Momento { get; set; }
|
|
public T_Code Dispositivo { get; set; }
|
|
public byte Id { get; set; }
|
|
public byte funcCodeTx { get; set; }
|
|
public byte funcCodeRx { get; set; }
|
|
public byte[] DataTx { get; set; }
|
|
public byte[] DataRx { get; set; }
|
|
public bool Enviado { get; set; }
|
|
public DateTime EnviadoEm { get; set; }
|
|
public bool ComResposta { get; set; }
|
|
public bool Respondido { get; set; }
|
|
public DateTime RespondidoEm { get; set; }
|
|
}
|
|
|
|
public interface ICanMessageHandler
|
|
{
|
|
void ProcessarMensagem(CanMessage mensagem);
|
|
}
|
|
|
|
public class CanService : IDisposable
|
|
{
|
|
public static SerialPort _PortaCAN;
|
|
public static string _portName;
|
|
private static int _baudRate;
|
|
private static object _lock = new object();
|
|
public static bool IsConnected { get; private set; }
|
|
public static bool Iniciado
|
|
{
|
|
get
|
|
{
|
|
return SerialService.DispositivosCan.Any(d => SerialService.DispositivosMapeados.Any(x => x.Conectado && x.Dispositivo == d));
|
|
}
|
|
}
|
|
private static AsyncTaskTimerModel tmrEnviarCAN;
|
|
private static AsyncTaskTimerModel tmrOuvirCAN;
|
|
private static StringBuilder SerialCANBuffer = new StringBuilder();
|
|
private static readonly ConcurrentDictionary<byte, ICanMessageHandler> _roteadores = new ConcurrentDictionary<byte, ICanMessageHandler>();
|
|
private static byte? _ultimoIdEnviado = null;
|
|
|
|
private static List<CanMessage> Mensagens = new List<CanMessage>();
|
|
|
|
|
|
public static void RegistrarHandler(byte endereco, ICanMessageHandler handler)
|
|
{
|
|
_roteadores[endereco] = handler;
|
|
}
|
|
|
|
public static void DefinirPortaCOM(SerialPort Porta)
|
|
{
|
|
if (!IsConnected)
|
|
{
|
|
Porta.Close();
|
|
}
|
|
|
|
if (_PortaCAN == null || !IsConnected)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_portName = Porta.PortName;
|
|
_baudRate = 115200;
|
|
|
|
_PortaCAN = new SerialPort(_portName, _baudRate, Parity.None, 8, StopBits.One);
|
|
_PortaCAN.Open();
|
|
|
|
EnviarComando("S6\r"); // 500Kbps
|
|
Thread.Sleep(100);
|
|
EnviarComando("O\r"); // Open CAN
|
|
Thread.Sleep(100);
|
|
|
|
tmrOuvirCAN?.Dispose();
|
|
tmrOuvirCAN = new AsyncTaskTimerModel("tmrOuvirCAN", OuvirCAN, 5);
|
|
tmrOuvirCAN.Start();
|
|
|
|
tmrEnviarCAN?.Dispose();
|
|
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 5);
|
|
tmrEnviarCAN.Start();
|
|
|
|
IsConnected = true;
|
|
}
|
|
}
|
|
|
|
if (_PortaCAN != null && !_PortaCAN.IsOpen)
|
|
{
|
|
_PortaCAN.Open();
|
|
}
|
|
}
|
|
|
|
private static async Task OuvirCAN()
|
|
{
|
|
if (_PortaCAN.BytesToRead > 0)
|
|
{
|
|
byte[] tempBuffer = await SerialService.LerDadosDaPortaSerialAsync(_PortaCAN, _PortaCAN.BytesToRead);
|
|
|
|
foreach (byte b in tempBuffer)
|
|
{
|
|
char c = (char)b;
|
|
|
|
if (c == '\r')
|
|
{
|
|
var frame = SerialCANBuffer.ToString();
|
|
SerialCANBuffer.Clear();
|
|
|
|
if (!string.IsNullOrWhiteSpace(frame))
|
|
{
|
|
ProcessarFrameCAN(frame);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SerialCANBuffer.Append(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ProcessarFrameCAN(string frame)
|
|
{
|
|
// Ex: t00183301020304
|
|
if (!frame.StartsWith("t")) return;
|
|
|
|
try
|
|
{
|
|
if (frame.Length < 5) return;
|
|
|
|
int id = Convert.ToInt32(frame.Substring(1, 3), 16);
|
|
int len = int.Parse(frame.Substring(4, 1));
|
|
|
|
byte[] data = new byte[len];
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
data[i] = Convert.ToByte(frame.Substring(5 + i * 2, 2), 16);
|
|
}
|
|
|
|
byte funcCode = data[0];
|
|
var mensagem = new CanMessage();
|
|
|
|
List<CanMessage> mensagensPorID = new List<CanMessage>();
|
|
lock (Mensagens)
|
|
{
|
|
mensagensPorID = Mensagens.Where(x => x.Id == id).ToList();
|
|
}
|
|
|
|
mensagem = mensagensPorID.OrderBy(x => x.EnviadoEm).FirstOrDefault(x => x.Id == id && x.funcCodeRx == funcCode && x.Enviado && !x.Respondido);
|
|
if (mensagem == null)
|
|
{
|
|
mensagem = mensagensPorID.OrderBy(x => x.EnviadoEm).FirstOrDefault(x => x.Id == id && x.Enviado && !x.Respondido);
|
|
}
|
|
|
|
if (mensagem == null)
|
|
{
|
|
mensagem = new CanMessage { Id = (byte)id, DataRx = data };
|
|
}
|
|
|
|
mensagem.DataRx = data;
|
|
mensagem.Respondido = true;
|
|
mensagem.RespondidoEm = DateTime.Now;
|
|
|
|
if (_roteadores.TryGetValue(mensagem.Id, out var handler))
|
|
{
|
|
handler.ProcessarMensagem(mensagem);
|
|
}
|
|
else
|
|
{
|
|
Console.WriteLine($"[CAN] FuncCode não tratado: 0x{funcCode:X2}");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Console.WriteLine($"[CAN] Erro ao processar frame: {ex.Message}");
|
|
}
|
|
}
|
|
|
|
public static void AdicionarMensagemNaFila(T_Code Dispositivo, byte id, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true)
|
|
{
|
|
if (_PortaCAN?.IsOpen != true) return;
|
|
|
|
// Monta o payload com funcCode + dados + CRC
|
|
var dados = new List<byte> { funcCodeTx };
|
|
if (payload != null)
|
|
dados.AddRange(payload);
|
|
|
|
byte crc = CalcularChecksum(id, dados.ToArray());
|
|
dados.Add(crc);
|
|
|
|
var novaMensagem = new CanMessage
|
|
{
|
|
Momento = DateTime.Now,
|
|
Dispositivo = Dispositivo,
|
|
Id = id,
|
|
funcCodeTx = funcCodeTx,
|
|
funcCodeRx = funcCodeRx,
|
|
DataTx = dados.ToArray(),
|
|
ComResposta = get
|
|
};
|
|
|
|
lock (Mensagens)
|
|
{
|
|
// Limpa mensagens antigas antes de avaliar
|
|
LimparMensagensAntigas();
|
|
|
|
var existente = Mensagens.FirstOrDefault(x =>
|
|
x.Id == novaMensagem.Id &&
|
|
x.funcCodeTx == novaMensagem.funcCodeTx &&
|
|
x.ComResposta == novaMensagem.ComResposta &&
|
|
x.DataTx.SequenceEqual(novaMensagem.DataTx) &&
|
|
(!x.Enviado || (x.ComResposta && !x.Respondido)) // Está pendente
|
|
);
|
|
|
|
if (existente != null)
|
|
{
|
|
// Já existe mensagem igual aguardando ou pendente
|
|
return;
|
|
}
|
|
|
|
// Log e adiciona
|
|
//Console.WriteLine($"[MSG] - MENSAGEM ADICIONADA NA FILA para o módulo {Dispositivo} - {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}");
|
|
Mensagens.Add(novaMensagem);
|
|
}
|
|
}
|
|
|
|
private static CanMessage ObterProximaMensagem()
|
|
{
|
|
lock (Mensagens)
|
|
{
|
|
var idsDisponiveis = Mensagens
|
|
.Where(x => !x.Enviado)
|
|
.Select(x => x.Id)
|
|
.Distinct()
|
|
.OrderBy(id => id)
|
|
.ToList();
|
|
|
|
if (!idsDisponiveis.Any())
|
|
return null;
|
|
|
|
byte proximoId;
|
|
|
|
if (!_ultimoIdEnviado.HasValue || !idsDisponiveis.Contains(_ultimoIdEnviado.Value))
|
|
{
|
|
// Começa pelo menor ID disponível
|
|
proximoId = idsDisponiveis.First();
|
|
}
|
|
else
|
|
{
|
|
// Busca o próximo ID após o último usado
|
|
var index = idsDisponiveis.IndexOf(_ultimoIdEnviado.Value);
|
|
if (index + 1 < idsDisponiveis.Count)
|
|
proximoId = idsDisponiveis[index + 1];
|
|
else
|
|
proximoId = idsDisponiveis.First(); // ciclo completo
|
|
}
|
|
|
|
var msg = Mensagens
|
|
.Where(x => !x.Enviado && x.Id == proximoId)
|
|
.OrderBy(x => x.Momento)
|
|
.FirstOrDefault();
|
|
|
|
return msg;
|
|
}
|
|
}
|
|
|
|
private static async Task EnviarCAN()
|
|
{
|
|
if (_PortaCAN?.IsOpen != true) return;
|
|
|
|
CanMessage msg = ObterProximaMensagem();
|
|
|
|
if (msg != null)
|
|
{
|
|
string frame = CriarFrame(msg);
|
|
|
|
if (_ultimoIdEnviado.HasValue && msg.Id == _ultimoIdEnviado.Value)
|
|
{
|
|
await Task.Delay(5);
|
|
}
|
|
|
|
lock (_lock)
|
|
{
|
|
EnviarComando(frame);
|
|
|
|
msg.Enviado = true;
|
|
msg.EnviadoEm = DateTime.Now;
|
|
_ultimoIdEnviado = msg.Id;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string CriarFrame(CanMessage msg)
|
|
{
|
|
string frame = $"t{msg.Id:X3}{msg.DataTx.Length}";
|
|
foreach (byte b in msg.DataTx)
|
|
frame += b.ToString("X2");
|
|
|
|
frame += "\r";
|
|
|
|
return frame;
|
|
}
|
|
|
|
private static void EnviarComando(string comando)
|
|
{
|
|
if (_PortaCAN?.IsOpen == true)
|
|
{
|
|
_PortaCAN.Write(comando);
|
|
|
|
//Console.WriteLine($"[CAN] Frame enviado: {comando}");
|
|
}
|
|
}
|
|
|
|
private static void LimparMensagensAntigas()
|
|
{
|
|
lock (Mensagens)
|
|
{
|
|
var limite = DateTime.Now.AddSeconds(-5); // ⏱ 5 segundos de validade
|
|
Mensagens.RemoveAll(x =>
|
|
(!x.ComResposta && x.Enviado) || // Mensagem que não precisa resposta e já foi enviada
|
|
(x.ComResposta && x.Respondido && x.RespondidoEm < limite) || // Resposta antiga
|
|
(x.ComResposta && x.Enviado && !x.Respondido && x.EnviadoEm < limite) // Enviada mas nunca respondida e já passou do prazo
|
|
);
|
|
}
|
|
}
|
|
|
|
public static void Fechar()
|
|
{
|
|
tmrOuvirCAN?.Dispose();
|
|
tmrEnviarCAN?.Dispose();
|
|
|
|
lock (Mensagens)
|
|
{
|
|
Mensagens.Clear();
|
|
}
|
|
|
|
SerialCANBuffer.Clear();
|
|
_roteadores.Clear();
|
|
_ultimoIdEnviado = null;
|
|
|
|
if (_PortaCAN?.IsOpen == true)
|
|
{
|
|
EnviarComando("C\r");
|
|
_PortaCAN.Close();
|
|
}
|
|
|
|
IsConnected = false;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Fechar();
|
|
}
|
|
|
|
private static byte CalcularChecksum(byte id, byte[] data)
|
|
{
|
|
int soma = id;
|
|
foreach (var b in data)
|
|
soma += b;
|
|
return (byte)(soma & 0xFF);
|
|
}
|
|
|
|
|
|
}
|
|
} |