ajustes calibragem inicial e inicialização automática dos workers python

This commit is contained in:
Diego Freitas 2026-06-30 15:59:15 -03:00
parent 2c3901c355
commit c5e5785718
11 changed files with 1139 additions and 397 deletions

View File

@ -1,6 +1,7 @@
using AgroBase.Forms.IHM; using AgroBase.Forms.IHM;
using AgroBase.Models; using AgroBase.Models;
using AgroBase.Services; using AgroBase.Services;
using AgroBase.Services.Operadores;
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -10,6 +11,8 @@ namespace AgroBase.Forms
{ {
public partial class frmInstancial : Form public partial class frmInstancial : Form
{ {
public static readonly bool IniciarWorkers = true;
public static AsyncTaskTimerModel tmrVarreduraDispositivos; public static AsyncTaskTimerModel tmrVarreduraDispositivos;
public static frmPrincipal frmPrincipal = new frmPrincipal(); public static frmPrincipal frmPrincipal = new frmPrincipal();
@ -21,11 +24,9 @@ namespace AgroBase.Forms
//TopMost = true //TopMost = true
}; };
private static readonly SemaphoreSlim _startupGate = private static readonly SemaphoreSlim _startupGate = new SemaphoreSlim(1, 1);
new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim _shutdownGate = private static readonly SemaphoreSlim _shutdownGate = new SemaphoreSlim(1, 1);
new SemaphoreSlim(1, 1);
private static CancellationTokenSource _appCts; private static CancellationTokenSource _appCts;
@ -44,10 +45,7 @@ namespace AgroBase.Forms
* O WinForms ainda está criando handles e a aplicação ainda não * O WinForms ainda está criando handles e a aplicação ainda não
* possui lifecycle seguro para aguardar falhas. * possui lifecycle seguro para aguardar falhas.
*/ */
ThreadPool.SetMinThreads( ThreadPool.SetMinThreads(workerThreads: 50, completionPortThreads: 50);
workerThreads: 50,
completionPortThreads: 50
);
} }
private async void frmInstancial_Load(object sender, EventArgs e) private async void frmInstancial_Load(object sender, EventArgs e)
@ -58,20 +56,11 @@ namespace AgroBase.Forms
} }
catch (Exception ex) catch (Exception ex)
{ {
Variaveis.MostrarLog( Variaveis.MostrarLog("[frmInstancial.Load] Falha crítica na inicialização: " + ex);
"[frmInstancial.Load] Falha crítica na inicialização: " +
ex
);
try try
{ {
MessageBox.Show( MessageBox.Show("Falha ao inicializar o sistema:\n\n" + ex.Message, "AgroBase", MessageBoxButtons.OK, MessageBoxIcon.Error);
"Falha ao inicializar o sistema:\n\n" +
ex.Message,
"AgroBase",
MessageBoxButtons.OK,
MessageBoxIcon.Error
);
} }
catch { } catch { }
} }
@ -103,16 +92,13 @@ namespace AgroBase.Forms
} }
catch (Exception ex) catch (Exception ex)
{ {
Variaveis.MostrarLog( Variaveis.MostrarLog("[frmInstancial.Shown] " + ex);
"[frmInstancial.Shown] " + ex
);
} }
} }
private static async Task InicializarServicosAsync() private static async Task InicializarServicosAsync()
{ {
await _startupGate.WaitAsync() await _startupGate.WaitAsync().ConfigureAwait(false);
.ConfigureAwait(false);
try try
{ {
@ -143,8 +129,7 @@ namespace AgroBase.Forms
GeneralJoystick.IniciarRotinas(); GeneralJoystick.IniciarRotinas();
await Variaveis.IniciarMqttAsync(_appCts.Token) await Variaveis.IniciarMqttAsync(_appCts.Token).ConfigureAwait(false);
.ConfigureAwait(false);
/* /*
* Hoje pode ficar comentado se UDP não for usado no rover, * Hoje pode ficar comentado se UDP não for usado no rover,
@ -157,28 +142,19 @@ namespace AgroBase.Forms
LivoxManagerProcess.Start(); LivoxManagerProcess.Start();
await VariaveisOperacao await VariaveisOperacao.Operadores.IniciarProcessamento(IniciarWorkers).ConfigureAwait(false);
.Operadores
.IniciarProcessamento(false)
.ConfigureAwait(false);
AudioAlertaService.SelecionarVoz( AudioAlertaService.SelecionarVoz(VariaveisEquipamento.VozAlerta);
VariaveisEquipamento.VozAlerta
);
IniciarTimerVarreduraDispositivos(); IniciarTimerVarreduraDispositivos();
_startupConcluido = true; _startupConcluido = true;
Variaveis.MostrarLog( Variaveis.MostrarLog("[frmInstancial] Inicialização concluída.");
"[frmInstancial] Inicialização concluída."
);
} }
catch catch
{ {
await EncerrarProcessosInternoAsync( await EncerrarProcessosInternoAsync(sairProcesso: false).ConfigureAwait(false);
sairProcesso: false
).ConfigureAwait(false);
throw; throw;
} }
@ -210,29 +186,7 @@ namespace AgroBase.Forms
if (Variaveis.Fechando) if (Variaveis.Fechando)
return; return;
await SerialService await SerialService.RealizarVarreduraPortasUSB().ConfigureAwait(false);
.RealizarVarreduraPortasUSB()
.ConfigureAwait(false);
}
/*
* Método mantido para compatibilidade com chamadas antigas.
* Não é mais chamado no Load, porque o startup correto é
* Variaveis.IniciarMqttAsync().
*/
private async void IniciarConexaoMqtt()
{
try
{
await Variaveis.IniciarMqttAsync();
}
catch (Exception ex)
{
Variaveis.MostrarLog(
"[frmInstancial.IniciarConexaoMqtt] " +
ex.Message
);
}
} }
public static Task EncerrarProcessos() public static Task EncerrarProcessos()
@ -376,6 +330,15 @@ namespace AgroBase.Forms
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar MQTT: " + ex.Message); Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar MQTT: " + ex.Message);
} }
try
{
VariaveisOperacao.Operadores.Encerrar();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar OperadoresService: " + ex.Message);
}
try try
{ {
_appCts?.Dispose(); _appCts?.Dispose();

View File

@ -1171,7 +1171,7 @@ namespace AgroBase.Models.Modules
BicosPulverizadores = new List<AtuadorBicoModel>(), BicosPulverizadores = new List<AtuadorBicoModel>(),
}; };
for (int i = 0; i < Variaveis.OperacaoEmAndamento.Parametros.QtdBicos; i++) for (int i = 0; i < VariaveisEquipamento.QuantidadeBicosPulverizadores; i++)
{ {
int posicao = (i + 1); int posicao = (i + 1);
Atuador.BicosPulverizadores.Add(new AtuadorBicoModel() Atuador.BicosPulverizadores.Add(new AtuadorBicoModel()
@ -1311,9 +1311,9 @@ namespace AgroBase.Models.Modules
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
var dadosAtuador = Dados; var dadosAtuador = Dados;
var controle = op.Parametros?.Controle; var controle = op?.Parametros?.Controle;
if (dadosAtuador == null || controle == null) if (op == null || dadosAtuador == null || controle == null)
return; return;
var bomba = dadosAtuador.BombaPressurizadora; var bomba = dadosAtuador.BombaPressurizadora;
@ -1420,7 +1420,7 @@ namespace AgroBase.Models.Modules
if (!CanManager.CanService.IsConnected) return false; if (!CanManager.CanService.IsConnected) return false;
if (op.DispAtu == null) return false; if (op?.DispAtu == null) return false;
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler); CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
@ -1997,25 +1997,42 @@ 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 DispAtu = op.DispAtu;
if (DispAtu != null)
{
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}");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}");
}
if (t.IsCompleted)
{
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
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));
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v));
}
})); if (op?.DispAtu?.Dados == null)
} return tasks;
tasks.Add(Task.Run(async () =>
{
try
{
bool sucesso = await op.DispAtu.Dados.RealizarTestesIniciais(forcar);
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não"));
if (sucesso)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
}
else
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
}
}
catch (Exception ex)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Erro no teste do módulo {Modulo_ID}: {ex.Message}");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no teste do módulo {Modulo_ID}: {ex.Message}");
throw;
}
}));
return tasks; return tasks;
} }
@ -2685,7 +2702,7 @@ namespace AgroBase.Models.Modules
valor.atual.valor = v; valor.atual.valor = v;
} }
if (Posicao == CanMessagePosicaoDados.Status) if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
{ {
if (sensor != null) if (sensor != null)
sensor.Inicializado = v; sensor.Inicializado = v;
@ -2861,7 +2878,7 @@ namespace AgroBase.Models.Modules
malhaFechada, malhaFechada,
} }
); );
Variaveis.MostrarLog($"BOMBA {ID_Num}, INICIADO: {iniciado}, MALHA_FECHADA: {malhaFechada}"); var bomba = Dados.BombasPressurizadoras?.FirstOrDefault(x => x.ID_Num == ID_Num);
break; break;
} }
case CanMessagePosicaoDados.Dados1: case CanMessagePosicaoDados.Dados1:

View File

@ -2810,7 +2810,7 @@ namespace AgroBase.Models.Modules
valor.atual.valor = v; valor.atual.valor = v;
} }
if (Posicao == CanMessagePosicaoDados.Status) if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
{ {
if (sensor != null) if (sensor != null)
sensor.Inicializado = v; sensor.Inicializado = v;

View File

@ -94,6 +94,7 @@ 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);
@ -1080,7 +1081,9 @@ namespace AgroBase.Models
op.Sensoriamento.Operacao.OperacaoIniciada = false; op.Sensoriamento.Operacao.OperacaoIniciada = false;
op.Sensoriamento.Operacao.Emergencia = false; op.Sensoriamento.Operacao.Emergencia = false;
op.Sensoriamento.Operacao.Calibrando = false;
op.Sensoriamento.Operacao.Pausa = false; op.Sensoriamento.Operacao.Pausa = false;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
op.Sensoriamento.Operacao.DataFim = DateTime.Now; op.Sensoriamento.Operacao.DataFim = DateTime.Now;
@ -1117,106 +1120,6 @@ namespace AgroBase.Models
op.EnviarParametorsOperacao(); op.EnviarParametorsOperacao();
} }
public async Task RealizarCalibragemInicialAsync(bool forcar)
{
var op = this;
if (op.Sensoriamento.Operacao.Calibrando) return;
var modsCalibragem = op.Sensoriamento.Operacao.ModsCalibragem;
bool modsCalibrados =
modsCalibragem != null &&
modsCalibragem.Any() &&
modsCalibragem.All(x =>
x.Value != null &&
x.Value.Values.Count > 0 &&
x.Value.Values.All(y => y == true)
);
if (!forcar && modsCalibrados)
{
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Todos os módulos já foram calibrados anteriormente, não irá calibrar novamente.");
return;
}
var DispAtu = op.DispAtu;
var DispMvd = op.DispMvd;
List<StatusModulo> sv = new List<StatusModulo>() { StatusModulo.Operante, StatusModulo.Alerta };
var s = op.Sensoriamento.OperadorSaude.ModulosSaude;
var pControle = op.Parametros.Controle;
var _Controle = op.Controle;
bool calibragemAtuNecessaria = pControle.PulverizadorAutomatico && (DispAtu?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Atu)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Atu].Any(x => !x.Value) || !modsCalibragem[T_Code.Atu].Any()));
bool calibragemDirNecessaria = (DispMvd?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Dir)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Dir].Any(x => !x.Value) || !modsCalibragem[T_Code.Dir].Any()));
bool calibragemMovNecessaria = (DispMvd?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Mov)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Mov].Any(x => !x.Value) || !modsCalibragem[T_Code.Mov].Any()));
if (!calibragemAtuNecessaria && !calibragemDirNecessaria && !calibragemMovNecessaria)
{
if (forcar)
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem não necessária");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem não necessária");
}
return;
}
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Pausa para calibragem inicial");
op.Sensoriamento.Operacao.Pausa = true;
await Task.Delay(5000);
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem dos modulos inciada...");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem dos modulos inciada...");
op.Sensoriamento.Operacao.Calibrando = true;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", op.Sensoriamento.Operacao.Calibrando));
TipoMovimentoDirecional tipoControle = _Controle.TipoMovimento;
var tasks = new List<Task>();
if (calibragemAtuNecessaria) tasks.AddRange(DispAtu.Dados.RealizarCalibragemAsync(forcar));
if (calibragemDirNecessaria) tasks.AddRange(DispMvd.Dados.RealizarCalibragemDirAsync(forcar));
if (calibragemMovNecessaria) tasks.AddRange(DispMvd.Dados.RealizarCalibragemMovAsync(forcar));
if (tasks.Count == 0)
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Nenhuma calibragem foi iniciada.");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, $"Nenhuma calibragem foi iniciada.");
_Controle.TipoMovimento = tipoControle;
op.Sensoriamento.Operacao.Calibrando = false;
op.Sensoriamento.Operacao.Pausa = false;
return;
}
try
{
var allTasks = Task.WhenAll(tasks);
var completed = await Task.WhenAny(allTasks, Task.Delay(2 * 60 * 1000));
if (completed == allTasks)
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem finalizada com sucesso!");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem finalizada com sucesso!");
}
else
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Timeout na calibragem. Continuando mesmo assim.");
op.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}");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Falha, 0, $"Erro durante calibragem: {ex.Message}");
}
_Controle.TipoMovimento = tipoControle;
op.Sensoriamento.Operacao.Calibrando = false;
op.Sensoriamento.Operacao.Pausa = false;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", op.Sensoriamento.Operacao.Calibrando));
}
public async Task IniciarSimulacao() public async Task IniciarSimulacao()
{ {
var op = this; var op = this;
@ -1265,7 +1168,6 @@ namespace AgroBase.Models
}); });
} }
public void IniciarTreinamento() public void IniciarTreinamento()
{ {
var op = this; var op = this;
@ -1362,6 +1264,220 @@ namespace AgroBase.Models
} }
#endregion
#region CALIBRAGEM
public async Task RealizarCalibragemInicialAsync(bool forcar)
{
var op = this;
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this))
return;
if (!await _calibragemGate.WaitAsync(0))
return;
TipoMovimentoDirecional tipoControle = op.Controle.TipoMovimento;
bool entrouEmCalibragem = false;
try
{
GarantirEstruturaModsCalibragem(op);
var modsCalibragem = op.Sensoriamento.Operacao.ModsCalibragem;
bool modsCalibrados =
modsCalibragem.Any() &&
modsCalibragem.All(x =>
x.Value != null &&
x.Value.Values.Count > 0 &&
x.Value.Values.All(y => y)
);
if (!forcar && modsCalibrados)
{
//op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Todos os módulos já foram calibrados anteriormente, não irá calibrar novamente.");
return;
}
var dispAtu = op.DispAtu;
var dispMvd = op.DispMvd;
var statusValidos = new List<StatusModulo>()
{
StatusModulo.Operante,
StatusModulo.Alerta
};
var saude = op.Sensoriamento?.OperadorSaude?.ModulosSaude ?? new List<ManagerWorkerMessageResponseModulosPendentesModel>();
var pControle = op.Parametros?.Controle;
if (pControle == null)
return;
bool atuSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Atu)?.status ?? StatusModulo.Desconectado);
bool dirSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Dir)?.status ?? StatusModulo.Desconectado);
bool movSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Mov)?.status ?? StatusModulo.Desconectado);
bool reservatorioOk = (op.Sensoriamento?.Atuador?.PercentualReservatorio ?? 0) > VariaveisEquipamento.PercentualReservatorioMinCritio;
bool bombaOk = dispAtu?.Dados?.BombaPressurizadora?.Inicializado ?? false;
bool calibragemAtuNecessaria =
pControle.PulverizadorAutomatico &&
bombaOk &&
reservatorioOk &&
dispAtu?.Dados != null &&
atuSaudavel &&
DeveCalibrar(modsCalibragem, T_Code.Atu, forcar);
bool calibragemDirNecessaria =
dispMvd?.Dados != null &&
dirSaudavel &&
DeveCalibrar(modsCalibragem, T_Code.Dir, forcar);
bool calibragemMovNecessaria =
dispMvd?.Dados != null &&
movSaudavel &&
DeveCalibrar(modsCalibragem, T_Code.Mov, forcar);
if (!calibragemAtuNecessaria && !calibragemDirNecessaria && !calibragemMovNecessaria)
{
if (forcar)
{
string motivo =
$"Calibragem não necessária. " +
$"AtuNec={calibragemAtuNecessaria}, DirNec={calibragemDirNecessaria}, MovNec={calibragemMovNecessaria}, " +
$"PulvAuto={pControle.PulverizadorAutomatico}, BombaOk={bombaOk}, ReservatorioOk={reservatorioOk}, " +
$"AtuSaude={atuSaudavel}, DirSaude={dirSaudavel}, MovSaude={movSaudavel}";
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] " + motivo);
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, motivo);
}
return;
}
entrouEmCalibragem = true;
op.Sensoriamento.Operacao.Calibrando = true;
op.Sensoriamento.Operacao.Pausa = true;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", 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);
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this))
return;
if (!op.Sensoriamento.Operacao.OperacaoIniciada)
return;
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem dos módulos iniciada...");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Calibragem dos módulos iniciada...");
var tasks = new List<Task>();
if (calibragemAtuNecessaria)
tasks.AddRange(dispAtu.Dados.RealizarCalibragemAsync(forcar));
if (calibragemDirNecessaria)
tasks.AddRange(dispMvd.Dados.RealizarCalibragemDirAsync(forcar));
if (calibragemMovNecessaria)
tasks.AddRange(dispMvd.Dados.RealizarCalibragemMovAsync(forcar));
if (tasks.Count == 0)
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Nenhuma calibragem foi iniciada.");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, "Nenhuma calibragem foi iniciada.");
return;
}
var allTasks = Task.WhenAll(tasks);
var timeoutTask = Task.Delay(2 * 60 * 1000);
var completed = await Task.WhenAny(allTasks, timeoutTask);
if (completed == allTasks)
{
await allTasks;
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem finalizada.");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Calibragem finalizada.");
}
else
{
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Timeout na calibragem. Liberando operação mesmo assim.");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, "Timeout na calibragem. Liberando operação mesmo assim.");
}
}
catch (Exception ex)
{
Variaveis.MostrarLog($"[OperacaoModel] [RealizarCalibragemInicialAsync] Erro durante calibragem: {ex}");
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Falha, 0, $"Erro durante calibragem: {ex.Message}");
}
finally
{
op.Controle.TipoMovimento = tipoControle;
if (entrouEmCalibragem)
{
op.Sensoriamento.Operacao.Calibrando = false;
op.Sensoriamento.Operacao.Pausa = false;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
}
_calibragemGate.Release();
}
}
private static void GarantirEstruturaModsCalibragem(OperacaoModel op)
{
if (op.Sensoriamento.Operacao.ModsCalibragem == null)
op.Sensoriamento.Operacao.ModsCalibragem = new Dictionary<T_Code, Dictionary<string, bool>>();
var mods = op.Sensoriamento.Operacao.ModsCalibragem;
if (!mods.ContainsKey(T_Code.Mov) || mods[T_Code.Mov] == null)
mods[T_Code.Mov] = new Dictionary<string, bool>();
if (!mods.ContainsKey(T_Code.Dir) || mods[T_Code.Dir] == null)
mods[T_Code.Dir] = new Dictionary<string, bool>();
if (!mods.ContainsKey(T_Code.Atu) || mods[T_Code.Atu] == null)
mods[T_Code.Atu] = new Dictionary<string, bool>();
}
private static bool DeveCalibrar(Dictionary<T_Code, Dictionary<string, bool>> modsCalibragem, T_Code modulo, bool forcar)
{
if (!modsCalibragem.ContainsKey(modulo) || modsCalibragem[modulo] == null)
return true;
var mods = modsCalibragem[modulo];
if (!mods.Any())
return true;
if (forcar)
return true;
return mods.Any(x => !x.Value);
}
#endregion #endregion
#region CONTROLE DA OPERACAO #region CONTROLE DA OPERACAO
@ -2538,6 +2654,9 @@ namespace AgroBase.Models
bool salvouComSucesso = false; bool salvouComSucesso = false;
DateTime agora = DateTime.Now; DateTime agora = DateTime.Now;
DateTime novoCursorEventos = DateTime.Now;
List<OperacaoSensoriamentoLogErrosModel> eventosSalvos = new List<OperacaoSensoriamentoLogErrosModel>();
try try
{ {
if (string.IsNullOrWhiteSpace(op.ID)) if (string.IsNullOrWhiteSpace(op.ID))
@ -2602,12 +2721,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.
// ----------------------------- // -----------------------------
if (sen.Logs != null && sen.Logs.Any()) eventosSalvos =
(sen.Logs ?? new List<OperacaoSensoriamentoLogErrosModel>())
.Where(x => x != null)
.OrderBy(x => x.Momento)
.ToList();
foreach (var evento in eventosSalvos)
{ {
foreach (var evento in sen.Logs.Where(x => x != null)) SalvarLogLine("eventos", evento);
{
SalvarLogLine("eventos", evento);
}
} }
// ----------------------------- // -----------------------------
@ -2638,6 +2760,15 @@ namespace AgroBase.Models
op.idxLog++; op.idxLog++;
salvouComSucesso = true; salvouComSucesso = true;
if (eventosSalvos.Any())
{
novoCursorEventos = eventosSalvos.Max(x => x.Momento);
}
else
{
novoCursorEventos = DateTime.Now;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@ -2649,7 +2780,7 @@ namespace AgroBase.Models
// Assim, se der erro no disco/JSON, você não perde eventos. // Assim, se der erro no disco/JSON, você não perde eventos.
if (salvouComSucesso && op?.Sensoriamento != null) if (salvouComSucesso && op?.Sensoriamento != null)
{ {
op.Sensoriamento.UltimoRegistroLog = agora; op.Sensoriamento.UltimoRegistroLog = novoCursorEventos;
} }
try try
@ -3323,7 +3454,7 @@ namespace AgroBase.Models
} }
if (mod.saude_individual.Any()) if (mod.saude_individual.Any())
{ {
foreach (var ind in mod.saude_individual) foreach (var ind in mod.saude_individual.Where(x => x.em_uso))
{ {
var ind_ant = saudeAnterior.FirstOrDefault(x => x.modulo == mod.modulo && x.saude_individual.Any(y => y.id == ind.id))?.saude_individual?.FirstOrDefault(x => x.id == ind.id); var ind_ant = saudeAnterior.FirstOrDefault(x => x.modulo == mod.modulo && x.saude_individual.Any(y => y.id == ind.id))?.saude_individual?.FirstOrDefault(x => x.id == ind.id);
if (ind.status != ind_ant?.status) if (ind.status != ind_ant?.status)
@ -4376,18 +4507,37 @@ 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) return; if (op == null)
return;
try try
{ {
if (Logs == null) if (Logs == null)
Logs = new List<OperacaoSensoriamentoLogErrosModel>(); Logs = new List<OperacaoSensoriamentoLogErrosModel>();
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() Logs.Add(new OperacaoSensoriamentoLogErrosModel()
{ {
Momento = DateTime.Now, Momento = agora,
Dispositivo = dispositivo, Dispositivo = dispositivo,
ID = id, ID = id,
Status = status, Status = status,
@ -4402,11 +4552,13 @@ namespace AgroBase.Models
} }
} }
public void AtualizarDados() public void AtualizarDados()
{ {
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();
@ -4495,7 +4647,8 @@ namespace AgroBase.Models
DadosPerformance = dadosPerformance, DadosPerformance = dadosPerformance,
LivoxLidar = dadosLivox, LivoxLidar = dadosLivox,
CoolerControl = dadosCooler, CoolerControl = dadosCooler,
Logs = Logs != null ? Logs.Where(x => x?.Momento >= UltimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(), Logs = Logs != null ? Logs.Where(x => x?.Momento >= ultimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(),
UltimoRegistroLog = ultimoRegistroLog,
}; };
op.Sensoriamento = dadosAtualizados; op.Sensoriamento = dadosAtualizados;

View File

@ -601,7 +601,7 @@ namespace AgroBase.Services
{ {
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
if (op.Sensoriamento.Operacao.Calibrando == true) if (op?.Sensoriamento?.Operacao?.Calibrando == true)
{ {
/* /*
* Durante a calibração, RealizarTestesIniciais() * Durante a calibração, RealizarTestesIniciais()

View File

@ -34,7 +34,7 @@ namespace AgroBase.Services.Operadores
DadosLeitura.Iniciado = true; DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto; bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000); await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
if (pronto()) if (pronto())
{ {
MostrarLog("Processamento iniciado com sucesso"); MostrarLog("Processamento iniciado com sucesso");

View File

@ -35,7 +35,7 @@ namespace AgroBase.Services.Operadores
DadosLeitura.Iniciado = true; DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto; bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000); await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
if (pronto()) if (pronto())
{ {
MostrarLog("Processamento iniciado com sucesso"); MostrarLog("Processamento iniciado com sucesso");

View File

@ -1,8 +1,10 @@
using AgroBase.Models; using AgroBase.Models;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using static AgroBase.Models.Enums; using static AgroBase.Models.Enums;
using static AgroBase.Models.Operadores.OperadoresModels; using static AgroBase.Models.Operadores.OperadoresModels;
@ -11,144 +13,470 @@ namespace AgroBase.Services.Operadores
{ {
public class OperadoresService public class OperadoresService
{ {
private readonly object _estadoLock = new object();
private readonly SemaphoreSlim _restartGate = new SemaphoreSlim(1, 1);
private Process pythonProcess; private Process pythonProcess;
public List<IWorkerModel> Workers = new List<IWorkerModel>() public List<IWorkerModel> Workers = new List<IWorkerModel>()
{ {
new ManagerWorkerService(), new ManagerWorkerService(),
new VisualWorkerService(), new VisualWorkerService(),
new HealthWorkerService(), new HealthWorkerService(),
new CameraWorkerService(), new CameraWorkerService(),
new WeedWorkerService(), new WeedWorkerService(),
}; };
private bool iniciarScript = false; private bool iniciarScript = true;
private bool workersInicializados = false;
private bool encerrando = false;
private int TimeoutDesconexao = 3; private int TimeoutDesconexao = 3;
private double TempoDesconectado = 0; private int TempoDesconectado = 0;
private DateTime ultimoRestart = DateTime.MinValue;
private DateTime ultimoLogDesconectado = DateTime.MinValue;
private readonly TimeSpan IntervaloMinimoRestart = TimeSpan.FromSeconds(10);
private readonly TimeSpan IntervaloLogDesconectado = TimeSpan.FromSeconds(5);
public readonly int TimeoutIniciarWorkerMs = 10000;
private AsyncTaskTimerModel tmrHearthbeat; private AsyncTaskTimerModel tmrHearthbeat;
public bool Conectado = false; public bool Conectado = false;
public bool TodosWorkersConectados = false;
public bool PythonRodando = false;
private bool encerrandoPythonIntencionalmente = false;
public async Task<bool> IniciarProcessamento(bool _iniciarScript = true) public async Task<bool> IniciarProcessamento(bool _iniciarScript = true)
{ {
iniciarScript = _iniciarScript; iniciarScript = _iniciarScript;
encerrando = false;
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Iniciando núcleo central de processamento...");
bool iniciou = await IniciarOperadoresAsync("inicio", inicializacaoCompleta: true);
tmrHearthbeat?.Dispose(); tmrHearthbeat?.Dispose();
tmrHearthbeat = new AsyncTaskTimerModel("tmrHearthbeatOperadores", tmrHearthbeat_Tick, 1000); tmrHearthbeat = new AsyncTaskTimerModel("tmrHearthbeatOperadores", tmrHearthbeat_Tick, 1000);
tmrHearthbeat.Start(); tmrHearthbeat.Start();
IniciarOperadores(); if (iniciou)
return true;
}
private void IniciarOperadores()
{
foreach (var worker in Workers)
{ {
worker.IniciarProcessamento(); Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Núcleo central solicitado com sucesso.");
}
else
{
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Núcleo central iniciou com pendências. Heartbeat tentará recuperar.");
} }
if (iniciarScript) return iniciou;
}
private async Task<bool> IniciarOperadoresAsync(string origem, bool inicializacaoCompleta)
{
bool entrou = false;
try
{ {
Encerrar(); entrou = await _restartGate.WaitAsync(0);
pythonProcess = PythonService.RunScript(Path.Combine("workers", "main_async.py"), new string[] { });
if (!entrou)
{
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Restart ignorado, já existe uma inicialização em andamento.");
return false;
}
ultimoRestart = DateTime.Now;
Variaveis.MostrarLog($"[OperadoresService.IniciarOperadoresAsync] Iniciando operadores. Origem={origem}, iniciarScript={iniciarScript}");
List<Task<bool>> tasksWorkers = new List<Task<bool>>();
if (!workersInicializados || inicializacaoCompleta)
{
foreach (var worker in Workers)
{
tasksWorkers.Add(IniciarWorkerSeguroAsync(worker));
}
}
if (iniciarScript)
{
EncerrarPythonAtual("restart do núcleo");
pythonProcess = PythonService.RunScript(
Path.Combine("workers", "main_async.py"),
new string[] { },
"workers_main_async"
);
if (pythonProcess == null)
{
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Falha ao iniciar processo Python.");
}
else
{
Variaveis.MostrarLog($"[OperadoresService.IniciarOperadoresAsync] Processo Python iniciado. PID={pythonProcess.Id}");
}
}
bool workersOk = true;
if (tasksWorkers.Any())
{
bool[] resultados = await Task.WhenAll(tasksWorkers);
workersOk = resultados.Any(x => x);
if (workersOk)
workersInicializados = true;
}
AtualizarEstadoProcessoPython();
return (!iniciarScript || PythonRodando) && workersOk;
}
catch (Exception ex)
{
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Erro ao iniciar operadores: " + ex);
return false;
}
finally
{
if (entrou)
_restartGate.Release();
}
}
private async Task<bool> IniciarWorkerSeguroAsync(IWorkerModel worker)
{
if (worker == null)
return false;
string nome = worker.GetType().Name;
try
{
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Inicializando worker C#: {nome}");
bool ok = await worker.IniciarProcessamento();
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Worker {nome} inicialização={(ok ? "OK" : "PENDENTE")}");
return ok;
}
catch (Exception ex)
{
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Erro ao iniciar worker {nome}: {ex.Message}");
return false;
} }
} }
public bool Encerrar() public bool Encerrar()
{ {
if (pythonProcess != null && !pythonProcess.HasExited) encerrando = true;
try
{ {
pythonProcess.Kill(); tmrHearthbeat?.Stop();
} }
pythonProcess = null; catch { }
EncerrarPythonAtual("encerramento solicitado");
Conectado = false;
TodosWorkersConectados = false;
PythonRodando = false;
TempoDesconectado = 0;
Variaveis.MostrarLog("[OperadoresService.Encerrar] Núcleo central encerrado.");
return true; return true;
} }
private void EncerrarPythonAtual(string motivo = "não informado")
{
try
{
if (pythonProcess != null)
{
int pid = -1;
try
{
pid = pythonProcess.Id;
}
catch { }
Variaveis.MostrarLog($"[OperadoresService] Encerrando processo Python. PID={pid}, Motivo={motivo}");
encerrandoPythonIntencionalmente = true;
PythonService.EncerrarProcesso(pythonProcess, matarArvore: true);
Variaveis.MostrarLog($"[OperadoresService] Processo Python encerrado intencionalmente. PID={pid}, Motivo={motivo}");
}
}
catch (Exception ex)
{
Variaveis.MostrarLog("[OperadoresService] Erro ao encerrar Python atual: " + ex.Message);
}
finally
{
encerrandoPythonIntencionalmente = false;
pythonProcess = null;
PythonRodando = false;
}
}
private async Task tmrHearthbeat_Tick() private async Task tmrHearthbeat_Tick()
{ {
if (encerrando)
return;
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
Conectado = false; try
foreach (var worker in Workers)
{ {
worker.AtualizarSaude(); AtualizarEstadoProcessoPython();
Conectado |= worker.Saude.conectado;
}
if (!Conectado) Dictionary<string, bool> statusWorkers = AtualizarSaudeWorkers();
{
TempoDesconectado++; bool algumWorkerConectado = statusWorkers.Any(x => x.Value);
if (op.Sensoriamento.Operacao.OperacaoIniciada && (op.Parametros.Controle.MovimentoAutomatico || op.Parametros.Controle.DirecionalAutomatico)) bool todosWorkersConectados = statusWorkers.Any() && statusWorkers.All(x => x.Value);
Conectado = iniciarScript
? PythonRodando && algumWorkerConectado
: algumWorkerConectado;
TodosWorkersConectados = todosWorkersConectados;
bool nucleoCriticoOk = Conectado;
if (!nucleoCriticoOk)
{ {
RedisService.AtualizarCampos( TempoDesconectado++;
CtxKey.DadosOperacao,
("status", StatusOperacao.Parado),
("liberado", false),
("configurado", false)
);
var _Controle = op.Controle; AplicarParadaSeguraSeNecessario(op);
if (op.Parametros.Controle.MovimentoAutomatico)
RedisService.DefinirEquipamentoDesconectado();
LogarDiagnosticoDesconectado(statusWorkers);
if (DeveTentarRestart())
{ {
if (_Controle.PercentualVelocidadeSP > 0) AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
{
_Controle.PercentualVelocidadeSP = 0; await IniciarOperadoresAsync("heartbeat", inicializacaoCompleta: false);
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("velocidade_sp", _Controle.PercentualVelocidadeSP)
);
}
} }
if (op.Parametros.Controle.DirecionalAutomatico) return;
{
if (_Controle.Angulo != 0)
{
_Controle.Angulo = 0;
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("angulo_sp", _Controle.Angulo)
);
}
}
if (op.Parametros.Controle.PulverizadorAutomatico)
{
if (_Controle.Bicos.Any(x => x.ComandoAtuar))
{
_Controle.Bicos.ForEach(x => x.ComandoAtuar = false);
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("controle_bicos", _Controle.Bicos.ToDictionary(x => x.Posicao - 1, x => x.ComandoAtuar))
);
}
}
op.AtualizaInformacoesControleOperacao();
} }
RedisService.DefinirEquipamentoDesconectado(); if (TempoDesconectado > 0)
// A cada 10 segundos desconectado, tenta reiniciar o nucleo de processamento
if (TempoDesconectado > 0 && TempoDesconectado % 10 == 0)
{ {
IniciarOperadores(); Variaveis.MostrarLog("[OperadoresService.tmrHearthbeat_Tick] Núcleo central reconectado.");
AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
} }
}
else
{
TempoDesconectado = 0; TempoDesconectado = 0;
if (!op.Sensoriamento.Operacao.OperacaoConfigurada)
if (!(op?.Sensoriamento?.Operacao?.OperacaoConfigurada ?? false))
{ {
HealthWorkerService.AtualizarDadosOperacao(true); HealthWorkerService.AtualizarDadosOperacao(true);
} }
} }
catch (Exception ex)
{
Variaveis.MostrarLog("[OperadoresService.tmrHearthbeat_Tick] Erro no heartbeat: " + ex);
}
} }
private void AtualizarEstadoProcessoPython()
{
try
{
if (!iniciarScript)
{
PythonRodando = true;
return;
}
if (pythonProcess == null)
{
PythonRodando = false;
return;
}
if (pythonProcess.HasExited)
{
int exitCode = -999;
try
{
exitCode = pythonProcess.ExitCode;
}
catch { }
PythonRodando = false;
Variaveis.MostrarLog($"[OperadoresService.AtualizarEstadoProcessoPython] Processo Python não está rodando. ExitCode={exitCode}");
return;
}
PythonRodando = true;
}
catch (Exception ex)
{
PythonRodando = false;
Variaveis.MostrarLog("[OperadoresService.AtualizarEstadoProcessoPython] Erro ao consultar processo Python: " + ex.Message);
}
}
private Dictionary<string, bool> AtualizarSaudeWorkers()
{
Dictionary<string, bool> status = new Dictionary<string, bool>();
foreach (var worker in Workers)
{
if (worker == null)
continue;
string nome = worker.GetType().Name;
try
{
worker.AtualizarSaude();
bool conectado = worker.Saude != null && worker.Saude.conectado;
status[nome] = conectado;
}
catch (Exception ex)
{
status[nome] = false;
Variaveis.MostrarLog($"[OperadoresService.AtualizarSaudeWorkers] Erro ao atualizar saúde do worker {nome}: {ex.Message}");
}
}
return status;
}
private void AplicarParadaSeguraSeNecessario(OperacaoModel op)
{
try
{
if (op?.Sensoriamento?.Operacao?.OperacaoIniciada != true)
return;
if (op.Parametros?.Controle == null)
return;
bool temControleAutomatico =
op.Parametros.Controle.MovimentoAutomatico ||
op.Parametros.Controle.DirecionalAutomatico ||
op.Parametros.Controle.PulverizadorAutomatico;
if (!temControleAutomatico)
return;
RedisService.AtualizarCampos(
CtxKey.DadosOperacao,
("status", StatusOperacao.Parado),
("liberado", false),
("configurado", false)
);
var controle = op.Controle;
if (controle == null)
return;
if (op.Parametros.Controle.MovimentoAutomatico)
{
if (controle.PercentualVelocidadeSP != 0)
{
controle.PercentualVelocidadeSP = 0;
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("velocidade_sp", controle.PercentualVelocidadeSP)
);
}
}
if (op.Parametros.Controle.DirecionalAutomatico)
{
if (controle.Angulo != 0)
{
controle.Angulo = 0;
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("angulo_sp", controle.Angulo)
);
}
}
if (op.Parametros.Controle.PulverizadorAutomatico)
{
if (controle.Bicos != null && controle.Bicos.Any(x => x.ComandoAtuar))
{
controle.Bicos.ForEach(x => x.ComandoAtuar = false);
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("controle_bicos", controle.Bicos.ToDictionary(x => x.Posicao - 1, x => x.ComandoAtuar))
);
}
}
op.AtualizaInformacoesControleOperacao();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[OperadoresService.AplicarParadaSeguraSeNecessario] Erro ao aplicar parada segura: " + ex.Message);
}
}
private bool DeveTentarRestart()
{
if (!iniciarScript)
return false;
if (TempoDesconectado < TimeoutDesconexao)
return false;
if ((DateTime.Now - ultimoRestart) < IntervaloMinimoRestart)
return false;
return true;
}
private void LogarDiagnosticoDesconectado(Dictionary<string, bool> statusWorkers)
{
if ((DateTime.Now - ultimoLogDesconectado) < IntervaloLogDesconectado)
return;
ultimoLogDesconectado = DateTime.Now;
string pythonStatus = iniciarScript
? $"PythonRodando={PythonRodando}"
: "Python gerenciado externamente";
string workersStatus = string.Join(
", ",
statusWorkers.Select(x => x.Key + "=" + (x.Value ? "OK" : "OFF"))
);
Variaveis.MostrarLog(
$"[OperadoresService.LogarDiagnosticoDesconectado] Núcleo desconectado há {TempoDesconectado}s. {pythonStatus}. Workers: {workersStatus}"
);
if (pythonProcess == null && iniciarScript)
{
Variaveis.MostrarLog("[OperadoresService.LogarDiagnosticoDesconectado] pythonProcess está null.");
}
}
} }
} }

View File

@ -39,7 +39,7 @@ namespace AgroBase.Services.Operadores
DadosLeitura.Iniciado = true; DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto; bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000); await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
if (pronto()) if (pronto())
{ {
MostrarLog("Processamento iniciado com sucesso"); MostrarLog("Processamento iniciado com sucesso");

View File

@ -37,7 +37,7 @@ namespace AgroBase.Services.Operadores
DadosLeitura.Iniciado = true; DadosLeitura.Iniciado = true;
bool pronto() => DadosLeitura.Pronto; bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000); await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
if (pronto()) if (pronto())
{ {
MostrarLog("Processamento iniciado com sucesso"); MostrarLog("Processamento iniciado com sucesso");

View File

@ -4,39 +4,65 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Text;
namespace AgroBase.Services namespace AgroBase.Services
{ {
public class PythonService public class PythonService
{ {
public static bool DebugMode = false; public static bool DebugMode = false;
public static string PythonExe => Path.Combine(CaminhoGeral, "venv", "Scripts", "python.exe");
public static string CaminhoGeral = "Python\\"; public static string CaminhoGeral = "Python\\";
public static string CaminhoScripts = "Scripts\\"; public static string CaminhoScripts = "Scripts\\";
public static string CaminhoLeitura = "Output\\"; public static string CaminhoLeitura = "Output\\";
public static string ScriptWeedDetector private static readonly object _lockProcessos = new object();
private static readonly object _lockLogs = new object();
private static readonly List<Process> processosIniciados = new List<Process>();
public static string BaseDirectory
{ {
get get
{ {
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal; return AppDomain.CurrentDomain.BaseDirectory;
} }
} }
public static string ScriptGreenDetector
public static string CaminhoPythonRoot
{ {
get get
{ {
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal; return ResolverCaminho(CaminhoGeral);
} }
} }
public static string ScriptStreetDetector
public static string PythonExe
{ {
get get
{ {
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_StreetDetector).NomeArquivoLocal; return Path.Combine(CaminhoPythonRoot, "venv", "Scripts", "python.exe");
} }
} }
public static string ScriptsRoot
{
get
{
return Path.Combine(CaminhoPythonRoot, CaminhoScripts);
}
}
public static string LogsRoot
{
get
{
return Path.Combine(CaminhoPythonRoot, "Logs");
}
}
public static string ScriptMapConverter public static string ScriptMapConverter
{ {
get get
@ -44,6 +70,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapLoad).NomeArquivoLocal; return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapLoad).NomeArquivoLocal;
} }
} }
public static string ScriptMapFollower public static string ScriptMapFollower
{ {
get get
@ -51,6 +78,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapFollow).NomeArquivoLocal; return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapFollow).NomeArquivoLocal;
} }
} }
public static string ScriptMapGPS public static string ScriptMapGPS
{ {
get get
@ -58,13 +86,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_GpsViewer).NomeArquivoLocal; return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_GpsViewer).NomeArquivoLocal;
} }
} }
public static string ScriptListOAK
{
get
{
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.ScriptListOAKCaneras).NomeArquivoLocal;
}
}
public static string ScriptMPCController public static string ScriptMPCController
{ {
get get
@ -73,164 +95,423 @@ namespace AgroBase.Services
} }
} }
public static Process RunScript(string script, string[] argumentos)
private static List<Process> processosIniciados = new List<Process>();
public static Process RunScript(string Script, string[] Argumentos)
{ {
return RunScript(script, argumentos, "python");
}
public static Process RunScript(string script, string[] argumentos, string nomeLog)
{
string logPath = null;
try try
{ {
string ScriptPath = CaminhoGeral + CaminhoScripts + Script; Directory.CreateDirectory(LogsRoot);
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{(arg ?? "").Replace("\\", "/")}\""));
Process pythonProcess = new Process logPath = CriarCaminhoLog(nomeLog);
string pythonExe = PythonExe;
string scriptPath = ResolverScriptPath(script);
EscreverLog(logPath, "============================================================");
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Iniciando processo Python");
EscreverLog(logPath, $"BaseDirectory: {BaseDirectory}");
EscreverLog(logPath, $"CaminhoPythonRoot: {CaminhoPythonRoot}");
EscreverLog(logPath, $"PythonExe: {pythonExe}");
EscreverLog(logPath, $"ScriptPath: {scriptPath}");
EscreverLog(logPath, $"WorkingDirectory: {BaseDirectory}");
if (!File.Exists(pythonExe))
{ {
StartInfo = new ProcessStartInfo string msg = $"Python não encontrado: {pythonExe}";
{ EscreverLog(logPath, msg);
FileName = PythonExe, Console.WriteLine(msg);
Arguments = $"{ScriptPath} {Args} --source={Variaveis.NomeAplicacao}", return null;
UseShellExecute = false,
RedirectStandardOutput = false,
RedirectStandardError = false,
CreateNoWindow = true,
Verb = Variaveis.NomeAplicacao
}
};
Console.WriteLine($"{pythonProcess.StartInfo.FileName} {pythonProcess.StartInfo.Arguments}");
//pythonProcess.Exited += PythonProcess_Exited;
//pythonProcess.OutputDataReceived += PythonProcess_OutputDataReceived;
//pythonProcess.ErrorDataReceived += PythonProcess_ErrorDataReceived;
pythonProcess.Start();
//pythonProcess.BeginOutputReadLine();
//pythonProcess.BeginErrorReadLine();
lock (processosIniciados)
{
processosIniciados.Add(pythonProcess);
} }
return pythonProcess; if (!File.Exists(scriptPath))
{
string msg = $"Script Python não encontrado: {scriptPath}";
EscreverLog(logPath, msg);
Console.WriteLine(msg);
return null;
}
string args = MontarArgumentos(scriptPath, argumentos);
EscreverLog(logPath, $"Arguments: {args}");
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = pythonExe,
Arguments = args,
WorkingDirectory = BaseDirectory,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
startInfo.EnvironmentVariables["PYTHONUNBUFFERED"] = "1";
startInfo.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
startInfo.EnvironmentVariables["AGROBASE_SOURCE"] = Variaveis.NomeAplicacao ?? "";
string pythonPathAtual = startInfo.EnvironmentVariables["PYTHONPATH"] ?? "";
string pythonPathNovo = ScriptsRoot;
string workersPath = Path.Combine(ScriptsRoot, "workers");
if (Directory.Exists(workersPath))
pythonPathNovo += Path.PathSeparator + workersPath;
if (!string.IsNullOrWhiteSpace(pythonPathAtual))
pythonPathNovo += Path.PathSeparator + pythonPathAtual;
startInfo.EnvironmentVariables["PYTHONPATH"] = pythonPathNovo;
Process processo = new Process
{
StartInfo = startInfo,
EnableRaisingEvents = true
};
processo.OutputDataReceived += (sender, e) =>
{
if (string.IsNullOrWhiteSpace(e.Data))
return;
string linha = "[OUT] " + e.Data;
EscreverLog(logPath, linha);
if (DebugMode)
Variaveis.MostrarLog($"[PythonService.RunScript] {linha}");
};
processo.ErrorDataReceived += (sender, e) =>
{
if (string.IsNullOrWhiteSpace(e.Data))
return;
string linha = "[ERR] " + e.Data;
EscreverLog(logPath, linha);
Variaveis.MostrarLog($"[PythonService.RunScript] {linha}");
};
processo.Exited += (sender, e) =>
{
try
{
int exitCode = -999;
try
{
exitCode = processo.ExitCode;
}
catch { }
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Processo Python finalizado. PID={processo.Id}, ExitCode={exitCode}");
lock (_lockProcessos)
{
processosIniciados.Remove(processo);
}
}
catch { }
};
Variaveis.MostrarLog($"[PythonService.RunScript] {startInfo.FileName} {startInfo.Arguments}");
bool iniciou = processo.Start();
if (!iniciou)
{
EscreverLog(logPath, "Process.Start retornou false.");
return null;
}
processo.BeginOutputReadLine();
processo.BeginErrorReadLine();
lock (_lockProcessos)
{
processosIniciados.Add(processo);
}
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Processo iniciado com PID={processo.Id}");
return processo;
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Erro ao iniciar script Python: {ex.Message}"); string msg = $"Erro ao iniciar script Python: {ex}";
Variaveis.MostrarLog($"[PythonService.RunScript] {msg}");
try
{
Directory.CreateDirectory(LogsRoot);
if (string.IsNullOrWhiteSpace(logPath))
logPath = Path.Combine(LogsRoot, "python_start_error.log");
EscreverLog(logPath, msg);
}
catch { }
return null; return null;
} }
} }
public static async Task<bool> RunScriptWithTimeout(string Script, string[] Argumentos, int timeoutMs) public static async Task<bool> RunScriptWithTimeout(string script, string[] argumentos, int timeoutMs)
{ {
var processo = RunScript(Script, Argumentos); Process processo = RunScript(script, argumentos, "python_timeout");
if (processo == null) return false;
if (processo == null)
return false;
bool completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
var completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
if (!completed) if (!completed)
{ {
Console.WriteLine("Script Python excedeu o tempo limite e será encerrado."); Variaveis.MostrarLog("[PythonService.RunScriptWithTimeout] Script Python excedeu o tempo limite e será encerrado.");
processo.Kill(); EncerrarProcesso(processo, true);
return false; return false;
} }
return true; return processo.ExitCode == 0;
} }
public static string RunScriptWithCallback(string Script, string[] Argumentos) public static string RunScriptWithCallback(string script, string[] argumentos)
{ {
string logPath = null;
try try
{ {
string ScriptPath = CaminhoGeral + CaminhoScripts + Script; Directory.CreateDirectory(LogsRoot);
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{arg.Replace("\\", "/")}\""));
Process pythonProcess = new Process logPath = CriarCaminhoLog("python_callback");
string pythonExe = PythonExe;
string scriptPath = ResolverScriptPath(script);
if (!File.Exists(pythonExe))
return $"Python não encontrado: {pythonExe}";
if (!File.Exists(scriptPath))
return $"Script Python não encontrado: {scriptPath}";
string args = MontarArgumentos(scriptPath, argumentos);
ProcessStartInfo startInfo = new ProcessStartInfo
{ {
StartInfo = new ProcessStartInfo FileName = pythonExe,
{ Arguments = args,
FileName = PythonExe, WorkingDirectory = BaseDirectory,
Arguments = $"{ScriptPath} {Args}",
UseShellExecute = false, StandardOutputEncoding = Encoding.UTF8,
RedirectStandardOutput = true, StandardErrorEncoding = Encoding.UTF8,
RedirectStandardError = true,
CreateNoWindow = true, UseShellExecute = false,
Verb = Variaveis.NomeAplicacao RedirectStandardOutput = true,
} RedirectStandardError = true,
CreateNoWindow = true
}; };
Console.WriteLine($"{pythonProcess.StartInfo.FileName} {pythonProcess.StartInfo.Arguments}"); startInfo.EnvironmentVariables["PYTHONUNBUFFERED"] = "1";
startInfo.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
pythonProcess.Start(); using (Process processo = new Process())
{
processo.StartInfo = startInfo;
string output = pythonProcess.StandardOutput.ReadToEnd(); Variaveis.MostrarLog($"[PythonService.RunScriptWithTimeout] {startInfo.FileName} {startInfo.Arguments}");
pythonProcess.WaitForExit();
return output; processo.Start();
string output = processo.StandardOutput.ReadToEnd();
string error = processo.StandardError.ReadToEnd();
processo.WaitForExit();
string resultado =
output +
Environment.NewLine +
error +
Environment.NewLine +
$"ExitCode={processo.ExitCode}";
EscreverLog(logPath, resultado);
return resultado;
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Erro ao iniciar script Python: {ex.Message}"); string msg = $"Erro ao iniciar script Python: {ex}";
return null; Variaveis.MostrarLog($"[PythonService.RunScriptWithTimeout] {msg}");
if (!string.IsNullOrWhiteSpace(logPath))
EscreverLog(logPath, msg);
return msg;
} }
} }
private static void PythonProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e) public static bool EncerrarProcesso(Process processo, bool matarArvore)
{ {
if (!string.IsNullOrWhiteSpace(e.Data)) if (processo == null)
{ return true;
Console.WriteLine($"Erro recebidos pelo Python: {e.Data}");
// Log adicional ou notificação pode ser adicionado aqui
}
}
private static void PythonProcess_OutputDataReceived(object sender, DataReceivedEventArgs e) try
{
if (DebugMode)
{ {
Console.WriteLine("Dados recebidos pelo Python: " + e.Data); if (processo.HasExited)
} return true;
}
private static void PythonProcess_Exited(object sender, EventArgs e) int pid = processo.Id;
{
if (DebugMode)
{
Console.WriteLine("Processo Python finalizado");
}
lock (processosIniciados) if (matarArvore)
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = "taskkill",
Arguments = $"/PID {pid} /T /F",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using (Process killer = Process.Start(psi))
{
if (killer != null)
killer.WaitForExit(5000);
}
}
else
{
processo.Kill();
processo.WaitForExit(5000);
}
return true;
}
catch (Exception ex)
{ {
var processo = (Process)sender; Variaveis.MostrarLog($"[PythonService.EncerrarProcesso] Erro ao encerrar processo Python: {ex.Message}");
processosIniciados.Remove(processo); return false;
processo.Dispose(); }
finally
{
lock (_lockProcessos)
{
processosIniciados.Remove(processo);
}
} }
} }
public static void EncerrarProcessos(int processId = -1) public static void EncerrarProcessos(int processId = -1)
{ {
return; List<Process> processos;
foreach (var processo in Process.GetProcessesByName("python"))
lock (_lockProcessos)
{
processos = processosIniciados
.Where(x => x != null)
.ToList();
}
foreach (Process processo in processos)
{ {
try try
{ {
// Verifica os argumentos do processo if (processId > -1 && processo.Id != processId)
string argumentos = processo.StartInfo.Arguments; continue;
if ((processId > -1 && processo.Id == processId))
{ EncerrarProcesso(processo, true);
processo.Kill();
}
else if (processId == -1)
{
processo.Kill();
}
} }
catch (Exception ex) catch (Exception ex)
{ {
Console.WriteLine($"Erro ao encerrar processo: {ex.Message}"); Variaveis.MostrarLog($"[PythonService.EncerrarProcessos] Erro ao encerrar processo controlado: {ex.Message}");
} }
} }
} }
private static string ResolverCaminho(string caminho)
{
if (string.IsNullOrWhiteSpace(caminho))
return BaseDirectory;
if (Path.IsPathRooted(caminho))
return Path.GetFullPath(caminho);
return Path.GetFullPath(Path.Combine(BaseDirectory, caminho));
}
private static string ResolverScriptPath(string script)
{
if (string.IsNullOrWhiteSpace(script))
return "";
if (Path.IsPathRooted(script))
return Path.GetFullPath(script);
return Path.GetFullPath(Path.Combine(ScriptsRoot, script));
}
private static string MontarArgumentos(string scriptPath, string[] argumentos)
{
List<string> args = new List<string>();
args.Add(QuoteArg(scriptPath));
if (argumentos != null)
{
foreach (string arg in argumentos)
args.Add(QuoteArg((arg ?? "").Replace("\\", "/")));
}
args.Add("--source=" + QuoteArg(Variaveis.NomeAplicacao ?? ""));
return string.Join(" ", args);
}
private static string QuoteArg(string value)
{
if (value == null)
value = "";
value = value.Replace("\"", "\\\"");
return "\"" + value + "\"";
}
private static string CriarCaminhoLog(string nomeLog)
{
string safeName = string.IsNullOrWhiteSpace(nomeLog)
? "python"
: nomeLog.Replace(" ", "_").Replace("/", "_").Replace("\\", "_");
string nomeArquivo = safeName + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".log";
return Path.Combine(LogsRoot, nomeArquivo);
}
private static void EscreverLog(string logPath, string texto)
{
try
{
if (string.IsNullOrWhiteSpace(logPath))
return;
lock (_lockLogs)
{
File.AppendAllText(logPath, texto + Environment.NewLine, Encoding.UTF8);
}
}
catch { }
}
} }
} }