agrobot_base/AgroBase/AgroBase/Forms/frmInstancial.cs

408 lines
12 KiB
C#

using AgroBase.Forms.IHM;
using AgroBase.Models;
using AgroBase.Services;
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AgroBase.Forms
{
public partial class frmInstancial : Form
{
public static AsyncTaskTimerModel tmrVarreduraDispositivos;
public static frmPrincipal frmPrincipal = new frmPrincipal();
public static frmJoystick frmJoystick = new frmJoystick();
public static frmIHM frmIHM = new frmIHM()
{
FormBorderStyle = FormBorderStyle.None,
//WindowState = FormWindowState.Maximized,
TopMost = true
};
private static readonly SemaphoreSlim _startupGate = new SemaphoreSlim(1, 1);
private static readonly SemaphoreSlim _shutdownGate = new SemaphoreSlim(1, 1);
private static CancellationTokenSource _appCts;
private static bool _startupConcluido;
private static bool _shutdownIniciado;
private bool _allowClose;
public frmInstancial()
{
InitializeComponent();
/*
* O construtor precisa ser leve.
* Nada de abrir MQTT, Redis, Python, CAN, Livox ou operadores aqui.
* 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);
}
private async void frmInstancial_Load(object sender, EventArgs e)
{
try
{
await InicializarServicosAsync();
}
catch (Exception 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);
}
catch { }
}
}
private void frmInstancial_Shown(object sender, EventArgs e)
{
try
{
if (Variaveis.UsarIHM)
{
frmIHM.FormClosing -= frmPrincipal_FormClosing;
frmIHM.FormClosing += frmPrincipal_FormClosing;
frmIHM.Show();
}
else
{
frmIHM.FormClosing -= FrmIHM_FormClosing;
frmPrincipal.FormClosing -= frmPrincipal_FormClosing;
frmIHM.FormClosing += FrmIHM_FormClosing;
frmPrincipal.FormClosing += frmPrincipal_FormClosing;
frmPrincipal.Show();
}
Hide();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Shown] " + ex);
}
}
private static async Task InicializarServicosAsync()
{
await _startupGate.WaitAsync().ConfigureAwait(false);
try
{
if (_startupConcluido)
return;
Variaveis.OperacaoEmAndamento = OperacaoModel.CarregarParametrosOperacaoPadrao(Enums.ModoOperacao.NaoDefinido, true);
_shutdownIniciado = false;
Variaveis.Fechando = false;
_appCts?.Dispose();
_appCts = new CancellationTokenSource();
//FuncoesGlobais.AddApplicationToStartup();
/*
* Ordem pensada para campo:
* 1. limpar processos externos velhos;
* 2. subir infraestrutura local;
* 3. subir comunicação MQTT/UDP;
* 4. iniciar rotinas e operadores;
* 5. iniciar varredura física.
*/
PythonService.EncerrarProcessos();
RedisService.Iniciar();
RedisService.LimparDadosIniciais();
GeneralJoystick.IniciarRotinas();
await Variaveis.IniciarMqttAsync(_appCts.Token).ConfigureAwait(false);
/*
* Hoje pode ficar comentado se UDP não for usado no rover,
* mas o método fica aqui no lugar certo do lifecycle.
*/
//Variaveis.IniciarUDP();
await APIService.IniciarRotinasAsync();
VersionamentoService.IniciarRotinas();
//LivoxManagerProcess.Start();
await VariaveisOperacao.Operadores.IniciarProcessamento(Variaveis.IniciarWorkers).ConfigureAwait(false);
AudioAlertaService.SelecionarVoz(VariaveisEquipamento.VozAlerta);
IniciarTimerVarreduraDispositivos();
_startupConcluido = true;
Variaveis.MostrarLog("[frmInstancial] Inicialização concluída.");
}
catch
{
await EncerrarProcessosInternoAsync(sairProcesso: false).ConfigureAwait(false);
throw;
}
finally
{
_startupGate.Release();
}
}
private static void IniciarTimerVarreduraDispositivos()
{
if (tmrVarreduraDispositivos != null)
return;
tmrVarreduraDispositivos =
new AsyncTaskTimerModel(
"tmrVarreduraDispositivos",
tmrVarreduraDispositivos_Tick,
SerialService.InteraloVerificacao,
null,
15000
);
tmrVarreduraDispositivos.Start();
}
private static async Task tmrVarreduraDispositivos_Tick()
{
if (Variaveis.Fechando)
return;
await SerialService.RealizarVarreduraPortasUSB().ConfigureAwait(false);
}
public static Task EncerrarProcessos()
{
return EncerrarProcessos(sairProcesso: false);
}
public static async Task EncerrarProcessos(bool sairProcesso)
{
await _shutdownGate.WaitAsync().ConfigureAwait(false);
try
{
if (_shutdownIniciado)
return;
_shutdownIniciado = true;
await EncerrarProcessosInternoAsync(sairProcesso).ConfigureAwait(false);
}
finally
{
_shutdownGate.Release();
}
}
private static async Task EncerrarProcessosInternoAsync(bool sairProcesso)
{
Variaveis.Fechando = true;
try { _appCts?.Cancel(); } catch { }
/*
* Primeiro paramos timers para nenhuma rotina reentrar
* enquanto desmontamos serial, CAN, MQTT e processos.
*/
try
{
await AsyncTaskTimerModel.PararTimersAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao parar timers: " + ex.Message);
}
tmrVarreduraDispositivos = null;
try
{
await SerialService.EncerrarAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar SerialService: " + ex.Message);
}
/*
* Tenta avisar dispositivos antes de fechar CAN/MQTT/processos.
*/
try
{
foreach (var disp in Variaveis.DispositivosConectados)
{
try
{
disp.EnviarProtocolosConfiguracao(false);
disp.SalvarParametros(disp.Dados);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao salvar " + disp.Dispositivo + ": " + ex.Message);
}
}
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao percorrer dispositivos: " + ex.Message);
}
/*
* Dá uma janela curta para a fila CAN drenar.
* Usa DateTime aqui só por compatibilidade com o padrão atual,
* mas sem travar indefinidamente.
*/
try
{
var timeout = DateTime.Now.AddSeconds(10);
while (!CanManager.CanService.FilaLiberada && DateTime.Now < timeout)
{
await Task.Delay(200).ConfigureAwait(false);
}
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro aguardando fila CAN: " + ex.Message);
}
try
{
LivoxManagerProcess.Dispose();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar Livox: " + ex.Message);
}
try
{
PythonService.EncerrarProcessos();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar Python: " + ex.Message);
}
try
{
Variaveis.StopUdpChannel();
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar UDP: " + ex.Message);
}
try
{
await GPSService.EncerrarAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.Encerrar] Erro ao encerrar GPS: " + ex.Message);
}
try
{
await Variaveis.EncerrarMqttAsync().ConfigureAwait(false);
}
catch (Exception ex)
{
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();
_appCts = null;
}
catch { }
_startupConcluido = false;
if (sairProcesso)
{
try
{
Environment.Exit(0);
}
catch { }
}
}
private async void frmPrincipal_FormClosing(object sender, FormClosingEventArgs e)
{
if (_allowClose)
return;
e.Cancel = true;
try
{
await EncerrarProcessos(sairProcesso: false);
_allowClose = true;
BeginInvoke(
new Action(() =>
{
try
{
Close();
}
catch { }
})
);
}
catch (Exception ex)
{
Variaveis.MostrarLog("[frmInstancial.FormClosing] " + ex);
try
{
Environment.Exit(Variaveis.ExitShutdownCode);
}
catch { }
}
}
private void FrmIHM_FormClosing(object sender, FormClosingEventArgs e)
{
/*
* Em modo desenvolvimento, fechar a IHM apenas oculta a janela.
* Em produção, o evento de fechamento da IHM é associado ao
* frmPrincipal_FormClosing e faz shutdown real.
*/
//e.Cancel = true;
//frmIHM.Hide();
}
}
}