agrobot_base/AgroBase/AgroBase/Forms/IHM/frmIHM.cs

891 lines
29 KiB
C#

using AgroBase.Forms.IHM.Controls;
using AgroBase.Models;
using AgroBase.Models.Operadores;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace AgroBase.Forms.IHM
{
public partial class frmIHM : Form
{
private AsyncTaskTimerModel tmrDispositivos;
private Form _telaAtual;
public frmMenu frmMenu = new frmMenu();
private frmAjustes _frmAjustes;
private frmDiagnosticos _frmDiagnosticos;
private frmOperacao _frmOperacao;
//private frmTestes _frmTestes;
private StatusOperacaoControl cardBateria;
private StatusOperacaoControl cardReservatorio;
private StatusOperacaoControl cardTemperatura;
private StatusOperacaoControl cardRede;
private StatusOperacaoControl cardGnss;
private StatusOperacaoControl cardBase;
public event EventHandler PausarOperacaoSolicitado;
public event EventHandler ParadaEmergenciaSolicitada;
public event EventHandler FinalizarOperacaoSolicitado;
public frmIHM()
{
InitializeComponent();
VincularNavegacaoMenu();
CriarCardsResumo();
VincularAcoesLaterais();
}
private void VincularNavegacaoMenu()
{
frmMenu.TelaSolicitada += FrmMenu_TelaSolicitada;
}
private void TelaFilha_VoltarSolicitado(object sender, EventArgs e)
{
AbrirTela(frmMenu);
}
private void FrmMenu_TelaSolicitada(object sender, TelaSolicitadaEventArgs e)
{
switch (e.Tela)
{
case TipoTelaIHM.Operacao:
if (_frmOperacao == null || _frmOperacao.IsDisposed)
{
_frmOperacao = new frmOperacao();
_frmOperacao.VoltarSolicitado += TelaFilha_VoltarSolicitado;
}
AbrirTela(_frmOperacao);
break;
case TipoTelaIHM.Ajustes:
if (_frmAjustes == null || _frmAjustes.IsDisposed)
{
_frmAjustes = new frmAjustes();
_frmAjustes.VoltarSolicitado += TelaFilha_VoltarSolicitado;
}
AbrirTela(_frmAjustes);
break;
case TipoTelaIHM.Diagnostico:
if (_frmDiagnosticos == null || _frmDiagnosticos.IsDisposed)
{
_frmDiagnosticos = new frmDiagnosticos();
_frmDiagnosticos.VoltarSolicitado += TelaFilha_VoltarSolicitado;
}
AbrirTela(_frmDiagnosticos);
break;
case TipoTelaIHM.Testes:
//AbrirTestes();
break;
case TipoTelaIHM.Menu:
default:
AbrirTela(frmMenu);
break;
}
}
private void CriarCardsResumo()
{
cardBateria = CriarCard(
"cardBateria",
"Bateria",
"47%",
HmiTheme.Success,
Properties.Resources.bateria);
cardReservatorio = CriarCard(
"cardReservatorio",
"Reservatório",
"23%",
HmiTheme.Info,
Properties.Resources.reservatorio);
cardTemperatura = CriarCard(
"cardTemperatura",
"Temperatura",
"32°C",
HmiTheme.Warning,
Properties.Resources.temperatura);
cardRede = CriarCard(
"cardRede",
"Rede",
"100%",
HmiTheme.Success,
Properties.Resources.rede1);
cardGnss = CriarCard(
"cardGnss",
"GNSS",
"RTK Fixo 0,25 cm",
HmiTheme.Info,
Properties.Resources.gnss);
cardBase = CriarCard(
"cardBase",
"Base",
"264,7 m",
HmiTheme.Manual,
Properties.Resources._base);
fpnlResumo.SuspendLayout();
try
{
fpnlResumo.Controls.Clear();
fpnlResumo.Controls.Add(cardBateria);
fpnlResumo.Controls.Add(cardReservatorio);
fpnlResumo.Controls.Add(cardTemperatura);
fpnlResumo.Controls.Add(cardRede);
fpnlResumo.Controls.Add(cardGnss);
fpnlResumo.Controls.Add(cardBase);
}
finally
{
fpnlResumo.ResumeLayout(true);
}
AjustarLarguraCards();
}
private StatusOperacaoControl CriarCard(string nome, string titulo, string valor, Color cor, Image icon)
{
return new StatusOperacaoControl
{
Name = nome,
Titulo = titulo,
Valor = valor,
CorValor = cor,
CorDestaque = cor,
CorFundo = HmiTheme.SurfaceRaised,
CorBorda = HmiTheme.BorderSoft,
CorTitulo = HmiTheme.Text,
Icone = icon,
Height = 42,
Margin = new Padding(0, 0, 0, 5)
};
}
private void VincularAcoesLaterais()
{
btnPausarOperacao.Click += delegate
{
if (PausarOperacaoSolicitado != null)
PausarOperacaoSolicitado(this, EventArgs.Empty);
};
btnParadaEmergencia.Click += delegate
{
if (ParadaEmergenciaSolicitada != null)
ParadaEmergenciaSolicitada(this, EventArgs.Empty);
};
btnFinalizarOperacao.Click += delegate
{
if (FinalizarOperacaoSolicitado != null)
FinalizarOperacaoSolicitado(this, EventArgs.Empty);
};
}
private void frmIHM_Load(object sender, EventArgs e)
{
lblVersao.Text = "IHM Rover • v " + Variaveis.Versao;
AbrirTela(frmMenu);
if (tmrDispositivos != null)
tmrDispositivos.Dispose();
tmrDispositivos = new AsyncTaskTimerModel("tmrDispositivos", tmrDispositivos_Tick, 1000, this);
tmrDispositivos.Start();
}
private void frmIHM_FormClosed(object sender, FormClosedEventArgs e)
{
if (tmrDispositivos == null)
return;
tmrDispositivos.Dispose();
tmrDispositivos = null;
}
private Task tmrDispositivos_Tick()
{
try
{
if (IsDisposed || Disposing || !IsHandleCreated)
return Task.CompletedTask;
if (InvokeRequired)
{
BeginInvoke(new Action(AtualizarControlesUI));
}
else
{
AtualizarControlesUI();
}
}
catch (Exception ex)
{
Variaveis.MostrarLog("Erro no tmrDispositivos_Tick: " + ex.Message);
}
return Task.CompletedTask;
}
public void AtualizarControlesUI()
{
var operacao = Variaveis.OperacaoEmAndamento;
var dadosOperacao = operacao?.Sensoriamento?.Operacao;
var saudeModulos = operacao?.Sensoriamento?.OperadorSaude?.ModulosSaude;
Enums.StatusOperacao statusOperacao = dadosOperacao?.StatusOperacaoAtual ?? Enums.StatusOperacao.NaoIniciado;
AtualizarStatusOperacao(statusOperacao, dadosOperacao?.Pausa ?? false, dadosOperacao?.Emergencia ?? false);
// =========================================================
// BATERIA
// =========================================================
double bateria = operacao?.Sensoriamento?.Bateria?.PercentualBateria ?? 0;
double bateriaSaude = ObterSaudeModulo(saudeModulos, Enums.T_Code.Bat);
Color corBateria = CorPorValorESaude(bateria, bateriaSaude);
AtualizarBateria($"{Limitar(bateria, 0, 100):0}%", corBateria);
// =========================================================
// RESERVATÓRIO
// =========================================================
double reservatorio = operacao?.Sensoriamento?.Atuador?.PercentualReservatorio ?? 0;
double atuadorSaude = ObterSaudeModulo(saudeModulos, Enums.T_Code.Atu);
Color corReservatorio = CorPorValorESaude(reservatorio, atuadorSaude);
AtualizarReservatorio($"{Limitar(reservatorio, 0, 100):0}%", corReservatorio);
// =========================================================
// TEMPERATURA
// =========================================================
double temperatura = operacao?.Sensoriamento?.CoolerControl?.Temperatura ?? 0;
double temperaturaSaude = ObterSaudeModulo(saudeModulos, Enums.T_Code.Npc);
Color corTemperatura = CorTemperatura(temperatura, temperaturaSaude);
AtualizarTemperatura($"{temperatura:0.#} °C", corTemperatura);
// =========================================================
// REDE / IPB
// =========================================================
double redeSaude = ObterSaudeModulo(saudeModulos, Enums.T_Code.Ipb);
Color corRede = CorPorPercentual(redeSaude);
AtualizarRede($"{Limitar(redeSaude, 0, 100):0}%", corRede);
// =========================================================
// GNSS
// =========================================================
var gnss = operacao?.Sensoriamento?.Gps;
double gnssSaude = ObterSaudeModulo(saudeModulos, Enums.T_Code.Gps);
bool gnssConectado = gnss != null && gnssSaude > 0;
if (!gnssConectado)
{
AtualizarGnss("Desconectado", HmiTheme.Danger);
}
else
{
Enums.TiposCorrecaoGPS qualidadeFix = gnss.QualidadeFix;
double precisaoCm = Math.Max(0, gnss.PrecisaoCm);
double notaGnss = CalcularNotaGnss(qualidadeFix, precisaoCm, gnssSaude);
Color corGnss = CorPorPercentual(notaGnss);
string textoGnss = $"{FormatarQualidadeGnss(qualidadeFix)} " + $"{precisaoCm:0.##} cm";
AtualizarGnss(textoGnss, corGnss);
}
// =========================================================
// BASE
// =========================================================
bool baseConectada = VariaveisOperacao.PosicaoBase?.Inicializado ?? false;
if (!baseConectada || gnss == null)
{
AtualizarDistanciaBase("Desconectada", HmiTheme.Danger);
}
else
{
double distanciaBase = GPSUtils.DistanciaEntrePontos(gnss, VariaveisOperacao.PosicaoBase);
Color corBase = CorDistanciaBase(distanciaBase, 1000);
string distanciaTexto = FormatarDistancia(distanciaBase);
AtualizarDistanciaBase(distanciaTexto, corBase);
}
// =========================================================
// BOTÕES DA OPERAÇÃO
// =========================================================
bool operacaoPausada = dadosOperacao?.Pausa ?? false;
bool operacaoEmergencia = dadosOperacao?.Emergencia ?? false;
bool operacaoIniciada = dadosOperacao?.OperacaoIniciada ?? false;
AtualizarAcoesOperacao(statusOperacao, operacaoIniciada, operacaoPausada, operacaoEmergencia);
// =========================================================
// FORMULÁRIO FILHO ATIVO
// =========================================================
if (_telaAtual != null && !_telaAtual.IsDisposed)
{
(_telaAtual as ITelaNavegavel).AtualizarDadosTela();
}
}
public void AbrirTela(Form tela)
{
if (tela == null)
return;
pnlConteudo.SuspendLayout();
try
{
if (_telaAtual != null && !_telaAtual.IsDisposed && _telaAtual != tela)
{
pnlConteudo.Controls.Remove(_telaAtual);
_telaAtual.Hide();
}
_telaAtual = tela;
tela.TopLevel = false;
tela.FormBorderStyle = FormBorderStyle.None;
tela.ControlBox = false;
tela.MinimizeBox = false;
tela.MaximizeBox = false;
tela.ShowInTaskbar = false;
tela.WindowState = FormWindowState.Normal;
tela.StartPosition = FormStartPosition.Manual;
tela.Dock = DockStyle.Fill;
tela.Margin = Padding.Empty;
tela.Padding = Padding.Empty;
if (!pnlConteudo.Controls.Contains(tela))
pnlConteudo.Controls.Add(tela);
tela.Show();
tela.BringToFront();
}
finally
{
pnlConteudo.ResumeLayout(true);
}
}
public void AtualizarCabecalhoOperacao(string status, string detalhes, Color corStatus)
{
ExecutarNaUI(delegate
{
lblStatusOperacao.Text = string.IsNullOrWhiteSpace(status) ? "NAODEFINIDO" : status.ToUpperInvariant();
lblStatusOperacaoDetalhes.Text = detalhes ?? string.Empty;
lblStatusOperacao.ForeColor = corStatus;
lblIndicadorStatus.ForeColor = corStatus;
pnlStatusOperacao.AccentColor = corStatus;
pnlStatusOperacao.BorderColor = MisturarCores(HmiTheme.BorderSoft, corStatus, 0.35F);
});
}
public void AtualizarBateria(string valor, Color cor)
{
AtualizarCard(cardBateria, valor, cor);
}
public void AtualizarReservatorio(string valor, Color cor)
{
AtualizarCard(cardReservatorio, valor, cor);
}
public void AtualizarTemperatura(string valor, Color cor)
{
AtualizarCard(cardTemperatura, valor, cor);
}
public void AtualizarRede(string valor, Color cor)
{
AtualizarCard(cardRede, valor, cor);
}
public void AtualizarGnss(string valor, Color cor)
{
AtualizarCard(cardGnss, valor, cor);
}
public void AtualizarDistanciaBase(string valor, Color cor)
{
AtualizarCard(cardBase, valor, cor);
}
public void DefinirOperacaoPausada(bool pausada)
{
ExecutarNaUI(delegate
{
btnPausarOperacao.Text =
pausada
? "RETOMAR OPERAÇÃO"
: "PAUSAR OPERAÇÃO";
btnPausarOperacao.IconText =
pausada
? "▶"
: "Ⅱ";
btnPausarOperacao.AccentColor =
pausada
? HmiTheme.Success
: HmiTheme.Warning;
btnPausarOperacao.BorderColor =
pausada
? HmiTheme.Success
: HmiTheme.Warning;
});
}
public void DefinirAcoesHabilitadas(bool pausar, bool emergencia, bool finalizar)
{
ExecutarNaUI(delegate
{
btnPausarOperacao.Enabled = pausar;
btnParadaEmergencia.Enabled = emergencia;
btnFinalizarOperacao.Enabled = finalizar;
});
}
private void AtualizarCard(StatusOperacaoControl card, string valor, Color cor)
{
if (card == null)
return;
if (card.Valor == valor || card.CorValor == cor)
return;
ExecutarNaUI(delegate
{
card.AtualizarStatus(valor, cor, cor);
});
}
private void AjustarLarguraCards()
{
if (fpnlResumo == null)
return;
int largura = fpnlResumo.ClientSize.Width - fpnlResumo.Padding.Horizontal;
if (fpnlResumo.VerticalScroll.Visible)
largura -= SystemInformation.VerticalScrollBarWidth;
largura = Math.Max(120, largura);
foreach (Control control in fpnlResumo.Controls)
control.Width = largura;
}
private void fpnlResumo_Resize(object sender, EventArgs e)
{
AjustarLarguraCards();
}
private void ExecutarNaUI(Action acao)
{
if (acao == null || IsDisposed || Disposing)
return;
if (InvokeRequired)
{
BeginInvoke(acao);
return;
}
acao();
}
private static Color MisturarCores(Color baseColor, Color accentColor, float pesoAccent)
{
pesoAccent = Math.Max(0F, Math.Min(1F, pesoAccent));
float pesoBase = 1F - pesoAccent;
return Color.FromArgb(
(int)(baseColor.R * pesoBase + accentColor.R * pesoAccent),
(int)(baseColor.G * pesoBase + accentColor.G * pesoAccent),
(int)(baseColor.B * pesoBase + accentColor.B * pesoAccent)
);
}
private static Color CorPorPercentual(double percentual)
{
percentual = Limitar(percentual, 0, 100);
if (percentual <= 50)
{
double fator = percentual / 50.0;
return InterpolarCor(HmiTheme.Danger, HmiTheme.Warning, fator);
}
double fatorVerde = (percentual - 50) / 50.0;
return InterpolarCor(HmiTheme.Warning, HmiTheme.Success, fatorVerde);
}
private static Color CorPorValorESaude(double valor, double saude)
{
double notaFinal = Math.Min(Limitar(valor, 0, 100), Limitar(saude, 0, 100));
return CorPorPercentual(notaFinal);
}
private static Color CorTemperatura(double temperatura, double saudeModulo)
{
Color corTemperatura;
if (temperatura <= 45)
{
corTemperatura = HmiTheme.Success;
}
else if (temperatura <= 60)
{
double fator = (temperatura - 45) / (60 - 45);
corTemperatura = InterpolarCor(HmiTheme.Success, HmiTheme.Warning, fator);
}
else if (temperatura <= 75)
{
double fator = (temperatura - 60) / (75 - 60);
corTemperatura = InterpolarCor(HmiTheme.Warning, HmiTheme.Danger, fator);
}
else
{
corTemperatura = HmiTheme.Danger;
}
Color corSaude = CorPorPercentual(saudeModulo);
/*
* Se a saúde estiver muito baixa,
* ela prevalece sobre a temperatura.
*/
if (saudeModulo < 40)
return corSaude;
return corTemperatura;
}
private static double NotaQualidadeGnss(Enums.TiposCorrecaoGPS qualidade)
{
switch (qualidade)
{
case Enums.TiposCorrecaoGPS.RTKFixo:
return 100;
case Enums.TiposCorrecaoGPS.BaseFix:
return 100;
case Enums.TiposCorrecaoGPS.RTKFlutuante:
return 78;
case Enums.TiposCorrecaoGPS.PPS:
return 65;
case Enums.TiposCorrecaoGPS.DGPS:
return 58;
case Enums.TiposCorrecaoGPS.DeadReckoing:
return 35;
case Enums.TiposCorrecaoGPS.Autonomo:
return 22;
case Enums.TiposCorrecaoGPS.SemCorrecao:
default:
return 0;
}
}
private static double NotaPrecisaoGnss(double precisaoCm)
{
if (precisaoCm <= 2)
return 100;
if (precisaoCm <= 5)
return InterpolarValor(precisaoCm, 2, 5, 100, 90);
if (precisaoCm <= 10)
return InterpolarValor(precisaoCm, 5, 10, 90, 75);
if (precisaoCm <= 30)
return InterpolarValor(precisaoCm, 10, 30, 75, 50);
if (precisaoCm <= 100)
return InterpolarValor(precisaoCm, 30, 100, 50, 20);
return 5;
}
private static double CalcularNotaGnss(Enums.TiposCorrecaoGPS qualidade, double precisaoCm, double saudeModulo)
{
double notaFix = NotaQualidadeGnss(qualidade);
double notaPrecisao = NotaPrecisaoGnss(precisaoCm);
/*
* O pior dos três critérios define a cor.
*
* RTK Fixo com saúde ruim não fica verde.
* Saúde boa com modo autônomo também não.
*/
return Math.Min(notaFix, Math.Min(notaPrecisao, Limitar(saudeModulo, 0, 100)));
}
private static string FormatarQualidadeGnss(Enums.TiposCorrecaoGPS qualidade)
{
switch (qualidade)
{
case Enums.TiposCorrecaoGPS.SemCorrecao:
return "Sem correção";
case Enums.TiposCorrecaoGPS.Autonomo:
return "Autônomo";
case Enums.TiposCorrecaoGPS.DGPS:
return "DGPS";
case Enums.TiposCorrecaoGPS.PPS:
return "PPS";
case Enums.TiposCorrecaoGPS.RTKFixo:
return "RTK Fixo";
case Enums.TiposCorrecaoGPS.RTKFlutuante:
return "RTK Flutuante";
case Enums.TiposCorrecaoGPS.DeadReckoing:
return "Dead Reckoning";
case Enums.TiposCorrecaoGPS.BaseFix:
return "Base Fix";
default:
return "Desconhecido";
}
}
private static Color CorDistanciaBase(double distanciaMetros, double distanciaMaximaMetros)
{
if (double.IsNaN(distanciaMetros) || double.IsInfinity(distanciaMetros) || distanciaMetros < 0)
{
return HmiTheme.Danger;
}
double percentualSaudavel = 100 - (distanciaMetros / distanciaMaximaMetros * 100);
return CorPorPercentual(percentualSaudavel);
}
private static string FormatarDistancia(double distanciaMetros)
{
if (double.IsNaN(distanciaMetros) || double.IsInfinity(distanciaMetros) || distanciaMetros < 0)
{
return "Indisponível";
}
if (distanciaMetros >= 1000)
{
return $"{distanciaMetros / 1000.0:0.00} km";
}
return $"{distanciaMetros:0.0} m";
}
private void AtualizarStatusOperacao(Enums.StatusOperacao status, bool pausada, bool emergencia, int? inicio_em = null)
{
string titulo;
string descricao;
Color cor;
if (emergencia)
{
titulo = "EMERGÊNCIA";
descricao = "Operação interrompida por emergência.";
cor = HmiTheme.Danger;
}
else if (pausada)
{
titulo = "OPERAÇÃO PAUSADA";
descricao = "Aguardando retomada pelo operador.";
cor = HmiTheme.Warning;
}
else
{
switch (status)
{
case Enums.StatusOperacao.NaoIniciado:
titulo = "AGUARDANDO";
descricao = "Aguardando parâmetros da operação.";
cor = HmiTheme.TextMuted;
break;
case Enums.StatusOperacao.Parametrizando:
titulo = "PARAMETRIZANDO";
descricao = "Aplicando parâmetros recebidos da base.";
cor = HmiTheme.Info;
break;
case Enums.StatusOperacao.Calibrando:
titulo = "CALIBRANDO";
descricao = "Executando verificações e calibrações.";
cor = HmiTheme.Warning;
break;
case Enums.StatusOperacao.Aguardando:
titulo = "PRONTO";
if (inicio_em.HasValue)
descricao = $"Início em {inicio_em.Value} segundos";
else
descricao = "Equipamento aguardando início.";
cor = HmiTheme.Info;
break;
case Enums.StatusOperacao.EmAndamento:
titulo = "EM OPERAÇÃO";
descricao = "Rover executando a operação.";
cor = HmiTheme.Success;
break;
case Enums.StatusOperacao.Parado:
titulo = "PARADO";
descricao = "Equipamento parado com segurança.";
cor = HmiTheme.Warning;
break;
case Enums.StatusOperacao.Concluido:
titulo = "CONCLUÍDO";
descricao = "Operação finalizada com sucesso.";
cor = HmiTheme.Success;
break;
case Enums.StatusOperacao.Erro:
titulo = "ERRO";
descricao = "Falha detectada durante a operação.";
cor = HmiTheme.Danger;
break;
default:
titulo = "INDEFINIDO";
descricao = "Estado operacional não reconhecido.";
cor = HmiTheme.TextMuted;
break;
}
}
AtualizarCabecalhoOperacao(titulo, descricao, cor);
}
private void AtualizarAcoesOperacao(Enums.StatusOperacao status, bool iniciada, bool pausada, bool emergencia)
{
DefinirOperacaoPausada(pausada);
bool concluida = status == Enums.StatusOperacao.Concluido;
bool comErro = status == Enums.StatusOperacao.Erro;
bool podePausar = iniciada && !emergencia && !concluida && !comErro;
bool podeEmergencia = iniciada && !emergencia && !concluida;
bool podeFinalizar = iniciada && !concluida;
DefinirAcoesHabilitadas(podePausar, podeEmergencia, podeFinalizar);
if (emergencia)
{
btnParadaEmergencia.Text = "EM EMERGÊNCIA";
btnParadaEmergencia.IconText = "■";
}
else
{
btnParadaEmergencia.Text = "EMERGÊNCIA";
btnParadaEmergencia.IconText = "■";
}
}
private static double ObterSaudeModulo(List<ManagerWorkerMessageResponseModulosPendentesModel> modulosSaude, Enums.T_Code modulo)
{
if (modulosSaude == null)
return 0;
var item = modulosSaude.FirstOrDefault(x => x.modulo == modulo);
if (item == null)
return 0;
return Limitar(Convert.ToDouble(item.saude), 0, 100);
}
private static Color InterpolarCor(Color inicial, Color final, double fator)
{
fator = Limitar(fator, 0, 1);
int r = (int)Math.Round(inicial.R + (final.R - inicial.R) * fator);
int g = (int)Math.Round(inicial.G + (final.G - inicial.G) * fator);
int b = (int)Math.Round(inicial.B + (final.B - inicial.B) * fator);
return Color.FromArgb(r, g, b);
}
private static double InterpolarValor(double valor, double origemMinima, double origemMaxima, double destinoInicial, double destinoFinal)
{
if (origemMaxima <= origemMinima)
return destinoInicial;
double fator = (valor - origemMinima) / (origemMaxima - origemMinima);
fator = Limitar(fator, 0, 1);
return destinoInicial + (destinoFinal - destinoInicial) * fator;
}
private static double Limitar(double valor, double minimo, double maximo)
{
return Math.Max(minimo, Math.Min(maximo, valor));
}
}
public interface ITelaNavegavel
{
event EventHandler VoltarSolicitado;
void AtualizarDadosTela();
}
}