ajuste no canservice da firmware

This commit is contained in:
Diego Freitas 2025-08-22 15:36:19 -03:00
parent cad0bfaabd
commit bde1ec5064
77 changed files with 1780 additions and 890 deletions

Binary file not shown.

View File

@ -768,16 +768,18 @@ namespace AgroBase.Forms.IHM
idx = PreencheDadosLinha(grid, Testes, idx, CorrenteMotor);
var ServoFreio = Modulo_Sen.Servos.FirstOrDefault(x => x.ID.Contains(Modulo.Modulo_ID) && x.Inicializado);
(bool atuar, int angAtivar) = Modulo.MovMotor.ConfigFreio.AtualizarDadosFreio(true, 0, 15);
bool AtivarFreio = SenIniciado && ServoFreio != null ? await EnviarComandoMovFreio(Variaveis.OperacaoEmAndamento.DispSen, ServoFreio, angAtivar, Testes[idx], 2000) : false;
Variaveis.OperacaoEmAndamento.Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Mov).UltimaDirecao = Direcao.EmFreio;
Variaveis.OperacaoEmAndamento.DispMvd.Dados.AtualizarDadosFreio();
bool AtivarFreio = SenIniciado && ServoFreio != null ? await EnviarComandoMovFreio(Variaveis.OperacaoEmAndamento.DispSen, ServoFreio, Modulo.MovMotor.ConfigFreio.AnguloControle, Testes[idx], 2000) : false;
idx = PreencheDadosLinha(grid, Testes, idx, ServoFreio != null ? Testes[idx].ValorAferido : -1);
var SensorAB7V2 = Modulo_Sen.Sensores.FirstOrDefault(x => x.ID == "AB7V2");
double CorrenteFreio = SensorAB7V2 != null ? SensorAB7V2.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente).atual.valor : -1;
double TensaoFreio = SensorAB7V2 != null ? SensorAB7V2.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Tensao).atual.valor : -1;
(bool desatuar, int angDesativar) = Modulo.MovMotor.ConfigFreio.AtualizarDadosFreio(false);
bool DesativarFreio = SenIniciado && ServoFreio != null ? await EnviarComandoMovFreio(Variaveis.OperacaoEmAndamento.DispSen, ServoFreio, angDesativar, Testes[idx], 2000) : false;
Variaveis.OperacaoEmAndamento.Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Mov).UltimaDirecao = Direcao.Parado;
Variaveis.OperacaoEmAndamento.DispMvd.Dados.AtualizarDadosFreio();
bool DesativarFreio = SenIniciado && ServoFreio != null ? await EnviarComandoMovFreio(Variaveis.OperacaoEmAndamento.DispSen, ServoFreio, Modulo.MovMotor.ConfigFreio.AnguloControle, Testes[idx], 2000) : false;
idx = PreencheDadosLinha(grid, Testes, idx, ServoFreio != null ? Testes[idx].ValorAferido : -1);
idx = PreencheDadosLinha(grid, Testes, idx, TensaoFreio);

View File

@ -168,7 +168,7 @@ namespace AgroBase.Forms.IHM
txtConcluidoEm.Text = _Sensoriamento.DataFim == DateTime.MinValue ? "-" : _Sensoriamento.DataFim.ToString("dd/MM HH:mm:ss");
txtDistanciaPercorrida.Text = _Sensoriamento.Trajetoria.DistanciaPercorrida.ToString("0.00") + " m";
txtTempoOperacao.Text = _Sensoriamento.TempoDecorrido.ToString("HH:mm:ss");
txtErvasIdentificadas.Text = _Sensoriamento.Atuador.QtdErvasIdentificadas.ToString();
//txtErvasIdentificadas.Text = _Sensoriamento.Atuador.QtdErvasIdentificadas.ToString();
txtHerbicidaAplicado.Text = _Sensoriamento.Atuador.HerbicidaConsumido.ToString("0.000") + " L";
txtBateriaConsumida.Text = _Sensoriamento.Bateria.BateriaConsumida.ToString("0.00") + " %";

View File

@ -136,45 +136,53 @@ namespace AgroBase.Forms.Operacoes
private async Task tmrLeitura_Tick()
{
this.Text = Variaveis.OperacaoEmAndamento.TextoFormOperacao("Manual");
pnlDir_DF.Invalidate();
pnlDir_DT.Invalidate();
pnlDir_EF.Invalidate();
pnlDir_ET.Invalidate();
pnlAtuadores.Invalidate();
pnlAnguloGPS.Invalidate();
pnlAnguloMagnetometro.Invalidate();
AtualizarInformacoesGerais();
var _Sensoriamento = Variaveis.OperacaoEmAndamento.Sensoriamento;
lblDistanciaEsquerda.Text = "Distancia Esq: " + _Sensoriamento.Trajetoria.DistanciaEsquerda.ToString("0.00") + " cm";
lblDistanciaDireita.Text = "Distancia Dir: " + _Sensoriamento.Trajetoria.DistanciaDireita.ToString("0.00") + " cm";
lblGPSsatelites.Text = _Sensoriamento.Gps.NumeroSatelites.valor.ToString() + " satélites";
lblGPSFix.Text = (_Sensoriamento.Gps.Ntrip_ativado ? "N " : "B ") + _Sensoriamento.Gps.IdadeCorrecao + ": " + _Sensoriamento.Gps.QualidadeFix.ToString();
lblGPSprecisao.Text = "Precisão: " + _Sensoriamento.Gps.PrecisaoCm.ToString("0.00") + " cm";
lblGPSvelocidade.Text = "Vel.: " + _Sensoriamento.Gps.Velocidade.ToString("0.00") + " km/h";
lblGPSaltitude.Text = "Altitude: " + _Sensoriamento.Gps.Altitude.ToString("0.0") + " m";
lblTensao.Text = _Sensoriamento.Bateria.TensaoInstantanea.ToString("0.00") + " v";
lblCorrente.Text = _Sensoriamento.Bateria.CorrenteInstantanea.ToString("0.00") + " A";
lblRPM.Text = _Sensoriamento.Movimentacao.RPMMedio.ToString("0") + " rpm";
lblVelocidade.Text = _Sensoriamento.Movimentacao.VelocidadeMedia.ToString("0.00");
bool atuadorAtivado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ConexaoAtiva || true;
chbPulverizador.Enabled = atuadorAtivado;
if (!atuadorAtivado)
try
{
chbPulverizador.Checked = false;
this.Text = Variaveis.OperacaoEmAndamento.TextoFormOperacao("Manual");
pnlDir_DF.Invalidate();
pnlDir_DT.Invalidate();
pnlDir_EF.Invalidate();
pnlDir_ET.Invalidate();
pnlAtuadores.Invalidate();
pnlAnguloGPS.Invalidate();
pnlAnguloMagnetometro.Invalidate();
AtualizarInformacoesGerais();
var _Sensoriamento = Variaveis.OperacaoEmAndamento.Sensoriamento;
lblDistanciaEsquerda.Text = "Distancia Esq: " + _Sensoriamento.Trajetoria.DistanciaEsquerda.ToString("0.00") + " cm";
lblDistanciaDireita.Text = "Distancia Dir: " + _Sensoriamento.Trajetoria.DistanciaDireita.ToString("0.00") + " cm";
lblGPSsatelites.Text = _Sensoriamento.Gps.NumeroSatelites.valor.ToString() + " satélites";
lblGPSFix.Text = (_Sensoriamento.Gps.Ntrip_ativado ? "N " : "B ") + _Sensoriamento.Gps.IdadeCorrecao + ": " + _Sensoriamento.Gps.QualidadeFix.ToString();
lblGPSprecisao.Text = "Precisão: " + _Sensoriamento.Gps.PrecisaoCm.ToString("0.00") + " cm";
lblGPSvelocidade.Text = "Vel.: " + _Sensoriamento.Gps.Velocidade.ToString("0.00") + " km/h";
lblGPSaltitude.Text = "Altitude: " + _Sensoriamento.Gps.Altitude.ToString("0.0") + " m";
lblTensao.Text = _Sensoriamento.Bateria.TensaoInstantanea.ToString("0.00") + " v";
lblCorrente.Text = _Sensoriamento.Bateria.CorrenteInstantanea.ToString("0.00") + " A";
lblRPM.Text = _Sensoriamento.Movimentacao.RPMMedio.ToString("0") + " rpm";
lblVelocidade.Text = _Sensoriamento.Movimentacao.VelocidadeMedia.ToString("0.00");
bool atuadorAtivado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ConexaoAtiva || true;
chbPulverizador.Enabled = atuadorAtivado;
if (!atuadorAtivado)
{
chbPulverizador.Checked = false;
}
if (!ReqFrameCam)
_ = AtualizarCameraSolo();
if (!ReqFrameSnr)
_ = AtualizarCameraCaminho();
}
catch
{
if (!ReqFrameCam)
_ = AtualizarCameraSolo();
if (!ReqFrameSnr)
_ = AtualizarCameraCaminho();
}
}
private async Task AtualizarCameraSolo()
@ -433,52 +441,57 @@ namespace AgroBase.Forms.Operacoes
private void PopularInformacoesGerais()
{
gridDados.Rows.Clear();
gridDados.Columns.Clear();
gridDados.Columns.Add("clDado", "Dado");
gridDados.Columns.Add("clValor", "Valor");
gridDados.Columns.Add("clUnidade", "Un");
try
{
gridDados.Rows.Clear();
gridDados.Columns.Clear();
gridDados.Columns.Add("clDado", "Dado");
gridDados.Columns.Add("clValor", "Valor");
gridDados.Columns.Add("clUnidade", "Un");
gridDados.Rows.Add("Tempo de Operação", "", "");
gridDados.Rows.Add("Tempo em Movimento", "", "");
gridDados.Rows.Add("Velocidade Média", "", "km/h");
gridDados.Rows.Add("Distância Percorrida", "", "m");
gridDados.Rows.Add("Nível de Bateria", "", "%");
gridDados.Rows.Add("Tensão da Bateria", "", "v");
gridDados.Rows.Add("Corrente da Bateria", "", "A");
gridDados.Rows.Add("Tempo Estimado Restante", "", "Min");
gridDados.Rows.Add("Barramento 3V", "", "V");
gridDados.Rows.Add("Barramento 3V", "", "mA");
gridDados.Rows.Add("Barramento 5V", "", "V");
gridDados.Rows.Add("Barramento 5V", "", "mA");
gridDados.Rows.Add("Barramento 7,2V", "", "V");
gridDados.Rows.Add("Barramento 7,2V", "", "mA");
gridDados.Rows.Add("Barramento 12V", "", "V");
gridDados.Rows.Add("Barramento 12V", "", "mA");
gridDados.Rows.Add("Barramento 19V", "", "V");
gridDados.Rows.Add("Barramento 19V", "", "mA");
gridDados.Rows.Add("Barramento 24V", "", "V");
gridDados.Rows.Add("Barramento 24V", "", "mA");
gridDados.Rows.Add("Nível do Reservatório", "", "%");
gridDados.Rows.Add("Volume do Reservatório", "", "L");
gridDados.Rows.Add("Pressão da Linha", "", "psi");
gridDados.Rows.Add("Potência na Bomba", "", "%");
gridDados.Rows.Add("Vazão Instantanea", "", "mL/s");
gridDados.Rows.Add("Vazão Media", "", "mL/s");
gridDados.Rows.Add("Volume Vazado", "", "mL");
gridDados.Rows.Add("", "", "");
gridDados.Rows.Add("Roll", "", "");
gridDados.Rows.Add("Pitch", "", "");
gridDados.Rows.Add("Yaw", "", "");
gridDados.Rows.Add("Temperatura", "", "");
gridDados.Rows.Add("Pressao", "", "");
gridDados.Rows.Add("Altitude", "", "");
gridDados.Rows.Add("Temperatura ET", "", "");
gridDados.Rows.Add("Temperatura EF", "", "");
gridDados.Rows.Add("Temperatura DT", "", "");
gridDados.Rows.Add("Temperatura DF", "", "");
gridDados.Rows.Add("Tempo de Operação", "", "");
gridDados.Rows.Add("Tempo em Movimento", "", "");
gridDados.Rows.Add("Velocidade Média", "", "km/h");
gridDados.Rows.Add("Distância Percorrida", "", "m");
gridDados.Rows.Add("Nível de Bateria", "", "%");
gridDados.Rows.Add("Tensão da Bateria", "", "v");
gridDados.Rows.Add("Corrente da Bateria", "", "A");
gridDados.Rows.Add("Tempo Estimado Restante", "", "Min");
gridDados.Rows.Add("Barramento 3V", "", "V");
gridDados.Rows.Add("Barramento 3V", "", "mA");
gridDados.Rows.Add("Barramento 5V", "", "V");
gridDados.Rows.Add("Barramento 5V", "", "mA");
gridDados.Rows.Add("Barramento 7,2V", "", "V");
gridDados.Rows.Add("Barramento 7,2V", "", "mA");
gridDados.Rows.Add("Barramento 12V", "", "V");
gridDados.Rows.Add("Barramento 12V", "", "mA");
gridDados.Rows.Add("Barramento 19V", "", "V");
gridDados.Rows.Add("Barramento 19V", "", "mA");
gridDados.Rows.Add("Barramento 24V", "", "V");
gridDados.Rows.Add("Barramento 24V", "", "mA");
gridDados.Rows.Add("Nível do Reservatório", "", "%");
gridDados.Rows.Add("Volume do Reservatório", "", "L");
gridDados.Rows.Add("Pressão da Linha", "", "psi");
gridDados.Rows.Add("Potência na Bomba", "", "%");
gridDados.Rows.Add("Vazão Instantanea", "", "mL/s");
gridDados.Rows.Add("Vazão Media", "", "mL/s");
gridDados.Rows.Add("Volume Vazado", "", "mL");
gridDados.Rows.Add("", "", "");
gridDados.Rows.Add("Roll", "", "");
gridDados.Rows.Add("Pitch", "", "");
gridDados.Rows.Add("Yaw", "", "");
gridDados.Rows.Add("Temperatura", "", "");
gridDados.Rows.Add("Pressao", "", "");
gridDados.Rows.Add("Altitude", "", "");
gridDados.Rows.Add("Temperatura ET", "", "");
gridDados.Rows.Add("Temperatura EF", "", "");
gridDados.Rows.Add("Temperatura DT", "", "");
gridDados.Rows.Add("Temperatura DF", "", "");
AtualizarInformacoesGerais();
AtualizarInformacoesGerais();
}
catch { }
}
private void AtualizarInformacoesGerais()

View File

@ -88,7 +88,7 @@ namespace AgroBase.Forms.Operacoes
lblDistanciaPercorrida.Text = "Distancia Percorrida: " + _Sensoriamento.Trajetoria.DistanciaPercorrida.ToString("0.00") + " m";
lblTempoOperacao.Text = "Tempo de Operação: " + _Sensoriamento.TempoDecorrido.ToString("HH:mm:ss");
lblVelocidadeMedia.Text = "Velocidade Média: " + _Sensoriamento.Movimentacao.VelocidadeMedia.ToString("0.00") + " km/h";
lblErvasIdentificadas.Text = "Ervas Identificadas: " + _Sensoriamento.Atuador.QtdErvasIdentificadas.ToString("000");
//lblErvasIdentificadas.Text = "Ervas Identificadas: " + _Sensoriamento.Atuador.QtdErvasIdentificadas.ToString("000");
lblHerbicidaAplicado.Text = "Herbicida Aplicado: " + _Sensoriamento.Atuador.HerbicidaConsumido.ToString("0.000") + " L";
lblHerbicidaPorErva.Text = "Herbicida por Erva: " + _Sensoriamento.Atuador.HerbicidaPorErva.ToString("0.000") + " mL";
lblBateriaConsumida.Text = "Bateria Consumida: " + _Sensoriamento.Bateria.BateriaConsumida.ToString("0.00") + "%";

View File

@ -741,7 +741,7 @@ namespace AgroBase.Forms.Operacoes
});
// Obtemos todos os tipos de ervas identificadas ao longo dos logs
var todasErvas = LogsOperacao
/*var todasErvas = LogsOperacao
.SelectMany(log => log.Atuador.ErvasIdentificadas)
.SelectMany(bico => bico.Keys)
.Distinct()
@ -795,7 +795,7 @@ namespace AgroBase.Forms.Operacoes
)
{
Titulo = "Ervas Identificadas"
};
};*/
List<GraficoInterativoSerieModel> seriesCamerasSolo = new List<GraficoInterativoSerieModel>();
foreach (var camSolo in LogsCam_Solo)
@ -1013,7 +1013,7 @@ namespace AgroBase.Forms.Operacoes
double scaleY = (double)bitmapComObjetos.Height / frame.height;
// Iterar sobre os objetos detectados
foreach (var objeto in frame.deteccoes)
/*foreach (var objeto in frame.deteccoes)
{
// Ajustar coordenadas e tamanho de acordo com a escala
int xAtual = (int)(objeto.bbox[0] * scaleX);
@ -1027,7 +1027,7 @@ namespace AgroBase.Forms.Operacoes
// Desenhar a descrição do objeto (id: descricao)
string descricao = $"{objeto.descricao} | {objeto.confianca.ToString("0.00")} | ({objeto.id})";
g.DrawString(descricao, new Font("Arial", 20), Brushes.White, new PointF(xAtual, yAtual - 15));
}
}*/
}
// Retornar o bitmap modificado
@ -1069,7 +1069,7 @@ namespace AgroBase.Forms.Operacoes
double herbicidaConsumido = Log.Atuador.HerbicidaConsumido != 0 ? Log.Atuador.HerbicidaConsumido : LogAtu.VolumeVazaoML;
double percentualHerbicidaConsumido = FuncoesMatematicas.Clamp(((herbicidaConsumido / 1000.0) / LogsAtuador.FirstOrDefault().VolumeReservatorio) * 100.0, 0, 100);
int totalAtuacoes = Log.Atuador.ErvasIdentificadas.Sum(dict => dict.Values.Sum());
int totalAtuacoes = 0; // Log.Atuador.ErvasIdentificadas.Sum(dict => dict.Values.Sum());
double herbicidaPorErva = Log.Atuador.HerbicidaPorErva != 0 ? Log.Atuador.HerbicidaPorErva : (herbicidaConsumido / (totalAtuacoes > 0 ? totalAtuacoes : 1));
lblPercentualBateria.Text = $"Bateria {Log.Bateria.PorcentagemBateria.ToString("0.00")}%";
@ -1444,9 +1444,9 @@ namespace AgroBase.Forms.Operacoes
private void AtualizarInformacoesSonar()
{
var Log = LogsOperacao[idxMomentoAtual].OperadorVisual.Analises;
txtSonarDistanciaObstaculo.Text = Log.matriz_confianca.block.d_obs_true_min_m.ToString("0.00");
txtSonarDistanciaParada.Text = Log.matriz_confianca.block.decision.dist_necessaria.ToString("0.00");
txtSonarDecisao.Text = Log.matriz_confianca.block.reason_detail;
txtSonarDistanciaObstaculo.Text = (Log.matriz_confianca?.block?.d_obs_true_min_m ?? 0).ToString("0.00");
txtSonarDistanciaParada.Text = (Log.matriz_confianca?.block?.decision?.dist_necessaria ?? 0).ToString("0.00");
txtSonarDecisao.Text = (Log.matriz_confianca?.block?.reason_detail ?? "");
if (gridObstaculos.Columns.Count == 0)
{
@ -1458,7 +1458,7 @@ namespace AgroBase.Forms.Operacoes
gridObstaculos.Rows.Clear();
if (Log != null)
if (Log?.deteccao != null)
{
foreach (var obstaculo in Log.deteccao)
{

View File

@ -56,7 +56,7 @@ namespace AgroBase.Models.Modules
FuncoesPinout.SCL,
};
public int QuantidadeBicos { get; set; } = 4;
public double DensidadeHerbicida { get; set; } = 1.1;
public double DensidadeHerbicida { get; set; } = 1.0;
public List<AtuadorBicoModel> BicosPulverizadores { get; set; } = new List<AtuadorBicoModel>();
public List<AtuadorBombaModel> BombasPressurizadoras { get; set; } = new List<AtuadorBombaModel>();
public List<SensorModel> Sensores { get; set; } = new List<SensorModel>();
@ -1330,7 +1330,7 @@ namespace AgroBase.Models.Modules
bool conectado = data[3] == 1;
int versao = data[4];
Console.WriteLine($"[ATU] Resposta {ID_Num} recebida. Tipo={tipoModulo}, Conectado={conectado}, Versão={versao}");
//Console.WriteLine($"[ATU] Resposta {ID_Num} recebida. Tipo={tipoModulo}, Conectado={conectado}, Versão={versao}");
Dados.DadosLeitura.Conectado = conectado;
Dados.DadosLeitura.Dispositivo = tipoModulo;
@ -1348,7 +1348,7 @@ namespace AgroBase.Models.Modules
{
double latenciaLoop = ConverterByteParaDouble(data, 2) * 100;
Dados.DadosLeitura.LatenciaLoop = Convert.ToInt32(latenciaLoop);
Console.WriteLine($"[ATU] Ciclo concluido. Latencia de loop: {latenciaLoop} ms");
//Console.WriteLine($"[ATU] Ciclo concluido. Latencia de loop: {latenciaLoop} ms");
break;
}
}

View File

@ -261,7 +261,7 @@ namespace AgroBase.Models.Modules
public List<MvdMotorMOVSensorimanetoGrafico> LogGrafico { get; set; } = new List<MvdMotorMOVSensorimanetoGrafico>();
public List<AlarmeModel> Alarmes { get; set; } = new List<AlarmeModel>();
public MvdServoFreioModel ConfigFreio { get; set; } = new MvdServoFreioModel();
public MvdServoFreioModel ConfigFreio { get; set; }
public static MovimentacaoModel CarregarParametrosIniciais(string Mod_ID, byte _EnderecoTx, byte _EnderecoRx)
@ -279,9 +279,9 @@ namespace AgroBase.Models.Modules
FuncoesPinout.PWM,
FuncoesPinout.BRK,
},
ConfigFreio = new MvdServoFreioModel()
ConfigFreio = new MvdServoFreioModel(Mod_ID)
{
AnguloInicial = 10,
AnguloInicial = Mod_ID.Contains("E") ? 10 : 170,
Incremental = Mod_ID.Contains("E"),
Leitura = false,
},
@ -291,70 +291,77 @@ namespace AgroBase.Models.Modules
TipoMovimentoDirecional.RodasDianteiras,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.RodasTraseiras,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.MovimentoDiagonal,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.MovimentoArco,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.RotacionarNoEixo,
new ConfiguracaoSentidoMotor()
{
Frente = Sentido.Horario,
Tras = Sentido.Antihorario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Sentido.Horario,
Tras = Sentido.Antihorario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.MovimentoLateral,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("T") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("T") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("T") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("T") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
{
TipoMovimentoDirecional.Diagnostico,
new ConfiguracaoSentidoMotor()
{
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario,
Direita = Sentido.Parado,
Esquerda = Sentido.Parado,
Frente = Mod_ID.Contains("E") ? Sentido.Horario : Sentido.Antihorario,
Tras = Mod_ID.Contains("E") ? Sentido.Antihorario : Sentido.Horario
Parado = Sentido.Parado,
}
},
},
@ -367,7 +374,6 @@ namespace AgroBase.Models.Modules
var simulando = Variaveis.OperacaoEmAndamento.Simulando || Variaveis.OperacaoEmAndamento.Treinando;
bool Freado = DirecaoAtual == Direcao.EmFreio;
(bool AtuarServo, int AnguloFreio) = ConfigFreio.AtualizarDadosFreio(Freado, 0, -1, 5);
if ((Comandar && Inicializado) || simulando)
{
@ -377,7 +383,6 @@ namespace AgroBase.Models.Modules
{
DirecaoAtual = Direcao.Parado;
Freado = false;
(AtuarServo, AnguloFreio) = ConfigFreio.AtualizarDadosFreio(Freado, 0, -1, 5);
}
RPM_SP_Controle = _Controle.RPM_Motor;
@ -437,16 +442,6 @@ namespace AgroBase.Models.Modules
LeituraRPM = RPM_SP;
}
}
if (AtuarServo)
{
string Mod_ID = Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.FirstOrDefault(x => x.MovMotor._EnderecoCAN_Tx == _EnderecoCAN_Tx)?.Modulo_ID ?? "";
var ServoFreio = Variaveis.OperacaoEmAndamento.DispSen.Dados.Servos.FirstOrDefault(x => x.ID.Contains(Mod_ID) && x.Inicializado && x.Controlar);
if (ServoFreio != null && ServoFreio.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura).atual.valor != AnguloFreio)
{
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sFRO, ServoFreio.ID, AnguloFreio);
}
}
}
@ -458,109 +453,170 @@ namespace AgroBase.Models.Modules
public class MvdServoFreioModel
{
public MvdServoFreioModel(string mod_id) { Mod_ID = mod_id; }
public string Mod_ID { get; set; }
// ---- Tunáveis ----
private const int TolAng = 2; // tolerância angular (graus)
private const int TolVelIn = 2; // histerese interna de velocidade (%)
private const int TolVelOut = 4; // histerese externa de velocidade (%)
private const int FatorEscala = 10; // proporcional p/ erro de velocidade
private const int AnguloMin = 0;
private const int AnguloMax = 180;
private const int AnguloControleMaxDefault = 20;
// ---- Estado ----
public bool Incremental { get; set; }
public int AnguloInicial { get; set; }
private int AnguloControle { get; set; } = -1;
private int AnguloControleMin
{
get
{
if (Incremental)
{
return AnguloMin;
}
else
{
return AnguloMax;
}
}
}
private int AnguloControleMax { get; set; } = 20;
public bool Leitura { get; set; }
private bool _controle { get; set; }
public bool Controle
public int AnguloControle { get; set; } = -1;
private int AnguloControleMax { get; set; } = AnguloControleMaxDefault;
private int VelocidadePercentual_SP { get; set; }
private bool VelOkMemo { get; set; } = true; // memorização p/ histerese
private bool UltimoControle { get; set; } = false; // último EmFreio?
public DateTime UltimoEnvio { get; set; }
public int DelayEnvio { get; set; } = 500;
public SensorServoModel Servo
{
get
{
return _controle;
return Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Servos?
.FirstOrDefault(x => x.ID.Contains(Mod_ID));
}
}
private int RPM_SP { get; set; }
private int MargemRPM { get; set; } = 2; // Margem de tolerância no RPM
private int AnguloMin { get; set; } = 0; // Ângulo mínimo permitido pelo servo
private int AnguloMax { get; set; } = 180; // Ângulo máximo permitido pelo servo
private int FatorEscala { get; set; } = 10; // Fator de ajuste do controle proporcional
public (bool, int) AtualizarDadosFreio(bool status, int rpm_sp = 0, int anguloInicial = -1, int anguloSP = -1)
private double VelocidadeAtual =>
FuncoesMatematicas.CalculaVelocidadePercentualMs(Variaveis.OperacaoEmAndamento.Sensoriamento.Movimentacao?.VelocidadeMedia ?? 0);
private bool StatusControleEmFreio =>
(Variaveis.OperacaoEmAndamento.Controle?.TiposControle?
.FirstOrDefault(x => x.Tipo == T_Code.Mov)?.UltimaDirecao
?? Direcao.Parado) == Direcao.EmFreio;
// leitura atual do ângulo (ou null)
private int AnguloLido
{
bool mudou = _controle != status;
_controle = status;
RPM_SP = rpm_sp;
if (!_controle)
get
{
AnguloControle = AnguloControleMin; // Solta o freio se não estiver no modo de controle
return (mudou, AnguloControle);
var v = Servo?.ValoresLeituras?
.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?
.atual?.valor ?? 0;
return (int)v;
}
}
if (anguloInicial == -1)
// histerese de velocidade
private bool VelocidadeOkHisterese(int sp, double atual)
{
var erro = Math.Abs(sp - atual);
if (VelOkMemo)
{
anguloInicial = AnguloInicial;
}
if (AnguloControle == -1)
{
AnguloControle = AnguloControleMin;
}
if (AnguloControle == AnguloControleMin)
{
AnguloControle = anguloInicial;
}
int ajuste = anguloSP;
if (anguloSP == -1)
{
double Rpm_Atual = FuncoesMatematicas.CalculaRPMVelocidade(Variaveis.OperacaoEmAndamento.DispMvd.Dados.VelocidadeMedia);
// Garante que os valores de RPM sejam válidos
Rpm_Atual = Math.Max(Rpm_Atual, 0);
RPM_SP = Math.Max(RPM_SP, 0);
// Verifica se o RPM atual está dentro da margem de tolerância
if (Math.Abs(Rpm_Atual - RPM_SP) <= MargemRPM)
{
return (false, AnguloControle); // Se dentro da margem, não ajusta o ângulo
}
// Calcula o erro e ajusta proporcionalmente
int erroRPM = RPM_SP - (int)Rpm_Atual;
ajuste = erroRPM / FatorEscala;
// Se incremental, aplica um ajuste limitado
if (Incremental)
{
ajuste = Math.Abs(ajuste); // Garante que o ajuste seja positivo
}
else
{
ajuste = -Math.Abs(ajuste); // Garante que o ajuste seja negativo
}
}
// Atualiza o ângulo de controle, garantindo que fique dentro dos limites
int limiteMax = AnguloInicial + (Incremental ? AnguloControleMax : -AnguloControleMax);
if (Incremental)
{
AnguloControle = (int)FuncoesMatematicas.Clamp(AnguloControle + ajuste, AnguloMin, limiteMax);
// estava OK → só sai se passar da banda externa
if (erro > TolVelOut) VelOkMemo = false;
}
else
{
AnguloControle = (int)FuncoesMatematicas.Clamp(AnguloControle - ajuste, limiteMax, AnguloMax);
// estava NOK → só entra se ficar dentro da banda interna
if (erro <= TolVelIn) VelOkMemo = true;
}
return VelOkMemo;
}
// condição “atingiu SP” com tolerância angular
private bool AtingiuSPComTol(int? lido, int alvo)
{
if (!lido.HasValue) return false; // sem leitura → considerar não atingido
return Math.Abs(lido.Value - alvo) <= TolAng;
}
public bool EnviarComando { get; private set; } = false;
public void AtualizarDados(int velocidade_sp = 0, int anguloInicial = -1, int anguloSP = -1)
{
VelocidadePercentual_SP = velocidade_sp;
if (!(Servo?.Controlar ?? false))
{
EnviarComando = false;
return;
}
return (true, AnguloControle);
bool emFreio = StatusControleEmFreio;
bool transicao = (UltimoControle != emFreio);
if (anguloInicial == -1) anguloInicial = AnguloInicial;
if (AnguloControle == -1) AnguloControle = Incremental ? AnguloMin : AnguloMax;
// ---- Calcula alvo de ângulo ----
int alvo;
if (!emFreio)
{
// Soltar freio: vai para o mínimo (liberado) conforme o sentido do seu hardware.
alvo = Incremental ? AnguloMin : AnguloMax;
}
else
{
// Em freio: definir alvo em torno do AnguloInicial +/- janela
int limiteLo, limiteHi;
if (Incremental)
{
limiteLo = AnguloMin;
limiteHi = Clamp(anguloInicial + AnguloControleMax, AnguloMin, AnguloMax);
}
else
{
limiteLo = Clamp(anguloInicial - AnguloControleMax, AnguloMin, AnguloMax);
limiteHi = AnguloMax;
}
if (anguloSP != -1)
{
// alvo explícito
alvo = Clamp(anguloSP, limiteLo, limiteHi);
}
else
{
// proporcional ao erro de velocidade (só para DEFINIR alvo, não para “reenviar”)
int erro = VelocidadePercentual_SP - (int)VelocidadeAtual; // pode ser + ou -
int ajuste = erro / FatorEscala;
ajuste = AnguloControleMax;
ajuste = Math.Abs(ajuste);
// direção do ajuste
if (Incremental)
{
alvo = Clamp(AnguloControle + ajuste, limiteLo, limiteHi);
}
else
{
alvo = Clamp(AnguloControle - ajuste, limiteLo, limiteHi);
}
}
}
// aplica alvo
int anguloAntigo = AnguloControle;
AnguloControle = alvo;
// ---- Decidir se deve enviar ----
// Regra: envia quando (a) houve transição de modo, (b) alvo mudou significativamente,
// ou (c) não atingiu o alvo atual (com tolerância).
bool alvoMudou = Math.Abs(AnguloControle - anguloAntigo) > 0;
bool atingiu = AtingiuSPComTol(AnguloLido, AnguloControle);
// histerese de velocidade apenas para DEFINIR o alvo (já considerado acima).
// Não use VelocidadeOk para forçar reenvio — isso causa “chatters” de comando.
bool vel_ok = VelocidadeOkHisterese(VelocidadePercentual_SP, VelocidadeAtual); // atualiza memo, se quiser expor depois
EnviarComando = transicao || alvoMudou || !atingiu;
UltimoControle = emFreio;
}
private static int Clamp(int v, int lo, int hi) => (v < lo) ? lo : (v > hi ? hi : v);
}
public class MvdMotorMOVSensorimanetoGrafico
@ -591,7 +647,7 @@ namespace AgroBase.Models.Modules
{
var ctrl = Variaveis.OperacaoEmAndamento?.Controle;
var disp = Variaveis.OperacaoEmAndamento?.DispMvd?.Dados?.Modulos;
if (Variaveis.OperacaoEmAndamento.StatusAtual == StatusOperacao.EmAndamento && ctrl == null || disp == null || !MovimentosCompensar.Contains(ctrl.TipoMovimento))
if (Variaveis.OperacaoEmAndamento.StatusAtual != StatusOperacao.EmAndamento || ctrl == null || disp == null || !MovimentosCompensar.Contains(ctrl.TipoMovimento))
{
compensacoes = (disp ?? Enumerable.Empty<ModuloMvdModel>()).ToDictionary(x => x.Modulo_ID, x => 1.0);
return;
@ -755,13 +811,6 @@ namespace AgroBase.Models.Modules
fRR = rExtRear / rIntRear;
}
if (!esquerda)
{
// Inverte se curva é para direita
(fFL, fFR) = (fFR, fFL);
(fRL, fRR) = (fRR, fRL);
}
// Saturação e leve suavização (ex: EMA) podem ser aplicadas aqui
fFL = Math.Min(fatorMaximo, fFL);
fFR = Math.Min(fatorMaximo, fFR);

View File

@ -417,6 +417,19 @@ namespace AgroBase.Models.Modules
return tasks;
}
public void AtualizarDadosFreio()
{
foreach (var Mod in Modulos)
{
var Freio = Mod.MovMotor.ConfigFreio;
Freio.AtualizarDados();
if (Freio.EnviarComando && Freio.UltimoEnvio.AddMilliseconds(Freio.DelayEnvio) < DateTime.Now)
{
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sFRO, Freio.Servo.ID, Freio.AnguloControle);
Freio.UltimoEnvio = DateTime.Now;
}
}
}
}

View File

@ -2466,7 +2466,7 @@ namespace AgroBase.Models.Modules
// FLOAT com ou sem ponto
float f = float.Parse(valor, CultureInfo.InvariantCulture);
ushort final = (ushort)(Escalar ? f * 100 : f);
short final = (short)(Escalar ? f * 100 : f);
if (QtdBytes == 1)
return new byte[] { (byte)(final & 0xFF) };

View File

@ -1019,6 +1019,10 @@ namespace AgroBase.Models
}
case T_Code.Atu:
{
if (ControleAnterior.BicosAtuados.Count != Controle.BicosAtuados.Count)
{
ControleAnterior.BicosAtuados = Controle.BicosAtuados.Select(x => x.Clone()).ToList();
}
for (int i = 0; i < _Controle.BicosAtuados.Count; i++)
{
if (ForcarEnvio || _Controle.BicosAtuados[i].ComandoAtuar != _ControleAnterior.BicosAtuados[i].ComandoAtuar)
@ -1677,6 +1681,7 @@ namespace AgroBase.Models
Variaveis.OperacaoEmAndamento.DadosPerformance.AtualizarDadosPerformance();
MovimentacaoMovimentoCompensadoModel.AtualizarDados();
Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.AtualizarDadosFreio();
// Soma total de atuações para todas as ervas de todos os bicos
int totalAtuacoes = 0;
@ -1702,8 +1707,7 @@ namespace AgroBase.Models
HerbicidaConsumido = _DispAtu.Dados.VolumeVazaoML,
PercentualErvasTerreno = _DispAtu.Dados.PercentualErvasTerreno,
HerbicidaPorErva = HerbicidaPorErva,
ErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.deteccoes?.Count ?? 0,
ErvasIdentificadas = WeedWorkerService.DadosLeitura?.Analise?.ervas_identificadas ?? new List<Dictionary<string, int>>(),
ErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.ervas_no_radar ?? false,
PercentualReservatorio = FuncoesMatematicas.Clamp(_DispAtu.Dados.PercentualReservatorio, 0, 100),
VolumeReservatorio = _DispAtu.Dados.VolumeReservatorio,
PressaoLinha = _DispAtu.Dados.PressaoLinha,
@ -1880,7 +1884,7 @@ namespace AgroBase.Models
public double HerbicidaConsumido { get; set; }
public double PercentualErvasTerreno { get; set; }
public double HerbicidaPorErva { get; set; }
public int ErvasNoRadar { get; set; }
public bool ErvasNoRadar { get; set; }
public double PercentualReservatorio { get; set; }
public double VolumeReservatorio { get; set; }
public double PressaoLinha { get; set; }
@ -1888,26 +1892,12 @@ namespace AgroBase.Models
public double VazaoMedia { get; set; }
public double LeituraPotenciaBomba { get; set; }
public double VolumeVazaoMl { get; set; }
public List<Dictionary<string, int>> ErvasIdentificadas { get; set; }
public int QtdErvasIdentificadas
{
get
{
int atuacoes = 0;
foreach (var bico in ErvasIdentificadas)
{
atuacoes += bico.Sum(x => x.Value);
}
return atuacoes;
}
}
public OperacaoSensoriamentoLogAtuModel Clone()
{
return new OperacaoSensoriamentoLogAtuModel()
{
QtdCamerasSolo = QtdCamerasSolo,
ErvasIdentificadas = new List<Dictionary<string, int>>(ErvasIdentificadas),
ErvasNoRadar = ErvasNoRadar,
HerbicidaConsumido = HerbicidaConsumido,
HerbicidaPorErva = HerbicidaPorErva,

View File

@ -26,8 +26,8 @@ namespace AgroBase.Models.Operadores
Pronto = Pronto,
ProntoEm = ProntoEm,
CameraFrames = new Dictionary<CameraFrameType, OAKCameraFrameModel>(CameraFrames ?? new Dictionary<CameraFrameType, OAKCameraFrameModel>()),
Analises = Analises.Clone(),
Imu = Imu.Clone(),
Analises = Analises?.Clone(),
Imu = Imu?.Clone(),
UltimaMensagem = UltimaMensagem,
};
}
@ -522,9 +522,9 @@ namespace AgroBase.Models.Operadores
grid_w = grid_w,
grid_h = grid_h,
y_range_m = y_range_m,
row_dist_m = new List<float>(row_dist_m),
row_scale_x_m = new List<float>(row_scale_x_m),
block = block.Clone()
row_dist_m = new List<float>(row_dist_m ?? new List<float>()),
row_scale_x_m = new List<float>(row_scale_x_m ?? new List<float>()),
block = block?.Clone() ?? new VisualWorkerMessageMatrizConfiancaBlockModel()
};
}
}

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static OpenTK.Graphics.OpenGL.GL;
namespace AgroBase.Models.Operadores
{
@ -41,23 +42,30 @@ namespace AgroBase.Models.Operadores
public class WeedWorkerAnaliseModel
{
public DateTime timestamp { get; set; }
public int width { get; set; }
public DateTime Timestamp
{
get
{
return FuncoesGlobais.UnixToDateTime(timestamp.ToString());
}
}
public double timestamp { get; set; }
public int height { get; set; }
public List<WeedWorkerAnaliseDeteccaoModel> deteccoes { get; set; }
public int width { get; set; }
public Dictionary<int, bool> controle { get; set; }
public List<Dictionary<string, int>> ervas_identificadas { get; set; }
public bool ervas_no_radar { get; set; }
public WeedWorkerAnaliseEstatisticasModel estatisticas { get; set; }
public WeedWorkerAnaliseModel Clone()
{
return new WeedWorkerAnaliseModel()
{
timestamp = timestamp,
deteccoes = new List<WeedWorkerAnaliseDeteccaoModel>(deteccoes ?? new List<WeedWorkerAnaliseDeteccaoModel>()),
controle = new Dictionary<int, bool>(controle ?? new Dictionary<int, bool>()),
ervas_identificadas = ervas_identificadas?.Select(dict => new Dictionary<string, int>(dict)).ToList(),
height = height,
width = width,
ervas_no_radar = ervas_no_radar,
estatisticas = estatisticas?.Clone()
};
}
}
@ -75,6 +83,76 @@ namespace AgroBase.Models.Operadores
public double confianca { get; set; }
}
public class WeedWorkerAnaliseEstatisticasModel
{
public WeedWorkerAnaliseEstatisticasErvasModel erva { get; set; }
public WeedWorkerAnaliseEstatisticasBicosModel bicos { get; set; }
public WeedWorkerAnaliseEstatisticasModel Clone()
{
return new WeedWorkerAnaliseEstatisticasModel()
{
erva = erva?.Clone(),
bicos = bicos?.Clone()
};
}
}
public class WeedWorkerAnaliseEstatisticasErvasModel
{
public double frac_global { get; set; }
public double ema_global { get; set; }
public double thr_on_global { get; set; }
public double thr_off_global { get; set; }
public double vel_adj { get; set; }
public WeedWorkerAnaliseEstatisticasErvasModel Clone()
{
return new WeedWorkerAnaliseEstatisticasErvasModel()
{
ema_global = ema_global,
frac_global = frac_global,
thr_off_global = thr_off_global,
thr_on_global = thr_on_global,
vel_adj = vel_adj,
};
}
}
public class WeedWorkerAnaliseEstatisticasBicosModel
{
public List<double> larguras { get; set; }
public List<double> contagem_cana_px_por_bico { get; set; }
public List<double> contagem_cana_frac_por_bico { get; set; }
public WeedWorkerAnaliseEstatisticasBicosFaixaModel faixa { get; set; }
public WeedWorkerAnaliseEstatisticasBicosModel Clone()
{
return new WeedWorkerAnaliseEstatisticasBicosModel()
{
larguras = new List<double>(larguras),
contagem_cana_frac_por_bico = new List<double>(contagem_cana_frac_por_bico),
contagem_cana_px_por_bico = new List<double>(contagem_cana_px_por_bico),
faixa = faixa?.Clone()
};
}
}
public class WeedWorkerAnaliseEstatisticasBicosFaixaModel
{
public int y_top { get; set; }
public int y_bot { get; set; }
public WeedWorkerAnaliseEstatisticasBicosFaixaModel Clone()
{
return new WeedWorkerAnaliseEstatisticasBicosFaixaModel()
{
y_bot = y_bot,
y_top = y_top,
};
}
}
public enum WeedWorkerCommandType
{
ScriptCarregado = 1,

View File

@ -1425,6 +1425,12 @@ namespace AgroBase.Models
return Velocidade;
}
public static double CalculaVelocidadePercentualMs(double vel_ms)
{
double Velocidade = vel_ms / CalculaVelocidadeMsPercentual(100);
return Velocidade;
}
public static double GrausParaRadianos(double angle)
{
return Math.PI / 180.0 * angle;

View File

@ -168,7 +168,7 @@ namespace AgroBase.Services
{
get
{
return SerialService.DispositivosCan.Any(d => SerialService.DispositivosMapeados.Any(x => x.Status == StatusModulo.Operante && x.Dispositivo == d));
return SerialService.DispositivosMapeados.Any(x => /*x.Status != StatusModulo.Desconectado &&*/ SerialService.DispositivosCan.Contains(x.Dispositivo));
}
}
private AsyncTaskTimerModel tmrEnviarCAN;

View File

@ -379,7 +379,7 @@ namespace AgroBase.Services
int.TryParse(qualidade, out int fix);
double.TryParse(idadeCorrecaoRaw, out double idadeCorrecao);
double.TryParse(idadeCorrecaoRaw.Replace(".", ","), out double idadeCorrecao);
//Console.WriteLine($"GNGGA: Hora={horaUTC}, Latitude={latitude}, Longitude={longitude}, Qualidade={qualidade}, Satélites={satelitesUsados}, HDOP={hdop}, Altitude={altitude}");
@ -793,7 +793,7 @@ namespace AgroBase.Services
private static async Task AplicarCorrecaoRTK_Ntrip()
{
if (LoopRTK_Ntrip || !APIService.HasInternet)
if (LoopRTK_Ntrip || !APIService.HasInternet || true)
return;
LoopRTK_Ntrip = true;
@ -853,8 +853,9 @@ namespace AgroBase.Services
}
else
{
Console.WriteLine($"Falha na conexão: {response}");
Console.WriteLine($"Falha na conexão com NTRIP: {response}");
await Task.Delay(5000); // Aguarde antes de tentar novamente
break;
}
}
}

View File

@ -170,7 +170,7 @@ namespace AgroBase.Services
if (!EnviarComando(comando))
{
porta.Close();
_PortaLoRa.Close();
return false;
}

View File

@ -330,7 +330,8 @@ namespace AgroBase.Services
_pulverizador.AddRange(CanPrefix(DadosLoRaParse.DadosPulverizador));
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.PercentualReservatorio)); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.PressaoLinha)); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.ErvasIdentificadas.Sum(x => x.Values.Sum()))); // 1 byte
//_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.ErvasIdentificadas.Sum(x => x.Values.Sum()))); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(0)); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.HerbicidaConsumido)); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.HerbicidaPorErva)); // 1 byte
_pulverizador.Add((byte)Convert.ToInt32(sensoriamento.Atuador.PercentualErvasTerreno)); // 1 byte

View File

@ -140,7 +140,7 @@ namespace AgroBase.Services.Operadores
("Gerais", new
{
velocidade_ms = _Sensoriamento.Movimentacao != null ? _Sensoriamento.Movimentacao.VelocidadeMedia : 0,
ervas_no_radar = _Sensoriamento.Atuador != null ? _Sensoriamento.Atuador.ErvasNoRadar : 0,
ervas_no_radar = _Sensoriamento.Atuador != null ? _Sensoriamento.Atuador.ErvasNoRadar : false,
}
)
);

View File

@ -16,7 +16,6 @@ namespace AgroBase.Services.Operadores
public SaudeWorkerModel Saude { get; set; } = new SaudeWorkerModel();
private bool DebugMode = true;
static long _lastTs = -1;
public void MostrarLog(string message, int limite = 500)
{
@ -172,70 +171,23 @@ namespace AgroBase.Services.Operadores
var analise = root["analise"] as JObject;
if (analise == null) return;
// 1) pega timestamp sem recriar dicionários
long tsUnix = analise["timestamp"]?.Value<long>() ?? 0;
if (tsUnix != 0)
try
{
var ts = FuncoesGlobais.UnixToDateTime(tsUnix.ToString());
DadosLeitura.Analise.timestamp = ts;
DadosLeitura.Analise = JsonConvert.DeserializeObject<WeedWorkerAnaliseModel>(root["analise"].ToString());
}
// 2) se não mudou, pule deserializações pesadas
if (tsUnix == _lastTs) goto AtualizaBicos;
_lastTs = tsUnix;
// 3) deteccoes (se precisar manter o tipo forte)
var detToken = analise["deteccoes"];
if (detToken != null)
catch (Exception ex)
{
var lista = detToken.ToObject<List<WeedWorkerAnaliseDeteccaoModel>>();
DadosLeitura.Analise.deteccoes = lista;
Console.WriteLine($"Erro ao deserializar analise de ervas: {ex.Message}");
}
// 4) controle: converte direto p/ int->bool sem ToString/dupla desserialização
var ctrlObj = analise["controle"] as JObject;
if (ctrlObj != null)
{
var dict = new Dictionary<int, bool>(ctrlObj.Count);
foreach (var prop in ctrlObj.Properties())
if (int.TryParse(prop.Name, out int k))
dict[k] = prop.Value.Value<bool>();
DadosLeitura.Analise.controle = dict;
}
// 5) ervas_identificadas: JArray -> List<Dictionary<string,int>>
var ervasTok = analise["ervas_identificadas"] as JArray;
if (ervasTok != null)
{
var ervas = new List<Dictionary<string, int>>(ervasTok.Count);
foreach (var item in ervasTok.OfType<JObject>())
{
var d = new Dictionary<string, int>(item.Count);
foreach (var p in item.Properties())
d[p.Name] = p.Value.Value<int>();
ervas.Add(d);
}
DadosLeitura.Analise.ervas_identificadas = ervas;
}
AtualizaBicos:
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BicosPulverizadores?.ForEach(bico =>
{
int atuacoes = 0;
var ervas = DadosLeitura?.Analise?.ervas_identificadas ?? new List<Dictionary<string, int>>();
if (bico.Posicao > 0 && bico.Posicao <= ervas.Count)
atuacoes = ervas[bico.Posicao - 1]?.Sum(x => x.Value) ?? 0;
bico.Atuacoes = atuacoes;
});
}
public static void ReiniciarLeituraAnalise()
{
RedisService.AtualizarCampos(
CtxKey.DadosWeedWorker,
("analise.deteccoes", new List<WeedWorkerAnaliseDeteccaoModel>()),
("analise.ervas_no_radar", false),
("analise.controle", new Dictionary<string, bool>(Variaveis.OperacaoEmAndamento.DispAtu.Dados.BicosPulverizadores.ToDictionary(x => x.Posicao.ToString(), x => x.ComandoAtuar))),
("analise.ervas_identificadas", new List<Dictionary<string, int>>())
("analise.estatisticas", new WeedWorkerAnaliseEstatisticasModel())
);
}

View File

@ -22,7 +22,7 @@ namespace AgroBase.Services
{
get
{
return SerialService.DispositivosCan.Any(d => SerialService.DispositivosMapeados.Any(x => x.Status != StatusModulo.Desconectado && x.Dispositivo == d));
return SerialService.DispositivosMapeados.Any(x => /*x.Status != StatusModulo.Desconectado &&*/ SerialService.DispositivosCan.Contains(x.Dispositivo));
}
}
private AsyncTaskTimerModel tmrEnviarCAN;
@ -55,22 +55,14 @@ namespace AgroBase.Services
{
try
{
if (Porta == null && (_PortaCAN?.IsOpen ?? false))
{
Porta = _PortaCAN;
}
if (Porta == null && _PortaCAN == null)
{
return false;
}
if (!IsConnected)
{
Porta.Close();
}
Porta?.Close();
if (_PortaCAN == null || !IsConnected)
if ((Porta != null && _PortaCAN == null) || !IsConnected)
{
lock (_lock)
{

View File

@ -208,7 +208,6 @@ namespace AgroBase.Services
bool LoRa = await ProcurarDispositivoLoRa(_Porta);
if (LoRa)
{
_Porta.Close();
continue;
}
}
@ -218,7 +217,6 @@ namespace AgroBase.Services
bool GPSEncontrado = await ProcurarDispositivoGPS(_Porta);
if (GPSEncontrado)
{
_Porta.Close();
continue;
}
}
@ -229,7 +227,6 @@ namespace AgroBase.Services
bool dispositivoCan = await ProcurarDispositivosCAN(_Porta);
if (dispositivoCan)
{
_Porta.Close();
AtualizarConsole("Barramento CAN encontrado");
continue;
}
@ -253,6 +250,10 @@ namespace AgroBase.Services
GPSService.PortaGPS?.Close();
GPSService.PortaGPS = null;
break;
case T_Code.Lra:
LoRaBaseService._PortaLoRa?.Close();
LoRaBaseService._PortaLoRa = null;
break;
}
}
@ -396,6 +397,11 @@ namespace AgroBase.Services
if (!CanManager.CanService.Inicializar(Porta))
{
AtualizarConsole($"Adaptador {CanManager.CanService._portName} não conectado!");
var dispositivosRemover = DispositivosMapeados.Where(x => DispositivosCan.Contains(x.Dispositivo)).ToList();
foreach (var d in dispositivosRemover)
{
DispositivosMapeados.Remove(d);
}
return false;
}
@ -419,7 +425,7 @@ namespace AgroBase.Services
}
}
if (true && !MKS057DCanService.Referenciando && (!MKS057DCanService.Iniciado || MKS057DCanService.DadosLeitura.Count(x => x.Iniciado) < (Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.Count() ?? 4)))
if (true && Variaveis.OperacaoEmAndamento.DispMvd != null && !MKS057DCanService.Referenciando && (!MKS057DCanService.Iniciado || MKS057DCanService.DadosLeitura.Count(x => x.Iniciado) < (Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.Count() ?? 4)))
{
AtualizarConsole($"{CanManager.CanService._portName} - Procurando dispositivos MKS");
bool DispMks = await MKS057DCanService.VerificaDispositivoConectado();
@ -429,7 +435,7 @@ namespace AgroBase.Services
}
}
if (true && (!OIDCanService.Iniciado || OIDCanService.DadosLeitura.Count(x => x.Iniciado) < (Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.Count() ?? 4)))
if (true && Variaveis.OperacaoEmAndamento.DispMvd != null && (!OIDCanService.Iniciado || OIDCanService.DadosLeitura.Count(x => x.Iniciado) < (Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.Count() ?? 4)))
{
AtualizarConsole($"{CanManager.CanService._portName} - Procurando dispositivo OID");
bool DispOid = await OIDCanService.VerificaDispositivoConectado();

View File

@ -1,12 +1,5 @@
{
"epochs": [ {
"calculation_time": "13397850728648898",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13398455528662484",
"config_version": 0,
"model_version": "0",
@ -29,5 +22,5 @@
"top_topics_and_observing_domains": [ ]
} ],
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
"next_scheduled_calculation_time": "13400432611771339"
"next_scheduled_calculation_time": "13400432611772238"
}

View File

@ -1,3 +1,3 @@
2025/08/18-17:01:38.585 14e0 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/18-17:01:38.592 14e0 Recovering log #3
2025/08/18-17:01:38.596 14e0 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/08/22-10:57:48.747 3fc4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/22-10:57:48.753 3fc4 Recovering log #3
2025/08/22-10:57:48.756 3fc4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1,3 +1,3 @@
2025/08/18-17:00:07.328 1a28 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/18-17:00:07.334 1a28 Recovering log #3
2025/08/18-17:00:07.337 1a28 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/08/22-10:47:37.338 2a88 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/22-10:47:37.345 2a88 Recovering log #3
2025/08/22-10:47:37.349 2a88 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log

View File

@ -1 +1 @@
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13400107303917497","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":10944},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:627:be00:b128:5cd2:a1f6:e65c","used_quic":true},"version":5},"network_qualities":{"CAASABiAgICA+P////8B":"4G","CAESABiAgICA+P////8B":"4G","CAISABiAgICA+P////8B":"4G","CAYSABiAgICA+P////8B":"Offline"}}}
{"net":{"http_server_properties":{"servers":[{"alternative_service":[{"advertised_alpns":["h3"],"expiration":"13400431527833615","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":18302},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:60c:f100:c78:e04e:44f:8fe","used_quic":true},"version":5},"network_qualities":{"CAASABiAgICA+P////8B":"4G","CAESABiAgICA+P////8B":"4G","CAISABiAgICA+P////8B":"4G","CAYSABiAgICA+P////8B":"Offline"}}}

View File

@ -1 +1 @@
{"sts":[{"expiry":1787083300.948773,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1755547300.948778}],"version":2}
{"sts":[{"expiry":1787405309.402605,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1755869309.402614}],"version":2}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2025/08/18-17:06:16.351 14e0 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/18-17:06:16.352 14e0 Recovering log #3
2025/08/18-17:06:16.355 14e0 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/08/22-13:45:28.240 3fc4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/22-13:45:28.242 3fc4 Recovering log #3
2025/08/22-13:45:28.246 3fc4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/18-17:00:57.069 1a28 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/18-17:00:57.071 1a28 Recovering log #3
2025/08/18-17:00:57.075 1a28 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/08/22-10:49:31.955 2a88 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/22-10:49:31.957 2a88 Recovering log #3
2025/08/22-10:49:31.960 2a88 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log

View File

@ -1,3 +1,3 @@
2025/08/18-17:01:38.507 481c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/18-17:01:38.509 481c Recovering log #7
2025/08/18-17:01:38.509 481c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/08/22-10:57:48.675 c54 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/22-10:57:48.676 c54 Recovering log #7
2025/08/22-10:57:48.677 c54 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

View File

@ -1,3 +1,3 @@
2025/08/18-17:00:07.253 4f4c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/18-17:00:07.255 4f4c Recovering log #7
2025/08/18-17:00:07.255 4f4c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/08/22-10:47:37.263 3d50 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/22-10:47:37.265 3d50 Recovering log #7
2025/08/22-10:47:37.265 3d50 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log

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

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Id":"1","Name":"16_08_2025_13_03_09_Manual","Length":0.0,"Dist1":0.0,"Dist2":0.0},"geometry":{"id":null,"type":"LineString","coordinates":[[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0],[0.0,0.0]]}}]}
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Id":"1","Name":"22_08_2025_10_41_55_Manual","Length":29.677008111445875,"Dist1":0.0,"Dist2":0.0},"geometry":{"id":null,"type":"LineString","coordinates":[[-47.395256450666665,-22.172346369833335],[-47.3952565435,-22.172346308833333],[-47.395256576833333,-22.1723463895],[-47.395256607833332,-22.172346390833333],[-47.395256612,-22.172346383666667],[-47.3952566095,-22.172346395833333],[-47.395256603,-22.17234638],[-47.395256633333332,-22.172346400166667],[-47.39525668666667,-22.172346436166666],[-47.395256690333333,-22.172346315],[-47.395256662833333,-22.172346244833335],[-47.3952566795,-22.172346234166668],[-47.395256709166667,-22.172346255666668],[-47.395256872166669,-22.172346330833335],[-47.395256888,-22.172346301],[-47.3952568545,-22.172346285],[-47.395256829,-22.172346216166666],[-47.395256821666663,-22.172346155333333],[-47.395256853333336,-22.172346067333333],[-47.395256828333331,-22.172346081333334],[-47.395256798333335,-22.172346064166668],[-47.39525658783333,-22.1723460885],[-47.395256589166664,-22.172345992],[-47.395256726833331,-22.17234593],[-47.3952567375,-22.1723458955],[-47.395256776833335,-22.172345912166666],[-47.3952566485,-22.172345831166666],[-47.395256515833331,-22.172345763833334],[-47.395256549666669,-22.172345739333334],[-47.395256393,-22.172345694166665],[-47.395256296166664,-22.172345727166668],[-47.395256282166663,-22.172345670166667],[-47.395256309333334,-22.1723456805],[-47.39525658116667,-22.172347672333334],[-47.395256880833337,-22.172348807333332],[-47.395258087333332,-22.172352117833334],[-47.395258773666669,-22.1723537055],[-47.395258515,-22.172356928333333],[-47.395258686666665,-22.172358251333332],[-47.395259433666666,-22.1723619705],[-47.395259733333333,-22.172363576],[-47.395260085,-22.172365142166665],[-47.395261047666665,-22.172367679166666],[-47.395261088833337,-22.172368784333333],[-47.395261036333331,-22.172368743833335],[-47.395260981166665,-22.172368728166667],[-47.395260906333334,-22.172370990833333],[-47.395261183166667,-22.172372657666667],[-47.395261496166668,-22.172374629166665],[-47.395261777166667,-22.1723753845],[-47.395261561333335,-22.1723772325],[-47.3952616635,-22.172379212666666],[-47.395259892666665,-22.1723810495],[-47.395259789333331,-22.172383026333332],[-47.395258998833334,-22.172386347833335],[-47.3952587635,-22.172388393333332],[-47.3952582685,-22.1723898615],[-47.395258494833335,-22.172393639666666],[-47.395250912166667,-22.172404825833333],[-47.395250136666668,-22.172405724833332],[-47.395250509,-22.172405612],[-47.3952558565,-22.172405050666665],[-47.39525849333333,-22.172409069],[-47.395260315166666,-22.172416536166665],[-47.3952563085,-22.172417938],[-47.395253391,-22.172420802166666],[-47.395256822166665,-22.172420572833332],[-47.395256906833332,-22.172424407666668],[-47.3952563335,-22.172425210166665],[-47.395257268833333,-22.172427588],[-47.395257951666665,-22.172428887166667],[-47.395257650833337,-22.172429874833334],[-47.395260252833332,-22.172433116666667],[-47.395263901833331,-22.172437103],[-47.3952669375,-22.172439262833333],[-47.395269575833332,-22.172440730833333],[-47.395276767333335,-22.172442326666665],[-47.395280671833333,-22.1724421815],[-47.395281228166667,-22.172442044666667],[-47.3952777085,-22.172442249166668],[-47.395270072833334,-22.172444610833335],[-47.395266372,-22.172447167833333],[-47.395261905,-22.172453009333335],[-47.395260418,-22.172456977166668],[-47.395258931166666,-22.172460745166667],[-47.395256378333336,-22.172467218166666],[-47.395255179333333,-22.1724699915],[-47.395252460666669,-22.1724764045],[-47.395250798333336,-22.172480014833333],[-47.3952483145,-22.1724866155],[-47.395247748666669,-22.172488646166666],[-47.395247018166664,-22.172494215333334],[-47.395246894333333,-22.172498196],[-47.39524629466667,-22.172500856166668],[-47.395242381833334,-22.172506576833332],[-47.39523919483333,-22.172508991666668],[-47.395232237333332,-22.172512476666668],[-47.3952284705,-22.172514312666667],[-47.395221581833333,-22.172517214333332],[-47.395217751333334,-22.172518779333334],[-47.395214971,-22.172520573666667],[-47.395210067333331,-22.1725241545],[-47.3952079915,-22.172527879833332],[-47.395204200333332,-22.1725354175],[-47.395202777666668,-22.1725386405],[-47.395202796833331,-22.17254124],[-47.39520273116667,-22.172542654166666],[-47.3952026795,-22.172543427166666],[-47.3952027095,-22.17254343],[-47.395202701833334,-22.172543459333333],[-47.395202711,-22.172543445166667],[-47.395202679833332,-22.172543447166667],[-47.395202698333335,-22.172543444],[-47.395202698333335,-22.172543444]]}}]}

View File

@ -17,7 +17,7 @@
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
#map_67e0a7b51ff20c385c068ee0a8d7495e {
#map_7d7f70bc75999486401c90b97c0360ad {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,14 +54,14 @@
<body>
<div class="folium-map" id="map_67e0a7b51ff20c385c068ee0a8d7495e" ></div>
<div class="folium-map" id="map_7d7f70bc75999486401c90b97c0360ad" ></div>
</body>
<script>
var map_67e0a7b51ff20c385c068ee0a8d7495e = L.map(
"map_67e0a7b51ff20c385c068ee0a8d7495e",
var map_7d7f70bc75999486401c90b97c0360ad = L.map(
"map_7d7f70bc75999486401c90b97c0360ad",
{
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
@ -77,26 +77,6 @@
var tile_layer_bcfeae14139b4f2f33677d1b47c8ee4f = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
"maxZoom": 19,
"maxNativeZoom": 19,
"noWrap": false,
"attribution": "\u0026copy; \u003ca href=\"https://www.openstreetmap.org/copyright\"\u003eOpenStreetMap\u003c/a\u003e contributors",
"subdomains": "abc",
"detectRetina": false,
"tms": false,
"opacity": 1,
}
);
tile_layer_bcfeae14139b4f2f33677d1b47c8ee4f.addTo(map_67e0a7b51ff20c385c068ee0a8d7495e);
</script>
<script>
@ -116,7 +96,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_67e0a7b51ff20c385c068ee0a8d7495e);
trajeto_json.addTo(map_7d7f70bc75999486401c90b97c0360ad);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -179,9 +159,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_67e0a7b51ff20c385c068ee0a8d7495e);
}).addTo(map_7d7f70bc75999486401c90b97c0360ad);
var marcadorBase = L.marker([0, 0], {}).addTo(map_67e0a7b51ff20c385c068ee0a8d7495e);
var marcadorBase = L.marker([0, 0], {}).addTo(map_7d7f70bc75999486401c90b97c0360ad);
var icon = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
);
@ -246,7 +226,7 @@
}
if (foco) {
map_67e0a7b51ff20c385c068ee0a8d7495e.setView(novaPosicao, map_67e0a7b51ff20c385c068ee0a8d7495e.getZoom());
map_7d7f70bc75999486401c90b97c0360ad.setView(novaPosicao, map_7d7f70bc75999486401c90b97c0360ad.getZoom());
}
}
@ -268,7 +248,7 @@
marcadorDinamico.setRotationAngle(angulo);
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
map_67e0a7b51ff20c385c068ee0a8d7495e.setView(novaPosicao, map_67e0a7b51ff20c385c068ee0a8d7495e.getZoom());*/
map_7d7f70bc75999486401c90b97c0360ad.setView(novaPosicao, map_7d7f70bc75999486401c90b97c0360ad.getZoom());*/
});
function calcularOrientacao(P1latitude, P1longitude, P2latitude, P2longitude) {

View File

@ -17,7 +17,7 @@
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<style>
#map_fa80b7c1ff2d8431553ba20c387f3aad {
#map_353f6ba1c95182a48ae91b9a891f095d {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,16 +54,16 @@
<body>
<div class="folium-map" id="map_fa80b7c1ff2d8431553ba20c387f3aad" ></div>
<div class="folium-map" id="map_353f6ba1c95182a48ae91b9a891f095d" ></div>
</body>
<script>
var map_fa80b7c1ff2d8431553ba20c387f3aad = L.map(
"map_fa80b7c1ff2d8431553ba20c387f3aad",
var map_353f6ba1c95182a48ae91b9a891f095d = L.map(
"map_353f6ba1c95182a48ae91b9a891f095d",
{
center: [0.0, 0.0],
center: [-22.172444564750002, -47.395241953833334],
crs: L.CRS.EPSG3857,
...{
"zoom": 12,
@ -78,7 +78,7 @@
var tile_layer_b446020bdc934e39b8d676cd95436167 = L.tileLayer(
var tile_layer_8564e33b7369e4710f4819bd3c55e0bf = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_b446020bdc934e39b8d676cd95436167.addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
tile_layer_8564e33b7369e4710f4819bd3c55e0bf.addTo(map_353f6ba1c95182a48ae91b9a891f095d);
@ -111,7 +111,7 @@
}*/
});
}
function geo_json_09ba5926a79ad74ce059c256c57c60e9_onEachFeature(feature, layer) {
function geo_json_b417ccdd5de3860de9f9bb6e9ce890b6_onEachFeature(feature, layer) {
layer.on({
@ -148,23 +148,23 @@
}*/
});
};
var geo_json_09ba5926a79ad74ce059c256c57c60e9 = L.geoJson(null, {
onEachFeature: geo_json_09ba5926a79ad74ce059c256c57c60e9_onEachFeature,
var geo_json_b417ccdd5de3860de9f9bb6e9ce890b6 = L.geoJson(null, {
onEachFeature: geo_json_b417ccdd5de3860de9f9bb6e9ce890b6_onEachFeature,
...{
}
});
function geo_json_09ba5926a79ad74ce059c256c57c60e9_add (data) {
geo_json_09ba5926a79ad74ce059c256c57c60e9
function geo_json_b417ccdd5de3860de9f9bb6e9ce890b6_add (data) {
geo_json_b417ccdd5de3860de9f9bb6e9ce890b6
.addData(data);
}
geo_json_09ba5926a79ad74ce059c256c57c60e9_add({"features": [{"geometry": {"coordinates": [[0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0], [0.0, 0.0]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 0.0, "Dist2": 0.0, "Id": "1", "Length": 0.0, "Name": "16_08_2025_13_03_09_Manual"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_09ba5926a79ad74ce059c256c57c60e9.setStyle(function(feature) {return feature.properties.style;});
geo_json_b417ccdd5de3860de9f9bb6e9ce890b6_add({"features": [{"geometry": {"coordinates": [[-47.395256450666665, -22.172346369833335], [-47.3952565435, -22.172346308833333], [-47.39525657683333, -22.1723463895], [-47.39525660783333, -22.172346390833333], [-47.395256612, -22.172346383666667], [-47.3952566095, -22.172346395833333], [-47.395256603, -22.17234638], [-47.39525663333333, -22.172346400166667], [-47.39525668666667, -22.172346436166666], [-47.39525669033333, -22.172346315], [-47.39525666283333, -22.172346244833335], [-47.3952566795, -22.172346234166668], [-47.39525670916667, -22.172346255666668], [-47.39525687216667, -22.172346330833335], [-47.395256888, -22.172346301], [-47.3952568545, -22.172346285], [-47.395256829, -22.172346216166666], [-47.39525682166666, -22.172346155333333], [-47.395256853333336, -22.172346067333333], [-47.39525682833333, -22.172346081333334], [-47.395256798333335, -22.172346064166668], [-47.39525658783333, -22.1723460885], [-47.395256589166664, -22.172345992], [-47.39525672683333, -22.17234593], [-47.3952567375, -22.1723458955], [-47.395256776833335, -22.172345912166666], [-47.3952566485, -22.172345831166666], [-47.39525651583333, -22.172345763833334], [-47.39525654966667, -22.172345739333334], [-47.395256393, -22.172345694166665], [-47.395256296166664, -22.172345727166668], [-47.39525628216666, -22.172345670166667], [-47.395256309333334, -22.1723456805], [-47.39525658116667, -22.172347672333334], [-47.39525688083334, -22.172348807333332], [-47.39525808733333, -22.172352117833334], [-47.39525877366667, -22.1723537055], [-47.395258515, -22.172356928333333], [-47.395258686666665, -22.172358251333332], [-47.395259433666666, -22.1723619705], [-47.39525973333333, -22.172363576], [-47.395260085, -22.172365142166665], [-47.395261047666665, -22.172367679166666], [-47.39526108883334, -22.172368784333333], [-47.39526103633333, -22.172368743833335], [-47.395260981166665, -22.172368728166667], [-47.395260906333334, -22.172370990833333], [-47.39526118316667, -22.172372657666667], [-47.39526149616667, -22.172374629166665], [-47.39526177716667, -22.1723753845], [-47.395261561333335, -22.1723772325], [-47.3952616635, -22.172379212666666], [-47.395259892666665, -22.1723810495], [-47.39525978933333, -22.172383026333332], [-47.395258998833334, -22.172386347833335], [-47.3952587635, -22.172388393333332], [-47.3952582685, -22.1723898615], [-47.395258494833335, -22.172393639666666], [-47.39525091216667, -22.172404825833333], [-47.39525013666667, -22.172405724833332], [-47.395250509, -22.172405612], [-47.3952558565, -22.172405050666665], [-47.39525849333333, -22.172409069], [-47.39526031516667, -22.172416536166665], [-47.3952563085, -22.172417938], [-47.395253391, -22.172420802166666], [-47.395256822166665, -22.17242057283333], [-47.39525690683333, -22.172424407666668], [-47.3952563335, -22.172425210166665], [-47.39525726883333, -22.172427588], [-47.395257951666665, -22.172428887166667], [-47.39525765083334, -22.172429874833334], [-47.39526025283333, -22.172433116666667], [-47.39526390183333, -22.172437103], [-47.3952669375, -22.172439262833333], [-47.39526957583333, -22.172440730833333], [-47.395276767333335, -22.172442326666665], [-47.39528067183333, -22.1724421815], [-47.39528122816667, -22.172442044666667], [-47.3952777085, -22.172442249166668], [-47.395270072833334, -22.172444610833335], [-47.395266372, -22.172447167833333], [-47.395261905, -22.172453009333335], [-47.395260418, -22.172456977166668], [-47.395258931166666, -22.172460745166667], [-47.395256378333336, -22.172467218166666], [-47.39525517933333, -22.1724699915], [-47.39525246066667, -22.1724764045], [-47.395250798333336, -22.172480014833333], [-47.3952483145, -22.1724866155], [-47.39524774866667, -22.172488646166666], [-47.395247018166664, -22.172494215333334], [-47.39524689433333, -22.172498196], [-47.39524629466667, -22.172500856166668], [-47.395242381833334, -22.17250657683333], [-47.39523919483333, -22.172508991666668], [-47.39523223733333, -22.172512476666668], [-47.3952284705, -22.172514312666667], [-47.39522158183333, -22.172517214333332], [-47.395217751333334, -22.172518779333334], [-47.395214971, -22.172520573666667], [-47.39521006733333, -22.1725241545], [-47.3952079915, -22.172527879833332], [-47.39520420033333, -22.1725354175], [-47.39520277766667, -22.1725386405], [-47.39520279683333, -22.17254124], [-47.39520273116667, -22.172542654166666], [-47.3952026795, -22.172543427166666], [-47.3952027095, -22.17254343], [-47.395202701833334, -22.172543459333333], [-47.395202711, -22.172543445166667], [-47.39520267983333, -22.172543447166667], [-47.395202698333335, -22.172543444], [-47.395202698333335, -22.172543444]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 0.0, "Dist2": 0.0, "Id": "1", "Length": 29.677008111445875, "Name": "22_08_2025_10_41_55_Manual"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_b417ccdd5de3860de9f9bb6e9ce890b6.setStyle(function(feature) {return feature.properties.style;});
geo_json_09ba5926a79ad74ce059c256c57c60e9.addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
geo_json_b417ccdd5de3860de9f9bb6e9ce890b6.addTo(map_353f6ba1c95182a48ae91b9a891f095d);
</script>
@ -185,7 +185,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
trajeto_json.addTo(map_353f6ba1c95182a48ae91b9a891f095d);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -243,7 +243,7 @@
}
trajeto_dinamico_json_add({"features": []});
trajeto_dinamico_json.addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
trajeto_dinamico_json.addTo(map_353f6ba1c95182a48ae91b9a891f095d);
function adicionarGeometriaDinamica(novaGeometria) {
trajeto_dinamico_json.addData(novaGeometria);
@ -296,9 +296,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
}).addTo(map_353f6ba1c95182a48ae91b9a891f095d);
var marcadorBase = L.marker([0, 0], {}).addTo(map_fa80b7c1ff2d8431553ba20c387f3aad);
var marcadorBase = L.marker([0, 0], {}).addTo(map_353f6ba1c95182a48ae91b9a891f095d);
var icon = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
);
@ -380,7 +380,7 @@
}
if (foco) {
map_fa80b7c1ff2d8431553ba20c387f3aad.setView(novaPosicao, map_fa80b7c1ff2d8431553ba20c387f3aad.getZoom());
map_353f6ba1c95182a48ae91b9a891f095d.setView(novaPosicao, map_353f6ba1c95182a48ae91b9a891f095d.getZoom());
}
}
@ -397,7 +397,7 @@
function atualizarSelecaoRuas(selecionadas) {
selecionadas = JSON.parse(selecionadas);
RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : [];
geo_json_09ba5926a79ad74ce059c256c57c60e9.eachLayer(function (layer) {
geo_json_b417ccdd5de3860de9f9bb6e9ce890b6.eachLayer(function (layer) {
if (RuasSelecionadas.includes(parseInt(layer.feature.id))) {
layer.setStyle({ color: 'blue' });
} else {

View File

@ -199,23 +199,35 @@ class CameraOak:
except Exception as e:
self.mostrar_log(f"[WARN] Falha ao montar pipeline imu: {e}")
script = pipeline.create(dai.node.Script)
if self.modelo_ia_seg is not None or self.modelo_ia_det is not None:
N_seg = self.modelo_ia_seg["det_every_n"]
N_det = self.modelo_ia_det["det_every_n"]
script = pipeline.create(dai.node.Script)
script.setProcessor(dai.ProcessorType.LEON_CSS)
script.setScript(f"""
from time import monotonic
i = 0
while True:
f = node.io['in'].get()
if i % {N_seg} == 0:
node.io['toSeg'].send(f)
if i % {N_det} == 0:
node.io['toDet'].send(f)
i += 1
""")
cam.video.link(script.inputs['in'])
try:
N_seg = (self.modelo_ia_seg or {}).get("det_every_n", 1)
N_det = (self.modelo_ia_det or {}).get("det_every_n", 1)
script.setProcessor(dai.ProcessorType.LEON_CSS)
script.setScript(f"""
from time import monotonic
i = 0
while True:
f = node.io['in'].get()
"""
+
f"""
if i % {N_seg} == 0:
node.io['toSeg'].send(f)
""" if self.modelo_ia_seg is not None else ""
+
f"""
if i % {N_det} == 0:
node.io['toDet'].send(f)
""" if self.modelo_ia_det is not None else ""
+
f"""
i += 1
""")
cam.video.link(script.inputs['in'])
except Exception as e:
self.mostrar_log(f"Erro ao criar script: {e}")
if self.modelo_ia_seg is not None:
try:

View File

@ -93,7 +93,7 @@ def definir_comando(pid: PIDAdaptativo):
},
"RegrasAtivas": {
"matriz_custo": False,
"deteccao_obstaculos": False,
"deteccao_obstaculos": True,
},
"Equipamento": {
"largura": _equipamento.get("largura", 0.85),
@ -197,22 +197,23 @@ def _regras_taticas(contexto):
# >>> NOVO: gate de bloqueio/parede com block + d_obs_min <<<
if vw_operante and contexto.get("RegrasAtivas", {}).get("deteccao_obstaculos", False):
block = dados_vw.get("MatrizCusto", {}).get("Block", {})
reason = block.get("reason", "none")
decision = block.get("decision", {})
block = dados_vw.get("MatrizCusto", {}).get("Block", None)
if block is not None:
reason = block.get("reason", "none")
decision = block.get("decision", {})
#print(block)
#print(block)
if decision.get("parar", False):
mostrar_log(f"🟥 Parada necessaria por {reason}.")
return _comando_direcional_parado(True)
if decision.get("parar", False):
mostrar_log(f"🟥 Parada necessaria por {reason}.")
return _comando_direcional_parado(True)
# motivo narrow: não para, mas dá dica lateral
if isinstance(block, dict) and reason == "narrow":
sb = (block.get("side_bias") or {}).get("value", 0.0)
hints = contexto.setdefault("DirecionalHints", {})
if sb > 0.25: hints["vetar_direita"] = True
if sb < -0.25: hints["vetar_esquerda"] = True
# motivo narrow: não para, mas dá dica lateral
if isinstance(block, dict) and reason == "narrow":
sb = (block.get("side_bias") or {}).get("value", 0.0)
hints = contexto.setdefault("DirecionalHints", {})
if sb > 0.25: hints["vetar_direita"] = True
if sb < -0.25: hints["vetar_esquerda"] = True
# se chegou até aqui, pode seguir
return _comando_direcional_parado(False)

View File

@ -25,7 +25,7 @@ def definir_comando(parada_necessaria: bool, erro: bool, filtro_vel: FiltroVeloc
_contexto = ContextoGlobalRedis.get_contexto()
_controle = ContextoGlobalRedis.get_controle()
pulverizador_automatico = _controle.get("pulverizador_automatico", False)
ervas_no_radar = _contexto.get("Gerais", {}).get("ervas_no_radar")
ervas_no_radar = _contexto.get("Gerais", {}).get("ervas_no_radar", False)
vel_min = _operacao.get("Mov", {}).get("percent_vel_min", 0)
vel_max = _operacao.get("Mov", {}).get("percent_vel_max", 100)
ang_max = _operacao.get("Dir", {}).get("angulo_max", 30)
@ -57,7 +57,7 @@ def definir_comando(parada_necessaria: bool, erro: bool, filtro_vel: FiltroVeloc
elif status_carro in [StatusCarroMapa.EntrandoRua, StatusCarroMapa.SaindoRua, StatusCarroMapa.Manobrando]:
velocidade_sp = vel_min
elif status_carro == StatusCarroMapa.CaminhandoRua:
if pulverizador_automatico and ervas_no_radar > 0:
if pulverizador_automatico and ervas_no_radar:
velocidade_sp = vel_min
else:
velocidade_sp = calcular_velocidade_relativa(vel_min, vel_max, ang_max, erro)

View File

@ -43,14 +43,14 @@ class CameraManager:
self.mx_id = mx_id
from weed_worker.config import load_seg_config
camera_config = load_seg_config()
seg_config = load_seg_config()
try:
nova = CameraOak(self.mostrar_log, mx_id, modelo_ia_onboard=camera_config)
nova = CameraOak(self.mostrar_log, mx_id, modelo_ia_seg=seg_config)
if nova.iniciado:
self.camera = nova
except Exception as e:
self.mostrar_log(f"⚠️ Camera com ID {mx_id} não conectada.")
self.mostrar_log(f"⚠️ Camera com ID {mx_id} não conectada: {e}")
self.iniciando = False
return
if self.camera is None:

View File

@ -92,7 +92,8 @@ def load_seg_config(force_reload=False):
"min_frac_erva_top_off": 0.0010,
"min_frac_erva_por_bico": 0.02,
"usar_morfologia": True,
"kernel_morf": 3
"kernel_morf": 3,
"det_every_n": 1,
}
dadosAtu = ContextoGlobalRedis.get_operacao().get("Atu", {})
contexto = ContextoGlobalRedis.get_contexto()

View File

@ -2,7 +2,6 @@ from enum import IntEnum
import time
import cv2
import numpy as np
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
from shared.utils import encode_image_base64
class ClassesSegmentacao(IntEnum):
@ -47,7 +46,6 @@ class WeedDetector:
self.ervas_ativas_filtradas = []
self.next_erva_id = 1
self.frame_idx = 0
self.ervas_identificadas = [dict() for _ in range(qtd_bicos)]
self.ultimo_status_bicos = {i: False for i in range(qtd_bicos)}
self.ervas_registradas_bico = [set() for _ in range(qtd_bicos)]
self.pred_rgb = np.empty((self.resolucao[1], self.resolucao[0], 3), dtype=np.uint8)
@ -59,12 +57,7 @@ class WeedDetector:
self._erva_frac_global_ema = 0.0
self._ervas_no_radar = False
self._ervas_no_radar_percent = 0.0
self.debug_estat = False
ContextoGlobalRedis.atualizar_ctx_dict(
CtxKey.DadosWeedWorker,
analise__ervas_identificadas=self.ervas_identificadas
)
self.debug_estat = True
def _segmentar_predictions(self, predictions):
try:
@ -112,9 +105,7 @@ class WeedDetector:
"timestamp": time.time(),
"height": predictions.shape[0],
"width": predictions.shape[1],
"deteccoes": [], # deteccoes_filtro,
"controle": controle_bicos,
"ervas_identificadas": self.ervas_identificadas,
"ervas_no_radar": ervas_no_radar,
"estatisticas": {
"erva": estat_erva,
@ -251,7 +242,7 @@ class WeedDetector:
"ema_global": ema_g,
"thr_on_global": on_global,
"thr_off_global": off_global,
"vel_adj": adj,
"vel_adj": adj
}
return ervas_no_radar, None

View File

@ -2,8 +2,8 @@
#include <SimpleKalmanFilter.h>
// Defina os pinos para o módulo HX711
#define PINO_DT 4 // Pino de dados do HX711 ao GPIO5 do ESP32
#define PINO_SCK 5 // Pino de clock do HX711 ao GPIO18 do ESP32
#define PINO_DT 15 // Pino de dados do HX711 ao GPIO5 do ESP32
#define PINO_SCK 16 // Pino de clock do HX711 ao GPIO18 do ESP32
SimpleKalmanFilter kalman = SimpleKalmanFilter(2, 2, 0.01);
double massaRef = 0.8;

View File

@ -0,0 +1,276 @@
#include "HX711.h"
#include <SimpleKalmanFilter.h>
// --------- Pinos (ESP32) ----------
#define PINO_DT 15 // HX711 DT
#define PINO_SCK 16 // HX711 SCK
// --------- Opções de filtragem ----------
int USAR_KALMAN = 1;
#define MEDIA_N 4 // média simples antes do Kalman (2~4 é bom)
// Kalman: (measErr, estErr, q)
SimpleKalmanFilter kalman(0.05, 0.05, 0.25);
// --------- HX711 ----------
HX711 balanca;
// --------- Estado ----------
bool temBaseline = false;
long baseline_raw = 0; // raw sem peso (offset "real")
float escala_cnt_por_kg = 1.0f; // scale
bool scale_negativo = false;
bool calibrado = false;
// --------- Utilidades ----------
String rxLine;
long lerRawMedio(uint8_t n = 10, uint16_t dt_ms = 4) {
long soma = 0;
for (uint8_t i = 0; i < n; i++) {
soma += balanca.read();
delay(dt_ms);
}
return soma / (long)n;
}
float lerKgInstantaneo() {
// Sem depender de tare():
// kg = (raw_atual - baseline_raw) / escala_cnt_por_kg
long raw = lerRawMedio(MEDIA_N, 2);
float kg = (float)(raw - baseline_raw) / escala_cnt_por_kg;
#if USAR_KALMAN
return kalman.updateEstimate(kg);
#else
return kg;
#endif
}
void aplicarOffsetEScale(long offset_raw, float scale_cnt_por_kg) {
// Se a tua lib tiver set_offset(), usamos pra manter get_units funcional
// Mas como estamos calculando na mão, isto é opcional.
// Ainda assim setamos por conveniência:
balanca.set_offset(offset_raw);
balanca.set_scale(scale_cnt_por_kg);
baseline_raw = offset_raw;
escala_cnt_por_kg = scale_cnt_por_kg;
temBaseline = true;
calibrado = true;
}
void printStatus() {
Serial.println(F("\n--- STATUS ---"));
Serial.print(F("baseline_raw (offset): ")); Serial.println(baseline_raw);
Serial.print(F("scale (counts/kg): ")); Serial.println(escala_cnt_por_kg, 6);
Serial.print(F("calibrado: ")); Serial.println(calibrado ? "sim" : "nao");
Serial.print(F("kg (inst): ")); Serial.println(lerKgInstantaneo(), 3);
Serial.println(F("--------------\n"));
}
// --------- Comandos ---------
// Formatos aceitos (digite e ENTER no Serial Monitor em 115200):
// baseline -> mede e salva raw sem peso
// calibrar <kg> -> mede raw com peso e calcula scale = (raw1 - baseline) / kg
// peso <kg> -> alias de calibrar <kg>
// scale <valor> -> aplica scale diretamente (mantém baseline salvo)
// offset <valor> -> aplica offset/baseline diretamente
// status -> mostra config atual
// q <valor> -> ajusta Kalman.Q (ruído de processo)
// kalman on/off -> liga/desliga Kalman
// help -> lista
void processCommand(const String& line) {
String cmd = line;
cmd.trim();
cmd.toLowerCase();
if (cmd == "" ) return;
if (cmd == "help") {
Serial.println(F("\nComandos:"));
Serial.println(F(" baseline"));
Serial.println(F(" calibrar <kg> (ex: calibrar 6.045)"));
Serial.println(F(" peso <kg> (alias de calibrar)"));
Serial.println(F(" scale <valor> (counts por kg)"));
Serial.println(F(" offset <valor> (raw sem peso)"));
Serial.println(F(" status"));
Serial.println(F(" q <valor> (ajusta Kalman Q)"));
Serial.println(F(" kalman on | off"));
Serial.println();
return;
}
if (cmd == "baseline") {
Serial.println(F("\n[BASELINE] Deixe SEM peso e mantenha estável..."));
delay(1200);
long raw0 = lerRawMedio(20, 5);
temBaseline = true;
baseline_raw = raw0;
// Mantém scale atual; se ainda não calibrado, fica 1.0
balanca.set_offset(baseline_raw);
Serial.print(F("[OK] baseline_raw = "));
Serial.println(baseline_raw);
if (!calibrado) {
Serial.println(F("Agora coloque uma massa conhecida e use: calibrar <kg>"));
}
return;
}
if (cmd.startsWith("calibrar ") || cmd.startsWith("peso ")) {
int sp = cmd.indexOf(' ');
if (sp > 0) {
float kg_ref = cmd.substring(sp + 1).toFloat();
if (kg_ref <= 0.0f) {
Serial.println(F("[ERRO] Peso de referencia invalido."));
return;
}
if (!temBaseline) {
Serial.println(F("[ERRO] Sem baseline. Rode 'baseline' primeiro (sem peso)."));
return;
}
Serial.print(F("\n[CALIBRAR] Massa de referencia = "));
Serial.print(kg_ref, 3);
Serial.println(F(" kg. Mantenha estavel..."));
delay(1500);
long raw1 = lerRawMedio(30, 5);
long delta = raw1 - baseline_raw;
if (delta == 0) {
Serial.println(F("[ERRO] Delta zero. Cheque conexoes / massa / baseline."));
return;
}
float scale_calc = (float)delta / kg_ref;
if (scale_calc < 0) scale_calc = -scale_calc; // garante sinal correto
aplicarOffsetEScale(baseline_raw, scale_calc);
// Teste rápido
float kg_med = lerKgInstantaneo();
Serial.print(F("[OK] scale = "));
Serial.println(scale_calc, 6);
Serial.print(F("Leitura estimada (esperado ~ "));
Serial.print(kg_ref, 3);
Serial.print(F(" kg): "));
Serial.print(kg_med, 3);
Serial.println(F(" kg\nRemova a massa e use 'status' para conferir.\n"));
return;
}
}
if (cmd.startsWith("scale ")) {
int sp = cmd.indexOf(' ');
float sc = cmd.substring(sp + 1).toFloat();
if (sc <= 0.0f) {
Serial.println(F("[ERRO] Scale invalido."));
return;
}
escala_cnt_por_kg = sc;
balanca.set_scale(sc);
calibrado = temBaseline; // se já tem baseline, agora temos os dois
Serial.print(F("[OK] scale = "));
Serial.println(escala_cnt_por_kg, 6);
return;
}
if (cmd.startsWith("offset ")) {
int sp = cmd.indexOf(' ');
long off = (long)cmd.substring(sp + 1).toDouble(); // aceita grandão
baseline_raw = off;
temBaseline = true;
balanca.set_offset(off);
calibrado = calibrado || (escala_cnt_por_kg > 0.0f);
Serial.print(F("[OK] baseline_raw (offset) = "));
Serial.println(baseline_raw);
return;
}
if (cmd == "status") {
printStatus();
return;
}
if (cmd.startsWith("q ")) {
int sp = cmd.indexOf(' ');
float q = cmd.substring(sp + 1).toFloat();
if (q <= 0) {
Serial.println(F("[ERRO] Q invalido."));
return;
}
// recria o filtro mantendo erros iguais
kalman = SimpleKalmanFilter(0.05, 0.05, q);
Serial.print(F("[OK] Kalman.Q = "));
Serial.println(q, 4);
return;
}
if (cmd == "kalman on") {
//#if USAR_KALMAN
// Serial.println(F("[OK] Kalman ja esta ON (compile-time)."));
//#else
// Serial.println(F("[INFO] Recompile com USAR_KALMAN=1 para ligar."));
//#endif
Serial.println(F("[OK] Kalman ativado."));
USAR_KALMAN = 1;
return;
}
if (cmd == "kalman off") {
//#if USAR_KALMAN
// Serial.println(F("[INFO] Recompile com USAR_KALMAN=0 para desligar."));
//#else
// Serial.println(F("[OK] Kalman OFF (compile-time)."));
//#endif
Serial.println(F("[OK] Kalman desativado."));
USAR_KALMAN = 0;
return;
}
Serial.println(F("[ERRO] Comando desconhecido. Digite 'help' para lista."));
}
// --------- Setup / Loop ---------
void setup() {
Serial.begin(115200);
delay(300);
balanca.begin(PINO_DT, PINO_SCK);
// Não usar tare(). Vamos trabalhar com baseline/offset reais.
balanca.set_scale(1.0); // neutro inicial
// offset será configurado quando fizermos 'baseline' ou 'offset'
Serial.println(F("\n=== Balança 4 células (HX711) ==="));
Serial.println(F("Pinos: DT=15, SCK=16"));
Serial.println(F("Fluxo sugerido:"));
Serial.println(F(" 1) baseline (sem peso)"));
Serial.println(F(" 2) calibrar 6.045 (exemplo)"));
Serial.println(F(" 3) status"));
Serial.println(F("Comandos: help | baseline | calibrar <kg> | peso <kg> | scale <v> | offset <v> | status | q <v> | kalman on/off\n"));
}
void loop() {
// Coleta linha completa do Serial (até '\n')
while (Serial.available()) {
char c = (char)Serial.read();
if (c == '\r') continue;
if (c == '\n') {
processCommand(rxLine);
rxLine = "";
} else {
rxLine += c;
// proteção simples contra linha gigante
if (rxLine.length() > 120) rxLine = "";
}
}
// Leitura contínua
if (temBaseline && (escala_cnt_por_kg > 0.0f)) {
float kg = lerKgInstantaneo();
Serial.print(F("Massa (kg): "));
Serial.println(kg, 3);
} else {
// Ajuda visual até calibrar
long raw = lerRawMedio(4, 2);
Serial.print(F("Aguardando baseline/calibracao... raw="));
Serial.println(raw);
}
delay(120);
}

View File

@ -1,474 +1,542 @@
#include "SerialService.h"
// CanService.h
#ifndef CANSERVICE_H
#define CANSERVICE_H
/*
#include <CAN.h>
#include <Arduino.h>
class CanService {
public:
typedef void (*OnReceiveCallback)(int packetSize, int senderId, byte funcCode, byte* data, int dataLength);
CanService(uint8_t nodeId, uint32_t baudRate = 500E3) {
_baudRate = baudRate;
_nodeId = nodeId;
}
void begin() {
if (!CAN.begin(_baudRate)) {
PrintTela("[CAN] Falha ao iniciar");
return;
}
PrintTela("[CAN] Inicializado com sucesso");
CAN.onReceive(onReceiveWrapper);
instance = this;
}
void loop() {
// Apenas processa mensagens recebidas via interrupcao
}
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool enviarMensagem(byte funcCode, const std::vector<uint8_t>& payload) {
if (payload.size() > 7) return false;
CAN.beginPacket(_nodeId);
CAN.write(funcCode);
for (uint8_t b : payload) {
CAN.write(b);
}
return CAN.endPacket();
}
std::vector<uint8_t> MontarFrameChk(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> resposta;
resposta.push_back(static_cast<uint8_t>(D_Code)); // Tipo do módulo
resposta.push_back(Conectado ? 1 : 0); // Conectado
resposta.push_back(Versao); // Versão
return resposta;
}
private:
uint32_t _baudRate;
uint8_t _nodeId;
OnReceiveCallback _callback = nullptr;
static CanService* instance;
static void onReceiveWrapper(int packetSize) {
if (instance) instance->onReceive(packetSize);
}
void onReceive(int packetSize) {
int senderId = CAN.packetId();
byte buffer[8];
int i = 0;
while (CAN.available() && i < 8) {
buffer[i++] = CAN.read();
}
byte funcCode = buffer[0];
if (_callback != nullptr) {
_callback(packetSize, senderId, funcCode, buffer + 1, i - 1);
}
}
};
CanService* CanService::instance = nullptr;
*/
#include <Arduino.h>
#include <driver/twai.h>
#include <esp_task_wdt.h>
// Garante o core de aplicação (ESP32-S3)
#ifndef APP_CPU_NUM
#define APP_CPU_NUM 1
#endif
class CanService {
public:
typedef void (*OnReceiveCallback)(int packetSize, int senderId,
CanMessagePosicaoDados posicao, byte* data, int dataLength);
typedef void (*OnReceiveCallback)(int packetSize, int senderId, CanMessagePosicaoDados posicao, byte* data, int dataLength);
// --- IDs especiais do teu protocolo ---
uint8_t ID_Num_sMOD = 0;
uint8_t ID_Num_sTOD = 250;
uint8_t ID_Num_sLRA = 251;
typedef struct {
OnReceiveCallback callback;
int packetSize;
int senderId;
CanMessagePosicaoDados posicao;
byte data[7];
int dataLength;
long idMsg;
} CallbackPayload;
CanService(uint8_t nodeId, uint32_t baudRate = 500000)
: _nodeId(nodeId), _baudRate(baudRate) {}
static TaskHandle_t handleCallbackTask;
void begin() {
PrintTela("[CAN] Iniciando TWAI...");
public:
uint8_t ID_Num_sMOD = 0;
uint8_t ID_Num_sTOD = 250;
uint8_t ID_Num_sLRA = 251;
// Modo NORMAL (com ACK)
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL);
// Aumenta filas internas do driver para evitar enrosco
g_config.tx_queue_len = 32;
g_config.rx_queue_len = 32;
// (alerts_enabled será reconfigurado depois de start)
CanService(uint8_t nodeId, uint32_t baudRate = 500000) {
_nodeId = nodeId;
_baudRate = baudRate;
// 500 kbit/s (ajuste se precisar)
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
// Começa aceitando tudo para depurar; depois afinamos a máscara
twai_filter_config_t f_config = {
.acceptance_code = (uint32_t(_nodeId) << 21),
.acceptance_mask = ~(0x7FFu << 21),
.single_filter = true
};
esp_err_t err = twai_driver_install(&g_config, &t_config, &f_config);
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao instalar driver: ", false); PrintTela(String(err));
return;
}
void begin() {
PrintTela("[CAN] Iniciando TWAI...");
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL); // modo sem ACK
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = {
.acceptance_code = (_nodeId << 21), // Shiftado para alinhar com o padrão TWAI
.acceptance_mask = ~(0x7FF << 21), // Mascara para filtrar apenas esse ID
.single_filter = true
};
esp_err_t err;
err = twai_driver_install(&g_config, &t_config, &f_config);
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao instalar driver: ", false);
PrintTela(String(err));
return;
}
err = twai_start();
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao iniciar TWAI: ", false);
PrintTela(String(err));
return;
}
err = twai_start();
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao iniciar TWAI: ", false); PrintTela(String(err));
return;
}
if (CanTaskRxHandle == NULL) {
canQueueRx = xQueueCreate(50, sizeof(twai_message_t));
if (canQueueRx == NULL) {
PrintTela("[CAN] Falha ao criar fila RX!");
// Habilita alerts úteis
uint32_t alert_mask = 0;
alert_mask |= TWAI_ALERT_BUS_OFF;
alert_mask |= TWAI_ALERT_RX_QUEUE_FULL;
alert_mask |= TWAI_ALERT_TX_FAILED;
#if defined(TWAI_ALERT_PERIPH_ERR)
alert_mask |= TWAI_ALERT_PERIPH_ERR;
#elif defined(TWAI_ALERT_PERIPH_RESET)
alert_mask |= TWAI_ALERT_PERIPH_RESET;
#endif
#if defined(TWAI_ALERT_RECOVERY_COMPLETE)
alert_mask |= TWAI_ALERT_RECOVERY_COMPLETE;
#elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS)
alert_mask |= TWAI_ALERT_RECOVERY_IN_PROGRESS;
#endif
twai_reconfigure_alerts(alert_mask, nullptr);
// --- Filas da nossa arquitetura ---
callbacksQ = xQueueCreate(64, sizeof(twai_message_t)); // jobs de RX -> callback
keysQ = xQueueCreate(64, sizeof(uint16_t)); // chaves coalescentes para TX
if (!callbacksQ || !keysQ) { PrintTela("[CAN] Falha ao criar filas"); }
// --- Tasks ---
xTaskCreatePinnedToCore(CanTaskRxWrapper, "CanTaskRx", 4096, this, 6, &CanTaskRxHandle, APP_CPU_NUM);
xTaskCreatePinnedToCore(CallbackWorkerWrapper,"CallbackWorker",6144,this,5,&CallbackWorkerHandle,APP_CPU_NUM);
xTaskCreatePinnedToCore(CanTaskTxWrapper, "CanTaskTx", 4096, this, 4, &CanTaskTxHandle, APP_CPU_NUM);
xTaskCreatePinnedToCore(HealthTaskWrapper, "CanHealth", 4096, this, 3, &HealthTaskHandle, APP_CPU_NUM);
// Watchdog por task (vamos resetar dentro dos loops)
esp_task_wdt_init(5, true);
esp_task_wdt_add(CanTaskRxHandle);
esp_task_wdt_add(CallbackWorkerHandle);
esp_task_wdt_add(CanTaskTxHandle);
esp_task_wdt_add(HealthTaskHandle);
PrintTela("[CAN] TWAI iniciado com sucesso");
}
// --- Filas e tasks ---
QueueHandle_t callbacksQ = nullptr; // fila para o callback worker
QueueHandle_t keysQ = nullptr; // fila de chaves para TX coalescente
// (Opcional) Se quiser manter uma RX intermediária: QueueHandle_t canQueueRx = nullptr;
TaskHandle_t CanTaskRxHandle = NULL;
TaskHandle_t CallbackWorkerHandle = NULL;
TaskHandle_t CanTaskTxHandle = NULL;
TaskHandle_t HealthTaskHandle = NULL;
// --- Métricas/health ---
volatile uint64_t last_rx_ts = 0; // us
volatile uint64_t last_tx_ts = 0; // us
volatile uint32_t rx_drops = 0;
volatile uint32_t tx_retries = 0;
// --- TX coalescing (tabela simples) ---
static const int MAX_KEYS = 64; // ajuste conforme nº (pos,addr)
struct Slot {
uint16_t key = 0;
bool used = false;
bool pending = false; // 🔧 NOVO: chave já enfileirada para envio
twai_message_t msg;
uint32_t gen = 0;
};
Slot latestByKey[MAX_KEYS];
// Helpers de chave (pos,addr -> 16 bits)
static inline uint16_t make_key(const twai_message_t& m) {
return (m.data_length_code >= 2) ? ( (uint16_t(m.data[0])<<8) | uint16_t(m.data[1]) ) : 0xFFFF;
}
bool put_latest(uint16_t key, const twai_message_t& m) {
int idx = key % MAX_KEYS;
for (int i=0; i<MAX_KEYS; ++i, idx=(idx+1)%MAX_KEYS) {
if (!latestByKey[idx].used || latestByKey[idx].key == key) {
latestByKey[idx].used = true;
latestByKey[idx].key = key;
latestByKey[idx].msg = m;
latestByKey[idx].gen++;
return true;
}
}
return false;
}
bool get_latest(uint16_t key, twai_message_t* out) {
int idx = key % MAX_KEYS;
for (int i=0; i<MAX_KEYS; ++i, idx=(idx+1)%MAX_KEYS) {
if (latestByKey[idx].used && latestByKey[idx].key == key) { *out = latestByKey[idx].msg; return true; }
if (!latestByKey[idx].used) break;
}
return false;
}
static void CanTaskRxWrapper(void *pvParameters) {
static_cast<CanService*>(pvParameters)->CanTaskRx(pvParameters);
}
void CanTaskRx(void* pvParameters) {
twai_message_t message;
for (;;) {
// WDT
esp_task_wdt_reset();
if (twai_receive(&message, pdMS_TO_TICKS(50)) == ESP_OK) {
// ignore frames que não vamos tratar
if (message.data_length_code == 0 || message.extd || message.rtr) {
continue;
}
xTaskCreatePinnedToCore(CanService::CanTaskRxWrapper, "CanTaskRx", 4096, this, 17, &CanTaskRxHandle, APP_CPU_NUM);
}
if (CanTaskTxHandle == NULL) {
canQueueTx = xQueueCreate(50, sizeof(twai_message_t));
if (canQueueTx == NULL) {
PrintTela("[CAN] Falha ao criar fila TX!");
// marca vida do barramento SEMPRE que recebeu algo válido
last_rx_ts = esp_timer_get_time();
if (DebugMode) {
PrintTela("[CAN RX] ID: 0x" + String(message.identifier, HEX) + " Len: " + String(message.data_length_code));
}
// tenta enfileirar pro worker
if (callbacksQ && xQueueSend(callbacksQ, &message, 0) != pdTRUE) {
rx_drops++;
// (opcional) política "drop oldest": remove 1 e tenta de novo
// twai_message_t dump;
// if (xQueueReceive(callbacksQ, &dump, 0) == pdTRUE) {
// xQueueSend(callbacksQ, &message, 0);
// }
vTaskDelay(pdMS_TO_TICKS(1)); // dá uma respirada
}
xTaskCreatePinnedToCore(CanService::CanTaskTxWrapper, "CanTaskTx", 4096, this, 16, &CanTaskTxHandle, APP_CPU_NUM);
}
if (CanTaskProcessHandle == NULL) {
xTaskCreatePinnedToCore(CanService::CanTaskProcessWrapper, "CanTaskProcess", 6144, this, 18, &CanTaskProcessHandle, APP_CPU_NUM);
vTaskDelay(1);
}
}
static void CallbackWorkerWrapper(void *pvParameters) {
static_cast<CanService*>(pvParameters)->CallbackWorker(pvParameters);
}
void CallbackWorker(void* pvParameters) {
twai_message_t msg;
for (;;) {
// WDT
esp_task_wdt_reset();
if (xQueueReceive(callbacksQ, &msg, portMAX_DELAY) == pdTRUE) {
if (_callback && msg.data_length_code > 0) {
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)msg.data[0];
_callback(msg.data_length_code, msg.identifier, posicao,
&msg.data[1], msg.data_length_code - 1);
}
}
PrintTela("[CAN] TWAI iniciado com sucesso");
// yield leve
taskYIELD();
}
}
QueueHandle_t canQueueRx;
TaskHandle_t CanTaskRxHandle = NULL;
static void CanTaskRxWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskRx(pvParameters);
}
void PublicarTx(const twai_message_t& msg) {
// Sanidade básica
if (msg.data_length_code < 2 || msg.data_length_code > 8) return;
if (msg.extd || msg.rtr) return; // trabalhamos com standard data frames
void CanTaskRx(void* pvParameters) {
twai_message_t message;
while (true) {
if (twai_receive(&message, pdMS_TO_TICKS(10)) == ESP_OK) {
uint16_t key = make_key(msg);
if (key == 0xFFFF) return;
if (message.data_length_code == 0 || message.extd) continue;
bool MensagemTx = message.identifier == static_cast<uint32_t>(_nodeId + 1);
if (DebugMode) {
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) {
PrintTela("[CAN RX] Ouviu a própria resposta!");
continue;
}
if (!mensagemJaNaFila(canQueueRx, message)) {
if (uxQueueSpacesAvailable(canQueueRx) < 5) {
PrintTela("[CAN RX] Alerta: Fila RX quase cheia (" + String(uxQueueSpacesAvailable(canQueueRx)) + " mensagens restantes)");
}
if (xQueueSend(canQueueRx, &message, 0) != pdTRUE) {
PrintTela("[CAN RX] Fila RX cheia! Mensagem descartada.");
}
else {
//PrintTela("[CAN RX] Mensagem adicionada na fila RX");
}
// Atualiza a versão mais nova na tabela
int idx = key % MAX_KEYS;
bool stored = false;
for (int i=0; i<MAX_KEYS; ++i, idx=(idx+1)%MAX_KEYS) {
if (!latestByKey[idx].used || latestByKey[idx].key == key) {
latestByKey[idx].used = true;
latestByKey[idx].key = key;
latestByKey[idx].msg = msg;
latestByKey[idx].gen++;
stored = true;
// Se ainda não está pendente, enfileira a chave
if (!latestByKey[idx].pending && keysQ) {
if (xQueueSend(keysQ, &key, 0) == pdTRUE) {
latestByKey[idx].pending = true;
} else {
PrintTela("[CAN RX] Mensagem duplicada ignorada.");
// fila cheia: sem pânico; mantemos a versão mais nova no slot
}
}
vTaskDelay(1);
break;
}
}
QueueHandle_t canQueueTx;
TaskHandle_t CanTaskTxHandle = NULL;
static void CanTaskTxWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskTx(pvParameters);
if (!stored) {
// tabela cheia (raríssimo se MAX_KEYS está adequado) — poderia logar/contar.
}
}
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 TX] Mensagem recebida da fila TX");
if (service->enviarDadosCan(msg)) {
//PrintTela("[CAN TX] Mensagem enviada via CAN com sucesso");
}
else {
PrintTela("[CAN TX] Erro ao enviar mensagem via CAN");
// --- Wrapper padrão ---
static void CanTaskTxWrapper(void *pvParameters) {
static_cast<CanService*>(pvParameters)->CanTaskTx(pvParameters);
}
// --- Task de TX com retry/backoff e limpeza do "pending" ---
void CanTaskTx(void* pvParameters) {
uint16_t key;
for (;;) {
// WDT
esp_task_wdt_reset();
if (xQueueReceive(keysQ, &key, portMAX_DELAY) == pdTRUE) {
// busca a msg mais nova dessa chave
twai_message_t m{};
int idx = key % MAX_KEYS;
int foundIdx = -1;
for (int i=0; i<MAX_KEYS; ++i, idx=(idx+1)%MAX_KEYS) {
if (latestByKey[idx].used && latestByKey[idx].key == key) {
m = latestByKey[idx].msg;
foundIdx = idx;
break;
}
if (!latestByKey[idx].used) break;
}
vTaskDelay(1);
}
}
TaskHandle_t CanTaskProcessHandle = NULL;
static void CanTaskProcessWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskProcess(pvParameters);
}
if (foundIdx < 0) {
// chave desapareceu; nada a fazer
continue;
}
void CanTaskProcess(void* pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
twai_message_t msg;
// Garantias do frame (caso quem chamou não tenha setado)
m.extd = 0; // standard frame
m.rtr = 0; // data frame
while (true) {
while (uxQueueMessagesWaiting(service->canQueueRx) > 0) {
if (xQueueReceive(service->canQueueRx, &msg, portMAX_DELAY) == pdTRUE) {
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)msg.data[0];
bool ok = false;
for (int tent=0; tent<3; ++tent) {
if (enviarDadosCan(m, pdMS_TO_TICKS(50))) {
last_tx_ts = esp_timer_get_time();
ok = true;
break;
}
tx_retries++;
vTaskDelay(pdMS_TO_TICKS(5));
}
if (service->_callback) {
String frameEmProcessamento = "";
for (int i = 0; i < msg.data_length_code; i++) {
frameEmProcessamento += String(msg.data[i], HEX) + " ";
}
// Limpa o "pending" SEMPRE; se falhou, a app pode chamar PublicarTx de novo
latestByKey[foundIdx].pending = false;
idmsg++;
CallbackPayload* payload = new CallbackPayload();
payload->idMsg = idmsg;
payload->callback = service->_callback;
payload->packetSize = msg.data_length_code;
payload->senderId = msg.identifier;
payload->posicao = posicao;
memcpy(payload->data, &msg.data[1], msg.data_length_code - 1);
payload->dataLength = msg.data_length_code - 1;
// Captura o handle da task atual (quem vai esperar a notificação)
TaskHandle_t callingTaskHandle = xTaskGetCurrentTaskHandle();
// Cria um pacote com o payload + handle do notificador
auto* bundle = new std::pair<CallbackPayload*, TaskHandle_t>(payload, callingTaskHandle);
PrintTela("[CAN Process] Processando frame: " + frameEmProcessamento);
String taskName = "TaskCallbackTimeout_" + String(idmsg);
TaskHandle_t handleTask = nullptr;
// Cria a task do callback
BaseType_t status = xTaskCreatePinnedToCore(
TaskCallbackWrapper,
taskName.c_str(),
8192,
bundle,
5,
&handleTask,
APP_CPU_NUM
);
if (status != pdPASS) {
PrintTela("[CAN Process] ❌ Falha ao criar task de callback!");
delete payload;
delete bundle;
continue;
}
if (false && msg.data[1] == ID_Num_sLRA) {
// Comandos para o LoRa nao precisam aguardar, podem ser processados sem tempo
}
else {
// Aguarda até 5000ms pela notificação de término
BaseType_t sinal = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000));
if (sinal == 0) {
PrintTela("[CAN Process] ⚠️ Callback demorou demais: " + String(payload->idMsg) + " — timeout ao executar comando: " + frameEmProcessamento);
vTaskDelay(pdMS_TO_TICKS(10)); // dá tempo da task morrer sozinha
eTaskState estado = eTaskGetState(handleTask);
if (estado != eDeleted && estado != eInvalid) {
vTaskDelete(handleTask);
}
}
}
if (!ok) {
// Re-enfileira a chave só se ainda houver interesse (a app pode ter publicado algo novo)
// Re-check: se a slot ainda está usada e não está pendente, refile
if (latestByKey[foundIdx].used && !latestByKey[foundIdx].pending) {
if (xQueueSend(keysQ, &key, 0) == pdTRUE) {
latestByKey[foundIdx].pending = true;
}
}
}
vTaskDelay(1);
}
taskYIELD();
}
}
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]) {
return true; // já tem uma igual
}
}
}
// --- Versão de enviarDadosCan com timeout custom ---
bool enviarDadosCan(const twai_message_t& msg, TickType_t timeoutTicks) {
// Aqui você pode normalizar o ID/dados se precisar
// Ex.: msg.identifier já deve estar correto para o PC
esp_err_t err = twai_transmit(&msg, timeoutTicks);
if (err != ESP_OK) {
PrintTela("[CAN TX] Falha transmit (" + String((int)err) + ")");
return false;
}
return true;
}
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool adicionarMensagemFila(const std::vector<uint8_t>& payload) {
if (payload.size() > 8) return false;
twai_message_t msg;
msg.identifier = _nodeId; // + 1;
msg.extd = 0;
msg.rtr = 0;
msg.ss = 0;
msg.data_length_code = payload.size();
for (uint8_t i = 0; i < payload.size(); ++i) {
msg.data[i] = payload[i]; // 👈 Preenche os dados corretamente
}
if (!mensagemJaNaFila(canQueueTx, msg)) {
if (xQueueSend(canQueueTx, &msg, 0) != pdTRUE) {
PrintTela("[CAN] Fila TX cheia! Mensagem descartada.");
return false;
static void HealthTaskWrapper(void *pvParameters) {
static_cast<CanService*>(pvParameters)->HealthTask(pvParameters);
}
bool reinit_twai_unsafe_() {
// ⚠️ Use os mesmos pinos/bitrate do begin(); ajuste se necessário
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL);
g_config.tx_queue_len = 32;
g_config.rx_queue_len = 32;
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
if (twai_driver_install(&g_config, &t_config, &f_config) != ESP_OK) return false;
if (twai_start() != ESP_OK) { twai_driver_uninstall(); return false; }
uint32_t alert_mask = 0;
alert_mask |= TWAI_ALERT_BUS_OFF;
alert_mask |= TWAI_ALERT_RX_QUEUE_FULL;
alert_mask |= TWAI_ALERT_TX_FAILED;
#if defined(TWAI_ALERT_PERIPH_ERR)
alert_mask |= TWAI_ALERT_PERIPH_ERR;
#elif defined(TWAI_ALERT_PERIPH_RESET)
alert_mask |= TWAI_ALERT_PERIPH_RESET;
#endif
#if defined(TWAI_ALERT_RECOVERY_COMPLETE)
alert_mask |= TWAI_ALERT_RECOVERY_COMPLETE;
#elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS)
alert_mask |= TWAI_ALERT_RECOVERY_IN_PROGRESS;
#endif
twai_reconfigure_alerts(alert_mask, nullptr);
return true;
}
void HealthTask(void* pvParameters) {
const uint32_t RX_TIMEOUT_MS = 3000; // ajuste conforme seu tráfego esperado
const uint32_t TX_STALL_MS = 2000; // quanto tempo uma chave pode ficar sem sair
const uint8_t MAX_REC_FAILS = 3; // depois disso, reboot
static uint8_t consecutive_rec_fails = 0;
for (;;) {
esp_task_wdt_reset();
uint64_t now = esp_timer_get_time();
// ---- Alerts do driver ----
uint32_t alerts = 0;
if (twai_read_alerts(&alerts, 0) == ESP_OK && alerts) {
if (alerts & TWAI_ALERT_BUS_OFF) {
PrintTela("[CAN ALERT] BUS OFF → iniciando recovery...");
if (twai_initiate_recovery() != ESP_OK) {
PrintTela("[CAN ALERT] Falha ao iniciar recovery; reiniciando driver...");
twai_stop(); twai_driver_uninstall();
if (!reinit_twai_unsafe_()) consecutive_rec_fails++;
else consecutive_rec_fails = 0;
}
}
else {
//PrintTela("[CAN] Mensagem adicionada na fila TX");
return true;
#if defined(TWAI_ALERT_RECOVERY_COMPLETE)
if (alerts & TWAI_ALERT_RECOVERY_COMPLETE) {
PrintTela("[CAN ALERT] Recovery completo.");
consecutive_rec_fails = 0;
}
#elif defined(TWAI_ALERT_RECOVERY_IN_PROGRESS)
// Algumas versões só têm "em progresso"; trate como sinal de vida do recovery
if (alerts & TWAI_ALERT_RECOVERY_IN_PROGRESS) {
PrintTela("[CAN ALERT] Recovery em progresso (OK).");
// opcional: zere contador se quiser considerar como sinal saudável
consecutive_rec_fails = 0;
}
#endif
if (alerts & TWAI_ALERT_RX_QUEUE_FULL) {
PrintTela("[CAN ALERT] RX interno do driver CHEIO.");
}
if (alerts & TWAI_ALERT_TX_FAILED) {
PrintTela("[CAN ALERT] TX FAILED.");
}
#if defined(TWAI_ALERT_PERIPH_ERR)
if (alerts & TWAI_ALERT_PERIPH_ERR) {
PrintTela("[CAN ALERT] ERRO no periférico CAN. Reiniciando driver...");
twai_stop(); twai_driver_uninstall();
if (!reinit_twai_unsafe_()) consecutive_rec_fails++;
else consecutive_rec_fails = 0;
}
#elif defined(TWAI_ALERT_PERIPH_RESET)
if (alerts & TWAI_ALERT_PERIPH_RESET) {
PrintTela("[CAN ALERT] RESET do periférico CAN detectado. Reconfigurando...");
twai_stop(); twai_driver_uninstall();
if (!reinit_twai_unsafe_()) consecutive_rec_fails++;
else consecutive_rec_fails = 0;
}
#endif
}
// ---- Liveness de RX ----
if (last_rx_ts && ((now - last_rx_ts) / 1000ULL) > RX_TIMEOUT_MS) {
PrintTela("[CAN HLTH] Sem RX há " + String((now - last_rx_ts)/1000ULL) + " ms → reiniciando driver...");
twai_stop(); twai_driver_uninstall();
if (!reinit_twai_unsafe_()) consecutive_rec_fails++;
else consecutive_rec_fails = 0;
last_rx_ts = esp_timer_get_time(); // evita loop de reinicialização
}
// ---- Stall de TX (chaves pendentes) ----
// Se houver muitas chaves esperando por muito tempo, force drenagem
UBaseType_t pendentes = keysQ ? uxQueueMessagesWaiting(keysQ) : 0;
if (pendentes > 0 && last_tx_ts && ((now - last_tx_ts) / 1000ULL) > TX_STALL_MS) {
PrintTela("[CAN HLTH] TX aparentemente parado (" + String(pendentes) + " chaves pendentes).");
// Estratégia: deixa o CanTaskTx re-enfileirar; se persistir, reinicia driver
static uint8_t tx_stall_count = 0;
tx_stall_count++;
if (tx_stall_count >= 3) {
PrintTela("[CAN HLTH] TX stall persistente → reiniciando driver...");
twai_stop(); twai_driver_uninstall();
if (!reinit_twai_unsafe_()) consecutive_rec_fails++;
else { consecutive_rec_fails = 0; tx_stall_count = 0; }
last_tx_ts = esp_timer_get_time();
}
}
else {
PrintTela("[CAN] Mensagem duplicada ignorada.");
}
}
bool enviarDadosCan(twai_message_t msg) {
return twai_transmit(&msg, pdMS_TO_TICKS(10)) == ESP_OK;
}
F_Code FuncaoPorPosicao(CanMessagePosicaoDados posicao) {
F_Code funcao = F_Code::Nda;
int pos = static_cast<int>(posicao);
if (pos >= 0 && pos <= 50) {
funcao = F_Code::ReqTx;
}
else if (pos > 50 && pos <= 100) {
funcao = F_Code::CfgTx;
}
else if (pos > 100 && pos <= 150) {
funcao = F_Code::CmdTx;
// ---- Escalonamento final: reboot do chip se não recuperar ----
if (consecutive_rec_fails >= MAX_REC_FAILS) {
PrintTela("[CAN HLTH] Falhas consecutivas ao reiniciar TWAI. Reiniciando ESP32...");
vTaskDelay(pdMS_TO_TICKS(100));
esp_restart();
}
//PrintTela("Funcao parseada para posicao: " + String(pos) + ", funcao: " + String(funcao));
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
return funcao;
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool adicionarMensagemFila(const std::vector<uint8_t>& payload) {
// 1) Sanidade
if (payload.empty() || payload.size() > 8) {
PrintTela("[CAN] Payload inválido (0 ou >8 bytes).");
return false;
}
if (payload.size() < 2) { // precisa de pos (byte0) e addr (byte1) para formar a chave
PrintTela("[CAN] Payload precisa ter pelo menos pos e addr (2 bytes).");
return false;
}
std::vector<uint8_t> MontarFrameReqStatusMod(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::Status));
data.push_back(ID_Num_sMOD);
data.push_back(static_cast<uint8_t>(D_Code));
data.push_back(Conectado ? 1 : 0);
data.push_back(Versao);
return data;
// 2) Monta frame
twai_message_t msg{};
// ⚠️ ID: use o ID que o PC espera para respostas desse nó (ajuste se necessário)
msg.identifier = _nodeId; // ou outro mapeamento do seu protocolo
msg.extd = 0; // standard frame
msg.rtr = 0; // data frame
msg.ss = 0;
msg.data_length_code = payload.size();
for (uint8_t i = 0; i < payload.size(); ++i) {
msg.data[i] = payload[i];
}
std::vector<uint8_t> MontarFrameReqDadosFim(int latencia) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::DadosAll));
data.push_back(ID_Num_sTOD);
data.push_back(latencia >> 8); data.push_back(latencia & 0xFF);
return data;
// 3) Publica via coalescência (substitui a antiga fila TX)
PublicarTx(msg);
return true;
}
F_Code FuncaoPorPosicao(CanMessagePosicaoDados posicao) {
F_Code funcao = F_Code::Nda;
int pos = static_cast<int>(posicao);
if (pos >= 0 && pos <= 50) {
funcao = F_Code::ReqTx;
}
static void TaskCallbackWrapper(void* param) {
auto bundle = static_cast<std::pair<CallbackPayload*, TaskHandle_t>*>(param);
CallbackPayload* payload = bundle->first;
TaskHandle_t notifyTo = bundle->second;
payload->callback(
payload->packetSize,
payload->senderId,
payload->posicao,
payload->data,
payload->dataLength
);
PrintTela("[CAN Callback] Processamento finalizado: " + String(payload->idMsg));
// Notifica a task principal que terminou
xTaskNotifyGive(notifyTo);
// Limpa memória
delete payload;
delete bundle;
// Encerra a task
vTaskDelete(NULL);
else if (pos > 50 && pos <= 100) {
funcao = F_Code::CfgTx;
}
else if (pos > 100 && pos <= 150) {
funcao = F_Code::CmdTx;
}
//PrintTela("Funcao parseada para posicao: " + String(pos) + ", funcao: " + String(funcao));
return funcao;
}
private:
uint8_t _nodeId;
uint32_t _baudRate;
OnReceiveCallback _callback = nullptr;
bool DebugMode = false;
volatile bool processamentoEmAndamento = false;
volatile long idmsg = 0;
std::vector<uint8_t> MontarFrameReqStatusMod(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::Status));
data.push_back(ID_Num_sMOD);
data.push_back(static_cast<uint8_t>(D_Code));
data.push_back(Conectado ? 1 : 0);
data.push_back(static_cast<uint8_t>(Versao));
return data;
}
std::vector<uint8_t> MontarFrameReqDadosFim(int latencia) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::DadosAll));
data.push_back(ID_Num_sTOD);
data.push_back(latencia >> 8); data.push_back(latencia & 0xFF);
return data;
}
private:
uint32_t _baudRate = 500000;
uint8_t _nodeId = 0;
OnReceiveCallback _callback = nullptr;
bool DebugMode = true;
};
TaskHandle_t CanService::handleCallbackTask = NULL;
#endif
#endif // CANSERVICE_H

View File

@ -0,0 +1,474 @@
#include "SerialService.h"
// CanService.h
#ifndef CANSERVICE_H
#define CANSERVICE_H
/*
#include <CAN.h>
#include <Arduino.h>
class CanService {
public:
typedef void (*OnReceiveCallback)(int packetSize, int senderId, byte funcCode, byte* data, int dataLength);
CanService(uint8_t nodeId, uint32_t baudRate = 500E3) {
_baudRate = baudRate;
_nodeId = nodeId;
}
void begin() {
if (!CAN.begin(_baudRate)) {
PrintTela("[CAN] Falha ao iniciar");
return;
}
PrintTela("[CAN] Inicializado com sucesso");
CAN.onReceive(onReceiveWrapper);
instance = this;
}
void loop() {
// Apenas processa mensagens recebidas via interrupcao
}
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool enviarMensagem(byte funcCode, const std::vector<uint8_t>& payload) {
if (payload.size() > 7) return false;
CAN.beginPacket(_nodeId);
CAN.write(funcCode);
for (uint8_t b : payload) {
CAN.write(b);
}
return CAN.endPacket();
}
std::vector<uint8_t> MontarFrameChk(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> resposta;
resposta.push_back(static_cast<uint8_t>(D_Code)); // Tipo do módulo
resposta.push_back(Conectado ? 1 : 0); // Conectado
resposta.push_back(Versao); // Versão
return resposta;
}
private:
uint32_t _baudRate;
uint8_t _nodeId;
OnReceiveCallback _callback = nullptr;
static CanService* instance;
static void onReceiveWrapper(int packetSize) {
if (instance) instance->onReceive(packetSize);
}
void onReceive(int packetSize) {
int senderId = CAN.packetId();
byte buffer[8];
int i = 0;
while (CAN.available() && i < 8) {
buffer[i++] = CAN.read();
}
byte funcCode = buffer[0];
if (_callback != nullptr) {
_callback(packetSize, senderId, funcCode, buffer + 1, i - 1);
}
}
};
CanService* CanService::instance = nullptr;
*/
#include <Arduino.h>
#include <driver/twai.h>
class CanService {
typedef void (*OnReceiveCallback)(int packetSize, int senderId, CanMessagePosicaoDados posicao, byte* data, int dataLength);
typedef struct {
OnReceiveCallback callback;
int packetSize;
int senderId;
CanMessagePosicaoDados posicao;
byte data[7];
int dataLength;
long idMsg;
} CallbackPayload;
static TaskHandle_t handleCallbackTask;
public:
uint8_t ID_Num_sMOD = 0;
uint8_t ID_Num_sTOD = 250;
uint8_t ID_Num_sLRA = 251;
CanService(uint8_t nodeId, uint32_t baudRate = 500000) {
_nodeId = nodeId;
_baudRate = baudRate;
}
void begin() {
PrintTela("[CAN] Iniciando TWAI...");
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_5, GPIO_NUM_4, TWAI_MODE_NORMAL); // modo sem ACK
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = {
.acceptance_code = (_nodeId << 21), // Shiftado para alinhar com o padrão TWAI
.acceptance_mask = ~(0x7FF << 21), // Mascara para filtrar apenas esse ID
.single_filter = true
};
esp_err_t err;
err = twai_driver_install(&g_config, &t_config, &f_config);
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao instalar driver: ", false);
PrintTela(String(err));
return;
}
err = twai_start();
if (err != ESP_OK) {
PrintTela("[CAN] Erro ao iniciar TWAI: ", false);
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, 17, &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, 16, &CanTaskTxHandle, APP_CPU_NUM);
}
if (CanTaskProcessHandle == NULL) {
xTaskCreatePinnedToCore(CanService::CanTaskProcessWrapper, "CanTaskProcess", 6144, this, 18, &CanTaskProcessHandle, APP_CPU_NUM);
}
PrintTela("[CAN] TWAI iniciado com sucesso");
}
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;
while (true) {
if (twai_receive(&message, pdMS_TO_TICKS(10)) == ESP_OK) {
if (message.data_length_code == 0 || message.extd) continue;
bool MensagemTx = message.identifier == static_cast<uint32_t>(_nodeId + 1);
if (DebugMode) {
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) {
PrintTela("[CAN RX] Ouviu a própria resposta!");
continue;
}
if (!mensagemJaNaFila(canQueueRx, message)) {
if (uxQueueSpacesAvailable(canQueueRx) < 5) {
PrintTela("[CAN RX] Alerta: Fila RX quase cheia (" + String(uxQueueSpacesAvailable(canQueueRx)) + " mensagens restantes)");
}
if (xQueueSend(canQueueRx, &message, 0) != pdTRUE) {
PrintTela("[CAN RX] Fila RX cheia! Mensagem descartada.");
}
else {
//PrintTela("[CAN RX] Mensagem adicionada na fila RX");
}
} else {
PrintTela("[CAN RX] Mensagem duplicada ignorada.");
}
}
vTaskDelay(1);
}
}
QueueHandle_t canQueueTx;
TaskHandle_t CanTaskTxHandle = NULL;
static void CanTaskTxWrapper(void *pvParameters) {
CanService *service = static_cast<CanService*>(pvParameters);
service->CanTaskTx(pvParameters);
}
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 TX] Mensagem recebida da fila TX");
if (service->enviarDadosCan(msg)) {
//PrintTela("[CAN TX] Mensagem enviada via CAN com sucesso");
}
else {
PrintTela("[CAN TX] 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) {
CanService *service = static_cast<CanService*>(pvParameters);
twai_message_t msg;
while (true) {
while (uxQueueMessagesWaiting(service->canQueueRx) > 0) {
if (xQueueReceive(service->canQueueRx, &msg, portMAX_DELAY) == pdTRUE) {
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)msg.data[0];
if (service->_callback) {
String frameEmProcessamento = "";
for (int i = 0; i < msg.data_length_code; i++) {
frameEmProcessamento += String(msg.data[i], HEX) + " ";
}
idmsg++;
CallbackPayload* payload = new CallbackPayload();
payload->idMsg = idmsg;
payload->callback = service->_callback;
payload->packetSize = msg.data_length_code;
payload->senderId = msg.identifier;
payload->posicao = posicao;
memcpy(payload->data, &msg.data[1], msg.data_length_code - 1);
payload->dataLength = msg.data_length_code - 1;
// Captura o handle da task atual (quem vai esperar a notificação)
TaskHandle_t callingTaskHandle = xTaskGetCurrentTaskHandle();
// Cria um pacote com o payload + handle do notificador
auto* bundle = new std::pair<CallbackPayload*, TaskHandle_t>(payload, callingTaskHandle);
PrintTela("[CAN Process] Processando frame: " + frameEmProcessamento);
String taskName = "TaskCallbackTimeout_" + String(idmsg);
TaskHandle_t handleTask = nullptr;
// Cria a task do callback
BaseType_t status = xTaskCreatePinnedToCore(
TaskCallbackWrapper,
taskName.c_str(),
8192,
bundle,
5,
&handleTask,
APP_CPU_NUM
);
if (status != pdPASS) {
PrintTela("[CAN Process] ❌ Falha ao criar task de callback!");
delete payload;
delete bundle;
continue;
}
if (false && msg.data[1] == ID_Num_sLRA) {
// Comandos para o LoRa nao precisam aguardar, podem ser processados sem tempo
}
else {
// Aguarda até 5000ms pela notificação de término
BaseType_t sinal = ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(5000));
if (sinal == 0) {
PrintTela("[CAN Process] ⚠️ Callback demorou demais: " + String(payload->idMsg) + " — timeout ao executar comando: " + frameEmProcessamento);
vTaskDelay(pdMS_TO_TICKS(10)); // dá tempo da task morrer sozinha
eTaskState estado = eTaskGetState(handleTask);
if (estado != eDeleted && estado != eInvalid) {
vTaskDelete(handleTask);
}
}
}
}
}
}
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]) {
return true; // já tem uma igual
}
}
}
return false;
}
void setReceiveCallback(OnReceiveCallback cb) {
_callback = cb;
}
bool adicionarMensagemFila(const std::vector<uint8_t>& payload) {
if (payload.size() > 8) return false;
twai_message_t msg;
msg.identifier = _nodeId; // + 1;
msg.extd = 0;
msg.rtr = 0;
msg.ss = 0;
msg.data_length_code = payload.size();
for (uint8_t i = 0; i < payload.size(); ++i) {
msg.data[i] = payload[i]; // 👈 Preenche os dados corretamente
}
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;
}
F_Code FuncaoPorPosicao(CanMessagePosicaoDados posicao) {
F_Code funcao = F_Code::Nda;
int pos = static_cast<int>(posicao);
if (pos >= 0 && pos <= 50) {
funcao = F_Code::ReqTx;
}
else if (pos > 50 && pos <= 100) {
funcao = F_Code::CfgTx;
}
else if (pos > 100 && pos <= 150) {
funcao = F_Code::CmdTx;
}
//PrintTela("Funcao parseada para posicao: " + String(pos) + ", funcao: " + String(funcao));
return funcao;
}
std::vector<uint8_t> MontarFrameReqStatusMod(T_Code D_Code, bool Conectado, int Versao) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::Status));
data.push_back(ID_Num_sMOD);
data.push_back(static_cast<uint8_t>(D_Code));
data.push_back(Conectado ? 1 : 0);
data.push_back(Versao);
return data;
}
std::vector<uint8_t> MontarFrameReqDadosFim(int latencia) {
std::vector<uint8_t> data;
data.push_back(static_cast<uint8_t>(CanMessagePosicaoDados::DadosAll));
data.push_back(ID_Num_sTOD);
data.push_back(latencia >> 8); data.push_back(latencia & 0xFF);
return data;
}
static void TaskCallbackWrapper(void* param) {
auto bundle = static_cast<std::pair<CallbackPayload*, TaskHandle_t>*>(param);
CallbackPayload* payload = bundle->first;
TaskHandle_t notifyTo = bundle->second;
payload->callback(
payload->packetSize,
payload->senderId,
payload->posicao,
payload->data,
payload->dataLength
);
PrintTela("[CAN Callback] Processamento finalizado: " + String(payload->idMsg));
// Notifica a task principal que terminou
xTaskNotifyGive(notifyTo);
// Limpa memória
delete payload;
delete bundle;
// Encerra a task
vTaskDelete(NULL);
}
private:
uint8_t _nodeId;
uint32_t _baudRate;
OnReceiveCallback _callback = nullptr;
bool DebugMode = false;
volatile bool processamentoEmAndamento = false;
volatile long idmsg = 0;
};
TaskHandle_t CanService::handleCallbackTask = NULL;
#endif

View File

@ -22,14 +22,13 @@ class SensorMassa : public ComponenteCAN {
PinoModel _pinoDT;
PinoModel _pinoSCK;
float _Offset = 1895800.0;
float _MassaRef = 0.8;
float _MassaLeituraRef = 40876.37;
long _Offset = 1996283;
float _Escala = 50857.316406;
float _OffsetMassa = 0.0;
HX711 balanca = HX711();
SimpleKalmanFilter filtro = SimpleKalmanFilter(2, 2, 0.01);
SimpleKalmanFilter filtro = SimpleKalmanFilter(0.05, 0.05, 0.25);
double leituraMassa = 0;
float Massa = 0;
bool Iniciado = false;
@ -42,7 +41,8 @@ class SensorMassa : public ComponenteCAN {
if (_pinoDT.Definido() && _pinoSCK.Definido()) {
balanca.begin(_pinoDT.num, _pinoSCK.num);
balanca.set_scale();
balanca.set_offset(_Offset);
balanca.set_scale(_Escala);
//balanca->tare();
Iniciado = true;
@ -63,6 +63,7 @@ class SensorMassa : public ComponenteCAN {
// Redefinir as configurações para os valores iniciais
_pinoDT.Desconectar();
_pinoSCK.Desconectar();
Massa = 0;
PrintTela(_ID + " Desligado");
@ -119,7 +120,7 @@ class SensorMassa : public ComponenteCAN {
sensor->ID_Num = idNum;
sensor->_pinoDT = PinoModel(pinoDt, CONTROLADOR, _INPUT, DIGITAL);
sensor->_pinoSCK = PinoModel(pinoSck, CONTROLADOR, _OUTPUT);
sensor->_Offset = offset;
sensor->_OffsetMassa = offset;
sensor->Inicializar();
if (!jaExiste) {
lista.push_back(sensor);
@ -146,22 +147,12 @@ class SensorMassa : public ComponenteCAN {
private:
void AferirMassa() {
if (Iniciado) {
float leitura = balanca.get_units(5);
leituraMassa = filtro.updateEstimate(leitura);
Massa = CalcularMassa(leituraMassa);
float leitura = -balanca.get_units(5);
leitura += _OffsetMassa;
Massa = filtro.updateEstimate(leitura);
}
}
float CalcularMassa(float _leitura) {
double leituraMassa = _leitura * -1;
leituraMassa += _Offset;
float FatorLeitura = _MassaRef / _MassaLeituraRef;
float massa = FatorLeitura * leituraMassa;
return massa;
}
};

View File

@ -11,9 +11,9 @@ RESOLUCAO = config["resolucao"]
pasta_origem = os.path.join(MODELO, "dataset", f"{RESOLUCAO[0]}x{RESOLUCAO[1]}")
pasta_destino = os.path.join(MODELO, "dataset", "split")
percent_train = 0.9
percent_val = 0.09
percent_test = 0.01
percent_train = 0.8
percent_val = 0.18
percent_test = 0.02
seed = 42
random.seed(seed)

View File

@ -1,11 +1,11 @@
{
"camera": "oak-d",
"camera": "oak-1",
"modelo": "fast_scnn",
"model_name": "ruas_new",
"main_class_name": "cana",
"model_name": "ervas_medium_new",
"main_class_name": "erva",
"use_main_class": false,
"resolucao": [512, 288],
"roi_inicio": 0.0,
"roi_tamanho": 1.0,
"shaves": 3
"shaves": 6
}