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

View File

@ -1171,7 +1171,7 @@ namespace AgroBase.Models.Modules
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);
Atuador.BicosPulverizadores.Add(new AtuadorBicoModel()
@ -1311,9 +1311,9 @@ namespace AgroBase.Models.Modules
var op = Variaveis.OperacaoEmAndamento;
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;
var bomba = dadosAtuador.BombaPressurizadora;
@ -1420,7 +1420,7 @@ namespace AgroBase.Models.Modules
if (!CanManager.CanService.IsConnected) return false;
if (op.DispAtu == null) return false;
if (op?.DispAtu == null) return false;
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
@ -1997,25 +1997,42 @@ namespace AgroBase.Models.Modules
var op = Variaveis.OperacaoEmAndamento;
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;
}
@ -2685,7 +2702,7 @@ namespace AgroBase.Models.Modules
valor.atual.valor = v;
}
if (Posicao == CanMessagePosicaoDados.Status)
if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
{
if (sensor != null)
sensor.Inicializado = v;
@ -2861,7 +2878,7 @@ namespace AgroBase.Models.Modules
malhaFechada,
}
);
Variaveis.MostrarLog($"BOMBA {ID_Num}, INICIADO: {iniciado}, MALHA_FECHADA: {malhaFechada}");
var bomba = Dados.BombasPressurizadoras?.FirstOrDefault(x => x.ID_Num == ID_Num);
break;
}
case CanMessagePosicaoDados.Dados1:

View File

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

View File

@ -94,6 +94,7 @@ namespace AgroBase.Models
private static string _discoverySessionId = Guid.NewGuid().ToString("N");
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.Emergencia = false;
op.Sensoriamento.Operacao.Calibrando = false;
op.Sensoriamento.Operacao.Pausa = false;
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
op.Sensoriamento.Operacao.DataFim = DateTime.Now;
@ -1117,106 +1120,6 @@ namespace AgroBase.Models
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()
{
var op = this;
@ -1265,7 +1168,6 @@ namespace AgroBase.Models
});
}
public void IniciarTreinamento()
{
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
#region CONTROLE DA OPERACAO
@ -2538,6 +2654,9 @@ namespace AgroBase.Models
bool salvouComSucesso = false;
DateTime agora = DateTime.Now;
DateTime novoCursorEventos = DateTime.Now;
List<OperacaoSensoriamentoLogErrosModel> eventosSalvos = new List<OperacaoSensoriamentoLogErrosModel>();
try
{
if (string.IsNullOrWhiteSpace(op.ID))
@ -2602,12 +2721,15 @@ namespace AgroBase.Models
// Eventos discretos de saúde/erro
// 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++;
salvouComSucesso = true;
if (eventosSalvos.Any())
{
novoCursorEventos = eventosSalvos.Max(x => x.Momento);
}
else
{
novoCursorEventos = DateTime.Now;
}
}
catch (Exception ex)
{
@ -2649,7 +2780,7 @@ namespace AgroBase.Models
// Assim, se der erro no disco/JSON, você não perde eventos.
if (salvouComSucesso && op?.Sensoriamento != null)
{
op.Sensoriamento.UltimoRegistroLog = agora;
op.Sensoriamento.UltimoRegistroLog = novoCursorEventos;
}
try
@ -3323,7 +3454,7 @@ namespace AgroBase.Models
}
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);
if (ind.status != ind_ant?.status)
@ -4376,18 +4507,37 @@ 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;
if (op == null)
return;
try
{
if (Logs == null)
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()
{
Momento = DateTime.Now,
Momento = agora,
Dispositivo = dispositivo,
ID = id,
Status = status,
@ -4402,11 +4552,13 @@ namespace AgroBase.Models
}
}
public void AtualizarDados()
{
if (op == null) return;
DateTime agora = DateTime.Now;
DateTime ultimoRegistroLog = UltimoRegistroLog;
// Contexto
HealthWorkerService.AtualizarDadosContexto();
@ -4495,7 +4647,8 @@ namespace AgroBase.Models
DadosPerformance = dadosPerformance,
LivoxLidar = dadosLivox,
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;

View File

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

View File

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

View File

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

View File

@ -1,8 +1,10 @@
using AgroBase.Models;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static AgroBase.Models.Enums;
using static AgroBase.Models.Operadores.OperadoresModels;
@ -11,144 +13,470 @@ namespace AgroBase.Services.Operadores
{
public class OperadoresService
{
private readonly object _estadoLock = new object();
private readonly SemaphoreSlim _restartGate = new SemaphoreSlim(1, 1);
private Process pythonProcess;
public List<IWorkerModel> Workers = new List<IWorkerModel>()
{
new ManagerWorkerService(),
new VisualWorkerService(),
new VisualWorkerService(),
new HealthWorkerService(),
new CameraWorkerService(),
new WeedWorkerService(),
};
private bool iniciarScript = false;
private bool iniciarScript = true;
private bool workersInicializados = false;
private bool encerrando = false;
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;
public bool Conectado = false;
public bool TodosWorkersConectados = false;
public bool PythonRodando = false;
private bool encerrandoPythonIntencionalmente = false;
public async Task<bool> IniciarProcessamento(bool _iniciarScript = true)
{
iniciarScript = _iniciarScript;
encerrando = false;
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Iniciando núcleo central de processamento...");
bool iniciou = await IniciarOperadoresAsync("inicio", inicializacaoCompleta: true);
tmrHearthbeat?.Dispose();
tmrHearthbeat = new AsyncTaskTimerModel("tmrHearthbeatOperadores", tmrHearthbeat_Tick, 1000);
tmrHearthbeat.Start();
IniciarOperadores();
return true;
}
private void IniciarOperadores()
{
foreach (var worker in Workers)
if (iniciou)
{
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();
pythonProcess = PythonService.RunScript(Path.Combine("workers", "main_async.py"), new string[] { });
entrou = await _restartGate.WaitAsync(0);
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()
{
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;
}
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()
{
if (encerrando)
return;
var op = Variaveis.OperacaoEmAndamento;
Conectado = false;
foreach (var worker in Workers)
try
{
worker.AtualizarSaude();
Conectado |= worker.Saude.conectado;
}
AtualizarEstadoProcessoPython();
if (!Conectado)
{
TempoDesconectado++;
if (op.Sensoriamento.Operacao.OperacaoIniciada && (op.Parametros.Controle.MovimentoAutomatico || op.Parametros.Controle.DirecionalAutomatico))
Dictionary<string, bool> statusWorkers = AtualizarSaudeWorkers();
bool algumWorkerConectado = statusWorkers.Any(x => x.Value);
bool todosWorkersConectados = statusWorkers.Any() && statusWorkers.All(x => x.Value);
Conectado = iniciarScript
? PythonRodando && algumWorkerConectado
: algumWorkerConectado;
TodosWorkersConectados = todosWorkersConectados;
bool nucleoCriticoOk = Conectado;
if (!nucleoCriticoOk)
{
RedisService.AtualizarCampos(
CtxKey.DadosOperacao,
("status", StatusOperacao.Parado),
("liberado", false),
("configurado", false)
);
TempoDesconectado++;
var _Controle = op.Controle;
if (op.Parametros.Controle.MovimentoAutomatico)
AplicarParadaSeguraSeNecessario(op);
RedisService.DefinirEquipamentoDesconectado();
LogarDiagnosticoDesconectado(statusWorkers);
if (DeveTentarRestart())
{
if (_Controle.PercentualVelocidadeSP > 0)
{
_Controle.PercentualVelocidadeSP = 0;
RedisService.AtualizarCampos(
CtxKey.DadosControle,
("velocidade_sp", _Controle.PercentualVelocidadeSP)
);
}
AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
await IniciarOperadoresAsync("heartbeat", inicializacaoCompleta: false);
}
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.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();
return;
}
RedisService.DefinirEquipamentoDesconectado();
// A cada 10 segundos desconectado, tenta reiniciar o nucleo de processamento
if (TempoDesconectado > 0 && TempoDesconectado % 10 == 0)
if (TempoDesconectado > 0)
{
IniciarOperadores();
AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
Variaveis.MostrarLog("[OperadoresService.tmrHearthbeat_Tick] Núcleo central reconectado.");
}
}
else
{
TempoDesconectado = 0;
if (!op.Sensoriamento.Operacao.OperacaoConfigurada)
if (!(op?.Sensoriamento?.Operacao?.OperacaoConfigurada ?? false))
{
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;
bool pronto() => DadosLeitura.Pronto;
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
if (pronto())
{
MostrarLog("Processamento iniciado com sucesso");

View File

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

View File

@ -4,39 +4,65 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text;
namespace AgroBase.Services
{
public class PythonService
{
public static bool DebugMode = false;
public static string PythonExe => Path.Combine(CaminhoGeral, "venv", "Scripts", "python.exe");
public static string CaminhoGeral = "Python\\";
public static string CaminhoScripts = "Scripts\\";
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
{
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal;
return AppDomain.CurrentDomain.BaseDirectory;
}
}
public static string ScriptGreenDetector
public static string CaminhoPythonRoot
{
get
{
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal;
return ResolverCaminho(CaminhoGeral);
}
}
public static string ScriptStreetDetector
public static string PythonExe
{
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
{
get
@ -44,6 +70,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapLoad).NomeArquivoLocal;
}
}
public static string ScriptMapFollower
{
get
@ -51,6 +78,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapFollow).NomeArquivoLocal;
}
}
public static string ScriptMapGPS
{
get
@ -58,13 +86,7 @@ namespace AgroBase.Services
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_GpsViewer).NomeArquivoLocal;
}
}
public static string ScriptListOAK
{
get
{
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.ScriptListOAKCaneras).NomeArquivoLocal;
}
}
public static string ScriptMPCController
{
get
@ -73,164 +95,423 @@ namespace AgroBase.Services
}
}
private static List<Process> processosIniciados = new List<Process>();
public static Process RunScript(string Script, string[] Argumentos)
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
{
string ScriptPath = CaminhoGeral + CaminhoScripts + Script;
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{(arg ?? "").Replace("\\", "/")}\""));
Directory.CreateDirectory(LogsRoot);
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
{
FileName = PythonExe,
Arguments = $"{ScriptPath} {Args} --source={Variaveis.NomeAplicacao}",
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);
string msg = $"Python não encontrado: {pythonExe}";
EscreverLog(logPath, msg);
Console.WriteLine(msg);
return null;
}
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)
{
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;
}
}
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);
if (processo == null) return false;
Process processo = RunScript(script, argumentos, "python_timeout");
if (processo == null)
return false;
bool completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
var completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
if (!completed)
{
Console.WriteLine("Script Python excedeu o tempo limite e será encerrado.");
processo.Kill();
Variaveis.MostrarLog("[PythonService.RunScriptWithTimeout] Script Python excedeu o tempo limite e será encerrado.");
EncerrarProcesso(processo, true);
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
{
string ScriptPath = CaminhoGeral + CaminhoScripts + Script;
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{arg.Replace("\\", "/")}\""));
Directory.CreateDirectory(LogsRoot);
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 = $"{ScriptPath} {Args}",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
Verb = Variaveis.NomeAplicacao
}
FileName = pythonExe,
Arguments = args,
WorkingDirectory = BaseDirectory,
StandardOutputEncoding = Encoding.UTF8,
StandardErrorEncoding = Encoding.UTF8,
UseShellExecute = false,
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();
pythonProcess.WaitForExit();
Variaveis.MostrarLog($"[PythonService.RunScriptWithTimeout] {startInfo.FileName} {startInfo.Arguments}");
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)
{
Console.WriteLine($"Erro ao iniciar script Python: {ex.Message}");
return null;
string msg = $"Erro ao iniciar script Python: {ex}";
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))
{
Console.WriteLine($"Erro recebidos pelo Python: {e.Data}");
// Log adicional ou notificação pode ser adicionado aqui
}
}
if (processo == null)
return true;
private static void PythonProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
if (DebugMode)
try
{
Console.WriteLine("Dados recebidos pelo Python: " + e.Data);
}
}
if (processo.HasExited)
return true;
private static void PythonProcess_Exited(object sender, EventArgs e)
{
if (DebugMode)
{
Console.WriteLine("Processo Python finalizado");
}
int pid = processo.Id;
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;
processosIniciados.Remove(processo);
processo.Dispose();
Variaveis.MostrarLog($"[PythonService.EncerrarProcesso] Erro ao encerrar processo Python: {ex.Message}");
return false;
}
finally
{
lock (_lockProcessos)
{
processosIniciados.Remove(processo);
}
}
}
public static void EncerrarProcessos(int processId = -1)
{
return;
foreach (var processo in Process.GetProcessesByName("python"))
List<Process> processos;
lock (_lockProcessos)
{
processos = processosIniciados
.Where(x => x != null)
.ToList();
}
foreach (Process processo in processos)
{
try
{
// Verifica os argumentos do processo
string argumentos = processo.StartInfo.Arguments;
if ((processId > -1 && processo.Id == processId))
{
processo.Kill();
}
else if (processId == -1)
{
processo.Kill();
}
if (processId > -1 && processo.Id != processId)
continue;
EncerrarProcesso(processo, true);
}
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 { }
}
}
}
}