Compare commits
2 Commits
2c3901c355
...
d7f1689610
| Author | SHA1 | Date |
|---|---|---|
|
|
d7f1689610 | |
|
|
c5e5785718 |
|
|
@ -1,6 +1,7 @@
|
||||||
using AgroBase.Forms.IHM;
|
using AgroBase.Forms.IHM;
|
||||||
using AgroBase.Models;
|
using AgroBase.Models;
|
||||||
using AgroBase.Services;
|
using AgroBase.Services;
|
||||||
|
using AgroBase.Services.Operadores;
|
||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -10,6 +11,8 @@ namespace AgroBase.Forms
|
||||||
{
|
{
|
||||||
public partial class frmInstancial : Form
|
public partial class frmInstancial : Form
|
||||||
{
|
{
|
||||||
|
public static readonly bool IniciarWorkers = true;
|
||||||
|
|
||||||
public static AsyncTaskTimerModel tmrVarreduraDispositivos;
|
public static AsyncTaskTimerModel tmrVarreduraDispositivos;
|
||||||
|
|
||||||
public static frmPrincipal frmPrincipal = new frmPrincipal();
|
public static frmPrincipal frmPrincipal = new frmPrincipal();
|
||||||
|
|
@ -21,11 +24,9 @@ namespace AgroBase.Forms
|
||||||
//TopMost = true
|
//TopMost = true
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly SemaphoreSlim _startupGate =
|
private static readonly SemaphoreSlim _startupGate = new SemaphoreSlim(1, 1);
|
||||||
new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
private static readonly SemaphoreSlim _shutdownGate =
|
private static readonly SemaphoreSlim _shutdownGate = new SemaphoreSlim(1, 1);
|
||||||
new SemaphoreSlim(1, 1);
|
|
||||||
|
|
||||||
private static CancellationTokenSource _appCts;
|
private static CancellationTokenSource _appCts;
|
||||||
|
|
||||||
|
|
@ -44,10 +45,7 @@ namespace AgroBase.Forms
|
||||||
* O WinForms ainda está criando handles e a aplicação ainda não
|
* O WinForms ainda está criando handles e a aplicação ainda não
|
||||||
* possui lifecycle seguro para aguardar falhas.
|
* possui lifecycle seguro para aguardar falhas.
|
||||||
*/
|
*/
|
||||||
ThreadPool.SetMinThreads(
|
ThreadPool.SetMinThreads(workerThreads: 50, completionPortThreads: 50);
|
||||||
workerThreads: 50,
|
|
||||||
completionPortThreads: 50
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async void frmInstancial_Load(object sender, EventArgs e)
|
private async void frmInstancial_Load(object sender, EventArgs e)
|
||||||
|
|
@ -58,20 +56,11 @@ namespace AgroBase.Forms
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog("[frmInstancial.Load] Falha crítica na inicialização: " + ex);
|
||||||
"[frmInstancial.Load] Falha crítica na inicialização: " +
|
|
||||||
ex
|
|
||||||
);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
MessageBox.Show(
|
MessageBox.Show("Falha ao inicializar o sistema:\n\n" + ex.Message, "AgroBase", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
"Falha ao inicializar o sistema:\n\n" +
|
|
||||||
ex.Message,
|
|
||||||
"AgroBase",
|
|
||||||
MessageBoxButtons.OK,
|
|
||||||
MessageBoxIcon.Error
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
@ -103,16 +92,13 @@ namespace AgroBase.Forms
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog("[frmInstancial.Shown] " + ex);
|
||||||
"[frmInstancial.Shown] " + ex
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task InicializarServicosAsync()
|
private static async Task InicializarServicosAsync()
|
||||||
{
|
{
|
||||||
await _startupGate.WaitAsync()
|
await _startupGate.WaitAsync().ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -143,8 +129,7 @@ namespace AgroBase.Forms
|
||||||
|
|
||||||
GeneralJoystick.IniciarRotinas();
|
GeneralJoystick.IniciarRotinas();
|
||||||
|
|
||||||
await Variaveis.IniciarMqttAsync(_appCts.Token)
|
await Variaveis.IniciarMqttAsync(_appCts.Token).ConfigureAwait(false);
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Hoje pode ficar comentado se UDP não for usado no rover,
|
* Hoje pode ficar comentado se UDP não for usado no rover,
|
||||||
|
|
@ -157,28 +142,19 @@ namespace AgroBase.Forms
|
||||||
|
|
||||||
LivoxManagerProcess.Start();
|
LivoxManagerProcess.Start();
|
||||||
|
|
||||||
await VariaveisOperacao
|
await VariaveisOperacao.Operadores.IniciarProcessamento(IniciarWorkers).ConfigureAwait(false);
|
||||||
.Operadores
|
|
||||||
.IniciarProcessamento(false)
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
|
|
||||||
AudioAlertaService.SelecionarVoz(
|
AudioAlertaService.SelecionarVoz(VariaveisEquipamento.VozAlerta);
|
||||||
VariaveisEquipamento.VozAlerta
|
|
||||||
);
|
|
||||||
|
|
||||||
IniciarTimerVarreduraDispositivos();
|
IniciarTimerVarreduraDispositivos();
|
||||||
|
|
||||||
_startupConcluido = true;
|
_startupConcluido = true;
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog("[frmInstancial] Inicialização concluída.");
|
||||||
"[frmInstancial] Inicialização concluída."
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
await EncerrarProcessosInternoAsync(
|
await EncerrarProcessosInternoAsync(sairProcesso: false).ConfigureAwait(false);
|
||||||
sairProcesso: false
|
|
||||||
).ConfigureAwait(false);
|
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
@ -210,29 +186,7 @@ namespace AgroBase.Forms
|
||||||
if (Variaveis.Fechando)
|
if (Variaveis.Fechando)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
await SerialService
|
await SerialService.RealizarVarreduraPortasUSB().ConfigureAwait(false);
|
||||||
.RealizarVarreduraPortasUSB()
|
|
||||||
.ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Método mantido para compatibilidade com chamadas antigas.
|
|
||||||
* Não é mais chamado no Load, porque o startup correto é
|
|
||||||
* Variaveis.IniciarMqttAsync().
|
|
||||||
*/
|
|
||||||
private async void IniciarConexaoMqtt()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await Variaveis.IniciarMqttAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog(
|
|
||||||
"[frmInstancial.IniciarConexaoMqtt] " +
|
|
||||||
ex.Message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task EncerrarProcessos()
|
public static Task EncerrarProcessos()
|
||||||
|
|
@ -376,6 +330,15 @@ namespace AgroBase.Forms
|
||||||
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar MQTT: " + ex.Message);
|
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar MQTT: " + ex.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
VariaveisOperacao.Operadores.Encerrar();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar OperadoresService: " + ex.Message);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_appCts?.Dispose();
|
_appCts?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -1171,7 +1171,7 @@ namespace AgroBase.Models.Modules
|
||||||
BicosPulverizadores = new List<AtuadorBicoModel>(),
|
BicosPulverizadores = new List<AtuadorBicoModel>(),
|
||||||
|
|
||||||
};
|
};
|
||||||
for (int i = 0; i < Variaveis.OperacaoEmAndamento.Parametros.QtdBicos; i++)
|
for (int i = 0; i < VariaveisEquipamento.QuantidadeBicosPulverizadores; i++)
|
||||||
{
|
{
|
||||||
int posicao = (i + 1);
|
int posicao = (i + 1);
|
||||||
Atuador.BicosPulverizadores.Add(new AtuadorBicoModel()
|
Atuador.BicosPulverizadores.Add(new AtuadorBicoModel()
|
||||||
|
|
@ -1311,9 +1311,9 @@ namespace AgroBase.Models.Modules
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
var dadosAtuador = Dados;
|
var dadosAtuador = Dados;
|
||||||
var controle = op.Parametros?.Controle;
|
var controle = op?.Parametros?.Controle;
|
||||||
|
|
||||||
if (dadosAtuador == null || controle == null)
|
if (op == null || dadosAtuador == null || controle == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var bomba = dadosAtuador.BombaPressurizadora;
|
var bomba = dadosAtuador.BombaPressurizadora;
|
||||||
|
|
@ -1420,7 +1420,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
if (!CanManager.CanService.IsConnected) return false;
|
if (!CanManager.CanService.IsConnected) return false;
|
||||||
|
|
||||||
if (op.DispAtu == null) return false;
|
if (op?.DispAtu == null) return false;
|
||||||
|
|
||||||
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
|
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
|
||||||
|
|
||||||
|
|
@ -1997,25 +1997,42 @@ namespace AgroBase.Models.Modules
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
List<Task> tasks = new List<Task>();
|
List<Task> tasks = new List<Task>();
|
||||||
var DispAtu = op.DispAtu;
|
|
||||||
if (DispAtu != null)
|
|
||||||
{
|
|
||||||
tasks.Add(DispAtu.Dados.RealizarTestesIniciais(forcar).ContinueWith(t => {
|
|
||||||
if (t.IsFaulted)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}");
|
|
||||||
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}");
|
|
||||||
}
|
|
||||||
if (t.IsCompleted)
|
|
||||||
{
|
|
||||||
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
|
|
||||||
var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não"));
|
|
||||||
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v));
|
|
||||||
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v));
|
|
||||||
}
|
|
||||||
|
|
||||||
}));
|
if (op?.DispAtu?.Dados == null)
|
||||||
}
|
return tasks;
|
||||||
|
|
||||||
|
tasks.Add(Task.Run(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool sucesso = await op.DispAtu.Dados.RealizarTestesIniciais(forcar);
|
||||||
|
|
||||||
|
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
|
||||||
|
var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não"));
|
||||||
|
|
||||||
|
if (sucesso)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Erro no teste do módulo {Modulo_ID}: {ex.Message}");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no teste do módulo {Modulo_ID}: {ex.Message}");
|
||||||
|
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2685,7 +2702,7 @@ namespace AgroBase.Models.Modules
|
||||||
valor.atual.valor = v;
|
valor.atual.valor = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Posicao == CanMessagePosicaoDados.Status)
|
if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
|
||||||
{
|
{
|
||||||
if (sensor != null)
|
if (sensor != null)
|
||||||
sensor.Inicializado = v;
|
sensor.Inicializado = v;
|
||||||
|
|
@ -2861,7 +2878,7 @@ namespace AgroBase.Models.Modules
|
||||||
malhaFechada,
|
malhaFechada,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
Variaveis.MostrarLog($"BOMBA {ID_Num}, INICIADO: {iniciado}, MALHA_FECHADA: {malhaFechada}");
|
var bomba = Dados.BombasPressurizadoras?.FirstOrDefault(x => x.ID_Num == ID_Num);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CanMessagePosicaoDados.Dados1:
|
case CanMessagePosicaoDados.Dados1:
|
||||||
|
|
|
||||||
|
|
@ -2810,7 +2810,7 @@ namespace AgroBase.Models.Modules
|
||||||
valor.atual.valor = v;
|
valor.atual.valor = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Posicao == CanMessagePosicaoDados.Status)
|
if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
|
||||||
{
|
{
|
||||||
if (sensor != null)
|
if (sensor != null)
|
||||||
sensor.Inicializado = v;
|
sensor.Inicializado = v;
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,7 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
private static string _discoverySessionId = Guid.NewGuid().ToString("N");
|
private static string _discoverySessionId = Guid.NewGuid().ToString("N");
|
||||||
private static long _telemetrySequence = 0;
|
private static long _telemetrySequence = 0;
|
||||||
|
private readonly SemaphoreSlim _calibragemGate = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1080,7 +1081,9 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
op.Sensoriamento.Operacao.OperacaoIniciada = false;
|
op.Sensoriamento.Operacao.OperacaoIniciada = false;
|
||||||
op.Sensoriamento.Operacao.Emergencia = false;
|
op.Sensoriamento.Operacao.Emergencia = false;
|
||||||
|
op.Sensoriamento.Operacao.Calibrando = false;
|
||||||
op.Sensoriamento.Operacao.Pausa = false;
|
op.Sensoriamento.Operacao.Pausa = false;
|
||||||
|
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
|
||||||
|
|
||||||
op.Sensoriamento.Operacao.DataFim = DateTime.Now;
|
op.Sensoriamento.Operacao.DataFim = DateTime.Now;
|
||||||
|
|
||||||
|
|
@ -1117,106 +1120,6 @@ namespace AgroBase.Models
|
||||||
op.EnviarParametorsOperacao();
|
op.EnviarParametorsOperacao();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task RealizarCalibragemInicialAsync(bool forcar)
|
|
||||||
{
|
|
||||||
var op = this;
|
|
||||||
|
|
||||||
if (op.Sensoriamento.Operacao.Calibrando) return;
|
|
||||||
|
|
||||||
var modsCalibragem = op.Sensoriamento.Operacao.ModsCalibragem;
|
|
||||||
|
|
||||||
bool modsCalibrados =
|
|
||||||
modsCalibragem != null &&
|
|
||||||
modsCalibragem.Any() &&
|
|
||||||
modsCalibragem.All(x =>
|
|
||||||
x.Value != null &&
|
|
||||||
x.Value.Values.Count > 0 &&
|
|
||||||
x.Value.Values.All(y => y == true)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!forcar && modsCalibrados)
|
|
||||||
{
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Todos os módulos já foram calibrados anteriormente, não irá calibrar novamente.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var DispAtu = op.DispAtu;
|
|
||||||
var DispMvd = op.DispMvd;
|
|
||||||
List<StatusModulo> sv = new List<StatusModulo>() { StatusModulo.Operante, StatusModulo.Alerta };
|
|
||||||
var s = op.Sensoriamento.OperadorSaude.ModulosSaude;
|
|
||||||
var pControle = op.Parametros.Controle;
|
|
||||||
var _Controle = op.Controle;
|
|
||||||
|
|
||||||
bool calibragemAtuNecessaria = pControle.PulverizadorAutomatico && (DispAtu?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Atu)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Atu].Any(x => !x.Value) || !modsCalibragem[T_Code.Atu].Any()));
|
|
||||||
bool calibragemDirNecessaria = (DispMvd?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Dir)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Dir].Any(x => !x.Value) || !modsCalibragem[T_Code.Dir].Any()));
|
|
||||||
bool calibragemMovNecessaria = (DispMvd?.Dados != null && sv.Contains(s.FirstOrDefault(x => x.modulo == T_Code.Mov)?.status ?? StatusModulo.Desconectado) && (forcar || modsCalibragem[T_Code.Mov].Any(x => !x.Value) || !modsCalibragem[T_Code.Mov].Any()));
|
|
||||||
|
|
||||||
if (!calibragemAtuNecessaria && !calibragemDirNecessaria && !calibragemMovNecessaria)
|
|
||||||
{
|
|
||||||
if (forcar)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem não necessária");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem não necessária");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Pausa para calibragem inicial");
|
|
||||||
op.Sensoriamento.Operacao.Pausa = true;
|
|
||||||
await Task.Delay(5000);
|
|
||||||
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem dos modulos inciada...");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem dos modulos inciada...");
|
|
||||||
op.Sensoriamento.Operacao.Calibrando = true;
|
|
||||||
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", op.Sensoriamento.Operacao.Calibrando));
|
|
||||||
|
|
||||||
TipoMovimentoDirecional tipoControle = _Controle.TipoMovimento;
|
|
||||||
|
|
||||||
var tasks = new List<Task>();
|
|
||||||
|
|
||||||
if (calibragemAtuNecessaria) tasks.AddRange(DispAtu.Dados.RealizarCalibragemAsync(forcar));
|
|
||||||
if (calibragemDirNecessaria) tasks.AddRange(DispMvd.Dados.RealizarCalibragemDirAsync(forcar));
|
|
||||||
if (calibragemMovNecessaria) tasks.AddRange(DispMvd.Dados.RealizarCalibragemMovAsync(forcar));
|
|
||||||
|
|
||||||
if (tasks.Count == 0)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Nenhuma calibragem foi iniciada.");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, $"Nenhuma calibragem foi iniciada.");
|
|
||||||
_Controle.TipoMovimento = tipoControle;
|
|
||||||
op.Sensoriamento.Operacao.Calibrando = false;
|
|
||||||
op.Sensoriamento.Operacao.Pausa = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var allTasks = Task.WhenAll(tasks);
|
|
||||||
var completed = await Task.WhenAny(allTasks, Task.Delay(2 * 60 * 1000));
|
|
||||||
|
|
||||||
if (completed == allTasks)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem finalizada com sucesso!");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, $"Calibragem finalizada com sucesso!");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Timeout na calibragem. Continuando mesmo assim.");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, $"Timeout na calibragem. Continuando mesmo assim.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Variaveis.MostrarLog($"[OperacaoModel] [RealizarCalibragemInicialAsync] Erro durante calibragem: {ex.Message}");
|
|
||||||
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Falha, 0, $"Erro durante calibragem: {ex.Message}");
|
|
||||||
}
|
|
||||||
_Controle.TipoMovimento = tipoControle;
|
|
||||||
op.Sensoriamento.Operacao.Calibrando = false;
|
|
||||||
op.Sensoriamento.Operacao.Pausa = false;
|
|
||||||
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", op.Sensoriamento.Operacao.Calibrando));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public async Task IniciarSimulacao()
|
public async Task IniciarSimulacao()
|
||||||
{
|
{
|
||||||
var op = this;
|
var op = this;
|
||||||
|
|
@ -1265,7 +1168,6 @@ namespace AgroBase.Models
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void IniciarTreinamento()
|
public void IniciarTreinamento()
|
||||||
{
|
{
|
||||||
var op = this;
|
var op = this;
|
||||||
|
|
@ -1362,6 +1264,220 @@ namespace AgroBase.Models
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region CALIBRAGEM
|
||||||
|
|
||||||
|
public async Task RealizarCalibragemInicialAsync(bool forcar)
|
||||||
|
{
|
||||||
|
var op = this;
|
||||||
|
|
||||||
|
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!await _calibragemGate.WaitAsync(0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
TipoMovimentoDirecional tipoControle = op.Controle.TipoMovimento;
|
||||||
|
bool entrouEmCalibragem = false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
GarantirEstruturaModsCalibragem(op);
|
||||||
|
|
||||||
|
var modsCalibragem = op.Sensoriamento.Operacao.ModsCalibragem;
|
||||||
|
|
||||||
|
bool modsCalibrados =
|
||||||
|
modsCalibragem.Any() &&
|
||||||
|
modsCalibragem.All(x =>
|
||||||
|
x.Value != null &&
|
||||||
|
x.Value.Values.Count > 0 &&
|
||||||
|
x.Value.Values.All(y => y)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!forcar && modsCalibrados)
|
||||||
|
{
|
||||||
|
//op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Todos os módulos já foram calibrados anteriormente, não irá calibrar novamente.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dispAtu = op.DispAtu;
|
||||||
|
var dispMvd = op.DispMvd;
|
||||||
|
|
||||||
|
var statusValidos = new List<StatusModulo>()
|
||||||
|
{
|
||||||
|
StatusModulo.Operante,
|
||||||
|
StatusModulo.Alerta
|
||||||
|
};
|
||||||
|
|
||||||
|
var saude = op.Sensoriamento?.OperadorSaude?.ModulosSaude ?? new List<ManagerWorkerMessageResponseModulosPendentesModel>();
|
||||||
|
var pControle = op.Parametros?.Controle;
|
||||||
|
|
||||||
|
if (pControle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool atuSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Atu)?.status ?? StatusModulo.Desconectado);
|
||||||
|
bool dirSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Dir)?.status ?? StatusModulo.Desconectado);
|
||||||
|
bool movSaudavel = statusValidos.Contains(saude.FirstOrDefault(x => x.modulo == T_Code.Mov)?.status ?? StatusModulo.Desconectado);
|
||||||
|
|
||||||
|
bool reservatorioOk = (op.Sensoriamento?.Atuador?.PercentualReservatorio ?? 0) > VariaveisEquipamento.PercentualReservatorioMinCritio;
|
||||||
|
|
||||||
|
bool bombaOk = dispAtu?.Dados?.BombaPressurizadora?.Inicializado ?? false;
|
||||||
|
|
||||||
|
bool calibragemAtuNecessaria =
|
||||||
|
pControle.PulverizadorAutomatico &&
|
||||||
|
bombaOk &&
|
||||||
|
reservatorioOk &&
|
||||||
|
dispAtu?.Dados != null &&
|
||||||
|
atuSaudavel &&
|
||||||
|
DeveCalibrar(modsCalibragem, T_Code.Atu, forcar);
|
||||||
|
|
||||||
|
bool calibragemDirNecessaria =
|
||||||
|
dispMvd?.Dados != null &&
|
||||||
|
dirSaudavel &&
|
||||||
|
DeveCalibrar(modsCalibragem, T_Code.Dir, forcar);
|
||||||
|
|
||||||
|
bool calibragemMovNecessaria =
|
||||||
|
dispMvd?.Dados != null &&
|
||||||
|
movSaudavel &&
|
||||||
|
DeveCalibrar(modsCalibragem, T_Code.Mov, forcar);
|
||||||
|
|
||||||
|
if (!calibragemAtuNecessaria && !calibragemDirNecessaria && !calibragemMovNecessaria)
|
||||||
|
{
|
||||||
|
if (forcar)
|
||||||
|
{
|
||||||
|
string motivo =
|
||||||
|
$"Calibragem não necessária. " +
|
||||||
|
$"AtuNec={calibragemAtuNecessaria}, DirNec={calibragemDirNecessaria}, MovNec={calibragemMovNecessaria}, " +
|
||||||
|
$"PulvAuto={pControle.PulverizadorAutomatico}, BombaOk={bombaOk}, ReservatorioOk={reservatorioOk}, " +
|
||||||
|
$"AtuSaude={atuSaudavel}, DirSaude={dirSaudavel}, MovSaude={movSaudavel}";
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] " + motivo);
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, motivo);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entrouEmCalibragem = true;
|
||||||
|
|
||||||
|
op.Sensoriamento.Operacao.Calibrando = true;
|
||||||
|
op.Sensoriamento.Operacao.Pausa = true;
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", true));
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Pausa para calibragem inicial");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Pausa para calibragem inicial");
|
||||||
|
|
||||||
|
await Task.Delay(3000);
|
||||||
|
|
||||||
|
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, this))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!op.Sensoriamento.Operacao.OperacaoIniciada)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem dos módulos iniciada...");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Calibragem dos módulos iniciada...");
|
||||||
|
|
||||||
|
var tasks = new List<Task>();
|
||||||
|
|
||||||
|
if (calibragemAtuNecessaria)
|
||||||
|
tasks.AddRange(dispAtu.Dados.RealizarCalibragemAsync(forcar));
|
||||||
|
|
||||||
|
if (calibragemDirNecessaria)
|
||||||
|
tasks.AddRange(dispMvd.Dados.RealizarCalibragemDirAsync(forcar));
|
||||||
|
|
||||||
|
if (calibragemMovNecessaria)
|
||||||
|
tasks.AddRange(dispMvd.Dados.RealizarCalibragemMovAsync(forcar));
|
||||||
|
|
||||||
|
if (tasks.Count == 0)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Nenhuma calibragem foi iniciada.");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, "Nenhuma calibragem foi iniciada.");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var allTasks = Task.WhenAll(tasks);
|
||||||
|
var timeoutTask = Task.Delay(2 * 60 * 1000);
|
||||||
|
|
||||||
|
var completed = await Task.WhenAny(allTasks, timeoutTask);
|
||||||
|
|
||||||
|
if (completed == allTasks)
|
||||||
|
{
|
||||||
|
await allTasks;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Calibragem finalizada.");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Operante, 100, "Calibragem finalizada.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperacaoModel] [RealizarCalibragemInicialAsync] Timeout na calibragem. Liberando operação mesmo assim.");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Alerta, 50, "Timeout na calibragem. Liberando operação mesmo assim.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[OperacaoModel] [RealizarCalibragemInicialAsync] Erro durante calibragem: {ex}");
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(T_Code.Mod, StatusModulo.Falha, 0, $"Erro durante calibragem: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
op.Controle.TipoMovimento = tipoControle;
|
||||||
|
|
||||||
|
if (entrouEmCalibragem)
|
||||||
|
{
|
||||||
|
op.Sensoriamento.Operacao.Calibrando = false;
|
||||||
|
op.Sensoriamento.Operacao.Pausa = false;
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(CtxKey.DadosOperacao, ("calibrando", false));
|
||||||
|
}
|
||||||
|
|
||||||
|
_calibragemGate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void GarantirEstruturaModsCalibragem(OperacaoModel op)
|
||||||
|
{
|
||||||
|
if (op.Sensoriamento.Operacao.ModsCalibragem == null)
|
||||||
|
op.Sensoriamento.Operacao.ModsCalibragem = new Dictionary<T_Code, Dictionary<string, bool>>();
|
||||||
|
|
||||||
|
var mods = op.Sensoriamento.Operacao.ModsCalibragem;
|
||||||
|
|
||||||
|
if (!mods.ContainsKey(T_Code.Mov) || mods[T_Code.Mov] == null)
|
||||||
|
mods[T_Code.Mov] = new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
if (!mods.ContainsKey(T_Code.Dir) || mods[T_Code.Dir] == null)
|
||||||
|
mods[T_Code.Dir] = new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
if (!mods.ContainsKey(T_Code.Atu) || mods[T_Code.Atu] == null)
|
||||||
|
mods[T_Code.Atu] = new Dictionary<string, bool>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DeveCalibrar(Dictionary<T_Code, Dictionary<string, bool>> modsCalibragem, T_Code modulo, bool forcar)
|
||||||
|
{
|
||||||
|
if (!modsCalibragem.ContainsKey(modulo) || modsCalibragem[modulo] == null)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var mods = modsCalibragem[modulo];
|
||||||
|
|
||||||
|
if (!mods.Any())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (forcar)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return mods.Any(x => !x.Value);
|
||||||
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region CONTROLE DA OPERACAO
|
#region CONTROLE DA OPERACAO
|
||||||
|
|
@ -2538,6 +2654,9 @@ namespace AgroBase.Models
|
||||||
bool salvouComSucesso = false;
|
bool salvouComSucesso = false;
|
||||||
DateTime agora = DateTime.Now;
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
DateTime novoCursorEventos = DateTime.Now;
|
||||||
|
List<OperacaoSensoriamentoLogErrosModel> eventosSalvos = new List<OperacaoSensoriamentoLogErrosModel>();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(op.ID))
|
if (string.IsNullOrWhiteSpace(op.ID))
|
||||||
|
|
@ -2602,12 +2721,15 @@ namespace AgroBase.Models
|
||||||
// Eventos discretos de saúde/erro
|
// Eventos discretos de saúde/erro
|
||||||
// Melhor salvar um evento por linha.
|
// Melhor salvar um evento por linha.
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
if (sen.Logs != null && sen.Logs.Any())
|
eventosSalvos =
|
||||||
|
(sen.Logs ?? new List<OperacaoSensoriamentoLogErrosModel>())
|
||||||
|
.Where(x => x != null)
|
||||||
|
.OrderBy(x => x.Momento)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var evento in eventosSalvos)
|
||||||
{
|
{
|
||||||
foreach (var evento in sen.Logs.Where(x => x != null))
|
SalvarLogLine("eventos", evento);
|
||||||
{
|
|
||||||
SalvarLogLine("eventos", evento);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// -----------------------------
|
// -----------------------------
|
||||||
|
|
@ -2638,6 +2760,15 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
op.idxLog++;
|
op.idxLog++;
|
||||||
salvouComSucesso = true;
|
salvouComSucesso = true;
|
||||||
|
|
||||||
|
if (eventosSalvos.Any())
|
||||||
|
{
|
||||||
|
novoCursorEventos = eventosSalvos.Max(x => x.Momento);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
novoCursorEventos = DateTime.Now;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -2649,7 +2780,7 @@ namespace AgroBase.Models
|
||||||
// Assim, se der erro no disco/JSON, você não perde eventos.
|
// Assim, se der erro no disco/JSON, você não perde eventos.
|
||||||
if (salvouComSucesso && op?.Sensoriamento != null)
|
if (salvouComSucesso && op?.Sensoriamento != null)
|
||||||
{
|
{
|
||||||
op.Sensoriamento.UltimoRegistroLog = agora;
|
op.Sensoriamento.UltimoRegistroLog = novoCursorEventos;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
|
|
@ -3323,7 +3454,7 @@ namespace AgroBase.Models
|
||||||
}
|
}
|
||||||
if (mod.saude_individual.Any())
|
if (mod.saude_individual.Any())
|
||||||
{
|
{
|
||||||
foreach (var ind in mod.saude_individual)
|
foreach (var ind in mod.saude_individual.Where(x => x.em_uso))
|
||||||
{
|
{
|
||||||
var ind_ant = saudeAnterior.FirstOrDefault(x => x.modulo == mod.modulo && x.saude_individual.Any(y => y.id == ind.id))?.saude_individual?.FirstOrDefault(x => x.id == ind.id);
|
var ind_ant = saudeAnterior.FirstOrDefault(x => x.modulo == mod.modulo && x.saude_individual.Any(y => y.id == ind.id))?.saude_individual?.FirstOrDefault(x => x.id == ind.id);
|
||||||
if (ind.status != ind_ant?.status)
|
if (ind.status != ind_ant?.status)
|
||||||
|
|
@ -4376,18 +4507,37 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue;
|
public DateTime UltimoRegistroLog { get; set; } = DateTime.MinValue;
|
||||||
|
|
||||||
|
|
||||||
|
private static readonly TimeSpan JanelaDedupeEvento = TimeSpan.FromSeconds(2);
|
||||||
public void InserirLog(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null)
|
public void InserirLog(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null)
|
||||||
{
|
{
|
||||||
if (op == null) return;
|
if (op == null)
|
||||||
|
return;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (Logs == null)
|
if (Logs == null)
|
||||||
Logs = new List<OperacaoSensoriamentoLogErrosModel>();
|
Logs = new List<OperacaoSensoriamentoLogErrosModel>();
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
bool duplicadoRecente = Logs.Any(x =>
|
||||||
|
x != null &&
|
||||||
|
(agora - x.Momento).Duration() <= JanelaDedupeEvento &&
|
||||||
|
x.Dispositivo == dispositivo &&
|
||||||
|
x.ID == id &&
|
||||||
|
x.Status == status &&
|
||||||
|
Math.Abs(x.Saude - saude) < 0.001 &&
|
||||||
|
x.Mensagem == mensagem &&
|
||||||
|
x.Condicoes == condicoes
|
||||||
|
);
|
||||||
|
|
||||||
|
if (duplicadoRecente)
|
||||||
|
return;
|
||||||
|
|
||||||
Logs.Add(new OperacaoSensoriamentoLogErrosModel()
|
Logs.Add(new OperacaoSensoriamentoLogErrosModel()
|
||||||
{
|
{
|
||||||
Momento = DateTime.Now,
|
Momento = agora,
|
||||||
Dispositivo = dispositivo,
|
Dispositivo = dispositivo,
|
||||||
ID = id,
|
ID = id,
|
||||||
Status = status,
|
Status = status,
|
||||||
|
|
@ -4402,11 +4552,13 @@ namespace AgroBase.Models
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void AtualizarDados()
|
public void AtualizarDados()
|
||||||
{
|
{
|
||||||
if (op == null) return;
|
if (op == null) return;
|
||||||
|
|
||||||
DateTime agora = DateTime.Now;
|
DateTime agora = DateTime.Now;
|
||||||
|
DateTime ultimoRegistroLog = UltimoRegistroLog;
|
||||||
|
|
||||||
// Contexto
|
// Contexto
|
||||||
HealthWorkerService.AtualizarDadosContexto();
|
HealthWorkerService.AtualizarDadosContexto();
|
||||||
|
|
@ -4495,7 +4647,8 @@ namespace AgroBase.Models
|
||||||
DadosPerformance = dadosPerformance,
|
DadosPerformance = dadosPerformance,
|
||||||
LivoxLidar = dadosLivox,
|
LivoxLidar = dadosLivox,
|
||||||
CoolerControl = dadosCooler,
|
CoolerControl = dadosCooler,
|
||||||
Logs = Logs != null ? Logs.Where(x => x?.Momento >= UltimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(),
|
Logs = Logs != null ? Logs.Where(x => x?.Momento >= ultimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(),
|
||||||
|
UltimoRegistroLog = ultimoRegistroLog,
|
||||||
};
|
};
|
||||||
|
|
||||||
op.Sensoriamento = dadosAtualizados;
|
op.Sensoriamento = dadosAtualizados;
|
||||||
|
|
|
||||||
|
|
@ -601,7 +601,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
if (op.Sensoriamento.Operacao.Calibrando == true)
|
if (op?.Sensoriamento?.Operacao?.Calibrando == true)
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Durante a calibração, RealizarTestesIniciais()
|
* Durante a calibração, RealizarTestesIniciais()
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ namespace AgroBase.Services.Operadores
|
||||||
DadosLeitura.Iniciado = true;
|
DadosLeitura.Iniciado = true;
|
||||||
|
|
||||||
bool pronto() => DadosLeitura.Pronto;
|
bool pronto() => DadosLeitura.Pronto;
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
|
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
|
||||||
if (pronto())
|
if (pronto())
|
||||||
{
|
{
|
||||||
MostrarLog("Processamento iniciado com sucesso");
|
MostrarLog("Processamento iniciado com sucesso");
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ namespace AgroBase.Services.Operadores
|
||||||
DadosLeitura.Iniciado = true;
|
DadosLeitura.Iniciado = true;
|
||||||
|
|
||||||
bool pronto() => DadosLeitura.Pronto;
|
bool pronto() => DadosLeitura.Pronto;
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
|
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
|
||||||
if (pronto())
|
if (pronto())
|
||||||
{
|
{
|
||||||
MostrarLog("Processamento iniciado com sucesso");
|
MostrarLog("Processamento iniciado com sucesso");
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
using AgroBase.Models;
|
using AgroBase.Models;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static AgroBase.Models.Enums;
|
using static AgroBase.Models.Enums;
|
||||||
using static AgroBase.Models.Operadores.OperadoresModels;
|
using static AgroBase.Models.Operadores.OperadoresModels;
|
||||||
|
|
@ -11,6 +13,9 @@ namespace AgroBase.Services.Operadores
|
||||||
{
|
{
|
||||||
public class OperadoresService
|
public class OperadoresService
|
||||||
{
|
{
|
||||||
|
private readonly object _estadoLock = new object();
|
||||||
|
private readonly SemaphoreSlim _restartGate = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
private Process pythonProcess;
|
private Process pythonProcess;
|
||||||
|
|
||||||
public List<IWorkerModel> Workers = new List<IWorkerModel>()
|
public List<IWorkerModel> Workers = new List<IWorkerModel>()
|
||||||
|
|
@ -22,133 +27,456 @@ namespace AgroBase.Services.Operadores
|
||||||
new WeedWorkerService(),
|
new WeedWorkerService(),
|
||||||
};
|
};
|
||||||
|
|
||||||
private bool iniciarScript = false;
|
private bool iniciarScript = true;
|
||||||
|
private bool workersInicializados = false;
|
||||||
|
private bool encerrando = false;
|
||||||
|
|
||||||
private int TimeoutDesconexao = 3;
|
private int TimeoutDesconexao = 3;
|
||||||
private double TempoDesconectado = 0;
|
private int TempoDesconectado = 0;
|
||||||
|
|
||||||
|
private DateTime ultimoRestart = DateTime.MinValue;
|
||||||
|
private DateTime ultimoLogDesconectado = DateTime.MinValue;
|
||||||
|
|
||||||
|
private readonly TimeSpan IntervaloMinimoRestart = TimeSpan.FromSeconds(10);
|
||||||
|
private readonly TimeSpan IntervaloLogDesconectado = TimeSpan.FromSeconds(5);
|
||||||
|
public readonly int TimeoutIniciarWorkerMs = 10000;
|
||||||
|
|
||||||
private AsyncTaskTimerModel tmrHearthbeat;
|
private AsyncTaskTimerModel tmrHearthbeat;
|
||||||
|
|
||||||
public bool Conectado = false;
|
public bool Conectado = false;
|
||||||
|
public bool TodosWorkersConectados = false;
|
||||||
|
public bool PythonRodando = false;
|
||||||
|
private bool encerrandoPythonIntencionalmente = false;
|
||||||
|
|
||||||
public async Task<bool> IniciarProcessamento(bool _iniciarScript = true)
|
public async Task<bool> IniciarProcessamento(bool _iniciarScript = true)
|
||||||
{
|
{
|
||||||
iniciarScript = _iniciarScript;
|
iniciarScript = _iniciarScript;
|
||||||
|
encerrando = false;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Iniciando núcleo central de processamento...");
|
||||||
|
|
||||||
|
bool iniciou = await IniciarOperadoresAsync("inicio", inicializacaoCompleta: true);
|
||||||
|
|
||||||
tmrHearthbeat?.Dispose();
|
tmrHearthbeat?.Dispose();
|
||||||
tmrHearthbeat = new AsyncTaskTimerModel("tmrHearthbeatOperadores", tmrHearthbeat_Tick, 1000);
|
tmrHearthbeat = new AsyncTaskTimerModel("tmrHearthbeatOperadores", tmrHearthbeat_Tick, 1000);
|
||||||
tmrHearthbeat.Start();
|
tmrHearthbeat.Start();
|
||||||
|
|
||||||
IniciarOperadores();
|
if (iniciou)
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void IniciarOperadores()
|
|
||||||
{
|
|
||||||
foreach (var worker in Workers)
|
|
||||||
{
|
{
|
||||||
worker.IniciarProcessamento();
|
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Núcleo central solicitado com sucesso.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.IniciarProcessamento] Núcleo central iniciou com pendências. Heartbeat tentará recuperar.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (iniciarScript)
|
return iniciou;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IniciarOperadoresAsync(string origem, bool inicializacaoCompleta)
|
||||||
|
{
|
||||||
|
bool entrou = false;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
Encerrar();
|
entrou = await _restartGate.WaitAsync(0);
|
||||||
pythonProcess = PythonService.RunScript(Path.Combine("workers", "main_async.py"), new string[] { });
|
|
||||||
|
if (!entrou)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Restart ignorado, já existe uma inicialização em andamento.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ultimoRestart = DateTime.Now;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.IniciarOperadoresAsync] Iniciando operadores. Origem={origem}, iniciarScript={iniciarScript}");
|
||||||
|
|
||||||
|
List<Task<bool>> tasksWorkers = new List<Task<bool>>();
|
||||||
|
|
||||||
|
if (!workersInicializados || inicializacaoCompleta)
|
||||||
|
{
|
||||||
|
foreach (var worker in Workers)
|
||||||
|
{
|
||||||
|
tasksWorkers.Add(IniciarWorkerSeguroAsync(worker));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iniciarScript)
|
||||||
|
{
|
||||||
|
EncerrarPythonAtual("restart do núcleo");
|
||||||
|
|
||||||
|
pythonProcess = PythonService.RunScript(
|
||||||
|
Path.Combine("workers", "main_async.py"),
|
||||||
|
new string[] { },
|
||||||
|
"workers_main_async"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pythonProcess == null)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Falha ao iniciar processo Python.");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.IniciarOperadoresAsync] Processo Python iniciado. PID={pythonProcess.Id}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool workersOk = true;
|
||||||
|
|
||||||
|
if (tasksWorkers.Any())
|
||||||
|
{
|
||||||
|
bool[] resultados = await Task.WhenAll(tasksWorkers);
|
||||||
|
workersOk = resultados.Any(x => x);
|
||||||
|
|
||||||
|
if (workersOk)
|
||||||
|
workersInicializados = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtualizarEstadoProcessoPython();
|
||||||
|
|
||||||
|
return (!iniciarScript || PythonRodando) && workersOk;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.IniciarOperadoresAsync] Erro ao iniciar operadores: " + ex);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
if (entrou)
|
||||||
|
_restartGate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> IniciarWorkerSeguroAsync(IWorkerModel worker)
|
||||||
|
{
|
||||||
|
if (worker == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string nome = worker.GetType().Name;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Inicializando worker C#: {nome}");
|
||||||
|
|
||||||
|
bool ok = await worker.IniciarProcessamento();
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Worker {nome} inicialização={(ok ? "OK" : "PENDENTE")}");
|
||||||
|
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.IniciarWorkerSeguroAsync] Erro ao iniciar worker {nome}: {ex.Message}");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Encerrar()
|
public bool Encerrar()
|
||||||
{
|
{
|
||||||
if (pythonProcess != null && !pythonProcess.HasExited)
|
encerrando = true;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
pythonProcess.Kill();
|
tmrHearthbeat?.Stop();
|
||||||
}
|
}
|
||||||
pythonProcess = null;
|
catch { }
|
||||||
|
|
||||||
|
EncerrarPythonAtual("encerramento solicitado");
|
||||||
|
|
||||||
|
Conectado = false;
|
||||||
|
TodosWorkersConectados = false;
|
||||||
|
PythonRodando = false;
|
||||||
|
TempoDesconectado = 0;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.Encerrar] Núcleo central encerrado.");
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EncerrarPythonAtual(string motivo = "não informado")
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (pythonProcess != null)
|
||||||
|
{
|
||||||
|
int pid = -1;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
pid = pythonProcess.Id;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService] Encerrando processo Python. PID={pid}, Motivo={motivo}");
|
||||||
|
|
||||||
|
encerrandoPythonIntencionalmente = true;
|
||||||
|
|
||||||
|
PythonService.EncerrarProcesso(pythonProcess, matarArvore: true);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService] Processo Python encerrado intencionalmente. PID={pid}, Motivo={motivo}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService] Erro ao encerrar Python atual: " + ex.Message);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
encerrandoPythonIntencionalmente = false;
|
||||||
|
pythonProcess = null;
|
||||||
|
PythonRodando = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task tmrHearthbeat_Tick()
|
private async Task tmrHearthbeat_Tick()
|
||||||
{
|
{
|
||||||
|
if (encerrando)
|
||||||
|
return;
|
||||||
|
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
Conectado = false;
|
try
|
||||||
foreach (var worker in Workers)
|
|
||||||
{
|
{
|
||||||
worker.AtualizarSaude();
|
AtualizarEstadoProcessoPython();
|
||||||
Conectado |= worker.Saude.conectado;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Conectado)
|
Dictionary<string, bool> statusWorkers = AtualizarSaudeWorkers();
|
||||||
{
|
|
||||||
TempoDesconectado++;
|
bool algumWorkerConectado = statusWorkers.Any(x => x.Value);
|
||||||
if (op.Sensoriamento.Operacao.OperacaoIniciada && (op.Parametros.Controle.MovimentoAutomatico || op.Parametros.Controle.DirecionalAutomatico))
|
bool todosWorkersConectados = statusWorkers.Any() && statusWorkers.All(x => x.Value);
|
||||||
|
|
||||||
|
Conectado = iniciarScript
|
||||||
|
? PythonRodando && algumWorkerConectado
|
||||||
|
: algumWorkerConectado;
|
||||||
|
|
||||||
|
TodosWorkersConectados = todosWorkersConectados;
|
||||||
|
|
||||||
|
bool nucleoCriticoOk = Conectado;
|
||||||
|
|
||||||
|
if (!nucleoCriticoOk)
|
||||||
{
|
{
|
||||||
RedisService.AtualizarCampos(
|
TempoDesconectado++;
|
||||||
CtxKey.DadosOperacao,
|
|
||||||
("status", StatusOperacao.Parado),
|
|
||||||
("liberado", false),
|
|
||||||
("configurado", false)
|
|
||||||
);
|
|
||||||
|
|
||||||
var _Controle = op.Controle;
|
AplicarParadaSeguraSeNecessario(op);
|
||||||
if (op.Parametros.Controle.MovimentoAutomatico)
|
|
||||||
|
RedisService.DefinirEquipamentoDesconectado();
|
||||||
|
|
||||||
|
LogarDiagnosticoDesconectado(statusWorkers);
|
||||||
|
|
||||||
|
if (DeveTentarRestart())
|
||||||
{
|
{
|
||||||
if (_Controle.PercentualVelocidadeSP > 0)
|
AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
|
||||||
{
|
|
||||||
_Controle.PercentualVelocidadeSP = 0;
|
await IniciarOperadoresAsync("heartbeat", inicializacaoCompleta: false);
|
||||||
RedisService.AtualizarCampos(
|
|
||||||
CtxKey.DadosControle,
|
|
||||||
("velocidade_sp", _Controle.PercentualVelocidadeSP)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (op.Parametros.Controle.DirecionalAutomatico)
|
return;
|
||||||
{
|
|
||||||
if (_Controle.Angulo != 0)
|
|
||||||
{
|
|
||||||
_Controle.Angulo = 0;
|
|
||||||
RedisService.AtualizarCampos(
|
|
||||||
CtxKey.DadosControle,
|
|
||||||
("angulo_sp", _Controle.Angulo)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (op.Parametros.Controle.PulverizadorAutomatico)
|
|
||||||
{
|
|
||||||
if (_Controle.Bicos.Any(x => x.ComandoAtuar))
|
|
||||||
{
|
|
||||||
_Controle.Bicos.ForEach(x => x.ComandoAtuar = false);
|
|
||||||
RedisService.AtualizarCampos(
|
|
||||||
CtxKey.DadosControle,
|
|
||||||
("controle_bicos", _Controle.Bicos.ToDictionary(x => x.Posicao - 1, x => x.ComandoAtuar))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
op.AtualizaInformacoesControleOperacao();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
RedisService.DefinirEquipamentoDesconectado();
|
if (TempoDesconectado > 0)
|
||||||
|
|
||||||
// A cada 10 segundos desconectado, tenta reiniciar o nucleo de processamento
|
|
||||||
if (TempoDesconectado > 0 && TempoDesconectado % 10 == 0)
|
|
||||||
{
|
{
|
||||||
IniciarOperadores();
|
Variaveis.MostrarLog("[OperadoresService.tmrHearthbeat_Tick] Núcleo central reconectado.");
|
||||||
AudioAlertaService.Falar("Núcleo central de processamento desconectado, tentando reconectar.");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
TempoDesconectado = 0;
|
TempoDesconectado = 0;
|
||||||
if (!op.Sensoriamento.Operacao.OperacaoConfigurada)
|
|
||||||
|
if (!(op?.Sensoriamento?.Operacao?.OperacaoConfigurada ?? false))
|
||||||
{
|
{
|
||||||
HealthWorkerService.AtualizarDadosOperacao(true);
|
HealthWorkerService.AtualizarDadosOperacao(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.tmrHearthbeat_Tick] Erro no heartbeat: " + ex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AtualizarEstadoProcessoPython()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!iniciarScript)
|
||||||
|
{
|
||||||
|
PythonRodando = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pythonProcess == null)
|
||||||
|
{
|
||||||
|
PythonRodando = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pythonProcess.HasExited)
|
||||||
|
{
|
||||||
|
int exitCode = -999;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
exitCode = pythonProcess.ExitCode;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
PythonRodando = false;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.AtualizarEstadoProcessoPython] Processo Python não está rodando. ExitCode={exitCode}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PythonRodando = true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
PythonRodando = false;
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.AtualizarEstadoProcessoPython] Erro ao consultar processo Python: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Dictionary<string, bool> AtualizarSaudeWorkers()
|
||||||
|
{
|
||||||
|
Dictionary<string, bool> status = new Dictionary<string, bool>();
|
||||||
|
|
||||||
|
foreach (var worker in Workers)
|
||||||
|
{
|
||||||
|
if (worker == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
string nome = worker.GetType().Name;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
worker.AtualizarSaude();
|
||||||
|
|
||||||
|
bool conectado = worker.Saude != null && worker.Saude.conectado;
|
||||||
|
|
||||||
|
status[nome] = conectado;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
status[nome] = false;
|
||||||
|
Variaveis.MostrarLog($"[OperadoresService.AtualizarSaudeWorkers] Erro ao atualizar saúde do worker {nome}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AplicarParadaSeguraSeNecessario(OperacaoModel op)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (op?.Sensoriamento?.Operacao?.OperacaoIniciada != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (op.Parametros?.Controle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool temControleAutomatico =
|
||||||
|
op.Parametros.Controle.MovimentoAutomatico ||
|
||||||
|
op.Parametros.Controle.DirecionalAutomatico ||
|
||||||
|
op.Parametros.Controle.PulverizadorAutomatico;
|
||||||
|
|
||||||
|
if (!temControleAutomatico)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(
|
||||||
|
CtxKey.DadosOperacao,
|
||||||
|
("status", StatusOperacao.Parado),
|
||||||
|
("liberado", false),
|
||||||
|
("configurado", false)
|
||||||
|
);
|
||||||
|
|
||||||
|
var controle = op.Controle;
|
||||||
|
|
||||||
|
if (controle == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (op.Parametros.Controle.MovimentoAutomatico)
|
||||||
|
{
|
||||||
|
if (controle.PercentualVelocidadeSP != 0)
|
||||||
|
{
|
||||||
|
controle.PercentualVelocidadeSP = 0;
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(
|
||||||
|
CtxKey.DadosControle,
|
||||||
|
("velocidade_sp", controle.PercentualVelocidadeSP)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.Parametros.Controle.DirecionalAutomatico)
|
||||||
|
{
|
||||||
|
if (controle.Angulo != 0)
|
||||||
|
{
|
||||||
|
controle.Angulo = 0;
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(
|
||||||
|
CtxKey.DadosControle,
|
||||||
|
("angulo_sp", controle.Angulo)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.Parametros.Controle.PulverizadorAutomatico)
|
||||||
|
{
|
||||||
|
if (controle.Bicos != null && controle.Bicos.Any(x => x.ComandoAtuar))
|
||||||
|
{
|
||||||
|
controle.Bicos.ForEach(x => x.ComandoAtuar = false);
|
||||||
|
|
||||||
|
RedisService.AtualizarCampos(
|
||||||
|
CtxKey.DadosControle,
|
||||||
|
("controle_bicos", controle.Bicos.ToDictionary(x => x.Posicao - 1, x => x.ComandoAtuar))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
op.AtualizaInformacoesControleOperacao();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.AplicarParadaSeguraSeNecessario] Erro ao aplicar parada segura: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool DeveTentarRestart()
|
||||||
|
{
|
||||||
|
if (!iniciarScript)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (TempoDesconectado < TimeoutDesconexao)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if ((DateTime.Now - ultimoRestart) < IntervaloMinimoRestart)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LogarDiagnosticoDesconectado(Dictionary<string, bool> statusWorkers)
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - ultimoLogDesconectado) < IntervaloLogDesconectado)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ultimoLogDesconectado = DateTime.Now;
|
||||||
|
|
||||||
|
string pythonStatus = iniciarScript
|
||||||
|
? $"PythonRodando={PythonRodando}"
|
||||||
|
: "Python gerenciado externamente";
|
||||||
|
|
||||||
|
string workersStatus = string.Join(
|
||||||
|
", ",
|
||||||
|
statusWorkers.Select(x => x.Key + "=" + (x.Value ? "OK" : "OFF"))
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[OperadoresService.LogarDiagnosticoDesconectado] Núcleo desconectado há {TempoDesconectado}s. {pythonStatus}. Workers: {workersStatus}"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (pythonProcess == null && iniciarScript)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[OperadoresService.LogarDiagnosticoDesconectado] pythonProcess está null.");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -39,7 +39,7 @@ namespace AgroBase.Services.Operadores
|
||||||
DadosLeitura.Iniciado = true;
|
DadosLeitura.Iniciado = true;
|
||||||
|
|
||||||
bool pronto() => DadosLeitura.Pronto;
|
bool pronto() => DadosLeitura.Pronto;
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
|
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
|
||||||
if (pronto())
|
if (pronto())
|
||||||
{
|
{
|
||||||
MostrarLog("Processamento iniciado com sucesso");
|
MostrarLog("Processamento iniciado com sucesso");
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ namespace AgroBase.Services.Operadores
|
||||||
DadosLeitura.Iniciado = true;
|
DadosLeitura.Iniciado = true;
|
||||||
|
|
||||||
bool pronto() => DadosLeitura.Pronto;
|
bool pronto() => DadosLeitura.Pronto;
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(pronto, 5000);
|
await FuncoesGlobais.AguardarCondicaoAsync(pronto, VariaveisOperacao.Operadores.TimeoutIniciarWorkerMs);
|
||||||
if (pronto())
|
if (pronto())
|
||||||
{
|
{
|
||||||
MostrarLog("Processamento iniciado com sucesso");
|
MostrarLog("Processamento iniciado com sucesso");
|
||||||
|
|
|
||||||
|
|
@ -4,39 +4,65 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace AgroBase.Services
|
namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
public class PythonService
|
public class PythonService
|
||||||
{
|
{
|
||||||
public static bool DebugMode = false;
|
public static bool DebugMode = false;
|
||||||
public static string PythonExe => Path.Combine(CaminhoGeral, "venv", "Scripts", "python.exe");
|
|
||||||
public static string CaminhoGeral = "Python\\";
|
public static string CaminhoGeral = "Python\\";
|
||||||
public static string CaminhoScripts = "Scripts\\";
|
public static string CaminhoScripts = "Scripts\\";
|
||||||
public static string CaminhoLeitura = "Output\\";
|
public static string CaminhoLeitura = "Output\\";
|
||||||
|
|
||||||
public static string ScriptWeedDetector
|
private static readonly object _lockProcessos = new object();
|
||||||
|
private static readonly object _lockLogs = new object();
|
||||||
|
|
||||||
|
private static readonly List<Process> processosIniciados = new List<Process>();
|
||||||
|
|
||||||
|
public static string BaseDirectory
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal;
|
return AppDomain.CurrentDomain.BaseDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static string ScriptGreenDetector
|
|
||||||
|
public static string CaminhoPythonRoot
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_WeedDetector).NomeArquivoLocal;
|
return ResolverCaminho(CaminhoGeral);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static string ScriptStreetDetector
|
|
||||||
|
public static string PythonExe
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_StreetDetector).NomeArquivoLocal;
|
return Path.Combine(CaminhoPythonRoot, "venv", "Scripts", "python.exe");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static string ScriptsRoot
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(CaminhoPythonRoot, CaminhoScripts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string LogsRoot
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return Path.Combine(CaminhoPythonRoot, "Logs");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static string ScriptMapConverter
|
public static string ScriptMapConverter
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -44,6 +70,7 @@ namespace AgroBase.Services
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapLoad).NomeArquivoLocal;
|
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapLoad).NomeArquivoLocal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ScriptMapFollower
|
public static string ScriptMapFollower
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -51,6 +78,7 @@ namespace AgroBase.Services
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapFollow).NomeArquivoLocal;
|
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_MapFollow).NomeArquivoLocal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string ScriptMapGPS
|
public static string ScriptMapGPS
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -58,13 +86,7 @@ namespace AgroBase.Services
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_GpsViewer).NomeArquivoLocal;
|
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.Script_GpsViewer).NomeArquivoLocal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static string ScriptListOAK
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return VersionamentoService.ArquivoScript(Enums.TipoArquivoVersionado.ScriptListOAKCaneras).NomeArquivoLocal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public static string ScriptMPCController
|
public static string ScriptMPCController
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -73,164 +95,423 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Process RunScript(string script, string[] argumentos)
|
||||||
private static List<Process> processosIniciados = new List<Process>();
|
|
||||||
|
|
||||||
public static Process RunScript(string Script, string[] Argumentos)
|
|
||||||
{
|
{
|
||||||
|
return RunScript(script, argumentos, "python");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Process RunScript(string script, string[] argumentos, string nomeLog)
|
||||||
|
{
|
||||||
|
string logPath = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string ScriptPath = CaminhoGeral + CaminhoScripts + Script;
|
Directory.CreateDirectory(LogsRoot);
|
||||||
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{(arg ?? "").Replace("\\", "/")}\""));
|
|
||||||
|
|
||||||
Process pythonProcess = new Process
|
logPath = CriarCaminhoLog(nomeLog);
|
||||||
|
|
||||||
|
string pythonExe = PythonExe;
|
||||||
|
string scriptPath = ResolverScriptPath(script);
|
||||||
|
|
||||||
|
EscreverLog(logPath, "============================================================");
|
||||||
|
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Iniciando processo Python");
|
||||||
|
EscreverLog(logPath, $"BaseDirectory: {BaseDirectory}");
|
||||||
|
EscreverLog(logPath, $"CaminhoPythonRoot: {CaminhoPythonRoot}");
|
||||||
|
EscreverLog(logPath, $"PythonExe: {pythonExe}");
|
||||||
|
EscreverLog(logPath, $"ScriptPath: {scriptPath}");
|
||||||
|
EscreverLog(logPath, $"WorkingDirectory: {BaseDirectory}");
|
||||||
|
|
||||||
|
if (!File.Exists(pythonExe))
|
||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
string msg = $"Python não encontrado: {pythonExe}";
|
||||||
{
|
EscreverLog(logPath, msg);
|
||||||
FileName = PythonExe,
|
Console.WriteLine(msg);
|
||||||
Arguments = $"{ScriptPath} {Args} --source={Variaveis.NomeAplicacao}",
|
return null;
|
||||||
UseShellExecute = false,
|
|
||||||
RedirectStandardOutput = false,
|
|
||||||
RedirectStandardError = false,
|
|
||||||
CreateNoWindow = true,
|
|
||||||
Verb = Variaveis.NomeAplicacao
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Console.WriteLine($"{pythonProcess.StartInfo.FileName} {pythonProcess.StartInfo.Arguments}");
|
|
||||||
|
|
||||||
//pythonProcess.Exited += PythonProcess_Exited;
|
|
||||||
//pythonProcess.OutputDataReceived += PythonProcess_OutputDataReceived;
|
|
||||||
//pythonProcess.ErrorDataReceived += PythonProcess_ErrorDataReceived;
|
|
||||||
|
|
||||||
pythonProcess.Start();
|
|
||||||
|
|
||||||
//pythonProcess.BeginOutputReadLine();
|
|
||||||
//pythonProcess.BeginErrorReadLine();
|
|
||||||
|
|
||||||
lock (processosIniciados)
|
|
||||||
{
|
|
||||||
processosIniciados.Add(pythonProcess);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pythonProcess;
|
if (!File.Exists(scriptPath))
|
||||||
|
{
|
||||||
|
string msg = $"Script Python não encontrado: {scriptPath}";
|
||||||
|
EscreverLog(logPath, msg);
|
||||||
|
Console.WriteLine(msg);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
string args = MontarArgumentos(scriptPath, argumentos);
|
||||||
|
|
||||||
|
EscreverLog(logPath, $"Arguments: {args}");
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = pythonExe,
|
||||||
|
Arguments = args,
|
||||||
|
WorkingDirectory = BaseDirectory,
|
||||||
|
|
||||||
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
|
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
startInfo.EnvironmentVariables["PYTHONUNBUFFERED"] = "1";
|
||||||
|
startInfo.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
|
||||||
|
startInfo.EnvironmentVariables["AGROBASE_SOURCE"] = Variaveis.NomeAplicacao ?? "";
|
||||||
|
|
||||||
|
string pythonPathAtual = startInfo.EnvironmentVariables["PYTHONPATH"] ?? "";
|
||||||
|
string pythonPathNovo = ScriptsRoot;
|
||||||
|
|
||||||
|
string workersPath = Path.Combine(ScriptsRoot, "workers");
|
||||||
|
if (Directory.Exists(workersPath))
|
||||||
|
pythonPathNovo += Path.PathSeparator + workersPath;
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(pythonPathAtual))
|
||||||
|
pythonPathNovo += Path.PathSeparator + pythonPathAtual;
|
||||||
|
|
||||||
|
startInfo.EnvironmentVariables["PYTHONPATH"] = pythonPathNovo;
|
||||||
|
|
||||||
|
Process processo = new Process
|
||||||
|
{
|
||||||
|
StartInfo = startInfo,
|
||||||
|
EnableRaisingEvents = true
|
||||||
|
};
|
||||||
|
|
||||||
|
processo.OutputDataReceived += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(e.Data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string linha = "[OUT] " + e.Data;
|
||||||
|
EscreverLog(logPath, linha);
|
||||||
|
|
||||||
|
if (DebugMode)
|
||||||
|
Variaveis.MostrarLog($"[PythonService.RunScript] {linha}");
|
||||||
|
};
|
||||||
|
|
||||||
|
processo.ErrorDataReceived += (sender, e) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(e.Data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string linha = "[ERR] " + e.Data;
|
||||||
|
EscreverLog(logPath, linha);
|
||||||
|
Variaveis.MostrarLog($"[PythonService.RunScript] {linha}");
|
||||||
|
};
|
||||||
|
|
||||||
|
processo.Exited += (sender, e) =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int exitCode = -999;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
exitCode = processo.ExitCode;
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Processo Python finalizado. PID={processo.Id}, ExitCode={exitCode}");
|
||||||
|
|
||||||
|
lock (_lockProcessos)
|
||||||
|
{
|
||||||
|
processosIniciados.Remove(processo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
};
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[PythonService.RunScript] {startInfo.FileName} {startInfo.Arguments}");
|
||||||
|
|
||||||
|
bool iniciou = processo.Start();
|
||||||
|
|
||||||
|
if (!iniciou)
|
||||||
|
{
|
||||||
|
EscreverLog(logPath, "Process.Start retornou false.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
processo.BeginOutputReadLine();
|
||||||
|
processo.BeginErrorReadLine();
|
||||||
|
|
||||||
|
lock (_lockProcessos)
|
||||||
|
{
|
||||||
|
processosIniciados.Add(processo);
|
||||||
|
}
|
||||||
|
|
||||||
|
EscreverLog(logPath, $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] Processo iniciado com PID={processo.Id}");
|
||||||
|
|
||||||
|
return processo;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Erro ao iniciar script Python: {ex.Message}");
|
string msg = $"Erro ao iniciar script Python: {ex}";
|
||||||
|
|
||||||
|
Variaveis.MostrarLog($"[PythonService.RunScript] {msg}");
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(LogsRoot);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(logPath))
|
||||||
|
logPath = Path.Combine(LogsRoot, "python_start_error.log");
|
||||||
|
|
||||||
|
EscreverLog(logPath, msg);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<bool> RunScriptWithTimeout(string Script, string[] Argumentos, int timeoutMs)
|
public static async Task<bool> RunScriptWithTimeout(string script, string[] argumentos, int timeoutMs)
|
||||||
{
|
{
|
||||||
var processo = RunScript(Script, Argumentos);
|
Process processo = RunScript(script, argumentos, "python_timeout");
|
||||||
if (processo == null) return false;
|
|
||||||
|
if (processo == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
|
||||||
|
|
||||||
var completed = await Task.Run(() => processo.WaitForExit(timeoutMs));
|
|
||||||
if (!completed)
|
if (!completed)
|
||||||
{
|
{
|
||||||
Console.WriteLine("Script Python excedeu o tempo limite e será encerrado.");
|
Variaveis.MostrarLog("[PythonService.RunScriptWithTimeout] Script Python excedeu o tempo limite e será encerrado.");
|
||||||
processo.Kill();
|
EncerrarProcesso(processo, true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return processo.ExitCode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string RunScriptWithCallback(string Script, string[] Argumentos)
|
public static string RunScriptWithCallback(string script, string[] argumentos)
|
||||||
{
|
{
|
||||||
|
string logPath = null;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string ScriptPath = CaminhoGeral + CaminhoScripts + Script;
|
Directory.CreateDirectory(LogsRoot);
|
||||||
string Args = string.Join(" ", Argumentos.Select(arg => $"\"{arg.Replace("\\", "/")}\""));
|
|
||||||
|
|
||||||
Process pythonProcess = new Process
|
logPath = CriarCaminhoLog("python_callback");
|
||||||
|
|
||||||
|
string pythonExe = PythonExe;
|
||||||
|
string scriptPath = ResolverScriptPath(script);
|
||||||
|
|
||||||
|
if (!File.Exists(pythonExe))
|
||||||
|
return $"Python não encontrado: {pythonExe}";
|
||||||
|
|
||||||
|
if (!File.Exists(scriptPath))
|
||||||
|
return $"Script Python não encontrado: {scriptPath}";
|
||||||
|
|
||||||
|
string args = MontarArgumentos(scriptPath, argumentos);
|
||||||
|
|
||||||
|
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||||
{
|
{
|
||||||
StartInfo = new ProcessStartInfo
|
FileName = pythonExe,
|
||||||
{
|
Arguments = args,
|
||||||
FileName = PythonExe,
|
WorkingDirectory = BaseDirectory,
|
||||||
Arguments = $"{ScriptPath} {Args}",
|
|
||||||
UseShellExecute = false,
|
StandardOutputEncoding = Encoding.UTF8,
|
||||||
RedirectStandardOutput = true,
|
StandardErrorEncoding = Encoding.UTF8,
|
||||||
RedirectStandardError = true,
|
|
||||||
CreateNoWindow = true,
|
UseShellExecute = false,
|
||||||
Verb = Variaveis.NomeAplicacao
|
RedirectStandardOutput = true,
|
||||||
}
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
};
|
};
|
||||||
|
|
||||||
Console.WriteLine($"{pythonProcess.StartInfo.FileName} {pythonProcess.StartInfo.Arguments}");
|
startInfo.EnvironmentVariables["PYTHONUNBUFFERED"] = "1";
|
||||||
|
startInfo.EnvironmentVariables["PYTHONIOENCODING"] = "utf-8";
|
||||||
|
|
||||||
pythonProcess.Start();
|
using (Process processo = new Process())
|
||||||
|
{
|
||||||
|
processo.StartInfo = startInfo;
|
||||||
|
|
||||||
string output = pythonProcess.StandardOutput.ReadToEnd();
|
Variaveis.MostrarLog($"[PythonService.RunScriptWithTimeout] {startInfo.FileName} {startInfo.Arguments}");
|
||||||
pythonProcess.WaitForExit();
|
|
||||||
|
|
||||||
return output;
|
processo.Start();
|
||||||
|
|
||||||
|
string output = processo.StandardOutput.ReadToEnd();
|
||||||
|
string error = processo.StandardError.ReadToEnd();
|
||||||
|
|
||||||
|
processo.WaitForExit();
|
||||||
|
|
||||||
|
string resultado =
|
||||||
|
output +
|
||||||
|
Environment.NewLine +
|
||||||
|
error +
|
||||||
|
Environment.NewLine +
|
||||||
|
$"ExitCode={processo.ExitCode}";
|
||||||
|
|
||||||
|
EscreverLog(logPath, resultado);
|
||||||
|
|
||||||
|
return resultado;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Erro ao iniciar script Python: {ex.Message}");
|
string msg = $"Erro ao iniciar script Python: {ex}";
|
||||||
return null;
|
Variaveis.MostrarLog($"[PythonService.RunScriptWithTimeout] {msg}");
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(logPath))
|
||||||
|
EscreverLog(logPath, msg);
|
||||||
|
|
||||||
|
return msg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PythonProcess_ErrorDataReceived(object sender, DataReceivedEventArgs e)
|
public static bool EncerrarProcesso(Process processo, bool matarArvore)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(e.Data))
|
if (processo == null)
|
||||||
{
|
return true;
|
||||||
Console.WriteLine($"Erro recebidos pelo Python: {e.Data}");
|
|
||||||
// Log adicional ou notificação pode ser adicionado aqui
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void PythonProcess_OutputDataReceived(object sender, DataReceivedEventArgs e)
|
try
|
||||||
{
|
|
||||||
if (DebugMode)
|
|
||||||
{
|
{
|
||||||
Console.WriteLine("Dados recebidos pelo Python: " + e.Data);
|
if (processo.HasExited)
|
||||||
}
|
return true;
|
||||||
}
|
|
||||||
|
|
||||||
private static void PythonProcess_Exited(object sender, EventArgs e)
|
int pid = processo.Id;
|
||||||
{
|
|
||||||
if (DebugMode)
|
|
||||||
{
|
|
||||||
Console.WriteLine("Processo Python finalizado");
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (processosIniciados)
|
if (matarArvore)
|
||||||
|
{
|
||||||
|
ProcessStartInfo psi = new ProcessStartInfo
|
||||||
|
{
|
||||||
|
FileName = "taskkill",
|
||||||
|
Arguments = $"/PID {pid} /T /F",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardError = true,
|
||||||
|
CreateNoWindow = true
|
||||||
|
};
|
||||||
|
|
||||||
|
using (Process killer = Process.Start(psi))
|
||||||
|
{
|
||||||
|
if (killer != null)
|
||||||
|
killer.WaitForExit(5000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processo.Kill();
|
||||||
|
processo.WaitForExit(5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
var processo = (Process)sender;
|
Variaveis.MostrarLog($"[PythonService.EncerrarProcesso] Erro ao encerrar processo Python: {ex.Message}");
|
||||||
processosIniciados.Remove(processo);
|
return false;
|
||||||
processo.Dispose();
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
lock (_lockProcessos)
|
||||||
|
{
|
||||||
|
processosIniciados.Remove(processo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void EncerrarProcessos(int processId = -1)
|
public static void EncerrarProcessos(int processId = -1)
|
||||||
{
|
{
|
||||||
return;
|
List<Process> processos;
|
||||||
foreach (var processo in Process.GetProcessesByName("python"))
|
|
||||||
|
lock (_lockProcessos)
|
||||||
|
{
|
||||||
|
processos = processosIniciados
|
||||||
|
.Where(x => x != null)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Process processo in processos)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Verifica os argumentos do processo
|
if (processId > -1 && processo.Id != processId)
|
||||||
string argumentos = processo.StartInfo.Arguments;
|
continue;
|
||||||
if ((processId > -1 && processo.Id == processId))
|
|
||||||
{
|
EncerrarProcesso(processo, true);
|
||||||
processo.Kill();
|
|
||||||
}
|
|
||||||
else if (processId == -1)
|
|
||||||
{
|
|
||||||
processo.Kill();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Erro ao encerrar processo: {ex.Message}");
|
Variaveis.MostrarLog($"[PythonService.EncerrarProcessos] Erro ao encerrar processo controlado: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string ResolverCaminho(string caminho)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(caminho))
|
||||||
|
return BaseDirectory;
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(caminho))
|
||||||
|
return Path.GetFullPath(caminho);
|
||||||
|
|
||||||
|
return Path.GetFullPath(Path.Combine(BaseDirectory, caminho));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ResolverScriptPath(string script)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(script))
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (Path.IsPathRooted(script))
|
||||||
|
return Path.GetFullPath(script);
|
||||||
|
|
||||||
|
return Path.GetFullPath(Path.Combine(ScriptsRoot, script));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string MontarArgumentos(string scriptPath, string[] argumentos)
|
||||||
|
{
|
||||||
|
List<string> args = new List<string>();
|
||||||
|
|
||||||
|
args.Add(QuoteArg(scriptPath));
|
||||||
|
|
||||||
|
if (argumentos != null)
|
||||||
|
{
|
||||||
|
foreach (string arg in argumentos)
|
||||||
|
args.Add(QuoteArg((arg ?? "").Replace("\\", "/")));
|
||||||
|
}
|
||||||
|
|
||||||
|
args.Add("--source=" + QuoteArg(Variaveis.NomeAplicacao ?? ""));
|
||||||
|
|
||||||
|
return string.Join(" ", args);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string QuoteArg(string value)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
value = "";
|
||||||
|
|
||||||
|
value = value.Replace("\"", "\\\"");
|
||||||
|
|
||||||
|
return "\"" + value + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string CriarCaminhoLog(string nomeLog)
|
||||||
|
{
|
||||||
|
string safeName = string.IsNullOrWhiteSpace(nomeLog)
|
||||||
|
? "python"
|
||||||
|
: nomeLog.Replace(" ", "_").Replace("/", "_").Replace("\\", "_");
|
||||||
|
|
||||||
|
string nomeArquivo = safeName + "_" + DateTime.Now.ToString("yyyyMMdd_HHmmss") + ".log";
|
||||||
|
|
||||||
|
return Path.Combine(LogsRoot, nomeArquivo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EscreverLog(string logPath, string texto)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(logPath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
lock (_lockLogs)
|
||||||
|
{
|
||||||
|
File.AppendAllText(logPath, texto + Environment.NewLine, Encoding.UTF8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,12 @@
|
||||||
import sys
|
import sys
|
||||||
import io
|
import io
|
||||||
|
import os
|
||||||
import time
|
import time
|
||||||
|
import signal
|
||||||
|
import traceback
|
||||||
|
import multiprocessing
|
||||||
from multiprocessing import Process
|
from multiprocessing import Process
|
||||||
|
|
||||||
from manager_worker.main import main as iniciar_manager_worker
|
from manager_worker.main import main as iniciar_manager_worker
|
||||||
from health_worker.main import main as iniciar_health_worker
|
from health_worker.main import main as iniciar_health_worker
|
||||||
from visual_worker.main import main as iniciar_visual_worker
|
from visual_worker.main import main as iniciar_visual_worker
|
||||||
|
|
@ -9,12 +14,41 @@ from camera_worker.main import main as iniciar_camera_worker
|
||||||
from weed_worker.main import main as iniciar_weed_worker
|
from weed_worker.main import main as iniciar_weed_worker
|
||||||
from shared.contexto_global_redis import ContextoGlobalRedis
|
from shared.contexto_global_redis import ContextoGlobalRedis
|
||||||
|
|
||||||
#sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
|
|
||||||
|
|
||||||
def main():
|
SHUTDOWN = False
|
||||||
ContextoGlobalRedis.reset_operacao()
|
|
||||||
|
|
||||||
processos = [
|
|
||||||
|
def configurar_console():
|
||||||
|
"""
|
||||||
|
Garante saída UTF-8 quando o processo é iniciado pelo C# com stdout/stderr redirecionados.
|
||||||
|
Evita problemas com emojis/logs em Windows.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if hasattr(sys.stdout, "reconfigure"):
|
||||||
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
|
||||||
|
|
||||||
|
if hasattr(sys.stderr, "reconfigure"):
|
||||||
|
sys.stderr.reconfigure(encoding="utf-8", errors="replace", line_buffering=True)
|
||||||
|
except Exception:
|
||||||
|
try:
|
||||||
|
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
||||||
|
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace", line_buffering=True)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(msg, flush=True)
|
||||||
|
|
||||||
|
|
||||||
|
def tratar_sinal(sig, frame):
|
||||||
|
global SHUTDOWN
|
||||||
|
SHUTDOWN = True
|
||||||
|
log(f"⛔ Sinal recebido: {sig}. Encerrando núcleo de workers...")
|
||||||
|
|
||||||
|
|
||||||
|
def criar_processos():
|
||||||
|
return [
|
||||||
Process(target=iniciar_manager_worker, name="ManagerWorker"),
|
Process(target=iniciar_manager_worker, name="ManagerWorker"),
|
||||||
Process(target=iniciar_health_worker, name="HealthWorker"),
|
Process(target=iniciar_health_worker, name="HealthWorker"),
|
||||||
Process(target=iniciar_visual_worker, name="VisualWorker"),
|
Process(target=iniciar_visual_worker, name="VisualWorker"),
|
||||||
|
|
@ -22,20 +56,118 @@ def main():
|
||||||
Process(target=iniciar_weed_worker, name="WeedWorker"),
|
Process(target=iniciar_weed_worker, name="WeedWorker"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def iniciar_processos(processos):
|
||||||
for p in processos:
|
for p in processos:
|
||||||
p.daemon = True
|
# Não usar daemon em campo.
|
||||||
|
# Se algum worker precisar criar subprocessos, daemon=True pode quebrar.
|
||||||
|
p.daemon = False
|
||||||
p.start()
|
p.start()
|
||||||
print(f"✅ Processo '{p.name}' iniciado com PID {p.pid}")
|
log(f"✅ Processo '{p.name}' iniciado com PID {p.pid}")
|
||||||
|
|
||||||
|
|
||||||
|
def encerrar_processos(processos, motivo="encerramento solicitado"):
|
||||||
|
log(f"⛔ Encerrando processos filhos: {motivo}")
|
||||||
|
|
||||||
|
for p in processos:
|
||||||
|
try:
|
||||||
|
if p.is_alive():
|
||||||
|
log(f"⛔ Terminando '{p.name}' PID={p.pid}")
|
||||||
|
p.terminate()
|
||||||
|
except Exception as ex:
|
||||||
|
log(f"⚠️ Erro ao terminar '{p.name}': {ex}")
|
||||||
|
|
||||||
|
limite_s = 8
|
||||||
|
inicio = time.time()
|
||||||
|
|
||||||
|
while time.time() - inicio < limite_s:
|
||||||
|
vivos = [p for p in processos if p.is_alive()]
|
||||||
|
if not vivos:
|
||||||
|
break
|
||||||
|
time.sleep(0.2)
|
||||||
|
|
||||||
|
for p in processos:
|
||||||
|
try:
|
||||||
|
if p.is_alive():
|
||||||
|
log(f"🧨 Matando processo resistente '{p.name}' PID={p.pid}")
|
||||||
|
p.kill()
|
||||||
|
except Exception as ex:
|
||||||
|
log(f"⚠️ Erro ao matar '{p.name}': {ex}")
|
||||||
|
|
||||||
|
for p in processos:
|
||||||
|
try:
|
||||||
|
p.join(timeout=2)
|
||||||
|
log(f"✅ Processo '{p.name}' encerrado. ExitCode={p.exitcode}")
|
||||||
|
except Exception as ex:
|
||||||
|
log(f"⚠️ Erro ao aguardar '{p.name}': {ex}")
|
||||||
|
|
||||||
|
|
||||||
|
def monitorar_processos(processos):
|
||||||
|
"""
|
||||||
|
Mantém o processo pai vivo e supervisiona os filhos.
|
||||||
|
Se algum worker morrer sozinho, finaliza todos e sai com erro.
|
||||||
|
O C# deve reiniciar o núcleo.
|
||||||
|
"""
|
||||||
|
ultimo_status = 0
|
||||||
|
|
||||||
|
while not SHUTDOWN:
|
||||||
|
mortos = [p for p in processos if p.exitcode is not None]
|
||||||
|
|
||||||
|
if mortos:
|
||||||
|
for p in mortos:
|
||||||
|
log(f"❌ Worker morreu: {p.name}, PID={p.pid}, ExitCode={p.exitcode}")
|
||||||
|
|
||||||
|
encerrar_processos(processos, motivo="worker morreu")
|
||||||
|
return 2
|
||||||
|
|
||||||
|
agora = time.time()
|
||||||
|
if agora - ultimo_status >= 10:
|
||||||
|
status = ", ".join([f"{p.name}=PID:{p.pid}/alive:{p.is_alive()}" for p in processos])
|
||||||
|
log(f"💓 Supervisor ativo. {status}")
|
||||||
|
ultimo_status = agora
|
||||||
|
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
encerrar_processos(processos, motivo="shutdown solicitado")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
configurar_console()
|
||||||
|
|
||||||
|
signal.signal(signal.SIGINT, tratar_sinal)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
while True:
|
signal.signal(signal.SIGTERM, tratar_sinal)
|
||||||
time.sleep(1)
|
except Exception:
|
||||||
except KeyboardInterrupt:
|
pass
|
||||||
print("⛔ Encerrando processos...")
|
|
||||||
for p in processos:
|
log("🚀 Iniciando supervisor dos workers Python...")
|
||||||
p.terminate()
|
log(f"Python exe: {sys.executable}")
|
||||||
p.join()
|
log(f"CWD: {os.getcwd()}")
|
||||||
print("✅ Todos os processos encerrados.")
|
log(f"Args: {sys.argv}")
|
||||||
|
|
||||||
|
ContextoGlobalRedis.reset_operacao()
|
||||||
|
|
||||||
|
processos = criar_processos()
|
||||||
|
iniciar_processos(processos)
|
||||||
|
|
||||||
|
exit_code = monitorar_processos(processos)
|
||||||
|
|
||||||
|
log(f"✅ Supervisor finalizado. ExitCode={exit_code}")
|
||||||
|
return exit_code
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
multiprocessing.freeze_support()
|
||||||
|
|
||||||
|
try:
|
||||||
|
codigo = main()
|
||||||
|
sys.exit(codigo)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
log("⛔ KeyboardInterrupt no processo pai.")
|
||||||
|
sys.exit(0)
|
||||||
|
except Exception:
|
||||||
|
log("💥 ERRO FATAL NO PROCESSO PAI main_async.py")
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue