diff --git a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs index 7ab7994ca..ccd404b89 100644 --- a/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs +++ b/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs @@ -1940,23 +1940,25 @@ namespace AgroBase.Models.Modules { var op = Variaveis.OperacaoEmAndamento; - if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(T_Code.Atu)) - op.Sensoriamento.Operacao.ModsCalibragem.Add(T_Code.Atu, new Dictionary()); + if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(Dispositivo)) + op.Sensoriamento.Operacao.ModsCalibragem.Add(Dispositivo, new Dictionary()); var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo]; // Ligar bomba foreach (var Bomba in BombasPressurizadoras.Where(x => x.Comandar)) { - calAtu.TryGetValue(Bomba.ID, out var cal); - if (!forcar && cal && calAtu[Bomba.ID]) continue; + if (!forcar && op.ModuloEstaCalibrado(Dispositivo, Bomba.ID)) + continue; - calAtu[Bomba.ID] = false; + op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, false); // Sem herbicida no reservatorio, bomba nao precisa ligar 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}"); op.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); + + op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, false); continue; } @@ -1997,7 +1999,7 @@ namespace AgroBase.Models.Modules { Variaveis.MostrarLog($"[AtuadorModel] [RealizarTestesIniciais] Bomba {Bomba.ID} não possui um sensor de pressão, será controlada mecanicamente pelo pressostato"); op.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; + op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, true); continue; } @@ -2006,7 +2008,7 @@ namespace AgroBase.Models.Modules bool BombaOk = Bomba.AtingiuPressao(); if (BombaOk) { - calAtu[Bomba.ID] = true; + op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, true); } else { @@ -2033,7 +2035,7 @@ namespace AgroBase.Models.Modules await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 4000, 200); BombaOk = Bomba.AtingiuPressao(); - calAtu[Bomba.ID] = BombaOk; + op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, BombaOk); } } @@ -2043,7 +2045,7 @@ namespace AgroBase.Models.Modules calAtu.TryGetValue(Bico.ID, out var cal); if (!forcar && cal && calAtu[Bico.ID]) continue; - calAtu[Bico.ID] = false; + op.MarcarModuloCalibrado(Dispositivo, Bico.ID, false); if (Bico.Inicializado) { @@ -2063,13 +2065,21 @@ namespace AgroBase.Models.Modules await Task.Delay(200); - calAtu[Bico.ID] = true; + op.MarcarModuloCalibrado(Dispositivo, Bico.ID, true); } } await Task.Delay(1000); - bool sucesso = calAtu.All(x => x.Value); + var idsEsperados = BombasPressurizadoras + .Where(x => x.Comandar) + .Select(x => x.ID) + .Concat(BicosPulverizadores + .Where(x => x.Comandar) + .Select(x => x.ID)) + .ToList(); + + bool sucesso = idsEsperados.All(id => op.ModuloEstaCalibrado(Dispositivo, id)); return sucesso; } diff --git a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs index 50de96ef7..71dddcc27 100644 --- a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs @@ -531,11 +531,20 @@ namespace AgroBase.Models.Modules if (!mc.ContainsKey(T_Code.Mov)) mc.Add(T_Code.Mov, new Dictionary()); var calMov = mc[T_Code.Mov]; - foreach (var mod in DispMvd.Dados.Modulos) + foreach (var Modulo in DispMvd.Dados.Modulos) { - calMov.TryGetValue(mod.Modulo_ID, out var cal); - if (!forcar && cal && calMov[mod.Modulo_ID]) continue; - calMov[mod.Modulo_ID] = mod.MovMotor.Inicializado; + calMov.TryGetValue(Modulo.Modulo_ID, out var cal); + if (!forcar && cal && calMov[Modulo.Modulo_ID]) continue; + + bool ok = Modulo.MovMotor?.Inicializado == true; + + op.MarcarModuloCalibrado(T_Code.Mov, Modulo.Modulo_ID, ok); + + if (!ok) + { + Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemMovAsync] MOV {Modulo.Modulo_ID} não inicializado."); + op.Sensoriamento.InserirLog(T_Code.Mov, StatusModulo.Alerta, 50, $"MOV {Modulo.Modulo_ID} não inicializado.", id: Modulo.Modulo_ID); + } } DispMvd.Dados.MovPronto = DispMvd.Dados.Modulos.All(x => x.MovMotor.Inicializado); @@ -548,45 +557,54 @@ namespace AgroBase.Models.Modules var op = Variaveis.OperacaoEmAndamento; List tasks = new List(); - var DispMvd = op.DispMvd; - if (DispMvd != null) + + var dispMvd = op?.DispMvd; + + if (dispMvd?.Dados?.Modulos == null) + return tasks; + + dispMvd.Dados.DirPronto = dispMvd.Dados.Modulos.All(x => x.DirMotor.Inicializado); + + var modsRef = dispMvd.Dados.Modulos + .ToList(); + + foreach (var modulo in modsRef) { - var mc = op.Sensoriamento.Operacao.ModsCalibragem; - if (!mc.ContainsKey(T_Code.Dir)) - mc.Add(T_Code.Dir, new Dictionary()); - var calDir = mc[T_Code.Dir]; - foreach (var mod in DispMvd.Dados.Modulos) + if (!forcar && op.ModuloEstaCalibrado(T_Code.Dir, modulo.Modulo_ID)) + continue; + + op.MarcarModuloCalibrado(T_Code.Dir, modulo.Modulo_ID, false); + + if (!modulo.DirMotor.Inicializado) { - calDir.TryGetValue(mod.Modulo_ID, out var cal); - if (!forcar && cal && calDir[mod.Modulo_ID]) continue; - calDir[mod.Modulo_ID] = false; + Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] DIR {modulo.Modulo_ID} não inicializado."); + op.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Alerta, 50, $"DIR {modulo.Modulo_ID} não inicializado.", id: modulo.Modulo_ID); + continue; } - DispMvd.Dados.DirPronto = DispMvd.Dados.Modulos.All(x => x.DirMotor.Inicializado); + op.DefinirTipoMovimentoControle(TipoMovimentoDirecional.Diagnostico); - if (DispMvd.Dados.Modulos.Any(x => x.DirMotor.Inicializado)) + tasks.Add(Task.Run(async () => { - op.DefinirTipoMovimentoControle(TipoMovimentoDirecional.Diagnostico); - var ModsRef = Modulos.Where(x => x.DirMotor.Comandar).ToList(); // && x.DirMotor.Inicializado - foreach (var Modulo in ModsRef) + try { - 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}"); - op.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!"); - op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Referenciamento do módulo {Modulo.Modulo_ID} concluído com sucesso!"); - calDir[Modulo.Modulo_ID] = true; - } - })); + await modulo.DirMotor.ReferenciaMotor(); + + op.MarcarModuloCalibrado(T_Code.Dir, modulo.Modulo_ID, true); + + Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Referenciamento do módulo {modulo.Modulo_ID} concluído com sucesso!"); + op.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Operante, 100, $"Referenciamento do módulo {modulo.Modulo_ID} concluído com sucesso!", id: modulo.Modulo_ID); } - } + catch (Exception ex) + { + op.MarcarModuloCalibrado(T_Code.Dir, modulo.Modulo_ID, false); + + Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Erro no referenciamento do módulo {modulo.Modulo_ID}: {ex.Message}"); + op.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {modulo.Modulo_ID}: {ex.Message}", id: modulo.Modulo_ID); + } + })); } + return tasks; } diff --git a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs index eaa5b53db..9842001cd 100644 --- a/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs +++ b/AgroBase/AgroBase/Models/Operacoes/OperacaoModel.cs @@ -148,6 +148,152 @@ namespace AgroBase.Models public List GPSTrajetoria { get; set; } = new List(); public OperacaoParametrosModel Parametros { get; set; } = new OperacaoParametrosModel(); public OperacaoSensoriamentoConjuntoModel Sensoriamento { get; set; } + + private readonly object _sensoriamentoStateLock = new object(); + internal void DefinirEstadoCalibragemRuntime(bool calibrando, bool pausa) + { + lock (_sensoriamentoStateLock) + { + if (Sensoriamento?.Operacao != null) + { + Sensoriamento.Operacao.Calibrando = calibrando; + Sensoriamento.Operacao.Pausa = pausa; + } + } + + RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", calibrando)); + } + internal void SubstituirSensoriamentoSnapshot(OperacaoSensoriamentoConjuntoModel dadosAtualizados) + { + if (dadosAtualizados == null) + return; + + lock (_sensoriamentoStateLock) + { + var runtime = Sensoriamento?.Operacao; + + if (runtime != null && dadosAtualizados.Operacao != null) + { + dadosAtualizados.Operacao.CopiarEstadoRuntimeDe(runtime); + } + + Sensoriamento = dadosAtualizados; + } + } + internal void MarcarModuloCalibrado(T_Code modulo, string id, bool ok) + { + lock (_sensoriamentoStateLock) + { + if (Sensoriamento?.Operacao?.ModsCalibragem == null) + { + Sensoriamento.Operacao.ModsCalibragem = + new Dictionary>(); + } + + if (!Sensoriamento.Operacao.ModsCalibragem.ContainsKey(modulo) || + Sensoriamento.Operacao.ModsCalibragem[modulo] == null) + { + Sensoriamento.Operacao.ModsCalibragem[modulo] = + new Dictionary(); + } + + Sensoriamento.Operacao.ModsCalibragem[modulo][id] = ok; + } + } + internal bool ModuloEstaCalibrado(T_Code modulo, string id) + { + lock (_sensoriamentoStateLock) + { + var mods = Sensoriamento?.Operacao?.ModsCalibragem; + + if (mods == null) + return false; + + if (!mods.TryGetValue(modulo, out var itens) || itens == null) + return false; + + return itens.TryGetValue(id, out bool ok) && ok; + } + } + + + private readonly object _eventosLock = new object(); + private readonly List _eventosPendentes = new List(); + private readonly Dictionary _dedupeEventosRecentes = new Dictionary(); + private static readonly TimeSpan JanelaDedupeEvento = TimeSpan.FromSeconds(2); + private static string CriarChaveEvento(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id, string condicoes) + { + return string.Join( + "|", + dispositivo, + id ?? "", + status, + Math.Round(saude, 3).ToString(CultureInfo.InvariantCulture), + mensagem ?? "", + condicoes ?? "" + ); + } + internal void InserirEventoOperacao(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null) + { + try + { + DateTime agora = DateTime.Now; + + string chave = CriarChaveEvento( + dispositivo, + status, + saude, + mensagem, + id, + condicoes + ); + + lock (_eventosLock) + { + var expirados = _dedupeEventosRecentes + .Where(x => agora - x.Value > JanelaDedupeEvento) + .Select(x => x.Key) + .ToList(); + + foreach (var key in expirados) + _dedupeEventosRecentes.Remove(key); + + if (_dedupeEventosRecentes.TryGetValue(chave, out DateTime ultimo) && agora - ultimo <= JanelaDedupeEvento) + { + return; + } + + _dedupeEventosRecentes[chave] = agora; + + _eventosPendentes.Add(new OperacaoSensoriamentoLogErrosModel() + { + Momento = agora, + Dispositivo = dispositivo, + ID = id, + Status = status, + Saude = saude, + Mensagem = mensagem, + Condicoes = condicoes + }); + } + } + catch (Exception ex) + { + Variaveis.MostrarLog($"[OperacaoModel.InserirEventoOperacao] Erro ao inserir evento: {ex.Message}"); + } + } + + + private readonly object _atualizarDadosLock = new object(); + internal void AtualizarSensoriamentoSeguro() + { + lock (_atualizarDadosLock) + { + Sensoriamento?.AtualizarDados(); + } + } + + public Form frmOperacao { get; set; } public bool Treinando { get; set; } = false; @@ -161,6 +307,58 @@ namespace AgroBase.Models private static string _discoverySessionId = Guid.NewGuid().ToString("N"); private static long _telemetrySequence = 0; private readonly SemaphoreSlim _calibragemGate = new SemaphoreSlim(1, 1); + public bool CalibrandoRuntime + { + get + { + lock (_sensoriamentoStateLock) + return Sensoriamento?.Operacao?.Calibrando == true; + } + } + private DateTime _ultimaTentativaCalibragem = DateTime.MinValue; + private readonly TimeSpan _cooldownCalibragem = TimeSpan.FromSeconds(30); + private bool PodeTentarCalibragemAutomatica() + { + return DateTime.Now - _ultimaTentativaCalibragem > _cooldownCalibragem; + } + private void RegistrarTentativaCalibragem() + { + _ultimaTentativaCalibragem = DateTime.Now; + } + internal List DrenarEventosPendentes() + { + lock (_eventosLock) + { + var eventos = _eventosPendentes + .Where(x => x != null) + .OrderBy(x => x.Momento) + .ToList(); + + _eventosPendentes.Clear(); + + return eventos; + } + } + internal List ListarEventosPendentes() + { + lock (_eventosLock) + { + return _eventosPendentes + .Where(x => x != null) + .OrderBy(x => x.Momento) + .ToList(); + } + } + internal void ReenfileirarEventosPendentes(List eventos) + { + if (eventos == null || eventos.Count == 0) + return; + + lock (_eventosLock) + { + _eventosPendentes.InsertRange(0, eventos.Where(x => x != null)); + } + } @@ -899,7 +1097,7 @@ namespace AgroBase.Models { op.DefinirComponentesEmUso(); RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false)); - op.Sensoriamento.AtualizarDados(); + op.AtualizarSensoriamentoSeguro(); } return op; @@ -1100,7 +1298,7 @@ namespace AgroBase.Models op.SalvarOperacao(); - op.Sensoriamento.AtualizarDados(); + op.AtualizarSensoriamentoSeguro(); dispMvd?.Dados?.ReiniciarLeituras(dispMvd?.Dados); dispAtu?.Dados?.ReiniciarLeituras(dispAtu?.Dados); @@ -1174,7 +1372,7 @@ namespace AgroBase.Models op.PararTimerLogsOperacao(); - op.SalvarLogs(true); + await op.SalvarLogsSeguroAsync(true); op.Sensoriamento.Operacao.Finalizando = false; op.Trajetoria?.AutonomiaCorredor?.Parar(); @@ -1233,7 +1431,7 @@ namespace AgroBase.Models op.ID = ""; - op.Sensoriamento.AtualizarDados(); + op.AtualizarSensoriamentoSeguro(); dispMvd?.Dados?.ReiniciarLeituras(dispMvd.Dados); dispAtu?.Dados?.ReiniciarLeituras(dispAtu.Dados); @@ -1481,16 +1679,13 @@ namespace AgroBase.Models entrouEmCalibragem = true; - op.Sensoriamento.Operacao.Calibrando = true; - op.Sensoriamento.Operacao.Pausa = true; - - RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", true)); + op.DefinirEstadoCalibragemRuntime(true, true); Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial"); op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Pausa para calibragem inicial"); - await Task.Delay(3000); + await Task.Delay(5000); if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this)) return; @@ -1554,10 +1749,7 @@ namespace AgroBase.Models if (entrouEmCalibragem) { - op.Sensoriamento.Operacao.Calibrando = false; - op.Sensoriamento.Operacao.Pausa = false; - - RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false)); + op.DefinirEstadoCalibragemRuntime(false, false); } _calibragemGate.Release(); @@ -1830,7 +2022,7 @@ namespace AgroBase.Models var op = this; - op.Sensoriamento?.AtualizarDados(); + op.AtualizarSensoriamentoSeguro(); //Variaveis.LoraService?.EnviarDadosParaBase(); if (op.Parametros?.Modo == ModoOperacao.Manual) @@ -2837,7 +3029,6 @@ namespace AgroBase.Models if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, op)) { Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Timer órfão parado. TimerHash={GetHashCode()}, OperacaoTimer={ID}, OperacaoAtual={Variaveis.OperacaoEmAndamento?.ID}"); - _tmrLogsOperacao?.Dispose(); return; } @@ -2845,25 +3036,39 @@ namespace AgroBase.Models if (op.Sensoriamento?.Operacao?.OperacaoIniciada != true) return; + bool salvou = await op.SalvarLogsSeguroAsync(false); + + if (!salvou) + return; + + if (op.idxLog % 10 == 0) + { + AudioAlertaService.AlertaSonoroPorStatus(op.Sensoriamento.Operacao.StatusOperacaoAtual); + } + + if (op.idxLog % 5 == 0 && !op.CalibrandoRuntime && op.PodeTentarCalibragemAutomatica()) + { + op.RegistrarTentativaCalibragem(); + _ = Task.Run(async () => await op.RealizarCalibragemInicialAsync(false)); + } + } + + private async Task SalvarLogsSeguroAsync(bool fim) + { if (!await _salvarLogsGate.WaitAsync(0)) { - Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Tick ignorado porque SalvarLogs ainda está em execução. Operacao={ID}, idxLog={idxLog}"); - return; + if (!fim) + { + Variaveis.MostrarLog($"[OperacaoModel.SalvarLogsSeguroAsync] Ignorado: já existe salvamento em execução. Operacao={ID}, idxLog={idxLog}"); + return false; + } + + await _salvarLogsGate.WaitAsync(); } try { - op.SalvarLogs(false); - - if (op.idxLog % 10 == 0) - { - AudioAlertaService.AlertaSonoroPorStatus(op.Sensoriamento.Operacao.StatusOperacaoAtual); - } - - if (op.idxLog % 5 == 0 && !op.Sensoriamento.Operacao.Calibrando) - { - _ = Task.Run(async () => await op.RealizarCalibragemInicialAsync(false)); - } + return SalvarLogs(fim); } finally { @@ -2871,23 +3076,23 @@ namespace AgroBase.Models } } - public void SalvarLogs(bool Fim) + public bool SalvarLogs(bool Fim) { var op = this; if (op == null) - return; + return false; bool salvouComSucesso = false; + bool eventosGravadosComSucesso = false; DateTime agora = DateTime.Now; - DateTime novoCursorEventos = DateTime.Now; List eventosSalvos = new List(); try { if (string.IsNullOrWhiteSpace(op.ID)) - return; + return false; // Garante pasta da operação string pastaOperacao = Path.Combine(Variaveis.CaminhoOperacoes, op.ID); @@ -2898,14 +3103,14 @@ namespace AgroBase.Models // // Se você já chama op.Sensoriamento.AtualizarDados() // imediatamente antes de SalvarLogs(), pode remover esse bloco. - op.Sensoriamento?.AtualizarDados(); + op.AtualizarSensoriamentoSeguro(); // AtualizarDados() substitui op.Sensoriamento por um novo snapshot, // então pegamos a referência novamente. var sen = op.Sensoriamento; if (sen == null) - return; + return false; // ----------------------------- // Logs principais @@ -2948,17 +3153,15 @@ namespace AgroBase.Models // Eventos discretos de saúde/erro // Melhor salvar um evento por linha. // ----------------------------- - eventosSalvos = - (sen.Logs ?? new List()) - .Where(x => x != null) - .OrderBy(x => x.Momento) - .ToList(); + eventosSalvos = op.DrenarEventosPendentes(); foreach (var evento in eventosSalvos) { SalvarLogLine("eventos", evento); } + eventosGravadosComSucesso = true; + // ----------------------------- // Câmeras // ----------------------------- @@ -2988,28 +3191,16 @@ namespace AgroBase.Models op.idxLog++; salvouComSucesso = true; - if (eventosSalvos.Any()) - { - novoCursorEventos = eventosSalvos.Max(x => x.Momento); - } - else - { - novoCursorEventos = DateTime.Now; - } + return true; } catch (Exception ex) { Variaveis.MostrarLog("[OperacaoModel.SalvarLogs] Erro ao salvar log: " + ex.Message); + + return false; } finally { - // Só avança o cursor de eventos se salvou de verdade. - // Assim, se der erro no disco/JSON, você não perde eventos. - if (salvouComSucesso && op?.Sensoriamento != null) - { - op.Sensoriamento.UltimoRegistroLog = novoCursorEventos; - } - try { op?.Controle?.TiposControle?.ForEach(x => x.Comandos?.Clear()); @@ -3018,6 +3209,11 @@ namespace AgroBase.Models { // Não deixa limpeza de comandos derrubar o log. } + + if (!eventosGravadosComSucesso && eventosSalvos.Any()) + { + op.ReenfileirarEventosPendentes(eventosSalvos); + } } } @@ -4686,48 +4882,16 @@ namespace AgroBase.Models public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue; - private static readonly TimeSpan JanelaDedupeEvento = TimeSpan.FromSeconds(2); public void InserirLog(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null) { - if (op == null) - return; - - try - { - if (Logs == null) - Logs = new List(); - - DateTime agora = DateTime.Now; - - bool duplicadoRecente = Logs.Any(x => - x != null && - (agora - x.Momento).Duration() <= JanelaDedupeEvento && - x.Dispositivo == dispositivo && - x.ID == id && - x.Status == status && - Math.Abs(x.Saude - saude) < 0.001 && - x.Mensagem == mensagem && - x.Condicoes == condicoes - ); - - if (duplicadoRecente) - return; - - Logs.Add(new OperacaoSensoriamentoLogErrosModel() - { - Momento = agora, - Dispositivo = dispositivo, - ID = id, - Status = status, - Saude = saude, - Mensagem = mensagem, - Condicoes = condicoes - }); - } - catch (Exception ex) - { - Variaveis.MostrarLog($"[OperacaoModel.InserirLog] Erro ao inserir log: {ex.Message}"); - } + op?.InserirEventoOperacao( + dispositivo, + status, + saude, + mensagem, + id, + condicoes + ); } @@ -4736,7 +4900,6 @@ namespace AgroBase.Models if (op == null) return; DateTime agora = DateTime.Now; - DateTime ultimoRegistroLog = UltimoRegistroLog; // Contexto HealthWorkerService.AtualizarDadosContexto(); @@ -4825,11 +4988,11 @@ namespace AgroBase.Models DadosPerformance = dadosPerformance, LivoxLidar = dadosLivox, CoolerControl = dadosCooler, - Logs = Logs != null ? Logs.Where(x => x != null && x.Momento > ultimoRegistroLog).ToList() : new List(), - UltimoRegistroLog = ultimoRegistroLog, + Logs = op.ListarEventosPendentes(), + UltimoRegistroLog = UltimoRegistroLog, }; - op.Sensoriamento = dadosAtualizados; + op.SubstituirSensoriamentoSnapshot(dadosAtualizados); } public OperacaoSensoriamentoConjuntoModel Clone() @@ -4896,7 +5059,23 @@ namespace AgroBase.Models public List DispositivosMapeados { get; set; } public Dictionary> ModsCalibragem { get; set; } public Dictionary> ModsCalibragemView { get; set; } - + + public void CopiarEstadoRuntimeDe(OperacaoSensoriamentoLogModel origem) + { + if (origem == null) + return; + + OperacaoIniciada = origem.OperacaoIniciada; + Emergencia = origem.Emergencia; + Pausa = origem.Pausa; + Calibrando = origem.Calibrando; + Finalizando = origem.Finalizando; + DataInicio = origem.DataInicio; + DataFim = origem.DataFim; + StatusOperacaoAnterior = origem.StatusOperacaoAnterior; + } + + public OperacaoSensoriamentoLogModel Clone() { return new OperacaoSensoriamentoLogModel()