ajustes na firmware can e lora async

This commit is contained in:
Diego Freitas 2025-04-30 18:49:17 -03:00
parent f82131569d
commit fcd545a5b5
42 changed files with 2198 additions and 181 deletions

Binary file not shown.

View File

@ -503,6 +503,7 @@
<Compile Include="Services\IDispositivosService.cs" />
<Compile Include="Services\IFirmwareSensorBase.cs" />
<Compile Include="Services\KinectService.cs" />
<Compile Include="Services\LoRaEspService.cs" />
<Compile Include="Services\LoRaSerializer.cs" />
<Compile Include="Services\LoRaService.cs" />
<Compile Include="Services\MapasService.cs" />

View File

@ -212,8 +212,8 @@ namespace AgroBase.Forms.IHM
return;
}
var Sucesso = await Variaveis.ConexaoLoRa.ConfigurarModuloAsync(Parametros);
if (Sucesso != null)
(bool Sucesso, LoRaParametrosModel par) = await Variaveis.ConexaoLoRa.ConfigurarModuloAsync(Parametros);
if (Sucesso)
{
CustomDialog.ShowDialog(
"Sucesso",

View File

@ -837,7 +837,7 @@ namespace AgroBase.Forms.IHM
bool Sucesso = false;
while (!Sucesso && Inicio.AddMilliseconds(Timeout) > DateTime.Now)
{
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx);
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx, ServoFreio.ID_Num, CanMessagePosicaoDados.Dados1);
await Task.Delay(1000);
Sucesso =
@ -1453,7 +1453,7 @@ namespace AgroBase.Forms.IHM
var novoEstado = Ligar ? Estado.Ligado : Estado.Desligado;
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sRLE, Rele.ID, novoEstado);
await Task.Delay(Delay);
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx);
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx, Rele.ID_Num, CanMessagePosicaoDados.Dados1);
await Task.Delay(Delay);
}
@ -1714,7 +1714,7 @@ namespace AgroBase.Forms.IHM
Comportamento.statusLED = Ligar ? StatusLED.Aceso : StatusLED.Apagado;
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sLED, Sinaleiro.ID, Comportamento);
await Task.Delay(Delay);
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx);
Dispositivo.Dados.RequisitarDadosModulo(F_Code.ReqTx, Sinaleiro.ID_Num, CanMessagePosicaoDados.Dados1);
await Task.Delay(Delay);
}

View File

@ -1,6 +1,7 @@
using AgroBase.Models;
using AgroBase.Services;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO.Ports;
using System.Linq;
@ -11,7 +12,6 @@ namespace AgroBase.Forms
{
public partial class frmLoraConfig : Form
{
LoRaService loraService = Variaveis.ConexaoLoRa;
LoRaParametrosModel Parametros = null;
AsyncTaskTimerModel tmrLeitura;
@ -34,14 +34,22 @@ namespace AgroBase.Forms
private async Task AtualizarParametros()
{
var (sucesso, par) = await loraService.RequisitarParametrosModuloAsync();
(bool sucesso, LoRaParametrosModel par) = (false, null);
if (Variaveis.IsAgroMonitor)
{
(sucesso, par) = await Variaveis.ConexaoLoRa.RequisitarParametrosModuloAsync();
}
else
{
(sucesso, par) = await Variaveis.LoraService?.RequisitarParametrosModulo();
}
if (!sucesso)
{
MessageBox.Show("Erro ao requisitar parâmetros! Verifique o modo do módulo LoRa!");
}
Parametros = par;
Parametros = par.Clone();
txtEndereco.Text = ((int)Parametros.address).ToString();
txtCanal.Text = ((int)Parametros.channel).ToString();
@ -72,7 +80,7 @@ namespace AgroBase.Forms
{
if (LoRaService.Iniciado)
{
var MensagemDisponivel = loraService.MensagemPendente;
var MensagemDisponivel = Variaveis.ConexaoLoRa.MensagemPendente;
if (MensagemDisponivel != null)
{
AtualizarConsole(MensagemDisponivel.Remetente, MensagemDisponivel.Destinatario, MensagemDisponivel.Mensagem);
@ -131,8 +139,16 @@ namespace AgroBase.Forms
private async Task ConfigurarModulo()
{
var Sucesso = await loraService.ConfigurarModuloAsync(Parametros);
if (Sucesso != null)
(bool Sucesso, LoRaParametrosModel par) = (false, null);
if (Variaveis.IsAgroMonitor)
{
(Sucesso, par) = await Variaveis.ConexaoLoRa.ConfigurarModuloAsync(Parametros);
}
else
{
(Sucesso, par) = await Variaveis.LoraService?.RedefinirParametros(Parametros);
}
if (Sucesso)
{
MessageBox.Show("Módulo configurado com sucesso!");
}
@ -147,8 +163,50 @@ namespace AgroBase.Forms
{
if (LoRaService.Iniciado)
{
LoRaService.MontarMensagemEnvio(txtMensagem.Text, Convert.ToByte(txtEnderecoDestino.Text), LoRaTipoProtocolo.MensagemTexto, true);
AtualizarConsole(Parametros.address, Convert.ToByte(txtEnderecoDestino.Text), txtMensagem.Text);
bool valido = byte.TryParse(txtEnderecoDestino.Text, out byte destinatario);
if (!valido)
{
MessageBox.Show("Preencha o destinatario corretamente!");
return;
}
if (Variaveis.IsAgroMonitor)
{
LoRaService.MontarMensagemEnvio(txtMensagem.Text, destinatario, LoRaTipoProtocolo.MensagemTexto, true);
}
else
{
try
{
// Remove espaços extras e divide por espaço
string[] partes = txtMensagem.Text.Trim().Split(' ');
// Converte cada string para byte
List<byte> payload = new List<byte>();
foreach (var parte in partes)
{
if (byte.TryParse(parte, System.Globalization.NumberStyles.HexNumber, null, out byte valor))
{
payload.Add(valor);
}
else
{
MessageBox.Show($"Valor inválido: '{parte}'", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
}
// Envia usando seu método
bool success = Variaveis.LoraService.EnviarMensagem(CanMessagePosicaoDados.Dados2, payload.ToArray());
MessageBox.Show("Dados enviados com sucesso!");
}
catch (Exception ex)
{
MessageBox.Show($"Erro ao enviar: {ex.Message}", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
AtualizarConsole(Parametros.address, destinatario, txtMensagem.Text);
}
}

View File

@ -90,6 +90,11 @@ namespace AgroBase.Forms
ID = x.ID,
Funcoes = x.Funcoes
}));
Dispositivos.Add(new DispositivoDataModel()
{
ID = Disp.Dados.loRaService.ID,
Funcoes = Disp.Dados.loRaService.Funcoes
});
break;
}
case T_Code.Atu:

View File

@ -110,6 +110,8 @@
sPOT = 23,
sMOD = 24,
sIMU = 25,
sLRA = 26,
sTOD = 27,
}
public enum BotoesJoystick

View File

@ -27,9 +27,7 @@ namespace AgroBase.Models
{
get
{
string _baudRate = Convert.ToInt32(baudRate.ToString("X")).ToString("00");
string _airRate = Convert.ToInt32(airRate.ToString("X")).ToString("00");
return Convert.ToByte(_baudRate[0].ToString() + _airRate[1].ToString(), 16);
return (byte)(baudRate | (airRate << 2));
}
}
public byte parity { get; set; }
@ -39,9 +37,7 @@ namespace AgroBase.Models
{
get
{
string _packetSize = Convert.ToInt32(packetSize.ToString("X")).ToString("00");
string _power = Convert.ToInt32(power.ToString("X")).ToString("00");
return Convert.ToByte(_packetSize[0].ToString() + _power[1].ToString(), 16);
return (byte)((packetSize & 0b11000000) | (power & 0b00000011));
}
}
public byte tranMode { get; set; }
@ -50,9 +46,7 @@ namespace AgroBase.Models
{
get
{
string _tranMode = Convert.ToInt32(tranMode.ToString("X")).ToString("00");
string _worCycle = Convert.ToInt32(worCycle.ToString("X")).ToString("00");
return Convert.ToByte(_tranMode[0].ToString() + _worCycle[1].ToString(), 16);
return (byte)((tranMode & 0b01000000) | (worCycle & 0b00000111));
}
}
public byte channelRssi { get; set; }
@ -61,6 +55,27 @@ namespace AgroBase.Models
public byte address { get; set; }
public byte channel { get; set; }
public byte key { get; set; }
public LoRaParametrosModel Clone()
{
return new LoRaParametrosModel()
{
address = address,
airRate = airRate,
baudRate = baudRate,
channel = channel,
channelRssi = channelRssi,
key = key,
lbt = lbt,
packetRssi = packetRssi,
packetSize = packetSize,
parity = parity,
power = power,
tranMode = tranMode,
worCycle = worCycle,
UltimaLeitura = UltimaLeitura
};
}
}
public class LoRaMensagemModel

View File

@ -383,7 +383,7 @@ namespace AgroBase.Models.Modules
CanService.DefinirPortaCOM(porta);
}
CanService.RegistrarHandler(_EnderecoCAN, _AtuHandler);
CanService.RegistrarHandler((byte)((int)_EnderecoCAN + 1), _AtuHandler);
if (CanService.IsConnected)
{
@ -1380,7 +1380,7 @@ namespace AgroBase.Models.Modules
{
byte ID_Num = mensagem.DataRx[0];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)mensagem.DataRx[1];
byte id = mensagem.Id;
byte id = mensagem.IdTx;
var data = mensagem.DataRx;
var item = Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura;

View File

@ -406,7 +406,10 @@ namespace AgroBase.Models.Modules
ModbusRO,
ModbusDE_RE,
ServoFreio,
Potenciometro
Potenciometro,
M0,
M1,
Aux,
}
public static class PinoutUtils

View File

@ -49,6 +49,7 @@ namespace AgroBase.Models.Modules
public UltrasonicA05Configuracoes ConfiguracaoSensorUltrassom { get; set; }
public SensoriamentoMensagemJsonModel DadosLeitura { get; set; } = new SensoriamentoMensagemJsonModel();
private SenHandler _SenHandler = new SenHandler();
public LoRaEspService loRaService { get; set; } = new LoRaEspService();
public LoRaParametrosModel LoRaParametrosBase { get; set; } = new LoRaParametrosModel()
{
address = 0x01,
@ -700,7 +701,24 @@ namespace AgroBase.Models.Modules
},
_AnguloControle = 180,
},
}
},
loRaService = new LoRaEspService()
{
ID = "LoRa",
ID_Num = Variaveis.ID_Num_sLRA,
Componente = S_Code.sLRA,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.M0,
FuncoesPinout.M1,
FuncoesPinout.Aux,
FuncoesPinout.RX,
FuncoesPinout.TX
},
Conectado = false,
Configurado = false,
Parametros = new LoRaParametrosModel()
},
};
for (int i = 0; i < Variaveis.OperacaoEmAndamento.QuantidadeCamerasSolo; i++)
@ -724,6 +742,9 @@ namespace AgroBase.Models.Modules
{
x._AnguloControle = x.Incremental ? 0 : 180;
});
loRaService.Conectado = false;
loRaService.Configurado = false;
loRaService.Parametros.UltimaLeitura = DateTime.MinValue;
ReiniciarLeituras(Dados);
}
@ -755,11 +776,11 @@ namespace AgroBase.Models.Modules
CanService.DefinirPortaCOM(porta);
}
CanService.RegistrarHandler(_EnderecoCAN, _SenHandler);
CanService.RegistrarHandler((byte)((int)_EnderecoCAN + 1), _SenHandler);
if (CanService.IsConnected)
{
RequisitarDadosModulo(F_Code.ChkTx);
RequisitarDadosModulo(F_Code.ChkTx, Variaveis.ID_Num_sMOD);
await Task.Delay(1000);
@ -797,9 +818,9 @@ namespace AgroBase.Models.Modules
}
}
public void RequisitarDadosModulo(F_Code dado, bool aguardaResposta = true)
public void RequisitarDadosModulo(F_Code f_code, int ID_Num, CanMessagePosicaoDados posicao = CanMessagePosicaoDados.Status, bool aguardaResposta = true)
{
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(dado, null, aguardaResposta);
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(f_code, new byte[] { (byte)ID_Num, (byte)posicao }, aguardaResposta);
}
public List<byte[]> ProtocoloConfiguracao(bool Conectar)
@ -832,6 +853,12 @@ namespace AgroBase.Models.Modules
List<byte[]> sensoresConfig = SensoresConfig(Conectar, configurarTodos);
config.AddRange(sensoresConfig);
// Adicionar configurações do LoRa
if (!loRaService.Conectado || !loRaService.Configurado || configurarTodos)
{
config.AddRange(loRaService.ProtocoloConfiguracao(Pinout, Conectar));
}
return config;
}
@ -845,6 +872,7 @@ namespace AgroBase.Models.Modules
(byte)(Conectar ? 1 : 0),
(byte)(_TaxaAmostragem / 10),
(byte)(RequisitarDados ? 1 : 0),
LoRaParametrosBase.address
};
var config2 = new List<byte>
@ -981,7 +1009,7 @@ namespace AgroBase.Models.Modules
{
if (RequisitarDados)
{
RequisitarDadosModulo(F_Code.ReqTx, false);
RequisitarDadosModulo(F_Code.ReqTx, Variaveis.ID_Num_sTOD, CanMessagePosicaoDados.DadosAll, false);
}
VerificaControleLeitura();
@ -2016,6 +2044,7 @@ namespace AgroBase.Models.Modules
private S_Code DetectarComponenteDoID_Num(int ID_Num)
{
if (ID_Num == Variaveis.ID_Num_sMOD) return S_Code.sMOD;
if (ID_Num == Variaveis.ID_Num_sLRA) return S_Code.sLRA;
var Disp = Variaveis.OperacaoEmAndamento.DispSen.Dados;
var sensor = Disp.Sensores.FirstOrDefault(x => x.ID_Num == ID_Num);
@ -2303,7 +2332,7 @@ namespace AgroBase.Models.Modules
{
byte ID_Num = mensagem.DataRx[0];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)mensagem.DataRx[1];
byte id = mensagem.Id;
byte id = mensagem.IdTx;
var data = mensagem.DataRx;
var item = Variaveis.OperacaoEmAndamento.DispSen.Dados.DadosLeitura;
@ -2316,22 +2345,38 @@ namespace AgroBase.Models.Modules
{
case CanMessagePosicaoDados.Status:
{
if (componente == S_Code.sMOD)
switch (componente)
{
T_Code tipoModulo = (T_Code)data[2];
bool conectado = data[3] == 1;
int versao = data[4];
case S_Code.sMOD:
{
T_Code tipoModulo = (T_Code)data[2];
bool conectado = data[3] == 1;
int versao = data[4];
Console.WriteLine($"[SEN] Resposta {ID_Num} recebida. Tipo={tipoModulo}, Conectado={conectado}, Versão={versao}");
Console.WriteLine($"[SEN] Resposta {ID_Num} recebida. Tipo={tipoModulo}, Conectado={conectado}, Versão={versao}");
item.Conectado = conectado;
item.Dispositivo = tipoModulo;
item.Versao = versao;
}
else
{
bool Iniciado = data[2] == 1;
DefinirOuCriar(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
item.Conectado = conectado;
item.Dispositivo = tipoModulo;
item.Versao = versao;
break;
}
case S_Code.sLRA:
{
bool Conectado = data[2] == 1;
bool Configurado = data[3] == 1;
Console.WriteLine($"[LRA] Resposta {ID_Num} recebida. Conectado={Conectado}, Configurado={Configurado}");
var loraService = Variaveis.OperacaoEmAndamento.DispSen.Dados.loRaService;
loraService.Conectado = Conectado;
loraService.Configurado = Configurado;
loraService.Parametros.UltimaLeitura = DateTime.Now;
break;
}
default:
{
bool Iniciado = data[2] == 1;
DefinirOuCriar(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
}
break;
}
@ -2381,6 +2426,32 @@ namespace AgroBase.Models.Modules
DefinirOuCriar(ID_Num, posicao, componente, new List<dynamic>() { roll, pitch, yaw });
break;
}
case S_Code.sLRA:
{
byte address = data[2];
byte baudAndAirRate = data[3];
byte baud = (byte)(baudAndAirRate & 0b11100000);
byte airRate = (byte)((baudAndAirRate & 0b00011100) >> 2);
byte packetSizeAndPower = data[4];
byte packetSize = (byte)((packetSizeAndPower & 0b11000000));
byte power = (byte)(packetSizeAndPower & 0b00000011);
byte channel = data[5];
byte tranModeAndWorCycle = data[6];
byte tranMode = (byte)(tranModeAndWorCycle & 0b01000000);
byte worCycle = (byte)(tranModeAndWorCycle & 0b00000111);
var loraService = Variaveis.OperacaoEmAndamento.DispSen.Dados.loRaService;
Console.WriteLine($"[LRA] Resposta {ID_Num} recebida. Address={address.ToString("X")}, BaudRate={baud.ToString("X")}, AirRate={airRate.ToString("X")}, PacketSize={packetSize.ToString("X")}, Power={power.ToString("X")}, Channel={channel.ToString("X")}, transMode={tranMode.ToString("X")}, WorCycle={worCycle.ToString("X")}");
loraService.Parametros.address = address;
loraService.Parametros.baudRate = baud;
loraService.Parametros.airRate = airRate;
loraService.Parametros.packetSize = packetSize;
loraService.Parametros.power = power;
loraService.Parametros.channel = channel;
loraService.Parametros.worCycle = worCycle;
loraService.Parametros.tranMode = tranMode;
loraService.Parametros.UltimaLeitura = DateTime.Now;
break;
}
default:
{
@ -2410,7 +2481,11 @@ namespace AgroBase.Models.Modules
DefinirOuCriar(ID_Num, posicao, componente, new List<dynamic>() { temperatura, pressao, altitude });
break;
}
case S_Code.sLRA:
{
Console.WriteLine("Dados LORA recebidos da base: " + FuncoesGlobais.ConverterComandoBytesParaTexto(data));
break;
}
default:
{
Console.WriteLine($"[SEN] Resposta de {ID_Num} na posicao {posicao.ToString()} recebida. Parametros nao mapeados: {FuncoesGlobais.ConverterComandoBytesParaTexto(data)}");

View File

@ -55,6 +55,13 @@ namespace AgroBase.Models
public static OperacaoModel OperacaoEmAndamento { get; set; } = new OperacaoModel(ModoOperacao.Manual);
public static MqttService MqttService { get; set; } = new MqttService("localhost", 1883);
public static LoRaService ConexaoLoRa { get; set; } = new LoRaService();
public static LoRaEspService LoraService
{
get
{
return OperacaoEmAndamento.DispSen?.Dados?.loRaService;
}
}
public static string FaixaIPwifi { get; set; } = "192.168.100";
public static string FaixaIPethernet { get; set; } = "192.168.105";
public static string IP_Host { get; set; } = FaixaIPwifi + ".105";
@ -68,7 +75,9 @@ namespace AgroBase.Models
}
}
public static byte ID_Num_sMOD { get; } = 0x00;
public static byte ID_Num_sMOD { get; } = 0;
public static byte ID_Num_sTOD { get; } = 250;
public static byte ID_Num_sLRA { get; } = 251;
}
public static class VariaveisPortas

View File

@ -15,7 +15,8 @@ namespace AgroBase.Services
{
public DateTime Momento { get; set; }
public T_Code Dispositivo { get; set; }
public byte Id { get; set; }
public byte IdTx { get; set; }
public byte IdRx { get; set; }
public byte funcCodeTx { get; set; }
public byte funcCodeRx { get; set; }
public byte[] DataTx { get; set; }
@ -30,11 +31,13 @@ namespace AgroBase.Services
public enum CanMessagePosicaoDados
{
Status = 0,
Dados1 = 1,
Dados2 = 2,
Dados3 = 3,
Dados4 = 4,
Dados5 = 5,
DadosAll = 1,
Dados1 = 2,
Dados2 = 3,
Dados3 = 4,
Dados4 = 5,
Dados5 = 6,
Dados6 = 7,
Config1 = 51,
Config2 = 52,
Config3 = 53,
@ -171,19 +174,19 @@ namespace AgroBase.Services
lock (Mensagens)
{
mensagem = Mensagens.Where(x => x.Id == id).OrderBy(x => x.EnviadoEm).FirstOrDefault(x => x.Enviado && !x.Respondido && x.funcCodeRx == funcCodeRx);
mensagem = Mensagens.Where(x => x.IdRx == id).OrderBy(x => x.EnviadoEm).FirstOrDefault(x => x.Enviado && !x.Respondido && x.funcCodeRx == funcCodeRx);
}
if (mensagem == null)
{
mensagem = new CanMessage { Id = (byte)id, DataRx = data };
mensagem = new CanMessage { IdTx = (byte)id, IdRx = (byte)id, DataRx = data };
}
mensagem.DataRx = data;
mensagem.Respondido = true;
mensagem.RespondidoEm = DateTime.Now;
if (_roteadores.TryGetValue(mensagem.Id, out var handler))
if (_roteadores.TryGetValue(mensagem.IdRx, out var handler))
{
handler.ProcessarMensagem(mensagem);
}
@ -198,7 +201,7 @@ namespace AgroBase.Services
}
}
public static void AdicionarMensagemNaFila(T_Code Dispositivo, byte id, byte funcCode, byte[] payload = null, bool get = true, byte ID_Num = 0x00)
public static void AdicionarMensagemNaFila(T_Code Dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true, byte ID_Num = 0x00)
{
if (_PortaCAN?.IsOpen != true) return;
@ -209,7 +212,7 @@ namespace AgroBase.Services
if (Dispositivo == T_Code.Mks)
{
byte crc = CalcularChecksum(id, dados.ToArray());
byte crc = CalcularChecksum(idTx, dados.ToArray());
dados.Add(crc);
}
@ -218,7 +221,8 @@ namespace AgroBase.Services
Momento = DateTime.Now,
Dispositivo = Dispositivo,
funcCodeTx = funcCode,
Id = id,
IdTx = idTx,
IdRx = idRx,
funcCodeRx = ID_Num,
DataTx = dados.ToArray(),
ComResposta = get
@ -230,7 +234,7 @@ namespace AgroBase.Services
LimparMensagensAntigas();
var existente = Mensagens.FirstOrDefault(x =>
x.Id == novaMensagem.Id &&
x.IdTx == novaMensagem.IdTx &&
x.funcCodeTx == novaMensagem.funcCodeTx &&
x.funcCodeRx == novaMensagem.funcCodeRx &&
x.ComResposta == novaMensagem.ComResposta &&
@ -256,7 +260,7 @@ namespace AgroBase.Services
{
var idsDisponiveis = Mensagens
.Where(x => !x.Enviado)
.Select(x => x.Id)
.Select(x => x.IdTx)
.Distinct()
.OrderBy(id => id)
.ToList();
@ -282,7 +286,7 @@ namespace AgroBase.Services
}
var msg = Mensagens
.Where(x => !x.Enviado && x.Id == proximoId)
.Where(x => !x.Enviado && x.IdTx == proximoId)
.OrderBy(x => x.Momento)
.FirstOrDefault();
@ -300,7 +304,7 @@ namespace AgroBase.Services
{
string frame = CriarFrame(msg);
if (_ultimoIdEnviado.HasValue && msg.Id == _ultimoIdEnviado.Value)
if (_ultimoIdEnviado.HasValue && msg.IdTx == _ultimoIdEnviado.Value)
{
await Task.Delay(5);
}
@ -311,14 +315,14 @@ namespace AgroBase.Services
msg.Enviado = true;
msg.EnviadoEm = DateTime.Now;
_ultimoIdEnviado = msg.Id;
_ultimoIdEnviado = msg.IdTx;
}
}
}
private static string CriarFrame(CanMessage msg)
{
string frame = $"t{msg.Id:X3}{msg.DataTx.Length}";
string frame = $"t{msg.IdTx:X3}{msg.DataTx.Length}";
foreach (byte b in msg.DataTx)
frame += b.ToString("X2");

View File

@ -75,7 +75,7 @@ namespace AgroBase.Comum
{
if ((int)funcao % 2 == 1)
{
CanService.AdicionarMensagemNaFila(Dispositivo, _EnderecoCAN, (byte)funcao, dados, get, ID_Num);
CanService.AdicionarMensagemNaFila(Dispositivo, _EnderecoCAN, (byte)((int)_EnderecoCAN + 1), (byte)funcao, dados, get, ID_Num);
}
}

View File

@ -0,0 +1,205 @@
using AgroBase.Models;
using AgroBase.Models.Modules;
using static AgroBase.Models.Enums;
using System.Collections.Generic;
using System.Linq;
using System;
using System.Threading.Tasks;
namespace AgroBase.Services
{
public class LoRaEspService
{
public string ID { get; set; }
public int ID_Num { get; set; }
public S_Code Componente { get; set; }
public bool Conectado { get; set; }
public bool Configurado { get; set; }
public bool Iniciado
{
get
{
return Conectado && Configurado;
}
}
public LoRaParametrosModel Parametros { get; set; }
public List<FuncoesPinout> Funcoes { get; set; }
public List<byte[]> ProtocoloConfiguracao(PinoutDataModel Pinout, bool conectar)
{
List<byte> config1 = new List<byte>
{
(byte)ID_Num,
(byte)CanMessagePosicaoDados.Config1,
};
var _pinout = Pinout.Clone();
_pinout.Pinos = _pinout.Pinos.Where(x => x.ComponenteID == ID).ToList();
if (_pinout.Pinos.Count > 0)
{
byte[] pinos = PinoutModel.PinoProtocolo(_pinout, Funcoes, false);
config1.AddRange(pinos);
}
List<byte> config2 = new List<byte>
{
(byte)ID_Num,
(byte)CanMessagePosicaoDados.Config2,
Parametros.address,
Parametros.baudAndAirRate,
Parametros.packetSizeAndPower,
Parametros.channel,
Parametros.tranModeAndWorCycle
};
return new List<byte[]>() { config1.ToArray(), config2.ToArray() };
}
public async Task<(bool, LoRaParametrosModel)> RequisitarParametrosModulo()
{
DateTime inicio = DateTime.Now;
bool sucesso = false;
Variaveis.OperacaoEmAndamento.DispSen?.Dados?.RequisitarDadosModulo(F_Code.CmdTx, Variaveis.ID_Num_sLRA, CanMessagePosicaoDados.Command1);
while (!sucesso && inicio.AddMilliseconds(2000) > DateTime.Now)
{
await Task.Delay(10);
sucesso = Parametros.UltimaLeitura < inicio;
}
return (sucesso, Parametros);
}
public async Task<(bool, LoRaParametrosModel)> RedefinirParametros(LoRaParametrosModel parametros)
{
Parametros = parametros.Clone();
Configurado = false;
DateTime inicio = DateTime.Now;
Variaveis.OperacaoEmAndamento.DispSen?.EnviarProtocolosConfiguracao(true);
while (!Configurado && inicio.AddMilliseconds(2000) > DateTime.Now)
{
await Task.Delay(10);
}
return (Configurado, Parametros);
}
public (bool, byte[]) EnviarDadosLoRa(DadosLoRaParse TipoDado)
{
(CanMessagePosicaoDados posicao, byte[] dados) = MontarMensagemLoRa(TipoDado);
bool sucesso = EnviarMensagem(posicao, dados);
return (sucesso, dados);
}
public bool EnviarMensagem(CanMessagePosicaoDados posicao, byte[] payload)
{
if (payload == null) return false;
if (payload.Length > 5) return false; // Payload muito grande para o nosso protocolo CAN-Style LoRa
List<byte> dados = new List<byte>
{
(byte)ID_Num,
(byte)posicao
};
dados.AddRange(payload);
Variaveis.OperacaoEmAndamento.DispSen?.AdicionarMensagemFila(F_Code.CmdTx, dados.ToArray(), false);
return true;
}
private (CanMessagePosicaoDados, byte[]) MontarMensagemLoRa(DadosLoRaParse TipoDado)
{
List<byte> dados = new List<byte>();
switch (TipoDado)
{
case DadosLoRaParse.GpsQualidade:
{
var Gps = Variaveis.OperacaoEmAndamento.Sensoriamento.Gps;
dados.Add((byte)(Gps.Inicializado ? 1 : 0)); // 1 byte
dados.Add((byte)Gps.QualidadeFix); // 1 byte
dados.Add((byte)Gps.NumeroSatelites); // 1 byte
dados.AddRange(CompactarDecimal(Gps.PrecisaoCm)); // 2 bytes
break;
}
case DadosLoRaParse.GpsLatitude:
{
var Gps = Variaveis.OperacaoEmAndamento.Sensoriamento.Gps;
dados.AddRange(CompactarCoordenada(Gps.Latitude)); // 4 bytes
break;
}
case DadosLoRaParse.GpsLongitude:
{
var Gps = Variaveis.OperacaoEmAndamento.Sensoriamento.Gps;
dados.AddRange(CompactarCoordenada(Gps.Longitude)); // 4 bytes
break;
}
case DadosLoRaParse.GpsGerais:
{
var Gps = Variaveis.OperacaoEmAndamento.Sensoriamento.Gps;
dados.Add((byte)Gps.VariacaoMagnetica); // 1 byte
dados.AddRange(CompactarDecimal(Gps.Altitude)); // 2 bytes
dados.AddRange(CompactarDecimal(Gps.Velocidade)); // 2 bytes
break;
}
case DadosLoRaParse.DadosControle:
{
var Controle = Variaveis.OperacaoEmAndamento.Sensoriamento.Controle;
dados.Add((byte)Controle.Direcao); // 1 byte
dados.Add((byte)Controle.PercentualVelocidadeSP); // 1 byte
dados.Add((byte)Controle.TipoMovimento); // 1 byte
dados.Add((byte)Controle.Angulo); // 1 byte
dados.Add((byte)Controle.VelocidadeMP); // 1 byte
break;
}
}
return ((CanMessagePosicaoDados)TipoDado, dados.ToArray());
}
private byte[] CompactarCoordenada(double valor)
{
int inteiro = (int)(valor * 1e7); // Multiplica para preservar precisão
byte[] bytes = BitConverter.GetBytes(inteiro); // 4 bytes (little endian)
return bytes;
}
private double DescompactarCoordenada(byte[] payload)
{
int inteiro = BitConverter.ToInt32(payload, 0); // 4 primeiros bytes
return inteiro / 1e7; // Voltar para graus decimais
}
private byte[] CompactarDecimal(double precisaoCm)
{
ushort valor = (ushort)Math.Round(precisaoCm); // Arredonda para inteiro
return BitConverter.GetBytes(valor); // 2 bytes (little endian)
}
private double DescompactarDecimal(byte[] payload, int offset)
{
ushort valor = BitConverter.ToUInt16(payload, offset); // Lê 2 bytes
return (double)valor; // Volta para double
}
public enum DadosLoRaParse
{
GpsQualidade = (int)CanMessagePosicaoDados.Dados2,
GpsLatitude = (int)CanMessagePosicaoDados.Dados3,
GpsLongitude = (int)CanMessagePosicaoDados.Dados4,
GpsGerais = (int)CanMessagePosicaoDados.Dados5,
DadosControle = (int)CanMessagePosicaoDados.Dados6,
}
public enum LoRaCaracteresEspeciais
{
BeginMsg = 0xAA,
BreakLine = 0xBB,
EndMsg = 0x55
}
}
}

View File

@ -1,5 +1,6 @@
using AgroBase.Models;
using AgroMonitor;
using OpenTK.Graphics.OpenGL;
using System;
using System.Collections.Generic;
using System.IO.Ports;
@ -8,6 +9,7 @@ using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using static AgroBase.Models.Enums;
using static AgroBase.Services.LoRaEspService;
namespace AgroBase.Services
{
@ -18,19 +20,26 @@ namespace AgroBase.Services
{
get
{
if (_PortaLoRa != null && !_PortaLoRa.IsOpen)
if (!Variaveis.IsAgroMonitor)
{
try
{
_PortaLoRa.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
_PortaLoRa = null;
}
return Variaveis.OperacaoEmAndamento.DispSen?.Dados?.loRaService?.Iniciado ?? false;
}
else
{
if (_PortaLoRa != null && !_PortaLoRa.IsOpen)
{
try
{
_PortaLoRa.Open();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
_PortaLoRa = null;
}
}
return _PortaLoRa != null && _PortaLoRa.IsOpen;
}
return _PortaLoRa != null && _PortaLoRa.IsOpen;
}
}
private static Dictionary<DateTime, List<byte>> bufferRecebido = new Dictionary<DateTime, List<byte>>();
@ -406,17 +415,39 @@ namespace AgroBase.Services
int numBytes = resposta.Buffer[2];
if (numBytes == 6 || numBytes == 11) // Set ou Get nos parametros
{
modelo.address = resposta.Buffer[4];
string baudAndAirRates = Convert.ToInt32(resposta.Buffer[5].ToString("X")).ToString("00");
modelo.baudRate = Convert.ToByte(baudAndAirRates[0] + "0", 16);
modelo.airRate = Convert.ToByte("0" + baudAndAirRates[1], 16);
string packetSizeAndPower = Convert.ToInt32(resposta.Buffer[6].ToString("X")).ToString("00");
modelo.packetSize = Convert.ToByte(packetSizeAndPower[0] + "0", 16);
modelo.power = Convert.ToByte("0" + packetSizeAndPower[1], 16);
modelo.channel = resposta.Buffer[7];
string tranModeAndWorCycle = Convert.ToInt32(resposta.Buffer[8].ToString("X")).ToString("00");
modelo.tranMode = Convert.ToByte(tranModeAndWorCycle[0] + "0", 16);
modelo.worCycle = Convert.ToByte("0" + tranModeAndWorCycle[1], 16);
var data = resposta.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);
/*modelo.address = resposta.Buffer[4];
string baudAndAirRates = Convert.ToInt32(resposta.Buffer[5].ToString("X")).ToString("00");
modelo.baudRate = Convert.ToByte(baudAndAirRates[0] + "0", 16);
modelo.airRate = Convert.ToByte("0" + baudAndAirRates[1], 16);
string packetSizeAndPower = Convert.ToInt32(resposta.Buffer[6].ToString("X")).ToString("00");
modelo.packetSize = Convert.ToByte(packetSizeAndPower[0] + "0", 16);
modelo.power = Convert.ToByte("0" + packetSizeAndPower[1], 16);
modelo.channel = resposta.Buffer[7];
string tranModeAndWorCycle = Convert.ToInt32(resposta.Buffer[8].ToString("X")).ToString("00");
modelo.tranMode = Convert.ToByte(tranModeAndWorCycle[0] + "0", 16);
modelo.worCycle = Convert.ToByte("0" + tranModeAndWorCycle[1], 16);*/
modelo.address = address;
modelo.baudRate = baud;
modelo.airRate = airRate;
modelo.packetSize = packetSize;
modelo.power = power;
modelo.channel = channel;
modelo.tranMode = tranMode;
modelo.worCycle = worCycle;
return modelo;
}
@ -551,7 +582,7 @@ namespace AgroBase.Services
return true;
}
public async Task<LoRaParametrosModel> ConfigurarModuloAsync(LoRaParametrosModel Parametros)
public async Task<(bool, LoRaParametrosModel)> ConfigurarModuloAsync(LoRaParametrosModel Parametros)
{
parametrosModel = Parametros;
@ -568,7 +599,7 @@ namespace AgroBase.Services
// Verifica se a última leitura foi atualizada nos últimos 3 segundos
if (parametrosModel.UltimaLeitura >= DateTime.Now.AddSeconds(-3))
{
return parametrosModel;
return (true, parametrosModel);
}
// Aguarda de forma assíncrona por um curto período de tempo
@ -576,7 +607,7 @@ namespace AgroBase.Services
}
// Se o timeout for atingido, retorna o modelo com os dados disponíveis até o momento
return null;
return (false, null);
}
@ -655,14 +686,13 @@ namespace AgroBase.Services
{
List<byte[]> Partes = new List<byte[]>();
byte[] Cabecalho = new byte[] { parametrosModel.address, targetAddress, (byte)tipo };
byte addressHigh = 0x00;
byte channel = parametrosModel.channel;
byte remetente = parametrosModel.address;
int maxPacketSize = CodigosPacketSizes.FirstOrDefault(x => x.Value == parametrosModel.packetSize).Key - 10;
int prefixLength = 5; // Tamanho fixo do cabeçalho (ajuste conforme necessário)
int maxDataLength = maxPacketSize - prefixLength - 1; // Subtrai tamanho do cabeçalho e um byte para o sufixo
byte addressHigh = 0x00; // Endereço alto (pode ajustar conforme necessário)
byte channel = parametrosModel.channel; // Canal do LoRa (ajuste conforme necessário)
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)
{
@ -670,23 +700,25 @@ namespace AgroBase.Services
byte[] dataFragment = new byte[length];
Array.Copy(bytes, i, dataFragment, 0, length);
// Montar cabeçalho
string Prefixo = (i == 0 ? SerialService.BeginLine : SerialService.BreakLine);
string Sulfixo = (i + length >= bytes.Length ? SerialService.EndLine : SerialService.BreakLine) + "\n";
byte checksum = (byte)(dataFragment.Sum(b => b) % 256);
byte[] prefixoBytes = Encoding.UTF8.GetBytes(Prefixo);
byte[] sulfixoBytes = Encoding.UTF8.GetBytes(Sulfixo);
byte prefix = (byte)LoRaCaracteresEspeciais.BeginMsg;
byte suffix = (byte)LoRaCaracteresEspeciais.EndMsg;
byte dataSize = (byte)dataFragment.Length;
byte[] mensagem = new byte[prefixoBytes.Length + Cabecalho.Length + dataFragment.Length + sulfixoBytes.Length + 3];
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;
Array.Copy(prefixoBytes, 0, mensagem, 3, prefixoBytes.Length);
Array.Copy(Cabecalho, 0, mensagem, 3 + prefixoBytes.Length, Cabecalho.Length);
Array.Copy(dataFragment, 0, mensagem, 3 + prefixoBytes.Length + Cabecalho.Length, dataFragment.Length);
Array.Copy(sulfixoBytes, 0, mensagem, 3 + prefixoBytes.Length + Cabecalho.Length + dataFragment.Length, sulfixoBytes.Length);
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);
}
@ -703,6 +735,8 @@ namespace AgroBase.Services
}
private static async Task tmrEnvio_Tick()
{
var mensagens = bufferEnvio.ToList();

View File

@ -138,7 +138,7 @@ namespace AgroBase.Services
private static void EnviarComando(T_Code Dispositivo, byte id, byte funcCode, byte[] payload = null, bool get = true)
{
CanService.AdicionarMensagemNaFila(Dispositivo, id, funcCode, payload, get, funcCode);
CanService.AdicionarMensagemNaFila(Dispositivo, id, id, funcCode, payload, get, funcCode);
}
public static void SetModoOperacao(byte id, byte modo)
@ -294,7 +294,7 @@ namespace AgroBase.Services
public void ProcessarMensagem(CanMessage mensagem)
{
MksFuncCode func = (MksFuncCode)mensagem.DataRx[0];
byte id = mensagem.Id;
byte id = mensagem.IdTx;
var data = mensagem.DataRx;
var item = ObterOuCriar(id);

View File

@ -203,7 +203,7 @@ namespace AgroBase.Services
AtualizarConsole("Procurando dispositivos conectados à porta " + PortaCom + "...");
SerialPort _Porta = new SerialPort() { PortName = PortaCom, BaudRate = 115200 };
if (!LoRaService.Iniciado)
if (Variaveis.IsAgroMonitor && !LoRaService.Iniciado)
{
bool LoRa = await ProcurarDispositivoLoRa(_Porta);
if (LoRa)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
a916f4b7a3b8728703c8d007e50dd3f8c3f77e39a6c79a9f0061bc86cd1cd2f6
94e5acd4ef698303080886ab8e455321319e02e14c31bfb9f4f93b4ef716322c

View File

@ -26,6 +26,8 @@
"stdexcept": "cpp",
"xmemory0": "cpp",
"xstring": "cpp",
"optional": "cpp"
"optional": "cpp",
"map": "cpp",
"xtree": "cpp"
}
}

View File

@ -92,6 +92,8 @@ CanService* CanService::instance = nullptr;
class CanService {
public:
uint8_t ID_Num_sMOD = 0;
uint8_t ID_Num_sTOD = 250;
uint8_t ID_Num_sLRA = 251;
typedef void (*OnReceiveCallback)(int packetSize, int senderId, byte funcCode, byte* data, int dataLength);
CanService(uint8_t nodeId, uint32_t baudRate = 500000) {
@ -126,53 +128,151 @@ class CanService {
PrintTela(String(err));
return;
}
if (CanTaskRxHandle == NULL) {
canQueueRx = xQueueCreate(50, sizeof(twai_message_t));
if (canQueueRx == NULL) {
PrintTela("[CAN] Falha ao criar fila RX!");
}
xTaskCreatePinnedToCore(CanService::CanTaskRxWrapper, "CanTaskRx", 4096, this, 3, &CanTaskRxHandle, APP_CPU_NUM);
}
if (CanTaskTxHandle == NULL) {
canQueueTx = xQueueCreate(50, sizeof(twai_message_t));
if (canQueueTx == NULL) {
PrintTela("[CAN] Falha ao criar fila TX!");
}
xTaskCreatePinnedToCore(CanService::CanTaskTxWrapper, "CanTaskTx", 4096, this, 3, &CanTaskTxHandle, APP_CPU_NUM);
}
if (CanTaskProcessHandle == NULL) {
xTaskCreatePinnedToCore(CanService::CanTaskProcessWrapper, "CanTaskProcess", 6144, this, 2, &CanTaskProcessHandle, APP_CPU_NUM);
}
PrintTela("[CAN] TWAI iniciado com sucesso");
}
void loop() {
QueueHandle_t canQueueRx;
TaskHandle_t CanTaskRxHandle = NULL;
static void CanTaskRxWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskRx(pvParameters);
}
void CanTaskRx(void* pvParameters) {
twai_message_t message;
if (twai_receive(&message, pdMS_TO_TICKS(10)) == ESP_OK) {
while (true) {
if (twai_receive(&message, pdMS_TO_TICKS(10)) == ESP_OK) {
if (message.data_length_code == 0 || message.extd) return;
byte funcCode = message.data[0];
bool MensagemTx = (int)funcCode % 2 == 0;
Serial.print("[CAN ");
Serial.print(MensagemTx ? "TX" : "RX");
Serial.print("] ID: 0x");
Serial.print(message.identifier, HEX);
Serial.print(" Len: ");
Serial.print(message.data_length_code);
Serial.print(" Data: ");
for (int i = 0; i < message.data_length_code; i++) {
Serial.print(message.data[i], HEX);
Serial.print(" ");
if (message.data_length_code == 0 || message.extd) continue;
bool MensagemTx = message.identifier == static_cast<uint32_t>(_nodeId + 1);
PrintTela("[CAN ", false);
PrintTela(MensagemTx ? "TX" : "RX", false);
PrintTela("] ID: 0x", false);
PrintTela(String(message.identifier, HEX), false);
PrintTela(" Len: ", false);
PrintTela(String(message.data_length_code), false);
PrintTela(" Data: ", false);
for (int i = 0; i < message.data_length_code; i++) {
PrintTela(String(message.data[i], HEX), false);
PrintTela(" ", false);
}
PrintTela("");
if (MensagemTx) {
continue;
}
if (!mensagemJaNaFila(canQueueRx, message)) {
if (xQueueSend(canQueueRx, &message, 0) != pdTRUE) {
PrintTela("[CAN] Fila RX cheia! Mensagem descartada.");
}
else {
//PrintTela("[CAN] Mensagem adicionada na fila RX");
}
} else {
PrintTela("[CAN] Mensagem duplicada ignorada.");
}
}
Serial.println();
vTaskDelay(1);
}
}
if (MensagemTx) { // funcCodes impares sao enviados pelo esp, ignorar
return;
}
QueueHandle_t canQueueTx;
TaskHandle_t CanTaskTxHandle = NULL;
static void CanTaskTxWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskTx(pvParameters);
}
if (_callback != nullptr) {
//PrintTela("Entrar no callback...");
_callback(message.data_length_code, message.identifier, funcCode, &message.data[1], message.data_length_code - 1);
void CanTaskTx(void* pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
twai_message_t msg;
while (true) {
if (xQueueReceive(service->canQueueTx, &msg, portMAX_DELAY) == pdTRUE) {
//PrintTela("[CAN] Mensagem recebida da fila TX");
if (service->enviarDadosCan(msg)) {
//PrintTela("[CAN] Mensagem enviada via CAN com sucesso");
}
else {
PrintTela("[CAN] Erro ao enviar mensagem via CAN");
}
}
vTaskDelay(1);
}
}
TaskHandle_t CanTaskProcessHandle = NULL;
static void CanTaskProcessWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskProcess(pvParameters);
}
void CanTaskProcess(void* pvParameters) {
//PrintTela("[CAN] Task de Processamento iniciada");
CanService *service = static_cast<CanService*>(pvParameters);
twai_message_t msg;
while (true) {
if (xQueueReceive(service->canQueueRx, &msg, portMAX_DELAY) == pdTRUE) {
//PrintTela("[CAN] Mensagem recebida da fila RX");
uint8_t funcCode = msg.data[0];
if (service->_callback) {
service->_callback(msg.data_length_code, msg.identifier, funcCode, &msg.data[1], msg.data_length_code - 1);
}
}
vTaskDelay(1);
}
}
bool mensagemJaNaFila(QueueHandle_t fila, const twai_message_t& novaMsg) {
twai_message_t msgTmp;
UBaseType_t items = uxQueueMessagesWaiting(fila);
for (UBaseType_t i = 0; i < items; i++) {
if (xQueuePeek(fila, &msgTmp, 0) == pdTRUE) {
if (msgTmp.identifier == novaMsg.identifier && msgTmp.data[0] == novaMsg.data[0] && msgTmp.data[1] == novaMsg.data[1] && msgTmp.data[2] == novaMsg.data[2]) {
return true; // já tem uma igual
}
}
}
return false;
}
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool enviarMensagem(const std::vector<uint8_t>& payload) {
bool adicionarMensagemFila(const std::vector<uint8_t>& payload) {
if (payload.size() > 8) return false;
twai_message_t msg;
msg.identifier = _nodeId;
msg.identifier = _nodeId + 1;
msg.extd = 0;
msg.rtr = 0;
msg.ss = 1;
@ -182,27 +282,26 @@ class CanService {
msg.data[i] = payload[i]; // 👈 Preenche os dados corretamente
}
return twai_transmit(&msg, pdMS_TO_TICKS(10)) == ESP_OK;
}
bool enviarMensagem(byte funcCode, const std::vector<uint8_t>& payload) {
if (payload.size() > 7) return false;
twai_message_t msg;
msg.identifier = _nodeId;
msg.extd = 0;
msg.rtr = 0;
msg.ss = 1;
msg.data_length_code = payload.size() + 1;
msg.data[0] = funcCode;
for (uint8_t i = 0; i < payload.size(); ++i) {
msg.data[i + 1] = payload[i];
if (!mensagemJaNaFila(canQueueTx, msg)) {
if (xQueueSend(canQueueTx, &msg, 0) != pdTRUE) {
PrintTela("[CAN] Fila TX cheia! Mensagem descartada.");
return false;
}
else {
//PrintTela("[CAN] Mensagem adicionada na fila TX");
return true;
}
}
else {
PrintTela("[CAN] Mensagem duplicada ignorada.");
}
}
bool enviarDadosCan(twai_message_t msg) {
return twai_transmit(&msg, pdMS_TO_TICKS(10)) == ESP_OK;
}
std::vector<uint8_t> MontarFrameChk(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> data;
data.push_back(ID_Num_sMOD);
@ -217,6 +316,8 @@ class CanService {
uint8_t _nodeId;
uint32_t _baudRate;
OnReceiveCallback _callback = nullptr;
};
#endif

View File

@ -66,6 +66,8 @@ enum S_Code {
sPOT = 23,
sMOD = 24,
sIMU = 25,
sLRA = 26,
sTOD = 27,
};
enum Estado {
@ -120,11 +122,12 @@ enum TiposLeituras {
enum CanMessagePosicaoDados
{
Status = 0,
Dados1 = 1,
Dados2 = 2,
Dados3 = 3,
Dados4 = 4,
Dados5 = 5,
DadosAll = 1,
Dados1 = 2,
Dados2 = 3,
Dados3 = 4,
Dados4 = 5,
Dados5 = 6,
Config1 = 51,
Config2 = 52,
Config3 = 53,

View File

@ -1,5 +1,5 @@
#ifndef MuxService_h
#define MuxService_h
#ifndef I2CService_h
#define I2CService_h
#include "SerialService.h"
#include <Wire.h>
@ -42,6 +42,11 @@ class I2CService {
}
idAtualUsandoI2C = -1;
tempoEntradaI2C = 0;
if (i2cTaskHandle == NULL) {
xTaskCreatePinnedToCore(I2CService::i2cTaskWrapper, "i2cTask", 4096, nullptr, 5, &i2cTaskHandle, tskNO_AFFINITY);
}
return I2CIniciado;
}
@ -75,7 +80,7 @@ class I2CService {
}
// Troca canal se necessário
if (canalMux >= 0 && !TrocarCanalMux(canalMux)) {
if (canalMux >= 0 && !SelecionarSensor(canalMux)) {
xSemaphoreGive(i2cMutex); // Libera caso a troca de canal falhe
return false;
}
@ -97,14 +102,6 @@ class I2CService {
return idAtualUsandoI2C;
}
static void VerificarI2CPreso() {
if (idAtualUsandoI2C >= 0 && (millis() - tempoEntradaI2C > LimiteTempoI2C)) {
PrintTela("[I2C] Semáforo preso pelo ID %d — forçando liberação!\n", idAtualUsandoI2C);
LiberarAcessoI2C(idAtualUsandoI2C);
IniciarI2C();
}
}
static bool LeituraSegura(uint8_t addr, uint8_t* buffer, size_t qtd, uint8_t reg = 0xFF, uint32_t timeoutMs = 50) {
//Wire.setTimeout(timeoutMs);
unsigned long t0 = millis();
@ -155,13 +152,20 @@ class I2CService {
return true;
}
static bool TrocarCanalMux(int canalMux) {
static bool SelecionarSensor(uint8_t canalGlobal) {
uint8_t _enderecoMux = 0x70 + (canalGlobal / 10);
uint8_t _canal = canalGlobal % 10;
return TrocarCanalMux(_enderecoMux, _canal);
}
static bool TrocarCanalMux(uint8_t endereco, uint8_t canalMux) {
if (MuxIniciado && canalMux >= 0 && canalMux < 9) {
if (canalMux != canalAtivo) {
Wire.beginTransmission(enderecoMux);
Wire.beginTransmission(endereco);
Wire.write(1 << canalMux);
Wire.endTransmission();
PrintTela("[MUX] Canal alterado de " + String(canalAtivo) + " para " + String(canalMux));
PrintTela("[MUX] 0x" + String(endereco) + " - Canal alterado de " + String(canalAtivo) + " para " + String(canalMux));
canalAtivo = canalMux;
}
return true;
@ -179,8 +183,7 @@ class I2CService {
PrintTela("[MUX] Iniciando TCA9548A...");
if (!MuxIniciado) {
SolicitarAcessoI2C();
Wire.beginTransmission(endereco);
MuxIniciado = (Wire.endTransmission() == 0);
MuxIniciado = VerificaEnderecoBarramento(endereco);
if (!MuxIniciado) {
PrintTela("[MUX] TCA9548A nao Iniciado, endereco " + String(endereco) + " nao encontrado no barramento");
if (Mandatorio) {
@ -273,6 +276,26 @@ class I2CService {
static unsigned long tempoEntradaI2C;
static unsigned long LimiteTempoI2C;
static TaskHandle_t i2cTaskHandle;
static void i2cTaskWrapper(void *pvParameters) {
I2CService::i2cTask();
}
static void i2cTask() {
while (true) {
VerificarI2CPreso();
vTaskDelay(1);
}
}
static void VerificarI2CPreso() {
if (idAtualUsandoI2C >= 0 && (millis() - tempoEntradaI2C > LimiteTempoI2C)) {
PrintTela("[I2C] Semáforo preso pelo ID %d — forçando liberação!\n", idAtualUsandoI2C);
LiberarAcessoI2C(idAtualUsandoI2C);
IniciarI2C();
}
}
};
int I2CService::_pinoSDA = 1;
@ -288,5 +311,6 @@ int I2CService::canalAtivo = -1;
int I2CService::idAtualUsandoI2C = -1;
unsigned long I2CService::tempoEntradaI2C = 0;
SemaphoreHandle_t I2CService::i2cMutex = nullptr;
TaskHandle_t I2CService::i2cTaskHandle = NULL;
#endif

View File

@ -0,0 +1,706 @@
// LoRaService.h
#ifndef LoRaService_h
#define LoRaService_h
#include "SerialService.h"
#include "CanService.h"
#include <Arduino.h>
#include <vector>
#include <map>
class LoRaService {
public:
enum LoRaMode {
NORMAL,
WAKE_UP,
POWER_SAVING,
CONFIG
};
enum CodigosFuncoes {
CfgTx = 0xC0,
CfgRx = 0xC1,
Msg = 0xE1,
WrongMode = 0xFC,
BeginMsg = 0xAA,
EndMsg = 0x55,
};
struct LoRaParametrosModel {
uint8_t address;
uint8_t baudAndAirRate;
uint8_t packetSizeAndPower;
uint8_t channel;
uint8_t tranModeAndWorCycle;
};
LoRaService(CanService* canService) {
_canService = canService;
}
bool Conectado = false;
bool Configurado = false;
byte AddressBase = 0x01;
// Inicializa pinos e UART
void Inicializar(uint8_t baudRate) {
Configurado = false;
_baudRate = encontrarChavePorValor(CodigosBaudRates, baudRate, 9600);
PrintTela("[LRA] Iniciando LoRa nos pinos TXD=" + String(_pinTXD) + ", RXD=" + String(_pinRXD) + ", baudRate=" + String(_baudRate) + "...");
// Inicializa GPIOs
pinMode(_pinM0, OUTPUT);
pinMode(_pinM1, OUTPUT);
pinMode(_pinAUX, INPUT);
_serialLoRa = &Serial2;
if (Conectado) {
_serialLoRa->end();
}
_serialLoRa->begin(_baudRate, SERIAL_8N1, _pinTXD, _pinRXD);
// Coloca o módulo em modo normal
setLoRaMode(NORMAL);
vTaskDelay(500); // Aguarda estabilização
// Testa conexão
Conectado = checkLoRaConnected();
if (Conectado) {
PrintTela("[LRA] E220 detectado!");
if (LraTaskRxHandle == NULL) {
lraQueueRx = xQueueCreate(50, sizeof(std::vector<uint8_t>));
if (lraQueueRx == NULL) {
PrintTela("[LRA] Falha ao criar fila RX!");
}
xTaskCreatePinnedToCore(LoRaService::LraTaskRxWrapper, "LraTaskRx", 4096, this, 3, &LraTaskRxHandle, APP_CPU_NUM);
}
if (LraTaskTxHandle == NULL) {
lraQueueTx = xQueueCreate(50, sizeof(std::vector<uint8_t>));
if (lraQueueTx == NULL) {
PrintTela("[LRA] Falha ao criar fila TX!");
}
xTaskCreatePinnedToCore(LoRaService::LraTaskTxWrapper, "LraTaskTx", 4096, this, 3, &LraTaskTxHandle, APP_CPU_NUM);
}
if (LraTaskProcessHandle == NULL) {
xTaskCreatePinnedToCore(LoRaService::LraTaskProcessWrapper, "LraTaskProcess", 4096, this, 2, &LraTaskProcessHandle, APP_CPU_NUM);
}
} else {
PrintTela("[LRA] E220 não detectado.");
}
}
std::vector<uint8_t> MontarMensagemCAN(CanMessagePosicaoDados posicao) {
std::vector<uint8_t> data;
data.push_back(_canService->ID_Num_sLRA);
data.push_back(static_cast<uint8_t>(posicao));
switch (posicao) {
case CanMessagePosicaoDados::Status: {
data.push_back(Conectado ? 1 : 0);
data.push_back(Configurado ? 1 : 0);
break;
}
case CanMessagePosicaoDados::Dados1: {
data.push_back(parametrosAtual.address);
data.push_back(parametrosAtual.baudAndAirRate);
data.push_back(parametrosAtual.packetSizeAndPower);
data.push_back(parametrosAtual.channel);
data.push_back(parametrosAtual.tranModeAndWorCycle);
break;
}
}
return data;
}
void EnviarComando(std::vector<uint8_t> dados) {
if (dados.size() < 3) return;
uint8_t idNum = dados[1];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)dados[2];
switch (posicao) {
case CanMessagePosicaoDados::Command1: {
bool sucesso = requisitarParametrosLoRa();
EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
break;
}
default: {
AdicionarMensagemFilaLoRa(dados);
}
}
}
bool AdicionarMensagemFilaLoRa(std::vector<uint8_t> dados) {
if (xQueueSend(lraQueueTx, &dados, 0) != pdTRUE) {
PrintTela("[LRA] Fila TX cheia! Mensagem descartada.");
return false;
}
else {
PrintTela("[LRA] Mensagem adicionada na fila TX");
return true;
}
}
std::vector<uint8_t> ConfigurarModulo(std::vector<uint8_t> data) {
std::vector<uint8_t> status;
if (data.size() < 3) return status;
uint8_t idNum = data[1];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[2];
PrintTela("[LRA] Recebido comando ConfigurarModulo: posicao = " + String((int)posicao));
switch (posicao) {
case CanMessagePosicaoDados::Config1: {
if (data.size() < 8) return data;
_pinM0 = data[3];
_pinM1 = data[4];
_pinAUX = data[5];
_pinRXD = data[6];
_pinTXD = data[7];
break;
}
case CanMessagePosicaoDados::Config2: {
if (data.size() < 8) return data;
if (_pinRXD == 0 || _pinTXD == 0) {
PrintTela("[LRA] Erro ao iniciar E220, pinout ainda nao definido!");
return status;
}
uint8_t baudField = data[4] & 0xE0; // bits 5,6,7 (baudrate)
Inicializar(baudField);
EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Status));
if (Conectado) {
if (requisitarParametrosLoRa()) {
EnviarDadosCAN(MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
Configurado =
parametrosAtual.address == data[3] &&
parametrosAtual.baudAndAirRate == data[4] &&
parametrosAtual.packetSizeAndPower == data[5] &&
parametrosAtual.channel == data[6] &&
parametrosAtual.tranModeAndWorCycle == data[7];
if (!Configurado) {
Configurado = configurarModuloLoRa(
data[3], // address
data[4], // baudAndAirRate
data[5], // packetSizeAndPower
data[6], // channel
data[7] // tranModeAndWorCycle
);
}
}
}
break;
}
}
return status;
}
private:
bool recebendo = false;
int bytesEsperados = -1;
int addrRemetente = -1;
int addrDestinatario = -1;
std::vector<uint8_t> buffer;
unsigned long ultimoByteRecebido = 0;
const unsigned long timeoutRecebimentoMs = 100; // por exemplo, 100ms
void reiniciarEstado() {
recebendo = false;
bytesEsperados = -1;
addrRemetente = -1;
addrDestinatario = -1;
buffer.clear();
}
QueueHandle_t lraQueueRx;
TaskHandle_t LraTaskRxHandle = NULL;
static void LraTaskRxWrapper(void *pvParameters) {
LoRaService *service = static_cast<LoRaService*>(pvParameters);
service->LraTaskRx(pvParameters);
}
void LraTaskRx(void* pvParameters) {
LoRaService* instance = (LoRaService*)pvParameters;
while (true) {
if (!Conectado || !Configurado) {
vTaskDelay(1000);
continue;
}
if (recebendo && (millis() - ultimoByteRecebido > timeoutRecebimentoMs)) {
PrintTela("[LRA] Timeout de recebimento. Reiniciando estado.");
reiniciarEstado();
}
while (currentMode == LoRaMode::NORMAL && instance->_serialLoRa->available()) {
uint8_t byteRecebido = instance->_serialLoRa->read();
ultimoByteRecebido = millis(); // <- atualiza o tempo
PrintTela("byteRecebido=" + String(byteRecebido));
if (!recebendo) {
if (byteRecebido == CodigosFuncoes::BeginMsg) {
reiniciarEstado();
PrintTela("[LRA] Iniciou o recebimento dos dados LoRa");
recebendo = true;
}
}
else {
if (bytesEsperados == -1) {
bytesEsperados = byteRecebido;
//PrintTela("[LRA] Definindo bytesEsperados = " + String(bytesEsperados));
}
else if (addrRemetente == -1) {
addrRemetente = byteRecebido;
//PrintTela("[LRA] Definindo addrRemetente = " + String(addrRemetente));
}
else if (addrDestinatario == -1) {
addrDestinatario = byteRecebido;
//PrintTela("[LRA] Definindo addrDestinatario = " + String(addrDestinatario));
if (addrDestinatario != parametrosAtual.address) {
PrintTela("[LRA] Mensagem para outro ID, descartando");
reiniciarEstado();
}
}
else {
buffer.push_back(byteRecebido);
bool protocoloCompleto = addrRemetente != -1 && addrDestinatario != -1 && buffer.size() == bytesEsperados + 2; // +2 = checksum + end
PrintTela("[LRA] protocoloCompleto = " + String(protocoloCompleto) + ", bufferSize = " + buffer.size() + ", bytesEsperados = " + String(bytesEsperados));
if (protocoloCompleto) {
if (buffer.back() == CodigosFuncoes::EndMsg) {
buffer.pop_back(); // remove 0x55 (EndMsg)
uint8_t checksumRecebido = buffer.back();
buffer.pop_back(); // remove checksum
uint8_t soma = 0;
for (size_t i = 0; i < buffer.size(); ++i) {
soma += buffer[i];
}
if ((soma % 256) == checksumRecebido) {
PrintTela("[LRA] Mensagem LoRa recebida de " + String(addrRemetente) + " com checksum válido");
if (buffer.size() >= 2) {
if (xQueueSend(lraQueueRx, &buffer, 0) != pdTRUE) {
PrintTela("[LRA] Fila RX cheia! Mensagem descartada.");
}
else {
PrintTela("[LRA] Mensagem adicionada na fila RX");
}
}
} else {
PrintTela("[LRA] Checksum inválido. Descartando.");
}
}
reiniciarEstado();
}
}
}
}
vTaskDelay(1);
}
}
QueueHandle_t lraQueueTx;
TaskHandle_t LraTaskTxHandle = NULL;
static void LraTaskTxWrapper(void *pvParameters) {
LoRaService *service = static_cast<LoRaService*>(pvParameters);
service->LraTaskTx(pvParameters);
}
void LraTaskTx(void* pvParameters) {
LoRaService *service = static_cast<LoRaService*>(pvParameters);
std::vector<uint8_t> msg;
while (true) {
if (!Conectado || !Configurado) {
vTaskDelay(1000);
continue;
}
if (xQueueReceive(service->lraQueueTx, &msg, portMAX_DELAY) == pdTRUE) {
service->EnviarDadosLoRa(msg);
}
vTaskDelay(1);
}
}
TaskHandle_t LraTaskProcessHandle = NULL;
static void LraTaskProcessWrapper(void *pvParameters) {
LoRaService *service = static_cast<LoRaService*>(pvParameters);
service->LraTaskProcess(pvParameters);
}
void LraTaskProcess(void* pvParameters) {
LoRaService *service = static_cast<LoRaService*>(pvParameters);
std::vector<uint8_t> msg;
while (true) {
if (!Conectado || !Configurado) {
vTaskDelay(1000);
continue;
}
if (xQueueReceive(service->lraQueueRx, &msg, portMAX_DELAY) == pdTRUE) {
service->EnviarDadosCAN(msg);
}
vTaskDelay(1);
}
}
CanService* _canService;
HardwareSerial* _serialLoRa;
uint8_t _pinTXD;
uint8_t _pinRXD;
uint8_t _pinM0;
uint8_t _pinM1;
uint8_t _pinAUX;
int _baudRate = 9600;
LoRaMode currentMode = NORMAL;
LoRaParametrosModel parametrosAtual;
int commandTimeout = 1000;
// Métodos internos
// Define o modo do LoRa
bool setLoRaMode(LoRaMode mode) {
if (mode == currentMode) return true;
// Ajusta os pinos M0 e M1 conforme o modo desejado
switch (mode) {
case NORMAL:
digitalWrite(_pinM0, LOW);
digitalWrite(_pinM1, LOW);
break;
case WAKE_UP:
digitalWrite(_pinM0, HIGH);
digitalWrite(_pinM1, LOW);
break;
case POWER_SAVING:
digitalWrite(_pinM0, LOW);
digitalWrite(_pinM1, HIGH);
break;
case CONFIG:
digitalWrite(_pinM0, HIGH);
digitalWrite(_pinM1, HIGH);
break;
}
PrintTela("[LRA] Modo alterado de " + String(currentMode) + " para " + String(mode));
currentMode = mode;
vTaskDelay(100); // Aguarda sinalização inicial de troca
// ESPERA o AUX ir para HIGH, indicando que a troca completou
unsigned long timeout = millis() + commandTimeout;
while (digitalRead(_pinAUX) == LOW) {
if (millis() > timeout) {
PrintTela("[LRA] Erro: Timeout aguardando AUX após mudança de modo.");
return false;
}
}
vTaskDelay(100); // Manual pede 2ms após AUX ficar HIGH
return true;
}
// Verifica se o E220 responde
bool checkLoRaConnected() {
PrintTela("[LRA] Verificando se o E220 esta conectado...");
if (!setLoRaMode(CONFIG)) {
PrintTela("[LRA] Falha ao mudar para CONFIG!");
return false;
}
bool conectado = false;
// Lista de possíveis baud rates (em ordem dos mais prováveis)
const uint32_t baudRates[] = { 9600, 19200, 38400, 57600, 115200, 4800, 2400, 1200 };
const int totalBauds = sizeof(baudRates) / sizeof(baudRates[0]);
int tentativaAtual = -1; // -1 = usar o baudrate já setado primeiro
while (!conectado && tentativaAtual < totalBauds) {
if (tentativaAtual >= 0) {
// Se estamos tentando outra taxa, mudar a serial:
_serialLoRa->end();
_serialLoRa->begin(baudRates[tentativaAtual], SERIAL_8N1, _pinTXD, _pinRXD);
_baudRate = baudRates[tentativaAtual];
PrintTela("[LRA] Tentando BaudRate: " + String(_baudRate));
}
// Limpa buffers antes de testar
limparBufferLoRa();
_serialLoRa->write(CodigosFuncoes::CfgRx);
unsigned long startTime = millis();
while (millis() - startTime < commandTimeout) {
if (_serialLoRa->available()) {
uint8_t dado = _serialLoRa->read();
if (dado == CodigosFuncoes::CfgRx) {
conectado = true;
break;
}
}
vTaskDelay(1);
}
tentativaAtual++;
}
limparBufferLoRa();
setLoRaMode(NORMAL);
if (conectado) {
PrintTela("[LRA] E220 conectado com baudRate " + String(_baudRate));
} else {
PrintTela("[LRA] Falha ao conectar no E220!");
}
return conectado;
}
// Método que monta o comando de configuração
bool configurarModuloLoRa(byte addr, byte baud, byte packet, byte channel, byte worCycle) {
if (!setLoRaMode(CONFIG)) {
PrintTela("[LRA] Falha ao mudar para CONFIG!");
return false;
}
PrintTela("[LRA] Configurando modulo...");
limparBufferLoRa();
uint8_t command[9] = {
CodigosFuncoes::CfgTx, // Código para configurar
0x00, // Registro inicial
0x06, // Quantidade de registros
0x00, // Reservado
addr,
baud,
packet,
channel,
worCycle
};
_serialLoRa->write(command, sizeof(command));
PrintTela("[LRA] Comando de configuração enviado!");
unsigned long startTime = millis();
bool sucesso = false;
while (millis() - startTime < commandTimeout) {
if (_serialLoRa->available()) {
uint8_t cabecalho = _serialLoRa->read();
if (cabecalho == CodigosFuncoes::CfgRx) { // 0xC1 é Resposta de Configuração
sucesso = true;
PrintTela("[LRA] Confirmação de configuração recebida!");
} else {
PrintTela("[LRA] Resposta inesperada: 0x" + String(cabecalho));
}
break;
}
vTaskDelay(10);
}
setLoRaMode(NORMAL);
return sucesso;
}
// Método que requisita a leitura dos parâmetros atuais
bool requisitarParametrosLoRa() {
PrintTela("Entrou aqui 3");
if (!setLoRaMode(CONFIG)) {
PrintTela("[LRA] Falha ao mudar para CONFIG!");
return false;
}
PrintTela("[LRA] Requisitando parametros do modulo...");
limparBufferLoRa();
uint8_t command[3] = { CodigosFuncoes::CfgRx, 0x00, 0x0B };
_serialLoRa->write(command, sizeof(command));
PrintTela("[LRA] Comando de requisição enviado!");
unsigned long startTime = millis();
bool sucesso = false;
while (!sucesso && millis() - startTime < commandTimeout) {
if (_serialLoRa->available()) {
uint8_t cabecalho = _serialLoRa->read();
if (cabecalho == CodigosFuncoes::CfgRx) { // Cabeçalho de resposta correta
PrintTela("[LRA] Cabecalho recebido corretamente.");
uint8_t startAddr = _serialLoRa->read(); // Deve ser 0x00
uint8_t dataLen = _serialLoRa->read(); // Deve ser 0x0B
if (startAddr == command[1] && dataLen == command[2]) {
PrintTela("[LRA] Parâmetros recebidos corretamente.");
// Agora ler os registros
uint8_t addh = _serialLoRa->read(); // byte 0
uint8_t addl = _serialLoRa->read(); // byte 1
parametrosAtual.address = addl; // usa só o ADDL (0~255)
// Continua normalmente
parametrosAtual.baudAndAirRate = _serialLoRa->read(); // byte 2
parametrosAtual.packetSizeAndPower = _serialLoRa->read(); // byte 3
parametrosAtual.channel = _serialLoRa->read(); // byte 4
parametrosAtual.tranModeAndWorCycle = _serialLoRa->read(); // byte 5
// Lê e ignora os 5 bytes restantes (6~10)
for (int i = 0; i < (0x0B - 6); i++) {
_serialLoRa->read();
}
sucesso = true;
}
else {
PrintTela("[LRA] Dados Corrompidos: startAddr=" + String(startAddr) + ", dataLen=" + String(dataLen));
}
} else if (cabecalho == CodigosFuncoes::WrongMode) {
PrintTela("[LRA] Modo invalido!");
}
}
vTaskDelay(10);
}
limparBufferLoRa();
setLoRaMode(NORMAL);
return sucesso;
}
// Limpa buffer anterior
void limparBufferLoRa() {
while (_serialLoRa->available()) {
_serialLoRa->read();
}
}
std::vector<uint8_t> MontarFrameLoRa(uint16_t destino, uint8_t canal, const std::vector<uint8_t>& payloadApp) {
std::vector<uint8_t> frame;
// Cabeçalho fixo LoRa (modo Fixed)
frame.push_back((destino >> 8) & 0xFF); // Addr High (geralmente 0x00)
frame.push_back(destino & 0xFF); // Addr Low
frame.push_back(canal); // Canal
// Estrutura do payload
frame.push_back(CodigosFuncoes::BeginMsg); // 0xAA
frame.push_back(payloadApp.size()); // Tamanho do payload
frame.push_back(parametrosAtual.address); // rementente
frame.push_back(destino); // destinatario
frame.insert(frame.end(), payloadApp.begin(), payloadApp.end());
// Calcula checksum (soma dos dados de payload)
uint8_t soma = 0;
for (uint8_t b : payloadApp) {
soma += b;
}
uint8_t checksum = soma % 256;
frame.push_back(checksum); // Checksum
frame.push_back(CodigosFuncoes::EndMsg); // 0x55
PrintTela("Frame LoRa montado: ", false);
for (int i = 0; i < frame.size(); i++) {
PrintTela(String(frame[i]) + " ", false);
}
PrintTela("");
return frame;
}
bool EnviarDadosCAN(std::vector<uint8_t> dados) {
_canService->adicionarMensagemFila(dados);
return true;
}
bool EnviarDadosLoRa(std::vector<uint8_t> dados) {
PrintTela("Enviando dados via LoRa para o endereco " + String(AddressBase) + " no canal " + String(parametrosAtual.channel));
std::vector<uint8_t> dadosLora = MontarFrameLoRa(AddressBase, parametrosAtual.channel, dados);
_serialLoRa->write(dadosLora.data(), dadosLora.size());
_serialLoRa->flush();
PrintTela("Dados enviados");
return true;
}
std::map<String, uint8_t> CodigosTipoTransmissao = {
{"Normal", 0x00},
{"Fixed", 0x40},
};
std::map<int, uint8_t> CodigosBaudRates = {
{1200, 0x00},
{2400, 0x20},
{4800, 0x40},
{9600, 0x60},
{19200, 0x80},
{38400, 0xA0},
{57600, 0xC0},
{115200, 0xE0},
};
std::map<double, uint8_t> CodigosAirRates = {
{2.4, 0x02},
{4.8, 0x03},
{9.6, 0x04},
{19.2, 0x05},
{38.4, 0x06},
{62.5, 0x67},
};
std::map<int, uint8_t> CodigosPacketSizes = {
{200, 0x00},
{128, 0x40},
{64, 0x80},
{32, 0xC0},
};
std::map<int, uint8_t> CodigosWorCycles = {
{500, 0x00},
{1000, 0x01},
{1500, 0x02},
{2000, 0x03},
{2500, 0x04},
{3000, 0x05},
{3500, 0x06},
{4000, 0x07},
};
std::map<int, uint8_t> CodigosPowers = {
{22, 0x00},
{17, 0x01},
{13, 0x02},
{10, 0x03},
};
template<typename K, typename V>
K encontrarChavePorValor(const std::map<K, V>& mapa, V valorProcurado, K valorDefault) {
for (const auto& par : mapa) {
if (par.second == valorProcurado) {
return par.first;
}
}
return valorDefault;
}
};
#endif

View File

@ -91,7 +91,7 @@ class SensorIMU {
Iniciado = MpuIniciado || BmpIniciado;
if (Iniciado) {
filterDelay = 1000.0f / filterHz;
xTaskCreatePinnedToCore(&SensorIMU::IMUTaskWrapper, "IMUTask", 5000, this, 20, &IMUTaskHandle, tskNO_AFFINITY);
xTaskCreatePinnedToCore(&SensorIMU::IMUTaskWrapper, "IMUTask", 4096, this, 20, &IMUTaskHandle, tskNO_AFFINITY);
PrintTela(_ID + " iniciado");
}

View File

@ -0,0 +1,8 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
"version": "0.2.0",
"configurations": [
]
}

View File

@ -0,0 +1,431 @@
// Dispositivo: Sensoriamento
// Versão Firmware: 17
// Ultima atualização: 28/04/2025
// Atualização: Implementado LoRa
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SerialService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\CanService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\LoRaService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\EepromService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\I2CService.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\Utils.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\Pinout.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorTemperaturaModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorCorrenteModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorIMUModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SinaleiroModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\ReleModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\ServoFreioModel.h"
#include "C:\ZendionInc\agrobot_base\Firmware\Modulos\SensorPotenciometroModel.h"
// Configurações do CAN
uint8_t CanID = 0x11;
CanService canService(CanID);
LoRaService loraService(&canService);
#define D_Code Sen
int VERSION = 17;
String Mod_ID = "Sen";
#define _pinoLED RGB_BUILTIN
bool Conectado = false;
bool Conectar = false;
int _TaxaAmostragem = 1000;
volatile bool RequisitarDados = false;
int latenciaLoop = 0;
int qtdEnviosStatus = 0;
int maxEnviosStatus = 5;
std::vector<SensorTemperatura*> listaSensoresTemperatura;
std::vector<SensorCorrente*> listaSensoresCorrente;
std::vector<Sinaleiro*> listaSinaleiros;
std::vector<Rele*> listaReles;
std::vector<ServoFreio*> listaServoFreios;
std::vector<SensorIMU*> listaSensoresIMU;
void inicializarDependenciasI2C() {
I2CService::IniciarI2C();
I2CService::IniciarMUX();
I2CService::IniciarMCP();
I2CService::IniciarADS();
}
void setup() {
delay(5000);
inicializarDependenciasI2C();
// Registrar callback para processar mensagens CAN recebidas
canService.setReceiveCallback([](int packetSize, int senderId, byte funcCode, byte* data, int dataLength) {
std::vector<uint8_t> fullData;
fullData.push_back(funcCode); // Primeiro byte é sempre o funcCode
for (int i = 0; i < dataLength; i++) {
fullData.push_back(data[i]);
}
ProcessarFrameCAN(fullData);
});
canService.begin();
}
void loop() {
if (!RequisitarDados) {
enviarLogsPeriodicamente();
}
delay(1);
}
// Função para enviar logs do sensor periodicamente
unsigned long ultimoEnvioLogs = 0;
const unsigned long intervaloEnvioLogs = 1000; // 1 segundo
void enviarLogsPeriodicamente() {
unsigned long tempoAtual = millis();
// Verifica se o intervalo de 5 segundos já passou
if (tempoAtual - ultimoEnvioLogs >= intervaloEnvioLogs) {
ultimoEnvioLogs = tempoAtual; // Atualiza o tempo do último envio
enviarDadosSensores(canService.ID_Num_sTOD, CanMessagePosicaoDados::DadosAll);
}
}
void enviarDadosSensores(int id_num, CanMessagePosicaoDados posicao) {
unsigned long startLoop = millis();
if (Conectado) {
PrintTela("Atualizando dados dos sensores...");
bool enviarStatus = qtdEnviosStatus >= maxEnviosStatus && posicao != CanMessagePosicaoDados::Status;
bool todosDados = posicao == CanMessagePosicaoDados::DadosAll;
bool todosIDs = id_num == canService.ID_Num_sTOD;
if (id_num == canService.ID_Num_sLRA) {
EnviarDadosCAN(loraService.MontarMensagemCAN(posicao));
return;
}
if (todosIDs && enviarStatus) {
ProcessarChk();
EnviarDadosCAN(loraService.MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
for (auto* sensor : listaSensoresTemperatura) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (sensor->Iniciado) {
if (todosDados) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
}
for (auto* sensor : listaSensoresCorrente) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (sensor->Iniciado) {
if (todosDados) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados2));
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
}
for (auto* sensor : listaSinaleiros) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (sensor->Iniciado) {
if (todosDados) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
}
for (auto* sensor : listaReles) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (sensor->Iniciado) {
if (todosDados) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
}
for (auto* sensor : listaServoFreios) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (sensor->Iniciado) {
if (todosDados) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
}
for (auto* sensor : listaSensoresIMU) {
if (sensor != nullptr && (todosIDs || sensor->ID_Num == id_num)) {
sensor->RequisitarDados();
if (enviarStatus) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Status));
}
if (todosDados) {
if (sensor->MpuIniciado) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados1));
}
if (sensor->BmpIniciado) {
EnviarDadosCAN(sensor->MontarMensagemCAN(CanMessagePosicaoDados::Dados2));
}
}
else {
EnviarDadosCAN(sensor->MontarMensagemCAN(posicao));
}
}
}
if (enviarStatus) {
qtdEnviosStatus = 0;
}
else {
qtdEnviosStatus++;
}
}
latenciaLoop = millis() - startLoop;
}
void EnviarDadosCAN(std::vector<uint8_t> data) {
if (data.size() > 2) {
canService.adicionarMensagemFila(data);
}
}
void ProcessarFrameCAN(std::vector<uint8_t> data) {
if (data.size() < 1) return;
F_Code funcao = (F_Code)data[0];
switch (funcao) {
case F_Code::ChkTx: ProcessarChk(); break;
case F_Code::ReqTx: ProcessarReq(data); break;
case F_Code::CmdTx: ProcessarCmd(data); break;
case F_Code::CfgTx: ProcessarCfg(data); break;
}
}
void ProcessarChk() {
std::vector<uint8_t> data = canService.MontarFrameChk(D_Code, Conectado, VERSION);
EnviarDadosCAN(data);
}
void ProcessarReq(std::vector<uint8_t> data) {
if (data.size() < 3) return;
uint8_t id = data[1];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[2];
enviarDadosSensores(id, posicao);
}
void ProcessarCfg(std::vector<uint8_t> data) {
if (data.size() < 4) return;
std::vector<uint8_t> _chkRx;
uint8_t id = data[1];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[2];
S_Code componente = DetectarComponente(id);
if (componente == S_Code::sMOD) {
switch (posicao) {
case CanMessagePosicaoDados::Config1: {
if (data.size() < 8) return;
bool conectar = data[4] == 1;
int taxa = data[5] * 10;
bool requisitar = data[6] == 1;
loraService.AddressBase = data[7];
_TaxaAmostragem = taxa;
RequisitarDados = requisitar;
Conectar = conectar;
break;
}
case CanMessagePosicaoDados::Config2: {
if (data.size() < 6) return;
int pinoSDA = data[4];
int pinoSCL = data[5];
if (pinoSDA != PINO_INVALIDO && pinoSCL != PINO_INVALIDO) {
I2CService::DefinirPinos(pinoSDA, pinoSCL);
inicializarDependenciasI2C();
}
vTaskDelay(pdMS_TO_TICKS(100));
Conectado = Conectar;
_chkRx = canService.MontarFrameChk(D_Code, Conectado, VERSION);
LimparListasComponentes();
PrintTela("[SEN] Configuracao do modulo concluida");
break;
}
}
}
if (componente == S_Code::sLRA) {
_chkRx = loraService.ConfigurarModulo(data);
}
else {
componente = (S_Code)data[3];
switch (componente) {
case sTMP: {
_chkRx = SensorTemperatura::ConfigurarSensor(listaSensoresTemperatura, data, Mod_ID);
break;
}
case sCOR: {
_chkRx = SensorCorrente::ConfigurarSensor(listaSensoresCorrente, data, Mod_ID);
break;
}
case sLED: {
_chkRx = Sinaleiro::ConfigurarSinaleiro(listaSinaleiros, data, Mod_ID);
break;
}
case sRLE: {
_chkRx = Rele::ConfigurarRele(listaReles, data, Mod_ID);
break;
}
case sFRO: {
_chkRx = ServoFreio::ConfigurarServo(listaServoFreios, data, Mod_ID);
break;
}
case sIMU: {
_chkRx = SensorIMU::ConfigurarSensor(listaSensoresIMU, data, Mod_ID);
break;
}
}
}
EnviarDadosCAN(_chkRx);
}
void ProcessarCmd(std::vector<uint8_t> data) {
if (data.size() < 3) return;
uint8_t id = data[1];
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)data[2];
S_Code componente = DetectarComponente(id);
switch (componente) {
case sLED: {
Sinaleiro::ComandarSinaleiro(listaSinaleiros, data, id);
break;
}
case sRLE: {
Rele::ComandarRele(listaReles, data, id);
break;
}
case sFRO: {
ServoFreio::ComandarServo(listaServoFreios, data, id);
break;
}
case sLRA: {
loraService.EnviarComando(data);
break;
}
default:
break;
}
}
void LimparListasComponentes() {
for (auto* rele : listaReles) {
rele->Desligar();
PrintTela("Rele " + rele->_ID + " desligado.");
delete rele;
}
listaReles.clear();
for (auto* sinaleiro : listaSinaleiros) {
sinaleiro->Desligar();
PrintTela("Sinaleiro " + sinaleiro->_ID + " desligado.");
delete sinaleiro;
}
listaSinaleiros.clear();
for (auto* servo : listaServoFreios) {
servo->Desligar();
PrintTela("Servo " + servo->_ID + " desligado.");
delete servo;
}
listaServoFreios.clear();
for (auto* sensor : listaSensoresCorrente) {
sensor->Desligar();
PrintTela("Sensor de Corrente " + sensor->_ID + " desligado.");
delete sensor;
}
listaSensoresCorrente.clear();
for (auto* sensor : listaSensoresTemperatura) {
sensor->Desligar();
PrintTela("Sensor de Temperatura " + sensor->_ID + " desligado.");
delete sensor;
}
listaSensoresTemperatura.clear();
for (auto* sensor : listaSensoresIMU) {
sensor->Desligar();
PrintTela("Sensor IMU " + sensor->_ID + " desligado.");
delete sensor;
}
listaSensoresIMU.clear();
}
S_Code DetectarComponente(uint8_t idNum) {
if (idNum == canService.ID_Num_sMOD) return S_Code::sMOD;
if (idNum == canService.ID_Num_sTOD) return S_Code::sTOD;
if (idNum == canService.ID_Num_sLRA) return S_Code::sLRA;
for (auto* s : listaSensoresTemperatura) {
if (s->ID_Num == idNum) return S_Code::sTMP;
}
for (auto* s : listaSensoresCorrente) {
if (s->ID_Num == idNum) return S_Code::sCOR;
}
for (auto* s : listaSinaleiros) {
if (s->ID_Num == idNum) return S_Code::sLED;
}
for (auto* s : listaReles) {
if (s->ID_Num == idNum) return S_Code::sRLE;
}
for (auto* s : listaServoFreios) {
if (s->ID_Num == idNum) return S_Code::sFRO;
}
for (auto* s : listaSensoresIMU) {
if (s->ID_Num == idNum) return S_Code::sIMU;
}
return S_Code::sVZO;
}

View File

@ -0,0 +1,331 @@
#include <HardwareSerial.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
// Definições de pinos
#define PIN_M0 7
#define PIN_M1 15
#define PIN_AUX 18
// Definições de Serial
HardwareSerial SerialLoRa(2); // UART2 para o E220
HardwareSerial SerialCAN(1); // UART1 simulando o CAN
// Estado atual do módulo
enum LoRaMode { NORMAL, WAKE_UP, POWER_SAVING, CONFIG };
LoRaMode currentMode = NORMAL;
// Prototipagem
bool setLoRaMode(LoRaMode mode);
bool checkLoRaConnected();
void taskBridge(void *pvParameters);
void readParameters();
void saveParameters();
// Config inicial
void setup() {
Serial.begin(115200);
Serial.println("Iniciando ESP32-S3 + E220...");
// Inicializa GPIOs
pinMode(PIN_M0, OUTPUT);
pinMode(PIN_M1, OUTPUT);
pinMode(PIN_AUX, INPUT);
// Inicializa Seriais
SerialLoRa.begin(9600, SERIAL_8N1, 17, 16); // RX=17, TX=16
SerialCAN.begin(115200, SERIAL_8N1, 9, 10); // Simulação CAN (mude os pinos conforme seu setup)
// Coloca o módulo em modo normal
setLoRaMode(NORMAL);
delay(500); // Aguarda estabilização
// Testa conexão
if (checkLoRaConnected()) {
requisitarParametrosLoRa();
Serial.println("LoRa E220 detectado!");
} else {
Serial.println("Erro: LoRa E220 não detectado.");
}
// Cria a task de ponte entre LoRa e CAN
xTaskCreatePinnedToCore(
taskBridge,
"TaskBridge",
4096,
NULL,
1,
NULL,
1
);
}
void loop() {
// Você pode colocar comandos de teste aqui se quiser.
}
// Define o modo do LoRa
bool setLoRaMode(LoRaMode mode) {
// Ajusta os pinos M0 e M1 conforme o modo desejado
switch (mode) {
case NORMAL:
digitalWrite(PIN_M0, LOW);
digitalWrite(PIN_M1, LOW);
break;
case WAKE_UP:
digitalWrite(PIN_M0, HIGH);
digitalWrite(PIN_M1, LOW);
break;
case POWER_SAVING:
digitalWrite(PIN_M0, LOW);
digitalWrite(PIN_M1, HIGH);
break;
case CONFIG:
digitalWrite(PIN_M0, HIGH);
digitalWrite(PIN_M1, HIGH);
break;
}
currentMode = mode;
delay(100); // Aguarda sinalização inicial de troca
// ESPERA o AUX ir para HIGH, indicando que a troca completou
unsigned long timeout = millis() + 1000;
while (digitalRead(PIN_AUX) == LOW) {
if (millis() > timeout) {
Serial.println("Erro: Timeout aguardando AUX após mudança de modo.");
return false;
}
}
delay(100); // Manual pede 2ms após AUX ficar HIGH
return true;
}
// Verifica se o E220 responde
bool checkLoRaConnected() {
if (!setLoRaMode(CONFIG)) {
Serial.println("Falha ao mudar para CONFIG!");
return false;
}
bool conectado = false;
SerialLoRa.write(0xC1);
delay(100);
while (SerialLoRa.available()) {
uint8_t dado = SerialLoRa.read();
if (dado == 0xC1) {
conectado = true;
}
}
setLoRaMode(NORMAL);
return conectado;
}
String comandoRecebido = "";
// Task que faz o espelhamento Serial e interpreta comandos de texto
void taskBridge(void *pvParameters) {
while (true) {
if (Serial.available()) {
char c = (char)Serial.read();
if (c == '\n' || c == '\r') {
// Quando apertar ENTER, processa o comando recebido
if (comandoRecebido.length() > 0) {
if (comandoRecebido == "1") {
Serial.println("Comando: Requisitar parâmetros do LoRa");
requisitarParametrosLoRa();
}
else if (comandoRecebido.startsWith("2") && comandoRecebido.length() == 11) {
// comando: 2 + 10 digitos (2 digitos por byte em hexadecimal)
Serial.println("Comando: Configurar LoRa com novos parâmetros");
// Parseia os números
byte addr = strtol(comandoRecebido.substring(1, 3).c_str(), NULL, 16);
byte baud = strtol(comandoRecebido.substring(3, 5).c_str(), NULL, 16);
byte packet = strtol(comandoRecebido.substring(5, 7).c_str(), NULL, 16);
byte channel = strtol(comandoRecebido.substring(7, 9).c_str(), NULL, 16);
byte worCycle = strtol(comandoRecebido.substring(9, 11).c_str(), NULL, 16);
Serial.print("Addr: "); Serial.println(addr, HEX);
Serial.print("Baud: "); Serial.println(baud, HEX);
Serial.print("Packet: "); Serial.println(packet, HEX);
Serial.print("Channel: "); Serial.println(channel, HEX);
Serial.print("WorCycle: "); Serial.println(worCycle, HEX);
configurarModuloLoRa(addr, baud, packet, channel, worCycle);
}
else {
Serial.println("Comando inválido.");
}
comandoRecebido = ""; // Limpa para próxima leitura
}
}
else {
comandoRecebido += c;
}
}
// Dados recebidos do LoRa -> envia para o CAN normalmente
if (SerialLoRa.available()) {
while (SerialLoRa.available()) {
SerialCAN.write(SerialLoRa.read());
}
}
vTaskDelay(1);
}
}
struct LoRaParametrosModel {
uint8_t address;
uint8_t baudAndAirRate;
uint8_t packetSizeAndPower;
uint8_t channel;
uint8_t tranModeAndWorCycle;
};
// Parâmetros globais
LoRaParametrosModel parametrosAtual;
// Método que monta o comando de configuração
bool configurarModuloLoRa(byte addr, byte baud, byte packet, byte channel, byte worCycle) {
if (!setLoRaMode(CONFIG)) {
Serial.println("Falha ao mudar para CONFIG!");
return false;
}
limparBufferLoRa();
uint8_t command[9] = {
0xC0, // Código para configurar
0x00, // Registro inicial
0x06, // Quantidade de registros
0x00, // Reservado
addr,
baud,
packet,
channel,
worCycle
};
SerialLoRa.write(command, sizeof(command));
Serial.println("Comando de configuração enviado!");
unsigned long startTime = millis();
bool sucesso = false;
while (millis() - startTime < 3000) { // timeout 3 segundos
if (SerialLoRa.available()) {
uint8_t cabecalho = SerialLoRa.read();
if (cabecalho == 0xC1) { // 0xC1 é Resposta de Configuração
sucesso = true;
Serial.println("Confirmação de configuração recebida!");
} else {
Serial.print("Resposta inesperada: 0x");
Serial.println(cabecalho, HEX);
}
break;
}
delay(10);
}
setLoRaMode(NORMAL);
return sucesso;
}
// Método que requisita a leitura dos parâmetros atuais
bool requisitarParametrosLoRa() {
if (!setLoRaMode(CONFIG)) {
Serial.println("Falha ao mudar para CONFIG!");
return false;
}
limparBufferLoRa();
uint8_t command[3] = { 0xC1, 0x00, 0x0B };
SerialLoRa.write(command, sizeof(command));
Serial.println("Comando de requisição enviado!");
unsigned long startTime = millis();
bool sucesso = false;
while (!sucesso && millis() - startTime < 3000) { // timeout 3 segundos
if (SerialLoRa.available()) {
uint8_t cabecalho = SerialLoRa.read();
if (cabecalho == 0xC1) { // Cabeçalho de resposta correta
uint8_t startAddr = SerialLoRa.read(); // Deve ser 0x00
uint8_t dataLen = SerialLoRa.read(); // Deve ser 0x0B
if (startAddr == 0x00 && dataLen == 0x0B) {
Serial.println("Parâmetros recebidos corretamente.");
// Agora ler os registros
parametrosAtual.address = SerialLoRa.read();
parametrosAtual.baudAndAirRate = SerialLoRa.read();
parametrosAtual.packetSizeAndPower = SerialLoRa.read();
parametrosAtual.channel = SerialLoRa.read();
parametrosAtual.tranModeAndWorCycle = SerialLoRa.read();
// Exibe os valores lidos
Serial.println("Parametros lidos do módulo:");
Serial.print("Address: 0x");
Serial.println(parametrosAtual.address, HEX);
Serial.print("Baud and AirRate: 0x");
Serial.println(parametrosAtual.baudAndAirRate, HEX);
Serial.print("Packet Size and Power: 0x");
Serial.println(parametrosAtual.packetSizeAndPower, HEX);
Serial.print("Channel: 0x");
Serial.println(parametrosAtual.channel, HEX);
Serial.print("TranMode and WorCycle: 0x");
Serial.println(parametrosAtual.tranModeAndWorCycle, HEX);
// Ignorar os bytes restantes (Reservados, etc)
for (int i = 0; i < (0x0B - 5); i++) {
SerialLoRa.read();
}
sucesso = true;
}
} else if (cabecalho == 0xFC) {
Serial.println("Modo invalido!");
}
/*else {
Serial.print("Resposta inesperada: 0x");
Serial.println(cabecalho, HEX);
Serial.println("Resposta recebida: ");
while (SerialLoRa.available()) {
uint8_t byteLido = SerialLoRa.read();
Serial.print("0x");
Serial.print(byteLido, HEX);
Serial.print(" ");
}
Serial.println();
}*/
}
delay(10);
}
setLoRaMode(NORMAL);
return sucesso;
}
// Limpa buffer anterior
void limparBufferLoRa() {
while (SerialLoRa.available()) {
SerialLoRa.read();
}
}