agrobot_base/AgroBase/AgroBase/Services/CANService.cs

370 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 _serialPort;
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<T_Code, ICanMessageHandler> _roteadores = new ConcurrentDictionary<T_Code, ICanMessageHandler>();
private static byte? _ultimoIdEnviado = null;
private static List<CanMessage> Mensagens = new List<CanMessage>();
public static void RegistrarHandler(T_Code dispositivo, ICanMessageHandler handler)
{
_roteadores[dispositivo] = handler;
}
public static void DefinirPortaCOM(SerialPort Porta)
{
if (!IsConnected)
{
Porta.Close();
}
if (_serialPort == null || !IsConnected)
{
lock (_lock)
{
_portName = Porta.PortName;
_baudRate = 115200;
_serialPort = new SerialPort(_portName, _baudRate, Parity.None, 8, StopBits.One);
_serialPort.Open();
EnviarComando("S6\r"); // 500Kbps
Thread.Sleep(100);
EnviarComando("O\r"); // Open CAN
Thread.Sleep(100);
tmrOuvirCAN?.Dispose();
tmrOuvirCAN = new AsyncTaskTimerModel("tmrOuvirCAN", OuvirCAN, 10);
tmrOuvirCAN.Start();
tmrEnviarCAN?.Dispose();
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 10);
tmrEnviarCAN.Start();
IsConnected = true;
}
}
if (_serialPort != null && !_serialPort.IsOpen)
{
_serialPort.Open();
}
}
private static async Task OuvirCAN()
{
if (_serialPort.BytesToRead > 0)
{
byte[] tempBuffer = await SerialService.LerDadosDaPortaSerialAsync(_serialPort, _serialPort.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, Dispositivo = mensagensPorID.FirstOrDefault()?.Dispositivo ?? T_Code.Vzo };
}
mensagem.DataRx = data;
mensagem.Respondido = true;
mensagem.RespondidoEm = DateTime.Now;
if (_roteadores.TryGetValue(mensagem.Dispositivo, 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 (_serialPort?.IsOpen != true) return;
var dados = new List<byte> { funcCodeTx };
if (payload != null)
dados.AddRange(payload);
byte crc = CalcularChecksum(id, dados.ToArray());
dados.Add(crc);
//Console.WriteLine($"[CAN OUT] ID: {id:X2} | Func: {funcCode:X2} | Payload: {BitConverter.ToString(dados.ToArray())}");
var msg = new CanMessage
{
Momento = DateTime.Now,
Dispositivo = Dispositivo,
Id = id,
funcCodeTx = funcCodeTx,
funcCodeRx = funcCodeRx,
DataTx = dados.ToArray(),
ComResposta = get
};
lock (Mensagens)
{
if (Mensagens.Any(x =>
x.Id == msg.Id && // do mesmo Id
x.funcCodeTx == msg.funcCodeTx && // da mesma funcao
x.DataTx.SequenceEqual(msg.DataTx) && // com os mesmos comandos
(!x.Enviado || (x.ComResposta && !x.Respondido)) // ainda nao enviadas ou que aguarda uma resposta
))
{
return;
}
else
{
Mensagens.Add(msg);
}
}
}
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()
{
LimparMensagensAntigas();
if (_serialPort?.IsOpen != true) return;
CanMessage msg = ObterProximaMensagem();
if (msg != null)
{
string frame = CriarFrame(msg);
if (_ultimoIdEnviado.HasValue && msg.Id == _ultimoIdEnviado.Value)
{
await Task.Delay(50);
}
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 (_serialPort?.IsOpen == true)
{
_serialPort.Write(comando);
Console.WriteLine($"[CAN] Frame enviado: {comando}");
}
}
private static void LimparMensagensAntigas()
{
lock (Mensagens)
{
var limite = DateTime.Now.AddSeconds(-10);
Mensagens.RemoveAll(x =>
(!x.ComResposta && x.Enviado) || // mensagens enviadas que nao exigem resposta
(x.Respondido && x.RespondidoEm < limite) || // mensagens respondidas a mais de 5 segundos
(x.Enviado && x.EnviadoEm < limite && !x.Respondido) // mensagens enviadas e nao respondidas a mais de 5 segundos
);
}
}
public static void Fechar()
{
tmrOuvirCAN?.Dispose();
tmrEnviarCAN?.Dispose();
lock (Mensagens)
{
Mensagens.Clear();
}
SerialCANBuffer.Clear();
_roteadores.Clear();
_ultimoIdEnviado = null;
if (_serialPort?.IsOpen == true)
{
EnviarComando("C\r");
_serialPort.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);
}
}
}