From 8eee8e5012c91a866fff0b3cd3dc469b319f9d9f Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Fri, 19 Jun 2026 09:15:05 -0300 Subject: [PATCH] refinamentos fluxo do atuador --- .../Forms/Atuador/frmAtuCalibragem.cs | 12 +- .../AgroBase/Forms/Atuador/frmAtuCamera.cs | 2 +- .../AgroBase/Forms/IHM/frmDiagnosticos.cs | 6 +- .../Forms/Operacoes/frmOperacaoManual.cs | 14 +- .../Forms/Operacoes/frmOperacaoMapaGPS.cs | 2 +- .../AgroBase/Models/Modules/AtuadorModel.cs | 1285 ++++++++++++++--- .../Models/Modules/DirecionalModel.cs | 3 + .../Modules/MovimentacaoUnificadoModel.cs | 4 + .../Models/Operacoes/OperacaoModel.cs | 36 +- AgroBase/AgroBase/Services/GPSService.cs | 16 +- .../Services/HerbicideCounterService.cs | 148 +- 11 files changed, 1281 insertions(+), 247 deletions(-) diff --git a/AgroBase/AgroBase/Forms/Atuador/frmAtuCalibragem.cs b/AgroBase/AgroBase/Forms/Atuador/frmAtuCalibragem.cs index bdd7a2755..f865d939d 100644 --- a/AgroBase/AgroBase/Forms/Atuador/frmAtuCalibragem.cs +++ b/AgroBase/AgroBase/Forms/Atuador/frmAtuCalibragem.cs @@ -32,7 +32,7 @@ namespace AgroBase.Forms.Atuador txtPressaoVmax.Text = DispAtu.Dados._PressaoVmax.ToString(); txtMLRef.Text = DispAtu.Dados.VazaoFluxoMLpSReferencia.ToString(); //txtPulsosRef.Text = DispAtu.Dados._FluxoPulsoRef.ToString(); - txtVolume.Text = DispAtu.Dados.VolumeVazaoAferido.ToString(); + //txtVolume.Text = DispAtu.Dados.VolumeVazaoAferido.ToString(); grdSensoriamento.Rows.Clear(); grdSensoriamento.Columns.Clear(); @@ -135,7 +135,7 @@ namespace AgroBase.Forms.Atuador private void AtualizarGridSensores() { - var BombaPressurizadora = DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(); + var BombaPressurizadora = DispAtu.Dados.BombaPressurizadora; grdSensoriamento.Rows[0].Cells[1].Value = BombaPressurizadora.Inicializado; grdSensoriamento.Rows[1].Cells[1].Value = BombaPressurizadora.ComandoAtuar; grdSensoriamento.Rows[2].Cells[1].Value = BombaPressurizadora._EstadoLeitura; @@ -150,20 +150,20 @@ namespace AgroBase.Forms.Atuador private void nudPressaoSP_ValueChanged(object sender, EventArgs e) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha = Convert.ToInt32(((NumericUpDown)sender).Value); GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: Bomba.ComandoAtuar, Comando: Services.CanMessagePosicaoDados.Command1); } private void btnLigarBomba_Click(object sender, EventArgs e) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: true, Comando: Services.CanMessagePosicaoDados.Command1, Forcar: true); } private void btnDesligarBomba_Click(object sender, EventArgs e) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: false, Comando: Services.CanMessagePosicaoDados.Command1, Forcar: true); } @@ -209,7 +209,7 @@ namespace AgroBase.Forms.Atuador DispAtu.Dados._PressaoVmax = Convert.ToDouble(txtPressaoVmax.Text); //DispAtu.Dados._FluxoMlRef = Convert.ToDouble(txtMLRef.Text); //DispAtu.Dados._FluxoPulsoRef = Convert.ToDouble(txtPulsosRef.Text); - DispAtu.Dados.VolumeVazaoAferido = Convert.ToDouble(txtVolume.Text); + //DispAtu.Dados.VolumeVazaoAferido = Convert.ToDouble(txtVolume.Text); } private void btnCalibrarVazao_Click(object sender, EventArgs e) diff --git a/AgroBase/AgroBase/Forms/Atuador/frmAtuCamera.cs b/AgroBase/AgroBase/Forms/Atuador/frmAtuCamera.cs index 906e3c990..391c354eb 100644 --- a/AgroBase/AgroBase/Forms/Atuador/frmAtuCamera.cs +++ b/AgroBase/AgroBase/Forms/Atuador/frmAtuCamera.cs @@ -121,7 +121,7 @@ namespace AgroBase.Forms //CameraSolo.IniciarRotinaProcesso(); CameraSolo.camera.IniciarCamera( - Variaveis.OperacaoEmAndamento.DispAtu.Dados.ScriptDeteccaoErvas, + "", //Variaveis.OperacaoEmAndamento.DispAtu.Dados.ScriptDeteccaoErvas, new string[] { CameraSolo.MaxLeituras.ToString(), CameraSolo.VideoPorta, diff --git a/AgroBase/AgroBase/Forms/IHM/frmDiagnosticos.cs b/AgroBase/AgroBase/Forms/IHM/frmDiagnosticos.cs index 09582dfac..846c937a5 100644 --- a/AgroBase/AgroBase/Forms/IHM/frmDiagnosticos.cs +++ b/AgroBase/AgroBase/Forms/IHM/frmDiagnosticos.cs @@ -430,8 +430,8 @@ namespace AgroBase.Forms.IHM bool Inicializado = Dispositivo != null && Dispositivo.Dados.ConexaoAtiva; idx = PreencheDadosLinha(grid, Testes, idx, Inicializado ? 1 : 0); - AtuadorBombaModel Bomba = Dispositivo.Dados.BombasPressurizadoras.FirstOrDefault(); - bool BombaInicializado = Bomba.Inicializado; + AtuadorBombaModel Bomba = Dispositivo.Dados.BombaPressurizadora; + bool BombaInicializado = Bomba?.Inicializado ?? false; idx = PreencheDadosLinha(grid, Testes, idx, BombaInicializado ? 1 : 0); SensorModel sMAS = Dispositivo.Dados.Sensores.FirstOrDefault(x => x.Componente == Enums.S_Code.sMAS); @@ -525,7 +525,7 @@ namespace AgroBase.Forms.IHM private async Task EnviarComandoBomba(bool Ligar, double Pressao, int Timeout, AtuadorModel disp = null) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha = Pressao + 10; GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: Ligar, Comando: CanMessagePosicaoDados.Command1); diff --git a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs index e951cae61..56fad6b66 100644 --- a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs +++ b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoManual.cs @@ -54,7 +54,7 @@ namespace AgroBase.Forms.Operacoes cmbModoAgitador.Items.AddRange(Enum.GetNames(typeof(ModoAgitadorCalda))); cmbModoAgitador.SelectedIndex = (int)pC.AtuAgitadorModo; - var b = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID == "BMBLN"); + var b = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; if (b != null) { txtKp.Text = b.Controle.Kp.ToString(); @@ -592,7 +592,7 @@ namespace AgroBase.Forms.Operacoes 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?.ContadorCarga?.AgitadorCalda?._Potencia ?? 0).ToString("0.00"); + gridDados.Rows[37].Cells[1].Value = (Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.AgitadorDeCalda?._Potencia ?? 0).ToString("0.00"); gridDados.Rows[38].Cells[1].Value = ""; gridDados.Rows[39].Cells[1].Value = (_IMU?.RollSeguro ?? 0).ToString("0.00"); gridDados.Rows[40].Cells[1].Value = (_IMU?.PitchSeguro ?? 0).ToString("0.00"); @@ -673,7 +673,7 @@ namespace AgroBase.Forms.Operacoes private void btnLigarBomba_Click(object sender, EventArgs e) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID == "BMBLN"); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; if (Bomba != null) { Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuModoControle = (ModoControleBomba)cmbModoBomba.SelectedIndex; @@ -691,14 +691,14 @@ namespace AgroBase.Forms.Operacoes private void btnDesligarBomba_Click(object sender, EventArgs e) { - var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID == "BMBLN"); + var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora; if (Bomba != null) GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: false, Comando: CanMessagePosicaoDados.Command1, Forcar: true); } private void btnLigarAgitador_Click(object sender, EventArgs e) { - var Agitador = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID == "BMBAGT"); + var Agitador = Variaveis.OperacaoEmAndamento.DispAtu.Dados.AgitadorDeCalda; if (Agitador != null) { Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuAgitadorModo = (ModoAgitadorCalda)cmbModoAgitador.SelectedIndex; @@ -710,7 +710,7 @@ namespace AgroBase.Forms.Operacoes private void btnDeslsigarAgitador_Click(object sender, EventArgs e) { - var Agitador = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID == "BMBAGT"); + var Agitador = Variaveis.OperacaoEmAndamento.DispAtu.Dados.AgitadorDeCalda; if (Agitador != null) GeneralJoystick.EnviarComandoAtuador(Agitador.Componente, Agitador.ID, Status: false, Comando: CanMessagePosicaoDados.Command1, Forcar: true); } @@ -729,7 +729,7 @@ namespace AgroBase.Forms.Operacoes { var Atuador = Variaveis.OperacaoEmAndamento.DispAtu.Dados; - bool ligado = Atuador.BombasPressurizadoras.FirstOrDefault()?._BombaLigada ?? false; + bool ligado = Atuador.BombaPressurizadora?._BombaLigada ?? false; AtuadorFuncoesGerais.DesenharAtuadores( pnlAtuadores, e, diff --git a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoMapaGPS.cs b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoMapaGPS.cs index 03ebd2634..87ee53d94 100644 --- a/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoMapaGPS.cs +++ b/AgroBase/AgroBase/Forms/Operacoes/frmOperacaoMapaGPS.cs @@ -180,7 +180,7 @@ namespace AgroBase.Forms.Operacoes Posicao = x.Posicao, StatusBico = x._EstadoLeitura == Enums.Estado.Ligado, TempoAtuado = x.TempoAtuado, - }).ToList(), Atuador.BombasPressurizadoras.FirstOrDefault()?._BombaLigada ?? false, Atuador.PressaoLinha, Atuador.TempoAtuado); + }).ToList(), Atuador.BombaPressurizadora?._BombaLigada ?? false, Atuador.PressaoLinha, Atuador.TempoAtuado); } private void pnlCaminho_Paint(object sender, PaintEventArgs e) diff --git a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs index 227f660ba..fe1f7d49c 100644 --- a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs +++ b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs @@ -2,6 +2,7 @@ using AgroBase.Services.Operadores; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -58,7 +59,6 @@ namespace AgroBase.Models.Modules public List BicosPulverizadores { get; set; } public List BombasPressurizadoras { get; set; } public List Sensores { get; set; } - public string ScriptDeteccaoErvas { get; set; } = PythonService.ScriptWeedDetector; public AsyncTaskTimerModel tmrRegistrador; private int _TempoReconexaoComponentes = 5; @@ -66,6 +66,31 @@ namespace AgroBase.Models.Modules public AtuadorMensagemJsonModel DadosLeitura { get; set; } = new AtuadorMensagemJsonModel(); public AtuHandler _AtuHandler = new AtuHandler(); + public AtuadorBombaModel BombaPressurizadora + { + get + { + var Bomba = BombasPressurizadoras?.FirstOrDefault(x => x.ID.Contains("BMBLN")); + return Bomba; + } + } + public AtuadorBombaModel AgitadorDeCalda + { + get + { + var Bomba = BombasPressurizadoras?.FirstOrDefault(x => x.ID.Contains("BMBAGT")); + return Bomba; + } + } + + public SensorModel SensorMassaReservatorio + { + get + { + var sMAS = Sensores?.FirstOrDefault(x => x.Componente == S_Code.sMAS && x.ID.Contains("MASRS")); + return sMAS; + } + } private SensorModel SensorAnguloBarraEsquerda { get @@ -89,8 +114,6 @@ namespace AgroBase.Models.Modules private SensorModel SensorFluxoLinha => Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX && x.ID == "FLXLN"); - public double VolumeVazaoAferido { get; set; } = 0; - public double VazaoMaximaSensor_LMin { get; set; } = 20.0; public double VazaoFluxoMLpSInstantanea => SensorFluxoLinha?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0; @@ -134,96 +157,10 @@ namespace AgroBase.Models.Modules } } - private DateTime _ultimoRegistroVazao = DateTime.MinValue; - public double VazaoMediaMLs { get; set; } public double VolumeVazadoML { get; set; } public double TempoAtuado { get; set; } - private List bicosAtivosAnterior = new List(); - - public void AtualizaTempoAtuadoGeral() - { - DateTime agora = DateTime.Now; - - var bicosAtivos = BicosPulverizadores.Where(x => x.Inicializado && x.ComandoAtuar).ToList(); - - bool algumBicoAtivo = bicosAtivos.Count > 0; - bool algumBicoAtivoAnterior = bicosAtivosAnterior?.Any() ?? false; - - if (algumBicoAtivo && !algumBicoAtivoAnterior) - _ultimoRegistroVazao = agora; - - if (!algumBicoAtivo && algumBicoAtivoAnterior) - { - AtualizaVazaoEVolume(bicosAtivosAnterior, agora); - - _ultimoRegistroVazao = DateTime.MinValue; - - foreach (var b in BicosPulverizadores) - b.VazaoInstantaneaMLs = 0; - } - - if (algumBicoAtivo) - AtualizaVazaoEVolume(bicosAtivos, agora); - - VazaoMediaMLs = TempoAtuado > 0 ? VolumeVazadoML / TempoAtuado : 0; - - AtualizaPercentualErvas(); - - bicosAtivosAnterior = new List(bicosAtivos); - } - - private void AtualizaVazaoEVolume(List bicosAtivos, DateTime agora) - { - if (bicosAtivos == null || bicosAtivos.Count == 0) - return; - - if (_ultimoRegistroVazao == DateTime.MinValue) - { - _ultimoRegistroVazao = agora; - return; - } - - double dt = (agora - _ultimoRegistroVazao).TotalSeconds; - _ultimoRegistroVazao = agora; - - if (dt <= 0 || dt > 2.0) - return; - - double vazaoTotalInst = VazaoFluxoMLpSReferencia; - - if (vazaoTotalInst <= 0) - { - foreach (var bico in bicosAtivos) - bico.VazaoInstantaneaMLs = 0; - - return; - } - - VolumeVazadoML += vazaoTotalInst * dt; - TempoAtuado += dt; - - double vazaoPorBicoInst = vazaoTotalInst / bicosAtivos.Count; - - foreach (var bico in bicosAtivos) - { - bico.VazaoInstantaneaMLs = vazaoPorBicoInst; - bico.VolumeVazadoML += vazaoPorBicoInst * dt; - bico.TempoAtuado += dt; - - bico.VazaoMediaMLs = bico.TempoAtuado > 0 - ? bico.VolumeVazadoML / bico.TempoAtuado - : 0; - } - } - - private void AtualizaPercentualErvas() - { - double tempoTotalOperacao = Variaveis.OperacaoEmAndamento.Sensoriamento?.Movimentacao?.TempoMovimentoSegs ?? 0; - PercentualErvasTerreno = tempoTotalOperacao > 0 ? Math.Round((TempoAtuado / tempoTotalOperacao) * 100.0, 2) : 0; - } - #endregion #region PRESSAO @@ -252,6 +189,595 @@ namespace AgroBase.Models.Modules public double PercentualErvasTerreno { get; set; } = 0; + #region METRICAS GERAIS DE TEMPO E VAZAO + + private readonly object _metricasLock = new object(); + private long _ultimoTickMetricas; + private HashSet _bicosAtivosIntervaloAnterior = new HashSet(); + private HashSet _bombasAtivasIntervaloAnterior = new HashSet(); + private Dictionary _potenciaBombasIntervaloAnterior = new Dictionary(); + private double _vazaoIntervaloAnteriorMLs; + private const double IntervaloMaximoIntegracaoSeg = 0.5; + private const double IdadeMaximaTelemetriaSeg = 2.5; + private double _tempoMovimentoAnteriorSeg; + + private bool LeituraSensorRecente(SensorValoresLeituraModel leitura, DateTime agora) + { + if (leitura?.atual == null || leitura.atual.lidoEm == DateTime.MinValue) + { + return false; + } + + double idadeSeg = (agora - leitura.atual.lidoEm).TotalSeconds; + + return idadeSeg >= -1.0 && idadeSeg <= IdadeMaximaTelemetriaSeg; + } + + private double ObterVazaoAtualConfiavelMLs(DateTime agora) + { + var sensor = SensorFluxoLinha; + + if (sensor == null || !sensor.Inicializado || sensor.ValoresLeituras == null) + { + return 0; + } + + var leituraFluxo = sensor.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo); + + /* + * A leitura direta é prioritária, inclusive quando vale zero. + * Zero recente é uma medição válida, não motivo para usar + * pulsos antigos como fallback. + */ + if (LeituraSensorRecente(leituraFluxo, agora)) + { + double vazaoLMin; + + try + { + vazaoLMin = + Convert.ToDouble( + leituraFluxo.atual.valor + ); + } + catch + { + return 0; + } + + if (double.IsNaN(vazaoLMin) || + double.IsInfinity(vazaoLMin) || + vazaoLMin < 0) + { + return 0; + } + + vazaoLMin = FuncoesMatematicas.Clamp( + vazaoLMin, + 0.0, + VazaoMaximaSensor_LMin + ); + + return vazaoLMin * 1000.0 / 60.0; + } + + /* + * Somente usa o cálculo por pulsos se os dois valores + * necessários também forem recentes. + */ + var leituraPulsos = + sensor.ValoresLeituras.FirstOrDefault( + x => x.funcao == FuncoesPinout.QtdPulsos + ); + + var leituraPeriodo = + sensor.ValoresLeituras.FirstOrDefault( + x => x.funcao == FuncoesPinout.Periodo + ); + + if (!LeituraSensorRecente(leituraPulsos, agora) || + !LeituraSensorRecente(leituraPeriodo, agora)) + { + return 0; + } + + double calculadaMLs = + VazaoFluxoMLpSInstantaneaCalc; + + if (double.IsNaN(calculadaMLs) || + double.IsInfinity(calculadaMLs) || + calculadaMLs < 0) + { + return 0; + } + + double maximaMLs = + VazaoMaximaSensor_LMin * + 1000.0 / + 60.0; + + return FuncoesMatematicas.Clamp( + calculadaMLs, + 0.0, + maximaMLs + ); + } + + private double ObterPotenciaAtualConfiavelPct(AtuadorBombaModel bomba, DateTime agora) + { + if (bomba == null || + !bomba.Inicializado || + bomba.ValoresLeituras == null) + { + return 0; + } + + var leituraPotencia = + bomba.ValoresLeituras.FirstOrDefault( + x => x.funcao == FuncoesPinout.Potencia + ); + + if (!LeituraSensorRecente( + leituraPotencia, + agora)) + { + return 0; + } + + double potencia; + + try + { + potencia = + Convert.ToDouble( + leituraPotencia.atual.valor + ); + } + catch + { + return 0; + } + + if (double.IsNaN(potencia) || + double.IsInfinity(potencia)) + { + return 0; + } + + return FuncoesMatematicas.Clamp( + potencia, + 0.0, + 100.0 + ); + } + + public void AtualizarMetricasOperacao() + { + lock (_metricasLock) + { + long agoraTicks = + Stopwatch.GetTimestamp(); + + DateTime agora = + DateTime.Now; + + bool operacaoConcluida = + Variaveis.OperacaoEmAndamento + .Sensoriamento != null && + Variaveis.OperacaoEmAndamento + .Sensoriamento + .StatusOperacao == + StatusOperacao.Concluido; + + bool operacaoAtiva = + Variaveis.OperacaoEmAndamento.Iniciado && + !operacaoConcluida; + + bool atuadorDisponivel = + operacaoAtiva && + DadosLeitura != null && + DadosLeitura.Conectado; + + bool pulverizadorAutomatico = + Variaveis.OperacaoEmAndamento + .Parametros? + .Controle? + .PulverizadorAutomatico + ?? false; + + /* + * Bicos são contabilizados pelo comando de alta resolução. + * O retorno CAN permanece sendo telemetria e diagnóstico. + */ + var bicosAtivosAgora = + atuadorDisponivel + ? BicosPulverizadores + .Where(b => + b != null && + b.Inicializado && + b.Comandar && + b.ComandoAtuar) + .Select(b => b.ID_Num) + .ToHashSet() + : new HashSet(); + + /* + * O mesmo estado usado nas métricas governa + * abertura e fechamento dos trechos. + */ + foreach (var bico in + BicosPulverizadores ?? + new List()) + { + bool atuandoComandado = + bicosAtivosAgora.Contains( + bico.ID_Num + ); + + bico.AtualizarEstadoTrecho( + atuandoComandado + ); + } + + /* + * Em pulverização automática, respeita a mesma liberação + * utilizada por VerificaControleLeitura(). + */ + bool bombasLiberadas = + !pulverizadorAutomatico || + ContadorCarga.PulverizadorLiberado; + + var bombasAtivasAgora = + atuadorDisponivel && + bombasLiberadas + ? BombasPressurizadoras + .Where(b => + b != null && + b.Inicializado && + b.Comandar && + b.ComandoAtuar) + .Select(b => b.ID_Num) + .ToHashSet() + : new HashSet(); + + double vazaoAtualMLs = + operacaoAtiva + ? ObterVazaoAtualConfiavelMLs( + agora + ) + : 0.0; + + double tempoMovimentoAtualSeg = + Variaveis.OperacaoEmAndamento + .Sensoriamento? + .Movimentacao? + .TempoMovimentoSegs + ?? 0.0; + + if (_ultimoTickMetricas != 0) + { + double dt = + (agoraTicks - + _ultimoTickMetricas) / + (double)Stopwatch.Frequency; + + if (dt > 0 && + dt <= IntervaloMaximoIntegracaoSeg) + { + double deltaMovimentoSeg = 0; + + if (tempoMovimentoAtualSeg >= + _tempoMovimentoAnteriorSeg) + { + deltaMovimentoSeg = + tempoMovimentoAtualSeg - + _tempoMovimentoAnteriorSeg; + } + + /* + * O tempo em movimento não pode exceder + * o tempo físico do intervalo integrado. + */ + deltaMovimentoSeg = + FuncoesMatematicas.Clamp( + deltaMovimentoSeg, + 0.0, + dt + ); + + IntegrarIntervaloAnterior( + dt, + deltaMovimentoSeg + ); + } + else + { + /* + * Houve pausa anormal no loop. + * Não inventa tempo nem volume. + */ + ZerarVazoesInstantaneas(); + } + } + + /* + * Contagem de atuações das bombas por borda + * do comando efetivo OFF -> ON. + */ + foreach (var bomba in + BombasPressurizadoras ?? + new List()) + { + bool estavaLigada = + _bombasAtivasIntervaloAnterior + .Contains(bomba.ID_Num); + + bool estaLigada = + bombasAtivasAgora + .Contains(bomba.ID_Num); + + if (!estavaLigada && + estaLigada) + { + bomba.Atuacoes++; + } + } + + /* + * Atualiza o snapshot que representará + * o próximo intervalo. + */ + _ultimoTickMetricas = + agoraTicks; + + _tempoMovimentoAnteriorSeg = + tempoMovimentoAtualSeg; + + _bicosAtivosIntervaloAnterior = + bicosAtivosAgora; + + _bombasAtivasIntervaloAnterior = + bombasAtivasAgora; + + _potenciaBombasIntervaloAnterior = + (BombasPressurizadoras ?? + new List()) + .ToDictionary( + b => b.ID_Num, + b => + ObterPotenciaAtualConfiavelPct( + b, + agora + ) + ); + + _vazaoIntervaloAnteriorMLs = + vazaoAtualMLs; + + AtualizarVazoesInstantaneas( + bicosAtivosAgora, + vazaoAtualMLs + ); + + VazaoMediaMLs = + TempoAtuado > 0 + ? VolumeVazadoML / + TempoAtuado + : 0; + + AtualizaPercentualErvas(); + } + } + + private void IntegrarIntervaloAnterior(double dt, double tempoMovimentoIntervaloSeg) + { + var bicosDoIntervalo = + (BicosPulverizadores ?? + new List()) + .Where(b => + _bicosAtivosIntervaloAnterior + .Contains(b.ID_Num)) + .ToList(); + + /* + * Tempo geral de pulverização: + * tempo durante o qual pelo menos um bico + * permaneceu comandado. + */ + if (bicosDoIntervalo.Count > 0) + { + TempoAtuado += dt; + + foreach (var bico in + bicosDoIntervalo) + { + bico.TempoAtuado += dt; + + bico.TempoAtuadoEmMovimento += + tempoMovimentoIntervaloSeg; + } + + double vazaoTotalMLs = + Math.Max( + 0, + _vazaoIntervaloAnteriorMLs + ); + + if (vazaoTotalMLs > 0) + { + double volumeIntervaloML = + vazaoTotalMLs * dt; + + VolumeVazadoML += + volumeIntervaloML; + + double volumePorBicoML = + volumeIntervaloML / + bicosDoIntervalo.Count; + + foreach (var bico in + bicosDoIntervalo) + { + bico.TempoComFluxo += dt; + + bico.VolumeVazadoML += + volumePorBicoML; + } + } + + /* + * A média precisa ser recalculada mesmo quando + * não existe fluxo. O tempo continua crescendo, + * logo a média operacional deve diminuir. + */ + foreach (var bico in + bicosDoIntervalo) + { + bico.VazaoMediaMLs = + bico.TempoAtuado > 0 + ? bico.VolumeVazadoML / + bico.TempoAtuado + : 0; + } + } + + /* + * Bombas incluem BMBLN e BMBAGT. + * TempoAtuado é baseado no comando efetivo. + * TempoComPotencia usa telemetria CAN recente. + */ + foreach (var bomba in + BombasPressurizadoras ?? + new List()) + { + if (!_bombasAtivasIntervaloAnterior + .Contains(bomba.ID_Num)) + { + continue; + } + + bomba.TempoAtuado += dt; + + double potencia = + _potenciaBombasIntervaloAnterior + .TryGetValue( + bomba.ID_Num, + out double valorPotencia) + ? valorPotencia + : 0; + + if (potencia > 0) + { + bomba.TempoComPotencia += dt; + + bomba.PotenciaAcumuladaPctSeg += + potencia * dt; + } + + /* + * Como o nome é PotenciaMediaAtiva, + * o denominador deve ser o período com + * potência efetivamente observada. + */ + bomba.PotenciaMediaAtiva = + bomba.TempoComPotencia > 0 + ? bomba.PotenciaAcumuladaPctSeg / + bomba.TempoComPotencia + : 0; + } + } + + private void AtualizarVazoesInstantaneas(HashSet ativos, double vazaoTotalMLs) + { + foreach (var bico in BicosPulverizadores) + bico.VazaoInstantaneaMLs = 0; + + if (ativos.Count == 0 || vazaoTotalMLs <= 0) + { + return; + } + + double vazaoPorBico = vazaoTotalMLs / ativos.Count; + + foreach (var bico in BicosPulverizadores) + { + if (ativos.Contains(bico.ID_Num)) + { + bico.VazaoInstantaneaMLs = vazaoPorBico; + } + } + } + + private void AtualizaPercentualErvas() + { + double tempoMovimentoSeg = + Variaveis.OperacaoEmAndamento + .Sensoriamento? + .Movimentacao? + .TempoMovimentoSegs + ?? 0; + + var bicosConsiderados = + BicosPulverizadores? + .Where(b => + b != null && + b.Comandar) + .ToList() + ?? new List(); + + if (tempoMovimentoSeg <= 0 || + bicosConsiderados.Count == 0) + { + PercentualErvasTerreno = 0; + return; + } + + /* + * Soma somente atuação ocorrida enquanto + * o rover acumulava tempo de movimento. + */ + double somaBicoSegEmMovimento = + bicosConsiderados.Sum( + b => b.TempoAtuadoEmMovimento + ); + + /* + * Capacidade total: + * tempo de movimento x quantidade de regiões/bicos. + */ + double capacidadeTotalBicoSeg = + tempoMovimentoSeg * + bicosConsiderados.Count; + + double percentual = + capacidadeTotalBicoSeg > 0 + ? somaBicoSegEmMovimento / + capacidadeTotalBicoSeg * + 100.0 + : 0; + + percentual = + FuncoesMatematicas.Clamp( + percentual, + 0.0, + 100.0 + ); + + PercentualErvasTerreno = + Math.Round( + percentual, + 2 + ); + } + + private void ZerarVazoesInstantaneas() + { + foreach (var bico in BicosPulverizadores) + bico.VazaoInstantaneaMLs = 0; + } + + #endregion + public static AtuadorModel CarregarParametrosIniciais() { AtuadorModel Atuador = new AtuadorModel() @@ -805,7 +1331,9 @@ namespace AgroBase.Models.Modules x.VolumeVazadoML = 0; x.VazaoMediaMLs = 0; x.TempoAtuado = 0; - x.Atuacoes = 0; + x.TempoAtuadoEmMovimento = 0; + x.TempoComFluxo = 0; + x.ReiniciarHistoricoAtuacao(true); x.ValoresLeituras.ForEach(l => { l.atual = new CanCommandFreqModel(); @@ -817,20 +1345,35 @@ namespace AgroBase.Models.Modules x.ComandoAtuar = false; x.PotenciaSP = 0; x.PressaoSP = 0; + x.Atuacoes = 0; + x.TempoAtuado = 0; + x.TempoComPotencia = 0; + x.PotenciaAcumuladaPctSeg = 0; + x.PotenciaMediaAtiva = 0; x.ValoresLeituras.ForEach(l => { l.atual = new CanCommandFreqModel(); }); }); - Dados.VolumeVazaoAferido = 0; - Dados._ultimoRegistroVazao = DateTime.MinValue; Dados.VolumeVazadoML = 0; Dados.VazaoMediaMLs = 0; Dados.TempoAtuado = 0; Dados.AnguloBarraDireita = 0; Dados.AnguloBarraEsquerda = 0; Dados.PercentualErvasTerreno = 0; + + lock (Dados._metricasLock) + { + Dados._ultimoTickMetricas = 0; + Dados._tempoMovimentoAnteriorSeg = 0; + + Dados._bicosAtivosIntervaloAnterior.Clear(); + Dados._bombasAtivasIntervaloAnterior.Clear(); + Dados._potenciaBombasIntervaloAnterior.Clear(); + + Dados._vazaoIntervaloAnteriorMLs = 0; + } } public async Task VerificaDispositivoConectado() @@ -1052,61 +1595,368 @@ namespace AgroBase.Models.Modules private void VerificaControleLeitura() { - if (Variaveis.Fechando) return; + if (Variaveis.Fechando) + return; - bool operacaoConcluida = Variaveis.OperacaoEmAndamento.Sensoriamento.StatusOperacao == StatusOperacao.Concluido; - bool pulverizadorAutomatico = Variaveis.OperacaoEmAndamento.Parametros?.Controle?.PulverizadorAutomatico ?? false; - - foreach (var bico in BicosPulverizadores.Where(x => x.Inicializado)) + var operacao = Variaveis.OperacaoEmAndamento; + + if (operacao?.DispAtu?.Dados?.Conectado != true) + return; + + bool operacaoConcluida = + operacao.Sensoriamento?.StatusOperacao == + StatusOperacao.Concluido; + + bool pulverizadorAutomatico = + operacao.Parametros? + .Controle? + .PulverizadorAutomatico + ?? false; + + bool calibrando = + operacao.Calibrando; + + /* + * Bloqueios absolutos. + * + * Nem mesmo a calibração deve conseguir atuar + * durante emergência, finalização ou após a + * operação ter sido encerrada. + */ + bool bloqueioGeral = + !operacao.Iniciado || + operacao.Finalizando || + operacao.Emergencia || + operacaoConcluida; + + /* + * Durante a calibração, Pausa=true é intencional. + * Portanto, a pausa somente bloqueia atuação fora + * da calibração. + */ + bool atuacaoPermitida = + !bloqueioGeral && + ( + !operacao.Pausa || + calibrando + ); + + /* + * BICOS + * + * ComandoAtuar continua representando a intenção + * gerada pelo Weed Worker, operador ou calibração. + * + * Este método apenas calcula o comando físico. + */ + foreach ( + var bico in + BicosPulverizadores + .Where(x => + x != null && + x.Inicializado) + ) { - bool statusDesejado = bico.Comandar && bico.ComandoAtuar; - if (pulverizadorAutomatico) - statusDesejado &= !operacaoConcluida && Variaveis.OperacaoEmAndamento.Iniciado; - - Estado estadoDesejado = statusDesejado ? Estado.Ligado : Estado.Desligado; - double anguloDesejado = statusDesejado ? bico.AnguloControle : bico.AnguloNeutro; + bool comandoSolicitado = + bico.Comandar && + bico.ComandoAtuar; - bool estadoDivergente = estadoDesejado != bico._EstadoLeitura; - bool anguloDivergente = statusDesejado && Math.Abs(anguloDesejado - bico._AnguloBicoLeitura) > 0.01; + bool statusDesejado = + comandoSolicitado && + atuacaoPermitida; - if (estadoDivergente || anguloDivergente) + /* + * Fora da calibração, a pulverização automática + * somente pode abrir bicos com reservatório liberado. + * + * Durante a calibração, permite o pulso de teste + * explicitamente solicitado pelo método de calibração. + */ + if ( + pulverizadorAutomatico && + !calibrando + ) { - GeneralJoystick.EnviarComandoAtuador( - bico.Componente, - bico.ID, - Status: statusDesejado, - Valores: new List() { anguloDesejado } + statusDesejado &= + ContadorCarga.PulverizadorLiberado; + } + + Estado estadoDesejado = + statusDesejado + ? Estado.Ligado + : Estado.Desligado; + + double anguloDesejado = + statusDesejado + ? bico.AnguloControle + : bico.AnguloNeutro; + + bool estadoDivergente = + estadoDesejado != + bico._EstadoLeitura; + + /* + * Verifica o ângulo também quando desligado, + * para garantir o retorno à posição neutra. + */ + bool anguloDivergente = + Math.Abs( + anguloDesejado - + bico._AnguloBicoLeitura + ) > 1.0; + + if ( + estadoDivergente || + anguloDivergente + ) + { + EnviarComandoFisicoBico( + bico, + statusDesejado, + anguloDesejado ); } } - foreach (var bomba in BombasPressurizadoras.Where(x => x.Inicializado)) + /* + * BOMBAS + */ + foreach ( + var bomba in + BombasPressurizadoras + .Where(x => + x != null && + x.Inicializado) + ) { - bool statusDesejado = bomba.Comandar && bomba.ComandoAtuar; - if (pulverizadorAutomatico) - statusDesejado &= ContadorCarga.PulverizadorLiberado && !operacaoConcluida && Variaveis.OperacaoEmAndamento.Iniciado; - - Estado estadoDesejado = statusDesejado ? Estado.Ligado : Estado.Desligado; - double potenciaDesejada = statusDesejado ? bomba.PotenciaSP : 0; - double pressaoDesejada = statusDesejado ? bomba.PressaoSP : 0; + bool ehBombaPrincipal = + bomba.ID == + BombaPressurizadora?.ID; - bool estadoDivergente = estadoDesejado != bomba._EstadoLeitura; - bool potenciaDivergente = Math.Abs(bomba._PotenciaSP - potenciaDesejada) > 1; - bool pressaoDivergente = bomba.Sensor_ID_Num > -1 && Math.Abs(bomba._PressaoSP - pressaoDesejada) > 0.05; + bool comandoSolicitado = + bomba.Comandar && + bomba.ComandoAtuar; - if (estadoDivergente || potenciaDivergente || pressaoDivergente) + bool statusDesejado; + + /* + * Durante a calibração, respeita exatamente + * o comando colocado por RealizarTestesIniciais(). + * + * Isso vale tanto para a bomba quanto para o + * agitador. + */ + if (calibrando) { - GeneralJoystick.EnviarComandoAtuador( - bomba.Componente, - bomba.ID, - Status: statusDesejado, - Valores: new List() { potenciaDesejada, pressaoDesejada }, - Comando: CanMessagePosicaoDados.Command1 + statusDesejado = + comandoSolicitado && + atuacaoPermitida; + } + /* + * Fora da calibração, a bomba principal automática + * deve permanecer ligada enquanto a operação estiver + * autorizada e houver produto suficiente. + * + * Ela não depende de um ComandoAtuar antigo. + */ + else if ( + ehBombaPrincipal && + pulverizadorAutomatico + ) + { + statusDesejado = + bomba.Comandar && + atuacaoPermitida && + ContadorCarga.PulverizadorLiberado; + } + /* + * Agitador e comandos manuais. + * + * Para o agitador, AtualizarAgitadorCalda() + * já definiu ComandoAtuar e PotenciaSP. + */ + else + { + statusDesejado = + comandoSolicitado && + atuacaoPermitida; + } + + Estado estadoDesejado = + statusDesejado + ? Estado.Ligado + : Estado.Desligado; + + /* + * Valores efetivamente enviados ao hardware. + * + * Os setpoints armazenados no modelo não são + * alterados quando existir um bloqueio temporário. + */ + double potenciaEfetiva = + statusDesejado + ? bomba.PotenciaSP + : 0; + + double pressaoEfetiva; + + if (bomba.Sensor_ID_Num > -1) + { + pressaoEfetiva = + statusDesejado + ? bomba.PressaoSP + : 0; + } + else + { + /* + * Mantém -1 como marcador de bomba sem + * sensor de pressão, como o agitador. + */ + pressaoEfetiva = + bomba.PressaoSP; + } + + bool estadoDivergente = + estadoDesejado != + bomba._EstadoLeitura; + + bool potenciaDivergente = + Math.Abs( + bomba._PotenciaSP - + potenciaEfetiva + ) > 1.0; + + bool pressaoDivergente = + bomba.Sensor_ID_Num > -1 && + Math.Abs( + bomba._PressaoSP - + pressaoEfetiva + ) > 0.05; + + if ( + estadoDivergente || + potenciaDivergente || + pressaoDivergente + ) + { + EnviarComandoFisicoBomba( + bomba, + statusDesejado, + potenciaEfetiva, + pressaoEfetiva ); } } } + private void EnviarComandoFisicoBico( + AtuadorBicoModel bico, + bool status, + double angulo) + { + var dispAtu = + Variaveis.OperacaoEmAndamento?.DispAtu; + + if ( + dispAtu?.Dados?.Conectado != true || + bico == null + ) + { + return; + } + + byte anguloByte = + (byte)Math.Round( + FuncoesMatematicas.Clamp( + angulo, + 0, + 255 + ) + ); + + byte[] data = + { + (byte)( + status + ? Estado.Ligado + : Estado.Desligado + ), + anguloByte + }; + + dispAtu.AdicionarMensagemFila( + bico.ID_Num, + CanMessagePosicaoDados.Command1, + CanMessagePosicaoDados.Command1, + data, + false + ); + } + + private void EnviarComandoFisicoBomba( + AtuadorBombaModel bomba, + bool status, + double potencia, + double pressao) + { + var dispAtu = + Variaveis.OperacaoEmAndamento?.DispAtu; + + if ( + dispAtu?.Dados?.Conectado != true || + bomba == null + ) + { + return; + } + + byte potenciaByte = + (byte)Math.Round( + FuncoesMatematicas.Clamp( + potencia, + 0, + 100 + ) + ); + + /* + * -1 é usado para indicar ausência de sensor + * de pressão. No protocolo atual, representa 255. + */ + byte pressaoByte = + pressao < 0 + ? byte.MaxValue + : (byte)Math.Round( + FuncoesMatematicas.Clamp( + pressao, + 0, + 255 + ) + ); + + byte[] data = + { + (byte)( + status + ? Estado.Ligado + : Estado.Desligado + ), + potenciaByte, + pressaoByte + }; + + dispAtu.AdicionarMensagemFila( + bomba.ID_Num, + CanMessagePosicaoDados.Command1, + CanMessagePosicaoDados.Command1, + data, + false + ); + } + + + private void AtualizaComponentesIniciados() { DateTime Agora = DateTime.Now; @@ -1122,6 +1972,7 @@ namespace AgroBase.Models.Modules foreach (var bico in BicosPulverizadores.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora)) { bico.Inicializado = false; + bico.AtualizarEstadoTrecho(false); } foreach (var sensor in Sensores.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora)) { @@ -1130,7 +1981,7 @@ namespace AgroBase.Models.Modules } - public async Task RealizarTestesIniciais(bool forcar) + private async Task RealizarTestesIniciais(bool forcar) { var calAtu = Variaveis.OperacaoEmAndamento.ModsCalibragem[Dispositivo]; @@ -1146,27 +1997,29 @@ namespace AgroBase.Models.Modules if (!Bomba.Inicializado || ContadorCarga.PercentualAtual < VariaveisEquipamento.PercentualReservatorioMin) { Variaveis.MostrarLog($"[AtuadorModel] [RealizarTestesIniciais] Bomba não iniciada ou nível do reservatório baixo, não irá ligar a bomba {Bomba.ID}"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Bomba não iniciada ou nível do reservatório baixo, não irá ligar a bomba {Bomba.ID}", id: Bomba.ID); continue; } Bomba.PotenciaSP = 100; - if (Bomba.ID == "BMBLN") + if (Bomba.ID == BombaPressurizadora.ID) { Bomba.Controle.Modo = Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuModoControle; - GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List() { (int)Bomba.Controle.Modo, Bomba.Controle.PotenciaMinPID, Bomba.Controle.Kp, Bomba.Controle.Ki, Bomba.Controle.Kd }, Comando: CanMessagePosicaoDados.Command2, Forcar: true); - Bomba.PressaoSP = Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha; + GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List() { (int)Bomba.Controle.Modo, Bomba.Controle.PotenciaMinPID, Bomba.Controle.Kp, Bomba.Controle.Ki, Bomba.Controle.Kd }, Comando: CanMessagePosicaoDados.Command2, Forcar: true); GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List() { Bomba.PotenciaSP, Bomba.PressaoSP }, Comando: CanMessagePosicaoDados.Command1, Forcar: true); } else { Bomba.PressaoSP = -1; - GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List() { Bomba.PotenciaSP, Bomba.PressaoSP }, Comando: CanMessagePosicaoDados.Command1, Forcar: true); + bool ligar = Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuAgitadorModo != ModoAgitadorCalda.SemAgitacao; + GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: ligar, Valores: new List() { Bomba.PotenciaSP, Bomba.PressaoSP }, Comando: CanMessagePosicaoDados.Command1, Forcar: true); } if (Bomba.Sensor_ID_Num == -1) { Variaveis.MostrarLog($"[AtuadorModel] [RealizarTestesIniciais] Bomba {Bomba.ID} não possui um sensor de pressão, será controlada mecanicamente pelo pressostato"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Bomba {Bomba.ID} não possui um sensor de pressão, será controlada mecanicamente pelo pressostato", id: Bomba.ID); calAtu[Bomba.ID] = true; continue; } @@ -1236,12 +2089,16 @@ namespace AgroBase.Models.Modules { tasks.Add(DispAtu.Dados.RealizarTestesIniciais(forcar).ContinueWith(t => { if (t.IsFaulted) + { Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}"); + } if (t.IsCompleted) { var calAtu = Variaveis.OperacaoEmAndamento.ModsCalibragem[T_Code.Atu]; var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não")); Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v)); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v)); } })); @@ -1275,18 +2132,6 @@ namespace AgroBase.Models.Modules AnguloBarraDireita = angulo; } break; - case S_Code.sBIC: - { - var bico = BicosPulverizadores.FirstOrDefault(x => x.ID_Num == id_num); - if (bico == null) return; - - if (bico._estadoLeituraAnterior == Estado.Desligado && bico._EstadoLeitura == Estado.Ligado) - { - bico.Atuacoes++; - bico._estadoLeituraAnterior = bico._EstadoLeitura; - } - } - break; } } @@ -1316,6 +2161,7 @@ namespace AgroBase.Models.Modules public double VazaoMediaMLs { get; set; } public double VazaoInstantaneaMLs { get; set; } public double VolumeVazadoML { get; set; } + public double TempoComFluxo { get; set; } public bool Inicializado { get; set; } public DateTime UltimaLeitura { @@ -1325,9 +2171,8 @@ namespace AgroBase.Models.Modules } } public List ValoresLeituras { get; set; } - public Estado _estadoLeituraAnterior { get; set; } = Estado.Desligado; public Estado _EstadoLeitura => (Estado)(ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? 0); - public int _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.LeituraAngulo)?.atual?.valor ?? 0; + public int _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual? .valor ?? 0; public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar) @@ -1361,28 +2206,104 @@ namespace AgroBase.Models.Modules } + + private readonly object _trechosLock = new object(); + + private List _trechoAtivo; + public List> TrechosAtuado { get; set; } = new List>(); + public int Atuacoes { get; set; } = 0; public double TempoAtuado { get; set; } = 0; + public double TempoAtuadoEmMovimento { get; set; } + public Estado _estadoLeituraAnterior { get; private set; } = Estado.Desligado; - public void AdicionarPontoTrechoAtivo(double latitude, double longitude) + /// + /// Atualiza o ciclo de vida do trecho com base no estado real do bico. + /// + public void AtualizarEstadoTrecho(bool atuandoAgora) { - lock (TrechosAtuado) + lock (_trechosLock) { - if (TrechosAtuado.Count == 0) - return; + bool atuavaAntes = _estadoLeituraAnterior == Estado.Ligado; - var trechoAtual = TrechosAtuado[TrechosAtuado.Count - 1]; + // Borda de subida: OFF -> ON + if (!atuavaAntes && atuandoAgora) + { + _trechoAtivo = new List(); - if (trechoAtual == null) - return; + if (TrechosAtuado == null) + TrechosAtuado = new List>(); - trechoAtual.Add(new PontoGeo() + TrechosAtuado.Add(_trechoAtivo); + + Atuacoes++; + } + // Borda de descida: ON -> OFF + else if (atuavaAntes && !atuandoAgora) + { + /* + * Se foi um pulso tão curto que nenhum GGA chegou, + * remove o trecho vazio, mas mantém a contagem da atuação. + */ + if (_trechoAtivo != null && _trechoAtivo.Count == 0) + { + TrechosAtuado?.Remove(_trechoAtivo); + } + + _trechoAtivo = null; + } + /* + * Recuperação defensiva: + * o estado diz ligado, mas a referência ativa foi perdida, + * por exemplo após carga, clone ou reconstrução do objeto. + */ + else if (atuandoAgora && _trechoAtivo == null) + { + _trechoAtivo = new List(); + + if (TrechosAtuado == null) + TrechosAtuado = new List>(); + + TrechosAtuado.Add(_trechoAtivo); + } + + _estadoLeituraAnterior = atuandoAgora ? Estado.Ligado : Estado.Desligado; + } + } + + public bool AdicionarPontoTrechoAtivo(double latitude, double longitude) + { + lock (_trechosLock) + { + if (_trechoAtivo == null) + return false; + + _trechoAtivo.Add(new PontoGeo() { Latitude = latitude, Longitude = longitude }); + + return true; + } + } + + public void ReiniciarHistoricoAtuacao(bool limparTrechos = true) { + lock (_trechosLock) + { + _trechoAtivo = null; + _estadoLeituraAnterior = Estado.Desligado; + Atuacoes = 0; + + if (limparTrechos) + { + if (TrechosAtuado == null) + TrechosAtuado = new List>(); + else + TrechosAtuado.Clear(); + } } } @@ -1404,13 +2325,29 @@ namespace AgroBase.Models.Modules ID = ID, ID_Num = ID_Num, VolumeVazadoML = VolumeVazadoML, - TrechosAtuado = TrechosAtuado, + TrechosAtuado = + TrechosAtuado? + .Select(trecho => + trecho? + .Select(ponto => new PontoGeo() + { + Latitude = ponto.Latitude, + Longitude = ponto.Longitude + }) + .ToList() + ?? new List() + ) + .ToList() + ?? new List>(), VazaoInstantaneaMLs = VazaoInstantaneaMLs, _estadoLeituraAnterior = _estadoLeituraAnterior, ValoresLeituras = new List(ValoresLeituras), VazaoMediaMLs = VazaoMediaMLs, Posicao = Posicao, TempoAtuado = TempoAtuado, + TempoAtuadoEmMovimento = TempoAtuadoEmMovimento, + AnguloNeutro = AnguloNeutro, + TempoComFluxo = TempoComFluxo, }; return clone; @@ -1454,6 +2391,22 @@ namespace AgroBase.Models.Modules public double _PressaoAtual => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; + public int Atuacoes { get; set; } + + /// + /// Tempo em segundos com estado real Ligado. + /// + public double TempoAtuado { get; set; } + + /// + /// Tempo em segundos com potência real maior que zero. + /// + public double TempoComPotencia { get; set; } + + public double PotenciaAcumuladaPctSeg { get; set; } + + public double PotenciaMediaAtiva { get; set; } + @@ -1656,7 +2609,7 @@ namespace AgroBase.Models.Modules g.DrawString(bicoAtuacao, fontA, Brushes.Black, textXPositionA, textYPositionA); // Tempo atuado em segundos (acima do círculo) - string tempoAtuadoSegundos = $"{(Bicos[i].TempoAtuado / 1000.0):0.0}s"; + string tempoAtuadoSegundos = $"{(Bicos[i].TempoAtuado):0.0}s"; Font fontTempo = new Font("Arial", 7, FontStyle.Regular); SizeF stringSizeTempo = g.MeasureString(tempoAtuadoSegundos, fontTempo); float textXPositionTempo = xPosition + (circleDiameter - stringSizeTempo.Width) / 2; @@ -1699,7 +2652,7 @@ namespace AgroBase.Models.Modules g.FillRectangle(Brushes.Black, tuboXPosition, tuboYPosition, tuboLargura, tuboAltura); // Texto para o tempo de atuação acima da bomba - string tempoAtuacaoTexto = $"{(TempoBombaAtuada / 1000.0):0.0}s"; + string tempoAtuacaoTexto = $"{(TempoBombaAtuada):0.0}s"; Font fontTempoAtuacao = new Font("Arial", 6, FontStyle.Regular); SizeF stringSizeTempoAtuacao = g.MeasureString(tempoAtuacaoTexto, fontTempoAtuacao); float textXPositionTempoAtuacao = bombaXPosition + bombaSize / 2 - stringSizeTempoAtuacao.Width / 2; @@ -1816,7 +2769,12 @@ namespace AgroBase.Models.Modules } catch (Exception ex) { - + Variaveis.MostrarLog( + $"[AtuHandler.AtualizarDados] " + + $"Erro ao atualizar componente={componente}, " + + $"ID_Num={CompID_Num}, " + + $"posicao={Posicao}: {ex}" + ); } } } @@ -1877,13 +2835,18 @@ namespace AgroBase.Models.Modules { bool Iniciado = data[2] == 1; AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); + + var bico = Dados.BicosPulverizadores.FirstOrDefault(x => x.ID_Num == ID_Num); + if (bico != null && !Iniciado) + bico.AtualizarEstadoTrecho(false); + break; } case CanMessagePosicaoDados.Dados1: { Estado estado = (Estado)data[2]; int angulo = data[3]; - AtualizarDados(ID_Num, posicao, componente, new List() { estado, angulo }); + AtualizarDados(ID_Num, posicao, componente, new List() { estado, angulo }); break; } } diff --git a/AgroBase/AgroBase/Models/Modules/DirecionalModel.cs b/AgroBase/AgroBase/Models/Modules/DirecionalModel.cs index ec0cc8464..e4c85d758 100644 --- a/AgroBase/AgroBase/Models/Modules/DirecionalModel.cs +++ b/AgroBase/AgroBase/Models/Modules/DirecionalModel.cs @@ -376,10 +376,12 @@ namespace AgroBase.Models.Modules { await SetZero(); Variaveis.MostrarLog($"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] - Referenciamento Concluído!"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Operante, 100, $"Referenciamento Concluído!", id: Mod_ID); } else { Variaveis.MostrarLog($"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] - Referenciamento Falhou!"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Falha, 0, $"Referenciamento Falhou!", id: Mod_ID); } Referenciando = false; @@ -468,6 +470,7 @@ namespace AgroBase.Models.Modules if (!Sucesso) { Variaveis.MostrarLog($"[DirecionalModel] [ReferenciaMotorRelativo] [DIR {Mod_ID}] - ReferenciaMotorRelativo falhou (timeout ou não inicializado). Entrada={AnguloEntrada}, Saida={AnguloSaida}"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Falha, 0, $"ReferenciaMotorRelativo falhou (timeout ou não inicializado). Entrada={AnguloEntrada}, Saida={AnguloSaida}", id: Mod_ID); } return Sucesso; diff --git a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs index 0c8cd761e..767d8dc9f 100644 --- a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs @@ -458,10 +458,14 @@ namespace AgroBase.Models.Modules if (calDir[Modulo.Modulo_ID]) continue; tasks.Add(Modulo.DirMotor.ReferenciaMotor().ContinueWith(t => { if (t.IsFaulted) + { Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Erro no referenciamento do módulo {Modulo.Modulo_ID}: {t.Exception?.InnerException?.Message}"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {Modulo.Modulo_ID}: {t.Exception?.InnerException?.Message}"); + } if (t.IsCompleted) { Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Referenciamento do módulo {Modulo.Modulo_ID} concluído com sucesso!"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Referenciamento do módulo {Modulo.Modulo_ID} concluído com sucesso!"); calDir[Modulo.Modulo_ID] = true; } })); diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 39cc29645..a653431b7 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -1304,10 +1304,12 @@ namespace AgroBase.Models if (!calibragemAtuNecessaria && !calibragemDirNecessaria && !calibragemMovNecessaria) return; Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Pausa para calibragem inicial"); Variaveis.OperacaoEmAndamento.Pausa = true; await Task.Delay(5000); Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem dos modulos inciada..."); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem dos modulos inciada..."); Variaveis.OperacaoEmAndamento.Calibrando = true; RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", Variaveis.OperacaoEmAndamento.Calibrando)); @@ -1322,6 +1324,7 @@ namespace AgroBase.Models if (tasks.Count == 0) { Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Nenhuma calibragem foi iniciada."); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, $"Nenhuma calibragem foi iniciada."); _Controle.TipoMovimento = tipoControle; Variaveis.OperacaoEmAndamento.Calibrando = false; Variaveis.OperacaoEmAndamento.Pausa = false; @@ -1336,15 +1339,18 @@ namespace AgroBase.Models if (completed == allTasks) { Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem finalizada com sucesso!"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem finalizada com sucesso!"); } else { Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Timeout na calibragem. Continuando mesmo assim."); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, $"Timeout na calibragem. Continuando mesmo assim."); } } catch (Exception ex) { Variaveis.MostrarLog($"[OperacaoModel] [RealizarCalibragemInicialAsync] Erro durante calibragem: {ex.Message}"); + Variaveis.OperacaoEmAndamento.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Falha, 0, $"Erro durante calibragem: {ex.Message}"); } _Controle.TipoMovimento = tipoControle; Variaveis.OperacaoEmAndamento.Calibrando = false; @@ -1623,26 +1629,12 @@ namespace AgroBase.Models } case T_Code.Atu: { - bool enviarComando = false; for (int i = 0; i < _Controle.Bicos.Count; i++) { Estado comandoEstado = _Controle.Bicos[i].ComandoAtuar ? Estado.Ligado : Estado.Desligado; if (ForcarEnvio || comandoEstado != _Controle.Bicos[i]._EstadoLeitura) { GeneralJoystick.EnviarComandoAtuador(_Controle.Bicos[i].Componente, _Controle.Bicos[i].ID, Status: _Controle.Bicos[i].ComandoAtuar); - enviarComando = true; - } - } - - // Ligar a bomba caso esteja desligada e tenha herbicida suficiente no reservatorio - if ((Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.ContadorCarga?.PulverizadorLiberado ?? false) && enviarComando) - { - foreach (var Bomba in Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras) - { - if (Bomba._EstadoLeitura == Estado.Desligado) - { - GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Comando: CanMessagePosicaoDados.Command1); - } } } @@ -2066,8 +2058,8 @@ namespace AgroBase.Models 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.BombasPressurizadoras.FirstOrDefault()?._Potencia ?? 0, - BombaLigada = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombasPressurizadoras.FirstOrDefault().ComandoAtuar, + PotenciaBomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?._Potencia ?? 0, + BombaLigada = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?.ComandoAtuar ?? false, VazaoFluxoMLpSInstantanea = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoFluxoMLpSInstantanea, VazaoFluxoMLpSMedia = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoMediaMLs, VolumeVazaoML = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VolumeVazadoML, @@ -2731,7 +2723,7 @@ namespace AgroBase.Models _DispMvd?.Dados?.AtualizarDadosFreio(); _DispSen?.Dados?.AtualizarStatusSinaleiros(Variaveis.OperacaoEmAndamento.Parametros?.Modo ?? ModoOperacao.NaoDefinido, Variaveis.OperacaoEmAndamento.StatusAtual); _DispSen?.Dados?.AtualizarDadosCameras(); - _DispAtu?.Dados?.AtualizaTempoAtuadoGeral(); + _DispAtu?.Dados?.AtualizarMetricasOperacao(); _Cooler?.Atualizar(tmp_max, DateTime.Now); _Bateria?.AtualizarConsumo(_DispMvd?.Dados?.DistanciaPercorridaTotal ?? 0); _Reservatorio?.AtualizarConsumo(_DispMvd?.Dados?.DistanciaPercorridaTotal ?? 0, Variaveis.OperacaoEmAndamento.TempoDecorridoSegs, _DispMvd?.Dados?.VelocidadeMedia ?? 0); @@ -2743,7 +2735,7 @@ 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?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == "BMBLN"); + var bombaLinha = _DispAtu?.Dados?.BombaPressurizadora; var dadosAtuador = _DispAtu != null ? new OperacaoSensoriamentoLogAtuModel() { QtdCamerasSolo = _DispSen.Dados.CamerasSolo.Count, @@ -2765,11 +2757,11 @@ namespace AgroBase.Models VazaoInstantaneaCalc = _DispAtu.Dados.VazaoFluxoMLpSInstantaneaCalc, VazaoMedia = _DispAtu.Dados.VazaoMediaMLs, TempoBombaAtuada = _DispAtu.Dados.TempoAtuado, - ComandoBomba = bombaLinha.ComandoAtuar, - LeituraComandoBomba = bombaLinha._EstadoLeitura == Estado.Ligado, - LeituraPotenciaBomba = FuncoesMatematicas.Clamp(bombaLinha._Potencia, 0, 100), + 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, + 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, diff --git a/AgroBase/AgroBase/Services/GPSService.cs b/AgroBase/AgroBase/Services/GPSService.cs index 724ddca18..e8bf59744 100644 --- a/AgroBase/AgroBase/Services/GPSService.cs +++ b/AgroBase/AgroBase/Services/GPSService.cs @@ -1214,19 +1214,17 @@ namespace AgroBase.Services _Trajetoria?.AtualizarDistanciaPercorrida(distancia); } - } - var bicos = Variaveis.OperacaoEmAndamento.Controle?.Bicos; - if (bicos != null) - { - foreach (var bico in bicos) + var bicos = Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BicosPulverizadores; + if (bicos != null) { - if (!bico.Inicializado || !bico.Comandar || bico._EstadoLeitura != Estado.Ligado) + foreach (var bico in bicos) { - continue; - } + if (!bico.Inicializado || !bico.Comandar || !bico.ComandoAtuar) + continue; - bico.AdicionarPontoTrechoAtivo(UltimaLeitura.Latitude, UltimaLeitura.Longitude); + bico.AdicionarPontoTrechoAtivo(UltimaLeitura.Latitude, UltimaLeitura.Longitude); + } } } diff --git a/AgroBase/AgroBase/Services/HerbicideCounterService.cs b/AgroBase/AgroBase/Services/HerbicideCounterService.cs index f7ea5f2be..ac31a7bdc 100644 --- a/AgroBase/AgroBase/Services/HerbicideCounterService.cs +++ b/AgroBase/AgroBase/Services/HerbicideCounterService.cs @@ -138,10 +138,6 @@ namespace AgroBase.Services private bool _inicializado; - private SensorModel SensorMassaReservatorio => Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.Sensores?.FirstOrDefault(x => x.Componente == S_Code.sMAS && x.ID == "MASRS"); - - public AtuadorBombaModel AgitadorCalda => Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == "AGTCLD"); - public void IniciarOperacao(double distanciaTotalMetrosAtual, double tempoTotalSegundosAtual) { if (!_inicializado) @@ -169,10 +165,19 @@ namespace AgroBase.Services public void AtualizarConsumo(double distanciaTotalMetrosAtual, double tempoTotalSegundosAtual, double velocidadeAtual_m_s) { - Iniciado = SensorMassaReservatorio?.Inicializado ?? false; + Iniciado = Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.SensorMassaReservatorio?.Inicializado ?? false; if (!Iniciado) + { + PulverizadorLiberado = false; + BicosAtivos = 0; + Pulverizando = false; + VazaoFluxoAtual_mLs = 0; + VazaoEstimativaBicos_mLs = 0; + VazaoReferencia_mLs = 0; + LPorMinutoEstimadoBicos = 0; return; + } LerReservatorio(out double massaLida_Kg, out double massaCalc_Kg, out double volumeMedido_L); @@ -237,11 +242,12 @@ namespace AgroBase.Services private void LerReservatorio(out double massaLida_Kg, out double massaCalc_Kg, out double volumeMedido_L) { - massaLida_Kg = SensorMassaReservatorio?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Massa)?.atual?.valor ?? 0; + var sensor = Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.SensorMassaReservatorio; + massaLida_Kg = sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Massa)?.atual?.valor ?? 0; - double leituraBruta = SensorMassaReservatorio?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.LeituraBruta)?.atual?.valor ?? 0; + double leituraBruta = sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.LeituraBruta)?.atual?.valor ?? 0; - double.TryParse(SensorMassaReservatorio?.Parametros?.FirstOrDefault(x => x.Descricao == "Offset")?.Valor ?? "0", out double offset); + double.TryParse(sensor?.Parametros?.FirstOrDefault(x => x.Descricao == "Offset")?.Valor ?? "0", out double offset); massaCalc_Kg = leituraBruta + offset; @@ -520,35 +526,69 @@ namespace AgroBase.Services public void AtualizarAgitadorCalda(ModoAgitadorCalda modo) { - if (!(AgitadorCalda?.Inicializado ?? false)) - return; + var operacao = Variaveis.OperacaoEmAndamento; - var controle = Variaveis.OperacaoEmAndamento?.Parametros?.Controle; - if (controle == null) + if (operacao?.Calibrando == true) { - AgitadorCalda.PotenciaSP = 0; + /* + * Durante a calibração, RealizarTestesIniciais() + * é o proprietário temporário do agitador. + */ return; } - bool operacaoIniciada = Variaveis.OperacaoEmAndamento?.Iniciado ?? false; - bool pausado = Variaveis.OperacaoEmAndamento?.Pausa ?? false; - bool operacaoAtiva = operacaoIniciada && !pausado; + var agitador = + operacao?.DispAtu?.Dados?.AgitadorDeCalda; + + if (!(agitador?.Inicializado ?? false)) + return; + + var controle = operacao.Parametros?.Controle; + + if (controle == null) + { + agitador.ComandoAtuar = false; + agitador.PotenciaSP = 0; + agitador.PressaoSP = -1; + return; + } + + bool operacaoConcluida = + operacao.Sensoriamento?.StatusOperacao == + StatusOperacao.Concluido; + + bool operacaoAtiva = + operacao.Iniciado && + !operacao.Pausa && + !operacaoConcluida && + PulverizadorLiberado; bool bicosAtuando = - Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.BicosPulverizadores? - .Any(x => x.Comandar && x.ComandoAtuar) ?? false; + operacao.DispAtu?.Dados?.BicosPulverizadores? + .Any(x => + x.Comandar && + x.ComandoAtuar) + ?? false; double potenciaFinal = 0; DateTime agora = DateTime.Now; - if (operacaoAtiva && !_agitadorOperacaoAtivaAnterior) + if ( + operacaoAtiva && + !_agitadorOperacaoAtivaAnterior + ) { - _agitadorBoostAte = agora.AddMilliseconds(AtuAgitadorBoostRetomadaMs); + _agitadorBoostAte = + agora.AddMilliseconds( + AtuAgitadorBoostRetomadaMs + ); + _agitadorUltimaTroca = agora; _agitadorIntermitenteLigado = true; } - bool emBoost = agora < _agitadorBoostAte; + bool emBoost = + agora < _agitadorBoostAte; switch (modo) { @@ -560,11 +600,20 @@ namespace AgroBase.Services if (operacaoAtiva) { if (emBoost) - potenciaFinal = AtuAgitadorPotenciaBoost; + { + potenciaFinal = + AtuAgitadorPotenciaBoost; + } else if (bicosAtuando) - potenciaFinal = AtuAgitadorPotenciaContinua; + { + potenciaFinal = + AtuAgitadorPotenciaContinua; + } else - potenciaFinal = AtuAgitadorPotenciaManutencao; + { + potenciaFinal = + AtuAgitadorPotenciaManutencao; + } } break; @@ -577,32 +626,57 @@ namespace AgroBase.Services } else if (emBoost) { - potenciaFinal = AtuAgitadorPotenciaBoost; + potenciaFinal = + AtuAgitadorPotenciaBoost; } else { - int tempoAtualMs = _agitadorIntermitenteLigado - ? AtuAgitadorTempoLigadoMs - : AtuAgitadorTempoDesligadoMs; + int tempoAtualMs = + _agitadorIntermitenteLigado + ? AtuAgitadorTempoLigadoMs + : AtuAgitadorTempoDesligadoMs; - if ((agora - _agitadorUltimaTroca).TotalMilliseconds >= tempoAtualMs) + if ( + (agora - _agitadorUltimaTroca) + .TotalMilliseconds >= + tempoAtualMs + ) { - _agitadorIntermitenteLigado = !_agitadorIntermitenteLigado; + _agitadorIntermitenteLigado = + !_agitadorIntermitenteLigado; + _agitadorUltimaTroca = agora; } - potenciaFinal = _agitadorIntermitenteLigado - ? AtuAgitadorPotenciaIntermitente - : 0; + potenciaFinal = + _agitadorIntermitenteLigado + ? AtuAgitadorPotenciaIntermitente + : 0; } break; } - potenciaFinal = Math.Min(potenciaFinal, PotenciaLimitePorNivel(PercentualAtual)); - potenciaFinal = Clamp(potenciaFinal, 0, 100); + potenciaFinal = + Math.Min( + potenciaFinal, + PotenciaLimitePorNivel(PercentualAtual) + ); - AgitadorCalda.PotenciaSP = potenciaFinal; - _agitadorOperacaoAtivaAnterior = operacaoAtiva; + potenciaFinal = + Clamp(potenciaFinal, 0, 100); + + /* + * Este método é a fonte de verdade do agitador. + */ + agitador.PotenciaSP = potenciaFinal; + agitador.PressaoSP = -1; + + agitador.ComandoAtuar = + agitador.Comandar && + potenciaFinal > 0; + + _agitadorOperacaoAtivaAnterior = + operacaoAtiva; } } } \ No newline at end of file