agrobot_base/AgroBase/AgroBase/Services/LoRaBaseService.cs

699 lines
26 KiB
C#

using AgroBase.Models;
using AgroMonitor;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using static AgroBase.Models.Enums;
using static AgroBase.Services.LoRaEspService;
namespace AgroBase.Services
{
public class LoRaBaseService
{
public static SerialPort _PortaLoRa;
public static bool Iniciado
{
get
{
if (_PortaLoRa != null && !_PortaLoRa.IsOpen)
{
try
{
_PortaLoRa.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
_PortaLoRa = null;
}
}
return _PortaLoRa != null && _PortaLoRa.IsOpen;
}
}
private static DateTime UltimaLeituraMensagem = DateTime.Now.AddMinutes(-1);
private static AsyncTaskTimerModel tmrLeitura;
private static AsyncTaskTimerModel tmrProcessamento;
private static ConcurrentQueue<byte[]> _filaTx = new ConcurrentQueue<byte[]>();
private static readonly ConcurrentQueue<(bool, byte[])> _filaRx = new ConcurrentQueue<(bool, byte[])>();
private static List<byte> _bufferAtual = new List<byte>();
private static bool _recebendo = false;
private static DateTime _inicioRecepcao;
private static TipoRecepcao _tipoAtual = TipoRecepcao.Nenhum;
private static int _tamanhoEsperadoConfig = 0;
private enum TipoRecepcao
{
Nenhum,
Config,
Mensagem
}
public static Dictionary<string, byte> CodigosFuncoes = new Dictionary<string, byte>()
{
{ "ComandoConfiguracao", 0xc0 },
{ "RespostaConfiguracao", 0xc1 },
{ "Mensagem", 0xe1 },
};
public static Dictionary<string, byte> CodigosTipoTransmissao = new Dictionary<string, byte>()
{
{ "Normal", 0x00 },
{ "Fixed", 0x40 },
};
public static Dictionary<int, byte> CodigosBaudRates = new Dictionary<int, byte>()
{
{ 1200, 0x00 },
{ 2400, 0x20 },
{ 4800, 0x40 },
{ 9600, 0x60 },
{ 19200, 0x80 },
{ 38400, 0xa0 },
{ 57600, 0xc0 },
{ 115200, 0xe0 },
};
public static Dictionary<double, byte> CodigosAirRates = new Dictionary<double, byte>()
{
{ 2.4, 0x00 },
{ 4.8, 0x01 },
{ 9.6, 0x02 },
{ 19.2, 0x03 },
{ 38.4, 0x04 },
{ 62.5, 0x05 },
};
public static Dictionary<int, byte> CodigosPacketSizes = new Dictionary<int, byte>()
{
{ 200, 0x00 },
{ 128, 0x40 },
{ 64, 0x80 },
{ 32, 0xc0 },
};
public static Dictionary<int, byte> CodigosWorCycles = new Dictionary<int, byte>()
{
{ 500, 0x00 },
{ 1000, 0x01 },
{ 1500, 0x02 },
{ 2000, 0x03 },
{ 2500, 0x04 },
{ 3000, 0x05 },
{ 3500, 0x06 },
{ 4000, 0x07 },
};
public static Dictionary<int, byte> CodigosPowers = new Dictionary<int, byte>()
{
{ 22, 0x00 },
{ 17, 0x01 },
{ 13, 0x02 },
{ 10, 0x03 },
};
private static List<LoRaMensagemModel> MensagensRecebidas = new List<LoRaMensagemModel>();
public static LoRaParametrosModel parametrosModel { get; set; } = new LoRaParametrosModel()
{
address = 0x01,
channel = 0x41,
tranMode = CodigosTipoTransmissao["Fixed"],
baudRate = CodigosBaudRates[9600],
airRate = CodigosAirRates[2.4],
packetSize = CodigosPacketSizes[32],
worCycle = CodigosWorCycles[2000],
power = CodigosPowers[22],
};
public static LoRaMensagemModel MensagemPendente
{
get
{
if (MensagensRecebidas.Any(x => !x.Lido && x.Momento > UltimaLeituraMensagem))
{
var Mensagem = MensagensRecebidas.OrderBy(x => x.Momento).Where(x => !x.Lido && x.Momento > UltimaLeituraMensagem).FirstOrDefault();
Mensagem.Lido = true;
return Mensagem;
}
return null;
}
}
public static int MensagensEnviadas { get; set; } = 0;
public static readonly object _dadosOperacaoLock = new object();
public static List<LoRaProtocoloTransmissaoModel> LeituraDadosOperacao = new List<LoRaProtocoloTransmissaoModel>();
private static AsyncTaskTimerModel tmrEnvio;
private static AsyncTaskTimerModel tmrPing;
public static async Task<bool> VerificaPortaLoRa(SerialPort porta)
{
if (!(tmrLeitura?.IsRunning ?? false))
{
tmrLeitura = new AsyncTaskTimerModel("tmrLeitura_LoRa", tmrLeitura_Tick, 5);
tmrLeitura.Start();
}
if (!(tmrProcessamento?.IsRunning ?? false))
{
tmrProcessamento = new AsyncTaskTimerModel("tmrProcessamento_LoRa", tmrProcessamento_Tick, 10);
tmrProcessamento.Start();
}
porta.Close();
_PortaLoRa = new SerialPort();
_PortaLoRa.BaudRate = 9600;
_PortaLoRa.PortName = porta.PortName;
var comando = ComandoRequisitarParametros();
DateTime Inicio = DateTime.Now;
if (!EnviarComando(comando))
{
_PortaLoRa.Close();
return false;
}
bool Respondido() => parametrosModel.UltimaLeitura > Inicio;
await FuncoesGlobais.AguardarCondicaoAsync(Respondido, 1000, 100);
if (Respondido())
{
DefinirDispositivo();
return Iniciado;
}
_PortaLoRa.Close();
_PortaLoRa = null;
return false;
}
private static void DefinirDispositivo()
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Lra,
Endereco = _PortaLoRa.PortName,
Versao = "220"
});
IniciarRotinas();
}
public static void IniciarRotinas()
{
PararRotinas();
tmrEnvio = new AsyncTaskTimerModel("tmrEnvio", tmrEnvio_Tick, 50);
tmrEnvio.Start();
tmrPing = new AsyncTaskTimerModel("tmrPing", tmrPing_Tick, VariaveisEquipamento.TempoEntrePingsConexao);
tmrPing.Start();
}
public static void PararRotinas()
{
tmrEnvio?.Dispose();
tmrPing?.Dispose();
}
private static async Task tmrLeitura_Tick()
{
while ((_PortaLoRa?.IsOpen ?? false) && (_PortaLoRa?.BytesToRead ?? 0) > 0)
{
byte b = (byte)_PortaLoRa.ReadByte();
if (!_recebendo)
{
if (b == CodigosFuncoes["RespostaConfiguracao"])
{
_recebendo = true;
_bufferAtual.Clear();
_bufferAtual.Add(b);
_tipoAtual = TipoRecepcao.Config;
_inicioRecepcao = DateTime.Now;
}
else if (b == (byte)LoRaCaracteresEspeciais.BeginMsg)
{
_recebendo = true;
_bufferAtual.Clear();
_bufferAtual.Add(b);
_tipoAtual = TipoRecepcao.Mensagem;
_inicioRecepcao = DateTime.Now;
}
}
else
{
_bufferAtual.Add(b);
if (_tipoAtual == TipoRecepcao.Config)
{
if (_bufferAtual.Count == 3)
{
_tamanhoEsperadoConfig = _bufferAtual[2];
}
else if (_bufferAtual.Count == 3 + _tamanhoEsperadoConfig)
{
_filaRx.Enqueue((true, _bufferAtual.ToArray()));
_bufferAtual.Clear();
_recebendo = false;
_tipoAtual = TipoRecepcao.Nenhum;
}
}
else if (_tipoAtual == TipoRecepcao.Mensagem)
{
if (b == (byte)LoRaCaracteresEspeciais.EndMsg)
{
_filaRx.Enqueue((false, _bufferAtual.ToArray()));
_bufferAtual.Clear();
_recebendo = false;
_tipoAtual = TipoRecepcao.Nenhum;
}
}
}
// ⏱️ Timeout de segurança
if (_recebendo && (DateTime.Now - _inicioRecepcao).TotalMilliseconds > 500)
{
_bufferAtual.Clear();
_recebendo = false;
_tipoAtual = TipoRecepcao.Nenhum;
}
}
}
private static async Task tmrProcessamento_Tick()
{
while (_filaRx.TryDequeue(out var item))
{
if (item.Item1)
{
ProcessarRespostaConfiguracao(item.Item2);
}
else
{
ProcessarMensagemLoRaCAN(item.Item2);
}
}
}
private static async Task tmrEnvio_Tick()
{
if (_filaTx.TryDequeue(out var buffer))
{
EnviarComando(buffer);
await Task.Delay(100);
}
}
private static void ProcessarRespostaConfiguracao(byte[] buffer)
{
Console.WriteLine($"Dados de parametrizacao recebidos do modulo: {FuncoesGlobais.ConverterComandoBytesParaTexto(buffer)}");
int numBytes = buffer[2];
if (numBytes != 6 && numBytes != 11)
{
Console.WriteLine("Número de bytes de dados não esperado.");
return;
}
byte[] data = buffer;
byte address = data[4];
byte baudAndAirRate = data[5];
byte baud = (byte)(baudAndAirRate & 0b11100000);
byte airRate = (byte)((baudAndAirRate & 0b00011100) >> 2);
byte packetSizeAndPower = data[6];
byte packetSize = (byte)((packetSizeAndPower & 0b11000000));
byte power = (byte)(packetSizeAndPower & 0b00000011);
byte channel = data[7];
byte tranModeAndWorCycle = data[8];
byte tranMode = (byte)(tranModeAndWorCycle & 0b01000000);
byte worCycle = (byte)(tranModeAndWorCycle & 0b00000111);
if (parametrosModel == null)
{
parametrosModel = new LoRaParametrosModel();
}
parametrosModel.address = address;
parametrosModel.baudRate = baud;
parametrosModel.airRate = airRate;
parametrosModel.packetSize = packetSize;
parametrosModel.power = power;
parametrosModel.channel = channel;
parametrosModel.tranMode = tranMode;
parametrosModel.worCycle = worCycle;
parametrosModel.UltimaLeitura = DateTime.Now;
Console.WriteLine("Parâmetros atualizados com sucesso.");
}
private static void ProcessarMensagemLoRaCAN(byte[] mensagem)
{
if (mensagem.Length < 6) return; // Tamanho mínimo: Begin + Size + Remetente + Dest + Check + End
byte size = mensagem[1];
if (mensagem.Length != size + 6) return; // Tamanho real esperado
byte remetente = mensagem[2];
byte destinatario = mensagem[3];
byte[] dados = mensagem.Skip(4).Take(size).ToArray();
byte checksum = mensagem[4 + size];
byte end = mensagem[5 + size];
if (checksum != (byte)(dados.Sum(x => x) % 256)) return;
if (end != (byte)LoRaCaracteresEspeciais.EndMsg) return;
// Aqui você pode aplicar a lógica CAN-style, como extrair posição, idNum, etc.
// Exemplo:
if (dados.Length >= 2)
{
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)dados[0];
byte idNum = dados[1];
Console.WriteLine($"Dados LoRa recebidos do rover {posicao.ToString()} ({((DadosLoRaParse)posicao).ToString()}) " + string.Join(" ", dados));
LoRaProtocoloTransmissaoModel equipamento = LeituraDadosOperacao.FirstOrDefault(x => x.EnderecoCarro == remetente);
if (equipamento == null)
{
equipamento = new LoRaProtocoloTransmissaoModel()
{
EnderecoCarro = remetente,
Coordenadas = new LoRaProtocoloTransmissaoCoordenadasModel(),
CodigoFalha = 0,
Momento = DateTime.Now,
Operacao = new LoRaProtocoloTransmissaoOperacaoModel(),
Sensores = new LoRaProtocoloTransmissaoSensoresModel(),
RecebidoEm = DateTime.Now,
};
LeituraDadosOperacao.Add(equipamento);
}
equipamento.RecebidoEm = DateTime.Now;
switch ((DadosLoRaParse)posicao)
{
case DadosLoRaParse.GpsLatitude:
{
equipamento.Coordenadas.Latitude = LoRaSerializer.DescompactarCoordenada(dados.Skip(2).ToArray());
break;
}
case DadosLoRaParse.GpsLongitude:
{
equipamento.Coordenadas.Longitude = LoRaSerializer.DescompactarCoordenada(dados.Skip(2).ToArray());
break;
}
case DadosLoRaParse.GpsGerais:
{
double _altitude = LoRaSerializer.DescompactarDecimal(dados, 2);
double _velocidade = LoRaSerializer.DescompactarDecimal(dados, 4);
double _orientacao = LoRaSerializer.DescompactarDecimal(dados, 6);
equipamento.Coordenadas.Orientacao = _orientacao;
equipamento.Coordenadas.Altitude = _altitude;
equipamento.Coordenadas.Velocidade = _velocidade;
break;
}
case DadosLoRaParse.GpsQualidade:
{
bool _inicializado = dados[2] == 1;
TiposCorrecaoGPS _fix = (TiposCorrecaoGPS)((int)dados[3]);
int _n_satelites = (int)dados[4];
double _precisao = LoRaSerializer.DescompactarDecimal(dados, 5);
equipamento.Coordenadas.Inicializado = _inicializado;
equipamento.Coordenadas.Correcao = _fix;
equipamento.Coordenadas.NSatelites = _n_satelites;
equipamento.Coordenadas.Precisao = _precisao;
break;
}
case DadosLoRaParse.ComandoControle:
{
Direcao _direcao = (Direcao)((int)dados[2]);
int _velocidade_sp =(int)dados[3];
TipoMovimentoDirecional _tipo = (TipoMovimentoDirecional)((int)dados[4]);
int _angulo_sp = (int)dados[5];
int _velocidade_mp = (int)dados[6];
equipamento.Controle.direcao = _direcao;
equipamento.Controle.percentual_velocidade_sp = _velocidade_sp;
equipamento.Controle.tipo_movimento = _tipo;
equipamento.Controle.angulo_sp = _angulo_sp;
equipamento.Controle.velocidade_mp = _velocidade_mp;
break;
}
case DadosLoRaParse.DadosOperacao:
{
StatusOperacao _status_operacao = (StatusOperacao)((int)dados[2]);
StatusCarroMapa _status_carro = (StatusCarroMapa)((int)dados[3]);
int _percentual_operacao = (int)dados[4];
int _percentual_rua = (int)dados[5];
int _idx_rua = (int)dados[6];
equipamento.Operacao.statusOperacao = _status_operacao;
equipamento.Operacao.statusCarro = _status_carro;
equipamento.Operacao.PercentualOperacao = _percentual_operacao;
equipamento.Operacao.PercentualRua = _percentual_rua;
equipamento.Operacao.idxRuaAtual = _idx_rua;
break;
}
case DadosLoRaParse.DadosSensores:
{
int _percentual_bateria = (int)dados[2];
int _temperatura_motores = (int)dados[3];
int _temperatura_campo = (int)dados[4];
int _distancia_percorrida = (int)dados[5];
int _velocidade = (int)dados[6];
equipamento.Sensores.PercentualBateria = _percentual_bateria;
equipamento.Sensores.TemperaturaMotores = _temperatura_motores;
equipamento.Sensores.TemperaturaCampo = _temperatura_campo;
equipamento.Sensores.DistanciaPercorrida = _distancia_percorrida;
equipamento.Sensores.Velocidade = _velocidade;
break;
}
case DadosLoRaParse.DadosPulverizador:
{
int _percentual_reservatorio = (int)dados[2];
int _pressao_linha = (int)dados[3];
int _ervas_identificadas = (int)dados[4];
int _herbicida_consumido = (int)dados[5];
int _herbicida_por_erva = (int)dados[6];
int _percentual_ervas_terreno = (int)dados[7];
equipamento.Sensores.PercentualReservatorio = _percentual_reservatorio;
equipamento.Sensores.PressaoLinha = _pressao_linha;
equipamento.Sensores.ErvasIdentificadas = _ervas_identificadas;
equipamento.Sensores.HerbicidaConsumido = _herbicida_consumido;
equipamento.Sensores.HerbicidaPorErva = _herbicida_por_erva;
equipamento.Sensores.PercentualErvasTerreno = _percentual_ervas_terreno;
break;
}
case DadosLoRaParse.DadosControle:
{
break;
}
case DadosLoRaParse.DadosLivre:
{
MensagensRecebidas.Add(new LoRaMensagemModel()
{
channel = parametrosModel.channel,
Destinatario = destinatario,
Remetente = remetente,
Data = dados,
Lido = false,
Momento = DateTime.Now,
Mensagem = string.Join("", dados.Select(x => x.ToString("X")))
});
break;
}
}
}
}
public static async Task<(bool, LoRaParametrosModel)> RequisitarParametrosModuloAsync()
{
byte[] command = ComandoRequisitarParametros();
DateTime inicio = DateTime.Now;
// Envia o comando para a porta COM
EnviarComando(command);
bool Respondido() => parametrosModel.UltimaLeitura > inicio;
await FuncoesGlobais.AguardarCondicaoAsync(Respondido, 1000, 100);
return (Respondido(), parametrosModel);
}
public static async Task<(bool, LoRaParametrosModel)> ConfigurarModuloAsync(LoRaParametrosModel Parametros)
{
parametrosModel = Parametros;
byte[] command = ComandoConfigurarModulo();
Console.WriteLine($"Comando de configuracao do LoRa enviado: {FuncoesGlobais.ConverterComandoBytesParaTexto(command)}");
DateTime inicio = DateTime.Now;
// Envia o comando para a porta COM
EnviarComando(command);
bool Respondido() => parametrosModel.UltimaLeitura > inicio;
await FuncoesGlobais.AguardarCondicaoAsync(Respondido, 1000, 100);
if (Respondido())
{
return (true, parametrosModel);
}
else
{
return (false, null);
}
}
public static List<byte[]> EnviarDadosLoRa(byte[] bytes, byte targetAddress)
{
List<byte[]> Partes = new List<byte[]>();
byte addressHigh = (byte)(targetAddress == 0xFF ? 0xFF : 0x00);
byte channel = parametrosModel.channel;
byte remetente = parametrosModel.address;
int maxPacketSize = CodigosPacketSizes.FirstOrDefault(x => x.Value == parametrosModel.packetSize).Key - 10;
int overhead = 1 + 1 + 1 + 1 + 1 + 1; // Begin + Size + Remetente + Dest + Checksum + End
int maxDataLength = maxPacketSize - overhead;
for (int i = 0; i < bytes.Length; i += maxDataLength)
{
int length = Math.Min(maxDataLength, bytes.Length - i);
byte[] dataFragment = new byte[length];
Array.Copy(bytes, i, dataFragment, 0, length);
byte checksum = (byte)(dataFragment.Sum(b => b) % 256);
byte prefix = (byte)LoRaCaracteresEspeciais.BeginMsg;
byte suffix = (byte)LoRaCaracteresEspeciais.EndMsg;
byte dataSize = (byte)dataFragment.Length;
byte[] mensagem = new byte[3 + 1 + 1 + 1 + 1 + length + 1 + 1]; // 3 físicos + protocolo
mensagem[0] = addressHigh;
mensagem[1] = targetAddress;
mensagem[2] = channel;
mensagem[3] = prefix;
mensagem[4] = dataSize;
mensagem[5] = remetente;
mensagem[6] = targetAddress;
Array.Copy(dataFragment, 0, mensagem, 7, length);
mensagem[7 + length] = checksum;
mensagem[8 + length] = suffix;
Partes.Add(mensagem);
}
foreach (var parte in Partes)
{
_filaTx.Enqueue(parte);
}
return Partes;
}
private static bool EnviarComando(byte[] Comando)
{
try
{
if (_PortaLoRa != null)
{
if (!_PortaLoRa.IsOpen)
{
_PortaLoRa.Open();
}
if (_PortaLoRa.IsOpen)
{
Console.WriteLine($"Dados enviados LoRa: {Comando.Length}");
MensagensEnviadas++;
return SerialService.EnviarDadosPortaSerial(_PortaLoRa, Comando, 0, Comando.Length);
}
}
}
catch (Exception ex)
{
}
return false;
}
private static byte[] ComandoConfigurarModulo()
{
byte[] command = new byte[] {
CodigosFuncoes["ComandoConfiguracao"],
0x00,
0x06,
0x00,
parametrosModel.address,
parametrosModel.baudAndAirRate,
parametrosModel.packetSizeAndPower,
parametrosModel.channel,
parametrosModel.tranModeAndWorCycle,
};
return command;
}
public static byte[] ComandoRequisitarParametros()
{
// caractere GET parametros, iniciando em qual registrador, quantidade registros
byte[] command = new byte[] { CodigosFuncoes["RespostaConfiguracao"], 0x00, 0x0b };
return command;
}
public static async Task tmrPing_Tick()
{
if (VariaveisMonitoramento.EnviarDadosEmBroadcast)
{
EnviarDadosPosicao();
}
else
{
foreach (var Equipamento in VariaveisMonitoramento.EquipamentosConectados.Where(x => x.Key != parametrosModel.address))
{
EnviarDadosPosicao(Equipamento.Key);
}
}
}
private static void EnviarDadosPosicao(byte EnderecoDestinatario = 0xFF)
{
if (Iniciado)
{
List<byte[]> dadosEnvio = LoRaSerializer.SerializeGPS(GPSService.UltimaLeitura);
foreach (var payload in dadosEnvio)
{
EnviarDadosLoRa(payload, EnderecoDestinatario);
}
}
}
public static void EnviarDadosComandoControle(OperacaoControleBaseModel Controle, byte EnderecoDestinatario)
{
if (Iniciado)
{
byte[] dadosEnvio = LoRaSerializer.SerializeComandoControle(Controle);
EnviarDadosLoRa(dadosEnvio, EnderecoDestinatario);
}
}
}
}