From f3cca4c54f31724ae8db17e0412e95368d1214d7 Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Thu, 25 Jun 2026 15:17:42 -0300 Subject: [PATCH] =?UTF-8?q?ajustado=20controle=20de=20press=C3=A3o=20da=20?= =?UTF-8?q?bomba=20pid?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Operacoes/frmOperacaoManual.Designer.cs | 30 + .../Forms/Operacoes/frmOperacaoManual.cs | 8 +- .../AgroBase/Models/Modules/AtuadorModel.cs | 228 ++-- .../AgroBase/Models/Modules/PinoutModel.cs | 2 + .../Models/Operacoes/OperacaoModel.cs | 463 ++++--- Firmware/Atuador/Atuador_v14/Atuador_v14.ino | 5 +- .../Modulos/BombaPressurizadoraModel_v2.h | 1109 +++++++++++++---- Firmware/Modulos/SensorPressaoModel_v2.h | 10 +- 8 files changed, 1348 insertions(+), 507 deletions(-) diff --git a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.Designer.cs b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.Designer.cs index 5d9515f4d..4851bd67f 100644 --- a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.Designer.cs +++ b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.Designer.cs @@ -104,6 +104,8 @@ this.chbSonarAtivado = new System.Windows.Forms.CheckBox(); this.lblPressao = new System.Windows.Forms.Label(); this.chbImuAtivado = new System.Windows.Forms.CheckBox(); + this.lblPressaoBomba = new System.Windows.Forms.Label(); + this.lblVazaoInstantanea = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.chartGraficos)).BeginInit(); this.tabControl1.SuspendLayout(); this.tabMapa.SuspendLayout(); @@ -500,6 +502,8 @@ // // groupBox2 // + this.groupBox2.Controls.Add(this.lblVazaoInstantanea); + this.groupBox2.Controls.Add(this.lblPressaoBomba); this.groupBox2.Controls.Add(this.label7); this.groupBox2.Controls.Add(this.nudPotenciaBombaMax); this.groupBox2.Controls.Add(this.label6); @@ -949,6 +953,30 @@ this.chbImuAtivado.UseVisualStyleBackColor = true; this.chbImuAtivado.CheckedChanged += new System.EventHandler(this.chbImuAtivado_CheckedChanged); // + // lblPressaoBomba + // + this.lblPressaoBomba.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.lblPressaoBomba.AutoSize = true; + this.lblPressaoBomba.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblPressaoBomba.Location = new System.Drawing.Point(305, 17); + this.lblPressaoBomba.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.lblPressaoBomba.Name = "lblPressaoBomba"; + this.lblPressaoBomba.Size = new System.Drawing.Size(76, 25); + this.lblPressaoBomba.TabIndex = 48; + this.lblPressaoBomba.Text = "80 psi"; + // + // lblVazaoInstantanea + // + this.lblVazaoInstantanea.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.lblVazaoInstantanea.AutoSize = true; + this.lblVazaoInstantanea.Font = new System.Drawing.Font("Microsoft Sans Serif", 15.75F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblVazaoInstantanea.Location = new System.Drawing.Point(305, 42); + this.lblVazaoInstantanea.Margin = new System.Windows.Forms.Padding(2, 0, 2, 0); + this.lblVazaoInstantanea.Name = "lblVazaoInstantanea"; + this.lblVazaoInstantanea.Size = new System.Drawing.Size(108, 25); + this.lblVazaoInstantanea.TabIndex = 49; + this.lblVazaoInstantanea.Text = "0,00 ml/s"; + // // frmOperacaoManual // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -1088,5 +1116,7 @@ private System.Windows.Forms.Label label7; private System.Windows.Forms.NumericUpDown nudPotenciaBombaMax; private System.Windows.Forms.CheckBox chbImuAtivado; + private System.Windows.Forms.Label lblPressaoBomba; + private System.Windows.Forms.Label lblVazaoInstantanea; } } \ No newline at end of file diff --git a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs index 46544acc8..abd84c8d9 100644 --- a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs +++ b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs @@ -189,6 +189,8 @@ namespace AgroBase.Forms.Operacoes lblRPM.Text = _Sensoriamento.Movimentacao.RPMMedio.ToString("0") + " rpm"; lblVelocidade.Text = (_Sensoriamento.Movimentacao.VelocidadeMedia * 3.6).ToString("0.00"); lblPressao.Text = $"{(_Sensoriamento.Atuador.PressaoLinha):F0} psi"; + lblPressaoBomba.Text = $"{(_Sensoriamento.Atuador.PressaoAtualBomba):F0} psi"; + lblVazaoInstantanea.Text = $"{(_Sensoriamento.Atuador.VazaoFluxoMLpSInstantaneaCorrigida):F0} ml/s"; bool atuadorAtivado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.Conectado; chbPulverizador.Enabled = atuadorAtivado; @@ -587,9 +589,9 @@ namespace AgroBase.Forms.Operacoes gridDados.Rows[29].Cells[1].Value = (_Atuador?.PressaoLinha ?? 0).ToString("0.00"); gridDados.Rows[30].Cells[1].Value = (_Atuador?.LeituraPotenciaBomba ?? 0).ToString("0.00"); gridDados.Rows[31].Cells[1].Value = (_Atuador?.BombaCavitada ?? false) ? "Sim" : "Não"; - gridDados.Rows[32].Cells[1].Value = $"{_Atuador?.VazaoInstantanea ?? 0:0.000} ({Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.VazaoFluxoMLpSInstantaneaCalc:0.000})"; - gridDados.Rows[33].Cells[1].Value = $"{_Atuador?.VazaoMedia ?? 0:0.000}"; - gridDados.Rows[34].Cells[1].Value = (_Atuador?.VolumeVazaoMl ?? 0).ToString("0.00"); + gridDados.Rows[32].Cells[1].Value = $"{_Atuador?.VazaoFluxoMLpSInstantanea ?? 0:0.000} ({_Atuador?.VazaoFluxoMLpSInstantaneaCorrigida ?? 0:0.000})"; + gridDados.Rows[33].Cells[1].Value = $"{_Atuador?.VazaoFluxoMLpSMedia ?? 0:0.000}"; + gridDados.Rows[34].Cells[1].Value = (_Atuador?.VolumeVazaoML ?? 0).ToString("0.00"); gridDados.Rows[35].Cells[1].Value = (_Atuador?.AnguloBarraEsquerda ?? 0).ToString("0.00"); gridDados.Rows[36].Cells[1].Value = (_Atuador?.AnguloBarraDireita ?? 0).ToString("0.00"); gridDados.Rows[37].Cells[1].Value = (Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.AgitadorDeCalda?._Potencia ?? 0).ToString("0.00"); diff --git a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs index ad6ae29f3..9c8bc11e7 100644 --- a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs +++ b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs @@ -121,11 +121,11 @@ namespace AgroBase.Models.Modules [JsonIgnore] private SensorModel SensorFluxoLinha => Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX && x.ID == "FLXLN"); - const double FatorCalibracaoFluxo = 0.776; + const double FatorCalibracaoFluxo = 0.55; public double VazaoMaximaSensor_LMin { get; set; } = 20.0; - public double VazaoFluxoMLpSInstantanea => SensorFluxoLinha?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0; + public double VazaoFluxoMLpSInstantanea => SensorFluxoLinha?.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0; public double VazaoFluxoMLpSInstantaneaCorrigida => VazaoFluxoMLpSInstantanea * FatorCalibracaoFluxo; @@ -188,12 +188,12 @@ namespace AgroBase.Models.Modules public double _PressaoVmin { get; set; } = 0.0; public double _PressaoVmax { get; set; } = 4.5; public double _PressaoMax { get; set; } = 174.045; // 1,2 MPa - public double PressaoLinha => SensorPressaoLinha?.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; + public double PressaoLinha => SensorPressaoLinha?.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; public double PressaoLinhaCalc { get { - double _LeituraPressao = SensorPressaoLinha?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.LeituraBruta)?.atual?.valor ?? 0; + double _LeituraPressao = SensorPressaoLinha?.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.LeituraBruta)?.atual?.valor ?? 0; double tensao = FuncoesMatematicas.Map(_LeituraPressao, 0, 4095, 0, 3300) / 1000.0; double pressao = FuncoesMatematicas.Map(tensao, _PressaoVmin, _PressaoVmax, 0, _PressaoMax); return pressao; @@ -231,22 +231,15 @@ namespace AgroBase.Models.Modules { var sensor = SensorFluxoLinha; - if (sensor == null || - !sensor.Inicializado || - sensor.ValoresLeituras == null) - { + if (sensor == null || !sensor.Inicializado || sensor.ValoresLeituras == null) return 0; - } double maximaMLs = VazaoMaximaSensor_LMin * 1000.0 / 60.0; - var leituraFluxo = - sensor.ValoresLeituras.FirstOrDefault( - x => x.funcao == FuncoesPinout.Fluxo - ); + var leituraFluxo = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Fluxo); /* * IMPORTANTE: @@ -262,10 +255,7 @@ namespace AgroBase.Models.Modules try { - vazaoMLs = - Convert.ToDouble( - leituraFluxo.atual.valor - ); + vazaoMLs = Convert.ToDouble(leituraFluxo.atual.valor); } catch { @@ -279,8 +269,10 @@ namespace AgroBase.Models.Modules return 0; } + double vazaoCorrigidaMLs = vazaoMLs * FatorCalibracaoFluxo; + return FuncoesMatematicas.Clamp( - vazaoMLs, + vazaoCorrigidaMLs, 0.0, maximaMLs ); @@ -290,32 +282,19 @@ namespace AgroBase.Models.Modules * Fallback por pulso: * aqui sim a fórmula calcula q em L/min e depois converte para mL/s. */ - var leituraPulsos = - sensor.ValoresLeituras.FirstOrDefault( - x => x.funcao == FuncoesPinout.QtdPulsos - ); + var leituraPulsos = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.QtdPulsos); + var leituraPeriodo = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Periodo); - var leituraPeriodo = - sensor.ValoresLeituras.FirstOrDefault( - x => x.funcao == FuncoesPinout.Periodo - ); - - if (!LeituraSensorRecente(leituraPulsos, agora) || - !LeituraSensorRecente(leituraPeriodo, agora)) - { + if (!LeituraSensorRecente(leituraPulsos, agora) || !LeituraSensorRecente(leituraPeriodo, agora)) return 0; - } + double calculadaMLs = VazaoFluxoMLpSInstantaneaCalc; - if (double.IsNaN(calculadaMLs) || - double.IsInfinity(calculadaMLs) || - calculadaMLs < 0) - { + if (double.IsNaN(calculadaMLs) || double.IsInfinity(calculadaMLs) || calculadaMLs < 0) return 0; - } - + return FuncoesMatematicas.Clamp( calculadaMLs, 0.0, @@ -325,44 +304,27 @@ namespace AgroBase.Models.Modules private double ObterPotenciaAtualConfiavelPct(AtuadorBombaModel bomba, DateTime agora) { - if (bomba == null || - !bomba.Inicializado || - bomba.ValoresLeituras == null) - { + if (bomba == null || !bomba.Inicializado || bomba.ValoresLeituras == null) return 0; - } - var leituraPotencia = - bomba.ValoresLeituras.FirstOrDefault( - x => x.funcao == FuncoesPinout.Potencia - ); + var leituraPotencia = bomba.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Potencia); - if (!LeituraSensorRecente( - leituraPotencia, - agora)) - { + if (!LeituraSensorRecente(leituraPotencia, agora)) return 0; - } double potencia; try { - potencia = - Convert.ToDouble( - leituraPotencia.atual.valor - ); + potencia = Convert.ToDouble(leituraPotencia.atual.valor); } catch { return 0; } - if (double.IsNaN(potencia) || - double.IsInfinity(potencia)) - { + if (double.IsNaN(potencia) || double.IsInfinity(potencia)) return 0; - } return FuncoesMatematicas.Clamp( potencia, @@ -1168,6 +1130,13 @@ namespace AgroBase.Models.Modules posicao = CanMessagePosicaoDados.Status, indice = 0, }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.MalhaFechada, + posicao = CanMessagePosicaoDados.Status, + indice = 1, + }, + new SensorValoresLeituraModel() { funcao = FuncoesPinout.EstadoLeitura, @@ -1203,7 +1172,44 @@ namespace AgroBase.Models.Modules funcao = FuncoesPinout.ModoControleBomba, posicao = CanMessagePosicaoDados.Dados1, indice = 5, - } + }, + + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.QtdPulsos, + posicao = CanMessagePosicaoDados.Dados2, + indice = 0, + }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.PotenciaSP, + posicao = CanMessagePosicaoDados.Dados2, + indice = 1, + }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.Potencia, + posicao = CanMessagePosicaoDados.Dados2, + indice = 2, + }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.Pressao, + posicao = CanMessagePosicaoDados.Dados2, + indice = 3, + }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.PressaoSP, + posicao = CanMessagePosicaoDados.Dados2, + indice = 4, + }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.PressaoSPExtra, + posicao = CanMessagePosicaoDados.Dados2, + indice = 5, + }, }, Controle = new AtuadorBombaModel.BombaModoControleModel() { @@ -1237,6 +1243,13 @@ namespace AgroBase.Models.Modules posicao = CanMessagePosicaoDados.Status, indice = 0, }, + new SensorValoresLeituraModel() + { + funcao = FuncoesPinout.MalhaFechada, + posicao = CanMessagePosicaoDados.Status, + indice = 1, + }, + new SensorValoresLeituraModel() { funcao = FuncoesPinout.EstadoLeitura, @@ -2256,7 +2269,7 @@ namespace AgroBase.Models.Modules var sensor = Sensores.FirstOrDefault(x => x.ID_Num == id_num); if (sensor == null) return; - double angulo = sensor.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.LeituraAngulo)?.atual?.valor ?? double.NaN; + double angulo = sensor.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.LeituraAngulo)?.atual?.valor ?? double.NaN; if (sensor.ID_Num == SensorAnguloBarraEsquerda.ID_Num) AnguloBarraEsquerda = angulo; else if (sensor.ID_Num == SensorAnguloBarraDireita.ID_Num) @@ -2302,8 +2315,8 @@ namespace AgroBase.Models.Modules } } public List ValoresLeituras { get; set; } - public Estado _EstadoLeitura => (Estado)(ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? 0); - public double _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual? .valor ?? 0; + public Estado _EstadoLeitura => (Estado)(ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? 0); + public double _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual? .valor ?? 0; public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue; public bool UltimoStatusDesejado { get; set; } = false; @@ -2517,12 +2530,19 @@ namespace AgroBase.Models.Modules } public List ValoresLeituras { get; set; } public BombaModoControleModel Controle { get; set; } - public Estado _EstadoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? Estado.Desligado; - public double _Potencia => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0; + public Estado _EstadoLeitura => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? Estado.Desligado; + public double _Potencia => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0; public bool _BombaLigada => _Potencia > 0; - public double _PressaoSP => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.PressaoSP)?.atual?.valor ?? 0; - public double _PotenciaSP => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.PotenciaSP)?.atual?.valor ?? 0; - public double _PressaoAtual => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; + public double _PressaoSP => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.PressaoSP)?.atual?.valor ?? 0; + public double _PotenciaSP => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.PotenciaSP)?.atual?.valor ?? 0; + public double _PressaoAtual => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; + + public int _QtdBicosLigados => (int)(ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.QtdPulsos)?.atual?.valor ?? 0); + public double _PotenciaFeedForwared => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.PotenciaSP)?.atual?.valor ?? 0; + public double _PotenciaFinal => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0; + public double _PressaoSensor => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; + public double _PressaoSPControle => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.PressaoSP)?.atual?.valor ?? 0; + public bool _PressaoSPForaDoMapa => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.PressaoSPExtra)?.atual?.valor ?? false; public int Atuacoes { get; set; } @@ -2623,10 +2643,10 @@ namespace AgroBase.Models.Modules { public ModoControleBomba Modo { get; set; } = ModoControleBomba.OnOff; - public double PotenciaMinPID { get; set; } = 30.0; + public double PotenciaMinPID { get; set; } = 40.0; - public double Kp { get; set; } = 6.0; - public double Ki { get; set; } = 0.2; + public double Kp { get; set; } = 0.08; + public double Ki { get; set; } = 0.008; public double Kd { get; set; } = 0.0; } @@ -3060,20 +3080,66 @@ namespace AgroBase.Models.Modules { case CanMessagePosicaoDados.Status: { - bool Iniciado = data[2] == 1; - bool MalhaFechada = data[3] == 1; - AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); + bool iniciado = data.Length > 2 && data[2] == 1; + bool malhaFechada = data.Length > 3 && data[3] == 1; + AtualizarDados( + ID_Num, + posicao, + componente, + new List() { + iniciado, + malhaFechada, + } + ); break; } case CanMessagePosicaoDados.Dados1: { - Estado estado = (Estado)data[2]; - int potenciaSp = data[3]; - int percentualPotencia = data[4]; - int pressaoSp = data[5]; - int pressaoAtual = data[6]; - ModoControleBomba modo = (ModoControleBomba)data[7]; - AtualizarDados(ID_Num, posicao, componente, new List() { estado, potenciaSp, percentualPotencia, pressaoSp, pressaoAtual, modo }); + Estado estado = data.Length > 2 ? (Estado)data[2] : Estado.Desligado; + int potenciaSp = data.Length > 3 ? data[3] : 0; + int percentualPotencia = data.Length > 4 ? data[4] : 0; + int pressaoSp = data.Length > 5 ? data[5] : 0; + int pressaoAtual = data.Length > 6 ? data[6] : 0; + ModoControleBomba modo = data.Length > 7 ? (ModoControleBomba)data[7] : ModoControleBomba.OnOff; + AtualizarDados( + ID_Num, + posicao, + componente, + new List() + { + estado, + potenciaSp, + percentualPotencia, + pressaoSp, + pressaoAtual, + modo + } + ); + break; + } + case CanMessagePosicaoDados.Dados2: + { + int qtdBicosLigados = data.Length > 2 ? data[2] : 0; + int potenciaFeedForward = data.Length > 3 ? data[3] : 0; + int potenciaFinal = data.Length > 4 ? data[4] : 0; + int pressaoSensor = data.Length > 5 ? data[5] : 0; + int pressaoSpControle = data.Length > 6 ? data[6] : 0; + bool pressaoSpForaDoMapa = data.Length > 7 && data[7] == 1; + AtualizarDados( + ID_Num, + posicao, + componente, + new List() + { + qtdBicosLigados, + potenciaFeedForward, + potenciaFinal, + pressaoSensor, + pressaoSpControle, + pressaoSpForaDoMapa + } + ); + break; } } diff --git a/AgroBase/AgroBase/Models/Modules/PinoutModel.cs b/AgroBase/AgroBase/Models/Modules/PinoutModel.cs index 10090c34b..19249c3b1 100644 --- a/AgroBase/AgroBase/Models/Modules/PinoutModel.cs +++ b/AgroBase/AgroBase/Models/Modules/PinoutModel.cs @@ -440,6 +440,8 @@ namespace AgroBase.Models.Modules PotenciaSP, ModoControleBomba, Botao, + MalhaFechada, + PressaoSPExtra, } public static class PinoutUtils diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 65001989a..715b9d645 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -3022,41 +3022,7 @@ namespace AgroBase.Models ); } - AtuSensoriamentoLogModel logAtu = new AtuSensoriamentoLogModel() - { - Momento = Agora, - Inicializado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ConexaoAtiva, - QualidadeWifi = Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.QualidadeWifi, - LatenciaLoop = Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.LatenciaLoop, - MassaReservatorio = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.MassaAtual_Kg, - PercentualReservatorio = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.PercentualAtual, - VolumeReservatorio = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.VolumeAtual_L, - PercentualHerbicidaConsumido = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.PercentualConsumidoTotal, - PercentualHerbicidaConsumidoOperacao = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.PercentualConsumidoOperacao, - HerbicidaPorAtuacao = Variaveis.OperacaoEmAndamento.DispAtu.Dados.ContadorCarga.HerbicidaPorAtuacao_L, - PressaoLinha_SP = Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha, - PressaoLinha = Variaveis.OperacaoEmAndamento.DispAtu.Dados.PressaoLinha, - PotenciaBomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?._Potencia ?? 0, - BombaLigada = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?.ComandoAtuar ?? false, - VazaoFluxoMLpSInstantanea = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoFluxoMLpSInstantaneaCorrigida, - VazaoFluxoMLpSMedia = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoMediaMLs, - VolumeVazaoML = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VolumeVazadoML, - QuantidadeBicos = Variaveis.OperacaoEmAndamento.Parametros.QtdBicos ?? VariaveisEquipamento.QuantidadeBicosPulverizadores, - TempoPulverizadorAtuado = Variaveis.OperacaoEmAndamento.DispAtu.Dados.TempoAtuado, - Bicos = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BicosPulverizadores.Select(Bico => new AtuBicoSensoriamentoLogModel() - { - ID = Bico.ID, - Inicializado = Bico.Inicializado, - Comandar = Bico.Comandar, - Atuado = Bico.ComandoAtuar, - MediaNivelFluxo = Bico.VazaoMediaMLs, - Posicao = Bico.Posicao, - StatusBico = Bico._EstadoLeitura == Estado.Ligado, - Atuacoes = Bico.Atuacoes, - TempoAtuado = Bico.TempoAtuado, - }).ToList(), - UltimoComandoRespondido = Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.Momento, - }; + var logAtu = AtuSensoriamentoLogModel.MontarLogAtuador(Agora); SalvarLogLine(T_Code.Atu.ToString(), logAtu); var senLeitura = Variaveis.OperacaoEmAndamento.DispSen.Dados; @@ -3640,7 +3606,7 @@ namespace AgroBase.Models } } public ModuloIMUModel IMU { get; set; } - public OperacaoSensoriamentoLogAtuModel Atuador { get; set; } + public AtuSensoriamentoLogModel Atuador { get; set; } public OperacaoSensoriamentoLogMovModel Movimentacao { get; set; } public OperacaoSensoriamentoLogDirModel Direcional { get; set; } public OperacaoSensoriamentoLogBateriaModel Bateria { get; set; } @@ -3725,56 +3691,8 @@ namespace AgroBase.Models bool imu_iniciado = sIMU?.timestamp > 0 && HealthWorkerService.ModulosSaude.FirstOrDefault(x => x.modulo == T_Code.Imu)?.status != StatusModulo.Desconectado; var dadosIMU = sIMU != null && imu_iniciado ? sIMU.Clone() : new ModuloIMUModel(); - var bombaLinha = _DispAtu?.Dados?.BombaPressurizadora; - var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel() - { - QtdCamerasSolo = CameraWorkerService.CamerasSolo.Count, - CapacidadeReservatorio = VariaveisEquipamento.CapacidadeReservatorio, - HerbicidaConsumido = _Reservatorio.VolumeConsumidoTotal_L, - HerbicidaConsumidoOperacao = _Reservatorio.VolumeConsumidoOperacao_L, - PercentualHerbicidaConsumido = _Reservatorio.PercentualConsumidoTotal, - PercentualHerbicidaConsumidoOperacao = _Reservatorio.PercentualConsumidoOperacao, - PercentualErvasTerreno = _DispAtu.Dados.PercentualErvasTerreno, - HerbicidaPorErva = _Reservatorio.HerbicidaPorAtuacao_L, - ErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.ervas_no_radar ?? false, - PercentualErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.percentual_ervas_no_radar ?? 0, - PercentualReservatorio = _Reservatorio.PercentualAtual, - VolumeReservatorio = _Reservatorio.VolumeAtual_L, - MassaReservatorio = _Reservatorio.MassaAtual_Kg, - PressaoLinha = _DispAtu.Dados.PressaoLinha, - PressaoLinhaCalc = _DispAtu.Dados.PressaoLinhaCalc, - VazaoInstantanea = _DispAtu.Dados.VazaoFluxoMLpSInstantaneaCorrigida, - VazaoInstantaneaCalc = _DispAtu.Dados.VazaoFluxoMLpSInstantaneaCalc, - VazaoMedia = _DispAtu.Dados.VazaoMediaMLs, - TempoBombaAtuada = _DispAtu.Dados.TempoAtuado, - ComandoBomba = bombaLinha?.ComandoAtuar ?? false, - LeituraComandoBomba = bombaLinha?._EstadoLeitura == Estado.Ligado, - LeituraPotenciaBomba = FuncoesMatematicas.Clamp(bombaLinha?._Potencia ?? 0, 0, 100), - VolumeVazaoMl = _DispAtu.Dados.VolumeVazadoML, - BombaCavitada = _DispAtu.Dados.Conectado && _DispAtu.Dados.PressaoLinha < pControle?.AtuPressaoLinha && _DispAtu.Dados.PressaoLinha < VariaveisEquipamento.PressaoMinimaCavitacao && bombaLinha?._EstadoLeitura == Estado.Ligado && bombaLinha?._Potencia > 20, - Iniciados = _DispAtu.Dados.Sensores.Select(s => new { s.ID, s.Inicializado }).Union(_DispAtu.Dados.BicosPulverizadores.Select(b => new { b.ID, b.Inicializado })).Union(_DispAtu.Dados.BombasPressurizadoras.Select(b => new { b.ID, b.Inicializado })).ToDictionary(x => x.ID, x => x.Inicializado), - AnguloBarraEsquerda = _DispAtu.Dados.AnguloBarraEsquerda, - AnguloBarraDireita = _DispAtu.Dados.AnguloBarraDireita, - UltimoComandoRespondido = _DispAtu.Dados.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue, - LPorMetro = _Reservatorio.LPorMetro, - LPorMinuto = _Reservatorio.LPorMinuto, - TempoEstimadoRestanteMinutos = _Reservatorio.TempoEstimadoMinutosPulverizando, - DistanciaEstimadaRestanteMetros = _Reservatorio.DistanciaEstimadaRestantePulverizando_m, - Bicos = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BicosPulverizadores.Select(Bico => new AtuBicoSensoriamentoLogModel() - { - ID = Bico.ID, - Inicializado = Bico.Inicializado, - Comandar = Bico.Comandar, - Atuado = Bico.ComandoAtuar, - MediaNivelFluxo = Bico.VazaoMediaMLs, - Posicao = Bico.Posicao, - StatusBico = Bico._EstadoLeitura == Estado.Ligado, - Atuacoes = Bico.Atuacoes, - TempoAtuado = Bico.TempoAtuado, - AnguloAbertura = Bico.AnguloAbertura, - AnguloControle = Bico.AnguloControle, - }).ToList(), - } : new OperacaoSensoriamentoLogAtuModel(); + var logAtu = AtuSensoriamentoLogModel.MontarLogAtuador(DateTime.Now); + var dadosAtuador = logAtu.Clone(); var dadosMovimentacao = _DispMvd != null ? new OperacaoSensoriamentoLogMovModel() { @@ -4040,83 +3958,6 @@ namespace AgroBase.Models } } - public class OperacaoSensoriamentoLogAtuModel - { - public int QtdCamerasSolo { get; set; } - public double CapacidadeReservatorio { get; set; } - public double HerbicidaConsumido { get; set; } - public double HerbicidaConsumidoOperacao { get; set; } - public double PercentualHerbicidaConsumido { get; set; } - public double PercentualHerbicidaConsumidoOperacao { get; set; } - 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 MassaReservatorio { get; set; } - public double VolumeReservatorio { get; set; } - public double PressaoLinha { get; set; } - public double PressaoLinhaCalc { get; set; } - public double VazaoInstantanea { get; set; } - public double VazaoInstantaneaCalc { get; set; } - public double VazaoMedia { get; set; } - public double TempoBombaAtuada { get; set; } - public bool ComandoBomba { get; set; } - public bool LeituraComandoBomba { get; set; } - public double LeituraPotenciaBomba { get; set; } - public double VolumeVazaoMl { get; set; } - public double AnguloBarraEsquerda { get; set; } - public double AnguloBarraDireita { get; set; } - public double LPorMetro { get; set; } - public double LPorMinuto { get; set; } - public double TempoEstimadoRestanteMinutos { get; set; } - public double DistanciaEstimadaRestanteMetros { get; set; } - public bool BombaCavitada { get; set; } - public Dictionary Iniciados { get; set; } - public List Bicos { get; set; } - public DateTime UltimoComandoRespondido { get; set; } - - public OperacaoSensoriamentoLogAtuModel Clone() - { - return new OperacaoSensoriamentoLogAtuModel() - { - QtdCamerasSolo = QtdCamerasSolo, - CapacidadeReservatorio = CapacidadeReservatorio, - ErvasNoRadar = ErvasNoRadar, - PercentualErvasNoRadar = PercentualErvasNoRadar, - HerbicidaConsumido = HerbicidaConsumido, - HerbicidaConsumidoOperacao = HerbicidaConsumidoOperacao, - PercentualHerbicidaConsumido = PercentualHerbicidaConsumido, - PercentualHerbicidaConsumidoOperacao = PercentualHerbicidaConsumidoOperacao, - HerbicidaPorErva = HerbicidaPorErva, - PercentualErvasTerreno = PercentualErvasTerreno, - PercentualReservatorio = PercentualReservatorio, - VolumeReservatorio = VolumeReservatorio, - MassaReservatorio = MassaReservatorio, - PressaoLinha = PressaoLinha, - PressaoLinhaCalc = PressaoLinhaCalc, - VazaoInstantanea = VazaoInstantanea, - VazaoInstantaneaCalc = VazaoInstantaneaCalc, - VazaoMedia = VazaoMedia, - TempoBombaAtuada = TempoBombaAtuada, - ComandoBomba = ComandoBomba, - LeituraComandoBomba = LeituraComandoBomba, - LeituraPotenciaBomba = LeituraPotenciaBomba, - VolumeVazaoMl = VolumeVazaoMl, - AnguloBarraEsquerda = AnguloBarraEsquerda, - AnguloBarraDireita = AnguloBarraDireita, - BombaCavitada = BombaCavitada, - Iniciados = new Dictionary(Iniciados ?? new Dictionary()), - UltimoComandoRespondido = UltimoComandoRespondido, - LPorMetro = LPorMetro, - LPorMinuto = LPorMinuto, - TempoEstimadoRestanteMinutos = TempoEstimadoRestanteMinutos, - DistanciaEstimadaRestanteMetros = DistanciaEstimadaRestanteMetros, - Bicos = new List(Bicos ?? new List()) - }; - } - } - public class OperacaoSensoriamentoLogMovModel { public double VelocidadeMedia { get; set; } @@ -4366,41 +4207,315 @@ namespace AgroBase.Models public class AtuSensoriamentoLogModel { public DateTime Momento { get; set; } + + // Conexão / saúde geral public bool Inicializado { get; set; } + public bool ConexaoAtiva { get; set; } public int QualidadeWifi { get; set; } public int LatenciaLoop { get; set; } + public DateTime UltimoComandoRespondido { get; set; } + + // Contexto da operação + public int QtdCamerasSolo { get; set; } + public int QuantidadeBicos { get; set; } + public double CapacidadeReservatorio { get; set; } + + // Reservatório / consumo public double MassaReservatorio { get; set; } - public double PressaoLinha_SP { get; set; } - public double PressaoLinha { get; set; } public double VolumeReservatorio { get; set; } public double PercentualReservatorio { get; set; } + + public double HerbicidaConsumido { get; set; } + public double HerbicidaConsumidoOperacao { get; set; } public double PercentualHerbicidaConsumido { get; set; } public double PercentualHerbicidaConsumidoOperacao { get; set; } + public double HerbicidaPorAtuacao { get; set; } - public double PotenciaBomba { get; set; } - public bool BombaLigada { get; set; } + public double HerbicidaPorErva { get; set; } + + // Ervas / contexto visual + public bool ErvasNoRadar { get; set; } + public double PercentualErvasNoRadar { get; set; } + public double PercentualErvasTerreno { get; set; } + + // Pressão / vazão + public double PressaoLinha_SP { get; set; } + public double PressaoLinha { get; set; } + public double PressaoLinhaCalc { get; set; } + public double PressaoAtualBomba { get; set; } + public double VazaoFluxoMLpSInstantanea { get; set; } + public double VazaoFluxoMLpSInstantaneaCorrigida { get; set; } public double VazaoFluxoMLpSMedia { get; set; } + public double VolumeVazaoML { get; set; } - public int QuantidadeBicos { get; set; } + + // Bomba linha + public bool BombaLigada { get; set; } + public bool ComandoBomba { get; set; } + public bool LeituraComandoBomba { get; set; } + + // Mantém nomes úteis para tela/log antigo + public double PotenciaBomba { get; set; } + public double LeituraPotenciaBomba { get; set; } + + // Novos dados da bomba via Dados2 + public int QtdBicosLigados { get; set; } + public double PotenciaFeedForward { get; set; } + public double PotenciaFinal { get; set; } + public double PressaoSensor { get; set; } + public double PressaoSPControle { get; set; } + public bool PressaoSPForaDoMapa { get; set; } + public double TempoPulverizadorAtuado { get; set; } - public List Bicos { get; set; } - public DateTime UltimoComandoRespondido { get; set; } + public double TempoBombaAtuada { get; set; } + public bool BombaCavitada { get; set; } + + // Agitador + public ModoAgitadorCalda ModoControleAgitador { get; set; } + public double PotenciaAgitador { get; set; } + + // Barra + public double AnguloBarraEsquerda { get; set; } + public double AnguloBarraDireita { get; set; } + + // Autonomia / estimativas + public double LPorMetro { get; set; } + public double LPorMinuto { get; set; } + public double TempoEstimadoRestanteMinutos { get; set; } + public double DistanciaEstimadaRestanteMetros { get; set; } + + // Componentes + public Dictionary Iniciados { get; set; } = new Dictionary(); + public List Bicos { get; set; } = new List(); + + public AtuSensoriamentoLogModel Clone() + { + return new AtuSensoriamentoLogModel() + { + Momento = Momento, + + Inicializado = Inicializado, + ConexaoAtiva = ConexaoAtiva, + QualidadeWifi = QualidadeWifi, + LatenciaLoop = LatenciaLoop, + UltimoComandoRespondido = UltimoComandoRespondido, + + QtdCamerasSolo = QtdCamerasSolo, + QuantidadeBicos = QuantidadeBicos, + CapacidadeReservatorio = CapacidadeReservatorio, + + MassaReservatorio = MassaReservatorio, + VolumeReservatorio = VolumeReservatorio, + PercentualReservatorio = PercentualReservatorio, + + HerbicidaConsumido = HerbicidaConsumido, + HerbicidaConsumidoOperacao = HerbicidaConsumidoOperacao, + PercentualHerbicidaConsumido = PercentualHerbicidaConsumido, + PercentualHerbicidaConsumidoOperacao = PercentualHerbicidaConsumidoOperacao, + + HerbicidaPorAtuacao = HerbicidaPorAtuacao, + HerbicidaPorErva = HerbicidaPorErva, + + ErvasNoRadar = ErvasNoRadar, + PercentualErvasNoRadar = PercentualErvasNoRadar, + PercentualErvasTerreno = PercentualErvasTerreno, + + PressaoLinha_SP = PressaoLinha_SP, + PressaoLinha = PressaoLinha, + PressaoLinhaCalc = PressaoLinhaCalc, + PressaoAtualBomba = PressaoAtualBomba, + + VazaoFluxoMLpSInstantanea = VazaoFluxoMLpSInstantanea, + VazaoFluxoMLpSInstantaneaCorrigida = VazaoFluxoMLpSInstantaneaCorrigida, + VazaoFluxoMLpSMedia = VazaoFluxoMLpSMedia, + VolumeVazaoML = VolumeVazaoML, + + BombaLigada = BombaLigada, + ComandoBomba = ComandoBomba, + LeituraComandoBomba = LeituraComandoBomba, + + PotenciaBomba = PotenciaBomba, + LeituraPotenciaBomba = LeituraPotenciaBomba, + + QtdBicosLigados = QtdBicosLigados, + PotenciaFeedForward = PotenciaFeedForward, + PotenciaFinal = PotenciaFinal, + PressaoSensor = PressaoSensor, + PressaoSPControle = PressaoSPControle, + PressaoSPForaDoMapa = PressaoSPForaDoMapa, + + TempoPulverizadorAtuado = TempoPulverizadorAtuado, + TempoBombaAtuada = TempoBombaAtuada, + BombaCavitada = BombaCavitada, + + ModoControleAgitador = ModoControleAgitador, + PotenciaAgitador = PotenciaAgitador, + + AnguloBarraEsquerda = AnguloBarraEsquerda, + AnguloBarraDireita = AnguloBarraDireita, + + LPorMetro = LPorMetro, + LPorMinuto = LPorMinuto, + TempoEstimadoRestanteMinutos = TempoEstimadoRestanteMinutos, + DistanciaEstimadaRestanteMetros = DistanciaEstimadaRestanteMetros, + + Iniciados = new Dictionary(Iniciados ?? new Dictionary()), + Bicos = Bicos?.Select(b => b.Clone()).ToList() ?? new List() + }; + } + + + public static AtuSensoriamentoLogModel MontarLogAtuador(DateTime agora) + { + var op = Variaveis.OperacaoEmAndamento; + var dadosAtu = op?.DispAtu?.Dados; + var pControle = op?.Parametros?.Controle; + var bombaLinha = dadosAtu?.BombaPressurizadora; + var reservatorio = dadosAtu?.ContadorCarga; + + if (dadosAtu == null) + { + return new AtuSensoriamentoLogModel() + { + Momento = agora, + Inicializado = false, + ConexaoAtiva = false + }; + } + + return new AtuSensoriamentoLogModel() + { + Momento = agora, + + Inicializado = dadosAtu.ConexaoAtiva, + ConexaoAtiva = dadosAtu.ConexaoAtiva, + QualidadeWifi = dadosAtu.DadosLeitura?.QualidadeWifi ?? 0, + LatenciaLoop = dadosAtu.DadosLeitura?.LatenciaLoop ?? 0, + UltimoComandoRespondido = dadosAtu.DadosLeitura?.UltimoComandoRespondido ?? dadosAtu.DadosLeitura?.Momento ?? DateTime.MinValue, + + QtdCamerasSolo = CameraWorkerService.CamerasSolo.Count, + QuantidadeBicos = op?.Parametros?.QtdBicos ?? VariaveisEquipamento.QuantidadeBicosPulverizadores, + CapacidadeReservatorio = VariaveisEquipamento.CapacidadeReservatorio, + + MassaReservatorio = dadosAtu.ContadorCarga?.MassaAtual_Kg ?? reservatorio?.MassaAtual_Kg ?? 0, + VolumeReservatorio = dadosAtu.ContadorCarga?.VolumeAtual_L ?? reservatorio?.VolumeAtual_L ?? 0, + PercentualReservatorio = dadosAtu.ContadorCarga?.PercentualAtual ?? reservatorio?.PercentualAtual ?? 0, + + HerbicidaConsumido = reservatorio?.VolumeConsumidoTotal_L ?? 0, + HerbicidaConsumidoOperacao = reservatorio?.VolumeConsumidoOperacao_L ?? 0, + PercentualHerbicidaConsumido = dadosAtu.ContadorCarga?.PercentualConsumidoTotal ?? reservatorio?.PercentualConsumidoTotal ?? 0, + PercentualHerbicidaConsumidoOperacao = dadosAtu.ContadorCarga?.PercentualConsumidoOperacao ?? reservatorio?.PercentualConsumidoOperacao ?? 0, + + HerbicidaPorAtuacao = dadosAtu.ContadorCarga?.HerbicidaPorAtuacao_L ?? 0, + HerbicidaPorErva = reservatorio?.HerbicidaPorAtuacao_L ?? dadosAtu.ContadorCarga?.HerbicidaPorAtuacao_L ?? 0, + + ErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.ervas_no_radar ?? false, + PercentualErvasNoRadar = WeedWorkerService.DadosLeitura?.Analise?.percentual_ervas_no_radar ?? 0, + PercentualErvasTerreno = dadosAtu.PercentualErvasTerreno, + + PressaoLinha_SP = pControle?.AtuPressaoLinha ?? 0, + PressaoLinha = dadosAtu.PressaoLinha, + PressaoLinhaCalc = dadosAtu.PressaoLinhaCalc, + PressaoAtualBomba = dadosAtu.BombaPressurizadora?._PressaoAtual ?? 0, + + VazaoFluxoMLpSInstantanea = dadosAtu.VazaoFluxoMLpSInstantanea, + VazaoFluxoMLpSInstantaneaCorrigida = dadosAtu.VazaoFluxoMLpSInstantaneaCorrigida, + VazaoFluxoMLpSMedia = dadosAtu.VazaoMediaMLs, + VolumeVazaoML = dadosAtu.VolumeVazadoML, + + BombaLigada = bombaLinha?.ComandoAtuar ?? false, + ComandoBomba = bombaLinha?.ComandoAtuar ?? false, + LeituraComandoBomba = bombaLinha?._EstadoLeitura == Estado.Ligado, + + // Eu manteria os dois, mas tentaria fazer ambos apontarem para a leitura real final. + PotenciaBomba = FuncoesMatematicas.Clamp(bombaLinha?._PotenciaFinal ?? bombaLinha?._Potencia ?? 0, 0, 100), + LeituraPotenciaBomba = FuncoesMatematicas.Clamp(bombaLinha?._PotenciaFinal ?? bombaLinha?._Potencia ?? 0, 0, 100), + + QtdBicosLigados = bombaLinha?._QtdBicosLigados ?? 0, + PotenciaFeedForward = bombaLinha?._PotenciaFeedForwared ?? 0, + PotenciaFinal = bombaLinha?._PotenciaFinal ?? 0, + PressaoSensor = bombaLinha?._PressaoSensor ?? 0, + PressaoSPControle = bombaLinha?._PressaoSPControle ?? 0, + PressaoSPForaDoMapa = bombaLinha?._PressaoSPForaDoMapa ?? false, + + TempoPulverizadorAtuado = dadosAtu.TempoAtuado, + TempoBombaAtuada = dadosAtu.TempoAtuado, + + BombaCavitada = + dadosAtu.Conectado && + dadosAtu.PressaoLinha < (pControle?.AtuPressaoLinha ?? 0) && + dadosAtu.PressaoLinha < VariaveisEquipamento.PressaoMinimaCavitacao && + bombaLinha?._EstadoLeitura == Estado.Ligado && + (bombaLinha?._PotenciaFinal ?? bombaLinha?._Potencia ?? 0) > 20, + + ModoControleAgitador = pControle?.AtuAgitadorModo ?? ModoAgitadorCalda.SemAgitacao, + PotenciaAgitador = dadosAtu.AgitadorDeCalda?.PotenciaMediaAtiva ?? 0, + + AnguloBarraEsquerda = dadosAtu.AnguloBarraEsquerda, + AnguloBarraDireita = dadosAtu.AnguloBarraDireita, + + LPorMetro = reservatorio?.LPorMetro ?? 0, + LPorMinuto = reservatorio?.LPorMinuto ?? 0, + TempoEstimadoRestanteMinutos = reservatorio?.TempoEstimadoMinutosPulverizando ?? 0, + DistanciaEstimadaRestanteMetros = reservatorio?.DistanciaEstimadaRestantePulverizando_m ?? 0, + + Iniciados = + dadosAtu.Sensores.Select(s => new { s.ID, s.Inicializado }) + .Union(dadosAtu.BicosPulverizadores.Select(b => new { b.ID, b.Inicializado })) + .Union(dadosAtu.BombasPressurizadoras.Select(b => new { b.ID, b.Inicializado })) + .ToDictionary(x => x.ID, x => x.Inicializado), + + Bicos = dadosAtu.BicosPulverizadores.Select(bico => new AtuBicoSensoriamentoLogModel() + { + ID = bico.ID, + Inicializado = bico.Inicializado, + Comandar = bico.Comandar, + Atuado = bico.ComandoAtuar, + MediaNivelFluxo = bico.VazaoMediaMLs, + Posicao = bico.Posicao, + StatusBico = bico._EstadoLeitura == Estado.Ligado, + Atuacoes = bico.Atuacoes, + TempoAtuado = bico.TempoAtuado, + AnguloAbertura = bico.AnguloAbertura, + AnguloControle = bico.AnguloControle + }).ToList() + }; + } + } public class AtuBicoSensoriamentoLogModel { public string ID { get; set; } - public int Posicao { get; set; } - public bool Atuado { get; set; } - public bool Comandar { get; set; } public bool Inicializado { get; set; } - public bool StatusBico { get; set; } + public bool Comandar { get; set; } + public bool Atuado { get; set; } public double MediaNivelFluxo { get; set; } + public int Posicao { get; set; } + public bool StatusBico { get; set; } public int Atuacoes { get; set; } public double TempoAtuado { get; set; } public double AnguloAbertura { get; set; } public double AnguloControle { get; set; } + + public AtuBicoSensoriamentoLogModel Clone() + { + return new AtuBicoSensoriamentoLogModel() + { + ID = ID, + Inicializado = Inicializado, + Comandar = Comandar, + Atuado = Atuado, + MediaNivelFluxo = MediaNivelFluxo, + Posicao = Posicao, + StatusBico = StatusBico, + Atuacoes = Atuacoes, + TempoAtuado = TempoAtuado, + AnguloAbertura = AnguloAbertura, + AnguloControle = AnguloControle + }; + } } public class CamSoloSensoriamentoLogModel diff --git a/Firmware/Atuador/Atuador_v14/Atuador_v14.ino b/Firmware/Atuador/Atuador_v14/Atuador_v14.ino index 3d14df39d..51f464af7 100644 --- a/Firmware/Atuador/Atuador_v14/Atuador_v14.ino +++ b/Firmware/Atuador/Atuador_v14/Atuador_v14.ino @@ -141,7 +141,8 @@ BombaPressurizadora_v2 sBMB_Linha( Sentido::Horario, &sPRS_Linha, BicosPulverizadores, - QUANTIDADE_BICOS + QUANTIDADE_BICOS, + &sFLX_Linha ); BombaPressurizadora_v2 sBMB_Agitador( "BMBAGT", @@ -531,7 +532,7 @@ void enviarDadosSensores(uint8_t id_num, CanMessagePosicaoDados posicao) { ComponenteCAN::ProcessarComponenteCAN(sBIC_06, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN); ComponenteCAN::ProcessarComponenteCAN(sBIC_07, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN); - ComponenteCAN::ProcessarComponenteCAN(sBMB_Linha, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN); + ComponenteCAN::ProcessarComponenteCAN(sBMB_Linha, {CanMessagePosicaoDados::Dados1,CanMessagePosicaoDados::Dados2}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN); ComponenteCAN::ProcessarComponenteCAN(sBMB_Agitador, {CanMessagePosicaoDados::Dados1}, todosIDs, id_num, enviarStatus, todosDados, posicao, EnviarDadosCAN); diff --git a/Firmware/Modulos/BombaPressurizadoraModel_v2.h b/Firmware/Modulos/BombaPressurizadoraModel_v2.h index ecf5e4462..12834c545 100644 --- a/Firmware/Modulos/BombaPressurizadoraModel_v2.h +++ b/Firmware/Modulos/BombaPressurizadoraModel_v2.h @@ -10,8 +10,23 @@ #include "Utils.h" #include "IComponenteCAN.h" #include "SensorPressaoModel_v2.h" +#include "SensorFluxoModel_v2.h" #include "BicoPulverizadorModel_v2.h" +/* + BombaPressurizadora_v2 - controle suave por pressao com supervisor de fluxo. + + Ideia desta versao: + - A pressao e o controle principal. + - A bomba NAO fica ligando/desligando dentro da malha fechada. + - Se existem bicos abertos e nao existe pulso/fluxo recente, o controle aumenta + potencia por boost progressivo, mesmo que a pressao esteja acima do SP. + - O sensor de fluxo nunca deve ser usado como "chave nervosa" para cortar a bomba. + Ele apenas adiciona potencia enquanto nao houver indicio de produto em movimento. + - O ON/OFF antigo fica preservado somente quando ModoControle == 0. + - O contrato CAN Dados1/Dados2 foi mantido. +*/ + struct EstadoAnteriorBomba { Estado estado = Estado::Desligado; double potenciaSP = 0.0; @@ -40,7 +55,8 @@ class BombaPressurizadora_v2 : public ComponenteCAN { Sentido sentido = Sentido::Horario, SensorPressao_v2* sensor = nullptr, BicoPulverizador_v2** bicos = nullptr, - size_t qtdBicos = 0 + size_t qtdBicos = 0, + SensorFluxo_v2* sensorFluxoLinha = nullptr ) : _ID(id), ID_Num(idNum), @@ -50,6 +66,7 @@ class BombaPressurizadora_v2 : public ComponenteCAN { _canalIN2(canalIN2), _sentido(sentido), sensorPressao(sensor), + sensorFluxo(sensorFluxoLinha), _bicos(bicos), _qtdBicos(qtdBicos) { } @@ -69,121 +86,240 @@ class BombaPressurizadora_v2 : public ComponenteCAN { bool bombaDesligadaPorPressao = false; SensorPressao_v2* sensorPressao = nullptr; + SensorFluxo_v2* sensorFluxo = nullptr; Estado EstadoLeitura = Estado::Desligado; + Estado _EstadoControle = Estado::Desligado; + int PWM_SP = 0; int PWM_Set = 0; int PotenciaLeitura = 0; double PercentualPotenciaLeitura = 0.0; - Estado _EstadoControle = Estado::Desligado; - double _PressaoSP = 0.0; double _PotenciaSP = 0.0; + double _PressaoSP = 0.0; ModoControleBomba ModoControle = static_cast(0); /* - Ganhos do controle fechado. - - Recomendacao inicial para campo: - - Comece com Kd = 0.0. - - Use o feedforward para carregar a demanda principal. - - Use Kp/Ki apenas para corrigir diferencas pequenas. + Ganhos da malha suave. + Observacao: o Command2 continua atualizando Kp/Ki/Kd. + Para esta versao, comece conservador: + Kp ~= 1.0 a 2.5 + Ki ~= 0.10 a 0.40 + Kd = 0 */ - double Kp = 1.8; - double Ki = 0.35; + double Kp = 1.10; + double Ki = 0.12; double Kd = 0.0; - double PotenciaMinPID = 18.0; + double PotenciaMinPID = 0.0; double PotenciaMaxPID = 100.0; /* - Potencia minima realmente util da bomba. - Abaixo disso a bomba pode girar, mas nao pressurizar de verdade. + Na malha fechada, a saida nunca deve ficar passeando entre 0 e 100. + Ela segue uma rampa. */ - double PotenciaMinAtiva = 22.0; - - /* - Tabelas de feedforward. - - Indice = quantidade de bicos ligados. - Exemplo: PotenciaBase30Psi[4] = potencia base para 4 bicos ligados em 30 psi. - - Valores abaixo sao CHUTES INICIAIS seguros para teste. - Substitua pelos valores medidos em bancada/campo. - */ - double PotenciaBase15Psi[8] = { - 0.0, // 0 bicos - 20.0, // 1 bico - 26.0, // 2 bicos - 33.0, // 3 bicos - 41.0, // 4 bicos - 50.0, // 5 bicos - 60.0, // 6 bicos - 72.0 // 7 bicos - }; - - double PotenciaBase30Psi[8] = { - 0.0, // 0 bicos - 34.0, // 1 bico - 45.0, // 2 bicos - 57.0, // 3 bicos - 69.0, // 4 bicos - 81.0, // 5 bicos - 93.0, // 6 bicos - 100.0 // 7 bicos - }; - - double PressaoReferenciaBaixaPsi = 15.0; - double PressaoReferenciaAltaPsi = 30.0; - - /* - Histerese usada somente no modo ON/OFF antigo. - */ - double HistereseOnOffPsi = 3.0; - - /* - Filtro leve para pressao. - 0.0 = nao atualiza; 1.0 = sem filtro. - */ - double AlphaFiltroPressao = 0.35; - - /* - Limites da correcao PI em cima da tabela. - A tabela deve fazer o grosso. O PI so afina. - */ - double CorrecaoMinPI = -25.0; - double CorrecaoMaxPI = 25.0; - - /* - Rampa de potencia. - Subida mais rapida para nao deixar a linha morrer quando abrem bicos. - Descida mais suave para evitar pico quando fecham bicos. - */ - double TaxaSubidaPctPorSegundo = 350.0; - double TaxaDescidaPctPorSegundo = 180.0; - + double TaxaSubidaPctPorSegundo = 220.0; + double TaxaDescidaPctPorSegundo = 55.0; unsigned long IntervaloControleMs = 50; /* - Por seguranca, a bomba desliga quando nao existe bico aberto em malha fechada. - Se houver bypass/retorno ou valvula reguladora e voce quiser manter linha pre-pressurizada, + Se true, com nenhum bico aberto a bomba desliga. + Se futuramente houver bypass/retorno e quiser pre-pressurizar linha, altere para false. */ bool DesligarSemBicosAbertos = true; + /* + Piso eletrico/hidraulico. + Nao confundir com boost de fluxo. Este piso apenas evita PWM inutil. + */ + double PotenciaMinAtiva = 18.0; + + /* + Piso suave por quantidade de bicos. + Mantive baixo para nao forcar sobrepressao permanente. + O boost de fluxo e quem sobe quando nao houver vazao. + */ + double PotenciaMinimaComBicosPorQtd[8] = { + 0.0, + 28.0, + 30.0, + 38.0, + 42.0, + 46.0, + 50.0, + 55.0 + }; + + /* + Kick inicial quando sai de 0 bicos para N bicos. + Diferente do supervisor de fluxo, isto e apenas uma pancada inicial curta. + */ + bool HabilitarKickInicial = true; + double PotenciaKickPoucosBicosPct = 65.0; + double PotenciaKickMuitosBicosPct = 58.0; + unsigned long DuracaoKickPoucosBicosMs = 1000; + unsigned long DuracaoKickMuitosBicosMs = 650; + + /* + Supervisor de fluxo. + Nao corta a bomba. Apenas adiciona boost se houver bico aberto e nao houver + indicio recente de fluxo/pulso. + */ + bool HabilitarSupervisorFluxo = true; + double FatorCalibracaoFluxoFirmware = 0.776; + double VazaoMinimaOperacaoMLs = 3.0; + unsigned long TimeoutPulsoFluxoRecenteMs = 700; + unsigned long TempoSemFluxoParaBoostMs = 300; + unsigned long TempoComFluxoParaAliviarMs = 800; + + double BoostFluxoMaxPoucosBicosPct = 40.0; + double BoostFluxoMaxMuitosBicosPct = 38.0; + double TaxaBoostFluxoSubidaPctPorSegundo = 45.0; + double TaxaBoostFluxoDescidaPctPorSegundo = 16.0; + + /* + Enquanto nao houver fluxo, a malha de pressao usa um SP auxiliar minimo. + Isto ajuda o PI a subir potencia sem depender apenas do boost. + Nao e usado para cortar a bomba. + */ + double PressaoSPMinSemFluxoPoucosBicosPsi = 22.0; + double PressaoSPMinSemFluxoMuitosBicosPsi = 22.0; + + /* + Se nao houver fluxo e a pressao ja estiver muito alta, o boost para de subir. + A bomba ainda nao fica ligando/desligando, apenas evita empurrar boost infinito. + */ + double PressaoMaxAssistenciaFluxoPsi = 85.0; + + /* + Corte duro de protecao. A linha/trava mecanica foram informadas como seguras + proximo de 120 psi. + */ + double PressaoCorteSegurancaPsi = 120.0; + double HistereseRearmeSegurancaPsi = 12.0; + bool CorteSegurancaAtivo = false; + + /* + ON/OFF legado. + Use apenas quando ModoControle == 0. + */ + double HistereseOnOffPsi = 3.0; + + /* + Mapa empirico mantido como chute inicial/feedforward, nao como dono da decisao. + A malha suave funciona mesmo se HabilitarFeedForward == false. + */ + bool HabilitarFeedForward = true; + double PesoFeedForward = 1.0; + + double PotenciaBase15Psi[8] = { + 0.0, + 20.0, + 40.0, + 56.0, + 61.0, + 68.0, + 72.0, // antes estava perto de 75 + 78.0 // antes estava perto de 88 + }; + + double PotenciaBase22Psi[8] = { + 0.0, + 35.0, + 56.4, + 62.5, + 65.8, + 71.7, + 77.5, + 100.0 + }; + + double PressaoReferenciaBaixaPsi = 15.0; + double PressaoReferenciaAltaPsi = 22.0; + + double PressaoMaximaMapeadaPorBicos[8] = { + 0.0, + 92.0, + 75.0, + 56.0, + 41.0, + 32.0, + 26.0, + 22.0 + }; + + double PotenciaBaseMaxPsi[8] = { + 0.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0, + 100.0 + }; + + /* + Correção opcional da pressão para aproximar ponto de aplicação/manômetro. + Se quiser controlar exatamente o sensor, zere esta tabela. + */ + double CorrecaoPressaoAplicacaoPorBicos[8] = { + 0.0, + -4.0, + -2.0, + 0.0, + 1.0, + 2.0, + 3.5, + 4.5 + }; + + double AlphaFiltroPressao = 0.35; + double DeadbandPressaoPsi = 1.2; + + /* + Estados/telemetria expostos via CAN/log. + */ uint8_t QtdBicosLigados = 0; + double PressaoSensorAtual = 0.0; double PressaoAtual = 0.0; double PressaoFiltrada = 0.0; + double PressaoSPControleAtual = 0.0; + bool PressaoSPLimitadoPorCapacidade = false; + bool PressaoSPForaDoMapaAtual = false; + double PressaoMaximaMapeadaAtual = 0.0; + double PotenciaFeedForwardAtual = 0.0; double CorrecaoPIAtual = 0.0; double PotenciaFinalAtual = 0.0; + double VazaoFluxoAtualMLs = 0.0; + double VazaoFluxoCorrigidaAtualMLs = 0.0; + bool FluxoPresenteAtual = false; + bool FluxoInsuficienteAtual = false; + bool RecuperacaoFluxoAtiva = false; + double BoostFluxoAtualPct = 0.0; + + /* + Compatibilidade com versões anteriores. + */ + bool ArmamentoLinhaAtivo = false; + bool LinhaArmadaPorFluxo = false; + bool FalhaArmamentoLinhaAtual = false; + void ConfigurarBicos(BicoPulverizador_v2** bicos, size_t qtdBicos) { _bicos = bicos; _qtdBicos = qtdBicos; } + void ConfigurarSensorFluxo(SensorFluxo_v2* sensor) { + sensorFluxo = sensor; + ResetarFluxo(); + } + void ConfigurarMapaPotencia15Psi( double b1, double b2, @@ -212,14 +348,66 @@ class BombaPressurizadora_v2 : public ComponenteCAN { double b6, double b7 ) { - PotenciaBase30Psi[0] = 0.0; - PotenciaBase30Psi[1] = b1; - PotenciaBase30Psi[2] = b2; - PotenciaBase30Psi[3] = b3; - PotenciaBase30Psi[4] = b4; - PotenciaBase30Psi[5] = b5; - PotenciaBase30Psi[6] = b6; - PotenciaBase30Psi[7] = b7; + PotenciaBase22Psi[0] = 0.0; + PotenciaBase22Psi[1] = b1; + PotenciaBase22Psi[2] = b2; + PotenciaBase22Psi[3] = b3; + PotenciaBase22Psi[4] = b4; + PotenciaBase22Psi[5] = b5; + PotenciaBase22Psi[6] = b6; + PotenciaBase22Psi[7] = b7; + } + + void ConfigurarMapaPotenciaAltaPsi( + double pressaoAltaPsi, + double b1, + double b2, + double b3, + double b4, + double b5, + double b6, + double b7 + ) { + PressaoReferenciaAltaPsi = constrain(pressaoAltaPsi, PressaoReferenciaBaixaPsi + 0.1, 250.0); + ConfigurarMapaPotencia30Psi(b1, b2, b3, b4, b5, b6, b7); + } + + void ConfigurarPressaoMaximaPorBicos( + double b1, + double b2, + double b3, + double b4, + double b5, + double b6, + double b7 + ) { + PressaoMaximaMapeadaPorBicos[0] = 0.0; + PressaoMaximaMapeadaPorBicos[1] = b1; + PressaoMaximaMapeadaPorBicos[2] = b2; + PressaoMaximaMapeadaPorBicos[3] = b3; + PressaoMaximaMapeadaPorBicos[4] = b4; + PressaoMaximaMapeadaPorBicos[5] = b5; + PressaoMaximaMapeadaPorBicos[6] = b6; + PressaoMaximaMapeadaPorBicos[7] = b7; + } + + void ConfigurarMapaPotenciaMaxPsi( + double b1, + double b2, + double b3, + double b4, + double b5, + double b6, + double b7 + ) { + PotenciaBaseMaxPsi[0] = 0.0; + PotenciaBaseMaxPsi[1] = b1; + PotenciaBaseMaxPsi[2] = b2; + PotenciaBaseMaxPsi[3] = b3; + PotenciaBaseMaxPsi[4] = b4; + PotenciaBaseMaxPsi[5] = b5; + PotenciaBaseMaxPsi[6] = b6; + PotenciaBaseMaxPsi[7] = b7; } void ConfigurarPID( @@ -234,15 +422,11 @@ class BombaPressurizadora_v2 : public ComponenteCAN { Kd = kd; PotenciaMinPID = constrain(potenciaMinPID, 0.0, 100.0); PotenciaMaxPID = constrain(potenciaMaxPID, PotenciaMinPID, 100.0); - - if (PotenciaMinAtiva < PotenciaMinPID) { - PotenciaMinAtiva = PotenciaMinPID; - } } void ConfigurarModoControle(ModoControleBomba modo) { ModoControle = modo; - ResetarControleFechado(); + ResetarControle(); } void Inicializar() { @@ -254,35 +438,29 @@ class BombaPressurizadora_v2 : public ComponenteCAN { _pinoIN1.Conectar(false); _pinoIN2.Conectar(false); - bool in1Ok = _pinoIN1.Definido(); - bool in2Ok = _pinoIN2.Definido(); - - if (!in1Ok || !in2Ok) { - Iniciado = false; - MostrarLog("Falha ao iniciar bomba: pinos invalidos"); - return; - } - ledcSetup(_canalIN1, PWM_FREQ_HZ, PWM_RESOLUTION_BITS); ledcSetup(_canalIN2, PWM_FREQ_HZ, PWM_RESOLUTION_BITS); ledcAttachPin(_pinoIN1.num, _canalIN1); ledcAttachPin(_pinoIN2.num, _canalIN2); - AplicarSaida(false, 0); + bool pinosOk = _pinoIN1.Definido() && _pinoIN2.Definido(); + + if (!pinosOk) { + Iniciado = false; + MostrarLog("Falha ao iniciar bomba"); + return; + } Iniciado = true; - EstadoLeitura = Estado::Desligado; - _EstadoControle = Estado::Desligado; - - _ultimoControleMs = millis(); - _ultimoTempoDtMs = _ultimoControleMs; - - MostrarLog("Bomba iniciada com sucesso"); + ResetarControle(); + AplicarSaida(false, 0); + MostrarLog("Bomba iniciada"); } void Desligar() { if (!Iniciado) { + MostrarLog("Bomba nao esta inicializada"); return; } @@ -294,7 +472,7 @@ class BombaPressurizadora_v2 : public ComponenteCAN { Iniciado = false; EstadoLeitura = Estado::Desligado; _EstadoControle = Estado::Desligado; - ResetarControleFechado(); + ResetarControle(); MostrarLog("Bomba desligada"); } @@ -304,6 +482,9 @@ class BombaPressurizadora_v2 : public ComponenteCAN { double potenciaSP, double pressaoSP = 0.0 ) { + bool mudouEstado = (_EstadoControle != estado); + bool mudouPressao = fabs(_PressaoSP - pressaoSP) > 0.01; + _EstadoControle = estado; _PotenciaSP = constrain(potenciaSP, 0.0, 100.0); _PressaoSP = constrain(pressaoSP, 0.0, 250.0); @@ -315,18 +496,23 @@ class BombaPressurizadora_v2 : public ComponenteCAN { static_cast(ModoControle) != 0 ); + if (mudouEstado || mudouPressao) { + ResetarIntegralPressao(); + } + if (_EstadoControle != Estado::Ligado) { AplicarSaida(false, 0); - ResetarControleFechado(); + ResetarControle(); AtualizarLeitura(); return; } if (!MalhaFechada) { double potencia = _PotenciaSP; + uint8_t idx = LimitarIndiceBicos(ContarBicosLigados()); - if (potencia > 0.0 && potencia < PotenciaMinAtiva) { - potencia = PotenciaMinAtiva; + if (idx > 0) { + potencia = MaiorDouble(potencia, ObterPotenciaMinimaOperacional(idx)); } AplicarPotenciaPercentual(potencia); @@ -355,53 +541,58 @@ class BombaPressurizadora_v2 : public ComponenteCAN { double dt = CalcularDtSegundos(agora); _ultimoControleMs = agora; + QtdBicosLigados = ContarBicosLigados(); + AtualizarPressao(); + AtualizarFluxo(); + if (_EstadoControle != Estado::Ligado) { AplicarSaida(false, 0); - ResetarControleFechado(); + ResetarControle(); AtualizarLeitura(); return; } - AtualizarPressao(); - QtdBicosLigados = ContarBicosLigados(); - - if (QtdBicosLigados == 0 && DesligarSemBicosAbertos) { - /* - Sem bico aberto nao existe demanda de aplicacao. - Por padrao, mantem a bomba desligada para nao pressurizar linha fechada. - - Se houver bypass/retorno/regulador e voce quiser manter a linha pre-pressurizada, - defina DesligarSemBicosAbertos = false. - - A rotina de lavagem continua funcionando, porque liga todos os bicos antes da bomba. - */ - if (_PressaoSP > 0.0 && sensorPressao != nullptr) { - AplicarSaida(false, 0); - ResetarControleFechado(); - AtualizarLeitura(); - return; - } + if (TratarCorteSeguranca()) { + AtualizarLeitura(); + return; } - if (sensorPressao == nullptr || _PressaoSP <= 0.0) { + MalhaFechada = ( + sensorPressao != nullptr && + _PressaoSP > 0.0 && + static_cast(ModoControle) != 0 + ); + + if (QtdBicosLigados == 0 && DesligarSemBicosAbertos) { + AplicarSaida(false, 0); + ResetarEstadosDeBicosFechados(); + AtualizarLeitura(); + return; + } + + if (!MalhaFechada) { double potencia = _PotenciaSP; - if (potencia > 0.0 && potencia < PotenciaMinAtiva) { - potencia = PotenciaMinAtiva; + if (QtdBicosLigados > 0) { + potencia = MaiorDouble(potencia, ObterPotenciaMinimaOperacional(QtdBicosLigados)); + potencia = AplicarSupervisorFluxo(potencia, dt); + } else { + ResetarSupervisorFluxo(); } - AplicarPotenciaPercentual(potencia); + double potenciaComRampa = AplicarRampaPercentual(potencia, dt); + AplicarPotenciaPercentual(potenciaComRampa); AtualizarLeitura(); return; } if (static_cast(ModoControle) == 0) { - ControlarOnOff(); + ControlarOnOffLegado(_PressaoSP, _PotenciaSP, HistereseOnOffPsi); AtualizarLeitura(); return; } - ControlarFeedForwardPI(dt); + ControlarPressaoSuaveComFluxo(dt); AtualizarLeitura(); } @@ -413,13 +604,17 @@ class BombaPressurizadora_v2 : public ComponenteCAN { return; } - PercentualPotenciaLeitura = constrain(PotenciaFinalAtual, 0.0, 100.0); - PotenciaLeitura = (int)round(PercentualPotenciaLeitura); - EstadoLeitura = (PercentualPotenciaLeitura > 2.0) ? Estado::Ligado : Estado::Desligado; + QtdBicosLigados = ContarBicosLigados(); if (sensorPressao != nullptr) { AtualizarPressao(); } + + AtualizarFluxo(); + + PercentualPotenciaLeitura = constrain(PotenciaFinalAtual, 0.0, 100.0); + PotenciaLeitura = (int)round(PercentualPotenciaLeitura); + EstadoLeitura = (PercentualPotenciaLeitura > 2.0) ? Estado::Ligado : Estado::Desligado; } void RequisitarDados() { @@ -479,24 +674,27 @@ class BombaPressurizadora_v2 : public ComponenteCAN { switch (posicao) { case CanMessagePosicaoDados::Status: { data.push_back(Iniciado ? 1 : 0); + data.push_back((MalhaFechada && sensorPressao != nullptr) ? 1 : 0); break; } case CanMessagePosicaoDados::Dados1: { - AtualizarLeitura(); + data.push_back(static_cast(EstadoLeitura)); // data[2] + data.push_back(LimitarByte(_PotenciaSP)); // data[3] potenciaSp + data.push_back(LimitarByte(PercentualPotenciaLeitura)); // data[4] percentualPotencia real + data.push_back(LimitarByte(_PressaoSP)); // data[5] pressaoSp pedido + data.push_back(LimitarByte(PressaoAtual)); // data[6] pressaoAtual + data.push_back(static_cast(ModoControle)); // data[7] modo + break; + } - data.push_back(static_cast(EstadoLeitura)); - data.push_back(LimitarByte(PercentualPotenciaLeitura)); - data.push_back(LimitarByte(_PressaoSP)); - data.push_back(LimitarByte(PressaoAtual)); - - /* - Bytes extras mantendo o pacote em 8 bytes: - - qtd bicos ligados - - potencia feedforward usada antes da correcao PI - */ - data.push_back(QtdBicosLigados); - data.push_back(LimitarByte(PotenciaFeedForwardAtual)); + case CanMessagePosicaoDados::Dados2: { + data.push_back(QtdBicosLigados); // data[2] + data.push_back(LimitarByte(PotenciaFeedForwardAtual)); // data[3] + data.push_back(LimitarByte(PotenciaFinalAtual)); // data[4] + data.push_back(LimitarByte(PressaoSensorAtual)); // data[5] + data.push_back(LimitarByte(PressaoSPControleAtual)); // data[6] + data.push_back(PressaoSPForaDoMapaAtual ? 1 : 0); // data[7] break; } @@ -520,11 +718,96 @@ class BombaPressurizadora_v2 : public ComponenteCAN { unsigned long _ultimoControleMs = 0; unsigned long _ultimoTempoDtMs = 0; - double _integral = 0.0; - double _ultimoErro = 0.0; - double _ultimaPressaoFiltrada = 0.0; - uint8_t _ultimaQtdBicosLigados = 0; bool _filtroInicializado = false; + double _ultimaPressaoFiltrada = 0.0; + double _integralPressao = 0.0; + double _ultimoErroPressao = 0.0; + + uint8_t _ultimaQtdBicosLigados = 0; + + bool _kickAtivo = false; + unsigned long _inicioKickMs = 0; + + unsigned long _ultimoPulsoFluxoMs = 0; + unsigned long _inicioSemFluxoMs = 0; + unsigned long _inicioComFluxoMs = 0; + + void ResetarControle() { + ResetarIntegralPressao(); + ResetarSupervisorFluxo(); + ResetarKick(); + _ultimoControleMs = 0; + _ultimoTempoDtMs = 0; + _ultimaQtdBicosLigados = 0; + _filtroInicializado = false; + PressaoSensorAtual = 0.0; + PressaoAtual = 0.0; + PressaoFiltrada = 0.0; + PressaoSPControleAtual = 0.0; + PressaoSPLimitadoPorCapacidade = false; + PressaoSPForaDoMapaAtual = false; + PressaoMaximaMapeadaAtual = 0.0; + PotenciaFeedForwardAtual = 0.0; + CorrecaoPIAtual = 0.0; + bombaDesligadaPorPressao = false; + CorteSegurancaAtivo = false; + ArmamentoLinhaAtivo = false; + LinhaArmadaPorFluxo = false; + FalhaArmamentoLinhaAtual = false; + ResetarFluxo(); + } + + void ResetarEstadosDeBicosFechados() { + ResetarIntegralPressao(); + ResetarSupervisorFluxo(); + ResetarKick(); + PotenciaFeedForwardAtual = 0.0; + CorrecaoPIAtual = 0.0; + PressaoSPControleAtual = 0.0; + PressaoSPForaDoMapaAtual = false; + bombaDesligadaPorPressao = false; + ArmamentoLinhaAtivo = false; + LinhaArmadaPorFluxo = false; + FalhaArmamentoLinhaAtual = false; + } + + void ResetarIntegralPressao() { + _integralPressao = 0.0; + _ultimoErroPressao = 0.0; + CorrecaoPIAtual = 0.0; + } + + void ResetarKick() { + _kickAtivo = false; + _inicioKickMs = 0; + } + + void ResetarFluxo() { + VazaoFluxoAtualMLs = 0.0; + VazaoFluxoCorrigidaAtualMLs = 0.0; + FluxoPresenteAtual = false; + FluxoInsuficienteAtual = false; + RecuperacaoFluxoAtiva = false; + _ultimoPulsoFluxoMs = 0; + _inicioSemFluxoMs = 0; + _inicioComFluxoMs = 0; + BoostFluxoAtualPct = 0.0; + } + + void ResetarSupervisorFluxo() { + FluxoInsuficienteAtual = false; + RecuperacaoFluxoAtiva = false; + _inicioSemFluxoMs = 0; + _inicioComFluxoMs = 0; + BoostFluxoAtualPct = 0.0; + } + + uint8_t LimitarIndiceBicos(uint8_t idx) { + if (idx > 7) { + return 7; + } + return idx; + } uint8_t ContarBicosLigados() { if (_bicos == nullptr || _qtdBicos == 0) { @@ -534,30 +817,42 @@ class BombaPressurizadora_v2 : public ComponenteCAN { uint8_t qtd = 0; for (size_t i = 0; i < _qtdBicos; i++) { - BicoPulverizador_v2* bico = _bicos[i]; - - if (bico == nullptr || !bico->Iniciado) { + if (_bicos[i] == nullptr) { continue; } - bico->AtualizarLeitura(); + /* + A leitura fisica do bico e atualizada no proprio model do bico. + Chamamos aqui para que a bomba enxergue o estado fisico mais recente. + */ + _bicos[i]->AtualizarLeitura(); - if (bico->_Status == Estado::Ligado) { + if (_bicos[i]->_Status == Estado::Ligado) { qtd++; } } - return qtd; + return LimitarIndiceBicos(qtd); } void AtualizarPressao() { if (sensorPressao == nullptr) { + PressaoSensorAtual = 0.0; PressaoAtual = 0.0; return; } sensorPressao->RequisitarDados(); - PressaoAtual = sensorPressao->Pressao; + PressaoSensorAtual = sensorPressao->Pressao; + + uint8_t idx = LimitarIndiceBicos(QtdBicosLigados); + double pressaoAplicacao = PressaoSensorAtual + CorrecaoPressaoAplicacaoPorBicos[idx]; + + if (pressaoAplicacao < 0.0) { + pressaoAplicacao = 0.0; + } + + PressaoAtual = pressaoAplicacao; if (!_filtroInicializado) { PressaoFiltrada = PressaoAtual; @@ -570,12 +865,294 @@ class BombaPressurizadora_v2 : public ComponenteCAN { PressaoFiltrada = (alpha * PressaoAtual) + ((1.0 - alpha) * PressaoFiltrada); } - void ControlarOnOff() { - if (PressaoAtual >= (_PressaoSP + HistereseOnOffPsi)) { + bool SensorFluxoDisponivel() { + return sensorFluxo != nullptr && sensorFluxo->Iniciado; + } + + void AtualizarFluxo() { + VazaoFluxoAtualMLs = 0.0; + VazaoFluxoCorrigidaAtualMLs = 0.0; + FluxoPresenteAtual = false; + + if (!SensorFluxoDisponivel()) { + return; + } + + /* + Nao chama RequisitarDados(), porque aquilo reinicia a janela de amostragem + do sensor e pode atrapalhar o log/CAN do FLX. + */ + sensorFluxo->Loop(); + + unsigned long agora = millis(); + unsigned int pulsos = sensorFluxo->_Pulsos; + unsigned long periodoMs = sensorFluxo->_PeriodoTotal; + + if (pulsos > 0) { + _ultimoPulsoFluxoMs = agora; + } + + double vazaoMLs = 0.0; + + if (pulsos > 0 && periodoMs > 0) { + double frequenciaHz = ((double)pulsos) * 1000.0 / ((double)periodoMs); + double q_L_min = (frequenciaHz + 3.0) / 8.1; + + if (q_L_min < 0.0) { + q_L_min = 0.0; + } + + if (q_L_min > 20.0) { + q_L_min = 20.0; + } + + vazaoMLs = q_L_min * (1000.0 / 60.0); + } + + bool pulsoRecente = + _ultimoPulsoFluxoMs > 0 && + (agora - _ultimoPulsoFluxoMs) <= TimeoutPulsoFluxoRecenteMs; + + if (!pulsoRecente) { + vazaoMLs = 0.0; + } + + VazaoFluxoAtualMLs = vazaoMLs; + VazaoFluxoCorrigidaAtualMLs = vazaoMLs * FatorCalibracaoFluxoFirmware; + + FluxoPresenteAtual = + pulsoRecente || + VazaoFluxoCorrigidaAtualMLs >= VazaoMinimaOperacaoMLs; + } + + void IniciarKick(uint8_t idx) { + if (!HabilitarKickInicial || idx == 0) { + return; + } + + _kickAtivo = true; + _inicioKickMs = millis(); + ArmamentoLinhaAtivo = true; + } + + double ObterPotenciaKick(uint8_t idx) { + idx = LimitarIndiceBicos(idx); + + if (idx > 0 && idx <= 2) { + return constrain(PotenciaKickPoucosBicosPct, 0.0, 100.0); + } + + if (idx >= 3) { + return constrain(PotenciaKickMuitosBicosPct, 0.0, 100.0); + } + + return 0.0; + } + + unsigned long ObterDuracaoKickMs(uint8_t idx) { + idx = LimitarIndiceBicos(idx); + + if (idx > 0 && idx <= 2) { + return DuracaoKickPoucosBicosMs; + } + + if (idx >= 3) { + return DuracaoKickMuitosBicosMs; + } + + return 0; + } + + double AplicarKickInicial(double potenciaDesejada, uint8_t idx) { + if (!_kickAtivo || idx == 0) { + return potenciaDesejada; + } + + unsigned long duracao = ObterDuracaoKickMs(idx); + unsigned long agora = millis(); + + if (duracao == 0 || (agora - _inicioKickMs) > duracao) { + ResetarKick(); + ArmamentoLinhaAtivo = false; + return potenciaDesejada; + } + + return MaiorDouble(potenciaDesejada, ObterPotenciaKick(idx)); + } + + double ObterPotenciaMinimaOperacional(uint8_t idx) { + idx = LimitarIndiceBicos(idx); + + double minimo = PotenciaMinAtiva; + + if (idx > 0) { + minimo = MaiorDouble(minimo, PotenciaMinimaComBicosPorQtd[idx]); + } + + minimo = MaiorDouble(minimo, PotenciaMinPID); + return constrain(minimo, 0.0, PotenciaMaxPID); + } + + double ObterPressaoMaximaMapeada(uint8_t idx) { + idx = LimitarIndiceBicos(idx); + return PressaoMaximaMapeadaPorBicos[idx]; + } + + double ObterPotenciaMaximaMapeada(uint8_t idx) { + idx = LimitarIndiceBicos(idx); + + double potencia = PotenciaBaseMaxPsi[idx]; + + if (potencia <= 0.1) { + potencia = PotenciaBase22Psi[idx]; + } + + if (potencia <= 0.1) { + potencia = PotenciaBase15Psi[idx]; + } + + return constrain(potencia, 0.0, 100.0); + } + + double Interpolar(double x1, double y1, double x2, double y2, double x) { + if (fabs(x2 - x1) < 0.001) { + return constrain(y2, 0.0, 100.0); + } + + double t = (x - x1) / (x2 - x1); + t = constrain(t, 0.0, 1.0); + + double y = y1 + ((y2 - y1) * t); + return constrain(y, 0.0, 100.0); + } + + double CalcularPotenciaFeedForward(uint8_t idx, double pressaoSP) { + idx = LimitarIndiceBicos(idx); + + if (!HabilitarFeedForward || idx == 0 || pressaoSP <= 0.0) { + return 0.0; + } + + double p15 = constrain(PotenciaBase15Psi[idx], 0.0, 100.0); + double p22 = constrain(PotenciaBase22Psi[idx], 0.0, 100.0); + double pMax = ObterPotenciaMaximaMapeada(idx); + double pressaoMax = ObterPressaoMaximaMapeada(idx); + + double potencia = 0.0; + + if (pressaoMax > 0.0 && pressaoSP >= pressaoMax) { + potencia = pMax; + } else if (pressaoMax > 0.0 && pressaoMax <= PressaoReferenciaBaixaPsi) { + potencia = Interpolar(0.0, 0.0, pressaoMax, pMax, pressaoSP); + } else if (pressaoSP <= PressaoReferenciaBaixaPsi) { + potencia = p15; + } else if (pressaoMax > PressaoReferenciaBaixaPsi && pressaoMax <= PressaoReferenciaAltaPsi) { + potencia = Interpolar( + PressaoReferenciaBaixaPsi, + p15, + pressaoMax, + pMax, + pressaoSP + ); + } else if (pressaoSP <= PressaoReferenciaAltaPsi) { + potencia = Interpolar( + PressaoReferenciaBaixaPsi, + p15, + PressaoReferenciaAltaPsi, + p22, + pressaoSP + ); + } else if (pressaoMax > PressaoReferenciaAltaPsi && pressaoSP <= pressaoMax) { + potencia = Interpolar( + PressaoReferenciaAltaPsi, + p22, + pressaoMax, + pMax, + pressaoSP + ); + } else { + potencia = pMax; + } + + return constrain(potencia * constrain(PesoFeedForward, 0.0, 1.0), 0.0, 100.0); + } + + double CalcularPressaoSPControle(uint8_t idx, double pressaoSP) { + idx = LimitarIndiceBicos(idx); + + double spControle = pressaoSP; + + bool precisaFluxo = + idx > 0 && + HabilitarSupervisorFluxo && + SensorFluxoDisponivel() && + !FluxoPresenteAtual; + + if (precisaFluxo) { + if (idx <= 2 && PressaoSPMinSemFluxoPoucosBicosPsi > 0.0) { + spControle = MaiorDouble(spControle, PressaoSPMinSemFluxoPoucosBicosPsi); + } else if (idx >= 3 && PressaoSPMinSemFluxoMuitosBicosPsi > 0.0) { + spControle = MaiorDouble(spControle, PressaoSPMinSemFluxoMuitosBicosPsi); + } + } + + return constrain(spControle, 0.0, 250.0); + } + + bool SPForaDoMapa(uint8_t idx, double pressaoSP) { + if (idx == 0 || pressaoSP <= 0.0) { + return false; + } + + double pressaoMax = ObterPressaoMaximaMapeada(idx); + + if (pressaoMax <= 0.0) { + return false; + } + + return pressaoSP > pressaoMax; + } + + bool TratarCorteSeguranca() { + if (sensorPressao == nullptr || PressaoCorteSegurancaPsi <= 0.0) { + CorteSegurancaAtivo = false; + return false; + } + + double pressaoProtecao = MaiorDouble(PressaoFiltrada, PressaoSensorAtual); + + if (pressaoProtecao >= PressaoCorteSegurancaPsi) { + CorteSegurancaAtivo = true; + } + + if (CorteSegurancaAtivo) { + AplicarSaida(false, 0); + ResetarIntegralPressao(); + bombaDesligadaPorPressao = true; + + if (pressaoProtecao <= (PressaoCorteSegurancaPsi - HistereseRearmeSegurancaPsi)) { + CorteSegurancaAtivo = false; + bombaDesligadaPorPressao = false; + } + + return true; + } + + return false; + } + + void ControlarOnOffLegado(double pressaoSP, double potenciaOn, double histeresePsi) { + PressaoSPControleAtual = pressaoSP; + PotenciaFeedForwardAtual = 0.0; + CorrecaoPIAtual = 0.0; + + double histerese = constrain(histeresePsi, 0.5, 30.0); + + if (PressaoFiltrada >= (pressaoSP + histerese)) { bombaDesligadaPorPressao = true; } - if (PressaoAtual <= (_PressaoSP - HistereseOnOffPsi)) { + if (PressaoFiltrada <= (pressaoSP - histerese)) { bombaDesligadaPorPressao = false; } @@ -584,123 +1161,169 @@ class BombaPressurizadora_v2 : public ComponenteCAN { return; } - double potencia = _PotenciaSP; + double potencia = potenciaOn; - if (potencia > 0.0 && potencia < PotenciaMinAtiva) { - potencia = PotenciaMinAtiva; + if (potencia <= 0.0) { + potencia = PotenciaMaxPID; } + if (QtdBicosLigados > 0) { + potencia = MaiorDouble(potencia, ObterPotenciaMinimaOperacional(QtdBicosLigados)); + } + + potencia = constrain(potencia, 0.0, PotenciaMaxPID); AplicarPotenciaPercentual(potencia); } - void ControlarFeedForwardPI(double dt) { - if (QtdBicosLigados != _ultimaQtdBicosLigados) { - _ultimaQtdBicosLigados = QtdBicosLigados; + void ControlarPressaoSuaveComFluxo(double dt) { + uint8_t idx = LimitarIndiceBicos(QtdBicosLigados); - /* - Mudou a demanda hidraulica. - Zera o erro acumulado para evitar integral velha brigando com a nova tabela. - */ - _integral = 0.0; - _ultimoErro = 0.0; + if (idx != _ultimaQtdBicosLigados) { + uint8_t anterior = _ultimaQtdBicosLigados; + _ultimaQtdBicosLigados = idx; + ResetarIntegralPressao(); + + if (anterior == 0 && idx > 0) { + IniciarKick(idx); + } } - double potenciaBase = CalcularPotenciaFeedForward(QtdBicosLigados, _PressaoSP); + if (idx == 0) { + ResetarEstadosDeBicosFechados(); + return; + } + + double spControle = CalcularPressaoSPControle(idx, _PressaoSP); + PressaoSPControleAtual = spControle; + PressaoMaximaMapeadaAtual = ObterPressaoMaximaMapeada(idx); + PressaoSPForaDoMapaAtual = SPForaDoMapa(idx, spControle); + PressaoSPLimitadoPorCapacidade = false; + + double potenciaBase = CalcularPotenciaFeedForward(idx, spControle); PotenciaFeedForwardAtual = potenciaBase; - double erro = _PressaoSP - PressaoFiltrada; + double erro = spControle - PressaoFiltrada; + double erroParaPI = erro; + + if (fabs(erroParaPI) <= DeadbandPressaoPsi) { + erroParaPI = 0.0; + } + double derivadaMedicao = 0.0; if (dt > 0.001) { derivadaMedicao = (PressaoFiltrada - _ultimaPressaoFiltrada) / dt; } - double correcaoSemIntegralNova = - (Kp * erro) + - (Ki * _integral) - - (Kd * derivadaMedicao); - - correcaoSemIntegralNova = constrain( - correcaoSemIntegralNova, - CorrecaoMinPI, - CorrecaoMaxPI - ); - - double saidaSemIntegralNova = potenciaBase + correcaoSemIntegralNova; - double minimo = (QtdBicosLigados > 0) ? PotenciaMinAtiva : 0.0; + double minimo = ObterPotenciaMinimaOperacional(idx); double maximo = PotenciaMaxPID; - bool saturouAlto = saidaSemIntegralNova >= maximo && erro > 0.0; - bool saturouBaixo = saidaSemIntegralNova <= minimo && erro < 0.0; + /* + Integrador anti-windup. Integra pouco quando saturado. + */ + double saidaTeste = + potenciaBase + + (Kp * erroParaPI) + + (Ki * _integralPressao) - + (Kd * derivadaMedicao); - if (!saturouAlto && !saturouBaixo) { - _integral += erro * dt; - _integral = constrain(_integral, -120.0, 120.0); + bool saturadoAlto = saidaTeste >= maximo && erroParaPI > 0.0; + bool saturadoBaixo = saidaTeste <= minimo && erroParaPI < 0.0; + + if (!saturadoAlto && !saturadoBaixo) { + _integralPressao += erroParaPI * dt; + _integralPressao = constrain(_integralPressao, -220.0, 220.0); } double correcao = - (Kp * erro) + - (Ki * _integral) - + (Kp * erroParaPI) + + (Ki * _integralPressao) - (Kd * derivadaMedicao); - correcao = constrain(correcao, CorrecaoMinPI, CorrecaoMaxPI); CorrecaoPIAtual = correcao; double potenciaDesejada = potenciaBase + correcao; potenciaDesejada = constrain(potenciaDesejada, minimo, maximo); - double potenciaComRampa = AplicarRampaPercentual(potenciaDesejada, dt); + potenciaDesejada = AplicarSupervisorFluxo(potenciaDesejada, dt); + potenciaDesejada = AplicarKickInicial(potenciaDesejada, idx); + double potenciaComRampa = AplicarRampaPercentual(potenciaDesejada, dt); AplicarPotenciaPercentual(potenciaComRampa); - _ultimoErro = erro; + _ultimoErroPressao = erro; _ultimaPressaoFiltrada = PressaoFiltrada; } - double CalcularPotenciaFeedForward(uint8_t qtdBicos, double pressaoSP) { - if (qtdBicos == 0) { - return 0.0; + double AplicarSupervisorFluxo(double potenciaDesejada, double dt) { + uint8_t idx = LimitarIndiceBicos(QtdBicosLigados); + + if (!HabilitarSupervisorFluxo || !SensorFluxoDisponivel() || idx == 0) { + ResetarSupervisorFluxo(); + return potenciaDesejada; } - if (qtdBicos > 7) { - qtdBicos = 7; + unsigned long agora = millis(); + bool fluxoOk = FluxoPresenteAtual || VazaoFluxoCorrigidaAtualMLs >= VazaoMinimaOperacaoMLs; + + if (!fluxoOk) { + FluxoInsuficienteAtual = true; + + if (_inicioSemFluxoMs == 0) { + _inicioSemFluxoMs = agora; + } + + _inicioComFluxoMs = 0; + + if ((agora - _inicioSemFluxoMs) >= TempoSemFluxoParaBoostMs) { + RecuperacaoFluxoAtiva = true; + } + } else { + FluxoInsuficienteAtual = false; + _inicioSemFluxoMs = 0; + + if (_inicioComFluxoMs == 0) { + _inicioComFluxoMs = agora; + } + + if ((agora - _inicioComFluxoMs) >= TempoComFluxoParaAliviarMs) { + RecuperacaoFluxoAtiva = false; + } } - double p15 = PotenciaBase15Psi[qtdBicos]; - double p30 = PotenciaBase30Psi[qtdBicos]; + double maxBoost = (idx <= 2) + ? BoostFluxoMaxPoucosBicosPct + : BoostFluxoMaxMuitosBicosPct; - bool p15Valida = p15 > 0.1; - bool p30Valida = p30 > 0.1; + maxBoost = constrain(maxBoost, 0.0, 100.0); - if (!p15Valida && !p30Valida) { - return _PotenciaSP; + bool permitirSubirBoost = + RecuperacaoFluxoAtiva && + PressaoFiltrada < PressaoMaxAssistenciaFluxoPsi; + + if (permitirSubirBoost) { + BoostFluxoAtualPct += TaxaBoostFluxoSubidaPctPorSegundo * dt; + } else { + BoostFluxoAtualPct -= TaxaBoostFluxoDescidaPctPorSegundo * dt; } - if (!p15Valida) { - return constrain(p30, 0.0, 100.0); + BoostFluxoAtualPct = constrain(BoostFluxoAtualPct, 0.0, maxBoost); + + if (BoostFluxoAtualPct > 0.1) { + potenciaDesejada = MaiorDouble( + potenciaDesejada, + ObterPotenciaMinimaOperacional(idx) + ); + + potenciaDesejada += BoostFluxoAtualPct; } - if (!p30Valida) { - return constrain(p15, 0.0, 100.0); - } - - if (pressaoSP <= PressaoReferenciaBaixaPsi) { - return constrain(p15, 0.0, 100.0); - } - - if (pressaoSP >= PressaoReferenciaAltaPsi) { - return constrain(p30, 0.0, 100.0); - } - - double t = - (pressaoSP - PressaoReferenciaBaixaPsi) / - (PressaoReferenciaAltaPsi - PressaoReferenciaBaixaPsi); - - double potencia = p15 + ((p30 - p15) * t); - return constrain(potencia, 0.0, 100.0); + return constrain(potenciaDesejada, 0.0, PotenciaMaxPID); } double AplicarRampaPercentual(double potenciaDesejada, double dt) { + potenciaDesejada = constrain(potenciaDesejada, 0.0, PotenciaMaxPID); + double atual = PotenciaFinalAtual; double delta = potenciaDesejada - atual; @@ -789,21 +1412,15 @@ class BombaPressurizadora_v2 : public ComponenteCAN { EstadoLeitura = Estado::Ligado; } - void ResetarControleFechado() { - _integral = 0.0; - _ultimoErro = 0.0; - _ultimaPressaoFiltrada = PressaoFiltrada; - _ultimaQtdBicosLigados = 0; - CorrecaoPIAtual = 0.0; - PotenciaFeedForwardAtual = 0.0; - bombaDesligadaPorPressao = false; - } - uint8_t LimitarByte(double valor) { valor = constrain(valor, 0.0, 255.0); return static_cast(round(valor)); } + double MaiorDouble(double a, double b) { + return (a > b) ? a : b; + } + void MostrarLog(String mensagem) { if (DebugMode) { PrintTela("[BMB " + _ID + "] " + mensagem); diff --git a/Firmware/Modulos/SensorPressaoModel_v2.h b/Firmware/Modulos/SensorPressaoModel_v2.h index 5cf70697d..bce8db63c 100644 --- a/Firmware/Modulos/SensorPressaoModel_v2.h +++ b/Firmware/Modulos/SensorPressaoModel_v2.h @@ -35,6 +35,10 @@ class SensorPressao_v2 : public ComponenteCAN { double _Vmax = 4.5; double _Pmax = 174.045; + double GanhoCalibracaoPressao = 1.1361506; + double OffsetCalibracaoPressao = -14.887795; + + int leitura = 0; double Tensao = 0.0; double Pressao = 0.0; @@ -90,12 +94,16 @@ class SensorPressao_v2 : public ComponenteCAN { double leituraFiltrada = filtro.updateEstimate(leitura); Tensao = fmap(leituraFiltrada, 0.0, 4095.0, 0.0, 3300.0) / 1000.0; - Pressao = fmap(Tensao, _Vmin, _Vmax, 0.0, _Pmax); + + double pressaoBruta = fmap(Tensao, _Vmin, _Vmax, 0.0, _Pmax); + + Pressao = (pressaoBruta * GanhoCalibracaoPressao) + OffsetCalibracaoPressao; if (Pressao < 0) Pressao = 0; if (Pressao > _Pmax) Pressao = _Pmax; } + std::vector MontarMensagemCAN(CanMessagePosicaoDados posicao) { std::vector data; data.push_back(static_cast(posicao));