ajustes ao salvar logs e calibragem

This commit is contained in:
Diego Freitas 2026-07-02 15:42:14 -03:00
parent b953ff524e
commit c4862a7ce4
3 changed files with 351 additions and 144 deletions

View File

@ -1940,23 +1940,25 @@ namespace AgroBase.Models.Modules
{ {
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(T_Code.Atu)) if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(Dispositivo))
op.Sensoriamento.Operacao.ModsCalibragem.Add(T_Code.Atu, new Dictionary<string, bool>()); op.Sensoriamento.Operacao.ModsCalibragem.Add(Dispositivo, new Dictionary<string, bool>());
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo]; var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
// Ligar bomba // Ligar bomba
foreach (var Bomba in BombasPressurizadoras.Where(x => x.Comandar)) foreach (var Bomba in BombasPressurizadoras.Where(x => x.Comandar))
{ {
calAtu.TryGetValue(Bomba.ID, out var cal); if (!forcar && op.ModuloEstaCalibrado(Dispositivo, Bomba.ID))
if (!forcar && cal && calAtu[Bomba.ID]) continue; continue;
calAtu[Bomba.ID] = false; op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, false);
// Sem herbicida no reservatorio, bomba nao precisa ligar // Sem herbicida no reservatorio, bomba nao precisa ligar
if (!Bomba.Inicializado || ContadorCarga.PercentualAtual < VariaveisEquipamento.PercentualReservatorioMin) 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.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.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; 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"); 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); 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; continue;
} }
@ -2006,7 +2008,7 @@ namespace AgroBase.Models.Modules
bool BombaOk = Bomba.AtingiuPressao(); bool BombaOk = Bomba.AtingiuPressao();
if (BombaOk) if (BombaOk)
{ {
calAtu[Bomba.ID] = true; op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, true);
} }
else else
{ {
@ -2033,7 +2035,7 @@ namespace AgroBase.Models.Modules
await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 4000, 200); await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 4000, 200);
BombaOk = Bomba.AtingiuPressao(); 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); calAtu.TryGetValue(Bico.ID, out var cal);
if (!forcar && cal && calAtu[Bico.ID]) continue; if (!forcar && cal && calAtu[Bico.ID]) continue;
calAtu[Bico.ID] = false; op.MarcarModuloCalibrado(Dispositivo, Bico.ID, false);
if (Bico.Inicializado) if (Bico.Inicializado)
{ {
@ -2063,13 +2065,21 @@ namespace AgroBase.Models.Modules
await Task.Delay(200); await Task.Delay(200);
calAtu[Bico.ID] = true; op.MarcarModuloCalibrado(Dispositivo, Bico.ID, true);
} }
} }
await Task.Delay(1000); 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; return sucesso;
} }

View File

@ -531,11 +531,20 @@ namespace AgroBase.Models.Modules
if (!mc.ContainsKey(T_Code.Mov)) if (!mc.ContainsKey(T_Code.Mov))
mc.Add(T_Code.Mov, new Dictionary<string, bool>()); mc.Add(T_Code.Mov, new Dictionary<string, bool>());
var calMov = mc[T_Code.Mov]; 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); calMov.TryGetValue(Modulo.Modulo_ID, out var cal);
if (!forcar && cal && calMov[mod.Modulo_ID]) continue; if (!forcar && cal && calMov[Modulo.Modulo_ID]) continue;
calMov[mod.Modulo_ID] = mod.MovMotor.Inicializado;
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); DispMvd.Dados.MovPronto = DispMvd.Dados.Modulos.All(x => x.MovMotor.Inicializado);
@ -548,45 +557,54 @@ namespace AgroBase.Models.Modules
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
List<Task> tasks = new List<Task>(); List<Task> tasks = new List<Task>();
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 (!forcar && op.ModuloEstaCalibrado(T_Code.Dir, modulo.Modulo_ID))
if (!mc.ContainsKey(T_Code.Dir)) continue;
mc.Add(T_Code.Dir, new Dictionary<string, bool>());
var calDir = mc[T_Code.Dir]; op.MarcarModuloCalibrado(T_Code.Dir, modulo.Modulo_ID, false);
foreach (var mod in DispMvd.Dados.Modulos)
if (!modulo.DirMotor.Inicializado)
{ {
calDir.TryGetValue(mod.Modulo_ID, out var cal); Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] DIR {modulo.Modulo_ID} não inicializado.");
if (!forcar && cal && calDir[mod.Modulo_ID]) continue; op.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Alerta, 50, $"DIR {modulo.Modulo_ID} não inicializado.", id: modulo.Modulo_ID);
calDir[mod.Modulo_ID] = false; 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); try
var ModsRef = Modulos.Where(x => x.DirMotor.Comandar).ToList(); // && x.DirMotor.Inicializado
foreach (var Modulo in ModsRef)
{ {
if (calDir[Modulo.Modulo_ID]) continue; await modulo.DirMotor.ReferenciaMotor();
tasks.Add(Modulo.DirMotor.ReferenciaMotor().ContinueWith(t => {
if (t.IsFaulted) op.MarcarModuloCalibrado(T_Code.Dir, modulo.Modulo_ID, true);
{
Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Erro no referenciamento do módulo {Modulo.Modulo_ID}: {t.Exception?.InnerException?.Message}"); Variaveis.MostrarLog($"[MovimentacaoUnificadaModel] [RealizarCalibragemDirAsync] Referenciamento do módulo {modulo.Modulo_ID} concluído com sucesso!");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {Modulo.Modulo_ID}: {t.Exception?.InnerException?.Message}"); op.Sensoriamento.InserirLog(T_Code.Dir, StatusModulo.Operante, 100, $"Referenciamento do módulo {modulo.Modulo_ID} concluído com sucesso!", id: modulo.Modulo_ID);
}
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;
}
}));
} }
} 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; return tasks;
} }

View File

@ -148,6 +148,152 @@ namespace AgroBase.Models
public List<GPSModel> GPSTrajetoria { get; set; } = new List<GPSModel>(); public List<GPSModel> GPSTrajetoria { get; set; } = new List<GPSModel>();
public OperacaoParametrosModel Parametros { get; set; } = new OperacaoParametrosModel(); public OperacaoParametrosModel Parametros { get; set; } = new OperacaoParametrosModel();
public OperacaoSensoriamentoConjuntoModel Sensoriamento { get; set; } 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<T_Code, Dictionary<string, bool>>();
}
if (!Sensoriamento.Operacao.ModsCalibragem.ContainsKey(modulo) ||
Sensoriamento.Operacao.ModsCalibragem[modulo] == null)
{
Sensoriamento.Operacao.ModsCalibragem[modulo] =
new Dictionary<string, bool>();
}
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<OperacaoSensoriamentoLogErrosModel> _eventosPendentes = new List<OperacaoSensoriamentoLogErrosModel>();
private readonly Dictionary<string, DateTime> _dedupeEventosRecentes = new Dictionary<string, DateTime>();
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 Form frmOperacao { get; set; }
public bool Treinando { get; set; } = false; public bool Treinando { get; set; } = false;
@ -161,6 +307,58 @@ namespace AgroBase.Models
private static string _discoverySessionId = Guid.NewGuid().ToString("N"); private static string _discoverySessionId = Guid.NewGuid().ToString("N");
private static long _telemetrySequence = 0; private static long _telemetrySequence = 0;
private readonly SemaphoreSlim _calibragemGate = new SemaphoreSlim(1, 1); 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<OperacaoSensoriamentoLogErrosModel> DrenarEventosPendentes()
{
lock (_eventosLock)
{
var eventos = _eventosPendentes
.Where(x => x != null)
.OrderBy(x => x.Momento)
.ToList();
_eventosPendentes.Clear();
return eventos;
}
}
internal List<OperacaoSensoriamentoLogErrosModel> ListarEventosPendentes()
{
lock (_eventosLock)
{
return _eventosPendentes
.Where(x => x != null)
.OrderBy(x => x.Momento)
.ToList();
}
}
internal void ReenfileirarEventosPendentes(List<OperacaoSensoriamentoLogErrosModel> 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(); op.DefinirComponentesEmUso();
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false)); RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("configurado", false));
op.Sensoriamento.AtualizarDados(); op.AtualizarSensoriamentoSeguro();
} }
return op; return op;
@ -1100,7 +1298,7 @@ namespace AgroBase.Models
op.SalvarOperacao(); op.SalvarOperacao();
op.Sensoriamento.AtualizarDados(); op.AtualizarSensoriamentoSeguro();
dispMvd?.Dados?.ReiniciarLeituras(dispMvd?.Dados); dispMvd?.Dados?.ReiniciarLeituras(dispMvd?.Dados);
dispAtu?.Dados?.ReiniciarLeituras(dispAtu?.Dados); dispAtu?.Dados?.ReiniciarLeituras(dispAtu?.Dados);
@ -1174,7 +1372,7 @@ namespace AgroBase.Models
op.PararTimerLogsOperacao(); op.PararTimerLogsOperacao();
op.SalvarLogs(true); await op.SalvarLogsSeguroAsync(true);
op.Sensoriamento.Operacao.Finalizando = false; op.Sensoriamento.Operacao.Finalizando = false;
op.Trajetoria?.AutonomiaCorredor?.Parar(); op.Trajetoria?.AutonomiaCorredor?.Parar();
@ -1233,7 +1431,7 @@ namespace AgroBase.Models
op.ID = ""; op.ID = "";
op.Sensoriamento.AtualizarDados(); op.AtualizarSensoriamentoSeguro();
dispMvd?.Dados?.ReiniciarLeituras(dispMvd.Dados); dispMvd?.Dados?.ReiniciarLeituras(dispMvd.Dados);
dispAtu?.Dados?.ReiniciarLeituras(dispAtu.Dados); dispAtu?.Dados?.ReiniciarLeituras(dispAtu.Dados);
@ -1481,16 +1679,13 @@ namespace AgroBase.Models
entrouEmCalibragem = true; entrouEmCalibragem = true;
op.Sensoriamento.Operacao.Calibrando = true; op.DefinirEstadoCalibragemRuntime(true, true);
op.Sensoriamento.Operacao.Pausa = true;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", true));
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial"); Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "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)) if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this))
return; return;
@ -1554,10 +1749,7 @@ namespace AgroBase.Models
if (entrouEmCalibragem) if (entrouEmCalibragem)
{ {
op.Sensoriamento.Operacao.Calibrando = false; op.DefinirEstadoCalibragemRuntime(false, false);
op.Sensoriamento.Operacao.Pausa = false;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
} }
_calibragemGate.Release(); _calibragemGate.Release();
@ -1830,7 +2022,7 @@ namespace AgroBase.Models
var op = this; var op = this;
op.Sensoriamento?.AtualizarDados(); op.AtualizarSensoriamentoSeguro();
//Variaveis.LoraService?.EnviarDadosParaBase(); //Variaveis.LoraService?.EnviarDadosParaBase();
if (op.Parametros?.Modo == ModoOperacao.Manual) if (op.Parametros?.Modo == ModoOperacao.Manual)
@ -2837,7 +3029,6 @@ namespace AgroBase.Models
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, op)) if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, op))
{ {
Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Timer órfão parado. TimerHash={GetHashCode()}, OperacaoTimer={ID}, OperacaoAtual={Variaveis.OperacaoEmAndamento?.ID}"); Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Timer órfão parado. TimerHash={GetHashCode()}, OperacaoTimer={ID}, OperacaoAtual={Variaveis.OperacaoEmAndamento?.ID}");
_tmrLogsOperacao?.Dispose(); _tmrLogsOperacao?.Dispose();
return; return;
} }
@ -2845,25 +3036,39 @@ namespace AgroBase.Models
if (op.Sensoriamento?.Operacao?.OperacaoIniciada != true) if (op.Sensoriamento?.Operacao?.OperacaoIniciada != true)
return; 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<bool> SalvarLogsSeguroAsync(bool fim)
{
if (!await _salvarLogsGate.WaitAsync(0)) if (!await _salvarLogsGate.WaitAsync(0))
{ {
Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Tick ignorado porque SalvarLogs ainda está em execução. Operacao={ID}, idxLog={idxLog}"); if (!fim)
return; {
Variaveis.MostrarLog($"[OperacaoModel.SalvarLogsSeguroAsync] Ignorado: já existe salvamento em execução. Operacao={ID}, idxLog={idxLog}");
return false;
}
await _salvarLogsGate.WaitAsync();
} }
try try
{ {
op.SalvarLogs(false); return SalvarLogs(fim);
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));
}
} }
finally finally
{ {
@ -2871,23 +3076,23 @@ namespace AgroBase.Models
} }
} }
public void SalvarLogs(bool Fim) public bool SalvarLogs(bool Fim)
{ {
var op = this; var op = this;
if (op == null) if (op == null)
return; return false;
bool salvouComSucesso = false; bool salvouComSucesso = false;
bool eventosGravadosComSucesso = false;
DateTime agora = DateTime.Now; DateTime agora = DateTime.Now;
DateTime novoCursorEventos = DateTime.Now;
List<OperacaoSensoriamentoLogErrosModel> eventosSalvos = new List<OperacaoSensoriamentoLogErrosModel>(); List<OperacaoSensoriamentoLogErrosModel> eventosSalvos = new List<OperacaoSensoriamentoLogErrosModel>();
try try
{ {
if (string.IsNullOrWhiteSpace(op.ID)) if (string.IsNullOrWhiteSpace(op.ID))
return; return false;
// Garante pasta da operação // Garante pasta da operação
string pastaOperacao = Path.Combine(Variaveis.CaminhoOperacoes, op.ID); string pastaOperacao = Path.Combine(Variaveis.CaminhoOperacoes, op.ID);
@ -2898,14 +3103,14 @@ namespace AgroBase.Models
// //
// Se você já chama op.Sensoriamento.AtualizarDados() // Se você já chama op.Sensoriamento.AtualizarDados()
// imediatamente antes de SalvarLogs(), pode remover esse bloco. // imediatamente antes de SalvarLogs(), pode remover esse bloco.
op.Sensoriamento?.AtualizarDados(); op.AtualizarSensoriamentoSeguro();
// AtualizarDados() substitui op.Sensoriamento por um novo snapshot, // AtualizarDados() substitui op.Sensoriamento por um novo snapshot,
// então pegamos a referência novamente. // então pegamos a referência novamente.
var sen = op.Sensoriamento; var sen = op.Sensoriamento;
if (sen == null) if (sen == null)
return; return false;
// ----------------------------- // -----------------------------
// Logs principais // Logs principais
@ -2948,17 +3153,15 @@ namespace AgroBase.Models
// Eventos discretos de saúde/erro // Eventos discretos de saúde/erro
// Melhor salvar um evento por linha. // Melhor salvar um evento por linha.
// ----------------------------- // -----------------------------
eventosSalvos = eventosSalvos = op.DrenarEventosPendentes();
(sen.Logs ?? new List<OperacaoSensoriamentoLogErrosModel>())
.Where(x => x != null)
.OrderBy(x => x.Momento)
.ToList();
foreach (var evento in eventosSalvos) foreach (var evento in eventosSalvos)
{ {
SalvarLogLine("eventos", evento); SalvarLogLine("eventos", evento);
} }
eventosGravadosComSucesso = true;
// ----------------------------- // -----------------------------
// Câmeras // Câmeras
// ----------------------------- // -----------------------------
@ -2988,28 +3191,16 @@ namespace AgroBase.Models
op.idxLog++; op.idxLog++;
salvouComSucesso = true; salvouComSucesso = true;
if (eventosSalvos.Any()) return true;
{
novoCursorEventos = eventosSalvos.Max(x => x.Momento);
}
else
{
novoCursorEventos = DateTime.Now;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Variaveis.MostrarLog("[OperacaoModel.SalvarLogs] Erro ao salvar log: " + ex.Message); Variaveis.MostrarLog("[OperacaoModel.SalvarLogs] Erro ao salvar log: " + ex.Message);
return false;
} }
finally 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 try
{ {
op?.Controle?.TiposControle?.ForEach(x => x.Comandos?.Clear()); op?.Controle?.TiposControle?.ForEach(x => x.Comandos?.Clear());
@ -3018,6 +3209,11 @@ namespace AgroBase.Models
{ {
// Não deixa limpeza de comandos derrubar o log. // 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; 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) public void InserirLog(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null)
{ {
if (op == null) op?.InserirEventoOperacao(
return; dispositivo,
status,
try saude,
{ mensagem,
if (Logs == null) id,
Logs = new List<OperacaoSensoriamentoLogErrosModel>(); condicoes
);
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}");
}
} }
@ -4736,7 +4900,6 @@ namespace AgroBase.Models
if (op == null) return; if (op == null) return;
DateTime agora = DateTime.Now; DateTime agora = DateTime.Now;
DateTime ultimoRegistroLog = UltimoRegistroLog;
// Contexto // Contexto
HealthWorkerService.AtualizarDadosContexto(); HealthWorkerService.AtualizarDadosContexto();
@ -4825,11 +4988,11 @@ namespace AgroBase.Models
DadosPerformance = dadosPerformance, DadosPerformance = dadosPerformance,
LivoxLidar = dadosLivox, LivoxLidar = dadosLivox,
CoolerControl = dadosCooler, CoolerControl = dadosCooler,
Logs = Logs != null ? Logs.Where(x => x != null && x.Momento > ultimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(), Logs = op.ListarEventosPendentes(),
UltimoRegistroLog = ultimoRegistroLog, UltimoRegistroLog = UltimoRegistroLog,
}; };
op.Sensoriamento = dadosAtualizados; op.SubstituirSensoriamentoSnapshot(dadosAtualizados);
} }
public OperacaoSensoriamentoConjuntoModel Clone() public OperacaoSensoriamentoConjuntoModel Clone()
@ -4896,7 +5059,23 @@ namespace AgroBase.Models
public List<DispositivoDetalhesModel> DispositivosMapeados { get; set; } public List<DispositivoDetalhesModel> DispositivosMapeados { get; set; }
public Dictionary<T_Code, Dictionary<string, bool>> ModsCalibragem { get; set; } public Dictionary<T_Code, Dictionary<string, bool>> ModsCalibragem { get; set; }
public Dictionary<string, Dictionary<string, bool>> ModsCalibragemView { get; set; } public Dictionary<string, Dictionary<string, bool>> 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() public OperacaoSensoriamentoLogModel Clone()
{ {
return new OperacaoSensoriamentoLogModel() return new OperacaoSensoriamentoLogModel()