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 _filaTx = new ConcurrentQueue(); private static readonly ConcurrentQueue<(bool, byte[])> _filaRx = new ConcurrentQueue<(bool, byte[])>(); private static List _bufferAtual = new List(); 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 CodigosFuncoes = new Dictionary() { { "ComandoConfiguracao", 0xc0 }, { "RespostaConfiguracao", 0xc1 }, { "Mensagem", 0xe1 }, }; public static Dictionary CodigosTipoTransmissao = new Dictionary() { { "Normal", 0x00 }, { "Fixed", 0x40 }, }; public static Dictionary CodigosBaudRates = new Dictionary() { { 1200, 0x00 }, { 2400, 0x20 }, { 4800, 0x40 }, { 9600, 0x60 }, { 19200, 0x80 }, { 38400, 0xa0 }, { 57600, 0xc0 }, { 115200, 0xe0 }, }; public static Dictionary CodigosAirRates = new Dictionary() { { 2.4, 0x00 }, { 4.8, 0x01 }, { 9.6, 0x02 }, { 19.2, 0x03 }, { 38.4, 0x04 }, { 62.5, 0x05 }, }; public static Dictionary CodigosPacketSizes = new Dictionary() { { 200, 0x00 }, { 128, 0x40 }, { 64, 0x80 }, { 32, 0xc0 }, }; public static Dictionary CodigosWorCycles = new Dictionary() { { 500, 0x00 }, { 1000, 0x01 }, { 1500, 0x02 }, { 2000, 0x03 }, { 2500, 0x04 }, { 3000, 0x05 }, { 3500, 0x06 }, { 4000, 0x07 }, }; public static Dictionary CodigosPowers = new Dictionary() { { 22, 0x00 }, { 17, 0x01 }, { 13, 0x02 }, { 10, 0x03 }, }; private static List MensagensRecebidas = new List(); 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 LeituraDadosOperacao = new List(); private static AsyncTaskTimerModel tmrEnvio; private static AsyncTaskTimerModel tmrPing; public static async Task 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 EnviarDadosLoRa(byte[] bytes, byte targetAddress) { List Partes = new List(); 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 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); } } } }