ajustes calibragem inicial e inicialização automática dos workers python
This commit is contained in:
parent
2c3901c355
commit
c5e5785718
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 { }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue