From e2301769507104fb74178db93b3e176f88a1b7e9 Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Thu, 26 Mar 2026 21:05:42 -0300 Subject: [PATCH] =?UTF-8?q?revis=C3=A3o=20contador=20de=20carga=20e=20cool?= =?UTF-8?q?er=20control?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Forms/Operacoes/frmResultadosOperacao.cs | 12 +- AgroBase/AgroBase/Models/GeneralJoystick.cs | 4 +- .../Models/Modules/MovimentacaoModel.cs | 2 +- .../Models/Modules/SensoriamentoModel.cs | 1 - .../Models/Operacoes/OperacaoModel.cs | 11 +- .../AgroBase/Services/CoolerControlService.cs | 22 +- .../Services/CoulombCounterService.cs | 446 +++++++++++------- 7 files changed, 304 insertions(+), 194 deletions(-) diff --git a/AgroBase/AgroBase/Forms/Operacoes/frmResultadosOperacao.cs b/AgroBase/AgroBase/Forms/Operacoes/frmResultadosOperacao.cs index 466633514..5b85a4b4a 100644 --- a/AgroBase/AgroBase/Forms/Operacoes/frmResultadosOperacao.cs +++ b/AgroBase/AgroBase/Forms/Operacoes/frmResultadosOperacao.cs @@ -1141,12 +1141,16 @@ namespace AgroBase.Forms.Operacoes lblPercentualBateria.Text = $"Bateria {Log.Bateria.PercentualBateria.ToString("0.00")}%"; FuncoesGlobais.PreencheValorProgressBar(pgbPercentualBateria, Log.Bateria.PercentualBateria); - lblBateriaConsumida.Text = $"Consumido {Log.Bateria.PercentualBateriaConsumida.ToString("0.00")}%"; - FuncoesGlobais.PreencheValorProgressBar(pgbPercentualReservatorio, LogAtu.PercentualReservatorio); + + lblBateriaConsumida.Text = $"Consumido {Log.Bateria.PercentualBateriaConsumidaOperacao.ToString("0.00")}%"; + FuncoesGlobais.PreencheValorProgressBar(pgbBateriaConsumida, Log.Bateria.PercentualBateriaConsumidaOperacao); + lblPercentualReservatorio.Text = $"Reservatório {LogAtu.PercentualReservatorio.ToString("0.00")}%"; - FuncoesGlobais.PreencheValorProgressBar(pgbHerbicidaConsumido, percentualHerbicidaConsumido); + FuncoesGlobais.PreencheValorProgressBar(pgbPercentualReservatorio, LogAtu.PercentualReservatorio); + lblHerbicidaConsumido.Text = $"Consumido {herbicidaConsumido.ToString("0.00")} ml"; - FuncoesGlobais.PreencheValorProgressBar(pgbBateriaConsumida, Log.Bateria.PercentualBateriaConsumida); + FuncoesGlobais.PreencheValorProgressBar(pgbHerbicidaConsumido, percentualHerbicidaConsumido); + txtTempoMovimento.Text = FuncoesGlobais.ConverterSegundosParaHHmmss((int)Log.Movimentacao.TempoMovimentoSegs); txtStatusOperacao.Text = Log.StatusOperacao.ToString(); txtStatusCarro.Text = $"{Log.Trajetoria.StatusCarro}"; diff --git a/AgroBase/AgroBase/Models/GeneralJoystick.cs b/AgroBase/AgroBase/Models/GeneralJoystick.cs index e1aeafde0..95bf109e9 100644 --- a/AgroBase/AgroBase/Models/GeneralJoystick.cs +++ b/AgroBase/AgroBase/Models/GeneralJoystick.cs @@ -834,13 +834,13 @@ namespace AgroBase } - public static void EnviarComandoSensoriamento(S_Code Componente, string ID, dynamic Status, bool Forcar = false) + public static void EnviarComandoSensoriamento(S_Code Componente, string ID, dynamic Status) //, bool Forcar = false { var DispSen = Variaveis.OperacaoEmAndamento.DispSen; if (!DispSen.Dados.Conectado) return; - if (!Forcar) + if (false) // !Forcar { bool componente_em_uso = Variaveis.OperacaoEmAndamento.Parametros.ModulosMandatorios.Any(x => x.Dispositivo == T_Code.Sen && (x.ComponentesEmUso?.ContainsKey(ID) ?? false) && x.ComponentesEmUso[ID].Item2); if (!componente_em_uso) diff --git a/AgroBase/AgroBase/Models/Modules/MovimentacaoModel.cs b/AgroBase/AgroBase/Models/Modules/MovimentacaoModel.cs index c60a28be3..5b97e7797 100644 --- a/AgroBase/AgroBase/Models/Modules/MovimentacaoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/MovimentacaoModel.cs @@ -626,7 +626,7 @@ namespace AgroBase.Models.Modules { if (EnviarComando && UltimoEnvio.AddMilliseconds(DelayEnvio) < DateTime.Now && Servo != null) { - GeneralJoystick.EnviarComandoSensoriamento(Servo.Componente, Servo.ID, AnguloControle, Forcar: true); + GeneralJoystick.EnviarComandoSensoriamento(Servo.Componente, Servo.ID, AnguloControle); UltimoEnvio = DateTime.Now; } } diff --git a/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs b/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs index 71547a876..f76cc3131 100644 --- a/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/SensoriamentoModel.cs @@ -8,7 +8,6 @@ using System.Globalization; using System.Data; using static AgroBase.Services.LoRaEspService; using AgroBase.Services.Operadores; -using AgroBase.Models.Operadores; using System.Diagnostics; namespace AgroBase.Models.Modules diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index 143ee8981..346301ae4 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -1184,6 +1184,7 @@ namespace AgroBase.Models Controle.Reiniciar(); ControleAnterior.Reiniciar(); + DispSen?.Dados?.ContadorCarga?.IniciarOperacao(); GPSTrajetoria = new List(); @@ -1219,6 +1220,7 @@ namespace AgroBase.Models Variaveis.OperacaoEmAndamento.Finalizando = false; Variaveis.OperacaoEmAndamento.Trajetoria?.AutonomiaCorredor?.Parar(); + Variaveis.OperacaoEmAndamento.DispSen?.Dados?.ContadorCarga?.FinalizarOperacao(); } public void ReiniciarOperacao(bool daBase) @@ -1812,7 +1814,7 @@ namespace AgroBase.Models } S_Code comp = get_comp(controleBase._comp_id); - GeneralJoystick.EnviarComandoSensoriamento(comp, controleBase._comp_id, controleBase._comp_value, Forcar: true); + GeneralJoystick.EnviarComandoSensoriamento(comp, controleBase._comp_id, controleBase._comp_value); return; } @@ -2788,12 +2790,12 @@ namespace AgroBase.Models BmsLigado = _Bateria.BmsLigado, _dt = _Bateria._dt, PercentualBateria = _Bateria.PercentualBateria, - PercentualBateriaConsumida = _Bateria.PercentualConsumido, + PercentualBateriaConsumida = _Bateria.PercentualConsumidoTotal, + PercentualBateriaConsumidaOperacao = _Bateria.PercentualConsumidoOperacao, TempoEstimadoRestanteMinutos = _Bateria.TempoEstimadoMinutos, CorrenteInstantanea = _Bateria.CorrenteInstantanea, TensaoInstantanea = _Bateria.TensaoInstantanea, PotenciaInstantanea = _Bateria.PotenciaInstantanea, - EnergiaConsumida_Wh = _Bateria.EnergiaConsumida_Wh, WhPorMetro = _Bateria.WhPorMetro, DistanciaEstimadaRestanteMetros = _Bateria.DistanciaEstimadaRestante_m, Temperatura = _Bateria.Temperatura, @@ -3143,10 +3145,10 @@ namespace AgroBase.Models public double _dt { get; set; } public double PercentualBateria { get; set; } public double PercentualBateriaConsumida { get; set; } + public double PercentualBateriaConsumidaOperacao { get; set; } public double CorrenteInstantanea { get; set; } public double TensaoInstantanea { get; set; } public double PotenciaInstantanea { get; set; } - public double EnergiaConsumida_Wh { get; set; } public double WhPorMetro { get; set; } public double TempoEstimadoRestanteMinutos { get; set; } public double DistanciaEstimadaRestanteMetros { get; set; } @@ -3165,7 +3167,6 @@ namespace AgroBase.Models CorrenteInstantanea = CorrenteInstantanea, TensaoInstantanea = TensaoInstantanea, PotenciaInstantanea = PotenciaInstantanea, - EnergiaConsumida_Wh = EnergiaConsumida_Wh, WhPorMetro = WhPorMetro, TempoEstimadoRestanteMinutos = TempoEstimadoRestanteMinutos, DistanciaEstimadaRestanteMetros = DistanciaEstimadaRestanteMetros, diff --git a/AgroBase/AgroBase/Services/CoolerControlService.cs b/AgroBase/AgroBase/Services/CoolerControlService.cs index 4038a7fa5..08075293a 100644 --- a/AgroBase/AgroBase/Services/CoolerControlService.cs +++ b/AgroBase/AgroBase/Services/CoolerControlService.cs @@ -33,23 +33,9 @@ namespace AgroBase.Services public bool AlertaTemperaturaCritica { get; private set; } - private SensorReleModel ReleCoolerEntrada - { - get - { - var sRLE = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Reles?.FirstOrDefault(x => x.ID == "RLCIN" && x.Inicializado && x.Controlar); - return sRLE; - } - } + private SensorReleModel ReleCoolerEntrada => Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Reles?.FirstOrDefault(x => x.ID == "RLCIN" && x.Inicializado && x.Controlar); - private SensorReleModel ReleCoolerSaida - { - get - { - var sRLE = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Reles?.FirstOrDefault(x => x.ID == "RLCOUT" && x.Inicializado && x.Controlar); - return sRLE; - } - } + private SensorReleModel ReleCoolerSaida => Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Reles?.FirstOrDefault(x => x.ID == "RLCOUT" && x.Inicializado && x.Controlar); public CoolerControlService() { @@ -169,11 +155,11 @@ namespace AgroBase.Services { if (ReleCoolerEntrada != null && CoolerEntradaEstadoRele != CoolerEntradaEstado) { - GeneralJoystick.EnviarComandoSensoriamento(ReleCoolerEntrada.Componente, ReleCoolerEntrada.ID, CoolerEntradaEstado, true); + GeneralJoystick.EnviarComandoSensoriamento(ReleCoolerEntrada.Componente, ReleCoolerEntrada.ID, CoolerEntradaEstado); } if (ReleCoolerSaida != null && CoolerSaidaEstadoRele != CoolerSaidaEstado) { - GeneralJoystick.EnviarComandoSensoriamento(ReleCoolerSaida.Componente, ReleCoolerSaida.ID, CoolerSaidaEstado, true); + GeneralJoystick.EnviarComandoSensoriamento(ReleCoolerSaida.Componente, ReleCoolerSaida.ID, CoolerSaidaEstado); } } diff --git a/AgroBase/AgroBase/Services/CoulombCounterService.cs b/AgroBase/AgroBase/Services/CoulombCounterService.cs index d7f2e11f8..439b73300 100644 --- a/AgroBase/AgroBase/Services/CoulombCounterService.cs +++ b/AgroBase/AgroBase/Services/CoulombCounterService.cs @@ -8,133 +8,249 @@ namespace AgroBase.Services { public class CoulombCounterService { - public CoulombCounterService(double _max_Amp, double w_m_base = 0.1167) // 3 km/h ~ 350 wh + public CoulombCounterService(double max_Ah, double w_m_base = 0.1167) { - double coulombsMax = _max_Amp * 3600; - CargaMaxima = coulombsMax; + CargaMaxima_C = max_Ah * 3600.0; // Ah -> Coulombs WhPorMetroBase = w_m_base; - //double consumo = (Variaveis.OperacaoEmAndamento.DispSen?.Dados?.BateriaCargaConsumida ?? 0); - //Consumido = consumo < CargaMaxima ? consumo : 0; - Consumido = 0; - CargaAtual = CargaMaxima - Consumido; - //double consumoW = (Variaveis.OperacaoEmAndamento.DispSen?.Dados?.BateriaCargaConsumidaWatts ?? 0); - //ConsumidoWatts = consumoW; - // Autonomia - MargemSegurancaAutonomia = 0.30; // 30% de margem padrão - EnergiaConsumida_Wh = 0; - _energiaConsumidaAnterior_Wh = 0; - _distanciaTotalMetrosAnterior = 0; + Iniciado = false; + BmsLigado = false; + + _estadoInicializado = false; + _ultimaLeituraIntegrada = null; + + CargaAtual_C = 0; + ConsumidoTotal_C = 0; + EnergiaConsumidaTotal_Wh = 0; + + _socInicialInstancia = 0; + _cargaInicialInstancia_C = 0; + + _socInicialOperacao = 0; + _cargaInicioOperacao_C = 0; + _consumidoTotalNoInicioOperacao_C = 0; + _energiaTotalNoInicioOperacao_Wh = 0; + + MargemSegurancaAutonomia = 0.30; + + ResetarHistoricosAutonomia(); } - public double TempoEstimadoMinutos { get; private set; } - private const int BufferSize = 30; // últimos 3 segundos se chamada a cada 100ms - private readonly Queue _historicoCorrente = new Queue(); - public bool Iniciado { get; private set; } - public bool BmsLigado { get; set; } - private double CargaMaxima { get; set; } - private double CargaAtual { get; set; } - public double CargaAtualAmp + public bool BmsLigado { get; private set; } + + private bool _estadoInicializado; + private DateTime? _ultimaLeituraIntegrada; + + // Capacidade e estado absoluto do pack + private double CargaMaxima_C { get; set; } // Coulombs + private double CargaAtual_C { get; set; } // Coulombs + + // Snapshot inicial da instância + private double _socInicialInstancia { get; set; } + private double _cargaInicialInstancia_C { get; set; } + + // Acumuladores totais desde a criação da instância + private double ConsumidoTotal_C { get; set; } + public double ConsumidoTotal_Ah => ConsumidoTotal_C / 3600.0; + + public double PercentualConsumidoTotal { get { - return CargaAtual / 3600.0; + if (CargaMaxima_C <= 0) return 0; + double calc = (ConsumidoTotal_C / CargaMaxima_C) * 100.0; + return FuncoesMatematicas.Clamp(calc, 0, 100); } } - private double Consumido { get; set; } - public double _Consumido + + public double EnergiaConsumidaTotal_Wh { get; private set; } + + // Snapshot do início da operação + private double _socInicialOperacao { get; set; } + private double _cargaInicioOperacao_C { get; set; } + private double _consumidoTotalNoInicioOperacao_C { get; set; } + private double _energiaTotalNoInicioOperacao_Wh { get; set; } + public bool OperacaoAtiva { get; private set; } + public bool OperacaoFinalizada { get; private set; } + + private double _consumidoTotalNoFimOperacao_C; + private double _energiaTotalNoFimOperacao_Wh; + + // Derivados da operação + public double ConsumidoOperacao_C { get { - return Consumido; + if (!OperacaoAtiva && !OperacaoFinalizada) + return 0; + + double referenciaFim = OperacaoFinalizada + ? _consumidoTotalNoFimOperacao_C + : ConsumidoTotal_C; + + return Math.Max(0, referenciaFim - _consumidoTotalNoInicioOperacao_C); } } - public double ConsumidoAmp + public double ConsumidoOperacao_Ah => ConsumidoOperacao_C / 3600.0; + + public double PercentualConsumidoOperacao { get { - return Consumido / 3600.0; + if (CargaMaxima_C <= 0) + return 0; + + double calc = (ConsumidoOperacao_C / CargaMaxima_C) * 100.0; + return FuncoesMatematicas.Clamp(calc, 0, 100); } } + + public double PercentualConsumidoOperacaoDaCargaInicial + { + get + { + if (!OperacaoAtiva && !OperacaoFinalizada) + return 0; + + if (_cargaInicioOperacao_C <= 0) + return 0; + + double calc = (ConsumidoOperacao_C / _cargaInicioOperacao_C) * 100.0; + return FuncoesMatematicas.Clamp(calc, 0, 100); + } + } + + public double EnergiaConsumidaOperacao_Wh + { + get + { + if (!OperacaoAtiva && !OperacaoFinalizada) + return 0; + + double referenciaFim = OperacaoFinalizada + ? _energiaTotalNoFimOperacao_Wh + : EnergiaConsumidaTotal_Wh; + + return Math.Max(0, referenciaFim - _energiaTotalNoInicioOperacao_Wh); + } + } + + // Estado atual public double PercentualAtual { get { - return (CargaAtual / CargaMaxima) * 100; - } - } - public double PercentualConsumido - { - get - { - double calc = (Consumido / CargaMaxima) * 100; - calc = FuncoesMatematicas.Clamp(calc, 0, 100); - return calc; + if (CargaMaxima_C <= 0) return 0; + return FuncoesMatematicas.Clamp((CargaAtual_C / CargaMaxima_C) * 100.0, 0, 100); } } + + public double CargaAtualAmp => CargaAtual_C / 3600.0; public double PercentualBateria { get; private set; } - public double EnergiaConsumida_Wh { get; set; } - public double _dt { get; private set; } + public double Temperatura { get; private set; } public double CorrenteInstantanea { get; private set; } public double TensaoInstantanea { get; private set; } public double PotenciaInstantanea { get; private set; } + public double TempoEstimadoMinutos { get; private set; } + public double _dt { get; private set; } + public double WhRestante { get { - // Exemplo: usar uma tensão nominal do pack - double tensaoNominal = VariaveisEquipamento.TensaoMaximaBateria; - return CargaAtualAmp * tensaoNominal; // Ah * V = Wh + double tensaoReferencia = TensaoInstantanea > 0 + ? TensaoInstantanea + : VariaveisEquipamento.TensaoMaximaBateria; + + return CargaAtualAmp * tensaoReferencia; } } - public double Temperatura { get; private set; } + // Histórico para suavização de corrente + private const int BufferSize = 30; + private readonly Queue _historicoCorrente = new Queue(); + + // Autonomia public double WhPorMetro { get; private set; } - public double WhPorMetroBase { get; set; } // configurado externamente + public double WhPorMetroBase { get; set; } public double WhPorMetroHistorico { get; private set; } + public double WhPorMetroReferencia { get { if (WhPorMetro > 0) return WhPorMetro; if (WhPorMetroHistorico > 0) return WhPorMetroHistorico; - return WhPorMetroBase; // pode ser 0 se você não configurar + return WhPorMetroBase; } } + public double DistanciaMaximaTeorica_m { get; private set; } public double DistanciaMaximaSegura_m { get; private set; } public double DistanciaEstimadaRestante_m { get; private set; } public double MargemSegurancaAutonomia { get; set; } - private const double JanelaAutonomia_m = 50.0; // metros analisados na média + + private const double JanelaAutonomia_m = 50.0; private readonly Queue _historicoDeltaDist_m = new Queue(); private readonly Queue _historicoDeltaWh = new Queue(); private double _somaDistanciaJanela_m; private double _somaEnergiaJanela_Wh; private double _distanciaTotalMetrosAnterior; - private double _energiaConsumidaAnterior_Wh; + private double _energiaConsumidaAnterior_Wh_Total; + public void IniciarOperacao() + { + if (!_estadoInicializado) + return; + + _socInicialOperacao = PercentualBateria; + _cargaInicioOperacao_C = CargaAtual_C; + _consumidoTotalNoInicioOperacao_C = ConsumidoTotal_C; + _energiaTotalNoInicioOperacao_Wh = EnergiaConsumidaTotal_Wh; + + _consumidoTotalNoFimOperacao_C = 0; + _energiaTotalNoFimOperacao_Wh = 0; + + OperacaoAtiva = true; + OperacaoFinalizada = false; + } + + public void FinalizarOperacao() + { + if (!OperacaoAtiva) + return; + + _consumidoTotalNoFimOperacao_C = ConsumidoTotal_C; + _energiaTotalNoFimOperacao_Wh = EnergiaConsumidaTotal_Wh; + + OperacaoAtiva = false; + OperacaoFinalizada = true; + } public void AtualizarConsumo(double distanciaTotalMetrosAtual) { - var DalyBms = DalyBMSService.DadosLeitura; - bool bms_iniciado = DalyBms?.Iniciado ?? false; - DateTime? bms_ultima_leitura = DalyBms?.UltimoComandoRecebido; - double bms_tensao = DalyBms?.Pack?.TensaoTotal_V ?? 0; - double bms_corrente = DalyBms?.Pack?.Corrente_A ?? 0; - double bms_potencia = bms_tensao * bms_corrente; - double bms_soc = DalyBms?.Pack?.SOC_percent ?? 0; - double bms_tmp = DalyBms?.TemperaturaMax ?? 0; + var dalyBms = DalyBMSService.DadosLeitura; + bool bms_iniciado = dalyBms?.Iniciado ?? false; + DateTime? bms_ultima_leitura = dalyBms?.UltimoComandoRecebido; + double bms_tensao = dalyBms?.Pack?.TensaoTotal_V ?? 0; + double bms_corrente = dalyBms?.Pack?.Corrente_A ?? 0; + double bms_soc = dalyBms?.Pack?.SOC_percent ?? 0; + double bms_tmp = dalyBms?.TemperaturaMax ?? 0; - var Sensor = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Sensores?.FirstOrDefault(x => x.ID == "AB36V"); - bool sen_iniciado = Sensor?.Inicializado ?? false; - DateTime? sen_ultima_leitura = Sensor?.UltimaLeitura; - double sen_dt = Sensor?.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente)?.atual?.dt ?? 0; - double sen_tensao = Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Tensao)?.atual?.valor ?? 0; - double sen_corrente = (Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente)?.atual?.valor ?? 0) / 1000.0; - double sen_potencia = Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0; - double sen_soc = Math.Round(FuncoesMatematicas.Map(sen_tensao, VariaveisEquipamento.TensaoMinimaBateria, VariaveisEquipamento.TensaoMaximaBateria, 0.0, 100.0), 2); - double sen_tmp = Sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Temperatura)?.atual?.valor ?? 0; + var sensor = Variaveis.OperacaoEmAndamento.DispSen?.Dados?.Sensores?.FirstOrDefault(x => x.ID == "AB36V"); + bool sen_iniciado = sensor?.Inicializado ?? false; + DateTime? sen_ultima_leitura = sensor?.UltimaLeitura; + double sen_tensao = sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Tensao)?.atual?.valor ?? 0; + double sen_corrente = (sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente)?.atual?.valor ?? 0) / 1000.0; + double sen_soc = Math.Round( + FuncoesMatematicas.Map( + sen_tensao, + VariaveisEquipamento.TensaoMinimaBateria, + VariaveisEquipamento.TensaoMaximaBateria, + 0.0, + 100.0), 2); + double sen_tmp = sensor?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Temperatura)?.atual?.valor ?? 0; Iniciado = bms_iniciado || sen_iniciado; BmsLigado = bms_iniciado; @@ -142,157 +258,161 @@ namespace AgroBase.Services if (!Iniciado) return; - //_dt = Sensor.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Corrente)?.atual?.dt ?? 0; - _dt = (DateTime.Now - ((BmsLigado ? bms_ultima_leitura : sen_ultima_leitura) ?? DateTime.Now)).TotalSeconds; + DateTime? leituraAtual = BmsLigado ? bms_ultima_leitura : sen_ultima_leitura; TensaoInstantanea = BmsLigado ? bms_tensao : sen_tensao; CorrenteInstantanea = BmsLigado ? bms_corrente : sen_corrente; - PotenciaInstantanea = BmsLigado ? bms_potencia : sen_potencia; - PercentualBateria = BmsLigado ? bms_soc : sen_soc; + PotenciaInstantanea = TensaoInstantanea * CorrenteInstantanea; + PercentualBateria = FuncoesMatematicas.Clamp(BmsLigado ? bms_soc : sen_soc, 0, 100); Temperatura = BmsLigado ? bms_tmp : sen_tmp; - PercentualBateria = FuncoesMatematicas.Clamp(PercentualBateria, 0, 100); - // Verifique se as datas são válidas (não MinValue, MaxValue ou valores anômalos) - if (_dt == 0 || _dt > 10) + if (!leituraAtual.HasValue || leituraAtual.Value == DateTime.MinValue) + return; + + // Primeira leitura válida da instância + if (!_estadoInicializado) { - Console.WriteLine("[COULOMB COUNTER] Leitura inválida detectada."); + _socInicialInstancia = PercentualBateria; + _cargaInicialInstancia_C = CargaMaxima_C * (PercentualBateria / 100.0); + CargaAtual_C = _cargaInicialInstancia_C; + + ConsumidoTotal_C = 0; + EnergiaConsumidaTotal_Wh = 0; + + _ultimaLeituraIntegrada = leituraAtual.Value; + _estadoInicializado = true; + + _dt = 0; + AtualizarAutonomia(distanciaTotalMetrosAtual); return; } - // --- COULOMB COUNTING (igual ao que já tinha, só com pequenos cuidados) --- - double deltaCoulombs = CorrenteInstantanea * _dt; // A * s + // Só integra se chegou leitura nova + if (_ultimaLeituraIntegrada.HasValue && leituraAtual.Value <= _ultimaLeituraIntegrada.Value) + { + _dt = 0; + AtualizarAutonomia(distanciaTotalMetrosAtual); + return; + } - Consumido += deltaCoulombs; - if (Consumido < 0) Consumido = 0; - if (Consumido > CargaMaxima) Consumido = CargaMaxima; + _dt = (leituraAtual.Value - _ultimaLeituraIntegrada.Value).TotalSeconds; + _ultimaLeituraIntegrada = leituraAtual.Value; - CargaAtual = CargaMaxima - Consumido; + if (_dt <= 0 || _dt > 10) + { + Console.WriteLine($"[COULOMB COUNTER] dt inválido: {_dt:F3}s"); + return; + } - // --- ENERGIA ACUMULADA (Wh) --- - double potenciaInst_W = TensaoInstantanea * CorrenteInstantanea; // W - double deltaEnergia_Wh = potenciaInst_W * _dt / 3600.0; // Wh + // Assumindo descarga positiva. Se no Daly a descarga vier negativa, inverter aqui. + double correnteDescarga_A = Math.Max(0, CorrenteInstantanea); - EnergiaConsumida_Wh += deltaEnergia_Wh; - //EnergiaConsumida_Wh = Sensor.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Energia)?.atual?.valor ?? 0; + double deltaCoulombs = correnteDescarga_A * _dt; + ConsumidoTotal_C += deltaCoulombs; + ConsumidoTotal_C = FuncoesMatematicas.Clamp(ConsumidoTotal_C, 0, CargaMaxima_C); - // --- HISTÓRICO P/ TEMPO ESTIMADO --- - _historicoCorrente.Enqueue(CorrenteInstantanea); + // Estado absoluto do pack sempre baseado no total da instância + double cargaAtualCalculada = _cargaInicialInstancia_C - ConsumidoTotal_C; + CargaAtual_C = FuncoesMatematicas.Clamp(cargaAtualCalculada, 0, CargaMaxima_C); + + double deltaEnergia_Wh = PotenciaInstantanea * _dt / 3600.0; + if (deltaEnergia_Wh > 0) + EnergiaConsumidaTotal_Wh += deltaEnergia_Wh; + + _historicoCorrente.Enqueue(correnteDescarga_A); if (_historicoCorrente.Count > BufferSize) _historicoCorrente.Dequeue(); - double correnteMedia = _historicoCorrente.Average(); + double correnteMedia = _historicoCorrente.Count > 0 + ? _historicoCorrente.Average() + : 0; - if (TensaoInstantanea > VariaveisEquipamento.TensaoMinimaBateria && correnteMedia > 0) - { - TempoEstimadoMinutos = (CargaAtualAmp / correnteMedia) * 60; - } + if (correnteMedia > 0 && CargaAtualAmp > 0) + TempoEstimadoMinutos = (CargaAtualAmp / correnteMedia) * 60.0; else - { TempoEstimadoMinutos = 0; - } AtualizarAutonomia(distanciaTotalMetrosAtual); } - /// - /// Atualiza os cálculos de autonomia em função da distância total percorrida. - /// - /// IMPORTANTE: - /// - distanciaTotalMetrosAtual deve ser o "total rodado" acumulado em metros (odometria / MPC / motores). - /// - Aqui dentro calculamos o delta de distância e o delta de energia desde a última chamada. - /// - /// Distância total percorrida até o momento (em metros). private void AtualizarAutonomia(double distanciaTotalMetrosAtual) { - if (!Iniciado) + if (!_estadoInicializado) return; - // Cálculo dos deltas - double deltaDist_m = distanciaTotalMetrosAtual - _distanciaTotalMetrosAnterior; - double deltaWh = EnergiaConsumida_Wh - _energiaConsumidaAnterior_Wh; - - // Atualiza referências para próxima chamada + double deltaDist_m = Math.Max(0, distanciaTotalMetrosAtual - _distanciaTotalMetrosAnterior); _distanciaTotalMetrosAnterior = distanciaTotalMetrosAtual; - _energiaConsumidaAnterior_Wh = EnergiaConsumida_Wh; - // ---------- 1) ATUALIZAÇÃO DA JANELA (SÓ SE ANDOU) ---------- + double energiaTotalAtual_Wh = EnergiaConsumidaTotal_Wh; + double deltaWh = Math.Max(0, energiaTotalAtual_Wh - _energiaConsumidaAnterior_Wh_Total); + _energiaConsumidaAnterior_Wh_Total = energiaTotalAtual_Wh; + if (deltaDist_m > 0 && deltaWh >= 0) { - // Alimenta a janela móvel _historicoDeltaDist_m.Enqueue(deltaDist_m); _historicoDeltaWh.Enqueue(deltaWh); - _somaDistanciaJanela_m += deltaDist_m; _somaEnergiaJanela_Wh += deltaWh; - // Mantém a janela com no máximo JanelaAutonomia_m metros while (_somaDistanciaJanela_m > JanelaAutonomia_m && _historicoDeltaDist_m.Count > 0) { - double dOld = _historicoDeltaDist_m.Dequeue(); - double whOld = _historicoDeltaWh.Dequeue(); - - _somaDistanciaJanela_m -= dOld; - _somaEnergiaJanela_Wh -= whOld; - } - - // Calcula Wh/m médio da janela - if (_somaDistanciaJanela_m > 0 && _somaEnergiaJanela_Wh > 0) - { - WhPorMetro = _somaEnergiaJanela_Wh / _somaDistanciaJanela_m; - } - else - { - WhPorMetro = 0; - } - - if (WhPorMetro > 0) - { - // EMA simples - double alpha = 0.2; // suavização - if (WhPorMetroHistorico <= 0) - WhPorMetroHistorico = WhPorMetro; - else - WhPorMetroHistorico = WhPorMetroHistorico * (1 - alpha) + WhPorMetro * alpha; + _somaDistanciaJanela_m -= _historicoDeltaDist_m.Dequeue(); + _somaEnergiaJanela_Wh -= _historicoDeltaWh.Dequeue(); } } - // se deltaDist_m <= 0, só não atualiza a janela, mas continua - // ---------- 2) CÁLCULO DA AUTONOMIA (SEMPRE QUE POSSÍVEL) ---------- - double consumoRef_WhPorMetro = WhPorMetroReferencia; + if (_somaDistanciaJanela_m > 0.5) + WhPorMetroHistorico = _somaEnergiaJanela_Wh / _somaDistanciaJanela_m; - if (consumoRef_WhPorMetro > 0 && WhRestante > 0) + WhPorMetro = WhPorMetroHistorico > 0 ? WhPorMetroHistorico : WhPorMetroBase; + + if (WhPorMetroReferencia > 0) { - DistanciaMaximaTeorica_m = WhRestante / consumoRef_WhPorMetro; - - double margem = FuncoesMatematicas.Clamp(MargemSegurancaAutonomia, 0.0, 0.9); - DistanciaMaximaSegura_m = DistanciaMaximaTeorica_m * (1.0 - margem); - if (DistanciaMaximaSegura_m < 0) - DistanciaMaximaSegura_m = 0; + DistanciaMaximaTeorica_m = WhRestante / WhPorMetroReferencia; + DistanciaMaximaSegura_m = DistanciaMaximaTeorica_m * (1.0 - MargemSegurancaAutonomia); + DistanciaEstimadaRestante_m = DistanciaMaximaSegura_m; } else { DistanciaMaximaTeorica_m = 0; DistanciaMaximaSegura_m = 0; + DistanciaEstimadaRestante_m = 0; } - - // Distância estimada restante = distância segura - DistanciaEstimadaRestante_m = DistanciaMaximaSegura_m; } - /// - /// Permite restaurar o valor consumido (em Ah) de um log salvo, por exemplo. - /// Atualiza também CargaAtual. - /// - public void AtualizarValorConsumido(double _consumo_Ah) + private void ResetarHistoricosAutonomia() { - double coulombsConsumidos = _consumo_Ah * 3600; - Consumido = FuncoesMatematicas.Clamp(coulombsConsumidos, 0, CargaMaxima); - CargaAtual = CargaMaxima - Consumido; + _historicoCorrente.Clear(); + + _historicoDeltaDist_m.Clear(); + _historicoDeltaWh.Clear(); + _somaDistanciaJanela_m = 0; + _somaEnergiaJanela_Wh = 0; + + _distanciaTotalMetrosAnterior = 0; + _energiaConsumidaAnterior_Wh_Total = EnergiaConsumidaTotal_Wh; + + WhPorMetro = 0; + WhPorMetroHistorico = 0; + DistanciaMaximaTeorica_m = 0; + DistanciaMaximaSegura_m = 0; + DistanciaEstimadaRestante_m = 0; + + TempoEstimadoMinutos = 0; + _dt = 0; + } + + public void AtualizarValorConsumidoTotal(double consumoTotal_Ah) + { + double coulombsConsumidos = consumoTotal_Ah * 3600.0; + ConsumidoTotal_C = FuncoesMatematicas.Clamp(coulombsConsumidos, 0, CargaMaxima_C); + + if (_estadoInicializado) + { + double cargaAtualCalculada = _cargaInicialInstancia_C - ConsumidoTotal_C; + CargaAtual_C = FuncoesMatematicas.Clamp(cargaAtualCalculada, 0, CargaMaxima_C); + } } - /// - /// Helper para o Manager/Health: indica se, com a situação atual, - /// o robô consegue percorrer uma certa distância em metros. - /// public bool ConseguePercorrer(double distanciaRequerida_m) { if (distanciaRequerida_m <= 0) @@ -300,6 +420,6 @@ namespace AgroBase.Services return DistanciaMaximaSegura_m >= distanciaRequerida_m; } - + } }