ajustes gps e movimentacao

This commit is contained in:
Diego Freitas 2025-09-02 10:49:11 -03:00
parent 36c7787d8d
commit b8ea15d2f3
166 changed files with 2715 additions and 1163 deletions

Binary file not shown.

View File

@ -1277,7 +1277,7 @@ namespace AgroBase.Forms.IHM
double Precisao = GpsInicializado ? (int)Variaveis.OperacaoEmAndamento.Sensoriamento.Gps.QualidadeFix : -1;
idx = PreencheDadosLinha(grid, Testes, idx, Precisao);
double Satelites = GpsInicializado ? Variaveis.OperacaoEmAndamento.Sensoriamento.Gps.NumeroSatelites.valor : -1;
double Satelites = GpsInicializado ? Variaveis.OperacaoEmAndamento.Sensoriamento.Gps.NumeroSatelites : -1;
idx = PreencheDadosLinha(grid, Testes, idx, Satelites);
double OrientacaoGPS = GpsInicializado ? Variaveis.OperacaoEmAndamento.Sensoriamento.Gps.OrientacaoReal : -1;

View File

@ -46,8 +46,8 @@
this.lblDistanciaPercorrida = new System.Windows.Forms.Label();
this.txtTempoOperacao = new System.Windows.Forms.TextBox();
this.lblTempoOperacao = new System.Windows.Forms.Label();
this.txtErvasIdentificadas = new System.Windows.Forms.TextBox();
this.lblErvasIdentificadas = new System.Windows.Forms.Label();
this.txtLatenciaControle = new System.Windows.Forms.TextBox();
this.lblLatenciaControle = new System.Windows.Forms.Label();
this.txtHerbicidaAplicado = new System.Windows.Forms.TextBox();
this.lblHerbicidaAplicado = new System.Windows.Forms.Label();
this.txtBateriaConsumida = new System.Windows.Forms.TextBox();
@ -271,24 +271,24 @@
this.lblTempoOperacao.TabIndex = 13;
this.lblTempoOperacao.Text = "Tempo de Operação";
//
// txtErvasIdentificadas
// txtLatenciaControle
//
this.txtErvasIdentificadas.Font = new System.Drawing.Font("Microsoft Sans Serif", 12.25F);
this.txtErvasIdentificadas.Location = new System.Drawing.Point(164, 261);
this.txtErvasIdentificadas.Name = "txtErvasIdentificadas";
this.txtErvasIdentificadas.ReadOnly = true;
this.txtErvasIdentificadas.Size = new System.Drawing.Size(120, 26);
this.txtErvasIdentificadas.TabIndex = 16;
this.txtErvasIdentificadas.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtLatenciaControle.Font = new System.Drawing.Font("Microsoft Sans Serif", 12.25F);
this.txtLatenciaControle.Location = new System.Drawing.Point(164, 261);
this.txtLatenciaControle.Name = "txtLatenciaControle";
this.txtLatenciaControle.ReadOnly = true;
this.txtLatenciaControle.Size = new System.Drawing.Size(120, 26);
this.txtLatenciaControle.TabIndex = 16;
this.txtLatenciaControle.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// lblErvasIdentificadas
// lblLatenciaControle
//
this.lblErvasIdentificadas.AutoSize = true;
this.lblErvasIdentificadas.Location = new System.Drawing.Point(3, 264);
this.lblErvasIdentificadas.Name = "lblErvasIdentificadas";
this.lblErvasIdentificadas.Size = new System.Drawing.Size(144, 20);
this.lblErvasIdentificadas.TabIndex = 15;
this.lblErvasIdentificadas.Text = "Ervas Identificadas";
this.lblLatenciaControle.AutoSize = true;
this.lblLatenciaControle.Location = new System.Drawing.Point(3, 264);
this.lblLatenciaControle.Name = "lblLatenciaControle";
this.lblLatenciaControle.Size = new System.Drawing.Size(134, 20);
this.lblLatenciaControle.TabIndex = 15;
this.lblLatenciaControle.Text = "Latencia Controle";
//
// txtHerbicidaAplicado
//
@ -462,9 +462,9 @@
this.pnlSensores.Controls.Add(this.txtBateriaConsumida);
this.pnlSensores.Controls.Add(this.txtTempoOperacao);
this.pnlSensores.Controls.Add(this.lblBateriaConsumida);
this.pnlSensores.Controls.Add(this.lblErvasIdentificadas);
this.pnlSensores.Controls.Add(this.lblLatenciaControle);
this.pnlSensores.Controls.Add(this.txtHerbicidaAplicado);
this.pnlSensores.Controls.Add(this.txtErvasIdentificadas);
this.pnlSensores.Controls.Add(this.txtLatenciaControle);
this.pnlSensores.Controls.Add(this.lblHerbicidaAplicado);
this.pnlSensores.Dock = System.Windows.Forms.DockStyle.Fill;
this.pnlSensores.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
@ -957,8 +957,8 @@
private System.Windows.Forms.Label lblDistanciaPercorrida;
private System.Windows.Forms.TextBox txtTempoOperacao;
private System.Windows.Forms.Label lblTempoOperacao;
private System.Windows.Forms.TextBox txtErvasIdentificadas;
private System.Windows.Forms.Label lblErvasIdentificadas;
private System.Windows.Forms.TextBox txtLatenciaControle;
private System.Windows.Forms.Label lblLatenciaControle;
private System.Windows.Forms.TextBox txtHerbicidaAplicado;
private System.Windows.Forms.Label lblHerbicidaAplicado;
private System.Windows.Forms.TextBox txtBateriaConsumida;

View File

@ -162,13 +162,13 @@ namespace AgroBase.Forms.IHM
txtOperacaoLiberada.Text = opLiberada ? "Sim" : "Não";
txtOperacaoLiberada.ForeColor = opLiberada ? Color.Black : Color.Red;
CodigoFalha = _Sensoriamento.CodigoFalha;
btnCodFalha.Text = CodigoFalha.ToString();
btnCodFalha.Text = CodigoFalha.ToString("0.000");
txtStatusAtual.Text = _Sensoriamento.StatusOperacao.ToString();
txtIniciadoEm.Text = _Sensoriamento.DataInicio == DateTime.MinValue ? "Aguardando" : _Sensoriamento.DataInicio.ToString("dd/MM HH:mm:ss");
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();
txtLatenciaControle.Text = _Sensoriamento.Controle.Latencia.ToString();
txtHerbicidaAplicado.Text = _Sensoriamento.Atuador.HerbicidaConsumido.ToString("0.000") + " L";
txtBateriaConsumida.Text = _Sensoriamento.Bateria.BateriaConsumida.ToString("0.00") + " %";

View File

@ -80,6 +80,7 @@
this.lblTipoMovimento = new System.Windows.Forms.Label();
this.chbPulverizador = new System.Windows.Forms.CheckBox();
this.flwCamerasSolo = new System.Windows.Forms.FlowLayoutPanel();
this.chbSonarAtivado = new System.Windows.Forms.CheckBox();
((System.ComponentModel.ISupportInitialize)(this.chartGraficos)).BeginInit();
this.tabControl1.SuspendLayout();
this.tabMapa.SuspendLayout();
@ -637,12 +638,25 @@
this.flwCamerasSolo.Size = new System.Drawing.Size(597, 172);
this.flwCamerasSolo.TabIndex = 36;
//
// chbSonarAtivado
//
this.chbSonarAtivado.AutoSize = true;
this.chbSonarAtivado.Location = new System.Drawing.Point(390, 310);
this.chbSonarAtivado.Margin = new System.Windows.Forms.Padding(2);
this.chbSonarAtivado.Name = "chbSonarAtivado";
this.chbSonarAtivado.Size = new System.Drawing.Size(92, 17);
this.chbSonarAtivado.TabIndex = 37;
this.chbSonarAtivado.Text = "Sonar ativado";
this.chbSonarAtivado.UseVisualStyleBackColor = true;
this.chbSonarAtivado.CheckedChanged += new System.EventHandler(this.chbSonarAtivado_CheckedChanged);
//
// frmOperacaoManual
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.White;
this.ClientSize = new System.Drawing.Size(1243, 649);
this.Controls.Add(this.chbSonarAtivado);
this.Controls.Add(this.flwCamerasSolo);
this.Controls.Add(this.chbPulverizador);
this.Controls.Add(this.lblTipoMovimento);
@ -742,5 +756,6 @@
private System.Windows.Forms.Label lblMov_DF;
private System.Windows.Forms.FlowLayoutPanel flwCamerasSolo;
private System.Windows.Forms.Label lblGPSFix;
private System.Windows.Forms.CheckBox chbSonarAtivado;
}
}

View File

@ -61,6 +61,7 @@ namespace AgroBase.Forms.Operacoes
}
chbPulverizador.Checked = Variaveis.OperacaoEmAndamento.Controle.PulverizadorAutomatico;
chbSonarAtivado.Checked = Variaveis.OperacaoEmAndamento.Controle.SonarAtivado;
InicializarComandosDirecional();
PopularInformacoesGerais();
@ -154,7 +155,7 @@ namespace AgroBase.Forms.Operacoes
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";
lblGPSsatelites.Text = _Sensoriamento.Gps.NumeroSatelites.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";
@ -165,13 +166,20 @@ namespace AgroBase.Forms.Operacoes
lblRPM.Text = _Sensoriamento.Movimentacao.RPMMedio.ToString("0") + " rpm";
lblVelocidade.Text = (_Sensoriamento.Movimentacao.VelocidadeMedia * 3.6).ToString("0.00");
bool atuadorAtivado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ConexaoAtiva || true;
bool atuadorAtivado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ConexaoAtiva;
chbPulverizador.Enabled = atuadorAtivado;
if (!atuadorAtivado)
{
chbPulverizador.Checked = false;
}
bool sonarAtivado = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual.Status == StatusModulo.Operante;
chbSonarAtivado.Enabled = sonarAtivado;
if (!sonarAtivado)
{
chbSonarAtivado.Checked = false;
}
if (!ReqFrameCam)
_ = AtualizarCameraSolo();
@ -674,7 +682,7 @@ namespace AgroBase.Forms.Operacoes
private void chbSonarAtivado_CheckedChanged(object sender, EventArgs e)
{
Variaveis.OperacaoEmAndamento.Controle.SonarAtivado = ((CheckBox)sender).Checked;
}
private void chbSonarDesvioAutomatico_CheckedChanged(object sender, EventArgs e)

View File

@ -1085,12 +1085,12 @@ namespace AgroBase.Forms.Operacoes
txtStatusCarro.Text = Log.Trajetoria.StatusCarro.ToString();
txtHerbicidaPorErva.Text = herbicidaPorErva.ToString("0.00") + " ml";
txtDentroRua.Text = Log.Trajetoria.CorredorAtual.Dentro ? "Sim" : "Não";
txtDirecaoCarro.Text = Log.Trajetoria.DirecaoCaminho.ToString();
txtDirecaoCarro.Text = Log.Trajetoria.DirecaoCaminho.ToString() + " (" + Log.Trajetoria.ProximoPonto.idxPonto + ")";
txtErvasNoRadar.Text = Log.Atuador.ErvasNoRadar.ToString();
txtErvasTerreno.Text = Log.Atuador.PercentualErvasTerreno.ToString("0.00");
txtTipoMovimento.Text = Log.Controle.TipoMovimento.ToString();
txtVelocidade.Text = (Log.Movimentacao.VelocidadeMedia * 3.6).ToString("0.00") + " km/h (" + Log.Movimentacao.VelocidadeMedia.ToString("0.00") + " m/s)";
txtDirecaoControle.Text = (Log.Controle?.Direcao ?? Enums.Direcao.Parado).ToString();
txtDirecaoControle.Text = (Log.Controle?.Direcao ?? Enums.Direcao.Parado).ToString() + " (" + (Log.Controle?.Latencia ?? 0).ToString("0.00") + " s)";
txtVelocidadeControle.Text = ((Log.Controle?.PercentualVelocidadeSP ?? 0) / VariaveisEquipamento.RPM_Max_Roda * 100).ToString() + " rpm";
txtErvasIdentificadas.Text = $"{totalAtuacoes}";
@ -1170,7 +1170,7 @@ namespace AgroBase.Forms.Operacoes
txtGPS_Longitude.Text = (Log?.Longitude ?? 0).ToString();
txtGPS_Correcao.Text = ((Log?.Ntrip_ativado ?? false) ? "N " : "B ") + (Log?.IdadeCorrecao ?? -1) + ": " + (Log?.QualidadeFix ?? Enums.TiposCorrecaoGPS.SemCorrecao).ToString();
txtGPS_Precisao.Text = (Log?.PrecisaoCm ?? 0).ToString("0.00") + " cm";
txtGPS_Satelites.Text = (Log?.NumeroSatelites?.valor ?? 0).ToString();
txtGPS_Satelites.Text = (Log?.NumeroSatelites ?? 0).ToString();
txtGPS_Velocidade.Text = (Log?.Velocidade ?? 0).ToString("0.00") + " (" + ((Log?.Velocidade ?? 0) * GPSUtils.ConversaoNosKmh).ToString("0.00") + " km/h)";
var LogLra = LogsOperacao.FirstOrDefault(x => x.Momento == MomentoAtual).DadosLoRa;

View File

@ -30,8 +30,8 @@
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmSimulacaoMapaGPS));
this.pnlOpcoes = new System.Windows.Forms.Panel();
this.label7 = new System.Windows.Forms.Label();
this.txtErroTempoComando = new System.Windows.Forms.TextBox();
this.lblDelayPosicao = new System.Windows.Forms.Label();
this.txtDelayPosicao = new System.Windows.Forms.TextBox();
this.lblCarregado = new System.Windows.Forms.Label();
this.btnCarregarTrajetoria = new System.Windows.Forms.Button();
this.label6 = new System.Windows.Forms.Label();
@ -46,13 +46,15 @@
this.lblKp = new System.Windows.Forms.Label();
this.txtKp = new System.Windows.Forms.TextBox();
this.btnReiniciar = new System.Windows.Forms.Button();
this.lblQtdErro = new System.Windows.Forms.Label();
this.lblDelayComando = new System.Windows.Forms.Label();
this.chbAutomatico = new System.Windows.Forms.CheckBox();
this.txtQtdErro = new System.Windows.Forms.TextBox();
this.txtDelayComando = new System.Windows.Forms.TextBox();
this.btnAtualizarLeitura = new System.Windows.Forms.Button();
this.btnCarregarMapa = new System.Windows.Forms.Button();
this.btnIniciarSimulacao = new System.Windows.Forms.Button();
this.gpbSonar = new System.Windows.Forms.GroupBox();
this.label8 = new System.Windows.Forms.Label();
this.cmbStatusCorredor = new System.Windows.Forms.ComboBox();
this.chbMoverObstaculo = new System.Windows.Forms.CheckBox();
this.chbSonarVirando = new System.Windows.Forms.CheckBox();
this.btnAdicionarDeteccao = new System.Windows.Forms.Button();
@ -102,8 +104,6 @@
this.lblTempoRestante = new System.Windows.Forms.Label();
this.lblPercentualRua = new System.Windows.Forms.Label();
this.lblPercentualOperacao = new System.Windows.Forms.Label();
this.label8 = new System.Windows.Forms.Label();
this.cmbStatusCorredor = new System.Windows.Forms.ComboBox();
this.pnlOpcoes.SuspendLayout();
this.gpbSonar.SuspendLayout();
this.gpbGPS.SuspendLayout();
@ -112,8 +112,8 @@
//
// pnlOpcoes
//
this.pnlOpcoes.Controls.Add(this.label7);
this.pnlOpcoes.Controls.Add(this.txtErroTempoComando);
this.pnlOpcoes.Controls.Add(this.lblDelayPosicao);
this.pnlOpcoes.Controls.Add(this.txtDelayPosicao);
this.pnlOpcoes.Controls.Add(this.lblCarregado);
this.pnlOpcoes.Controls.Add(this.btnCarregarTrajetoria);
this.pnlOpcoes.Controls.Add(this.label6);
@ -128,9 +128,9 @@
this.pnlOpcoes.Controls.Add(this.lblKp);
this.pnlOpcoes.Controls.Add(this.txtKp);
this.pnlOpcoes.Controls.Add(this.btnReiniciar);
this.pnlOpcoes.Controls.Add(this.lblQtdErro);
this.pnlOpcoes.Controls.Add(this.lblDelayComando);
this.pnlOpcoes.Controls.Add(this.chbAutomatico);
this.pnlOpcoes.Controls.Add(this.txtQtdErro);
this.pnlOpcoes.Controls.Add(this.txtDelayComando);
this.pnlOpcoes.Controls.Add(this.btnAtualizarLeitura);
this.pnlOpcoes.Controls.Add(this.btnCarregarMapa);
this.pnlOpcoes.Controls.Add(this.btnIniciarSimulacao);
@ -143,25 +143,25 @@
this.pnlOpcoes.Size = new System.Drawing.Size(1379, 100);
this.pnlOpcoes.TabIndex = 0;
//
// label7
// lblDelayPosicao
//
this.label7.AutoSize = true;
this.label7.Location = new System.Drawing.Point(1277, 20);
this.label7.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label7.Name = "label7";
this.label7.Size = new System.Drawing.Size(76, 13);
this.label7.TabIndex = 45;
this.label7.Text = "Delay Controle";
this.lblDelayPosicao.AutoSize = true;
this.lblDelayPosicao.Location = new System.Drawing.Point(1277, 20);
this.lblDelayPosicao.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblDelayPosicao.Name = "lblDelayPosicao";
this.lblDelayPosicao.Size = new System.Drawing.Size(75, 13);
this.lblDelayPosicao.TabIndex = 45;
this.lblDelayPosicao.Text = "Delay Posição";
//
// txtErroTempoComando
// txtDelayPosicao
//
this.txtErroTempoComando.Location = new System.Drawing.Point(1279, 35);
this.txtErroTempoComando.Margin = new System.Windows.Forms.Padding(2);
this.txtErroTempoComando.Name = "txtErroTempoComando";
this.txtErroTempoComando.Size = new System.Drawing.Size(88, 20);
this.txtErroTempoComando.TabIndex = 44;
this.txtErroTempoComando.Text = "1";
this.txtErroTempoComando.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtDelayPosicao.Location = new System.Drawing.Point(1279, 35);
this.txtDelayPosicao.Margin = new System.Windows.Forms.Padding(2);
this.txtDelayPosicao.Name = "txtDelayPosicao";
this.txtDelayPosicao.Size = new System.Drawing.Size(88, 20);
this.txtDelayPosicao.TabIndex = 44;
this.txtDelayPosicao.Text = "1";
this.txtDelayPosicao.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// lblCarregado
//
@ -307,15 +307,15 @@
this.btnReiniciar.UseVisualStyleBackColor = true;
this.btnReiniciar.Click += new System.EventHandler(this.btnReiniciar_Click);
//
// lblQtdErro
// lblDelayComando
//
this.lblQtdErro.AutoSize = true;
this.lblQtdErro.Location = new System.Drawing.Point(1087, 20);
this.lblQtdErro.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblQtdErro.Name = "lblQtdErro";
this.lblQtdErro.Size = new System.Drawing.Size(45, 13);
this.lblQtdErro.TabIndex = 29;
this.lblQtdErro.Text = "Qtd erro";
this.lblDelayComando.AutoSize = true;
this.lblDelayComando.Location = new System.Drawing.Point(1087, 20);
this.lblDelayComando.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblDelayComando.Name = "lblDelayComando";
this.lblDelayComando.Size = new System.Drawing.Size(58, 13);
this.lblDelayComando.TabIndex = 29;
this.lblDelayComando.Text = "Delay Cmd";
//
// chbAutomatico
//
@ -329,15 +329,15 @@
this.chbAutomatico.UseVisualStyleBackColor = true;
this.chbAutomatico.CheckedChanged += new System.EventHandler(this.chbAutomatico_CheckedChanged);
//
// txtQtdErro
// txtDelayComando
//
this.txtQtdErro.Location = new System.Drawing.Point(1089, 35);
this.txtQtdErro.Margin = new System.Windows.Forms.Padding(2);
this.txtQtdErro.Name = "txtQtdErro";
this.txtQtdErro.Size = new System.Drawing.Size(54, 20);
this.txtQtdErro.TabIndex = 28;
this.txtQtdErro.Text = "1";
this.txtQtdErro.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
this.txtDelayComando.Location = new System.Drawing.Point(1089, 35);
this.txtDelayComando.Margin = new System.Windows.Forms.Padding(2);
this.txtDelayComando.Name = "txtDelayComando";
this.txtDelayComando.Size = new System.Drawing.Size(54, 20);
this.txtDelayComando.TabIndex = 28;
this.txtDelayComando.Text = "1";
this.txtDelayComando.TextAlign = System.Windows.Forms.HorizontalAlignment.Center;
//
// btnAtualizarLeitura
//
@ -394,6 +394,26 @@
this.gpbSonar.TabStop = false;
this.gpbSonar.Text = "Sonar";
//
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(117, 53);
this.label8.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(80, 13);
this.label8.TabIndex = 41;
this.label8.Text = "Status Corredor";
//
// cmbStatusCorredor
//
this.cmbStatusCorredor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbStatusCorredor.FormattingEnabled = true;
this.cmbStatusCorredor.Location = new System.Drawing.Point(121, 68);
this.cmbStatusCorredor.Name = "cmbStatusCorredor";
this.cmbStatusCorredor.Size = new System.Drawing.Size(106, 21);
this.cmbStatusCorredor.TabIndex = 40;
this.cmbStatusCorredor.SelectedIndexChanged += new System.EventHandler(this.cmbStatusCorredor_SelectedIndexChanged);
//
// chbMoverObstaculo
//
this.chbMoverObstaculo.AutoSize = true;
@ -953,26 +973,6 @@
this.lblPercentualOperacao.TabIndex = 42;
this.lblPercentualOperacao.Text = "Operação: 0,00%";
//
// label8
//
this.label8.AutoSize = true;
this.label8.Location = new System.Drawing.Point(117, 53);
this.label8.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.label8.Name = "label8";
this.label8.Size = new System.Drawing.Size(80, 13);
this.label8.TabIndex = 41;
this.label8.Text = "Status Corredor";
//
// cmbStatusCorredor
//
this.cmbStatusCorredor.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
this.cmbStatusCorredor.FormattingEnabled = true;
this.cmbStatusCorredor.Location = new System.Drawing.Point(121, 68);
this.cmbStatusCorredor.Name = "cmbStatusCorredor";
this.cmbStatusCorredor.Size = new System.Drawing.Size(106, 21);
this.cmbStatusCorredor.TabIndex = 40;
this.cmbStatusCorredor.SelectedIndexChanged += new System.EventHandler(this.cmbStatusCorredor_SelectedIndexChanged);
//
// frmSimulacaoMapaGPS
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
@ -1071,8 +1071,8 @@
private System.Windows.Forms.Label lblStatusOperacao;
private System.Windows.Forms.Label lblTempoOperacao;
private System.Windows.Forms.CheckBox chbAutomatico;
private System.Windows.Forms.Label lblQtdErro;
private System.Windows.Forms.TextBox txtQtdErro;
private System.Windows.Forms.Label lblDelayComando;
private System.Windows.Forms.TextBox txtDelayComando;
private System.Windows.Forms.Button btnReiniciar;
private System.Windows.Forms.Label lblKd;
private System.Windows.Forms.TextBox txtKd;
@ -1091,8 +1091,8 @@
private System.Windows.Forms.TextBox txtTempoGPS;
private System.Windows.Forms.Button btnCarregarTrajetoria;
private System.Windows.Forms.Label lblCarregado;
private System.Windows.Forms.Label label7;
private System.Windows.Forms.TextBox txtErroTempoComando;
private System.Windows.Forms.Label lblDelayPosicao;
private System.Windows.Forms.TextBox txtDelayPosicao;
private System.Windows.Forms.Label label8;
private System.Windows.Forms.ComboBox cmbStatusCorredor;
}

View File

@ -26,6 +26,7 @@ namespace AgroBase.Forms
DateTime UltimaAtualizacaoGps = DateTime.MinValue;
Queue<double> historicoAngulosControle = new Queue<double>();
GPSModel posicaoAtual = new GPSModel();
List<MPCSimulacaoModel> simulacao = new List<MPCSimulacaoModel>();
@ -67,7 +68,7 @@ namespace AgroBase.Forms
private void frmSimulacaoMapaGPS_FormClosing(object sender, FormClosingEventArgs e)
{
tmrLeitura.Dispose();
tmrLeitura?.Dispose();
Variaveis.OperacaoEmAndamento.Iniciado = false;
}
@ -196,6 +197,14 @@ namespace AgroBase.Forms
private void btnIniciarGPS_Click(object sender, EventArgs e)
{
posicaoAtual = new GPSModel()
{
DataHora = DateTime.Now,
Longitude = double.Parse(txtLongitude.Text.Replace(".", ",")),
Latitude = double.Parse(txtLatitude.Text.Replace(".", ",")),
Heartbeat = GPSService.UltimaLeitura.Heartbeat,
OrientacaoReal = double.Parse(txtAnguloGPS.Text.Replace(".", ",")),
};
AtualizarPosicaoGPS();
btnAcrescentarGPS.Enabled = true;
txtAnguloGPS.Enabled = true;
@ -213,29 +222,24 @@ namespace AgroBase.Forms
Longitude = GPSService.UltimaLeitura.Longitude,
Latitude = GPSService.UltimaLeitura.Latitude,
Heartbeat = GPSService.UltimaLeitura.Heartbeat,
OrientacaoReal = GPSService.UltimaLeitura.OrientacaoReal
};
if (!GPSService.Iniciado || (txtLongitude.Text != "" && txtLatitude.Text != ""))
{
GPSService.UltimaLeitura = new GPSModel()
{
DataHora = DateTime.Now,
Longitude = double.Parse(txtLongitude.Text.Replace(".", ",")),
Latitude = double.Parse(txtLatitude.Text.Replace(".", ",")),
Heartbeat = GPSService.UltimaLeitura.Heartbeat
};
GPSService.UltimaLeitura = posicaoAtual;
}
GPSService.AtualizarAtrasoPosicoes(Convert.ToInt32(txtDelayPosicao.Text));
GPSService.AtualizarCoordenadasGPS();
}
private async void btnAcrescentarGPS_Click(object sender, EventArgs e)
{
GPSModel novaPosicao = new GPSModel();
double velocidadeMs = FuncoesMatematicas.CalculaVelocidadeMsPercentual(Variaveis.OperacaoEmAndamento.SimulacaoRpmControle);
velocidadeCarroMs = FuncoesMatematicas.ConverteMsParaKmh(velocidadeMs);
GPSModel novaPosicao = null;
velocidadeCarroMs = FuncoesMatematicas.CalculaVelocidadeMsPercentual(Variaveis.OperacaoEmAndamento.SimulacaoRpmControle);
Variaveis.OperacaoEmAndamento.DispMvd.Dados.VelocidadeMedia = velocidadeMs;
Variaveis.OperacaoEmAndamento.DispMvd.Dados.VelocidadeMedia = velocidadeCarroMs;
double tempoPassado =
!chbAutomatico.Checked || UltimaAtualizacaoGps == DateTime.MinValue ? // esta no modo manual
@ -266,10 +270,7 @@ namespace AgroBase.Forms
anguloControle = double.Parse(txtAnguloControle.Text);
double.TryParse(txtErroTempoComando.Text, out double fatorErro);
int ticksMax = (int)(ticksControleMax * Math.Max(1, fatorErro));
bool atualizarAnguloControle = (ticksControle >= ticksMax);
bool atualizarAnguloControle = (ticksControle >= ticksControleMax);
if (atualizarAnguloControle)
{
ticksControle = 0;
@ -288,20 +289,23 @@ namespace AgroBase.Forms
// Calcula a nova posicao do robo
//GPSModel pos = NovaPosicaoSimulacao(velocidadeMs, tempoPassado);
if (true || novaPosicao == null)
if (novaPosicao == null)
{
novaPosicao = VariaveisEquipamento.SimularNovaPosicao(tipoMovimento, anguloControle, velocidadeMs, tempoPassado, anguloAtual, _Gps);
novaPosicao = VariaveisEquipamento.SimularNovaPosicao(tipoMovimento, anguloControle, velocidadeCarroMs, tempoPassado, anguloAtual, _Gps);
}
posicaoAtual = novaPosicao;
//Console.WriteLine($"C#: {DadosPosicao(novaPosicao)}");
//Console.WriteLine($"Py: {DadosPosicao(pos)}");
// Atualiza a interface gráfica
txtAnguloGPS.Text = novaPosicao.OrientacaoReal.ToString("0.00");
txtAnguloGPS.Text = posicaoAtual.OrientacaoReal.ToString("0.00");
txtVelocidade.Text = velocidadeCarroMs.ToString("0.00");
txtDistanciaGPS.Text = novaPosicao.Distancia.ToString("0.0000");
txtLatitude.Text = novaPosicao.Latitude.ToString();
txtLongitude.Text = novaPosicao.Longitude.ToString();
txtDistanciaGPS.Text = posicaoAtual.Distancia.ToString("0.0000");
txtLatitude.Text = posicaoAtual.Latitude.ToString();
txtLongitude.Text = posicaoAtual.Longitude.ToString();
UltimaAtualizacaoGps = DateTime.Now;
@ -420,8 +424,10 @@ namespace AgroBase.Forms
{
try
{
double angulo = Variaveis.OperacaoEmAndamento.Trajetoria.AnguloCaminho;
double anguloCorredor = Variaveis.OperacaoEmAndamento.Trajetoria.AnguloMedioCorredor;
//double angulo = Variaveis.OperacaoEmAndamento.Trajetoria.AnguloCaminho;
//double anguloCorredor = Variaveis.OperacaoEmAndamento.Trajetoria.AnguloMedioCorredor;
double angulo = Variaveis.OperacaoEmAndamento.Trajetoria.ProximoPonto.Orientacao;
double anguloCorredor = Variaveis.OperacaoEmAndamento.Trajetoria.AnguloCaminho;
FuncoesGlobais.DesenharInclinacao((Panel)sender, e, (float)angulo, 90, (float)anguloCorredor);
}
catch { }
@ -455,19 +461,14 @@ namespace AgroBase.Forms
private void btnCalcularAngulo_Click(object sender, EventArgs e)
{
//Variaveis.OperacaoEmAndamento.OpMapaGPS.CalculaDadosMovimentacaoAutonoma();
historicoAngulosControle.Enqueue(Variaveis.OperacaoEmAndamento.SimulacaoAnguloControle);
int errosConsiderar = int.Parse(txtDelayComando.Text);
while (historicoAngulosControle.Count > 1 && (historicoAngulosControle.Count > errosConsiderar || errosConsiderar == 0))
{
historicoAngulosControle.Dequeue();
}
int errosConsiderar = int.Parse(txtQtdErro.Text);
double anguloControle = Variaveis.OperacaoEmAndamento.SimulacaoAnguloControle;
if (historicoAngulosControle.Count > 0 && (historicoAngulosControle.Count >= errosConsiderar || errosConsiderar == 0))
historicoAngulosControle.Dequeue(); // Remove o valor mais antigo
historicoAngulosControle.Enqueue(anguloControle);
double anguloAtrasado = errosConsiderar == 0 ? anguloControle : historicoAngulosControle.Peek(); // Valor de 5 ciclos atrás
txtAnguloControle.Text = anguloAtrasado.ToString("0.00");
txtAnguloControle.Text = historicoAngulosControle.Peek().ToString("0.00");
}
private void chbSonarVirando_CheckedChanged(object sender, EventArgs e)

View File

@ -112,7 +112,7 @@
// lblVariacaoMagnetica
//
this.lblVariacaoMagnetica.AutoSize = true;
this.lblVariacaoMagnetica.Location = new System.Drawing.Point(768, 41);
this.lblVariacaoMagnetica.Location = new System.Drawing.Point(815, 41);
this.lblVariacaoMagnetica.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblVariacaoMagnetica.Name = "lblVariacaoMagnetica";
this.lblVariacaoMagnetica.Size = new System.Drawing.Size(129, 13);
@ -122,7 +122,7 @@
// lblCursoVerdadeiro
//
this.lblCursoVerdadeiro.AutoSize = true;
this.lblCursoVerdadeiro.Location = new System.Drawing.Point(627, 41);
this.lblCursoVerdadeiro.Location = new System.Drawing.Point(674, 41);
this.lblCursoVerdadeiro.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblCursoVerdadeiro.Name = "lblCursoVerdadeiro";
this.lblCursoVerdadeiro.Size = new System.Drawing.Size(119, 13);
@ -197,7 +197,7 @@
// lblFix
//
this.lblFix.AutoSize = true;
this.lblFix.Location = new System.Drawing.Point(499, 41);
this.lblFix.Location = new System.Drawing.Point(506, 41);
this.lblFix.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblFix.Name = "lblFix";
this.lblFix.Size = new System.Drawing.Size(96, 13);
@ -225,7 +225,7 @@
// lblOrientacao
//
this.lblOrientacao.AutoSize = true;
this.lblOrientacao.Location = new System.Drawing.Point(627, 17);
this.lblOrientacao.Location = new System.Drawing.Point(674, 17);
this.lblOrientacao.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblOrientacao.Name = "lblOrientacao";
this.lblOrientacao.Size = new System.Drawing.Size(90, 13);
@ -255,7 +255,7 @@
// lblDatahora
//
this.lblDatahora.AutoSize = true;
this.lblDatahora.Location = new System.Drawing.Point(768, 17);
this.lblDatahora.Location = new System.Drawing.Point(815, 17);
this.lblDatahora.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblDatahora.Name = "lblDatahora";
this.lblDatahora.Size = new System.Drawing.Size(77, 13);
@ -265,7 +265,7 @@
// lblSatelites
//
this.lblSatelites.AutoSize = true;
this.lblSatelites.Location = new System.Drawing.Point(499, 17);
this.lblSatelites.Location = new System.Drawing.Point(506, 17);
this.lblSatelites.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0);
this.lblSatelites.Name = "lblSatelites";
this.lblSatelites.Size = new System.Drawing.Size(99, 13);

View File

@ -49,12 +49,12 @@ namespace AgroBase.Forms
txtLongitude.Text = Gps.Longitude.ToString();
lblPrecisaoHorizontal.Text = "Precisão Horizontal: " + Gps.PrecisaoHorizontal.ToString("0.00");
lblPrecisaoCm.Text = "Precisão Leitura: " + Gps.PrecisaoCm.ToString("0.00") + " cm";
lblSatelites.Text = "Satélites Visíveis: " + Gps.NumeroSatelites.valor.ToString();
lblSatelites.Text = "Satélites Visíveis: " + Gps.NumeroSatelites.ToString();
lblVelocidade.Text = "Velocidade: " + Gps.Velocidade.ToString("0.00") + " km/h";
lblOrientacao.Text = "Orientação: " + Gps.OrientacaoReal.ToString("0.00") + "º (" + Gps.OrientacaoMovimento.ToString("0.00") + "º)";
lblCursoVerdadeiro.Text = "Curso Verdadeiro: " + Gps.CursoVerdadeiro.ToString("0.00") + "º";
lblVariacaoMagnetica.Text = "Variação Magnética: " + Gps.VariacaoMagnetica.ToString("0.00") + "º";
lblFix.Text = "Correção: " + Gps.QualidadeFix.ToString();
lblFix.Text = "Correção: " + Gps.QualidadeFix.ToString() + " (" + Gps.IdadeCorrecao.ToString("0.0") + " s)";
if (chbSalvar.Checked)
{

View File

@ -10,6 +10,8 @@ namespace AgroBase.Models
public class GPSModel
{
public DateTime Momento { get; set; }
public CanCommandFreqModel<double> TimestampPos { get; set; } = new CanCommandFreqModel<double>();
public CanCommandFreqModel<double> TimestampOri { get; set; } = new CanCommandFreqModel<double>();
public bool Inicializado { get; set; }
public bool Ntrip_ativado { get; set; }
public double Latitude { get; set; }
@ -19,7 +21,8 @@ namespace AgroBase.Models
public double Velocidade { get; set; }
public double Distancia { get; set; }
public double OrientacaoMovimento { get; set; }
public CanCommandFreqModel<int> NumeroSatelites { get; set; } = new CanCommandFreqModel<int>();
public int NumeroSatelites { get; set; }
//public CanCommandFreqModel<int> NumeroSatelites { get; set; } = new CanCommandFreqModel<int>();
public double PrecisaoHorizontal { get; set; }
public double CursoVerdadeiro { get; set; }
public double OrientacaoReal { get; set; }
@ -59,6 +62,8 @@ namespace AgroBase.Models
{
return new GPSModel()
{
TimestampPos = TimestampPos.Clone(),
TimestampOri = TimestampOri.Clone(),
Ntrip_ativado = Ntrip_ativado,
NumeroSatelites = NumeroSatelites,
Altitude = Altitude,

View File

@ -686,7 +686,7 @@ namespace AgroBase
var Bico = DispAtu.Dados.BicosPulverizadores.FirstOrDefault(x => x.ID == ID);
if (Bico != null)
{
Console.WriteLine($"Bico {Bico.Posicao} atualizado de {Bico.ComandoAtuar} para {Status}");
//Console.WriteLine($"Bico {Bico.Posicao} atualizado de {Bico.ComandoAtuar} para {Status}");
Bico.ComandoAtuar = Status;
(id_num, posicao, data) = Bico.ProtocoloComando();
}
@ -755,10 +755,11 @@ namespace AgroBase
{
List<Keys> ComandosImpedidos = new List<Keys>();
/*var Sonar = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual;
if (Sonar.Iniciado && Variaveis.OperacaoEmAndamento.Controle.SonarAtivado && Sonar.Resumo.DesvioNecessario)
var Sonar = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual;
if (Variaveis.OperacaoEmAndamento.Controle.SonarAtivado && Sonar.Iniciado && Sonar.Status == StatusModulo.Operante && (Sonar.Analises.matriz_confianca?.block?.decision?.parar ?? false))
{
switch (Sonar.Resumo.DirecaoDesvio)
ComandosImpedidos.Add(Keys.Up);
/*switch (Sonar.Resumo.DirecaoDesvio)
{
case Direcao.Parado:
ComandosImpedidos.Add(Keys.Up);
@ -769,8 +770,8 @@ namespace AgroBase
case Direcao.Direita:
ComandosImpedidos.Add(Keys.Left);
break;
}
}*/
}*/
}
if (UltrasonicA05Service.Iniciado)
{

View File

@ -786,7 +786,7 @@ namespace AgroBase.Models.Modules
bool AtingiuPressao() => PressaoLinha >= Variaveis.OperacaoEmAndamento.Controle.PressaoLinha;
await FuncoesGlobais.AguardarCondicaoAsync(AtingiuPressao, 10000, 200);
await FuncoesGlobais.AguardarCondicaoAsync(AtingiuPressao, 6000, 200);
BombaOk = AtingiuPressao();
}

View File

@ -2958,7 +2958,7 @@ namespace AgroBase.Models.Modules
VariaveisOperacao.PosicaoBase.Inicializado = iniciado;
VariaveisOperacao.PosicaoBase.QualidadeFix = qualidadeFix;
VariaveisOperacao.PosicaoBase.NumeroSatelites.valor = numeroSatelites;
VariaveisOperacao.PosicaoBase.NumeroSatelites = numeroSatelites;
VariaveisOperacao.PosicaoBase.PrecisaoHorizontal = precisao;
VariaveisOperacao.PosicaoBase.Momento = DateTime.Now;
break;

View File

@ -727,6 +727,8 @@ namespace AgroBase.Models
Variaveis.OperacaoEmAndamento.Finalizando = true;
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, false, Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault()?.ID ?? "", true);
Variaveis.OperacaoEmAndamento.Trajetoria?.LoopAtualizaDados();
await Task.Delay(2000);
@ -1023,12 +1025,24 @@ namespace AgroBase.Models
{
ControleAnterior.BicosAtuados = Controle.BicosAtuados.Select(x => x.Clone()).ToList();
}
bool enviarComando = false;
for (int i = 0; i < _Controle.BicosAtuados.Count; i++)
{
if (ForcarEnvio || _Controle.BicosAtuados[i].ComandoAtuar != _ControleAnterior.BicosAtuados[i].ComandoAtuar)
{
GeneralJoystick.EnviarComandoAtuador(S_Code.sBIC, _Controle.BicosAtuados[i].ComandoAtuar, _Controle.BicosAtuados[i].ID);
_ControleAnterior.BicosAtuados[i].ComandoAtuar = _Controle.BicosAtuados[i].ComandoAtuar;
enviarComando = true;
}
}
if (enviarComando)
{
var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault();
bool Ligado = Bomba.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Potencia)?.atual?.valor > 0;
if (Bomba != null && Bomba.ComandoAtuar && !Ligado)
{
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, true, Bomba.ID);
}
}
@ -1056,6 +1070,17 @@ namespace AgroBase.Models
{
Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas();
}
if (Variaveis.OperacaoEmAndamento.Modo == ModoOperacao.Manual)
{
var Sonar = Variaveis.OperacaoEmAndamento.Sensoriamento.OperadorVisual;
bool deveParar = (Sonar.Analises.matriz_confianca?.block?.decision?.parar ?? false);
bool emMovimento = Variaveis.OperacaoEmAndamento.Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Mov)?.UltimaDirecao != Direcao.Parado;
if (Variaveis.OperacaoEmAndamento.Controle.SonarAtivado && Sonar.Iniciado && Sonar.Status == StatusModulo.Operante && deveParar && emMovimento)
{
GeneralJoystick.EnviaComandoMotor(Keys.Escape, T_Code.Mov);
}
}
}
public void ExecutaComandoDaBase(OperacaoControleBaseModel controleBase)
@ -1423,6 +1448,22 @@ namespace AgroBase.Models
nodoModulo.Nodes.Add(new TreeNode(motivo));
}
}
if (modulo.condicoes_operacionais?.Any() ?? false)
{
// Agrupamento das condições operacionais (se houver)
if (modulo.condicoes_operacionais != null && modulo.condicoes_operacionais.Count > 0)
{
TreeNode nodoCondicoes = new TreeNode("Condições Operacionais");
foreach (var cond in modulo.condicoes_operacionais)
{
string textoCond = $"{cond.descricao} (Valor: {cond.valor}, Severidade: {cond.severidade:0}%)";
nodoCondicoes.Nodes.Add(new TreeNode(textoCond));
}
nodoModulo.Nodes.Add(nodoCondicoes);
}
}
treeView.Nodes.Add(nodoModulo);
}
@ -1554,6 +1595,7 @@ namespace AgroBase.Models
public TiposControladorDirecional TipoControleDirecional { get; set; } = TiposControladorDirecional.PID;
public List<MPCSimulacaoModel> SimulacaoMPC { get; set; } = new List<MPCSimulacaoModel>();
public double Latencia { get; set; }
public OperacaoControleModel Clone()
{
@ -1580,7 +1622,8 @@ namespace AgroBase.Models
PulverizadorAutomatico = PulverizadorAutomatico,
MovimentoAutomatico = MovimentoAutomatico,
heartbeat = heartbeat,
ticks_sem_resposta = ticks_sem_resposta
ticks_sem_resposta = ticks_sem_resposta,
Latencia = Latencia,
};
}
}
@ -1663,6 +1706,7 @@ namespace AgroBase.Models
public VisualWorkerModel OperadorVisual { get; set; }
public WeedWorkerModel OperadorErvas { get; set; }
public List<ManagerWorkerMessageResponseModulosPendentesModel> ModulosSaude { get; set; }
public bool CanSerialAtivado { get; set; }
@ -1708,6 +1752,7 @@ namespace AgroBase.Models
PercentualErvasTerreno = _DispAtu.Dados.PercentualErvasTerreno,
HerbicidaPorErva = HerbicidaPorErva,
ErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.ervas_no_radar ?? false,
PercentualErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.estatisticas?.erva?.frac_global ?? 0,
PercentualReservatorio = FuncoesMatematicas.Clamp(_DispAtu.Dados.PercentualReservatorio, 0, 100),
VolumeReservatorio = _DispAtu.Dados.VolumeReservatorio,
PressaoLinha = _DispAtu.Dados.PressaoLinha,
@ -1772,6 +1817,7 @@ namespace AgroBase.Models
PercentualOperacao = _Trajetoria?.PercentualTrajetoria ?? 0,
PercentualRuaAtual = _Trajetoria?.CorredorAtual?.Progresso ?? 0,
IdxRuaAtual = _Trajetoria?.CorredorAtual?.Idx ?? 0,
QtdPontos = _Trajetoria?.TrajetoriaFixa?.Count ?? 0
};
var dadosGps = GPSService.UltimaLeitura.Clone();
@ -1800,6 +1846,7 @@ namespace AgroBase.Models
DataInicio = Variaveis.OperacaoEmAndamento.DataInicio,
DataFim = Variaveis.OperacaoEmAndamento.DataFim,
TempoDecorridoSeg = Variaveis.OperacaoEmAndamento.TempoDecorridoSegs,
CanSerialAtivado = CanManager.UseCanSerial,
IMU = dadosIMU,
Atuador = dadosAtuador,
Movimentacao = dadosMovimentacao,
@ -1834,6 +1881,7 @@ namespace AgroBase.Models
DataInicio = DataInicio,
DataFim = DataFim,
TempoDecorridoSeg = TempoDecorridoSeg,
CanSerialAtivado = CanSerialAtivado,
Atuador = Atuador?.Clone(),
Movimentacao = Movimentacao?.Clone(),
Bateria = Bateria?.Clone(),
@ -1885,6 +1933,7 @@ namespace AgroBase.Models
public double PercentualErvasTerreno { get; set; }
public double HerbicidaPorErva { get; set; }
public bool ErvasNoRadar { get; set; }
public double PercentualErvasNoRadar { get; set; }
public double PercentualReservatorio { get; set; }
public double VolumeReservatorio { get; set; }
public double PressaoLinha { get; set; }
@ -1899,6 +1948,7 @@ namespace AgroBase.Models
{
QtdCamerasSolo = QtdCamerasSolo,
ErvasNoRadar = ErvasNoRadar,
PercentualErvasNoRadar = PercentualErvasNoRadar,
HerbicidaConsumido = HerbicidaConsumido,
HerbicidaPorErva = HerbicidaPorErva,
PercentualErvasTerreno = PercentualErvasTerreno,
@ -2007,6 +2057,7 @@ namespace AgroBase.Models
public int IdxRuaAtual { get; set; }
public double PercentualOperacao { get; set; }
public double PercentualRuaAtual { get; set; }
public int QtdPontos { get; set; }
public OperacaoSensoriamentoLogTrajetoriaModel Clone()
{
@ -2038,6 +2089,7 @@ namespace AgroBase.Models
IdxRuaAtual = IdxRuaAtual,
PercentualOperacao = PercentualOperacao,
PercentualRuaAtual = PercentualRuaAtual,
QtdPontos = QtdPontos
};
}
}

View File

@ -52,6 +52,7 @@ namespace AgroBase.Models.Operadores
public TipoMovimentoDirecional tipo_movimento { get; set; }
public List<double[]> simulacao { get; set; }
public int heartbeat { get; set; }
public double latencia { get; set; }
}
public class ManagerWorkerMessageResponseModulosPendentesModel
@ -62,6 +63,7 @@ namespace AgroBase.Models.Operadores
public double saude { get; set; }
public List<string> motivos { get; set; }
public List<ManagerWorkerMessageResponseModulosPendentesSaudeModel> saude_individual { get; set; }
public List<ManagerWorkerMessageResponseModulosPendentesSaudeCondicoesModel> condicoes_operacionais { get; set; }
}
public class ManagerWorkerMessageResponseModulosPendentesSaudeModel

View File

@ -16,6 +16,7 @@ namespace AgroBase.Models.Operadores
public Dictionary<CameraFrameType, OAKCameraFrameModel> CameraFrames { get; set; } = new Dictionary<CameraFrameType, OAKCameraFrameModel>();
public VisualWorkerMessageAnaliseModel Analises { get; set; } = new VisualWorkerMessageAnaliseModel();
public VisualWorkerMessageIMUModel Imu { get; set; } = new VisualWorkerMessageIMUModel();
public StatusModulo Status { get; set; }
public VisualWorkerModel Clone()
{
@ -29,6 +30,7 @@ namespace AgroBase.Models.Operadores
Analises = Analises?.Clone(),
Imu = Imu?.Clone(),
UltimaMensagem = UltimaMensagem,
Status = Status
};
}
@ -41,6 +43,7 @@ namespace AgroBase.Models.Operadores
CameraFrames = new Dictionary<CameraFrameType, OAKCameraFrameModel>();
Analises = new VisualWorkerMessageAnaliseModel();
Imu = new VisualWorkerMessageIMUModel();
Status = StatusModulo.Desconectado;
}
}

View File

@ -1,7 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using static OpenTK.Graphics.OpenGL.GL;
namespace AgroBase.Models.Operadores
{

View File

@ -699,13 +699,15 @@ namespace AgroBase.Models
dictParams.TryGetValue("tipo_movimento_direcional", out var _movimento);
dictParams.TryGetValue("simulacao", out var _simulacao);
dictParams.TryGetValue("heartbeat", out var _heartbeat);
dictParams.TryGetValue("latencia", out var _latencia);
novoComando = new ManagerWorkerMessageResponseComandoModel()
{
angulo = Convert.ToDouble(_angulo),
percentual_velocidade = Convert.ToDouble(_velocidade),
tipo_movimento = (TipoMovimentoDirecional)Convert.ToInt32(_movimento),
simulacao = JsonConvert.DeserializeObject<List<double[]>>((_simulacao ?? "").ToString()),
heartbeat = Convert.ToInt32(_heartbeat)
heartbeat = Convert.ToInt32(_heartbeat),
latencia = Convert.ToDouble(_latencia.ToString().Replace(".", ",")),
};
}
catch (Exception ex)
@ -724,6 +726,7 @@ namespace AgroBase.Models
_Controle.TipoMovimento = novoComando.tipo_movimento;
_Controle.PercentualVelocidadeSP = novoComando.percentual_velocidade;
_Controle.SimulacaoMPC = novoComando.simulacao?.Select(x => new MPCSimulacaoModel() { latitude = x[0], longitude = x[1], orientacao = x[2] }).ToList() ?? new List<MPCSimulacaoModel>();
_Controle.Latencia = novoComando.latencia;
Variaveis.OperacaoEmAndamento.AtualizaInformacoesControleOperacao();
}
@ -1075,7 +1078,7 @@ namespace AgroBase.Models
double distancia = GPSUtils.DistanciaEntrePontos(ultimoPontoTrajetoria.Posicao, PontoInicial.Posicao);
bool distanciaMinima = distancia < DistanciaManobraEntreRuas;
if (distanciaMinima)
if (distanciaMinima && false)
{
int pontosAdicionr = Convert.ToInt32(CorredoresLarguras[idx] / (DistanciaEntrePontosCurva * _FatorLarguraCorredor));
@ -1149,7 +1152,8 @@ namespace AgroBase.Models
anguloProjetar2 += anguloAcrescentar;
anguloAcrescentar *= -1;
}
GPSModel UltimoPonto = GPSUtils.ProjetarPontoDeslocado(CorredorAtual.Last(), !ultimoCorredor ? (DistanciaProjecaoRua * 1.5) : (DistanciaProjecaoRua * 2.0), anguloProjetar2);
double percentualAcrescimoPontoAberturaCurva = 1.5;
GPSModel UltimoPonto = GPSUtils.ProjetarPontoDeslocado(CorredorAtual.Last(), !ultimoCorredor ? (DistanciaProjecaoRua * percentualAcrescimoPontoAberturaCurva) : (DistanciaProjecaoRua * 2.0), anguloProjetar2);
PontoTrajetoriaModel PontoFinal = new PontoTrajetoriaModel(TipoPontoRua.LigacaoSaida)
{

View File

@ -18,6 +18,8 @@ using Microsoft.Web.WebView2.WinForms;
using Microsoft.Web.WebView2.Core;
using System.Text.Json;
using AgroBase.Services.Operadores;
using MathNet.Numerics.Distributions;
using System.Diagnostics;
namespace AgroBase.Models
{
@ -113,7 +115,7 @@ namespace AgroBase.Models
public static double TensaoMaximaBateria { get; set; } = 42.0;
public static double CorrenteMaximaBateria { get; set; } = 20.0;
public static double PercentualTensaoBateriaMin { get; set; } = 25.0;
public static double PercentualReservatorioMin { get; set; } = 5.0;
public static double PercentualReservatorioMin { get; set; } = 8.0;
public static double PercentualToleranciaPressaoLinha { get; set; } = 0.15;
public static double ReducaoDirecional { get; set; } = 20.0;
public static int RPM_Min_Roda { get; set; } = 15;
@ -381,40 +383,41 @@ namespace AgroBase.Models
// Integração
double theta = anguloAtualDeg * Math.PI / 180.0;
double omega = -velocidadeMs * kappaEf; // rad/s
double omega = velocidadeMs * kappaEf; // rad/s
double dtheta = omega * tempoDelta;
theta += dtheta;
double moveHeading = theta; // direção de deslocamento
double heading = theta;
if (tipoMovimento == TipoMovimentoDirecional.MovimentoDiagonal)
moveHeading = theta + dfDeg * Math.PI / 180.0; // translada na direção do steering, sem girar
heading = theta + dfDeg * Math.PI / 180.0; // crab: desloca na direção do steering
double dx, dy;
if (tipoMovimento == TipoMovimentoDirecional.MovimentoDiagonal || Math.Abs(omega) < 1e-6)
{
dx = velocidadeMs * tempoDelta * Math.Cos(moveHeading);
dy = velocidadeMs * tempoDelta * Math.Sin(moveHeading);
}
else
{
dx = (velocidadeMs / omega) * (Math.Sin(theta + dtheta) - Math.Sin(theta));
dy = (velocidadeMs / omega) * (-Math.Cos(theta + dtheta) + Math.Cos(theta));
theta += dtheta; // atualiza yaw
}
// >>> mesma convenção do Python: 0=Norte
double dx = velocidadeMs * tempoDelta * Math.Sin(heading); // Leste(+)
double dy = velocidadeMs * tempoDelta * Math.Cos(heading); // Norte(+)
// Atualiza posição geográfica (aproximação local)
// geo
double R_earth = GPSUtils.RaioDaTerra;
double dLat = (dy / R_earth) * 180.0 / Math.PI;
double dLon = (dx / (R_earth * Math.Cos(pos.Latitude * Math.PI / 180.0))) * 180.0 / Math.PI;
return new GPSModel
GPSModel ultimaPosicao = GPSService.historicoPosicao.Peek();
GPSModel novaPosicao = new GPSModel
{
Momento = DateTime.Now,
Latitude = pos.Latitude + dLat,
Longitude = pos.Longitude + dLon,
OrientacaoReal = GPSUtils.NormalizarAngulo(theta * 180.0 / Math.PI),
Distancia = velocidadeMs * tempoDelta,
Velocidade = velocidadeMs
Velocidade = velocidadeMs,
Heartbeat = ultimaPosicao.Heartbeat,
TimestampOri = ultimaPosicao.TimestampOri.Clone(),
TimestampPos = ultimaPosicao.TimestampPos.Clone(),
};
novaPosicao.TimestampOri.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
novaPosicao.TimestampPos.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
return novaPosicao;
}
}

View File

@ -104,6 +104,20 @@ namespace AgroBase.Services
// Não é um número válido: ignora
}
}
public CanCommandFreqModel<T> Clone()
{
return new CanCommandFreqModel<T>()
{
_valor = _valor,
lidoEm = lidoEm,
frequencia = frequencia,
dt = dt,
ValorMaximo = ValorMaximo,
ValorMinimo = ValorMinimo
};
}
}
public enum CanMessagePosicaoDados
@ -143,7 +157,7 @@ namespace AgroBase.Services
public interface ICanService : IDisposable
{
void RegistrarHandler(byte endereco_rx, ICanMessageHandler handler);
bool Inicializar(SerialPort Porta = null);
bool Inicializar(SerialPort Porta = null, int BitRate = 250);
void AdicionarMensagemNaFila(T_Code Dispositivo, byte idTx, byte idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true);
void Fechar();
string _portName { get; set; }

View File

@ -16,6 +16,7 @@ namespace AgroBase.Services
public SerialPort _PortaCAN;
public string _portName { get; set; }
private int _baudRate;
private int _bitRate;
private object _lock = new object();
public bool IsConnected { get; private set; }
public bool Iniciado
@ -31,6 +32,7 @@ namespace AgroBase.Services
private ConcurrentQueue<string> FramesRecebidos = new ConcurrentQueue<string>();
private StringBuilder SerialCANBuffer = new StringBuilder();
private readonly ConcurrentDictionary<byte, ICanMessageHandler> _roteadores = new ConcurrentDictionary<byte, ICanMessageHandler>();
private readonly ConcurrentDictionary<byte, bool> _roteadoresRegistrados = new ConcurrentDictionary<byte, bool>();
private byte? _ultimoIdEnviado = null;
private List<CanMessage> Mensagens = new List<CanMessage>();
@ -50,14 +52,20 @@ namespace AgroBase.Services
public void RegistrarHandler(byte endereco_rx, ICanMessageHandler handler)
{
_roteadores[endereco_rx] = handler;
MostrarLog($"Roteador registrado: {endereco_rx.ToString("X")}", endereco_rx);
_roteadoresRegistrados.TryGetValue(endereco_rx, out bool registrado);
if (!registrado)
{
_roteadoresRegistrados.TryAdd(endereco_rx, true);
_roteadores.TryAdd(endereco_rx, handler);
MostrarLog($"Roteador registrado: {endereco_rx.ToString("X")}", endereco_rx);
}
}
public bool Inicializar(SerialPort Porta = null)
public bool Inicializar(SerialPort Porta = null, int BitRate = 250)
{
try
{
_bitRate = BitRate;
if (Porta == null && _PortaCAN == null)
{
return false;
@ -86,7 +94,7 @@ namespace AgroBase.Services
Thread.Sleep(50);
EnviarComando("Z0\r"); // desabilita timestamp
Thread.Sleep(50);
EnviarComando("S6\r"); // 500 kbps
EnviarComando(ComandoBitRate(_bitRate)); // 500 kbps
Thread.Sleep(50);
EnviarComando("O\r"); // open
Thread.Sleep(50);
@ -94,16 +102,19 @@ namespace AgroBase.Services
_PortaCAN.DiscardInBuffer();
_PortaCAN.DiscardOutBuffer();
UltimoRx = DateTime.Now;
UltimoTx = DateTime.Now;
tmrOuvirCAN?.Dispose();
tmrOuvirCAN = new AsyncTaskTimerModel("tmrOuvirCAN", OuvirCAN, 5);
tmrOuvirCAN = new AsyncTaskTimerModel("tmrOuvirCAN", OuvirCAN, 1);
tmrOuvirCAN.Start();
tmrEnviarCAN?.Dispose();
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 5);
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 3);
tmrEnviarCAN.Start();
tmrProcessarCAN?.Dispose();
tmrProcessarCAN = new AsyncTaskTimerModel("tmrProcessarCAN", ProcessarCAN, 10);
tmrProcessarCAN = new AsyncTaskTimerModel("tmrProcessarCAN", ProcessarCAN, 5);
tmrProcessarCAN.Start();
IsConnected = true;
@ -126,20 +137,22 @@ namespace AgroBase.Services
private async Task ProcessarCAN()
{
int mensagensParaProcessar = Math.Min(FramesRecebidos.Count, 10);
const int MAX_BATCH = 256; // teto de itens por tick
const int TIME_BUDGET_US = 1000; // ~1 ms de processamento
for (int i = 0; i < mensagensParaProcessar; i++)
var sw = System.Diagnostics.Stopwatch.StartNew();
int processed = 0;
while (processed < MAX_BATCH && FramesRecebidos.TryDequeue(out string frame))
{
if (FramesRecebidos.TryDequeue(out string frame))
{
ProcessarFrameCAN(frame);
}
else
{
// A fila ficou vazia antes de atingir o limite
ProcessarFrameCAN(frame);
processed++;
// Para não monopolizar o loop se entrar enxurrada de frames
if (sw.ElapsedTicks * (1_000_000.0 / System.Diagnostics.Stopwatch.Frequency) > TIME_BUDGET_US)
break;
}
}
LimparMensagensAntigas();
VerificaSaudeConexao();
}
@ -371,31 +384,36 @@ namespace AgroBase.Services
{
if (_PortaCAN?.IsOpen != true) return;
CanMessage msg = ObterProximaMensagem();
RefillTokens();
if (msg != null)
// pequeno burst controlado para reduzir latência:
const int MAX_BURST = 8;
int sent = 0;
while (_tokens >= 1 && sent < MAX_BURST)
{
string frame = CriarFrame(msg);
var msg = ObterProximaMensagem();
if (msg == null) break;
if (_ultimoIdEnviado.HasValue && msg.IdTx == _ultimoIdEnviado.Value)
{
await Task.Delay(5);
}
//if (!PodeEnviarAgora(msg)) break; // sem token “perdido”: sai do burst e tenta no próximo tick
if (msg.Dispositivo == T_Code.Mks)
{
await Task.Delay(10);
}
if (!EnviarComando(frame))
// Evite delays arbitrários em caminho “quente”
// Se precisar espaçar MKS, use cooldown por-ID (abaixo)
if (!EnviarComando(CriarFrame(msg)))
{
MostrarLog("Erro ao enviar frame", msg.IdTx);
break; // evita apertar ainda mais se o driver falhar
}
msg.Enviado = true;
msg.EnviadoEm = DateTime.Now;
_ultimoIdEnviado = msg.IdTx;
_tokens -= 1;
sent++;
}
if (sent == 0)
await Task.Delay(1); // cede o scheduler quando não tinha token/mensagem
}
private string CriarFrame(CanMessage msg)
@ -444,8 +462,11 @@ namespace AgroBase.Services
private void VerificaSaudeConexao()
{
if (IsConnected && (UltimoTx - UltimoRx).TotalSeconds > 2)
DateTime Agora = DateTime.Now;
if (IsConnected && ((Agora - UltimoTx).TotalSeconds > 10 || (Agora - UltimoRx).TotalSeconds > 10))
{
UltimoRx = Agora;
UltimoTx = Agora;
MostrarLog("Muito tempo sem receber novos dados, reiniciando conexao CAN...", 0);
try { _PortaCAN?.Close(); } catch { }
try
@ -453,10 +474,8 @@ namespace AgroBase.Services
_PortaCAN?.Open();
EnviarComando("C\r"); Thread.Sleep(50);
EnviarComando("Z0\r"); Thread.Sleep(50);
EnviarComando("S6\r"); Thread.Sleep(50);
EnviarComando(ComandoBitRate(_bitRate)); Thread.Sleep(50);
EnviarComando("O\r"); Thread.Sleep(50);
UltimoRx = DateTime.MinValue;
UltimoTx = DateTime.MinValue;
}
catch { }
}
@ -514,5 +533,52 @@ namespace AgroBase.Services
return $"{Id}_{data[0]}_{parametro}";
}
// alvo: ~200 fps (ajuste conforme bitrate/carga)
private const double TARGET_FPS = 200;
private double _tokens = TARGET_FPS;
private const double _capacity = TARGET_FPS;
private const double _refillPerMs = TARGET_FPS / 1000.0;
private DateTime _lastRefill = DateTime.UtcNow;
private void RefillTokens()
{
var now = DateTime.UtcNow;
var dtMs = (now - _lastRefill).TotalMilliseconds;
if (dtMs > 0)
{
_tokens = Math.Min(_capacity, _tokens + dtMs * _refillPerMs);
_lastRefill = now;
}
}
private string ComandoBitRate(int rate)
{
switch (rate)
{
case 10:
return "S0\r";
case 20:
return "S1\r";
case 50:
return "S2\r";
case 100:
return "S3\r";
case 125:
return "S4\r";
case 250:
return "S5\r";
case 500:
return "S6\r";
case 800:
return "S7\r";
case 1000:
return "S8\r";
default:
return "S6\r";
}
}
}
}

View File

@ -15,6 +15,7 @@ namespace AgroBase.Services
{
public WaveshareCANFDService Connector = new WaveshareCANFDService(0);
public string _portName { get; set; } = "CAN-FD";
private int _bitRate;
public bool IsConnected { get; private set; }
public bool Iniciado
{
@ -24,11 +25,11 @@ namespace AgroBase.Services
}
}
private AsyncTaskTimerModel tmrEnviarCAN;
private AsyncTaskTimerModel tmrOuvirCAN;
private AsyncTaskTimerModel tmrProcessarCAN;
private StringBuilder SerialCANBuffer = new StringBuilder();
private ConcurrentQueue<(uint, byte[])> FramesRecebidos = new ConcurrentQueue<(uint, byte[])>();
private readonly ConcurrentQueue<(uint id, byte[] data)> FramesRecebidos = new ConcurrentQueue<(uint, byte[])>();
private readonly ConcurrentDictionary<byte, ICanMessageHandler> _roteadores = new ConcurrentDictionary<byte, ICanMessageHandler>();
private readonly ConcurrentDictionary<byte, bool> _roteadoresRegistrados = new ConcurrentDictionary<byte, bool>();
private byte? _ultimoIdEnviado = null;
private List<CanMessage> MensagensPendentes = new List<CanMessage>();
private readonly object _LockMensagens = new object();
@ -46,23 +47,48 @@ namespace AgroBase.Services
public void RegistrarHandler(byte endereco_rx, ICanMessageHandler handler)
{
_roteadores[endereco_rx] = handler;
MostrarLog($"Roteador registrado: {endereco_rx.ToString("X")}", endereco_rx);
_roteadoresRegistrados.TryGetValue(endereco_rx, out bool registrado);
if (!registrado)
{
_roteadoresRegistrados.TryAdd(endereco_rx, true);
_roteadores.TryAdd(endereco_rx, handler);
MostrarLog($"Roteador registrado: {endereco_rx.ToString("X")}", endereco_rx);
}
}
public bool Inicializar(SerialPort Porta = null)
public bool Inicializar(SerialPort Porta = null, int BitRate = 250)
{
_bitRate = BitRate;
bool canIniciado = Connector.IsOpen;
if (!canIniciado)
{
canIniciado = Connector.Open((VCI_CAN_OBJ frame) =>
canIniciado = Connector.Open((VCI_CAN_OBJ[] frames) =>
{
byte[] dataRx = new byte[frame.DataLen];
Array.Copy(frame.Data, dataRx, frame.DataLen);
FramesRecebidos.Enqueue((frame.ID, dataRx));
UltimoRx = DateTime.Now;
}, samplePoint: 0.8, bitrateKbps: 250);
try
{
var now = DateTime.Now;
for (int i = 0; i < frames.Length; i++)
{
var f = frames[i];
var dlc = f.DataLen;
if (dlc < 0) dlc = 0; if (dlc > 8) dlc = 8;
// clone rápido (já vem deep do conector, mas garantimos)
var data = dlc > 0 ? (byte[])f.Data.Clone() : Array.Empty<byte>();
// nunca bloquear. se quiser cap, faça:
FramesRecebidos.Enqueue((f.ID, data));
while (FramesRecebidos.Count > 4096) // cap opcional
FramesRecebidos.TryDequeue(out _);
}
UltimoRx = now;
}
catch (Exception ex)
{
MostrarLog($"[CAN] ERRO no callback consumidor: {ex.Message}", 0);
}
}, bitrateKbps: _bitRate, samplePoint: 0.8);
}
else
{
@ -80,12 +106,15 @@ namespace AgroBase.Services
if (canIniciado || !IsConnected)
{
UltimoRx = DateTime.Now;
UltimoTx = DateTime.Now;
tmrEnviarCAN?.Dispose();
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 5);
tmrEnviarCAN = new AsyncTaskTimerModel("tmrEnviarCAN", EnviarCAN, 3);
tmrEnviarCAN.Start();
tmrProcessarCAN?.Dispose();
tmrProcessarCAN = new AsyncTaskTimerModel("tmrProcessarCAN", ProcessarCAN, 10);
tmrProcessarCAN = new AsyncTaskTimerModel("tmrProcessarCAN", ProcessarCAN, 5);
tmrProcessarCAN.Start();
IsConnected = true;
@ -96,20 +125,22 @@ namespace AgroBase.Services
private async Task ProcessarCAN()
{
int mensagensParaProcessar = Math.Min(FramesRecebidos.Count, 10);
const int MAX_BATCH = 256; // teto de itens por tick
const int TIME_BUDGET_US = 1000; // ~1 ms de processamento
for (int i = 0; i < mensagensParaProcessar; i++)
var sw = System.Diagnostics.Stopwatch.StartNew();
int processed = 0;
while (processed < MAX_BATCH && FramesRecebidos.TryDequeue(out var frame))
{
if (FramesRecebidos.TryDequeue(out (uint, byte[]) frame))
{
ProcessarFrameCAN(frame);
}
else
{
// A fila ficou vazia antes de atingir o limite
ProcessarFrameCAN(frame);
processed++;
// Para não monopolizar o loop se entrar enxurrada de frames
if (sw.ElapsedTicks * (1_000_000.0 / System.Diagnostics.Stopwatch.Frequency) > TIME_BUDGET_US)
break;
}
}
LimparMensagensAntigas();
VerificaSaudeConexao();
}
@ -125,10 +156,7 @@ namespace AgroBase.Services
{
MostrarLog($"Processando frame: {BitConverter.ToString(data)}", (byte)id);
if (_roteadores.TryGetValue((byte)id, out ICanMessageHandler handler))
{
}
_roteadores.TryGetValue((byte)id, out ICanMessageHandler handler);
T_Code dispositivo = handler?.Dispositivo ?? T_Code.Vzo;
byte fCodeRx = data[0];
@ -260,35 +288,56 @@ namespace AgroBase.Services
return proximaMsg;
}
private readonly ConcurrentDictionary<byte, DateTime> _cooldownPorId = new ConcurrentDictionary<byte, DateTime>();
private static readonly TimeSpan COOLDOWN_MKS = TimeSpan.FromMilliseconds(6);
private bool PodeEnviarAgora(CanMessage m)
{
// Exemplo: só MKS precisa de respiro
if (m.Dispositivo != T_Code.Mks) return true;
var now = DateTime.UtcNow;
var due = _cooldownPorId.GetOrAdd(m.IdTx, DateTime.MinValue);
if (now < due) return false;
_cooldownPorId[m.IdTx] = now + COOLDOWN_MKS;
return true;
}
private async Task EnviarCAN()
{
if (!Connector.IsOpen) return;
CanMessage msg = ObterProximaMensagem();
RefillTokens();
if (msg != null)
// pequeno burst controlado para reduzir latência:
const int MAX_BURST = 8;
int sent = 0;
while (_tokens >= 1 && sent < MAX_BURST)
{
string frame = CriarFrame(msg);
var msg = ObterProximaMensagem();
if (msg == null) break;
if (_ultimoIdEnviado.HasValue && msg.IdTx == _ultimoIdEnviado.Value)
{
await Task.Delay(5);
}
//if (!PodeEnviarAgora(msg)) break; // sem token “perdido”: sai do burst e tenta no próximo tick
if (msg.Dispositivo == T_Code.Mks)
{
await Task.Delay(10);
}
if (!EnviarComando(frame, msg))
// Evite delays arbitrários em caminho “quente”
// Se precisar espaçar MKS, use cooldown por-ID (abaixo)
if (!EnviarComando(CriarFrame(msg), msg))
{
MostrarLog("Erro ao enviar frame", msg.IdTx);
break; // evita apertar ainda mais se o driver falhar
}
msg.Enviado = true;
msg.EnviadoEm = DateTime.Now;
_ultimoIdEnviado = msg.IdTx;
_tokens -= 1;
sent++;
}
if (sent == 0)
await Task.Delay(1); // cede o scheduler quando não tinha token/mensagem
}
private string CriarFrame(CanMessage msg)
@ -333,7 +382,7 @@ namespace AgroBase.Services
.Where(x =>
(!x.ComResposta && x.Enviado) ||
(x.ComResposta && x.Respondido) ||
(x.ComResposta && x.Enviado && !x.Respondido && x.EnviadoEm < agora.AddSeconds(-1)) ||
(x.ComResposta && x.Enviado && !x.Respondido && x.EnviadoEm < agora.AddSeconds(-2)) ||
(x.Momento < agora.AddSeconds(-30))
)
.ToList();
@ -346,15 +395,16 @@ namespace AgroBase.Services
private void VerificaSaudeConexao()
{
if (IsConnected && (UltimoTx - UltimoRx).TotalSeconds > 10)
DateTime Agora = DateTime.Now;
if (IsConnected && ((Agora - UltimoTx).TotalSeconds > 10 || (Agora - UltimoRx).TotalSeconds > 10))
{
UltimoRx = Agora;
UltimoTx = Agora;
MostrarLog("Muito tempo sem receber novos dados, reiniciando conexao CAN...", 0);
try { Connector.Close(); } catch { }
try
{
Inicializar();
UltimoRx = DateTime.MinValue;
UltimoTx = DateTime.MinValue;
}
catch { }
}
@ -362,7 +412,6 @@ namespace AgroBase.Services
public void Fechar()
{
tmrOuvirCAN?.Dispose();
tmrEnviarCAN?.Dispose();
lock (_LockMensagens)
@ -371,7 +420,10 @@ namespace AgroBase.Services
}
SerialCANBuffer.Clear();
FramesRecebidos = new ConcurrentQueue<(uint, byte[])>();
while (FramesRecebidos.Count > 0)
{
FramesRecebidos.TryDequeue(out var frame);
}
_roteadores.Clear();
_ultimoIdEnviado = null;
@ -405,5 +457,26 @@ namespace AgroBase.Services
byte parametro = (data.Length > 1) ? data[1] : (byte)0x00;
return $"{Id}_{data[0]}_{parametro}";
}
// alvo: ~200 fps (ajuste conforme bitrate/carga)
private const double TARGET_FPS = 200;
private double _tokens = TARGET_FPS;
private const double _capacity = TARGET_FPS;
private const double _refillPerMs = TARGET_FPS / 1000.0;
private DateTime _lastRefill = DateTime.UtcNow;
private void RefillTokens()
{
var now = DateTime.UtcNow;
var dtMs = (now - _lastRefill).TotalMilliseconds;
if (dtMs > 0)
{
_tokens = Math.Min(_capacity, _tokens + dtMs * _refillPerMs);
_lastRefill = now;
}
}
}
}

View File

@ -2,6 +2,7 @@
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Ports;
@ -44,8 +45,9 @@ namespace AgroBase.Services
private static int rtk_timeout = 60;
private static int TempoMin_Ntrip = 10;
private static bool LoopRTK_Ntrip = false;
public static bool CorrecaoRTK_Ntrip = true;
public static bool CorrecaoRTK_Ntrip = false;
public static DateTime UltimoEnvioCorrecaoRTK = DateTime.MinValue;
private static AsyncTaskTimerModel tmrHeartbeat;
public static void AtualizarPortaCOM(SerialPort Porta)
{
@ -75,6 +77,9 @@ namespace AgroBase.Services
{
Task.Run(async () => await AplicarCorrecaoRTK_Ntrip());
}
tmrHeartbeat?.Dispose();
tmrHeartbeat = new AsyncTaskTimerModel("tmrHeartbeatGps", tmrHeartbeat_Tick, 500);
tmrHeartbeat.Start();
}
}
@ -103,7 +108,7 @@ namespace AgroBase.Services
}
}
private static async Task ConfigurarModuloRover(string porta_usb = "com3", string porta_entrada = "com2", int comprimento_antena = 130, int tolerancia_antena = 3)
private static async Task ConfigurarModuloRover(string porta_usb = "com3", string porta_entrada = "com2", int comprimento_antena = 130, int tolerancia_antena = 5)
{
string freq = (1.0 / TaxaAmostragemHz).ToString("0.0").Replace(",", ".");
string[] comandos = {
@ -126,6 +131,7 @@ namespace AgroBase.Services
// heading 2 antenas
$"config heading fixlength\r\n",
$"config heading tractor\r\n",
$"config heading length {comprimento_antena} {tolerancia_antena}\r\n",
// (se precisar, existe 'config heading offset <azim_off> <pitch_off>')
@ -262,7 +268,7 @@ namespace AgroBase.Services
AtualizarCoordenadasGPS();
}
// Coordenadas
else if (sentenca.StartsWith("$GNGGA"))
else if (sentenca.StartsWith("$GNGGA") || sentenca.StartsWith("$GLGGA"))
{
ProcessarGNGGA(sentenca);
AtualizarCoordenadasGPS();
@ -283,9 +289,10 @@ namespace AgroBase.Services
ProcessarGSV(sentenca);
}
// Orientação real
else if (sentenca.StartsWith("$GNTHS") || sentenca.StartsWith("$GPTHS"))
else if (sentenca.StartsWith("$GNTHS") || sentenca.StartsWith("$GPTHS") || sentenca.StartsWith("$GATHS"))
{
ProcessarGNTHS(sentenca);
//AtualizarCoordenadasGPS();
}
// GLL (GP/GN/GL/GA/BD)
else if (sentenca.Length > 6 && sentenca[3] == 'G' && sentenca[4] == 'L' && sentenca[5] == 'L')
@ -315,13 +322,17 @@ namespace AgroBase.Services
{
string[] parts = sentenca.Split(',');
PenultimaLeitura.Momento = UltimaLeitura.Momento;
PenultimaLeitura.TimestampPos = UltimaLeitura.TimestampPos.Clone();
PenultimaLeitura.Latitude = UltimaLeitura.Latitude;
PenultimaLeitura.Longitude = UltimaLeitura.Longitude;
PenultimaLeitura.Altitude = UltimaLeitura.Altitude;
PenultimaLeitura.PrecisaoHorizontal = UltimaLeitura.PrecisaoHorizontal;
PenultimaLeitura.DataHora = UltimaLeitura.DataHora;
PenultimaLeitura.NumeroSatelites.valor = UltimaLeitura.NumeroSatelites.valor;
PenultimaLeitura.NumeroSatelites = UltimaLeitura.NumeroSatelites;
UltimaLeitura.TimestampPos.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
UltimaLeitura.Momento = DateTime.Now;
if (parts[2] != "" && parts[3] != "")
{
UltimaLeitura.Latitude = GPSUtils.ConvertToDecimalDegrees(parts[2], parts[3], 2);
@ -330,7 +341,7 @@ namespace AgroBase.Services
{
UltimaLeitura.Longitude = GPSUtils.ConvertToDecimalDegrees(parts[4], parts[5], 3);
}
UltimaLeitura.NumeroSatelites.valor = int.TryParse(parts[7], out int numSat) ? numSat : 0;
UltimaLeitura.NumeroSatelites = int.TryParse(parts[7], out int numSat) ? numSat : 0;
UltimaLeitura.PrecisaoHorizontal = double.TryParse(parts[8], NumberStyles.Float, CultureInfo.InvariantCulture, out double hdop) ? hdop : 0;
UltimaLeitura.Altitude = double.TryParse(parts[9], NumberStyles.Float, CultureInfo.InvariantCulture, out double alt) ? alt : 0;
}
@ -384,23 +395,25 @@ namespace AgroBase.Services
//Console.WriteLine($"GNGGA: Hora={horaUTC}, Latitude={latitude}, Longitude={longitude}, Qualidade={qualidade}, Satélites={satelitesUsados}, HDOP={hdop}, Altitude={altitude}");
PenultimaLeitura.TimestampPos = UltimaLeitura.TimestampPos.Clone();
PenultimaLeitura.Momento = UltimaLeitura.Momento;
PenultimaLeitura.Latitude = UltimaLeitura.Latitude;
PenultimaLeitura.Longitude = UltimaLeitura.Longitude;
PenultimaLeitura.Altitude = UltimaLeitura.Altitude;
PenultimaLeitura.PrecisaoHorizontal = UltimaLeitura.PrecisaoHorizontal;
PenultimaLeitura.NumeroSatelites.valor = UltimaLeitura.NumeroSatelites.valor;
PenultimaLeitura.NumeroSatelites = UltimaLeitura.NumeroSatelites;
PenultimaLeitura.QualidadeFix = UltimaLeitura.QualidadeFix;
PenultimaLeitura.DataHora = UltimaLeitura.DataHora;
PenultimaLeitura.IdadeCorrecao = UltimaLeitura.IdadeCorrecao;
// Armazenar os valores na última leitura
UltimaLeitura.TimestampPos.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
UltimaLeitura.Momento = DateTime.Now;
UltimaLeitura.Latitude = latitude;
UltimaLeitura.Longitude = longitude;
UltimaLeitura.Altitude = altitude;
UltimaLeitura.PrecisaoHorizontal = precisao;
UltimaLeitura.NumeroSatelites.valor = nsatelites;
UltimaLeitura.NumeroSatelites = nsatelites;
UltimaLeitura.QualidadeFix = (TiposCorrecaoGPS)fix;
UltimaLeitura.IdadeCorrecao = string.IsNullOrEmpty(idadeCorrecaoRaw) ? -1 : idadeCorrecao;
@ -537,6 +550,7 @@ namespace AgroBase.Services
private static void ProcessarGNTHS(string sentenca)
{
//Console.WriteLine(sentenca);
// Remove o caractere de início '$' e divide os campos
var campos = sentenca.TrimStart('$').Split(',');
@ -558,10 +572,12 @@ namespace AgroBase.Services
string status = campos[2].Split('*')[0]; // Campo <Status>
string checksum = campos[2].Split('*')[1]; // Campo <Checksum>
PenultimaLeitura.TimestampOri = UltimaLeitura.TimestampOri.Clone();
PenultimaLeitura.Momento = UltimaLeitura.Momento;
PenultimaLeitura.OrientacaoReal = UltimaLeitura.OrientacaoReal;
PenultimaLeitura.TipoOrientacao = UltimaLeitura.TipoOrientacao;
UltimaLeitura.TimestampOri.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
UltimaLeitura.Momento = DateTime.Now;
UltimaLeitura.OrientacaoReal = headingTrue;
UltimaLeitura.TipoOrientacao = status;
@ -661,7 +677,7 @@ namespace AgroBase.Services
// Atualiza apenas o que faz sentido complementar
if (!double.IsInfinity(hdop) && !double.IsNaN(hdop)) UltimaLeitura.PrecisaoHorizontal = hdop;
if (satsUsados > 0) UltimaLeitura.NumeroSatelites.valor = Math.Max(UltimaLeitura.NumeroSatelites.valor, satsUsados);
if (satsUsados > 0) UltimaLeitura.NumeroSatelites = Math.Max(UltimaLeitura.NumeroSatelites, satsUsados);
// Se você quiser guardar os DOPs:
UltimaLeitura.PDOP = !double.IsInfinity(pdop) && !double.IsNaN(pdop) ? pdop : UltimaLeitura.PDOP;
@ -1083,7 +1099,7 @@ namespace AgroBase.Services
if (Variaveis.OperacaoEmAndamento.Simulando)
{
anguloFinal = UltimaLeitura.OrientacaoMovimento;
anguloFinal = UltimaLeitura.OrientacaoReal;
}
else if (gpsIniciado)
{
@ -1194,25 +1210,47 @@ namespace AgroBase.Services
return anguloUnificado;
}
private static async Task tmrHeartbeat_Tick()
{
AtualizaDadosRedis();
}
private static void AtualizaDadosRedis()
{
GPSModel posicaoAtual = Variaveis.OperacaoEmAndamento.Simulando ? historicoPosicao.Peek() : UltimaLeitura;
RedisService.AtualizarCampos(
RedisService.ModKey(T_Code.Gps),
("timestamp", UltimaLeitura.Momento),
("conectado", Iniciado),
("freq_base", TaxaAmostragemHz),
("lat", UltimaLeitura.Latitude),
("lon", UltimaLeitura.Longitude),
("theta", UltimaLeitura.AnguloCarroDefinido),
("fix", (int)UltimaLeitura.QualidadeFix),
("rtk", UltimaLeitura.IdadeCorrecao < rtk_timeout),
("hAcc", UltimaLeitura.PrecisaoCm),
("nSatelites", UltimaLeitura.NumeroSatelites.valor),
("freq", UltimaLeitura.NumeroSatelites.frequencia),
("age", UltimaLeitura.IdadeCorrecao),
("heartbeat", UltimaLeitura.Heartbeat)
("lat", posicaoAtual.Latitude),
("lon", posicaoAtual.Longitude),
("theta", posicaoAtual.AnguloCarroDefinido),
("fix", (int)posicaoAtual.QualidadeFix),
("rtk", posicaoAtual.IdadeCorrecao < rtk_timeout),
("hAcc", posicaoAtual.PrecisaoCm),
("nSatelites", posicaoAtual.NumeroSatelites),
("freq.posicao", posicaoAtual.TimestampPos.frequencia),
("freq.orientacao", posicaoAtual.TimestampOri.frequencia),
("latency.posicao", posicaoAtual.TimestampPos.dt),
("latency.orientacao", posicaoAtual.TimestampOri.dt),
("timestamp.posicao", posicaoAtual.TimestampPos.valor),
("timestamp.orientacao", posicaoAtual.TimestampOri.valor),
("age", posicaoAtual.IdadeCorrecao),
("heartbeat", posicaoAtual.Heartbeat)
);
}
public static Queue<GPSModel> historicoPosicao = new Queue<GPSModel>();
public static void AtualizarAtrasoPosicoes(int errosConsiderar = 0)
{
historicoPosicao.Enqueue(UltimaLeitura);
while (historicoPosicao.Count > 1 && (historicoPosicao.Count > errosConsiderar || errosConsiderar == 0))
{
historicoPosicao.Dequeue();
}
}
}
}

View File

@ -248,7 +248,7 @@ namespace AgroBase.Services
fix.AddRange(CanPrefix(DadosLoRaParse.GpsQualidade));
fix.Add((byte)(Gps.Inicializado ? 1 : 0)); // 1 byte
fix.Add((byte)Gps.QualidadeFix); // 1 byte
fix.Add((byte)Gps.NumeroSatelites.valor); // 1 byte
fix.Add((byte)Gps.NumeroSatelites); // 1 byte
fix.AddRange(CompactarDecimal(Gps.PrecisaoHorizontal)); // 2 bytes
List<byte> _lat = new List<byte>();

View File

@ -114,33 +114,44 @@ namespace AgroBase.Services.Operadores
return;
}
var _t = _Sensoriamento.Trajetoria;
PontoTrajetoriaModel pontoComparar = _t != null ? (_t.PontoAtual.Aproximando ? _t.PontoAtual : _t.ProximoPonto != null ? _t.ProximoPonto : _t.PontoAtual) : null;
int idx_p_alvo = _t != null ?
(pontoComparar.DistanciaAtual > 1.0 ? pontoComparar.idxPonto :
pontoComparar.idxPonto < _t.QtdPontos - 1 ? pontoComparar.idxPonto + 1 :
pontoComparar.idxPonto) : 0;
RedisService.AtualizarCampos(
CtxKey.DadosContexto,
("Trajetoria", _Sensoriamento.Trajetoria != null ? new
("Trajetoria", _t != null ? new
{
status = (int)_Sensoriamento.Trajetoria.StatusCarro,
manobrando = _Sensoriamento.Trajetoria.ManobrandoEntreRuas,
erro_angular = _Sensoriamento.Trajetoria.ErroOrientacaoAngularCombinado,
erro_angular_caminho = _Sensoriamento.Trajetoria.ErroOrientacaoAngularCaminho,
erro_angular_proximo_ponto = _Sensoriamento.Trajetoria.ErroOrientacaoAngularProximoPonto,
idx_proximo_ponto = _Sensoriamento.Trajetoria.ProximoPonto.idxPonto,
status = (int)_t.StatusCarro,
manobrando = _t.ManobrandoEntreRuas,
erro_angular = _t.ErroOrientacaoAngularCombinado,
erro_angular_caminho = _t.ErroOrientacaoAngularCaminho,
erro_angular_proximo_ponto = _t.ErroOrientacaoAngularProximoPonto,
idx_proximo_ponto = _t.ProximoPonto.idxPonto,
idx_ponto_alvo = idx_p_alvo,
CorredorAtual = new
{
concluido = _Sensoriamento.Trajetoria.CorredorAtual.Concluido,
dentro = _Sensoriamento.Trajetoria.CorredorAtual.Dentro,
ultimo = _Sensoriamento.Trajetoria.CorredorAtual.Ultimo,
largura = _Sensoriamento.Trajetoria.CorredorAtual.Largura,
progresso = _Sensoriamento.Trajetoria.CorredorAtual.Progresso,
dist_esq = _Sensoriamento.Trajetoria.DistanciaEsquerda,
dist_dir = _Sensoriamento.Trajetoria.DistanciaDireita,
angulo = _Sensoriamento.Trajetoria.AnguloCaminho,
concluido = _t.CorredorAtual.Concluido,
dentro = _t.CorredorAtual.Dentro,
ultimo = _t.CorredorAtual.Ultimo,
largura = _t.CorredorAtual.Largura,
progresso = _t.CorredorAtual.Progresso,
dist_esq = _t.DistanciaEsquerda,
dist_dir = _t.DistanciaDireita,
angulo = _t.AnguloMedioCorredor,
},
} : null
),
("Gerais", new
{
velocidade_ms = _Sensoriamento.Movimentacao?.VelocidadeMedia ?? 0,
//velocidade_ms = _Sensoriamento.Movimentacao?.VelocidadeMedia ?? 0,
velocidade_ms = FuncoesMatematicas.CalculaVelocidadeMsPercentual(Variaveis.OperacaoEmAndamento.Controle.PercentualVelocidadeSP),
ervas_no_radar = _Sensoriamento.Atuador?.ErvasNoRadar ?? false,
percentual_ervas_no_radar = _Sensoriamento.Atuador?.PercentualErvasNoRadar ?? 0,
percentual_reservatorio = _Sensoriamento.Atuador?.PercentualReservatorio ?? 0,
percentual_bateria = _Sensoriamento.Bateria?.PorcentagemBateria ?? 0,
}
)
);
@ -423,7 +434,7 @@ namespace AgroBase.Services.Operadores
int id = sensor.ID_Num;
string basePath = $"sensores.{tipo}.{id}";
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
var dados = sensor.ValoresLeituras
.Where(y => y.analise_health)
@ -462,7 +473,7 @@ namespace AgroBase.Services.Operadores
int id = bico.ID_Num;
string basePath = $"bicos.{tipo}.{id}";
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
var dados = bico.ValoresLeituras
.Where(y => y.analise_health)
@ -501,7 +512,7 @@ namespace AgroBase.Services.Operadores
int id = bomba.ID_Num;
string basePath = $"bombas.{tipo}.{id}";
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Sen), $"{basePath}.saude");
var saude = RedisService.GetField<object>(RedisService.ModKey(Enums.T_Code.Atu), $"{basePath}.saude");
var dados = bomba.ValoresLeituras
.Where(y => y.analise_health)

View File

@ -152,6 +152,8 @@ namespace AgroBase.Services.Operadores
catch { /* opcional: logar erro */ }
}
DadosLeitura.Status = HealthWorkerService.ModulosSaude.FirstOrDefault(x => x.modulo == Enums.T_Code.Snr)?.status ?? Enums.StatusModulo.Desconectado;
// VisualWorker
var json = RedisService.Get(CtxKey.DadosVisualWorker);
if (string.IsNullOrWhiteSpace(json)) return;

View File

@ -186,6 +186,7 @@ namespace AgroBase.Services.Operadores
RedisService.AtualizarCampos(
CtxKey.DadosWeedWorker,
("analise.ervas_no_radar", false),
("analise.percentual_ervas_no_radar", 0),
("analise.controle", new Dictionary<string, bool>(Variaveis.OperacaoEmAndamento.DispAtu.Dados.BicosPulverizadores.ToDictionary(x => x.Posicao.ToString(), x => x.ComandoAtuar))),
("analise.estatisticas", new WeedWorkerAnaliseEstatisticasModel())
);

View File

@ -145,7 +145,7 @@ namespace AgroBase.Services
public static void LimparDadosIniciais()
{
Set(CtxKey.DadosCameras, JsonConvert.SerializeObject(new Dictionary<string, CameraWorkerItemModel>()));
//Set(CtxKey.DadosCameras, JsonConvert.SerializeObject(new Dictionary<string, CameraWorkerItemModel>()));
AtualizarCampos(ModKey(T_Code.Gps), ("correcao_ntrip", false));
DefinirEquipamentoDesconectado();
}

View File

@ -240,7 +240,7 @@ namespace AgroBase.Services
await ProcurarDispositivosCAN();
var DispositivosRemovidos = dispositivosMapeados.Where(x => x.Tipo == TipoConexao.PortaCOM && !Portas.Contains(x.Endereco)).ToList();
var DispositivosRemovidos = dispositivosMapeados.Where(x => x.Tipo == TipoConexao.PortaCOM && (!Portas.Contains(x.Endereco) || x.Status == StatusModulo.Desconectado)).ToList();
foreach (var Dispositivo in DispositivosRemovidos)
{
DispositivosMapeados.Remove(Dispositivo);

View File

@ -1,5 +1,6 @@
using AgroBase.Models;
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
@ -9,15 +10,39 @@ public class WaveshareCANFDService
private const uint DEVICE_INDEX = 0;
private readonly uint channelIndex;
private Action<VCI_CAN_OBJ> callback;
private AsyncTaskTimerModel tmrLeitura;
private AsyncTaskTimerModel tmrWatchdogRx;
private AsyncTaskTimerModel tmrWatchdogSaude;
private readonly int delayTickTimer = 1;
private bool isOpen = false;
private bool _rxBusy = false;
private volatile bool _rxBusy = false;
private DateTime _ultimoRxTick = DateTime.MinValue;
private DateTime _ultimoRxFrame = DateTime.MinValue;
private int _rxRecoveryStep = 0; // 0=ok,1=soft,2=medium,3=hard
private DateTime _ultimoRecovery = DateTime.MinValue;
// buffer reutilizável para receber da DLL
private readonly VCI_CAN_OBJ[] _rxBuf = InitBuf(256); // 100 também funciona; 256 dá folga
private static VCI_CAN_OBJ[] InitBuf(int n)
{
var arr = new VCI_CAN_OBJ[n];
for (int i = 0; i < n; i++)
arr[i].Data = new byte[8];
return arr;
}
private FramesBatchHandler _onFramesBatch;
// versão do callback por lote com contagem
public delegate void FramesBatchHandler(VCI_CAN_OBJ[] frames);
public bool IsOpen => isOpen;
public int Canal => ((int)channelIndex + 1);
private int _lastKbps = 500;
private double _lastSp = 0.875;
public WaveshareCANFDService(uint channelIndex)
{
this.channelIndex = channelIndex;
@ -62,6 +87,8 @@ public class WaveshareCANFDService
[DllImport("ControlCANFD.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint VCI_StartCAN(uint DeviceType, uint DeviceInd, uint CANInd);
[DllImport("ControlCANFD.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint VCI_ClearBuffer(uint DeviceType, uint DeviceInd, uint CANInd);
[DllImport("ControlCANFD.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint VCI_Transmit(uint DeviceType, uint DeviceInd, uint CANInd, ref VCI_CAN_OBJ pSend, uint Len);
@ -72,38 +99,33 @@ public class WaveshareCANFDService
[DllImport("ControlCANFD.dll", CallingConvention = CallingConvention.StdCall)]
public static extern uint VCI_SetReference(uint DeviceType, uint DeviceInd, uint CANInd, uint RefType, ref uint pData);
public bool Open(Action<VCI_CAN_OBJ> onReceive, int bitrateKbps = 500, double samplePoint = 0.875)
public bool Open(FramesBatchHandler onFramesBatch, int bitrateKbps = 500, double samplePoint = 0.875)
{
callback = onReceive;
_onFramesBatch = onFramesBatch;
_lastKbps = bitrateKbps;
_lastSp = samplePoint;
if (VCI_OpenDevice(DEVICE_TYPE, DEVICE_INDEX, 0) != 1) return false;
if (VCI_OpenDevice(DEVICE_TYPE, DEVICE_INDEX, 0) != 1) return false;
// >>> SET BAUD PARA CAN-FD (ignora Timing0/1)
uint baudCode = MapToFdBaudCode(bitrateKbps);
if (VCI_SetReference(DEVICE_TYPE, DEVICE_INDEX, channelIndex, 0 /*RefType: baud*/, ref baudCode) != 1)
return false;
// o resto pode ficar igual; Timing0/1 serão ignorados pela DLL FD, então ponha 0 para clareza
var config = new VCI_INIT_CONFIG
{
AccCode = 0x00000000,
AccMask = 0xFFFFFFFF, // FD: máscara é ignorada segundo manual; sem problemas
Reserved = 0,
Filter = 0, // filtro padrão
Timing0 = 0, // ignorado em FD
Timing1 = 0, // ignorado em FD
Mode = 0
};
if (VCI_InitCAN(DEVICE_TYPE, DEVICE_INDEX, channelIndex, ref config) != 1) return false;
if (VCI_StartCAN(DEVICE_TYPE, DEVICE_INDEX, channelIndex) != 1) return false;
if (!InitAndStartCAN()) return false;
tmrLeitura?.Dispose();
tmrLeitura = new AsyncTaskTimerModel($"tmrLeitura_CAN_{Canal}", tmrLeitura_Tick, delayTickTimer);
tmrLeitura.Start();
tmrWatchdogRx?.Dispose();
tmrWatchdogRx = new AsyncTaskTimerModel($"tmrWatchdogRx_CAN_{Canal}", WatchdogRx, 500);
tmrWatchdogRx.Start();
tmrWatchdogSaude?.Dispose();
tmrWatchdogSaude = new AsyncTaskTimerModel($"tmrWatchdogSaude_CAN_{Canal}", WatchdogSaude, 100);
tmrWatchdogSaude.Start();
isOpen = true;
Console.WriteLine($"[CAN-FD] Canal {Canal} aberto @ {bitrateKbps} kbps, SP≈{samplePoint * 100:0.#}%");
return true;
@ -154,27 +176,56 @@ public class WaveshareCANFDService
_rxBusy = true;
try
{
// (re)aloca buffer local por tick para evitar lixo compartilhado
var local = new VCI_CAN_OBJ[100];
for (int i = 0; i < local.Length; i++)
local[i].Data = new byte[8];
_ultimoRxTick = DateTime.Now;
int emptyReads = 0;
var count = VCI_Receive(DEVICE_TYPE, DEVICE_INDEX, channelIndex, local, 100, 100);
if (count > 0)
const int MAX_PER_TICK = 512;
var outList = new List<VCI_CAN_OBJ>(Math.Min(MAX_PER_TICK, _rxBuf.Length));
int total = 0;
while (total < MAX_PER_TICK)
{
for (int i = 0; i < count; i++)
int got = (int)VCI_Receive(DEVICE_TYPE, DEVICE_INDEX, channelIndex, _rxBuf, (uint)_rxBuf.Length, 0);
if (got == 0)
{
var frame = local[i];
var data = new byte[frame.DataLen];
Array.Copy(frame.Data, data, frame.DataLen);
emptyReads++;
break; // sem dados agora; libera CPU
}
// 🔧 substitui o Data pelo array "desanexado" do buffer
frame.Data = data;
// chegou algo
_ultimoRxFrame = DateTime.Now;
_rxRecoveryStep = 0; // reset da escada de recovery, está saudável
// entrega frame "isolado" ao callback
callback?.Invoke(frame);
for (int i = 0; i < got && total < MAX_PER_TICK; i++, total++)
{
var src = _rxBuf[i];
var dlc = src.DataLen;
if (dlc < 0) dlc = 0;
if (dlc > 8) dlc = 8;
var dst = src;
var data = dlc > 0 ? new byte[dlc] : Array.Empty<byte>();
if (dlc > 0) Array.Copy(src.Data, data, dlc);
dst.Data = data;
outList.Add(dst);
}
}
if (outList.Count > 0)
{
try { _onFramesBatch?.Invoke(outList.ToArray()); }
catch (Exception ex)
{
// não deixe matar o RX
Console.WriteLine($"[CAN-FD] ERRO no callback de frames: {ex.Message}");
}
}
}
catch (Exception ex)
{
Console.WriteLine($"[CAN-FD] ERRO no tmrLeitura_Tick: {ex}");
// opcional: marcar para reabrir canal no próximo health
}
finally
{
@ -182,12 +233,111 @@ public class WaveshareCANFDService
}
}
private async Task WatchdogRx()
{
var semTick = (DateTime.Now - _ultimoRxTick).TotalSeconds;
if (semTick > 1)
{
Console.WriteLine("[CAN-FD] Watchdog: RX sem tick há >1s. Reiniciando leitura...");
try
{ // reinicialize apenas a tarefa/timer de leitura
// (seu AsyncTaskTimerModel deve permitir Stop/Start)
tmrLeitura?.Stop();
_rxBusy = false; // forçar liberação
tmrLeitura?.Start();
}
catch { }
}
}
private async Task WatchdogSaude()
{
var now = DateTime.Now;
// 2) watchdog de SAÚDE DO RX (frames reais)
var semRx = (now - _ultimoRxFrame).TotalMilliseconds;
// thresholds (ajuste conforme tráfego)
if (semRx > 1000 && _rxRecoveryStep == 0 && (now - _ultimoRecovery).TotalMilliseconds > 300)
{
_rxRecoveryStep = 1; _ultimoRecovery = now;
Console.WriteLine("[CAN-FD] RX sem frames >1s. Soft-kick: ClearBuffer + StartCAN.");
try
{
VCI_ClearBuffer(DEVICE_TYPE, DEVICE_INDEX, channelIndex);
VCI_StartCAN(DEVICE_TYPE, DEVICE_INDEX, channelIndex);
}
catch (Exception ex) { Console.WriteLine($"[CAN-FD] Soft-kick falhou: {ex.Message}"); }
return;
}
if (semRx > 2000 && _rxRecoveryStep <= 1 && (now - _ultimoRecovery).TotalMilliseconds > 500)
{
_rxRecoveryStep = 2; _ultimoRecovery = now;
Console.WriteLine("[CAN-FD] RX sem frames >2s. Medium reset: ResetCAN + InitCAN + StartCAN.");
try
{
if (!InitAndStartCAN()) Console.WriteLine("[CAN-FD] Medium reset: Init/Start falhou!");
}
catch (Exception ex) { Console.WriteLine($"[CAN-FD] Medium reset falhou: {ex.Message}"); }
return;
}
if (semRx > 5000 && _rxRecoveryStep <= 2 && (now - _ultimoRecovery).TotalMilliseconds > 1000)
{
_rxRecoveryStep = 3; _ultimoRecovery = now;
Console.WriteLine("[CAN-FD] RX sem frames >5s. HARD reopen: CloseDevice/OpenDevice/Init/Start.");
try
{
HardReopen(); // feche e reabra igual ao seu Open()
}
catch (Exception ex) { Console.WriteLine($"[CAN-FD] Hard reopen falhou: {ex.Message}"); }
return;
}
}
private bool InitAndStartCAN()
{
// reconfigura baud FD
uint baud = MapToFdBaudCode(_lastKbps);
if (VCI_SetReference(DEVICE_TYPE, DEVICE_INDEX, channelIndex, 0, ref baud) != 1)
return false;
var cfg = new VCI_INIT_CONFIG
{
AccCode = 0,
AccMask = 0xFFFFFFFF,
Reserved = 0,
Filter = 0,
Timing0 = 0,
Timing1 = 0,
Mode = 0
};
if (VCI_InitCAN(DEVICE_TYPE, DEVICE_INDEX, channelIndex, ref cfg) != 1)
return false;
return VCI_StartCAN(DEVICE_TYPE, DEVICE_INDEX, channelIndex) == 1;
}
private bool HardReopen()
{
try { VCI_CloseDevice(DEVICE_TYPE, DEVICE_INDEX); } catch { }
// reabre device
if (VCI_OpenDevice(DEVICE_TYPE, DEVICE_INDEX, 0) != 1) return false;
// reinit canal com os últimos parâmetros
return InitAndStartCAN();
}
public void Close()
{
if (!isOpen) return;
tmrLeitura?.Dispose();
tmrWatchdogRx?.Dispose();
tmrWatchdogSaude?.Dispose();
VCI_CloseDevice(DEVICE_TYPE, DEVICE_INDEX);
isOpen = false;
Console.WriteLine($"[CAN-FD] Canal {Canal} fechado");
}
}

View File

@ -1,12 +1,5 @@
{
"epochs": [ {
"calculation_time": "13398455528662484",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13399153521058315",
"config_version": 0,
"model_version": "0",
@ -27,7 +20,14 @@
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
}, {
"calculation_time": "13401214743883964",
"config_version": 0,
"model_version": "0",
"padded_top_topics_start_index": 0,
"taxonomy_version": 0,
"top_topics_and_observing_domains": [ ]
} ],
"hex_encoded_hmac_key": "40F346D3248C3AFDF2BEE1FE496DBD32F7CED6E5AE98B881ABC421AA7E7B5642",
"next_scheduled_calculation_time": "13401199940855097"
"next_scheduled_calculation_time": "13401819543884442"
}

View File

@ -1,3 +1,3 @@
2025/08/25-17:06:11.375 d6c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/25-17:06:11.381 d6c Recovering log #3
2025/08/25-17:06:11.384 d6c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/09/02-08:53:17.510 23f4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/09/02-08:53:17.517 23f4 Recovering log #3
2025/09/02-08:53:17.520 23f4 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/25-17:05:24.176 da4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/08/25-17:05:24.181 da4 Recovering log #3
2025/08/25-17:05:24.184 da4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/000003.log
2025/09/02-08:47:11.765 3b04 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Local Storage\leveldb/MANIFEST-000001
2025/09/02-08:47:11.773 3b04 Recovering log #3
2025/09/02-08:47:11.777 3b04 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":"13400712371911326","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":42895},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:60c:f100:fd1a:acec:81f9:ab34","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":"13401373997903613","port":443,"protocol_str":"quic"}],"anonymization":["DAAAAAcAAABmaWxlOi8vAA==",false,0],"network_stats":{"srtt":7779},"server":"https://tile.openstreetmap.org","supports_spdy":true}],"supports_quic":{"address":"2804:d78:6a8:5d00:c40:d68b:d066:c8cf","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":1787685962.878582,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1756149962.878585}],"version":2}
{"sts":[{"expiry":1788349633.320866,"host":"bWGAftl61rqoc0YzqPncsLvQQh/iC2Bdp3ejUeGC83w=","mode":"force-https","sts_include_subdomains":true,"sts_observed":1756813633.320875}],"version":2}

File diff suppressed because one or more lines are too long

View File

@ -1,3 +1,3 @@
2025/08/25-17:07:32.655 d6c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/25-17:07:32.656 d6c Recovering log #3
2025/08/25-17:07:32.661 d6c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/09/02-09:10:40.010 23f4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/09/02-09:10:40.012 23f4 Recovering log #3
2025/09/02-09:10:40.015 23f4 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/25-17:05:59.944 da4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/08/25-17:05:59.945 da4 Recovering log #3
2025/08/25-17:05:59.949 da4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/000003.log
2025/09/02-08:53:00.810 3b04 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Session Storage/MANIFEST-000001
2025/09/02-08:53:00.812 3b04 Recovering log #3
2025/09/02-08:53:00.817 3b04 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/25-17:06:11.307 51e4 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/25-17:06:11.309 51e4 Recovering log #7
2025/08/25-17:06:11.309 51e4 Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/09/02-08:53:17.431 67f8 Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/09/02-08:53:17.433 67f8 Recovering log #7
2025/09/02-08:53:17.433 67f8 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/25-17:05:24.104 698c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/08/25-17:05:24.106 698c Recovering log #7
2025/08/25-17:05:24.106 698c Reusing old log C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/000007.log
2025/09/02-08:47:11.681 4a8c Reusing MANIFEST C:\ZendionInc\agrobot_base\AgroBase\AgroBase\bin\x64\Debug\AgroBase.exe.WebView2\EBWebView\Default\Site Characteristics Database/MANIFEST-000001
2025/09/02-08:47:11.683 4a8c Recovering log #7
2025/09/02-08:47:11.683 4a8c 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 +1 @@
139.0.3405.111
139.0.3405.125

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

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,221 @@
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"Id":"1","Name":"25_08_2025_16_29_49_MapaGPS","Length":45.0669749673391,"Dist1":0.0,"Dist2":0.0},"geometry":{"id":null,"type":"LineString","coordinates":[[-47.395187119333336,-22.172544759833333],[-47.3951871205,-22.172544750333333],[-47.395187123666666,-22.17254474],[-47.395187119333336,-22.172544773],[-47.39518710983333,-22.172544767666668],[-47.3951870925,-22.172544773333332],[-47.3951871135,-22.1725447695],[-47.395187109,-22.172544759],[-47.395187124333333,-22.172544775333332],[-47.395187104,-22.172544765333335],[-47.395187114,-22.17254478],[-47.395187119,-22.1725447645],[-47.395187111166663,-22.172544784],[-47.39518645,-22.172549661833333],[-47.39518665166667,-22.172551579833332],[-47.395191283666669,-22.172556917166666],[-47.395194691166665,-22.172557394166667],[-47.395197058166666,-22.1725579445],[-47.39520246033333,-22.172560708833334],[-47.395205430833336,-22.172563393833332],[-47.3952061165,-22.172569330666665],[-47.395204892333332,-22.172570874],[-47.395202153,-22.172576186166665],[-47.3952015505,-22.172579329333335],[-47.395201410666665,-22.172581696333335],[-47.395201276666668,-22.172586928833333],[-47.395200646,-22.172589796],[-47.395201421833335,-22.172594937166668],[-47.395201952333331,-22.172597846666665],[-47.3952035565,-22.172603020166665],[-47.395204372,-22.172605900833332],[-47.3952052305,-22.172607986333333],[-47.3952063055,-22.1726129495],[-47.395207043666666,-22.172615775833332],[-47.3952076955,-22.172621029],[-47.395208046166665,-22.17262326],[-47.395208260333334,-22.172629409],[-47.3952083685,-22.172631642666666],[-47.3952084535,-22.1726344725],[-47.3952091315,-22.172639615666668],[-47.39520933033333,-22.172642971166667],[-47.395209947833337,-22.172648214166667],[-47.395210410333334,-22.172650344333334],[-47.395210539166669,-22.1726554185],[-47.395211094833336,-22.1726582835],[-47.3952113955,-22.172663612],[-47.395211805333332,-22.172665979],[-47.395212764166665,-22.172671918666666],[-47.395213434666665,-22.172674605333334],[-47.3952138935,-22.172676825166668],[-47.395214831666664,-22.172681917166667],[-47.395215301166665,-22.172684613166666],[-47.395216344666665,-22.172689754166665],[-47.395216705333333,-22.172692151666666],[-47.3952172425,-22.172695494833334],[-47.395217986666665,-22.172701318666668],[-47.395218297333336,-22.1727032815],[-47.395218085,-22.1727083455],[-47.395217370666664,-22.172711332],[-47.395221153,-22.172715260166665],[-47.395222875166667,-22.172716722166665],[-47.3952262205,-22.172721511833334],[-47.395226792166667,-22.1727251945],[-47.3952264485,-22.172728077666665],[-47.395221607833335,-22.1727346585],[-47.395217703833332,-22.172736784333335],[-47.395209911666669,-22.172737078833332],[-47.395206829833334,-22.172735761166667],[-47.395201955833336,-22.172729872333335],[-47.395201161833334,-22.172725887666665],[-47.395202950666665,-22.172722859166665],[-47.395206459,-22.1727181785],[-47.395209633,-22.172715328833334],[-47.395214860166668,-22.172716129],[-47.395217123166667,-22.172716103333332],[-47.395223301166666,-22.172714955333333],[-47.395226070333337,-22.172712866333335],[-47.395227983666665,-22.172710646166667],[-47.395229317,-22.172702757833335],[-47.395227237833332,-22.172699078333334],[-47.3952201425,-22.172694863666667],[-47.395216778,-22.17269502],[-47.395211916166666,-22.172697834666668],[-47.3952096905,-22.172699738166667],[-47.395207431,-22.172700759833333],[-47.395201152333335,-22.1727031105],[-47.395196381333335,-22.172702974833335],[-47.39519378,-22.172702100166667],[-47.395188575333336,-22.172697375166667],[-47.395187179,-22.172693494333334],[-47.3951886055,-22.172686481833335],[-47.395190795666664,-22.1726839655],[-47.395197941333336,-22.1726811135],[-47.395201979333336,-22.172681057],[-47.395203451833332,-22.172682550166666],[-47.395207649166665,-22.172685885666667],[-47.3952109425,-22.17268669],[-47.3952134685,-22.1726865585],[-47.395219690666664,-22.172683575],[-47.395223081166669,-22.1726776265],[-47.3952225125,-22.172672815666665],[-47.395220510166666,-22.172669935333332],[-47.395214666666668,-22.172667577333332],[-47.395210875833335,-22.172667172],[-47.395205466,-22.172665870166668],[-47.3952020855,-22.172664815333334],[-47.395198556666664,-22.172663721],[-47.395193420333335,-22.1726622985],[-47.395193420333335,-22.1726622985]]}}]}
{
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": {
"Id": "1",
"Name": "CidadeJardimTerreno2",
"Length": 14.558011415731592,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"coordinates": [
[
-47.395205344,
-22.172559531333334
],
[
-47.395212610166666,
-22.172614638833334
],
[
-47.395219157,
-22.172656417833334
],
[
-47.395223544833335,
-22.1726892105
],
[
-47.39522414098443,
-22.17269369161165
],
[
-47.395225326538004,
-22.172702654611555
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "2",
"Name": "CidadeJardimTerreno2",
"Length": 14.771318274761821,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"coordinates": [
[
-47.39521090233333,
-22.172708800833334
],
[
-47.395206943666665,
-22.172679811
],
[
-47.395202420666664,
-22.1726491015
],
[
-47.395198865666664,
-22.172615782833333
],
[
-47.395193255833334,
-22.172577132833332
],
[
-47.395192610024395,
-22.17257265769541
],
[
-47.395191325698136,
-22.172563706509337
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "3",
"Name": "CidadeJardimTerreno2",
"Length": 14.827915524386164,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"coordinates": [
[
-47.3951762925,
-22.172561603
],
[
-47.395180824166665,
-22.172591686166665
],
[
-47.395185745333336,
-22.172623080166666
],
[
-47.395190433,
-22.172656367166667
],
[
-47.395195199,
-22.172693641833334
],
[
-47.39519576904436,
-22.172698125891035
],
[
-47.39519690267159,
-22.172707094716973
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "4",
"Name": "CidadeJardimTerreno2",
"Length": 14.785159385903514,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"coordinates": [
[
-47.39518293216667,
-22.1727127985
],
[
-47.39517819266667,
-22.172680514833335
],
[
-47.3951732595,
-22.172646752833334
],
[
-47.39516880516667,
-22.1726149155
],
[
-47.39516381233334,
-22.172581167166665
],
[
-47.39516315429932,
-22.172576693573966
],
[
-47.39516184565584,
-22.172567745443878
]
]
}
},
{
"type": "Feature",
"properties": {
"Id": "5",
"Name": "CidadeJardimTerreno2",
"Length": 14.80117692191233,
"Dist1": 14.558011415731592,
"Dist2": 0.0
},
"geometry": {
"id": null,
"type": "LineString",
"coordinates": [
[
-47.395147317833334,
-22.172566031
],
[
-47.395151503166666,
-22.172595452333333
],
[
-47.3951561855,
-22.172628011166665
],
[
-47.39516159866667,
-22.172663757333332
],
[
-47.39516672716667,
-22.172697770833334
],
[
-47.395167397572706,
-22.172702242832194
],
[
-47.39516873082615,
-22.17271118781009
]
]
}
}
]
}

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_b2cfb7c0b8cd21733288866dbc6da89b {
#map_b38d811fa61af8b65eff35dfc9035850 {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,14 +54,14 @@
<body>
<div class="folium-map" id="map_b2cfb7c0b8cd21733288866dbc6da89b" ></div>
<div class="folium-map" id="map_b38d811fa61af8b65eff35dfc9035850" ></div>
</body>
<script>
var map_b2cfb7c0b8cd21733288866dbc6da89b = L.map(
"map_b2cfb7c0b8cd21733288866dbc6da89b",
var map_b38d811fa61af8b65eff35dfc9035850 = L.map(
"map_b38d811fa61af8b65eff35dfc9035850",
{
center: [0.0, 0.0],
crs: L.CRS.EPSG3857,
@ -78,7 +78,7 @@
var tile_layer_bab172d3f76c66b031f76d6ae9baaaaf = L.tileLayer(
var tile_layer_ad727f4f6e04926dedef75d899276bfb = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_bab172d3f76c66b031f76d6ae9baaaaf.addTo(map_b2cfb7c0b8cd21733288866dbc6da89b);
tile_layer_ad727f4f6e04926dedef75d899276bfb.addTo(map_b38d811fa61af8b65eff35dfc9035850);
</script>
@ -116,7 +116,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_b2cfb7c0b8cd21733288866dbc6da89b);
trajeto_json.addTo(map_b38d811fa61af8b65eff35dfc9035850);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -179,9 +179,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_b2cfb7c0b8cd21733288866dbc6da89b);
}).addTo(map_b38d811fa61af8b65eff35dfc9035850);
var marcadorBase = L.marker([0, 0], {}).addTo(map_b2cfb7c0b8cd21733288866dbc6da89b);
var marcadorBase = L.marker([0, 0], {}).addTo(map_b38d811fa61af8b65eff35dfc9035850);
var icon = L.AwesomeMarkers.icon(
{"extraClasses": "fa-rotate-0", "icon": "info-sign", "iconColor": "white", "markerColor": "red", "prefix": "glyphicon"}
);
@ -246,7 +246,7 @@
}
if (foco) {
map_b2cfb7c0b8cd21733288866dbc6da89b.setView(novaPosicao, map_b2cfb7c0b8cd21733288866dbc6da89b.getZoom());
map_b38d811fa61af8b65eff35dfc9035850.setView(novaPosicao, map_b38d811fa61af8b65eff35dfc9035850.getZoom());
}
}
@ -268,7 +268,7 @@
marcadorDinamico.setRotationAngle(angulo);
adicionarCoordenada("Tj", [novaLongitude, novaLatitude]);
map_b2cfb7c0b8cd21733288866dbc6da89b.setView(novaPosicao, map_b2cfb7c0b8cd21733288866dbc6da89b.getZoom());*/
map_b38d811fa61af8b65eff35dfc9035850.setView(novaPosicao, map_b38d811fa61af8b65eff35dfc9035850.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_b21baaed2057e8bbe47af17d68ecedd6 {
#map_21ca758b860567b95d146fc0853c971f {
position: relative;
width: 100.0%;
height: 100.0%;
@ -54,16 +54,16 @@
<body>
<div class="folium-map" id="map_b21baaed2057e8bbe47af17d68ecedd6" ></div>
<div class="folium-map" id="map_21ca758b860567b95d146fc0853c971f" ></div>
</body>
<script>
var map_b21baaed2057e8bbe47af17d68ecedd6 = L.map(
"map_b21baaed2057e8bbe47af17d68ecedd6",
var map_21ca758b860567b95d146fc0853c971f = L.map(
"map_21ca758b860567b95d146fc0853c971f",
{
center: [-22.172640909416664, -47.3952078835],
center: [-22.172636164916668, -47.395186322185666],
crs: L.CRS.EPSG3857,
...{
"zoom": 12,
@ -78,7 +78,7 @@
var tile_layer_faa254f73950244990d85d26fa7337d6 = L.tileLayer(
var tile_layer_aa19b16e8f9ad64dd51817e34341c533 = L.tileLayer(
"https://tile.openstreetmap.org/{z}/{x}/{y}.png",
{
"minZoom": 0,
@ -95,7 +95,7 @@
);
tile_layer_faa254f73950244990d85d26fa7337d6.addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
tile_layer_aa19b16e8f9ad64dd51817e34341c533.addTo(map_21ca758b860567b95d146fc0853c971f);
@ -111,7 +111,7 @@
}*/
});
}
function geo_json_99cfc632ca5e92adc9931d372fcc1b88_onEachFeature(feature, layer) {
function geo_json_40d1c4f07ee5691fa37b3e9a1cd24536_onEachFeature(feature, layer) {
layer.on({
@ -148,23 +148,23 @@
}*/
});
};
var geo_json_99cfc632ca5e92adc9931d372fcc1b88 = L.geoJson(null, {
onEachFeature: geo_json_99cfc632ca5e92adc9931d372fcc1b88_onEachFeature,
var geo_json_40d1c4f07ee5691fa37b3e9a1cd24536 = L.geoJson(null, {
onEachFeature: geo_json_40d1c4f07ee5691fa37b3e9a1cd24536_onEachFeature,
...{
}
});
function geo_json_99cfc632ca5e92adc9931d372fcc1b88_add (data) {
geo_json_99cfc632ca5e92adc9931d372fcc1b88
function geo_json_40d1c4f07ee5691fa37b3e9a1cd24536_add (data) {
geo_json_40d1c4f07ee5691fa37b3e9a1cd24536
.addData(data);
}
geo_json_99cfc632ca5e92adc9931d372fcc1b88_add({"features": [{"geometry": {"coordinates": [[-47.395187119333336, -22.172544759833333], [-47.3951871205, -22.172544750333333], [-47.395187123666666, -22.17254474], [-47.395187119333336, -22.172544773], [-47.39518710983333, -22.17254476766667], [-47.3951870925, -22.172544773333332], [-47.3951871135, -22.1725447695], [-47.395187109, -22.172544759], [-47.39518712433333, -22.172544775333332], [-47.395187104, -22.172544765333335], [-47.395187114, -22.17254478], [-47.395187119, -22.1725447645], [-47.39518711116666, -22.172544784], [-47.39518645, -22.172549661833333], [-47.39518665166667, -22.172551579833332], [-47.39519128366667, -22.172556917166666], [-47.395194691166665, -22.172557394166667], [-47.395197058166666, -22.1725579445], [-47.39520246033333, -22.172560708833334], [-47.395205430833336, -22.172563393833332], [-47.3952061165, -22.172569330666665], [-47.39520489233333, -22.172570874], [-47.395202153, -22.172576186166665], [-47.3952015505, -22.172579329333335], [-47.395201410666665, -22.172581696333335], [-47.39520127666667, -22.172586928833333], [-47.395200646, -22.172589796], [-47.395201421833335, -22.172594937166668], [-47.39520195233333, -22.172597846666665], [-47.3952035565, -22.172603020166665], [-47.395204372, -22.172605900833332], [-47.3952052305, -22.172607986333333], [-47.3952063055, -22.1726129495], [-47.395207043666666, -22.172615775833332], [-47.3952076955, -22.172621029], [-47.395208046166665, -22.17262326], [-47.395208260333334, -22.172629409], [-47.3952083685, -22.172631642666666], [-47.3952084535, -22.1726344725], [-47.3952091315, -22.172639615666668], [-47.39520933033333, -22.172642971166667], [-47.39520994783334, -22.172648214166667], [-47.395210410333334, -22.172650344333334], [-47.39521053916667, -22.1726554185], [-47.395211094833336, -22.1726582835], [-47.3952113955, -22.172663612], [-47.39521180533333, -22.172665979], [-47.395212764166665, -22.172671918666666], [-47.395213434666665, -22.172674605333334], [-47.3952138935, -22.17267682516667], [-47.395214831666664, -22.172681917166667], [-47.395215301166665, -22.172684613166666], [-47.395216344666665, -22.172689754166665], [-47.39521670533333, -22.172692151666666], [-47.3952172425, -22.172695494833334], [-47.395217986666665, -22.172701318666668], [-47.39521829733334, -22.1727032815], [-47.395218085, -22.1727083455], [-47.395217370666664, -22.172711332], [-47.395221153, -22.172715260166665], [-47.39522287516667, -22.172716722166665], [-47.3952262205, -22.172721511833334], [-47.39522679216667, -22.1727251945], [-47.3952264485, -22.172728077666665], [-47.395221607833335, -22.1727346585], [-47.39521770383333, -22.172736784333335], [-47.39520991166667, -22.172737078833332], [-47.395206829833334, -22.172735761166667], [-47.395201955833336, -22.172729872333335], [-47.395201161833334, -22.172725887666665], [-47.395202950666665, -22.172722859166665], [-47.395206459, -22.1727181785], [-47.395209633, -22.172715328833334], [-47.39521486016667, -22.172716129], [-47.39521712316667, -22.172716103333332], [-47.395223301166666, -22.172714955333333], [-47.39522607033334, -22.172712866333335], [-47.395227983666665, -22.172710646166667], [-47.395229317, -22.172702757833335], [-47.39522723783333, -22.172699078333334], [-47.3952201425, -22.172694863666667], [-47.395216778, -22.17269502], [-47.395211916166666, -22.172697834666668], [-47.3952096905, -22.172699738166667], [-47.395207431, -22.172700759833333], [-47.395201152333335, -22.1727031105], [-47.395196381333335, -22.172702974833335], [-47.39519378, -22.172702100166667], [-47.395188575333336, -22.172697375166667], [-47.395187179, -22.172693494333334], [-47.3951886055, -22.172686481833335], [-47.395190795666664, -22.1726839655], [-47.395197941333336, -22.1726811135], [-47.395201979333336, -22.172681057], [-47.39520345183333, -22.172682550166666], [-47.395207649166665, -22.172685885666667], [-47.3952109425, -22.17268669], [-47.3952134685, -22.1726865585], [-47.395219690666664, -22.172683575], [-47.39522308116667, -22.1726776265], [-47.3952225125, -22.172672815666665], [-47.395220510166666, -22.17266993533333], [-47.39521466666667, -22.172667577333332], [-47.395210875833335, -22.172667172], [-47.395205466, -22.172665870166668], [-47.3952020855, -22.172664815333334], [-47.395198556666664, -22.172663721], [-47.395193420333335, -22.1726622985], [-47.395193420333335, -22.1726622985]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 0.0, "Dist2": 0.0, "Id": "1", "Length": 45.0669749673391, "Name": "25_08_2025_16_29_49_MapaGPS"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_99cfc632ca5e92adc9931d372fcc1b88.setStyle(function(feature) {return feature.properties.style;});
geo_json_40d1c4f07ee5691fa37b3e9a1cd24536_add({"features": [{"geometry": {"coordinates": [[-47.395205344, -22.172559531333334], [-47.395212610166666, -22.172614638833334], [-47.395219157, -22.172656417833334], [-47.395223544833335, -22.1726892105], [-47.39522414098443, -22.17269369161165], [-47.395225326538004, -22.172702654611555]], "id": null, "type": "LineString"}, "id": 0, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "1", "Length": 14.558011415731592, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39521090233333, -22.172708800833334], [-47.395206943666665, -22.172679811], [-47.395202420666664, -22.1726491015], [-47.395198865666664, -22.172615782833333], [-47.395193255833334, -22.172577132833332], [-47.395192610024395, -22.17257265769541], [-47.395191325698136, -22.172563706509337]], "id": null, "type": "LineString"}, "id": 1, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "2", "Length": 14.771318274761821, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.3951762925, -22.172561603], [-47.395180824166665, -22.172591686166665], [-47.395185745333336, -22.172623080166666], [-47.395190433, -22.172656367166667], [-47.395195199, -22.172693641833334], [-47.39519576904436, -22.172698125891035], [-47.39519690267159, -22.172707094716973]], "id": null, "type": "LineString"}, "id": 2, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "3", "Length": 14.827915524386164, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.39518293216667, -22.1727127985], [-47.39517819266667, -22.172680514833335], [-47.3951732595, -22.172646752833334], [-47.39516880516667, -22.1726149155], [-47.39516381233334, -22.172581167166665], [-47.39516315429932, -22.172576693573966], [-47.39516184565584, -22.172567745443878]], "id": null, "type": "LineString"}, "id": 3, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "4", "Length": 14.785159385903514, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}, {"geometry": {"coordinates": [[-47.395147317833334, -22.172566031], [-47.395151503166666, -22.172595452333333], [-47.3951561855, -22.172628011166665], [-47.39516159866667, -22.172663757333332], [-47.39516672716667, -22.172697770833334], [-47.395167397572706, -22.172702242832194], [-47.39516873082615, -22.17271118781009]], "id": null, "type": "LineString"}, "id": 4, "properties": {"Dist1": 14.558011415731592, "Dist2": 0.0, "Id": "5", "Length": 14.80117692191233, "Name": "CidadeJardimTerreno2"}, "type": "Feature"}], "type": "FeatureCollection"});
geo_json_40d1c4f07ee5691fa37b3e9a1cd24536.setStyle(function(feature) {return feature.properties.style;});
geo_json_99cfc632ca5e92adc9931d372fcc1b88.addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
geo_json_40d1c4f07ee5691fa37b3e9a1cd24536.addTo(map_21ca758b860567b95d146fc0853c971f);
</script>
@ -185,7 +185,7 @@
}
trajeto_json_add({"features": []});
trajeto_json.addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
trajeto_json.addTo(map_21ca758b860567b95d146fc0853c971f);
function adicionarGeometria(novaGeometria) {
trajeto_json.addData(novaGeometria);
@ -243,7 +243,7 @@
}
trajeto_dinamico_json_add({"features": []});
trajeto_dinamico_json.addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
trajeto_dinamico_json.addTo(map_21ca758b860567b95d146fc0853c971f);
function adicionarGeometriaDinamica(novaGeometria) {
trajeto_dinamico_json.addData(novaGeometria);
@ -296,9 +296,9 @@
var marcadorEquipamento = L.marker([0, 0], {
icon: customIcon
}).addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
}).addTo(map_21ca758b860567b95d146fc0853c971f);
var marcadorBase = L.marker([0, 0], {}).addTo(map_b21baaed2057e8bbe47af17d68ecedd6);
var marcadorBase = L.marker([0, 0], {}).addTo(map_21ca758b860567b95d146fc0853c971f);
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_b21baaed2057e8bbe47af17d68ecedd6.setView(novaPosicao, map_b21baaed2057e8bbe47af17d68ecedd6.getZoom());
map_21ca758b860567b95d146fc0853c971f.setView(novaPosicao, map_21ca758b860567b95d146fc0853c971f.getZoom());
}
}
@ -397,7 +397,7 @@
function atualizarSelecaoRuas(selecionadas) {
selecionadas = JSON.parse(selecionadas);
RuasSelecionadas = Array.isArray(selecionadas) ? [...selecionadas] : [];
geo_json_99cfc632ca5e92adc9931d372fcc1b88.eachLayer(function (layer) {
geo_json_40d1c4f07ee5691fa37b3e9a1cd24536.eachLayer(function (layer) {
if (RuasSelecionadas.includes(parseInt(layer.feature.id))) {
layer.setStyle({ color: 'blue' });
} else {

View File

@ -12,18 +12,6 @@ class CameraOak:
self.modelo_ia_seg = modelo_ia_seg
self.modelo_ia_det = modelo_ia_det
disp_list = dai.Device.getAllAvailableDevices()
disp_info = next((d for d in disp_list if d.getMxId() == mx_id), None)
if not disp_info:
return
#raise RuntimeError(f"Dispositivo com mxid {mx_id} não encontrado")
self.dev_info = disp_info
#self.dev_info = dai.DeviceInfo(mx_id)
self.mx_id = self.dev_info.getMxId()
self.versao = self.dev_info.name
self.dispositivo = T_Code.Vzo
self.modelo = "Desconhecido"
self.ultima_saude = {}
@ -37,6 +25,19 @@ class CameraOak:
self.rodando = False
self.iniciado = False
disp_list = dai.Device.getAllAvailableDevices()
disp_info = next((d for d in disp_list if d.getMxId() == mx_id), None)
if not disp_info:
return
#raise RuntimeError(f"Dispositivo com mxid {mx_id} não encontrado")
self.dev_info = disp_info
#self.dev_info = dai.DeviceInfo(mx_id)
self.mx_id = self.dev_info.getMxId()
self.versao = self.dev_info.name
_camera = ContextoGlobalRedis.get_camera(self.mx_id) or {}
_camera["mx_id"] = self.mx_id
_camera["versao"] = self.versao
@ -88,7 +89,7 @@ class CameraOak:
self.q_seg = self.device.getOutputQueue(name="seg", maxSize=1, blocking=False)
if self.modelo_ia_det is not None:
self.q_det = self.device.getOutputQueue(name="det", maxSize=4, blocking=False)
self.q_det = self.device.getOutputQueue(name="det", maxSize=1, blocking=False)
if self.dispositivo == T_Code.Snr:
dadosSnr = ContextoGlobalRedis.get_operacao().get("Snr", {})
@ -204,28 +205,25 @@ class CameraOak:
script = pipeline.create(dai.node.Script)
if self.modelo_ia_seg is not None or self.modelo_ia_det is not None:
try:
N_seg = (self.modelo_ia_seg or {}).get("det_every_n", 1)
N_seg = (self.modelo_ia_seg or {}).get("seg_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
i = 0
while True:
f = node.io['in'].get()
send_seg = {1 if self.modelo_ia_seg else 0} and (i % {N_seg} == 0)
send_det = {1 if self.modelo_ia_det else 0} and (i % {N_det} == 0)
if send_seg and send_det:
node.io['toSeg'].send(f) # move o original
node.io['toDet'].send(f) # clone para a segunda rota
elif send_seg:
node.io['toSeg'].send(f)
elif send_det:
node.io['toDet'].send(f)
# se nenhum for enviar, apenas descarta 'f'
i += 1
""")
cam.video.link(script.inputs['in'])
except Exception as e:
@ -299,6 +297,7 @@ class CameraOak:
det.out.link(xout_det.input)
script.outputs['toDet'].link(manip_det.inputImage)
#cam.video.link(manip_det.inputImage)
# (opcional) passthrough para sincronizar timestamp/frame com a detecção
# xout_det_img = pipeline.createXLinkOut()

View File

@ -31,21 +31,26 @@ class CameraManager:
else:
cam_existente["timestamp"] = self._ultimo_scan
ContextoGlobalRedis.set(CtxKey.DadosCameras, cameras_mapeadas)
for cam_id in cameras_mapeadas.keys():
ids_para_remover = []
for cam_id, val in cameras_mapeadas.items():
cam = ContextoGlobalRedis.get_camera(cam_id)
if cam is not None:
if cam is not None and cam.get("saude", {}).get("status", StatusModulo.DESCONECTADO.value) != StatusModulo.DESCONECTADO.value:
if cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Snr.value:
ContextoGlobalRedis.publicar_comando(CmdKey.VisualWorkerRx, { "cmd": VisualWorkerCommandType.AtualizarSaudeCamera.value } )
elif cam.get("dispositivo", T_Code.Vzo.value) == T_Code.Cam.value:
ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.AtualizarSaudeCamera.value } )
elif cam is not None and val.get("timestamp", 0) < self._ultimo_scan:
ids_para_remover.append(cam_id)
for cam_id in ids_para_remover:
cameras_mapeadas.pop(cam_id, None)
ContextoGlobalRedis.set(CtxKey.DadosCameras, cameras_mapeadas)
visual_worker_camera_id = ContextoGlobalRedis.get_equipamento().get("camera_caminho_id")
if visual_worker_camera_id is not None and visual_worker_camera_id in dispositivos_serializados:
if visual_worker_camera_id is not None and visual_worker_camera_id in cameras_mapeadas:
ContextoGlobalRedis.publicar_comando(CmdKey.VisualWorkerRx, { "cmd": VisualWorkerCommandType.IniciarCameraManager.value, "params": visual_worker_camera_id } )
weed_worker_camera_id = ContextoGlobalRedis.get_equipamento().get("camera_solo_id")
if weed_worker_camera_id is not None and weed_worker_camera_id in dispositivos_serializados:
if weed_worker_camera_id is not None and weed_worker_camera_id in cameras_mapeadas:
ContextoGlobalRedis.publicar_comando(CmdKey.WeedWorkerRx, { "cmd": WeedWorkerCommandType.IniciarCameraManager.value, "params": weed_worker_camera_id } )

View File

@ -30,20 +30,25 @@ class ModuloAtuador(ModuloDiagnosticoBase):
FREQ_MIN = FREQ_BASE * 0.5
SAUDE_MIN_ALERTA = 80
sensor_massa_ativado = False
for tipo, sensores in dados_atu.get("sensores", {}).items():
for bico_id, dados_bico in sensores.items():
for sensor_id, dados_sensor in sensores.items():
saude = 100
motivos = []
condicoes_operacionais = []
aferir = dados_bico.get("aferir", False)
padroes = self.padroes_sensores.get(tipo, {}).get(bico_id, {})
conectado = dados_bico.get("conectado", False)
mandatorio = dados_bico.get("mandatorio", False)
aferir = dados_sensor.get("aferir", False)
padroes = self.padroes_sensores.get(tipo, {}).get(sensor_id, {})
conectado = dados_sensor.get("conectado", False)
mandatorio = dados_sensor.get("mandatorio", False)
if dados_sensor.get("label") == "MASRS" and conectado and mandatorio and aferir: sensor_massa_ativado = True
if not conectado:
saude = 0
motivos.append(f"desconectado")
else:
for campo in dados_bico.get("dados", []):
for campo in dados_sensor.get("dados", []):
label = campo.get("label")
if label is None:
continue
@ -69,10 +74,10 @@ class ModuloAtuador(ModuloDiagnosticoBase):
"descricao": f"{label} fora do padrão"
})
saude = max(0, saude)
chave = f"{S_Code(int(tipo)).name}_{bico_id}"
chave = f"{S_Code(int(tipo)).name}_{sensor_id}"
saude_mod = {
"id": bico_id,
"label": dados_bico.get("label"),
"id": sensor_id,
"label": dados_sensor.get("label"),
"status": (StatusModulo.DESCONECTADO if not conectado else StatusModulo.OPERANTE if saude >= SAUDE_MIN_ALERTA else StatusModulo.ALERTA if saude > 0 else StatusModulo.FALHA).value,
"saude": saude,
"motivos": motivos,
@ -84,7 +89,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
total_ativos += 1
saude_total += saude
motivos_gerais.extend([f"{chave}: {m}" for m in motivos])
dados_bico["saude"] = saude_mod
dados_sensor["saude"] = saude_mod
for tipo, bicos in dados_atu.get("bicos", {}).items():
for bico_id, dados_bico in bicos.items():
@ -198,7 +203,34 @@ class ModuloAtuador(ModuloDiagnosticoBase):
motivos_gerais.extend([f"{chave}: {m}" for m in motivos])
dados_bomba["saude"] = saude_mod
saude_final = (saude_total // total_ativos) if total_ativos > 0 else 100
cond_op_mod = []
penalidade_percentual_herbicida = 0
if sensor_massa_ativado:
percent_min = ContextoGlobalRedis.get_equipamento().get("percentual_reservatorio_min", 5.0)
percent_atual = ContextoGlobalRedis.get_contexto().get("Gerais", {}).get("percentual_reservatorio", 0.0)
if percent_atual < percent_min:
penalidade_percentual_herbicida = 50
cond_op_mod.append({
"valor": percent_atual,
"severidade": 100,
"descricao": f"Nível do reservatório de herbicida abaixo do limite mínimo!"
})
elif percent_atual < 20.0:
penalidade_percentual_herbicida = 21
cond_op_mod.append({
"valor": percent_atual,
"severidade": 30,
"descricao": f"Nível do reservatório de herbicida baixo!"
})
else:
cond_op_mod.append({
"valor": 0,
"severidade": 100,
"descricao": f"Sensor do nível do reservatório desconectado!"
})
saude_final = (saude_total // total_ativos) if total_ativos > 0 else 0
saude_final = saude_final - penalidade_percentual_herbicida
status = StatusModulo.OPERANTE
if not mod_conectado:
@ -211,9 +243,10 @@ class ModuloAtuador(ModuloDiagnosticoBase):
saude_geral = {
"conectado": mod_conectado,
"status": status.value,
"saude": saude_final,
"saude": max(0, saude_final),
"motivos": motivos_gerais,
"saude_individual": saude_individual
"saude_individual": saude_individual,
"condicoes_operacionais": cond_op_mod
}
ContextoGlobalRedis.atualizar_ctx_dict(

View File

@ -1,3 +1,4 @@
import math
import time
from shared.enums import T_Code, StatusModulo
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
@ -17,7 +18,8 @@ class ModuloGPS(ModuloDiagnosticoBase):
SAUDE_MIN_ALERTA = 80
conectado = modulo.get("conectado", False)
freq_hz = modulo.get("freq", 0.0)
freq_pos = modulo.get("freq", {}).get("posicao", 0.0)
freq_ori = modulo.get("freq", {}).get("orientacao", 0.0)
fix = modulo.get("fix", 0)
rtk = modulo.get("rtk", False)
hAcc = modulo.get("hAcc", 0)
@ -30,16 +32,26 @@ class ModuloGPS(ModuloDiagnosticoBase):
if not conectado:
motivos.append("GPS desconectado")
saude = 0
elif freq_hz <= FREQ_MIN:
motivos.append("Sem resposta do GPS")
elif freq_pos <= FREQ_MIN:
motivos.append("Sem resposta de coordenadas")
saude = 0
elif freq_ori <= FREQ_MIN:
motivos.append("Sem resposta de orientacao")
saude = 0
else:
f = min(freq_hz, FREQ_BASE)
erro = (FREQ_BASE - f) / FREQ_BASE
p_freq = min(int(erro * 100), 40)
saude -= abs(p_freq)
if abs(p_freq) > 0:
motivos.append(f"Frequência baixa: {f:.2f} Hz (penalidade {p_freq}%)")
f_pos = min(freq_pos, FREQ_BASE)
erro_pos = (FREQ_BASE - f_pos) / FREQ_BASE
p_freq_pos = min(int(erro_pos * 100), 40)
saude -= abs(p_freq_pos)
if abs(p_freq_pos) > 0:
motivos.append(f"Frequência de coordenadas baixa: {f_pos:.2f} Hz (penalidade {p_freq_pos}%)")
f_ori = min(freq_ori, FREQ_BASE)
erro_ori = (FREQ_BASE - f_ori) / FREQ_BASE
p_freq_ori = min(int(erro_ori * 100), 40)
saude -= abs(p_freq_ori)
if abs(p_freq_ori) > 0:
motivos.append(f"Frequência de orientacao baixa: {f_ori:.2f} Hz (penalidade {p_freq_ori}%)")
# 🔹 Fix
p_fix = self._penalidade_fix(fix)
@ -235,11 +247,7 @@ class ModuloGPS(ModuloDiagnosticoBase):
if h < 200: return 20
return 30
def _penalidade_age(self, age) -> int:
if age < 0: return 35
if age < 0.5: return 0
if age < 2.0: return 5
if age < 5.0: return 10
if age < 10.0: return 18
if age < 15.0: return 25
return 35
def _penalidade_age(self, age: float) -> float:
if age < 0:
return 35
return round(min(35, 35 * (1 - math.exp(-age / 8))), 2)

View File

@ -29,6 +29,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
FREQ_MIN = FREQ_BASE * 0.5
SAUDE_MIN_ALERTA = 80
sensor_bateria_ativado = False
for tipo, sensores in dados_sen.get("sensores", {}).items():
for sensor_id, dados_sensor in sensores.items():
saude = 100
@ -38,6 +40,9 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
padroes = self.padroes_sensores.get(tipo, {}).get(sensor_id, {})
conectado = dados_sensor.get("conectado", False)
mandatorio = dados_sensor.get("mandatorio", False)
if dados_sensor.get("label") == "AB36V" and conectado and mandatorio and aferir: sensor_bateria_ativado = True
if not conectado:
saude = 0
motivos.append(f"desconectado")
@ -138,7 +143,34 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
motivos_gerais.extend([f"{chave}: {m}" for m in motivos])
dados_servo["saude"] = saude_mod
saude_final = (saude_total // total_ativos) if total_ativos > 0 else 100
cond_op_mod = []
penalidade_percentual_bateria = 0
if sensor_bateria_ativado:
percent_min = ContextoGlobalRedis.get_equipamento().get("percentual_tensao_bateria_min", 5.0)
percent_atual = ContextoGlobalRedis.get_contexto().get("Gerais", {}).get("percentual_bateria", 0.0)
if percent_atual < percent_min:
penalidade_percentual_bateria = 70
cond_op_mod.append({
"valor": percent_atual,
"severidade": 100,
"descricao": f"Nível da bateria abaixo do limite mínimo!"
})
elif percent_atual < 25.0:
penalidade_percentual_bateria = 25
cond_op_mod.append({
"valor": percent_atual,
"severidade": 30,
"descricao": f"Nível da bateria baixo!"
})
else:
cond_op_mod.append({
"valor": 0,
"severidade": 100,
"descricao": f"Sensor de nível da bateria desconectado!"
})
saude_final = (saude_total // total_ativos) if total_ativos > 0 else 0
saude_final = saude_final - penalidade_percentual_bateria
status = StatusModulo.OPERANTE
if not mod_conectado:
@ -151,9 +183,10 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
saude_geral = {
"conectado": mod_conectado,
"status": status.value,
"saude": saude_final,
"saude": max(0, saude_final),
"motivos": motivos_gerais,
"saude_individual": saude_individual
"saude_individual": saude_individual,
"condicoes_operacionais": cond_op_mod
}
ContextoGlobalRedis.atualizar_ctx_dict(

View File

@ -24,7 +24,7 @@ def main():
fila = WorkerFilaMensagens("controle", fila_callback, 1, True, 100)
loop_fixo = True
frequencia_loop = 5 # hz
frequencia_loop = 15 # hz
def loop_ativo():
t0 = time.time()

View File

@ -7,7 +7,7 @@ from manager_worker.modulos.mpc import get_mpc, comando_parado
from manager_worker.modulos.pid import PIDAdaptativo
from visual_worker.processamento.costmap_fuser import unpack_snapshot
def definir_comando(pid: PIDAdaptativo):
def definir_comando(pid: PIDAdaptativo, envio_necessario: bool):
try:
_operacao = ContextoGlobalRedis.get_operacao()
_tipo_controle = TiposControladorDirecional(_operacao.get("Dir", {}).get("tipo_controle", TiposControladorDirecional.MPC.value))
@ -47,6 +47,13 @@ def definir_comando(pid: PIDAdaptativo):
pass_locked=_gps_passos_travados
)
latencia = time.perf_counter() - _gps.get("timestamp", {}).get("posicao", 0.0)
if not _gps_pos_atualizada and not envio_necessario:
return _comando_anterior(latencia, False)
Vmin = 0.1 # if _trajetoria.get("status", StatusCarroMapa.Parado.value) == StatusCarroMapa.CaminhandoRua.value else 0.8
contexto = {
"Operacao": {
"Status": _operacao.get("status"),
@ -56,16 +63,18 @@ def definir_comando(pid: PIDAdaptativo):
"Latitude": _gps.get("lat", 0),
"Longitude": _gps.get("lon", 0),
"AnguloCarro": _gps.get("theta", 0),
"PassosAtraso": _gps_passos_travados
"PassosAtraso": _gps_passos_travados,
"Timestamp": _gps.get("timestamp", {}).get("posicao", 0.0)
},
"Carro": {
"Velocidade": max(0.01, _contexto.get("Gerais", {}).get("velocidade_ms", 0.0)),
"Velocidade": max(Vmin, _contexto.get("Gerais", {}).get("velocidade_ms", 0.0)),
"Status": _trajetoria.get("status", StatusCarroMapa.Parado.value),
"ManobrandoEntreRuas": _trajetoria.get("manobrando", False),
"DentroCorredor": _trajetoria.get("CorredorAtual", {}).get("dentro", False),
"DistanciaEsquerda": _trajetoria.get("CorredorAtual", {}).get("dist_esq", 0.0),
"DistanciaDireita": _trajetoria.get("CorredorAtual", {}).get("dist_dir", 0.0),
"IdxProximoPonto": _trajetoria.get("idx_proximo_ponto", 0),
"IdxPontoAlvo": _trajetoria.get("idx_ponto_alvo", 0),
"AnguloCaminho": _trajetoria.get("CorredorAtual", {}).get("angulo", 0.0),
"ErroAngular": _trajetoria.get("erro_angular", 0),
"ErroAngularCaminho": _trajetoria.get("erro_angular_caminho", 0),
@ -93,7 +102,7 @@ def definir_comando(pid: PIDAdaptativo):
},
"RegrasAtivas": {
"matriz_custo": False,
"deteccao_obstaculos": True,
"deteccao_obstaculos": False,
},
"Equipamento": {
"largura": _equipamento.get("largura", 0.85),
@ -108,8 +117,8 @@ def definir_comando(pid: PIDAdaptativo):
if _tipo_controle == TiposControladorDirecional.MPC:
comando = _regras_taticas(contexto)
if (comando["comando_definido"]):
return comando["angulo"], comando["tipo"], comando["simulacao"], comando["parada_necessaria"], False
if (comando.get("comando_definido", False)):
return _montar_comando_retorno(comando, latencia=latencia)
else:
op_modo = ModoOperacao(_operacao.get("modo", ModoOperacao.NaoDefinido.value))
if op_modo == ModoOperacao.MapaGPS:
@ -117,8 +126,8 @@ def definir_comando(pid: PIDAdaptativo):
elif op_modo == ModoOperacao.MapeamentoVisual:
return _comando_mapeamento_visual(contexto)
else:
comando = _comando_direcional_parado()
return comando["angulo"], comando["tipo"], comando["simulacao"], comando["parada_necessaria"], False
comando = _comando_direcional_parado(True)
return _montar_comando_retorno(comando, latencia=latencia)
elif _tipo_controle == TiposControladorDirecional.PID:
_controle = ContextoGlobalRedis.get_controle()
@ -145,7 +154,17 @@ def definir_comando(pid: PIDAdaptativo):
tipo_movimento = TipoMovimentoDirecional.MovimentoArco if usar_arco else TipoMovimentoDirecional.RodasDianteiras
angulo = round(pid.atualizar(erro_angular), 2)
print(f"Angulo: {angulo}, Tipo Movimento: {tipo_movimento.name}, Erro Angular: {erro_angular:.2f}")
return angulo, tipo_movimento, [], False
_cmd = {
"comando_definido": True,
"enviar_comando": True,
"parada_necessaria": True,
"erro": False,
"latencia": 0.0,
"angulo": angulo,
"tipo": tipo_movimento.value,
"simulacao": []
}
return _montar_comando_retorno(_cmd)
except Exception as e:
mostrar_log(f"❌ Erro ao definir comando direcional: {e}")
@ -153,13 +172,17 @@ def definir_comando(pid: PIDAdaptativo):
def _comando_direcional_parado(definido: bool):
try:
angulo, tipo = comando_parado()
return {
"angulo": angulo,
_cmd = {
"comando_definido": definido,
"enviar_comando": True,
"parada_necessaria": True,
"erro": False,
"latencia": 0.0,
"angulo": angulo,
"tipo": tipo.value,
"simulacao": [],
"parada_necessaria": True,
"comando_definido": definido
"simulacao": []
}
return _cmd
except Exception as e:
mostrar_log(f"❌ Erro ao montar comando direcional parado: {e}")
@ -247,7 +270,7 @@ def _comando_mapa_gps_mpc(contexto):
#input("⏸️ Pressione Enter para continuar...")
return comando.get("angulo", 0.0), comando.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value), comando.get("simulacao", []), comando.get("parada_necessaria", False), comando.get("erro", False)
return _montar_comando_retorno(comando)
except Exception as e:
mostrar_log(f"❌ Erro ao definir comando MapaGPS: {e}")
@ -283,16 +306,38 @@ def _comando_mapeamento_visual(contexto):
# 🚦 Define se deve parar
parada_necessaria = (status_carro == StatusCarroMapa.Parado)
latencia = time.perf_counter() - contexto.get("GPS", {}).get("Timestamp", 0.0)
# 🔧 Monta comando final
comando = {
"angulo": round(angulo, 2),
"tipo": tipo_movimento.value,
"simulacao": [],
"parada_necessaria": parada_necessaria
"enviar_comando": True,
"parada_necessaria": parada_necessaria,
"erro": False,
"latencia": latencia,
"angulo": round(angulo, 2),
"tipo": tipo_movimento.value,
"simulacao": []
}
return comando["angulo"], comando["tipo"], comando["simulacao"], comando["parada_necessaria"], False
return _montar_comando_retorno(comando)
except Exception as e:
mostrar_log(f"❌ Erro ao definir comando MapeamentoVisual: {e}")
return 0.0, TipoMovimentoDirecional.RodasDianteiras.value, [], True
def _comando_anterior(latencia, enviar):
_controle = ContextoGlobalRedis.get_controle()
comando_anterior = {
"enviar_comando": enviar,
"parada_necessaria": False,
"erro": False,
"latencia": latencia,
"angulo": _controle.get("angulo_sp", 0.0),
"tipo": _controle.get("tipo_movimento_direcional", TipoMovimentoDirecional.RodasDianteiras.value),
"simulacao": []
}
return _montar_comando_retorno(comando_anterior)
def _montar_comando_retorno(comando, latencia: float = -1):
if latencia > -1:
comando["latencia"] = latencia
return comando

View File

@ -26,9 +26,11 @@ def definir_comando(parada_necessaria: bool, erro: bool, filtro_vel: FiltroVeloc
_controle = ContextoGlobalRedis.get_controle()
pulverizador_automatico = _controle.get("pulverizador_automatico", False)
ervas_no_radar = _contexto.get("Gerais", {}).get("ervas_no_radar", False)
percentual_ervas_no_radar = _contexto.get("Gerais", {}).get("percentual_ervas_no_radar", 0)
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)
ervas_min_percenet = 15.0
status_carro = StatusCarroMapa.Parado
@ -55,9 +57,9 @@ def definir_comando(parada_necessaria: bool, erro: bool, filtro_vel: FiltroVeloc
else:
velocidade_sp = calcular_velocidade_relativa(vel_min, vel_max, ang_max, erro)
elif status_carro in [StatusCarroMapa.EntrandoRua, StatusCarroMapa.SaindoRua, StatusCarroMapa.Manobrando]:
velocidade_sp = vel_min
velocidade_sp = min(15, vel_min)
elif status_carro == StatusCarroMapa.CaminhandoRua:
if pulverizador_automatico and ervas_no_radar:
if pulverizador_automatico and ervas_no_radar and percentual_ervas_no_radar > ervas_min_percenet:
velocidade_sp = vel_min
else:
velocidade_sp = calcular_velocidade_relativa(vel_min, vel_max, ang_max, erro)
@ -65,7 +67,7 @@ def definir_comando(parada_necessaria: bool, erro: bool, filtro_vel: FiltroVeloc
# Aplica limites
velocidade_sp = max(vel_min, min(vel_max, velocidade_sp))
if velocidade_sp <= vel_min:
if velocidade_sp < 1.0:
filtro_vel.reset(velocidade_sp)
else:
velocidade_sp = filtro_vel.filtrar(velocidade_sp)

View File

@ -1,3 +1,4 @@
from copy import deepcopy
import heapq
import math
import time
@ -35,7 +36,7 @@ class ControladorMPC:
self.ultima_atualizacao = None
self.dt = 0
print(parametros_mpc)
#print(parametros_mpc)
self.angulo_max_graus = parametros_mpc.get("angulo_max_graus", 30.0)
self.velocidade_min = parametros_mpc.get("velocidade_min", 0.4)
self.velocidade_max = parametros_mpc.get("velocidade_max", 1.9)
@ -364,6 +365,153 @@ class ControladorMPC:
except Exception as e:
mostrar_log(f"Erro ao calcular pesos de movimento: {e}")
def corrigir_pose_por_latencia(
self,
x, y, theta, visitados,
latency_s, # atraso a compensar (s)
dt, # dt nominal do seu controle
nova_posicao_fn, # self._nova_posicao
u_hold, # {'v': m/s, 'omega': rad/s} (ou forneça 'tipo' e 'angulo' + calc_omega)
cmd_seq=None, # opcional: [(dur_s, u_dict), ...] que cobre latency_s
time_left_ms=lambda: 1e9,
calc_omega_fn=None # opcional: fn(tipo, angulo_rad, v) -> omega
):
"""
Propaga (x,y,theta) por 'latency_s' usando 'nova_posicao_fn' que depende de self.dt.
Retorna (x, y, theta) estimados no presente.
"""
visitados_copy = deepcopy(visitados)
latency_s = max(0.0, float(latency_s))
if latency_s == 0.0:
return x, y, theta, visitados_copy
# --- constrói sequência efetiva de comandos ---
if not cmd_seq:
cmd_seq = [(latency_s, u_hold)]
else:
total = sum(max(0.0, d) for d, _ in cmd_seq)
if total < latency_s:
cmd_seq = list(cmd_seq) + [(latency_s - total, cmd_seq[-1][1])]
# --- adaptador para usar dt_eff com fn que usa self.dt ---
def aplicar_passo_dt(xk, yk, thetak, u, dt_eff):
# garantir que temos omega e v
v = u.get('v', 0.0)
if 'omega' in u:
omega = u['omega']
else:
if calc_omega_fn is not None and 'tipo' in u and 'angulo' in u:
omega = calc_omega_fn(v, u['angulo'], u['tipo'])
else:
omega = 0.0 # fallback: reta
old_dt = self.dt
try:
self.dt = dt_eff
return nova_posicao_fn(xk, yk, thetak, omega, v, u.get('tipo'), u.get('angulo', 0.0))
finally:
self.dt = old_dt
# --- integra por tempo, quebrando cada trecho em subpassos ~dt ---
t_rem = latency_s
for dur_s, u in cmd_seq:
if t_rem <= 0.0:
break
seg = min(dur_s, t_rem)
if seg <= 0.0:
continue
n = max(1, int(round(seg / dt)))
dt_eff = seg / n
for _ in range(n):
if time_left_ms() <= 0.0:
return x, y, theta, visitados_copy
x, y, theta = aplicar_passo_dt(x, y, theta, u, dt_eff)
idx_alvo = self._corrigir_pontos_visitados(x, y, visitados_copy)
t_rem -= seg
# normaliza theta se quiser
# theta = (theta + np.pi) % (2*np.pi) - np.pi
return x, y, theta, visitados_copy
def _wrap_pi(self, a):
try:
return (a + np.pi) % (2*np.pi) - np.pi
except Exception as e:
mostrar_log(f"Erro no wrap_pi: {e}")
def _circ_mean(self, a, b, wa=0.5):
wb = 1.0 - wa
ux = wa*np.cos(a) + wb*np.cos(b)
uy = wa*np.sin(a) + wb*np.sin(b)
return float(np.arctan2(uy, ux))
def _align_to_ref(self, theta, ref):
"""Devolve theta ou theta+pi, o que fica mais próximo de ref (evita flip 180°)."""
a = self._wrap_pi(theta - ref)
b = self._wrap_pi(theta + np.pi - ref)
return theta if abs(a) <= abs(b) else self._wrap_pi(theta + np.pi)
def delta_por_dist_esq_dir(
self,
estado_atual, # (x, y, theta_atual)
ponto_alvo_xy, # (x, y) do seu alvo (use seu idx_ponto_alvo/pure pursuit)
orient_corredor, # ângulo do caminho (rad) — se vier em graus, converta antes
dist_esq, dist_dir, # metros até borda esquerda/direita
angulo_max_graus=24.0,
sigma_factor=0.6, # 0.4..0.8 -> quanto “larga” é a zona de centralização
w_min=0.15, # peso mínimo p/ corredor (não zera nunca)
usar_nudge_centro=True, # opcional: empurrãozinho pro centro
k_e=1.0, v=0.0, v0=0.3 # ganho e velocidade para o “nudge” (tipo Stanley)
):
x, y, theta = estado_atual
# 1) Bearing até o alvo
orient = self.gps_handler.calcular_orientacao(ponto_alvo_xy, estado_atual)
theta_ponto = (orient - estado_atual[2] + np.pi) % (2*np.pi) - np.pi
# 2) Defina erro lateral a partir das distâncias
# Convenção: e_lat > 0 quando você está mais perto da ESQUERDA (dir > esq)
# (Se quiser o oposto, troque o sinal.)
e_lat = float((dist_dir - dist_esq) / 2.0)
print(e_lat)
# 3) Largura efetiva e peso para o ângulo do corredor
half = max((dist_esq + dist_dir) * 0.5, 1e-6)
sigma = sigma_factor * half
# Peso “tipo gaussiana”: centralizado -> w≈1; deslocado -> w≈0 (mas não menos que w_min)
w_rua = float(np.exp(- (e_lat*e_lat) / (2.0*sigma*sigma)))
w_rua = float(np.clip(w_rua, w_min, 1.0))
# 4) Alinhe o ângulo do corredor com o bearing (evita virar “de costas”)
orient_corredor = self._align_to_ref(orient_corredor, theta_ponto)
# 5) Média circular ponderada
theta_ref = self._circ_mean(orient_corredor, theta_ponto, wa=w_rua)
# 6) Opcional: empurrão suave pro centro (evita “andar paralelo” fora do meio)
nudge = 0.0
if usar_nudge_centro:
nudge = np.arctan2(k_e * e_lat, v + v0) # pequeno e estável
# 7) Delta final + clamp
delta = self._wrap_pi(theta_ref - theta) + nudge
ang_max = np.radians(angulo_max_graus)
delta = float(np.clip(delta, -ang_max, ang_max))
debug = {
"e_lat": e_lat, "half": half, "sigma": sigma, "w_rua": w_rua,
"theta_ponto_deg": np.degrees(theta_ponto),
"theta_corr_deg": np.degrees(orient_corredor),
"theta_ref_deg": np.degrees(theta_ref),
"nudge_deg": np.degrees(nudge),
"delta_deg": np.degrees(delta),
}
return delta, theta_ref, debug
# MPC COM GERACAO DE CANDIDATOS RECEDING
def compute_receding(self, contexto, comando_anterior):
@ -382,13 +530,13 @@ class ControladorMPC:
t_exec = max((now() - self.ultima_atualizacao), 1e-3)
if comando['erro']:
mostrar_log(f"🧭 Direcional MPC | Erro: {comando['erro']} | Parada: {comando['parada_necessaria']} | Movimento: {TipoMovimentoDirecional(comando['tipo']).name} | Ângulo: {comando['angulo']}° | Horizonte: {len(comando['simulacao'])} | dt: {dt_raw:.2f} s | freq = {(1.0 / dt_raw):.2f} Hz | t_exec: {t_exec:.2} s | f_exec: {(1.0 / t_exec):.2f} Hz")
mostrar_log(f"🧭 Direcional MPC | Latencia: {comando['latencia']} | Parada: {comando['parada_necessaria']} | Movimento: {TipoMovimentoDirecional(comando['tipo']).name} | Ângulo: {comando['angulo']}° | Horizonte: {len(comando['simulacao'])} | dt: {dt_raw:.2f} s | freq = {(1.0 / dt_raw):.2f} Hz | t_exec: {t_exec:.2} s | f_exec: {(1.0 / t_exec):.2f} Hz")
return comando
except Exception as e:
mostrar_log(f"❌ Erro ao processar compute MPC: {e}")
def _processar_mpc_receding(self, contexto, comando_anterior):
def _processar_mpc_receding_old(self, contexto, comando_anterior):
now = time.perf_counter
t0 = now()
@ -404,53 +552,73 @@ class ControladorMPC:
return None
GPS = contexto.get("GPS", {})
pos_lat = GPS.get("Latitude", 0)
pos_lon = GPS.get("Longitude", 0)
pos_theta = GPS.get("AnguloCarro", 0)
pos_lat = GPS.get("Latitude", 0.0)
pos_lon = GPS.get("Longitude", 0.0)
pos_theta = GPS.get("AnguloCarro", 0.0)
pos_passos_atraso = GPS.get("PassosAtraso", 0)
pos_timestamp = GPS.get("Timestamp", 0.0)
pos_latencia = now() - pos_timestamp
LAT_MAX = 2.0
if pos_latencia >= LAT_MAX:
mostrar_log(f"[GPS] Grande latencia entre coordenadas detectado ({pos_latencia:.3f}/{LAT_MAX}): parando por fallback")
return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia)
if (pos_passos_atraso > 3):
mostrar_log(f"[GPS] Atraso detectado de {(pos_passos_atraso / 3.0):.1f} passos, {pos_latencia:.3f} s")
theta = np.radians(pos_theta)
velocidade = contexto.get("Carro", {}).get("Velocidade", 0)
x, y = self.gps_handler.converter_latlon_para_xz(pos_lat, pos_lon)
theta = np.radians(pos_theta)
x0, y0, t0 = x, y, theta
velocidade = contexto.get("Carro", {}).get("Velocidade", 0)
status_carro = contexto.get("Carro", {}).get("Status", StatusCarroMapa.Parado.value)
idx_proximo_ponto_real = contexto.get("Carro", {}).get("IdxProximoPonto", 0)
idx_ponto_alvo = contexto.get("Carro", {}).get("IdxPontoAlvo", 0)
Nmax = 20 if status_carro == StatusCarroMapa.CaminhandoRua.value else 60
# Comando de hold (se não tiver buffer de comandos aplicados)
u_hold = {
"tipo": comando_anterior.get("tipo"),
"angulo": np.radians(comando_anterior.get("angulo", 0.0)),
"v": velocidade,
"omega": self.gps_handler.calcular_omega(velocidade, np.radians(comando_anterior.get("angulo", 0.0)), comando_anterior.get("tipo"))
}
# Opcional: sequência real de comandos aplicados durante a latência (se você tiver)
cmd_seq = None # ou algo como [(0.12, u1), (0.08, u2), ...]
x, y, theta, pts_visitados = self.corrigir_pose_por_latencia(
x, y, theta, self.visitados_execucao,
latency_s=min(pos_latencia, LAT_MAX),
dt=self.dt,
nova_posicao_fn=self._nova_posicao,
u_hold=u_hold,
cmd_seq=cmd_seq,
time_left_ms=lambda: (deadline - now()) * 1000.0,
calc_omega_fn=self.gps_handler.calcular_omega
)
idx_alvo_correcao = self._corrigir_pontos_visitados(x, y, pts_visitados, idx_proximo_ponto_real) # marca pontos antigos como visitados
#print(f"Anterior: x: {x0}, y: {y0}, t: {t0} - Novo: x: {x}, y: {y}, t: {theta}")
#print(GPS)
distancia_m = velocidade * self.dt
idx_alvo_real = contexto.get("Carro", {}).get("IdxProximoPonto", 0)
ultimo_ponto = idx_alvo_real >= len(self.pontos_info)
ultimo_ponto = idx_proximo_ponto_real >= len(self.pontos_info)
self.tempo_execucao_local = contexto.get("Carro", {}).get("TempoEntreComandos", 0.5)
if ultimo_ponto:
self.passos_horizonte_local = 1
self.qtd_comandos_sucessivos = 3
elif distancia_m > 0:
passos_total = min(int(self.horizonte / distancia_m), 20)
passos_total = min(int(self.horizonte / distancia_m), Nmax)
self.passos_horizonte_local = max(1, math.ceil(self.tempo_execucao_local / self.dt))
self.qtd_comandos_sucessivos = max(1, passos_total // self.passos_horizonte_local)
else:
self.passos_horizonte_local = 0
self.qtd_comandos_sucessivos = 0
passos_atraso = contexto.get("Carro", {}).get("PassosAtraso", 1)
angulo_anterior = np.radians(comando_anterior.get("angulo", 0))
tipo_anterior = TipoMovimentoDirecional(comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value))
max_simulacoes = min(passos_atraso + pos_passos_atraso, 5)
if max_simulacoes >= 5:
mostrar_log(f"[GPS] Grande atraso detectado: parando por fallback")
return self._comando_fallback_hot_stop(comando_anterior)
if (pos_passos_atraso > 0):
mostrar_log(f"[GPS] Atraso detectado: simulando {pos_passos_atraso} passos")
for passo in range(max_simulacoes):
if ms_left() <= 0:
return self._comando_fallback_hot_stop(comando_anterior)
_, posicao_futura, _, pts_visitados = self._simular_passo(x, y, theta, tipo_anterior, angulo_anterior, {}, { "tipo": tipo_anterior, "angulo": angulo_anterior }, contexto, self.visitados_execucao.copy())
x, y, theta = posicao_futura[-1]
simulacao_latlon = []
idx_alvo_correcao = self._corrigir_pontos_visitados(x, y, pts_visitados, idx_alvo_real) # marca pontos antigos como visitados
#mostrar_log(f"previsao_futura em {(t_1 - t_0):.4f}s, correcao_pontos_visitados em {(t_2 - t_1):.4f}")
@ -471,6 +639,7 @@ class ControladorMPC:
est = max(1.0, getattr(self, "_t_est_passo_ms", 6.0))
exp_budget = max(4, int(ms_left() // est))
#print(f"Comecando, idx_proximo_ponto_real: {idx_proximo_ponto_real}, idx_alvo_correcao: {idx_alvo_correcao}")
for passo in range(self.qtd_comandos_sucessivos):
if ms_left() <= 0:
break
@ -480,13 +649,18 @@ class ControladorMPC:
if exp_budget <= 0 or ms_left() <= 0:
break
x_atual, y_atual, theta_atual = candidato["x"], candidato["y"], candidato["theta"]
visitados = candidato["visitados"].copy()
visitados = deepcopy(candidato["visitados"])
cmd_anterior = {
"tipo": candidato["comandos"][-1][0],
"angulo": candidato["comandos"][-1][1],
}
idx_alvo = self._corrigir_pontos_visitados(x_atual, y_atual, visitados)
ponto_alvo = self.pontos_info[idx_alvo]["xy"]
#if passo == 0 and idx_alvo < idx_ponto_alvo:
# print(f"ponto alvo alterado de {idx_alvo} para {idx_ponto_alvo}")
# idx_alvo = idx_ponto_alvo
if idx_alvo < len(self.pontos_info) - 1:
idx_alvo = idx_alvo + 1
ponto_alvo = self.pontos_info[idx_alvo]
tipos, angs, custos_candidatos = self._gerar_angulos_candidatos_receding(ponto_alvo, (x_atual, y_atual, theta_atual), (x, y, theta), contexto, deadline, 15.0)
if ms_left() <= 0 or exp_budget <= 0:
break
@ -608,11 +782,13 @@ class ControladorMPC:
simulacao_latlon = []
#print(f"Simulacao: {len(simulacao_latlon)}")
_cmd = {
"angulo": float(round(np.degrees(angulo_final), 2)),
"tipo": tipo_final.value,
"simulacao": simulacao_latlon,
"parada_necessaria": parada_necessaria,
"erro": False
"enviar_comando": True,
"parada_necessaria": parada_necessaria,
"erro": False,
"latencia": pos_latencia,
"angulo": float(round(np.degrees(angulo_final), 2)),
"tipo": tipo_final.value,
"simulacao": simulacao_latlon
}
@ -624,7 +800,7 @@ class ControladorMPC:
# reconstroi set de candidatos só pra plot, com orçamento curtinho
deadline_dbg = time.perf_counter() + 0.03 # ~30 ms pra não pesar
tipos_dbg, angs_dbg, _ = self._gerar_angulos_candidatos_receding(
ponto_alvo=self.pontos_info[idx_alvo_correcao]["xy"],
ponto_alvo=self.pontos_info[idx_alvo_correcao],
ponto_atual=(x, y, theta),
ponto_ref=(x, y, theta),
contexto=contexto,
@ -646,7 +822,310 @@ class ControladorMPC:
except Exception as e:
mostrar_log(f"❌ Erro ao processar MPC: {e}")
return self._comando_fallback_hot_stop(comando_anterior)
return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia)
def _processar_mpc_receding(self, contexto, comando_anterior):
now = time.perf_counter
t0 = now()
# --- SLAs de execução ---
SLA_MS = getattr(self, "_mpc_sla_ms", 200.0) # 200 ms ⇒ alvo 5 Hz
HEAD_MS = getattr(self, "_mpc_headroom_ms", 5.0) # folga
deadline = t0 + (SLA_MS - HEAD_MS) / 1000.0
def ms_left():
return (deadline - now()) * 1000.0
try:
if not self.pontos_info:
return None
# -------------------- Estado atual (real) --------------------
GPS = contexto.get("GPS", {})
pos_lat = GPS.get("Latitude", 0.0)
pos_lon = GPS.get("Longitude", 0.0)
pos_theta = GPS.get("AnguloCarro", 0.0)
pos_passos_atraso = GPS.get("PassosAtraso", 0)
pos_timestamp = GPS.get("Timestamp", 0.0)
pos_latencia = now() - pos_timestamp
LAT_MAX = 2.0
if pos_latencia >= LAT_MAX:
mostrar_log(f"[GPS] Grande latencia entre coordenadas detectado ({pos_latencia:.3f}/{LAT_MAX}): parando por fallback")
return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia)
if (pos_passos_atraso > 3):
mostrar_log(f"[GPS] Atraso detectado de {(pos_passos_atraso / 3.0):.1f} passos, {pos_latencia:.3f} s")
x, y = self.gps_handler.converter_latlon_para_xz(pos_lat, pos_lon)
theta = np.radians(pos_theta)
velocidade = float(contexto.get("Carro", {}).get("Velocidade", 0.0))
status_carro = contexto.get("Carro", {}).get("Status", StatusCarroMapa.Parado.value)
idx_proximo_ponto_real = contexto.get("Carro", {}).get("IdxProximoPonto", 0)
idx_ponto_alvo = contexto.get("Carro", {}).get("IdxPontoAlvo", 0)
Nmax = 20 if status_carro == StatusCarroMapa.CaminhandoRua.value else 60
# -------------------- Correção por latência (vida real) --------------------
# Mantém correção com 'self.dt' e 'velocidade' REAIS
u_hold = {
"tipo": comando_anterior.get("tipo"),
"angulo": np.radians(comando_anterior.get("angulo", 0.0)),
"v": velocidade,
"omega": self.gps_handler.calcular_omega(
velocidade,
np.radians(comando_anterior.get("angulo", 0.0)),
comando_anterior.get("tipo")
)
}
cmd_seq = None
x, y, theta, pts_visitados = self.corrigir_pose_por_latencia(
x, y, theta, self.visitados_execucao,
latency_s=min(pos_latencia, LAT_MAX),
dt=self.dt,
nova_posicao_fn=self._nova_posicao,
u_hold=u_hold,
cmd_seq=cmd_seq,
time_left_ms=lambda: (deadline - now()) * 1000.0,
calc_omega_fn=self.gps_handler.calcular_omega
)
idx_alvo_correcao = self._corrigir_pontos_visitados(x, y, pts_visitados, idx_proximo_ponto_real)
# -------------------- Planejamento: horizonte ESPACIAL fixo --------------------
S_ALVO = float(getattr(self, "horizonte", 2.5))
V_FLOOR = float(getattr(self, "_vmin_planejamento", 0.40))
DT_MIN = float(getattr(self, "_dt_pred_min", 0.05))
DT_MAX = float(getattr(self, "_dt_pred_max", 0.25))
K_ALVO = int(getattr(self, "_k_alvo", 16))
B_EST = int(getattr(self, "_branch_est", 18))
v_sim = max(velocidade, V_FLOOR)
# orçamento de avaliações (continua igual, para pacing)
est_ms = max(1.0, float(getattr(self, "_t_est_passo_ms", 6.0)))
exp_budget = max(4, int(ms_left() // est_ms))
# >>> NOVO: limites teóricos de K impostos por DT_MAX/DT_MIN
K_lb = max(1, int(math.ceil(S_ALVO / max(1e-6, v_sim * DT_MAX)))) # mínimo p/ cobrir S
K_ub = max(K_lb, int(math.floor(S_ALVO / max(1e-6, v_sim * DT_MIN)))) # cima (granularidade)
# >>> NOVO: NÃO deixe Nmax te travar abaixo do necessário
# se quiser ainda respeitar Nmax, aumente-o dinamicamente:
Nmax = max(Nmax, K_lb)
# teto “de intenção” (não crítico, só para não explodir absurdamente)
K_cap = min(Nmax, max(K_ALVO, K_lb))
# >>> escolha K dentro do [K_lb, K_cap]; pacing do orçamento vem depois
K = max(K_lb, min(K_cap, K_ALVO))
# dt para que K passos cubram S (agora garantido dentro de [DT_MIN, DT_MAX])
dt_pred = (S_ALVO / (v_sim * K))
dt_pred = max(DT_MIN, min(dt_pred, DT_MAX))
self.tempo_execucao_local = contexto.get("Carro", {}).get("TempoEntreComandos", 0.5)
self.passos_horizonte_local = max(1, math.floor(1 / self.tempo_execucao_local))
self.qtd_comandos_sucessivos = K
# -------------------- Seleção do comando --------------------
angulo_anterior = np.radians(comando_anterior.get("angulo", 0.0))
tipo_anterior = TipoMovimentoDirecional(comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value))
simulacao_latlon = []
ultimo_ponto = idx_proximo_ponto_real >= len(self.pontos_info)
if ultimo_ponto:
angulo_final, tipo_final = comando_parado()
parada_necessaria = True
else:
candidatos_ativos = [{
"x": x, "y": y, "theta": theta,
"custo": 0.0,
"comandos": [(tipo_anterior, angulo_anterior)],
"trajetoria": [],
"visitados": pts_visitados,
"inicial": True
}]
# Loop de K comandos (receding); cada comando simula dt_pred com v_sim
for passo in range(self.qtd_comandos_sucessivos):
if ms_left() <= 0:
break
novos_candidatos = []
# nota: 'exp_budget' é decrecido dentro da simulação
for candidato in candidatos_ativos:
if exp_budget <= 0 or ms_left() <= 0:
break
x_atual, y_atual, theta_atual = candidato["x"], candidato["y"], candidato["theta"]
visitados = deepcopy(candidato["visitados"])
cmd_anterior_local = {
"tipo": candidato["comandos"][-1][0],
"angulo": candidato["comandos"][-1][1],
}
idx_alvo = self._corrigir_pontos_visitados(x_atual, y_atual, visitados)
if idx_alvo < len(self.pontos_info) - 1: idx_alvo += 1
ponto_alvo = self.pontos_info[idx_alvo]
tipos, angs, custos_candidatos = self._gerar_angulos_candidatos_receding(
ponto_alvo, (x_atual, y_atual, theta_atual), (x, y, theta),
contexto, deadline, 15.0
)
if ms_left() <= 0 or exp_budget <= 0:
break
# ---------- seleção leve (ordena por heurística) ----------
ang_array = np.asarray(angs, dtype=np.float32)
graus_r = np.round(np.degrees(ang_array), 2)
from itertools import chain
pairs_tipo, pairs_ang, pairs_heur = [], [], []
for tipo in tipos:
tv = tipo.value
h_tmp = np.array(
[float(custos_candidatos.get((tv, float(g)), 0.0)) for g in graus_r],
dtype=np.float32
)
pairs_tipo.append([tipo] * ang_array.size)
pairs_ang.append(ang_array)
pairs_heur.append(h_tmp)
if not pairs_tipo:
continue
tipos_flat = list(chain.from_iterable(pairs_tipo))
angs_flat = np.concatenate(pairs_ang, axis=0)
heur_flat = np.concatenate(pairs_heur, axis=0)
if heur_flat.size == 0:
continue
ord_idx = np.argsort(heur_flat)
tempo_util_ms = max(0.0, ms_left() - 10.0)
est_local = max(1.0, min(float(getattr(self, "_t_est_passo_ms", 6.0)), 20.0))
Kmax = 12
Kdyn = int(min(Kmax, max(3, tempo_util_ms // est_local)))
Kdyn = min(Kdyn, ord_idx.size)
melhor_custo_local = float("inf")
for k in range(Kdyn):
if ms_left() <= 0 or exp_budget <= 0:
break
i = int(ord_idx[k])
tipo_k = tipos_flat[i]
ang_k = float(angs_flat[i])
heur_k = float(heur_flat[i])
t_c_ini = now()
try:
# >>> CHANGED: passa dt_pred e v_sim para a simulação de um PASSO
custo, sim, valido, visitados_sim = self._simular_passo(
x_atual, y_atual, theta_atual,
tipo_k, ang_k,
custos_candidatos,
cmd_anterior_local, contexto,
visitados,
dt_pred=dt_pred, # <-- NOVO
v_planejado=v_sim # <-- NOVO
)
if valido:
x_f, y_f, theta_f = sim[-1]
novo = {
"x": x_f, "y": y_f, "theta": theta_f,
"custo": candidato["custo"] + float(custo),
"comandos": candidato["comandos"] + [(tipo_k, ang_k)],
"trajetoria": candidato["trajetoria"] + sim,
"visitados": visitados_sim,
"inicial": False
}
novos_candidatos.append(novo)
if novo["custo"] < melhor_custo_local:
melhor_custo_local = novo["custo"]
except Exception as e:
mostrar_log(f"Erro ao simular passo {tipo_k.name} | {np.degrees(ang_k):.2f}: {e}")
finally:
# EMA do custo de tempo por passo (auto-tuning de est_ms)
dt_ms = (now() - t_c_ini) * 1000.0
alpha = float(getattr(self, "_ema_alpha", 0.2))
self._t_est_passo_ms = (1.0 - alpha)*float(getattr(self, "_t_est_passo_ms", 6.0)) + alpha*dt_ms
exp_budget -= 1
if exp_budget <= 0 or ms_left() <= 0:
break
# Filtro de diversidade angular e top-N
candidatos_ativos = self._filtrar_por_margem_angular(novos_candidatos, margem_graus=2.0)
N_top = 5 if exp_budget > 6 else 3
candidatos_ativos = self._selecionar_melhores(candidatos_ativos, N=N_top)
candidatos_validos = [c for c in candidatos_ativos if not c.get("inicial", False)]
if len(candidatos_validos) > 0:
try:
melhor = min(candidatos_validos, key=lambda c: c["custo"])
except Exception as e:
mostrar_log(f"Erro ao selecionar melhor candidato: {e}")
melhor = None
else:
melhor = None
parada_necessaria = (melhor is None)
if not parada_necessaria:
_comandos = melhor.get("comandos", [])
if len(_comandos) == 0:
angulo_final = 0.0
tipo_final = TipoMovimentoDirecional.RodasDianteiras
else:
idx_cmd = 0 if len(_comandos) == 1 else 1
tipo_final, angulo_final = _comandos[idx_cmd]
simulacao_latlon = list(self.gps_handler.converter_trajetoria_para_latlon(melhor["trajetoria"]) or [])
simulacao_latlon.insert(0, (pos_lat, pos_lon, pos_theta))
else:
angulo_final = 0.0
tipo_final = TipoMovimentoDirecional.RodasDianteiras
simulacao_latlon = []
_cmd = {
"enviar_comando": True,
"parada_necessaria": parada_necessaria,
"erro": False,
"latencia": pos_latencia,
"angulo": float(round(np.degrees(angulo_final), 2)),
"tipo": tipo_final.value,
"simulacao": simulacao_latlon
}
# -------------------- Debug opcional da matriz de custo --------------------
self._tick_id = getattr(self, "_tick_id", 0) + 1
if (getattr(self, "debug_cost_vis", False) and contexto.get("RegrasAtivas", {}).get("matriz_custo", False) and (self._tick_id % 10 == 0)):
dados_matriz_custo = contexto.get("VisualWorker", {}).get("MatrizCusto", {})
deadline_dbg = time.perf_counter() + 0.03
tipos_dbg, angs_dbg, _ = self._gerar_angulos_candidatos_receding(
ponto_alvo=self.pontos_info[idx_alvo_correcao],
ponto_atual=(x, y, theta),
ponto_ref=(x, y, theta),
contexto=contexto,
deadline=deadline_dbg,
margem_ms=8.0,
)
angs_dbg_deg = [round(float(np.degrees(a)), 2) for a in angs_dbg]
self.debug_plot_matriz_custo(
dados_matriz_custo,
tipos_dbg,
angs_dbg_deg,
max_linhas=3,
titulo=f"debug_cost_{int(self._tick_id):06d}.png",
highlight=(tipo_final.value, round(float(np.degrees(angulo_final)), 2)),
)
return _cmd
except Exception as e:
mostrar_log(f"❌ Erro ao processar MPC: {e}")
return self._comando_fallback_hot_stop(comando_anterior, latencia=pos_latencia)
def _verifica_tempo_maximo_execucao(self, t_init, processo):
limite = 1.0 / 5.0
@ -656,14 +1135,17 @@ class ControladorMPC:
mostrar_log(f"🚨 Tempo maximo de execucao excedido: {delta}/{limite}, processo: {processo}")
return hot_stop
def _comando_fallback_hot_stop(self, comando_anterior):
return {
"angulo": comando_anterior.get("angulo", 0),
"tipo": comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value),
"simulacao": [],
"parada_necessaria": True,
"erro": True
def _comando_fallback_hot_stop(self, comando_anterior, latencia=0):
_cmd = {
"enviar_comando": True,
"parada_necessaria": True,
"erro": True,
"latencia": latencia,
"angulo": comando_anterior.get("angulo", 0),
"tipo": comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value),
"simulacao": []
}
return _cmd
def _gerar_angulos_candidatos_receding(self, ponto_alvo, ponto_atual, ponto_ref, contexto, deadline, margem_ms):
"""
@ -680,11 +1162,26 @@ class ControladorMPC:
now = time.perf_counter
# 1) Ângulo ideal (wrap [-pi,pi] e clamp ao máx)
orient = self.gps_handler.calcular_orientacao(ponto_alvo, ponto_atual)
delta_theta = (orient - ponto_atual[2] + np.pi) % (2 * np.pi) - np.pi
orient = self.gps_handler.calcular_orientacao(ponto_alvo["xy"], ponto_atual)
delta_theta = (orient - ponto_atual[2] + np.pi) % (2*np.pi) - np.pi
#delta_theta, theta_ref, dbg = self.delta_por_dist_esq_dir(
# estado_atual=ponto_atual,
# ponto_alvo_xy=ponto_alvo["xy"],
# orient_corredor=np.radians(contexto["Carro"]["AnguloCaminho"]), # em rad
# dist_esq=contexto["Carro"]["DistanciaEsquerda"],
# dist_dir=contexto["Carro"]["DistanciaDireita"],
# angulo_max_graus=self.angulo_max_graus,
# sigma_factor=0.6,
# w_min=0.15,
# usar_nudge_centro=True,
# k_e=1.0, v=contexto["Carro"].get("Velocidade", 0.0), v0=0.3
#)
ang_max_rad = np.radians(self.angulo_max_graus)
angulo_ideal_rad = float(np.clip(delta_theta, -ang_max_rad, ang_max_rad))
angulo_ideal_deg = float(np.degrees(angulo_ideal_rad))
#print(f"Orient: {np.degrees(orient)}, delta theta: {np.degrees(delta_theta)}, angulo ideal: {angulo_ideal_deg}")
# 2) Tipos válidos conforme contexto
CARRO = contexto.get("Carro", {})
@ -1410,87 +1907,119 @@ class ControladorMPC:
mostrar_log(f"❌ Erro ao filtrar por margem angular: {e}")
return candidatos
def _simular_passo(self, x, y, theta, tipo, angulo_testado, custos_candidatos, comando_anterior, contexto, visitados):
# Custo pode variar de 0 ~ 13, em situacoes mais extremas, por passo
def _simular_passo(
self, x, y, theta,
tipo, angulo_testado,
custos_candidatos, comando_anterior,
contexto, visitados,
dt_pred, v_planejado
):
"""
Simula UM 'passo' do receding (um comando) cobrindo dt_pred de tempo,
subdividido em passos menores (sub_dt) para estabilidade geométrica.
O custo é acumulado 'por metro' (multiplicando por v_planejado * sub_dt).
"""
try:
custo_total = 0.0
x_sim, y_sim, theta_sim = x, y, theta
self.x_ant, self.y_ant = 0.0, 0.0
# chave para custo heurístico vindo do VisualWorker (por ângulo/tipo)
chave = (tipo.value, round(float(np.degrees(angulo_testado)), 2))
custo_visual_worker = float(custos_candidatos.get(chave, 0.0))
simulacoes = []
prev_ang = comando_anterior.get("angulo", 0.0)
prev_ang = float(comando_anterior.get("angulo", 0.0))
prev_tipo = comando_anterior.get("tipo", TipoMovimentoDirecional.RodasDianteiras.value)
pontos_visitados = visitados.copy()
velocidade = contexto.get("Carro", {}).get("Velocidade", 0.0)
pontos_visitados = deepcopy(visitados)
# Contexto usado no custo
angulo_caminho = np.radians(contexto.get("Carro", {}).get("AnguloCaminho", 0.0))
status_carro = StatusCarroMapa(contexto.get("Carro", {}).get("Status", StatusCarroMapa.Parado.value))
dentro_corredor = contexto.get("Carro", {}).get("DentroCorredor", False)
#omega_bkp = self.gps_handler.calcular_omega_bkp(velocidade, angulo_testado, tipo)
omega = self.gps_handler.calcular_omega(velocidade, angulo_testado, tipo)
#print(f"Omega novo: {omega}, Omega antigo: {omega_bkp}, Velocidade: {velocidade}, Angulo: {angulo_testado}, Tipo: {tipo.name}")
custo_visual_worker = custos_candidatos.get(chave, 0.0)
dentro_corredor = bool(contexto.get("Carro", {}).get("DentroCorredor", False))
for passo in range(self.passos_horizonte_local):
try:
#old_theta = theta_sim
x_sim, y_sim, theta_sim = self._nova_posicao(x_sim, y_sim, theta_sim, omega, velocidade, tipo, angulo_testado)
# omega e sub-stepping
omega_const = self.gps_handler.calcular_omega(v_planejado, angulo_testado, tipo)
simulacoes.append((x_sim, y_sim, theta_sim))
# Se alguém setar self.passos_horizonte_local > 1, a gente cobre tudo em dt_total:
passos_local = max(1, int(getattr(self, "passos_horizonte_local", 1)))
dt_total = float(dt_pred) * passos_local
idx_alvo_sim = self._corrigir_pontos_visitados(x_sim, y_sim, pontos_visitados)
ponto_alvo_sim = self.pontos_info[idx_alvo_sim]["xy"]
orient_sim = self.gps_handler.calcular_orientacao((x_sim, y_sim), ponto_alvo_sim)
orient_sim = (orient_sim + np.pi) % (2 * np.pi)
SUB_MAX = float(getattr(self, "_dt_sub_max", 0.08)) # ~80 ms por subpasso
n_sub = max(1, int(math.ceil(dt_total / SUB_MAX)))
sub_dt = dt_total / n_sub
dist_sub = v_planejado * sub_dt # usado para custo por metro
erro_pos = np.linalg.norm([x_sim - ponto_alvo_sim[0], y_sim - ponto_alvo_sim[1]])
peso_erro_orientacao_caminho = 0.3 if status_carro == StatusCarroMapa.CaminhandoRua and dentro_corredor else 0.7
erro_ori_sim = self.gps_handler.erro_angular(orient_sim, theta_sim, angulo_caminho, peso_erro_orientacao_caminho)
for _ in range(n_sub):
# IMPORTANTE: _nova_posicao precisa aceitar dt opcional (ver nota abaixo)
x_sim, y_sim, theta_sim = self._nova_posicao(
x_sim, y_sim, theta_sim,
omega_const, v_planejado, # ordem original: (omega, v, tipo, angulo)
tipo, angulo_testado,
dt=sub_dt # <<--- dt variável
)
pesos = self._calcular_pesos_movimento(contexto, erro_ori_sim)
peso_erro_pos, peso_erro_ori, peso_suavidade, peso_fator_re, peso_movimento = pesos[0], pesos[1], pesos[2], pesos[3], pesos[5]
simulacoes.append((x_sim, y_sim, theta_sim))
vetor_alvo = np.array(ponto_alvo_sim) - np.array([x_sim, y_sim])
vetor_alvo_norm = vetor_alvo / np.linalg.norm(vetor_alvo)
vetor_movel = np.array([np.cos(theta_sim), np.sin(theta_sim)])
cos_angulo = -np.dot(vetor_movel, vetor_alvo_norm)
delta_angulo = abs(angulo_testado - prev_ang)
# alvo mais próximo após avançar
idx_alvo_sim = self._corrigir_pontos_visitados(x_sim, y_sim, pontos_visitados)
ponto_alvo_sim = self.pontos_info[idx_alvo_sim]["xy"]
custo_pos = erro_pos * peso_erro_pos
custo_ori = ((erro_ori_sim / np.pi) * erro_pos) * peso_erro_ori
custo_suavidade = ((delta_angulo / np.pi) * erro_pos) * peso_suavidade
custo_tipo_movimento = peso_movimento[tipo]
erro_re = (1.0 - cos_angulo) if cos_angulo > 0 else cos_angulo
custo_re = -erro_re * erro_pos * peso_fator_re
custo_mapa = (custo_pos + custo_ori + custo_suavidade + custo_tipo_movimento + custo_re + custo_visual_worker)
# orientação do caminho + erro angular combinado
orient_sim = self.gps_handler.calcular_orientacao((x_sim, y_sim), ponto_alvo_sim)
orient_sim = (orient_sim + np.pi) % (2 * np.pi)
custo_total += custo_mapa
# erros
erro_pos = float(np.linalg.norm([x_sim - ponto_alvo_sim[0], y_sim - ponto_alvo_sim[1]]))
peso_erro_orientacao_caminho = 0.3 if (status_carro == StatusCarroMapa.CaminhandoRua and dentro_corredor) else 0.7
erro_ori_sim = self.gps_handler.erro_angular(orient_sim, theta_sim, angulo_caminho, peso_erro_orientacao_caminho)
prev_ang = angulo_testado
prev_tipo = tipo
# pesos dinâmicos
pesos = self._calcular_pesos_movimento(contexto, erro_ori_sim)
peso_erro_pos, peso_erro_ori, peso_suavidade, peso_fator_re, peso_movimento = pesos[0], pesos[1], pesos[2], pesos[3], pesos[5]
#if matriz_concluida:
# break
# ângulo relativo ao alvo e suavidade
vetor_alvo = np.array(ponto_alvo_sim) - np.array([x_sim, y_sim])
vetor_alvo_norm = vetor_alvo / (np.linalg.norm(vetor_alvo) + 1e-9)
vetor_movel = np.array([np.cos(theta_sim), np.sin(theta_sim)])
cos_angulo = -float(np.dot(vetor_movel, vetor_alvo_norm))
delta_angulo = abs(angulo_testado - prev_ang)
#print(f"Passo: {passo}, Alvo: {idx_alvo_sim}, Distancia: {((passo + 1) * distancia_m):.2f} m, Theta: {theta_sim:.2f}°, DX: {(x - x_sim):.2f} m, DY: {(y - y_sim):.2f} m, y_sim: {y_sim:.4f}, x_sim: {x_sim:.4f}, Tipo: {tipo.name}, Angulo: {np.degrees(angulo_testado):.2f}, custo: {custo_total}")
#mostrar_log(f"Omega: {np.degrees(omega):.2f}°, Ori: {np.degrees(orient_sim):.2f}°, E Ori: {np.degrees(erro_ori_sim):.3f}°, E Pos: {erro_pos:.3f}, F Re: {erro_re:.3f}, d°: {np.degrees(delta_angulo):.2f}, CM: {custo_mapa:.4f}, CP: {custo_pos:.4}, CO: {custo_ori:.4f}, CT: {custo_tipo_movimento:.4f}, CS: {custo_suavidade:.4f}, CR: {custo_re:.4f}")
# componentes de custo
custo_pos = erro_pos * peso_erro_pos
custo_ori = ((erro_ori_sim / np.pi) * erro_pos) * peso_erro_ori
custo_suavidade = ((delta_angulo / np.pi) * erro_pos) * peso_suavidade
custo_tipo_movimento = float(peso_movimento[tipo])
erro_re = (1.0 - cos_angulo) if cos_angulo > 0 else cos_angulo
custo_re = -erro_re * erro_pos * peso_fator_re
except Exception as e:
mostrar_log(f"❌ Erro ao simular passo {passo}, {tipo.name}, {np.degrees(angulo_testado):.2f}: {e}")
# soma local (heurística do visual + demais), e NORMALIZA por metro
custo_mapa = (custo_pos + custo_ori + custo_suavidade + custo_tipo_movimento + custo_re + custo_visual_worker)
custo_total += custo_mapa * dist_sub # <<< custo POR METRO
prev_ang = angulo_testado
prev_tipo = tipo
return float(custo_total), simulacoes, True, pontos_visitados
return custo_total, simulacoes, True, pontos_visitados
except Exception as e:
mostrar_log(f"Erro ao simular passo para {tipo.name} | angulo: {np.degrees(angulo_testado):.2f}: {e}")
return float('inf'), [(0, 0, 0)], False, pontos_visitados
return float('inf'), [(0.0, 0.0, 0.0)], False, visitados
def _nova_posicao(self, x, y, theta, omega, velocidade, tipo, angulo_rad):
distancia_m = velocidade * self.dt
def _nova_posicao(self, x, y, theta, omega, velocidade, tipo, angulo_rad, dt=None):
"""
Atualiza a pose integrando por 'dt' (se None, cai em self.dt).
Mantém a ordem dos parâmetros antigos para não quebrar chamadas existentes.
"""
dt_use = self.dt if (dt is None) else float(dt)
distancia_m = velocidade * dt_use
theta_sim = theta + (omega * self.dt)
theta_sim = theta + (omega * dt_use)
x_sim = x + (np.sin(theta_sim) * distancia_m)
y_sim = y + (np.cos(theta_sim) * distancia_m)
return x_sim, y_sim, theta_sim
def _prefiltrar_angulos_por_matriz_quick(
self,
angulos_deg, # iterable de graus

View File

@ -1,3 +1,4 @@
import time
from shared.contexto_global_redis import ContextoGlobalRedis
from manager_worker.modulos.pid import PIDAdaptativo
from .base import ProcessadorBase
@ -20,18 +21,25 @@ class ProcessadorEmAndamento(ProcessadorBase):
saida_min=-ang_max,
saida_max=ang_max
)
self.ultimo_comando_enviado = time.time()
def processar(self):
try:
angulo_sp, tipo_dir, simulacao, parada_necessaria, erro = definir_comando_dir(self.pid_dir)
velocidade_sp = definir_comando_mov(parada_necessaria, erro, self.filtro_mov)
return comando_controle(
percentual_velocidade=velocidade_sp,
angulo=angulo_sp,
tipo_movimento=tipo_dir,
simulacao=simulacao,
erro=erro
)
agora = time.time()
envio_necessario = (agora - self.ultimo_comando_enviado) > 0.3
comando_dir = definir_comando_dir(self.pid_dir, envio_necessario)
if comando_dir.get("enviar_comando", False):
self.ultimo_comando_enviado = agora
velocidade_sp = definir_comando_mov(comando_dir.get("parada_necessaria", False), comando_dir.get("erro", False), self.filtro_mov)
return comando_controle(
percentual_velocidade=velocidade_sp,
angulo=comando_dir.get("angulo"),
tipo_movimento=comando_dir.get("tipo"),
simulacao=comando_dir.get("simulacao"),
erro=comando_dir.get("erro", False),
latencia=comando_dir.get("latencia", 0.0)
)
except Exception as e:
print(f"Erro no processador EmAndamento: {e}")
return None

View File

@ -3,7 +3,7 @@ from shared.enums import ModoOperacao, StatusOperacao
from manager_worker.config import mostrar_log
from shared.contexto_global_redis import ContextoGlobalRedis, CtxKey
def comando_controle(percentual_velocidade: float, angulo: float, tipo_movimento: int, simulacao = [], erro: bool = False):
def comando_controle(percentual_velocidade: float, angulo: float, tipo_movimento: int, simulacao = [], erro: bool = False, latencia: float = 0.0):
hb_atual = ContextoGlobalRedis.get_controle().get("heartbeat", 0)
if erro == False:
try:
@ -18,14 +18,16 @@ def comando_controle(percentual_velocidade: float, angulo: float, tipo_movimento
tipo_movimento_direcional=tipo_movimento,
velocidade_sp=percentual_velocidade,
simulacao=simulacao,
heartbeat=hb_atual
heartbeat=hb_atual,
latencia=latencia
)
return {
"velocidade_sp": percentual_velocidade,
"angulo_sp": angulo,
"tipo_movimento_direcional": tipo_movimento,
"simulacao": simulacao,
"heartbeat": hb_atual
"heartbeat": hb_atual,
"latencia": latencia
}
def comando_parado():

Some files were not shown because too many files have changed in this diff Show More