From c09986e146298142bc89abee36f0fb59b5ce9f34 Mon Sep 17 00:00:00 2001 From: Diego Freitas Date: Thu, 17 Sep 2026 08:17:03 -0300 Subject: [PATCH] =?UTF-8?q?otimiza=C3=A7=C3=A3o=20de=20software=20e=20melh?= =?UTF-8?q?orias=20de=20firmware?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Instalador/Instalador.iss | 2 +- OrionTardPro/OrionTardPro/Enuns.cs | 11 +- OrionTardPro/OrionTardPro/Forms/frmAlarmes.cs | 25 +- OrionTardPro/OrionTardPro/Forms/frmCamera.cs | 114 +- .../OrionTardPro/Forms/frmConexao.Designer.cs | 23 +- OrionTardPro/OrionTardPro/Forms/frmConexao.cs | 318 ++- .../OrionTardPro/Forms/frmControle.cs | 35 +- .../OrionTardPro/Forms/frmControlecarro.cs | 481 +++- .../OrionTardPro/Forms/frmInstancial.cs | 16 +- OrionTardPro/OrionTardPro/Forms/frmLicenca.cs | 229 +- .../Forms/frmParametros.Designer.cs | 510 ++-- .../OrionTardPro/Forms/frmParametros.cs | 21 +- .../Forms/frmPrincipal.Designer.cs | 81 +- .../OrionTardPro/Forms/frmPrincipal.cs | 437 ++- .../OrionTardPro/Forms/frmSensoriamento.cs | 319 ++- OrionTardPro/OrionTardPro/FuncoesGlobais.cs | 140 +- OrionTardPro/OrionTardPro/GeneralJoystick.cs | 911 +++++-- OrionTardPro/OrionTardPro/Models/Alertas.cs | 9 +- OrionTardPro/OrionTardPro/Models/Joystick.cs | 71 +- OrionTardPro/OrionTardPro/Models/LogsModel.cs | 621 ++++- .../OrionTardPro/Models/PerfilModel.cs | 15 +- .../OrionTardPro/Models/SensoresModel.cs | 867 ++++-- .../OrionTardPro/Properties/AssemblyInfo.cs | 4 +- .../OrionTardPro/Services/HttpService.cs | 437 ++- .../OrionTardPro/Services/SerialService.cs | 2336 ++++++++++++++++- .../Services/VersionamentoService.cs | 99 +- .../OrionTardPro/Services/VideoService.cs | 1052 ++++++-- OrionTardPro/OrionTardPro/Variaveis.cs | 520 +++- .../OrionTardPro/bin/Debug/Layouts/PS2.joy | 2 +- .../bin/Debug/Perfis/Factor2.otpp | 2 +- .../Perfis/Factor2_MedidorEspessura.otpp | 1 + .../bin/Debug/Perfis/Factor3.1.otpp | 2 +- .../bin/Debug/Perfis/Factor4.otpp | 2 +- .../bin/Debug/Perfis/Tardigrado.otpp | 2 +- 34 files changed, 7879 insertions(+), 1836 deletions(-) create mode 100644 OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2_MedidorEspessura.otpp diff --git a/Instalador/Instalador.iss b/Instalador/Instalador.iss index d797c63..7121f4b 100644 --- a/Instalador/Instalador.iss +++ b/Instalador/Instalador.iss @@ -2,7 +2,7 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "OrionTard Pro" -#define MyAppVersion "1.0.1.8" +#define MyAppVersion "1.1.0.0" #define MyAppPublisher "Zendion, Inc." #define MyAppURL "https://zendioninc.com.br" #define MyAppExeName "OrionTardPro.exe" diff --git a/OrionTardPro/OrionTardPro/Enuns.cs b/OrionTardPro/OrionTardPro/Enuns.cs index b4e912f..edea118 100644 --- a/OrionTardPro/OrionTardPro/Enuns.cs +++ b/OrionTardPro/OrionTardPro/Enuns.cs @@ -82,6 +82,7 @@ namespace OrionTardPro Ods, // Odometro Pns, // Ping Mod, // Dados Modulo + Inf, // Infiltração } public enum TipoEmbreagem @@ -131,7 +132,8 @@ namespace OrionTardPro InclinacaoHorizontal, Pressao, Altitude, - Odometro + Odometro, + InfiltracaoRisco } public enum TipoArquivoVersionado @@ -148,6 +150,13 @@ namespace OrionTardPro Factor4 = 4, } + public enum VersaoProtocolo + { + desconhecido = 0, + v1 = 1, + v2 = 2, + } + } } diff --git a/OrionTardPro/OrionTardPro/Forms/frmAlarmes.cs b/OrionTardPro/OrionTardPro/Forms/frmAlarmes.cs index 1a2aa1b..fe3b5c8 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmAlarmes.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmAlarmes.cs @@ -31,16 +31,23 @@ namespace OrionTardPro public void LimpaTabela() { - dtAlarmes.Columns.Clear(); + // A implementação antiga destruía e recriava todas as colunas + // a cada frame de telemetria. As colunas são estáticas, então + // criamos somente uma vez e limpamos apenas as linhas. + if (dtAlarmes.Columns.Count == 0) + { + dtAlarmes.Columns.Add("clAlarme", "Alarme"); + dtAlarmes.Columns.Add("clLimite", "Limite"); + dtAlarmes.Columns.Add("clValor", "Valor"); + dtAlarmes.Columns.Add("clAlerta", "Alerta"); + dtAlarmes.Columns[0].Width = 180; + dtAlarmes.Columns[1].Width = 70; + dtAlarmes.Columns[2].Width = 70; + dtAlarmes.Columns[3].AutoSizeMode = + DataGridViewAutoSizeColumnMode.AllCells; + } + dtAlarmes.Rows.Clear(); - dtAlarmes.Columns.Add("clAlarme", "Alarme"); - dtAlarmes.Columns.Add("clLimite", "Limite"); - dtAlarmes.Columns.Add("clValor", "Valor"); - dtAlarmes.Columns.Add("clAlerta", "Alerta"); - dtAlarmes.Columns[0].Width = 180; - dtAlarmes.Columns[1].Width = 70; - dtAlarmes.Columns[2].Width = 70; - dtAlarmes.Columns[3].AutoSizeMode = DataGridViewAutoSizeColumnMode.AllCells; } diff --git a/OrionTardPro/OrionTardPro/Forms/frmCamera.cs b/OrionTardPro/OrionTardPro/Forms/frmCamera.cs index c9e7f95..f313a5a 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmCamera.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmCamera.cs @@ -1,5 +1,6 @@ using AForge.Video; using AForge.Video.DirectShow; +using OrionTardPro.Services; using System; using System.Collections.Generic; using System.ComponentModel; @@ -8,6 +9,7 @@ using System.Drawing; using System.Linq; using System.Management; using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; @@ -15,57 +17,105 @@ namespace OrionTardPro { public partial class frmCamera : Form { + private readonly SemaphoreSlim _cameraDiscoveryLock = + new SemaphoreSlim(1, 1); public frmCamera() { InitializeComponent(); } + // Compatibilidade com chamadas antigas. O fluxo novo usa a versão + // async para não executar WMI na thread da interface. public void AtualizaListaCameras() { + _ = AtualizaListaCamerasAsync(); + } + + public async Task AtualizaListaCamerasAsync() + { + if (!await _cameraDiscoveryLock.WaitAsync(0)) + return; + try { - //Variaveis.videoDevices = new FilterInfoCollection(FilterCategory.VideoInputDevice); + btnAtualizar.Enabled = false; - var cameraNames = new List(); - var camerasList = new Dictionary(); - using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_PnPEntity WHERE (PNPClass = 'Image' OR PNPClass = 'Camera')")) + long inicioDiscoveryPerf = + PerformanceMonitorService.MarcarInicio(); + + List cameraNames = await Task.Run(() => { - foreach (var device in searcher.Get()) + var nomes = new List(); + + using (var searcher = new ManagementObjectSearcher( + "SELECT * FROM Win32_PnPEntity WHERE (PNPClass = 'Image' OR PNPClass = 'Camera')")) { - camerasList.Add(device["DeviceID"].ToString(), device["Caption"].ToString()); - cameraNames.Add(device["Caption"].ToString()); + foreach (ManagementObject device in searcher.Get()) + { + string caption = device["Caption"]?.ToString(); + if (!string.IsNullOrWhiteSpace(caption) && + !nomes.Contains(caption)) + { + nomes.Add(caption); + } + } } - } - //cameraNames = camerasList.OrderByDescending(x => x.Key).Select(x => x.Value).ToList(); + return nomes; + }); - cmbCamera.Items.Clear(); - cmbCamera.Items.Add("Sem vídeo"); - if (cameraNames.Count == 0) - throw new ApplicationException(); - - foreach (string device in cameraNames) + cmbCamera.BeginUpdate(); + try { - cmbCamera.Items.Add(device); + cmbCamera.Items.Clear(); + cmbCamera.Items.Add("Sem vídeo"); + + foreach (string device in cameraNames) + cmbCamera.Items.Add(device); + + cmbCamera.SelectedIndex = 0; } - cmbCamera.SelectedIndex = 0; - if (cmbCamera.SelectedIndex > 0) - VideoService.LigarCamera(); - else - LimpaCamera(); + finally + { + cmbCamera.EndUpdate(); + } + + if (cameraNames.Count == 0) + { + Variaveis.frmPrincipal.lblCameraconexao.Text = + "Câmera: Desconectado"; + } + + PerformanceMonitorService.ReportarSeLento( + "frmCamera.DiscoveryWMI", + inicioDiscoveryPerf, + 1200, + "devices=" + cameraNames.Count); + + LimpaCamera(); } - catch (ApplicationException) + catch (Exception ex) { + Console.WriteLine( + "[CAMERA] Falha ao enumerar dispositivos: " + ex.Message); + cmbCamera.Items.Clear(); cmbCamera.Items.Add("Nenhum dispositivo encontrado!"); - Variaveis.frmPrincipal.lblCameraconexao.Text = "Câmera: Desconectado"; + cmbCamera.SelectedIndex = 0; + Variaveis.frmPrincipal.lblCameraconexao.Text = + "Câmera: Desconectado"; + } + finally + { + btnAtualizar.Enabled = true; + _cameraDiscoveryLock.Release(); } } - private void btnAtualizar_Click(object sender, EventArgs e) + private async void btnAtualizar_Click(object sender, EventArgs e) { - AtualizaListaCameras(); + await AtualizaListaCamerasAsync(); } private void btnSalvar_Click(object sender, EventArgs e) @@ -93,12 +143,9 @@ namespace OrionTardPro else { Variaveis.frmPrincipal.lblCameraconexao.Text = "Câmera: Desconectado"; - VideoService.camera.Abort(); - VideoService._videoCapture.Release(); - VideoService._videoCapture = null; + VideoService.DesligarCamera(); Variaveis.frmPrincipal.gpbImagem.Enabled = false; Variaveis.frmPrincipal.gpbGravacao.Enabled = false; - Variaveis.VideoIsRuning = false; LimpaCamera(); } } @@ -108,9 +155,12 @@ namespace OrionTardPro { PictureBox img = Variaveis.frmPrincipal.imgCamera; Bitmap bmp = new Bitmap(img.Width, img.Height); - Graphics g = Graphics.FromImage(bmp); - g.Clear(Color.Black); - Variaveis.frmPrincipal.imgCamera.Image = bmp; + using (Graphics g = Graphics.FromImage(bmp)) + { + g.Clear(Color.Black); + } + + VideoService.ExibirImagemEstatica(bmp); } private void frmCamera_FormClosing(object sender, FormClosingEventArgs e) diff --git a/OrionTardPro/OrionTardPro/Forms/frmConexao.Designer.cs b/OrionTardPro/OrionTardPro/Forms/frmConexao.Designer.cs index f641c1e..ca14100 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmConexao.Designer.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmConexao.Designer.cs @@ -43,6 +43,7 @@ this.lblTempoenvio = new System.Windows.Forms.Label(); this.chbProtocolocompleto = new System.Windows.Forms.CheckBox(); this.tabTransmissor = new System.Windows.Forms.TabPage(); + this.chbPingCamera = new System.Windows.Forms.CheckBox(); this.chbCamera = new System.Windows.Forms.CheckBox(); this.lblSegundos = new System.Windows.Forms.Label(); this.lblTempoPings = new System.Windows.Forms.Label(); @@ -50,7 +51,6 @@ this.lblEnvios = new System.Windows.Forms.Label(); this.txtEnvios = new System.Windows.Forms.NumericUpDown(); this.btnConectar = new System.Windows.Forms.Button(); - this.chbPingCamera = new System.Windows.Forms.CheckBox(); this.tabControl1.SuspendLayout(); this.tabConexao.SuspendLayout(); this.tabReceptor.SuspendLayout(); @@ -249,6 +249,16 @@ this.tabTransmissor.Text = "Transmissor"; this.tabTransmissor.UseVisualStyleBackColor = true; // + // chbPingCamera + // + this.chbPingCamera.AutoSize = true; + this.chbPingCamera.Location = new System.Drawing.Point(11, 89); + this.chbPingCamera.Name = "chbPingCamera"; + this.chbPingCamera.Size = new System.Drawing.Size(184, 20); + this.chbPingCamera.TabIndex = 6; + this.chbPingCamera.Text = "Enviar ping para a câmera"; + this.chbPingCamera.UseVisualStyleBackColor = true; + // // chbCamera // this.chbCamera.AutoSize = true; @@ -345,16 +355,6 @@ this.btnConectar.UseVisualStyleBackColor = true; this.btnConectar.Click += new System.EventHandler(this.btnConectar_Click); // - // chbPingCamera - // - this.chbPingCamera.AutoSize = true; - this.chbPingCamera.Location = new System.Drawing.Point(11, 89); - this.chbPingCamera.Name = "chbPingCamera"; - this.chbPingCamera.Size = new System.Drawing.Size(184, 20); - this.chbPingCamera.TabIndex = 6; - this.chbPingCamera.Text = "Enviar ping para a câmera"; - this.chbPingCamera.UseVisualStyleBackColor = true; - // // frmConexao // this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); @@ -374,6 +374,7 @@ this.Text = "Gerenciador de conexão"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmConexao_FormClosing); this.Load += new System.EventHandler(this.frmConexao_Load); + this.Shown += new System.EventHandler(this.frmConexao_Shown); this.tabControl1.ResumeLayout(false); this.tabConexao.ResumeLayout(false); this.tabConexao.PerformLayout(); diff --git a/OrionTardPro/OrionTardPro/Forms/frmConexao.cs b/OrionTardPro/OrionTardPro/Forms/frmConexao.cs index ef06647..4a0bee1 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmConexao.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmConexao.cs @@ -1,12 +1,16 @@ using OrionTardPro.Services; using System; using System.IO.Ports; +using System.Linq; using System.Windows.Forms; +using static OrionTardPro.Enuns; namespace OrionTardPro { public partial class frmConexao : Form { + private bool _conectando; + public frmConexao() { InitializeComponent(); @@ -14,70 +18,302 @@ namespace OrionTardPro private void frmConexao_Load(object sender, EventArgs e) { - //AtualizaListadePortas(); + AtualizarControlesProtocolo(); + AtualizaListadePortas(false); } - public void AtualizaListadePortas() + private void frmConexao_Shown(object sender, EventArgs e) { - cmbPorta.Items.Clear(); - for (int i = 0; i < SerialPort.GetPortNames().Length; i++) + AtualizarControlesProtocolo(); + AtualizaListadePortas(false); + } + + private bool ProtocoloV2Selecionado() + { + return Variaveis.PerfilVigente.VersaoProtocolo == VersaoProtocolo.v2; + } + + private void AtualizarControlesProtocolo() + { + bool protocoloV2 = ProtocoloV2Selecionado(); + + // Configuráveis tanto na V1 quanto na V2. + txtTempoPings.Enabled = true; + txtDelay.Enabled = true; + chbRequisitarDados.Enabled = true; + chbCamera.Enabled = true; + chbPingCamera.Enabled = true; + + if (protocoloV2) { - cmbPorta.Items.Add(SerialPort.GetPortNames()[i].ToString()); + cmbBaud.Text = "9600"; + cmbBaud.Enabled = false; + + // V2 usa ACK, sequência, timeout e retry próprios. + txtEnvios.Enabled = false; + + // Frame completo e CRC são obrigatórios na V2. + chbProtocolocompleto.Checked = true; + chbProtocolocompleto.Enabled = false; } - if (cmbPorta.Items.Count > 1) - cmbPorta.SelectedIndex = 1; - else if (cmbPorta.Items.Count > 0) - cmbPorta.SelectedIndex = 0; else - throw new Exception("Não foi encontrado um conversor ligado ao computador, favor conferir!"); - int idxBaud = cmbBaud.Items.IndexOf(Variaveis.PerfilVigente.BaudRate.ToString()); - cmbBaud.SelectedIndex = idxBaud; + { + cmbBaud.Enabled = true; + txtEnvios.Enabled = true; + chbProtocolocompleto.Enabled = true; + + cmbBaud.Text = + Variaveis.PerfilVigente.BaudRate.ToString(); + } + } + + public bool AtualizaListadePortas(bool exibirErro = true) + { + string portaAnterior = cmbPorta.Text; + + string[] portas = SerialPort + .GetPortNames() + .OrderBy(x => x) + .ToArray(); + + cmbPorta.BeginUpdate(); + + try + { + cmbPorta.Items.Clear(); + cmbPorta.Items.AddRange(portas); + + if (portas.Length == 0) + { + cmbPorta.SelectedIndex = -1; + + if (exibirErro) + { + MessageBox.Show( + "Não foi encontrada nenhuma porta serial. " + + "Confira o conversor USB/RS485 e o driver.", + "Porta serial não encontrada", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + } + + return false; + } + + int indiceAnterior = Array.IndexOf(portas, portaAnterior); + + cmbPorta.SelectedIndex = + indiceAnterior >= 0 + ? indiceAnterior + : 0; + + return true; + } + finally + { + cmbPorta.EndUpdate(); + } + } + + private bool AplicarConfiguracoes() + { + if (string.IsNullOrWhiteSpace(cmbPorta.Text)) + { + MessageBox.Show( + "Selecione uma porta serial.", + "Configuração incompleta", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + + bool protocoloV2 = ProtocoloV2Selecionado(); + + int tempoPing = + Convert.ToInt32(txtTempoPings.Value); + + int tempoTelemetria = + Convert.ToInt32(txtDelay.Value); + + if (protocoloV2) + { + if (tempoPing < 1 || tempoPing > 4) + { + MessageBox.Show( + "Na versão V2, o intervalo de heartbeat deve estar " + + "entre 1 e 4 segundos.", + "Heartbeat inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + + if (tempoTelemetria < 250 || + tempoTelemetria > 10000) + { + MessageBox.Show( + "Na versão V2, o intervalo de telemetria deve estar " + + "entre 250 e 10.000 ms.", + "Intervalo inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + } + else + { + if (tempoPing < 1) + { + MessageBox.Show( + "O intervalo entre pings deve ser maior que zero.", + "Intervalo inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + + if (tempoTelemetria < 100) + { + MessageBox.Show( + "O intervalo de sensores deve ser de pelo menos 100 ms.", + "Intervalo inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + } + + SerialService.PortaCom = cmbPorta.Text.Trim(); + + // Configurações compartilhadas entre V1 e V2. + Variaveis.PerfilVigente.TempoEntrePings = + tempoPing; + + Variaveis.PerfilVigente.TempoRecepcaoSensores = + tempoTelemetria; + + Variaveis.PerfilVigente.RequisitarDados = + chbRequisitarDados.Checked; + + Variaveis.PerfilVigente.ControleIndividualCamera = + chbCamera.Checked; + + Variaveis.PerfilVigente.EnviarPingCamera = + chbPingCamera.Checked; + + if (protocoloV2) + { + Variaveis.PerfilVigente.BaudRate = 9600; + + // Obrigatório na V2. + Variaveis.PerfilVigente.ReceberProtocoloCompleto = true; + + return true; + } + + if (!int.TryParse(cmbBaud.Text, out int baudRate) || + baudRate <= 0) + { + MessageBox.Show( + "Selecione um baud rate válido.", + "Configuração inválida", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + + return false; + } + + // Configurações exclusivamente legadas. + Variaveis.PerfilVigente.BaudRate = baudRate; + + Variaveis.PerfilVigente.QuantidadeEnvios = + Convert.ToInt32(txtEnvios.Value); + + Variaveis.PerfilVigente.ReceberProtocoloCompleto = + chbProtocolocompleto.Checked; + + return true; } private async void btnConectar_Click(object sender, EventArgs e) { - Variaveis.PerfilVigente.QuantidadeEnvios = Convert.ToInt32(txtEnvios.Value); - Variaveis.PerfilVigente.TempoEntrePings = Convert.ToInt32(txtTempoPings.Value); - SerialService.PortaCom = cmbPorta.Text; - Variaveis.PerfilVigente.BaudRate = Convert.ToInt32(cmbBaud.Text); - Variaveis.PerfilVigente.TempoRecepcaoSensores = Convert.ToInt32(txtDelay.Value); - Variaveis.PerfilVigente.RequisitarDados = chbRequisitarDados.Checked; - SerialService.SensoresTimer.Interval = Variaveis.PerfilVigente.TempoRecepcaoSensores; + if (_conectando) + return; - bool Sucesso = await SerialService.ConectaPortaSerial(); + if (!AplicarConfiguracoes()) + return; - if (Sucesso) + _conectando = true; + btnConectar.Enabled = false; + //btnCancelar.Enabled = false; + UseWaitCursor = true; + + try { + bool sucesso = + await SerialService.ConectaPortaSerial(); + + if (!sucesso) + return; + SerialService.Pings = 0; - GeneralJoystick.joystickTimer.Start(); - SerialService.Conectado = true; - this.Hide(); - SerialService.SensoresTimer.Interval = Convert.ToInt32(txtDelay.Value); + + /* + * Por enquanto estes timers são iniciados aqui porque + * ConectaPortaSerial ainda não os inicia internamente. + * + * Não alteramos os intervalos: o SerialService já escolheu + * os valores corretos de acordo com V1 ou V2. + */ SerialService.SensoresTimer.Start(); SerialService.EnvioTimer.Start(); - Variaveis.frmPrincipal.lblCarroconexao.Text = $"Carro: Conectado ({Variaveis._Model.ToString()} | NS {Variaveis._RobotNS} | FV {Variaveis._FirmwareVersion})"; + + if (GeneralJoystick.JoystickExist) + GeneralJoystick.joystickTimer?.Start(); + + Hide(); + } + catch (Exception ex) + { + MessageBox.Show( + "Não foi possível concluir a conexão.\r\n\r\n" + + ex.Message, + "Erro de conexão", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + finally + { + _conectando = false; + btnConectar.Enabled = true; + //btnCancelar.Enabled = true; + UseWaitCursor = false; } - else - MessageBox.Show("Houve um erro ao tentar conectar-se à prota " + SerialService.PortaCom + ".", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error); } private void btnCancelar_Click(object sender, EventArgs e) { - try - { - AtualizaListadePortas(); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, "Erro ao listar dispositivos", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + AtualizaListadePortas(); } - private void frmConexao_FormClosing(object sender, FormClosingEventArgs e) + private void frmConexao_FormClosing( + object sender, + FormClosingEventArgs e) { - Variaveis.PerfilVigente.QuantidadeEnvios = Convert.ToInt32(txtEnvios.Value); - Variaveis.PerfilVigente.TempoEntrePings = Convert.ToInt32(txtTempoPings.Value); - Variaveis.PerfilVigente.TempoRecepcaoSensores = Convert.ToInt32(txtDelay.Value); + /* + * Este formulário é reutilizado durante toda a execução. + * O fechamento real ocorrerá junto com o encerramento da aplicação. + */ + if (e.CloseReason == CloseReason.UserClosing) + { + e.Cancel = true; + Hide(); + } } } -} +} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/Forms/frmControle.cs b/OrionTardPro/OrionTardPro/Forms/frmControle.cs index 2b03f7e..67c39a4 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmControle.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmControle.cs @@ -46,10 +46,18 @@ namespace OrionTardPro #region EVENTOS DOS BOTOES - private void btnAtualizar_Click(object sender, EventArgs e) + private async void btnAtualizar_Click(object sender, EventArgs e) { - GeneralJoystick.InstanciaJoystick(); - AtualizaLista(); + btnAtualizar.Enabled = false; + try + { + await GeneralJoystick.InstanciaJoystickAsync(); + AtualizaLista(); + } + finally + { + btnAtualizar.Enabled = true; + } } public void btnOK_Click(object sender, EventArgs e) @@ -74,15 +82,28 @@ namespace OrionTardPro ((Button)sender).ForeColor = Color.DarkRed; } - private void btnEscolher_Click(object sender, EventArgs e) + private async void btnEscolher_Click(object sender, EventArgs e) { if (!cmbJoysticks.Enabled) + { btnEscolher.Text = "Salvar"; + } else { - btnEscolher.Text = "Editar"; - GeneralJoystick.InstanciaJoystick(false, cmbJoysticks.SelectedIndex); + btnEscolher.Enabled = false; + try + { + await GeneralJoystick.InstanciaJoystickAsync( + false, + cmbJoysticks.SelectedIndex); + btnEscolher.Text = "Editar"; + } + finally + { + btnEscolher.Enabled = true; + } } + btnAtualizar.Enabled = !btnAtualizar.Enabled; cmbJoysticks.Enabled = !cmbJoysticks.Enabled; } @@ -280,7 +301,7 @@ namespace OrionTardPro private void trkVelocidade_Scroll(object sender, EventArgs e) { Variaveis.frmPrincipal.trkVelocidade.Value = trkVelocidade.Value; - GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value * 10); + GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value); } #endregion diff --git a/OrionTardPro/OrionTardPro/Forms/frmControlecarro.cs b/OrionTardPro/OrionTardPro/Forms/frmControlecarro.cs index dd713b4..420e839 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmControlecarro.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmControlecarro.cs @@ -2,12 +2,7 @@ using OrionTardPro.Services; using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.InteropServices; using System.Windows.Forms; using static OrionTardPro.Enuns; @@ -15,114 +10,490 @@ namespace OrionTardPro { public partial class frmControlecarro : Form { + private readonly HashSet _teclasPressionadas = + new HashSet(); + + private bool _processandoFechamento; + private readonly System.Windows.Forms.Timer _supervisorTecladoTimer; + private const int IntervaloSupervisorTecladoMs = 50; + + // GetAsyncKeyState consulta o estado físico atual da tecla e não depende de o + // Windows ter entregado o KeyUp ao formulário. O bit alto indica pressionada. + [DllImport("user32.dll")] + private static extern short GetAsyncKeyState(int vKey); + + private static bool TeclaFisicamentePressionada(Keys tecla) + { + return (GetAsyncKeyState((int)tecla) & 0x8000) != 0; + } + public frmControlecarro() { InitializeComponent(); + + // Permite ao form receber teclas antes dos controles filhos. + KeyPreview = true; + + Deactivate += frmControlecarro_Deactivate; + VisibleChanged += frmControlecarro_VisibleChanged; + FormClosed += frmControlecarro_FormClosed; + + _supervisorTecladoTimer = new System.Windows.Forms.Timer + { + Interval = IntervaloSupervisorTecladoMs + }; + _supervisorTecladoTimer.Tick += SupervisorTecladoTimer_Tick; + _supervisorTecladoTimer.Start(); } - public void frmControlecarro_KeyDown(object sender, KeyEventArgs e) + public void frmControlecarro_KeyDown( + object sender, + KeyEventArgs e) { - var ListaTeclasPressionadas = GeneralJoystick.ListaTeclasPressionadas; + Comando comando = + GeneralJoystick.TipoControle(e.KeyCode); - switch (GeneralJoystick.TipoControle(e.KeyCode)) + if (comando == Comando.Vazio) + return; + + /* + * Add retorna false quando a tecla já estava pressionada. + * Isso bloqueia o auto-repeat do Windows para relés, + * câmeras, iluminação e outros comandos de alternância. + * + * Na V2, a renovação de MOT é feita pelo supervisor do + * GeneralJoystick, não por KeyDown repetido. + */ + if (!_teclasPressionadas.Add(e.KeyCode)) + { + e.Handled = true; + e.SuppressKeyPress = true; + return; + } + + e.Handled = true; + e.SuppressKeyPress = true; + + switch (comando) { case Comando.Motor: - if (ListaTeclasPressionadas[ListaTeclasPressionadas.Count - 1] != e.KeyCode.ToString()) - { - if (ListaTeclasPressionadas[ListaTeclasPressionadas.Count - 1] == "Stop") - ListaTeclasPressionadas.Clear(); + AtualizarIntencaoMotores(); + break; - ListaTeclasPressionadas.Add(e.KeyCode.ToString()); - GeneralJoystick.ProcessaComandoCarro(e.KeyCode.ToString()); - } - break; case Comando.Camera: - GeneralJoystick.ProcessaComandoCamera(e.KeyCode); + AtualizarIntencaoCamera(); break; + case Comando.Iluminacao: GeneralJoystick.AlternarStatusIluminacao(); break; + case Comando.Laser: GeneralJoystick.AlternarStatusLaser(); break; + case Comando.BrilhoMais: - GeneralJoystick.AumentarIntensidadeBrilhoLED(); + GeneralJoystick + .AumentarIntensidadeBrilhoLED(); break; + case Comando.BrilhoMenos: - GeneralJoystick.ReduzirIntensidadeBrilhoLED(); + GeneralJoystick + .ReduzirIntensidadeBrilhoLED(); break; + case Comando.Embreagem: GeneralJoystick.AlternarStatusEmbreagem(); break; + case Comando.MudaCamera: GeneralJoystick.AlternarCamera(); break; + case Comando.CaputrarImagem: GeneralJoystick.EditarImagem(); break; + case Comando.Marcador: GeneralJoystick.AdicionarMarcador(); break; + case Comando.IniciaOdometroParcial: GeneralJoystick.IniciarOdometroParcial(); break; + case Comando.ReleGeral: GeneralJoystick.AlternarStatusReleGeral(); break; + case Comando.MedidorEspessura: - bool carimbar = e.KeyCode == Keys.Q; - if (!carimbar) - { - Variaveis.frmPrincipal.trkMedidorEspessura.Value = Variaveis.Angulo_MedidorEspessuraVertical == 0 ? Variaveis.PerfilVigente.AnguloMedicaoIdeal : 0; - GeneralJoystick.AtualizaAnguloMedidorEspessura(Variaveis.frmPrincipal.trkMedidorEspessura.Value); - } - GeneralJoystick.AlternarAnguloMedidorEspessura(carimbar); + ProcessarMedidorEspessura(e.KeyCode); break; } } - private void frmControlecarro_KeyUp(object sender, KeyEventArgs e) + private void frmControlecarro_KeyUp( + object sender, + KeyEventArgs e) { - var ListaTeclasPressionadas = GeneralJoystick.ListaTeclasPressionadas; + Comando comando = + GeneralJoystick.TipoControle(e.KeyCode); - switch (GeneralJoystick.TipoControle(e.KeyCode)) + if (comando == Comando.Vazio) + return; + + _teclasPressionadas.Remove(e.KeyCode); + + e.Handled = true; + e.SuppressKeyPress = true; + + switch (comando) { case Comando.Motor: - if (ListaTeclasPressionadas.Count == 1) - { - ListaTeclasPressionadas[0] = "Stop"; - GeneralJoystick.ProcessaComandoCarro("Stop"); - } - else - { - for (int i = 0; i < ListaTeclasPressionadas.Count; i++) - { - if (ListaTeclasPressionadas[i] == e.KeyCode.ToString()) - { - ListaTeclasPressionadas.RemoveAt(i); - } - } - GeneralJoystick.ProcessaComandoCarro(ListaTeclasPressionadas[0]); - } + /* + * Se ainda houver uma seta pressionada, recalcula + * a direção. Caso contrário, envia parada. + */ + AtualizarIntencaoMotores(); break; + case Comando.Camera: - GeneralJoystick.ProcessaComandoCamera(Keys.Escape); + // Recalcula os dois eixos. Soltar D em W+D para apenas o + // horizontal, mantendo W ativo. + AtualizarIntencaoCamera(); break; } } - private void frmControlecarro_FormClosing(object sender, FormClosingEventArgs e) + private void AtualizarIntencaoMotores() { - if(!Variaveis.frmPrincipal.FechandoControle) + bool cima = + _teclasPressionadas.Contains(Keys.Up); + + bool baixo = + _teclasPressionadas.Contains(Keys.Down); + + bool esquerda = + _teclasPressionadas.Contains(Keys.Left); + + bool direita = + _teclasPressionadas.Contains(Keys.Right); + + /* + * Teclas opostas se anulam. Isso impede comandos + * ambíguos como Up + Down. + */ + int eixoVertical = + (cima ? 1 : 0) - + (baixo ? 1 : 0); + + int eixoHorizontal = + (direita ? 1 : 0) - + (esquerda ? 1 : 0); + + List listaCompatibilidade = + GeneralJoystick.ListaTeclasPressionadas; + + listaCompatibilidade.Clear(); + + if (eixoVertical == 0 && + eixoHorizontal == 0) { - Variaveis.frmPrincipal.FechandoControle = true; - Variaveis.frmPrincipal.FecharControleAnalogico(); + listaCompatibilidade.Add("Stop"); + + GeneralJoystick.ProcessaComandoCarro( + "Stop"); + + return; } - e.Cancel = true; - this.Hide(); + + /* + * O GeneralJoystick atual monta diagonais consultando + * a primeira tecla da lista. Por isso colocamos primeiro + * a direção horizontal e processamos a vertical. + */ + if (eixoHorizontal < 0 && + eixoVertical > 0) + { + listaCompatibilidade.Add("Left"); + listaCompatibilidade.Add("Up"); + + GeneralJoystick.ProcessaComandoCarro("Up"); + return; + } + + if (eixoHorizontal > 0 && + eixoVertical > 0) + { + listaCompatibilidade.Add("Right"); + listaCompatibilidade.Add("Up"); + + GeneralJoystick.ProcessaComandoCarro("Up"); + return; + } + + if (eixoHorizontal < 0 && + eixoVertical < 0) + { + listaCompatibilidade.Add("Left"); + listaCompatibilidade.Add("Down"); + + GeneralJoystick.ProcessaComandoCarro("Down"); + return; + } + + if (eixoHorizontal > 0 && + eixoVertical < 0) + { + listaCompatibilidade.Add("Right"); + listaCompatibilidade.Add("Down"); + + GeneralJoystick.ProcessaComandoCarro("Down"); + return; + } + + if (eixoVertical > 0) + { + listaCompatibilidade.Add("Up"); + GeneralJoystick.ProcessaComandoCarro("Up"); + return; + } + + if (eixoVertical < 0) + { + listaCompatibilidade.Add("Down"); + GeneralJoystick.ProcessaComandoCarro("Down"); + return; + } + + if (eixoHorizontal < 0) + { + listaCompatibilidade.Add("Left"); + GeneralJoystick.ProcessaComandoCarro("Left"); + return; + } + + listaCompatibilidade.Add("Right"); + GeneralJoystick.ProcessaComandoCarro("Right"); } - + private void AtualizarIntencaoCamera() + { + bool cima = _teclasPressionadas.Contains(Keys.W); + bool baixo = _teclasPressionadas.Contains(Keys.S); + bool esquerda = _teclasPressionadas.Contains(Keys.A); + bool direita = _teclasPressionadas.Contains(Keys.D); + // O GeneralJoystick também anula pares opostos, mas fazemos isso aqui + // explicitamente para o estado do formulário representar a intenção real. + if (cima && baixo) + cima = baixo = false; + + if (esquerda && direita) + esquerda = direita = false; + + GeneralJoystick.AtualizarEstadoCameraTeclado( + cima, baixo, esquerda, direita); + } + + private void SupervisorTecladoTimer_Tick(object sender, EventArgs e) + { + // Movimento por teclado só é válido enquanto esta tela estiver visível e + // possuir foco (diretamente ou por um controle filho). + if (!Visible || !ContainsFocus) + { + if (ExisteTeclaMovimentoPressionada() || + ExisteTeclaCameraPressionada() || + GeneralJoystick.UltimaDirecao != Direcao.Parado || + GeneralJoystick.CameraEmMovimento) + { + LiberarTodosOsControles("supervisor detectou perda de foco"); + } + return; + } + + bool mudouMotor = false; + bool mudouCamera = false; + + SincronizarTeclaFisica(Keys.Up, Comando.Motor, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.Down, Comando.Motor, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.Left, Comando.Motor, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.Right, Comando.Motor, ref mudouMotor, ref mudouCamera); + + SincronizarTeclaFisica(Keys.W, Comando.Camera, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.S, Comando.Camera, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.A, Comando.Camera, ref mudouMotor, ref mudouCamera); + SincronizarTeclaFisica(Keys.D, Comando.Camera, ref mudouMotor, ref mudouCamera); + + if (mudouMotor) + AtualizarIntencaoMotores(); + + if (mudouCamera) + AtualizarIntencaoCamera(); + + if (ExisteTeclaMovimentoPressionada()) + GeneralJoystick.RenovarHeartbeatTecladoMovimento(); + + if (ExisteTeclaCameraPressionada()) + GeneralJoystick.RenovarHeartbeatTecladoCamera(); + } + + private void SincronizarTeclaFisica( + Keys tecla, + Comando comando, + ref bool mudouMotor, + ref bool mudouCamera) + { + bool fisicamentePressionada = TeclaFisicamentePressionada(tecla); + bool registrada = _teclasPressionadas.Contains(tecla); + + if (fisicamentePressionada == registrada) + return; + + if (fisicamentePressionada) + _teclasPressionadas.Add(tecla); + else + _teclasPressionadas.Remove(tecla); + + if (comando == Comando.Motor) + mudouMotor = true; + else if (comando == Comando.Camera) + mudouCamera = true; + } + + private bool ExisteTeclaMovimentoPressionada() + { + return _teclasPressionadas.Contains(Keys.Up) || + _teclasPressionadas.Contains(Keys.Down) || + _teclasPressionadas.Contains(Keys.Left) || + _teclasPressionadas.Contains(Keys.Right); + } + + private bool ExisteTeclaCameraPressionada() + { + foreach (Keys tecla in _teclasPressionadas) + { + if (GeneralJoystick.TipoControle(tecla) == + Comando.Camera) + { + return true; + } + } + + return false; + } + + private void ProcessarMedidorEspessura(Keys tecla) + { + bool carimbar = tecla == Keys.Q; + + if (!carimbar) + { + int angulo = + Variaveis.Angulo_MedidorEspessuraVertical == 0 + ? Variaveis.PerfilVigente.AnguloMedicaoIdeal + : 0; + + Variaveis.frmPrincipal + .trkMedidorEspessura.Value = angulo; + + GeneralJoystick + .AtualizaAnguloMedidorEspessura(angulo); + } + + GeneralJoystick + .AlternarAnguloMedidorEspessura(carimbar); + } + + private void LiberarTodosOsControles(string motivo) + { + bool haviaMovimento = + ExisteTeclaMovimentoPressionada() || + GeneralJoystick.UltimaDirecao != Direcao.Parado; + + bool haviaCamera = + ExisteTeclaCameraPressionada() || + GeneralJoystick.CameraEmMovimento; + + _teclasPressionadas.Clear(); + + GeneralJoystick.ListaTeclasPressionadas.Clear(); + GeneralJoystick.ListaTeclasPressionadas.Add("Stop"); + + if (haviaMovimento) + { + GeneralJoystick.ParadaSegura(motivo); + } + + if (haviaCamera) + { + GeneralJoystick.PararCameraSegura(motivo); + } + } + + private void frmControlecarro_Deactivate( + object sender, + EventArgs e) + { + LiberarTodosOsControles( + "tela de controle perdeu o foco"); + } + + private void frmControlecarro_VisibleChanged( + object sender, + EventArgs e) + { + if (!Visible) + { + LiberarTodosOsControles( + "tela de controle ocultada"); + } + } + + private void frmControlecarro_FormClosed(object sender, FormClosedEventArgs e) + { + _supervisorTecladoTimer?.Stop(); + _supervisorTecladoTimer?.Dispose(); + } + + private void frmControlecarro_FormClosing( + object sender, + FormClosingEventArgs e) + { + LiberarTodosOsControles( + "fechamento da tela de controle"); + + /* + * Durante o encerramento da aplicação precisamos permitir + * que o formulário seja realmente destruído. + */ + if (e.CloseReason != + CloseReason.UserClosing) + { + return; + } + + if (!_processandoFechamento && + !Variaveis.frmPrincipal.FechandoControle) + { + _processandoFechamento = true; + + try + { + Variaveis.frmPrincipal.FechandoControle = + true; + + Variaveis.frmPrincipal + .FecharControleAnalogico(); + } + finally + { + _processandoFechamento = false; + } + } + + e.Cancel = true; + Hide(); + } } -} +} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/Forms/frmInstancial.cs b/OrionTardPro/OrionTardPro/Forms/frmInstancial.cs index 08231e3..f61232b 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmInstancial.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmInstancial.cs @@ -75,22 +75,30 @@ namespace OrionTardPro SerialService.Inicializar(); GeneralJoystick.Inicializar(); Variaveis.CarregarDadosEmpresa(); + try { Variaveis.CarregarPerfil(); } - catch (Exception ex) + catch (Exception) { Variaveis.SetVariaveisGerais(true); } Variaveis.frmControle.ConfiguraJoystick(); - //Variaveis.frmPrincipal = new frmPrincipal(); - Variaveis.frmPrincipal.Show(); + Variaveis.frmPrincipal.FormClosed += FrmPrincipal_FormClosed; + Variaveis.frmPrincipal.Show(this); } - + private void FrmPrincipal_FormClosed(object sender, FormClosedEventArgs e) + { + GeneralJoystick.ParadaSegura("encerramento da aplicação"); + + Environment.Exit(0); + } + + } } diff --git a/OrionTardPro/OrionTardPro/Forms/frmLicenca.cs b/OrionTardPro/OrionTardPro/Forms/frmLicenca.cs index 3845121..d43e7b8 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmLicenca.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmLicenca.cs @@ -36,11 +36,12 @@ namespace OrionTardPro InitializeComponent(); } - private void frmLicenca_Load(object sender, EventArgs e) + private async void frmLicenca_Load(object sender, EventArgs e) { - //Console.WriteLine(GenerateSerialKey("425.855.768-41", "31032021")); - if (CheckLicense()) - this.Close(); + // A validação local é imediata; qualquer consulta de rede usa await + // e nunca bloqueia a message pump do WinForms. + if (await CheckLicenseAsync()) + Close(); } private void frmLicenca_FormClosing(object sender, FormClosingEventArgs e) @@ -54,7 +55,7 @@ namespace OrionTardPro } VersionamentoService.ForcarDownloadArquivos = true; - VersionamentoService.AtualizarArquivoVersionamento(true).ConfigureAwait(false); + _ = VersionamentoService.AtualizarArquivoVersionamento(true); } #endregion @@ -65,15 +66,29 @@ namespace OrionTardPro Cancelar(); } - private void btnValidar_Click(object sender, EventArgs e) + private async void btnValidar_Click(object sender, EventArgs e) { if (txtChave.Text == "") { - MessageBox.Show("Digite os dados do registro corretamente!", "Dados inválidos", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show( + "Digite os dados do registro corretamente!", + "Dados inválidos", + MessageBoxButtons.OK, + MessageBoxIcon.Error); return; } - if (Verify(true)) + + btnValidar.Enabled = false; + UseWaitCursor = true; + try + { + await VerifyAsync(true); + } + finally + { btnValidar.Enabled = true; + UseWaitCursor = false; + } } #endregion @@ -90,88 +105,74 @@ namespace OrionTardPro return Sair; } - private bool CheckLicense() + private async Task CheckLicenseAsync() { - bool isLicensed = false; - if (File.Exists(VariaveisGerais.CaminhoArquivoData)) { - string[] DateConfig = File.ReadAllLines(VariaveisGerais.CaminhoArquivoData, Encoding.UTF8); - DataInstalacao = DateConfig[1].Split(';')[1]; + string[] dateConfig = File.ReadAllLines( + VariaveisGerais.CaminhoArquivoData, + Encoding.UTF8); + + if (dateConfig.Length > 1 && dateConfig[1].Contains(";")) + DataInstalacao = dateConfig[1].Split(';')[1]; } - /*if (!File.Exists("C:\\Windows\\config_key.cnf")) - { - NomeCliente = "Versão de avaliação"; - CPFCliente = "132.658.664-33"; - ValidadeChave = DateTime.Now.AddDays(30).ToString(); - ValidadeChave = ValidadeChave.Substring(0, 10); - ValidadeChave = ValidadeChave[0].ToString() + - ValidadeChave[1].ToString() + - ValidadeChave[3].ToString() + - ValidadeChave[4].ToString() + - ValidadeChave[6].ToString() + - ValidadeChave[7].ToString() + - ValidadeChave[8].ToString() + - ValidadeChave[9].ToString(); - SerialKey = GenerateSerialKey(CPFCliente, ValidadeChave); - ValidadeChave = ValidadeChave[0].ToString() + ValidadeChave[1].ToString() + "-" + - ValidadeChave[2].ToString() + ValidadeChave[3].ToString() + "-" + - ValidadeChave[4].ToString() + ValidadeChave[5].ToString() + ValidadeChave[6].ToString() + ValidadeChave[7].ToString(); - WriteConfigFile(); - MessageBox.Show("Licença gratuita por 30 dias", "Licensa gratuita", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); - Licensed = true; - this.Close(); - isLicensed = true; - SetVariables(); - return isLicensed; - }*/ - if (File.Exists(VariaveisGerais.CaminhoArquivoLicenca)) - { - FileExists = true; - string[] Config = File.ReadAllLines(VariaveisGerais.CaminhoArquivoLicenca, Encoding.UTF8); - txtLicenciadopara.Text = Config[1].Split(';')[1]; - txtCNPJ.Text = Config[2].Split(';')[1]; - txtChave.Text = Config[3].Split(';')[1]; - txtExpira.Text = Config[4].Split(';')[1]; - Verify(false); - } + if (!File.Exists(VariaveisGerais.CaminhoArquivoLicenca)) + return false; - return isLicensed; + FileExists = true; + string[] config = File.ReadAllLines( + VariaveisGerais.CaminhoArquivoLicenca, + Encoding.UTF8); + + if (config.Length < 5) + return false; + + txtLicenciadopara.Text = config[1].Split(';')[1]; + txtCNPJ.Text = config[2].Split(';')[1]; + txtChave.Text = config[3].Split(';')[1]; + txtExpira.Text = config[4].Split(';')[1]; + + return await VerifyAsync(false); } - private bool Verify(bool MakeRequest) + private async Task VerifyAsync(bool MakeRequest) { bool LicenseIsOk = false; string ChaveAcesso = txtChave.Text; string CPF = txtCNPJ.Text; - string Validade = txtExpira.Text; - if (MakeRequest && HttpService.ServerUp) + if (MakeRequest && await HttpService.ServerUpAsync()) { - var Response = httpService.ValidaLicenca(ChaveAcesso); + HttpService.LicencaResponse response = + await httpService.ValidaLicencaAsync(ChaveAcesso); - if (Response == null || !Response.response.Any()) + if (response == null || + response.response == null || + !response.response.Any()) { - MessageBox.Show("Chave de acesso não encontrada ou já ativada em outra máquina!", "Inválido", MessageBoxButtons.OK, MessageBoxIcon.Warning); + MessageBox.Show( + "Chave de acesso não encontrada ou já ativada em outra máquina!", + "Inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); return false; } - Variaveis.licencaModel.Nome = Response.response.FirstOrDefault().Nome; - txtLicenciadopara.Text = Variaveis.licencaModel.Nome; - Variaveis.licencaModel.CPF = Response.response.FirstOrDefault().CPF; - Variaveis.licencaModel.Usada = Response.response.FirstOrDefault().Usada; - Variaveis.licencaModel.IDSistema = Response.response.FirstOrDefault().IDSistema; + LicencaModel licenca = response.response.First(); + Variaveis.licencaModel.Nome = licenca.Nome; + txtLicenciadopara.Text = licenca.Nome; + Variaveis.licencaModel.CPF = licenca.CPF; + Variaveis.licencaModel.Usada = licenca.Usada; + Variaveis.licencaModel.IDSistema = licenca.IDSistema; } - string CPFChave = GetCPFofKey(ChaveAcesso); - CPFChave = DecryptText(CPFChave); - string DataChave = GetDataofKey(ChaveAcesso); - DataChave = DecryptText(DataChave); - DateTime DataValidade = new DateTime(); - DataValidade = Convert.ToDateTime(DataChave); - DateTime DataInstalacaoVerify = new DateTime(); + string CPFChave = DecryptText(GetCPFofKey(ChaveAcesso)); + string DataChave = DecryptText(GetDataofKey(ChaveAcesso)); + DateTime DataValidade = Convert.ToDateTime(DataChave); + DateTime DataInstalacaoVerify; + try { DataInstalacaoVerify = Convert.ToDateTime(DataInstalacao); @@ -181,48 +182,54 @@ namespace OrionTardPro DataInstalacaoVerify = DateTime.Now.Date; } - if (FixedCPF(CPFChave) == FixedCPF(CPF) && DataValidade >= DateTime.Now) + if (FixedCPF(CPFChave) == FixedCPF(CPF) && + DataValidade >= DateTime.Now) { if (MakeRequest) { - if (Variaveis.licencaModel.Usada == true && MessageBox.Show("Sua chave de licença já está ativada em outra máquina! Deseja remover o acesso antigo para ativar esse computador?", "Ativação", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.No) + if (Variaveis.licencaModel.Usada && + MessageBox.Show( + "Sua chave de licença já está ativada em outra máquina! Deseja remover o acesso antigo para ativar esse computador?", + "Ativação", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning) == DialogResult.No) { return false; } - else + + if (!Variaveis.licencaModel.Usada && + await HttpService.ServerUpAsync()) { - if (!Variaveis.licencaModel.Usada && HttpService.ServerUp) - httpService.SetChaveUsada(ChaveAcesso); + await httpService.SetChaveUsadaAsync(ChaveAcesso); } - } - - if (File.Exists(VariaveisGerais.CaminhoArquivoLicenca)) + + if (File.Exists(VariaveisGerais.CaminhoArquivoLicenca) && + DateTime.Now < DataInstalacaoVerify) { - if (DateTime.Now < DataInstalacaoVerify) + if (MessageBox.Show( + "Sua chave de licença expirou! Deseja renovar sua licença agora?", + "Expirado", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning) == DialogResult.Yes) { - if (MessageBox.Show("Sua chave de licença expirou! Deseja renovar sua licença agora?", "Expirado", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) - { - Process.Start(VariaveisGerais.LinkSite + "/login"); - } - return LicenseIsOk; + Process.Start(VariaveisGerais.LinkSite + "/login"); } + + return false; } LicenseIsOk = true; SerialKey = txtChave.Text; - NomeCliente = (MakeRequest) ? Variaveis.licencaModel.Nome : txtLicenciadopara.Text; + NomeCliente = MakeRequest + ? Variaveis.licencaModel.Nome + : txtLicenciadopara.Text; ValidadeChave = DataChave; CPFCliente = CPF; txtExpira.Text = DataChave; Variaveis.ValidadeLicenca = ValidadeChave; - if (NomeCliente == "Versão de avaliação") - Variaveis.Licenciado = false; - else - Variaveis.Licenciado = true; - - WriteConfigFile(); + Variaveis.Licenciado = NomeCliente != "Versão de avaliação"; Variaveis.licencaModel.Nome = txtLicenciadopara.Text; Variaveis.licencaModel.CPF = txtCNPJ.Text; @@ -231,22 +238,50 @@ namespace OrionTardPro if (!FileExists) { - MessageBox.Show(VariaveisGerais.NomeProjeto + " licenciado com sucesso até a data " + ValidadeChave.Replace("-", "/"), "Sucesso", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); + MessageBox.Show( + VariaveisGerais.NomeProjeto + + " licenciado com sucesso até a data " + + ValidadeChave.Replace("-", "/"), + "Sucesso", + MessageBoxButtons.OK, + MessageBoxIcon.Asterisk); } - else + else if (DataValidade < DateTime.Now.AddDays(7)) { - if (DataValidade < DateTime.Now.AddDays(7)) - MessageBox.Show("Sua licença expira em " + (DataValidade.DayOfYear - DateTime.Now.DayOfYear).ToString() + " dias", "Validade", MessageBoxButtons.OK, MessageBoxIcon.Asterisk); + MessageBox.Show( + "Sua licença expira em " + + (DataValidade.DayOfYear - DateTime.Now.DayOfYear) + + " dias", + "Validade", + MessageBoxButtons.OK, + MessageBoxIcon.Asterisk); } + + // Escreve e fecha apenas depois de terminar de atualizar o estado + // e a interface. Evita acessar controles após Close(). + WriteConfigFile(); } else { if (FixedCPF(CPFChave) != FixedCPF(CPF)) - MessageBox.Show("Chave de licença inválida!", "Inválido", MessageBoxButtons.OK, MessageBoxIcon.Error); - if (DataValidade < DateTime.Now && (MessageBox.Show("Sua chave de licença expirou! Deseja renovar sua licença agora?", "Expirado", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes)) - Process.Start(VariaveisGerais.LinkSite + "/login"); - } + { + MessageBox.Show( + "Chave de licença inválida!", + "Inválido", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + if (DataValidade < DateTime.Now && + MessageBox.Show( + "Sua chave de licença expirou! Deseja renovar sua licença agora?", + "Expirado", + MessageBoxButtons.YesNo, + MessageBoxIcon.Warning) == DialogResult.Yes) + { + Process.Start(VariaveisGerais.LinkSite + "/login"); + } + } return LicenseIsOk; } diff --git a/OrionTardPro/OrionTardPro/Forms/frmParametros.Designer.cs b/OrionTardPro/OrionTardPro/Forms/frmParametros.Designer.cs index 8c467b3..ae34207 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmParametros.Designer.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmParametros.Designer.cs @@ -38,30 +38,32 @@ this.txtCorrentebateria = new System.Windows.Forms.TextBox(); this.lblCorrentebateria = new System.Windows.Forms.Label(); this.lblRPM = new System.Windows.Forms.Label(); - this.txtOffsetvelocidade = new System.Windows.Forms.TextBox(); this.lblPings = new System.Windows.Forms.Label(); this.txtPings = new System.Windows.Forms.NumericUpDown(); this.txtResolucao = new System.Windows.Forms.NumericUpDown(); this.lblResolucao = new System.Windows.Forms.Label(); this.gpbParametros = new System.Windows.Forms.GroupBox(); this.pnlParametros = new System.Windows.Forms.Panel(); + this.nudAnguloMedicaoMargemLimite = new System.Windows.Forms.NumericUpDown(); + this.lblAnguloMedicaoMargemLimite = new System.Windows.Forms.Label(); + this.nudAnguloCarimboVertical = new System.Windows.Forms.NumericUpDown(); + this.lblAnguloCarimboVertical = new System.Windows.Forms.Label(); + this.nudAnguloMedicaoIdeal = new System.Windows.Forms.NumericUpDown(); + this.lblAnguloMedicaoIdeal = new System.Windows.Forms.Label(); + this.nudAnguloCarimboHorizontal = new System.Windows.Forms.NumericUpDown(); + this.lblAnguloCarimboHorizontal = new System.Windows.Forms.Label(); this.nudCanalReleGeral = new System.Windows.Forms.NumericUpDown(); this.lblCanalReleGeral = new System.Windows.Forms.Label(); this.nudEnderecoReleGeral = new System.Windows.Forms.NumericUpDown(); this.lblEnderecoReleGeral = new System.Windows.Forms.Label(); - this.txtMultiplicadorCorrente = new System.Windows.Forms.TextBox(); this.lblOdometroInspecaoHrs = new System.Windows.Forms.Label(); this.nudOdometroInspecao = new System.Windows.Forms.NumericUpDown(); this.lblOdometroInspecao = new System.Windows.Forms.Label(); this.lblRampaUnidade = new System.Windows.Forms.Label(); this.lblPotenciaUnidade = new System.Windows.Forms.Label(); this.lblVelocidadeMax = new System.Windows.Forms.Label(); - this.txtCoefB = new System.Windows.Forms.TextBox(); - this.lblCoefB = new System.Windows.Forms.Label(); this.nudDiametroRoda = new System.Windows.Forms.NumericUpDown(); - this.txtCoefA = new System.Windows.Forms.TextBox(); this.lblDiametroRoda = new System.Windows.Forms.Label(); - this.lblCoefA = new System.Windows.Forms.Label(); this.txtPrecisaominima = new System.Windows.Forms.NumericUpDown(); this.nudRPM = new System.Windows.Forms.NumericUpDown(); this.lblPrecisaominima = new System.Windows.Forms.Label(); @@ -100,6 +102,8 @@ this.btnZerar = new System.Windows.Forms.Button(); this.gpbSensores = new System.Windows.Forms.GroupBox(); this.pnlSensores = new System.Windows.Forms.Panel(); + this.chbInfDebug = new System.Windows.Forms.CheckBox(); + this.chbInf = new System.Windows.Forms.CheckBox(); this.chbUmiDebug = new System.Windows.Forms.CheckBox(); this.chbWatchdogDebug = new System.Windows.Forms.CheckBox(); this.chbCorDebug = new System.Windows.Forms.CheckBox(); @@ -174,21 +178,20 @@ this.lblDesenrolar = new System.Windows.Forms.Label(); this.cmbEnrolar = new System.Windows.Forms.ComboBox(); this.lblEnrolar = new System.Windows.Forms.Label(); - this.gpbTipoPerfil = new System.Windows.Forms.GroupBox(); - this.rdbF4 = new System.Windows.Forms.RadioButton(); - this.rdbF3 = new System.Windows.Forms.RadioButton(); - this.nudAnguloCarimboHorizontal = new System.Windows.Forms.NumericUpDown(); - this.lblAnguloCarimboHorizontal = new System.Windows.Forms.Label(); - this.nudAnguloMedicaoIdeal = new System.Windows.Forms.NumericUpDown(); - this.lblAnguloMedicaoIdeal = new System.Windows.Forms.Label(); - this.nudAnguloCarimboVertical = new System.Windows.Forms.NumericUpDown(); - this.lblAnguloCarimboVertical = new System.Windows.Forms.Label(); - this.nudAnguloMedicaoMargemLimite = new System.Windows.Forms.NumericUpDown(); - this.lblAnguloMedicaoMargemLimite = new System.Windows.Forms.Label(); + this.gpbVersaoProtocolo = new System.Windows.Forms.GroupBox(); + this.cmbProtocoloVersao = new System.Windows.Forms.ComboBox(); + this.lblOffsetVelocidade = new System.Windows.Forms.Label(); + this.lblMultiplicadorCorrente = new System.Windows.Forms.Label(); + this.txtOffsetvelocidade = new System.Windows.Forms.TextBox(); + this.txtMultiplicadorCorrente = new System.Windows.Forms.TextBox(); ((System.ComponentModel.ISupportInitialize)(this.txtPings)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.txtResolucao)).BeginInit(); this.gpbParametros.SuspendLayout(); this.pnlParametros.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoMargemLimite)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboVertical)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoIdeal)).BeginInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboHorizontal)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudCanalReleGeral)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudEnderecoReleGeral)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOdometroInspecao)).BeginInit(); @@ -222,11 +225,7 @@ this.gpbRaqueMotor.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetDesenrolar)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetEnrolar)).BeginInit(); - this.gpbTipoPerfil.SuspendLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboHorizontal)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoIdeal)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboVertical)).BeginInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoMargemLimite)).BeginInit(); + this.gpbVersaoProtocolo.SuspendLayout(); this.SuspendLayout(); // // lblDescricao @@ -299,9 +298,9 @@ this.lblCorrentebateria.AutoSize = true; this.lblCorrentebateria.Location = new System.Drawing.Point(20, 109); this.lblCorrentebateria.Name = "lblCorrentebateria"; - this.lblCorrentebateria.Size = new System.Drawing.Size(117, 16); + this.lblCorrentebateria.Size = new System.Drawing.Size(137, 16); this.lblCorrentebateria.TabIndex = 9; - this.lblCorrentebateria.Text = "Corrente da bateria"; + this.lblCorrentebateria.Text = "Capacidade da bateria"; // // lblRPM // @@ -312,20 +311,10 @@ this.lblRPM.TabIndex = 11; this.lblRPM.Text = "RPM"; // - // txtOffsetvelocidade - // - this.txtOffsetvelocidade.Location = new System.Drawing.Point(305, 173); - this.txtOffsetvelocidade.Name = "txtOffsetvelocidade"; - this.txtOffsetvelocidade.Size = new System.Drawing.Size(28, 23); - this.txtOffsetvelocidade.TabIndex = 10; - this.txtOffsetvelocidade.Text = "0"; - this.txtOffsetvelocidade.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.txtOffsetvelocidade.Visible = false; - // // lblPings // this.lblPings.AutoSize = true; - this.lblPings.Location = new System.Drawing.Point(20, 209); + this.lblPings.Location = new System.Drawing.Point(20, 139); this.lblPings.Name = "lblPings"; this.lblPings.Size = new System.Drawing.Size(158, 16); this.lblPings.TabIndex = 13; @@ -333,7 +322,7 @@ // // txtPings // - this.txtPings.Location = new System.Drawing.Point(235, 207); + this.txtPings.Location = new System.Drawing.Point(235, 137); this.txtPings.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtPings.Maximum = new decimal(new int[] { 9999, @@ -352,7 +341,7 @@ // // txtResolucao // - this.txtResolucao.Location = new System.Drawing.Point(235, 242); + this.txtResolucao.Location = new System.Drawing.Point(235, 172); this.txtResolucao.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtResolucao.Maximum = new decimal(new int[] { 12, @@ -377,7 +366,7 @@ // lblResolucao // this.lblResolucao.AutoSize = true; - this.lblResolucao.Location = new System.Drawing.Point(20, 243); + this.lblResolucao.Location = new System.Drawing.Point(20, 173); this.lblResolucao.Name = "lblResolucao"; this.lblResolucao.Size = new System.Drawing.Size(107, 16); this.lblResolucao.TabIndex = 15; @@ -396,6 +385,10 @@ // pnlParametros // this.pnlParametros.AutoScroll = true; + this.pnlParametros.Controls.Add(this.txtMultiplicadorCorrente); + this.pnlParametros.Controls.Add(this.txtOffsetvelocidade); + this.pnlParametros.Controls.Add(this.lblMultiplicadorCorrente); + this.pnlParametros.Controls.Add(this.lblOffsetVelocidade); this.pnlParametros.Controls.Add(this.nudAnguloMedicaoMargemLimite); this.pnlParametros.Controls.Add(this.lblAnguloMedicaoMargemLimite); this.pnlParametros.Controls.Add(this.nudAnguloCarimboVertical); @@ -408,19 +401,14 @@ this.pnlParametros.Controls.Add(this.lblCanalReleGeral); this.pnlParametros.Controls.Add(this.nudEnderecoReleGeral); this.pnlParametros.Controls.Add(this.lblEnderecoReleGeral); - this.pnlParametros.Controls.Add(this.txtMultiplicadorCorrente); this.pnlParametros.Controls.Add(this.lblOdometroInspecaoHrs); this.pnlParametros.Controls.Add(this.nudOdometroInspecao); this.pnlParametros.Controls.Add(this.lblOdometroInspecao); this.pnlParametros.Controls.Add(this.lblRampaUnidade); this.pnlParametros.Controls.Add(this.lblPotenciaUnidade); this.pnlParametros.Controls.Add(this.lblVelocidadeMax); - this.pnlParametros.Controls.Add(this.txtCoefB); - this.pnlParametros.Controls.Add(this.lblCoefB); this.pnlParametros.Controls.Add(this.nudDiametroRoda); - this.pnlParametros.Controls.Add(this.txtCoefA); this.pnlParametros.Controls.Add(this.lblDiametroRoda); - this.pnlParametros.Controls.Add(this.lblCoefA); this.pnlParametros.Controls.Add(this.txtPrecisaominima); this.pnlParametros.Controls.Add(this.nudRPM); this.pnlParametros.Controls.Add(this.lblPrecisaominima); @@ -438,7 +426,6 @@ this.pnlParametros.Controls.Add(this.txtRampamaxima); this.pnlParametros.Controls.Add(this.lbltensaomaxima); this.pnlParametros.Controls.Add(this.lblRampamaxima); - this.pnlParametros.Controls.Add(this.txtOffsetvelocidade); this.pnlParametros.Controls.Add(this.txtTensaominima); this.pnlParametros.Controls.Add(this.txtPotenciaminima); this.pnlParametros.Controls.Add(this.lblPotenciaminima); @@ -461,9 +448,121 @@ this.pnlParametros.Size = new System.Drawing.Size(367, 378); this.pnlParametros.TabIndex = 36; // + // nudAnguloMedicaoMargemLimite + // + this.nudAnguloMedicaoMargemLimite.Location = new System.Drawing.Point(235, 533); + this.nudAnguloMedicaoMargemLimite.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.nudAnguloMedicaoMargemLimite.Maximum = new decimal(new int[] { + 180, + 0, + 0, + 0}); + this.nudAnguloMedicaoMargemLimite.Name = "nudAnguloMedicaoMargemLimite"; + this.nudAnguloMedicaoMargemLimite.Size = new System.Drawing.Size(60, 23); + this.nudAnguloMedicaoMargemLimite.TabIndex = 64; + this.nudAnguloMedicaoMargemLimite.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.nudAnguloMedicaoMargemLimite.Value = new decimal(new int[] { + 20, + 0, + 0, + 0}); + // + // lblAnguloMedicaoMargemLimite + // + this.lblAnguloMedicaoMargemLimite.AutoSize = true; + this.lblAnguloMedicaoMargemLimite.Location = new System.Drawing.Point(20, 535); + this.lblAnguloMedicaoMargemLimite.Name = "lblAnguloMedicaoMargemLimite"; + this.lblAnguloMedicaoMargemLimite.Size = new System.Drawing.Size(199, 16); + this.lblAnguloMedicaoMargemLimite.TabIndex = 63; + this.lblAnguloMedicaoMargemLimite.Text = "Margem Medição Margem Limite"; + // + // nudAnguloCarimboVertical + // + this.nudAnguloCarimboVertical.Location = new System.Drawing.Point(235, 471); + this.nudAnguloCarimboVertical.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.nudAnguloCarimboVertical.Maximum = new decimal(new int[] { + 180, + 0, + 0, + 0}); + this.nudAnguloCarimboVertical.Name = "nudAnguloCarimboVertical"; + this.nudAnguloCarimboVertical.Size = new System.Drawing.Size(60, 23); + this.nudAnguloCarimboVertical.TabIndex = 62; + this.nudAnguloCarimboVertical.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.nudAnguloCarimboVertical.Value = new decimal(new int[] { + 63, + 0, + 0, + 0}); + // + // lblAnguloCarimboVertical + // + this.lblAnguloCarimboVertical.AutoSize = true; + this.lblAnguloCarimboVertical.Location = new System.Drawing.Point(20, 473); + this.lblAnguloCarimboVertical.Name = "lblAnguloCarimboVertical"; + this.lblAnguloCarimboVertical.Size = new System.Drawing.Size(145, 16); + this.lblAnguloCarimboVertical.TabIndex = 61; + this.lblAnguloCarimboVertical.Text = "Angulo Carimbo Vertical"; + // + // nudAnguloMedicaoIdeal + // + this.nudAnguloMedicaoIdeal.Location = new System.Drawing.Point(235, 502); + this.nudAnguloMedicaoIdeal.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.nudAnguloMedicaoIdeal.Maximum = new decimal(new int[] { + 180, + 0, + 0, + 0}); + this.nudAnguloMedicaoIdeal.Name = "nudAnguloMedicaoIdeal"; + this.nudAnguloMedicaoIdeal.Size = new System.Drawing.Size(60, 23); + this.nudAnguloMedicaoIdeal.TabIndex = 60; + this.nudAnguloMedicaoIdeal.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.nudAnguloMedicaoIdeal.Value = new decimal(new int[] { + 132, + 0, + 0, + 0}); + // + // lblAnguloMedicaoIdeal + // + this.lblAnguloMedicaoIdeal.AutoSize = true; + this.lblAnguloMedicaoIdeal.Location = new System.Drawing.Point(20, 504); + this.lblAnguloMedicaoIdeal.Name = "lblAnguloMedicaoIdeal"; + this.lblAnguloMedicaoIdeal.Size = new System.Drawing.Size(133, 16); + this.lblAnguloMedicaoIdeal.TabIndex = 59; + this.lblAnguloMedicaoIdeal.Text = "Angulo Medição Ideal"; + // + // nudAnguloCarimboHorizontal + // + this.nudAnguloCarimboHorizontal.Location = new System.Drawing.Point(235, 440); + this.nudAnguloCarimboHorizontal.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.nudAnguloCarimboHorizontal.Maximum = new decimal(new int[] { + 180, + 0, + 0, + 0}); + this.nudAnguloCarimboHorizontal.Name = "nudAnguloCarimboHorizontal"; + this.nudAnguloCarimboHorizontal.Size = new System.Drawing.Size(60, 23); + this.nudAnguloCarimboHorizontal.TabIndex = 58; + this.nudAnguloCarimboHorizontal.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; + this.nudAnguloCarimboHorizontal.Value = new decimal(new int[] { + 67, + 0, + 0, + 0}); + // + // lblAnguloCarimboHorizontal + // + this.lblAnguloCarimboHorizontal.AutoSize = true; + this.lblAnguloCarimboHorizontal.Location = new System.Drawing.Point(20, 442); + this.lblAnguloCarimboHorizontal.Name = "lblAnguloCarimboHorizontal"; + this.lblAnguloCarimboHorizontal.Size = new System.Drawing.Size(162, 16); + this.lblAnguloCarimboHorizontal.TabIndex = 57; + this.lblAnguloCarimboHorizontal.Text = "Angulo Carimbo Horizontal"; + // // nudCanalReleGeral // - this.nudCanalReleGeral.Location = new System.Drawing.Point(235, 479); + this.nudCanalReleGeral.Location = new System.Drawing.Point(235, 409); this.nudCanalReleGeral.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.nudCanalReleGeral.Maximum = new decimal(new int[] { 2, @@ -488,7 +587,7 @@ // lblCanalReleGeral // this.lblCanalReleGeral.AutoSize = true; - this.lblCanalReleGeral.Location = new System.Drawing.Point(20, 481); + this.lblCanalReleGeral.Location = new System.Drawing.Point(20, 411); this.lblCanalReleGeral.Name = "lblCanalReleGeral"; this.lblCanalReleGeral.Size = new System.Drawing.Size(100, 16); this.lblCanalReleGeral.TabIndex = 55; @@ -496,7 +595,7 @@ // // nudEnderecoReleGeral // - this.nudEnderecoReleGeral.Location = new System.Drawing.Point(235, 448); + this.nudEnderecoReleGeral.Location = new System.Drawing.Point(235, 378); this.nudEnderecoReleGeral.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.nudEnderecoReleGeral.Maximum = new decimal(new int[] { 255, @@ -511,25 +610,16 @@ // lblEnderecoReleGeral // this.lblEnderecoReleGeral.AutoSize = true; - this.lblEnderecoReleGeral.Location = new System.Drawing.Point(20, 450); + this.lblEnderecoReleGeral.Location = new System.Drawing.Point(20, 380); this.lblEnderecoReleGeral.Name = "lblEnderecoReleGeral"; this.lblEnderecoReleGeral.Size = new System.Drawing.Size(122, 16); this.lblEnderecoReleGeral.TabIndex = 53; this.lblEnderecoReleGeral.Text = "Endereço Relé Geral"; // - // txtMultiplicadorCorrente - // - this.txtMultiplicadorCorrente.Location = new System.Drawing.Point(305, 140); - this.txtMultiplicadorCorrente.Name = "txtMultiplicadorCorrente"; - this.txtMultiplicadorCorrente.Size = new System.Drawing.Size(28, 23); - this.txtMultiplicadorCorrente.TabIndex = 52; - this.txtMultiplicadorCorrente.Text = "1"; - this.txtMultiplicadorCorrente.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - // // lblOdometroInspecaoHrs // this.lblOdometroInspecaoHrs.AutoSize = true; - this.lblOdometroInspecaoHrs.Location = new System.Drawing.Point(298, 419); + this.lblOdometroInspecaoHrs.Location = new System.Drawing.Point(298, 349); this.lblOdometroInspecaoHrs.Name = "lblOdometroInspecaoHrs"; this.lblOdometroInspecaoHrs.Size = new System.Drawing.Size(23, 16); this.lblOdometroInspecaoHrs.TabIndex = 51; @@ -537,7 +627,7 @@ // // nudOdometroInspecao // - this.nudOdometroInspecao.Location = new System.Drawing.Point(235, 417); + this.nudOdometroInspecao.Location = new System.Drawing.Point(235, 347); this.nudOdometroInspecao.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.nudOdometroInspecao.Maximum = new decimal(new int[] { 99999, @@ -562,7 +652,7 @@ // lblOdometroInspecao // this.lblOdometroInspecao.AutoSize = true; - this.lblOdometroInspecao.Location = new System.Drawing.Point(20, 419); + this.lblOdometroInspecao.Location = new System.Drawing.Point(20, 349); this.lblOdometroInspecao.Name = "lblOdometroInspecao"; this.lblOdometroInspecao.Size = new System.Drawing.Size(150, 16); this.lblOdometroInspecao.TabIndex = 49; @@ -571,7 +661,7 @@ // lblRampaUnidade // this.lblRampaUnidade.AutoSize = true; - this.lblRampaUnidade.Location = new System.Drawing.Point(301, 349); + this.lblRampaUnidade.Location = new System.Drawing.Point(301, 279); this.lblRampaUnidade.Name = "lblRampaUnidade"; this.lblRampaUnidade.Size = new System.Drawing.Size(23, 16); this.lblRampaUnidade.TabIndex = 48; @@ -581,7 +671,7 @@ // this.lblPotenciaUnidade.AutoSize = true; this.lblPotenciaUnidade.Font = new System.Drawing.Font("Microsoft JhengHei", 7.8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); - this.lblPotenciaUnidade.Location = new System.Drawing.Point(299, 317); + this.lblPotenciaUnidade.Location = new System.Drawing.Point(299, 247); this.lblPotenciaUnidade.Name = "lblPotenciaUnidade"; this.lblPotenciaUnidade.Size = new System.Drawing.Size(34, 15); this.lblPotenciaUnidade.TabIndex = 47; @@ -596,24 +686,6 @@ this.lblVelocidadeMax.TabIndex = 24; this.lblVelocidadeMax.Text = "0,2 m/s"; // - // txtCoefB - // - this.txtCoefB.Location = new System.Drawing.Point(235, 173); - this.txtCoefB.Name = "txtCoefB"; - this.txtCoefB.Size = new System.Drawing.Size(60, 23); - this.txtCoefB.TabIndex = 6; - this.txtCoefB.Text = "-0,17"; - this.txtCoefB.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - // - // lblCoefB - // - this.lblCoefB.AutoSize = true; - this.lblCoefB.Location = new System.Drawing.Point(20, 176); - this.lblCoefB.Name = "lblCoefB"; - this.lblCoefB.Size = new System.Drawing.Size(133, 16); - this.lblCoefB.TabIndex = 45; - this.lblCoefB.Text = "Coeficiente B Corrente"; - // // nudDiametroRoda // this.nudDiametroRoda.Location = new System.Drawing.Point(198, 8); @@ -633,15 +705,6 @@ 0}); this.nudDiametroRoda.Leave += new System.EventHandler(this.nudDiametroRoda_Leave); // - // txtCoefA - // - this.txtCoefA.Location = new System.Drawing.Point(235, 140); - this.txtCoefA.Name = "txtCoefA"; - this.txtCoefA.Size = new System.Drawing.Size(60, 23); - this.txtCoefA.TabIndex = 5; - this.txtCoefA.Text = "0,097"; - this.txtCoefA.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - // // lblDiametroRoda // this.lblDiametroRoda.AutoSize = true; @@ -651,18 +714,9 @@ this.lblDiametroRoda.TabIndex = 46; this.lblDiametroRoda.Text = "Φ roda"; // - // lblCoefA - // - this.lblCoefA.AutoSize = true; - this.lblCoefA.Location = new System.Drawing.Point(20, 143); - this.lblCoefA.Name = "lblCoefA"; - this.lblCoefA.Size = new System.Drawing.Size(134, 16); - this.lblCoefA.TabIndex = 42; - this.lblCoefA.Text = "Coeficiente A Corrente"; - // // txtPrecisaominima // - this.txtPrecisaominima.Location = new System.Drawing.Point(92, 382); + this.txtPrecisaominima.Location = new System.Drawing.Point(92, 312); this.txtPrecisaominima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtPrecisaominima.Maximum = new decimal(new int[] { 10, @@ -706,7 +760,7 @@ // lblPrecisaominima // this.lblPrecisaominima.AutoSize = true; - this.lblPrecisaominima.Location = new System.Drawing.Point(20, 384); + this.lblPrecisaominima.Location = new System.Drawing.Point(20, 314); this.lblPrecisaominima.Name = "lblPrecisaominima"; this.lblPrecisaominima.Size = new System.Drawing.Size(49, 16); this.lblPrecisaominima.TabIndex = 39; @@ -714,7 +768,7 @@ // // txtPrecisaomaxima // - this.txtPrecisaomaxima.Location = new System.Drawing.Point(235, 382); + this.txtPrecisaomaxima.Location = new System.Drawing.Point(235, 312); this.txtPrecisaomaxima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtPrecisaomaxima.Maximum = new decimal(new int[] { 85, @@ -739,7 +793,7 @@ // lblPrecisaomaxima // this.lblPrecisaomaxima.AutoSize = true; - this.lblPrecisaomaxima.Location = new System.Drawing.Point(164, 384); + this.lblPrecisaomaxima.Location = new System.Drawing.Point(164, 314); this.lblPrecisaomaxima.Name = "lblPrecisaomaxima"; this.lblPrecisaomaxima.Size = new System.Drawing.Size(52, 16); this.lblPrecisaomaxima.TabIndex = 36; @@ -747,7 +801,7 @@ // // txtRampaminima // - this.txtRampaminima.Location = new System.Drawing.Point(92, 347); + this.txtRampaminima.Location = new System.Drawing.Point(92, 277); this.txtRampaminima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtRampaminima.Maximum = new decimal(new int[] { 10, @@ -772,7 +826,7 @@ // lblRampaminima // this.lblRampaminima.AutoSize = true; - this.lblRampaminima.Location = new System.Drawing.Point(20, 349); + this.lblRampaminima.Location = new System.Drawing.Point(20, 279); this.lblRampaminima.Name = "lblRampaminima"; this.lblRampaminima.Size = new System.Drawing.Size(58, 16); this.lblRampaminima.TabIndex = 33; @@ -780,7 +834,7 @@ // // txtRampamaxima // - this.txtRampamaxima.Location = new System.Drawing.Point(235, 347); + this.txtRampamaxima.Location = new System.Drawing.Point(235, 277); this.txtRampamaxima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtRampamaxima.Maximum = new decimal(new int[] { 10, @@ -805,7 +859,7 @@ // lblRampamaxima // this.lblRampamaxima.AutoSize = true; - this.lblRampamaxima.Location = new System.Drawing.Point(158, 349); + this.lblRampamaxima.Location = new System.Drawing.Point(158, 279); this.lblRampamaxima.Name = "lblRampamaxima"; this.lblRampamaxima.Size = new System.Drawing.Size(61, 16); this.lblRampamaxima.TabIndex = 30; @@ -813,7 +867,7 @@ // // txtPotenciaminima // - this.txtPotenciaminima.Location = new System.Drawing.Point(92, 312); + this.txtPotenciaminima.Location = new System.Drawing.Point(92, 242); this.txtPotenciaminima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtPotenciaminima.Maximum = new decimal(new int[] { 10, @@ -838,7 +892,7 @@ // lblPotenciaminima // this.lblPotenciaminima.AutoSize = true; - this.lblPotenciaminima.Location = new System.Drawing.Point(20, 314); + this.lblPotenciaminima.Location = new System.Drawing.Point(20, 244); this.lblPotenciaminima.Name = "lblPotenciaminima"; this.lblPotenciaminima.Size = new System.Drawing.Size(53, 16); this.lblPotenciaminima.TabIndex = 27; @@ -846,7 +900,7 @@ // // txtPotenciamaxima // - this.txtPotenciamaxima.Location = new System.Drawing.Point(235, 312); + this.txtPotenciamaxima.Location = new System.Drawing.Point(235, 242); this.txtPotenciamaxima.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtPotenciamaxima.Maximum = new decimal(new int[] { 10, @@ -871,7 +925,7 @@ // lblPotenciamaxima // this.lblPotenciamaxima.AutoSize = true; - this.lblPotenciamaxima.Location = new System.Drawing.Point(158, 314); + this.lblPotenciamaxima.Location = new System.Drawing.Point(158, 244); this.lblPotenciamaxima.Name = "lblPotenciamaxima"; this.lblPotenciamaxima.Size = new System.Drawing.Size(56, 16); this.lblPotenciamaxima.TabIndex = 24; @@ -880,7 +934,7 @@ // lblUcabo // this.lblUcabo.AutoSize = true; - this.lblUcabo.Location = new System.Drawing.Point(301, 279); + this.lblUcabo.Location = new System.Drawing.Point(301, 209); this.lblUcabo.Name = "lblUcabo"; this.lblUcabo.Size = new System.Drawing.Size(18, 16); this.lblUcabo.TabIndex = 23; @@ -897,7 +951,7 @@ // // txtComprimentocabo // - this.txtComprimentocabo.Location = new System.Drawing.Point(235, 277); + this.txtComprimentocabo.Location = new System.Drawing.Point(235, 207); this.txtComprimentocabo.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); this.txtComprimentocabo.Maximum = new decimal(new int[] { 9999, @@ -926,7 +980,7 @@ // lblComprimentocabo // this.lblComprimentocabo.AutoSize = true; - this.lblComprimentocabo.Location = new System.Drawing.Point(20, 279); + this.lblComprimentocabo.Location = new System.Drawing.Point(20, 209); this.lblComprimentocabo.Name = "lblComprimentocabo"; this.lblComprimentocabo.Size = new System.Drawing.Size(137, 16); this.lblComprimentocabo.TabIndex = 21; @@ -944,7 +998,7 @@ // lblUbits // this.lblUbits.AutoSize = true; - this.lblUbits.Location = new System.Drawing.Point(301, 243); + this.lblUbits.Location = new System.Drawing.Point(301, 173); this.lblUbits.Name = "lblUbits"; this.lblUbits.Size = new System.Drawing.Size(27, 16); this.lblUbits.TabIndex = 20; @@ -1130,6 +1184,8 @@ // pnlSensores // this.pnlSensores.AutoScroll = true; + this.pnlSensores.Controls.Add(this.chbInfDebug); + this.pnlSensores.Controls.Add(this.chbInf); this.pnlSensores.Controls.Add(this.chbUmiDebug); this.pnlSensores.Controls.Add(this.chbWatchdogDebug); this.pnlSensores.Controls.Add(this.chbCorDebug); @@ -1154,6 +1210,26 @@ this.pnlSensores.Size = new System.Drawing.Size(155, 256); this.pnlSensores.TabIndex = 13; // + // chbInfDebug + // + this.chbInfDebug.AutoSize = true; + this.chbInfDebug.Location = new System.Drawing.Point(4, 249); + this.chbInfDebug.Name = "chbInfDebug"; + this.chbInfDebug.Size = new System.Drawing.Size(15, 14); + this.chbInfDebug.TabIndex = 18; + this.chbInfDebug.UseVisualStyleBackColor = true; + this.chbInfDebug.Visible = false; + // + // chbInf + // + this.chbInf.AutoSize = true; + this.chbInf.Location = new System.Drawing.Point(25, 246); + this.chbInf.Name = "chbInf"; + this.chbInf.Size = new System.Drawing.Size(82, 20); + this.chbInf.TabIndex = 17; + this.chbInf.Text = "Infiltração"; + this.chbInf.UseVisualStyleBackColor = true; + // // chbUmiDebug // this.chbUmiDebug.AutoSize = true; @@ -1935,157 +2011,67 @@ this.lblEnrolar.TabIndex = 0; this.lblEnrolar.Text = "Enrolar"; // - // gpbTipoPerfil + // gpbVersaoProtocolo // - this.gpbTipoPerfil.Controls.Add(this.rdbF4); - this.gpbTipoPerfil.Controls.Add(this.rdbF3); - this.gpbTipoPerfil.Location = new System.Drawing.Point(191, 418); - this.gpbTipoPerfil.Name = "gpbTipoPerfil"; - this.gpbTipoPerfil.Size = new System.Drawing.Size(194, 65); - this.gpbTipoPerfil.TabIndex = 7; - this.gpbTipoPerfil.TabStop = false; - this.gpbTipoPerfil.Text = "Tipo de Perfil"; + this.gpbVersaoProtocolo.Controls.Add(this.cmbProtocoloVersao); + this.gpbVersaoProtocolo.Location = new System.Drawing.Point(191, 418); + this.gpbVersaoProtocolo.Name = "gpbVersaoProtocolo"; + this.gpbVersaoProtocolo.Size = new System.Drawing.Size(194, 65); + this.gpbVersaoProtocolo.TabIndex = 7; + this.gpbVersaoProtocolo.TabStop = false; + this.gpbVersaoProtocolo.Text = "Versão Protocolo"; // - // rdbF4 + // cmbProtocoloVersao // - this.rdbF4.AutoSize = true; - this.rdbF4.Location = new System.Drawing.Point(109, 30); - this.rdbF4.Name = "rdbF4"; - this.rdbF4.Size = new System.Drawing.Size(59, 20); - this.rdbF4.TabIndex = 1; - this.rdbF4.Text = ">= F4"; - this.rdbF4.UseVisualStyleBackColor = true; + this.cmbProtocoloVersao.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.cmbProtocoloVersao.FormattingEnabled = true; + this.cmbProtocoloVersao.Location = new System.Drawing.Point(6, 24); + this.cmbProtocoloVersao.Name = "cmbProtocoloVersao"; + this.cmbProtocoloVersao.Size = new System.Drawing.Size(182, 24); + this.cmbProtocoloVersao.TabIndex = 0; // - // rdbF3 + // lblOffsetVelocidade // - this.rdbF3.AutoSize = true; - this.rdbF3.Checked = true; - this.rdbF3.Location = new System.Drawing.Point(17, 30); - this.rdbF3.Name = "rdbF3"; - this.rdbF3.Size = new System.Drawing.Size(59, 20); - this.rdbF3.TabIndex = 0; - this.rdbF3.TabStop = true; - this.rdbF3.Text = "<= F3"; - this.rdbF3.UseVisualStyleBackColor = true; + this.lblOffsetVelocidade.AutoSize = true; + this.lblOffsetVelocidade.Location = new System.Drawing.Point(20, 566); + this.lblOffsetVelocidade.Name = "lblOffsetVelocidade"; + this.lblOffsetVelocidade.Size = new System.Drawing.Size(109, 16); + this.lblOffsetVelocidade.TabIndex = 67; + this.lblOffsetVelocidade.Text = "Offset Velocidade"; // - // nudAnguloCarimboHorizontal + // lblMultiplicadorCorrente // - this.nudAnguloCarimboHorizontal.Location = new System.Drawing.Point(235, 510); - this.nudAnguloCarimboHorizontal.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.nudAnguloCarimboHorizontal.Maximum = new decimal(new int[] { - 180, - 0, - 0, - 0}); - this.nudAnguloCarimboHorizontal.Name = "nudAnguloCarimboHorizontal"; - this.nudAnguloCarimboHorizontal.Size = new System.Drawing.Size(60, 23); - this.nudAnguloCarimboHorizontal.TabIndex = 58; - this.nudAnguloCarimboHorizontal.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.nudAnguloCarimboHorizontal.Value = new decimal(new int[] { - 67, - 0, - 0, - 0}); + this.lblMultiplicadorCorrente.AutoSize = true; + this.lblMultiplicadorCorrente.Location = new System.Drawing.Point(20, 595); + this.lblMultiplicadorCorrente.Name = "lblMultiplicadorCorrente"; + this.lblMultiplicadorCorrente.Size = new System.Drawing.Size(135, 16); + this.lblMultiplicadorCorrente.TabIndex = 68; + this.lblMultiplicadorCorrente.Text = "Multiplicador Corrente"; // - // lblAnguloCarimboHorizontal + // txtOffsetvelocidade // - this.lblAnguloCarimboHorizontal.AutoSize = true; - this.lblAnguloCarimboHorizontal.Location = new System.Drawing.Point(20, 512); - this.lblAnguloCarimboHorizontal.Name = "lblAnguloCarimboHorizontal"; - this.lblAnguloCarimboHorizontal.Size = new System.Drawing.Size(162, 16); - this.lblAnguloCarimboHorizontal.TabIndex = 57; - this.lblAnguloCarimboHorizontal.Text = "Angulo Carimbo Horizontal"; + this.txtOffsetvelocidade.Location = new System.Drawing.Point(235, 563); + this.txtOffsetvelocidade.Name = "txtOffsetvelocidade"; + this.txtOffsetvelocidade.Size = new System.Drawing.Size(60, 23); + this.txtOffsetvelocidade.TabIndex = 69; + this.txtOffsetvelocidade.Text = "0"; + this.txtOffsetvelocidade.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; // - // nudAnguloMedicaoIdeal + // txtMultiplicadorCorrente // - this.nudAnguloMedicaoIdeal.Location = new System.Drawing.Point(235, 572); - this.nudAnguloMedicaoIdeal.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.nudAnguloMedicaoIdeal.Maximum = new decimal(new int[] { - 180, - 0, - 0, - 0}); - this.nudAnguloMedicaoIdeal.Name = "nudAnguloMedicaoIdeal"; - this.nudAnguloMedicaoIdeal.Size = new System.Drawing.Size(60, 23); - this.nudAnguloMedicaoIdeal.TabIndex = 60; - this.nudAnguloMedicaoIdeal.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.nudAnguloMedicaoIdeal.Value = new decimal(new int[] { - 132, - 0, - 0, - 0}); - // - // lblAnguloMedicaoIdeal - // - this.lblAnguloMedicaoIdeal.AutoSize = true; - this.lblAnguloMedicaoIdeal.Location = new System.Drawing.Point(20, 574); - this.lblAnguloMedicaoIdeal.Name = "lblAnguloMedicaoIdeal"; - this.lblAnguloMedicaoIdeal.Size = new System.Drawing.Size(133, 16); - this.lblAnguloMedicaoIdeal.TabIndex = 59; - this.lblAnguloMedicaoIdeal.Text = "Angulo Medição Ideal"; - // - // nudAnguloCarimboVertical - // - this.nudAnguloCarimboVertical.Location = new System.Drawing.Point(235, 541); - this.nudAnguloCarimboVertical.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.nudAnguloCarimboVertical.Maximum = new decimal(new int[] { - 180, - 0, - 0, - 0}); - this.nudAnguloCarimboVertical.Name = "nudAnguloCarimboVertical"; - this.nudAnguloCarimboVertical.Size = new System.Drawing.Size(60, 23); - this.nudAnguloCarimboVertical.TabIndex = 62; - this.nudAnguloCarimboVertical.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.nudAnguloCarimboVertical.Value = new decimal(new int[] { - 63, - 0, - 0, - 0}); - // - // lblAnguloCarimboVertical - // - this.lblAnguloCarimboVertical.AutoSize = true; - this.lblAnguloCarimboVertical.Location = new System.Drawing.Point(20, 543); - this.lblAnguloCarimboVertical.Name = "lblAnguloCarimboVertical"; - this.lblAnguloCarimboVertical.Size = new System.Drawing.Size(145, 16); - this.lblAnguloCarimboVertical.TabIndex = 61; - this.lblAnguloCarimboVertical.Text = "Angulo Carimbo Vertical"; - // - // nudAnguloMedicaoMargemLimite - // - this.nudAnguloMedicaoMargemLimite.Location = new System.Drawing.Point(235, 603); - this.nudAnguloMedicaoMargemLimite.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); - this.nudAnguloMedicaoMargemLimite.Maximum = new decimal(new int[] { - 180, - 0, - 0, - 0}); - this.nudAnguloMedicaoMargemLimite.Name = "nudAnguloMedicaoMargemLimite"; - this.nudAnguloMedicaoMargemLimite.Size = new System.Drawing.Size(60, 23); - this.nudAnguloMedicaoMargemLimite.TabIndex = 64; - this.nudAnguloMedicaoMargemLimite.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; - this.nudAnguloMedicaoMargemLimite.Value = new decimal(new int[] { - 20, - 0, - 0, - 0}); - // - // lblAnguloMedicaoMargemLimite - // - this.lblAnguloMedicaoMargemLimite.AutoSize = true; - this.lblAnguloMedicaoMargemLimite.Location = new System.Drawing.Point(20, 605); - this.lblAnguloMedicaoMargemLimite.Name = "lblAnguloMedicaoMargemLimite"; - this.lblAnguloMedicaoMargemLimite.Size = new System.Drawing.Size(199, 16); - this.lblAnguloMedicaoMargemLimite.TabIndex = 63; - this.lblAnguloMedicaoMargemLimite.Text = "Margem Medição Margem Limite"; + this.txtMultiplicadorCorrente.Location = new System.Drawing.Point(235, 592); + this.txtMultiplicadorCorrente.Name = "txtMultiplicadorCorrente"; + this.txtMultiplicadorCorrente.Size = new System.Drawing.Size(60, 23); + this.txtMultiplicadorCorrente.TabIndex = 70; + this.txtMultiplicadorCorrente.Text = "1"; + this.txtMultiplicadorCorrente.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; // // frmParametros // this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 16F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(1130, 490); - this.Controls.Add(this.gpbTipoPerfil); + this.Controls.Add(this.gpbVersaoProtocolo); this.Controls.Add(this.btnSalvarMotores); this.Controls.Add(this.btnDeFabrica); this.Controls.Add(this.gpbRaque); @@ -2110,6 +2096,10 @@ this.gpbParametros.ResumeLayout(false); this.pnlParametros.ResumeLayout(false); this.pnlParametros.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoMargemLimite)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboVertical)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoIdeal)).EndInit(); + ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboHorizontal)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudCanalReleGeral)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudEnderecoReleGeral)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOdometroInspecao)).EndInit(); @@ -2153,12 +2143,7 @@ this.gpbRaqueMotor.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetDesenrolar)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.nudOffsetEnrolar)).EndInit(); - this.gpbTipoPerfil.ResumeLayout(false); - this.gpbTipoPerfil.PerformLayout(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboHorizontal)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoIdeal)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloCarimboVertical)).EndInit(); - ((System.ComponentModel.ISupportInitialize)(this.nudAnguloMedicaoMargemLimite)).EndInit(); + this.gpbVersaoProtocolo.ResumeLayout(false); this.ResumeLayout(false); } @@ -2229,7 +2214,6 @@ public System.Windows.Forms.TextBox txtTensaominima; public System.Windows.Forms.TextBox txtTensaomaxima; public System.Windows.Forms.TextBox txtCorrentebateria; - public System.Windows.Forms.TextBox txtOffsetvelocidade; public System.Windows.Forms.NumericUpDown txtPings; public System.Windows.Forms.NumericUpDown txtResolucao; public System.Windows.Forms.NumericUpDown txtComprimentocabo; @@ -2255,10 +2239,6 @@ public System.Windows.Forms.NumericUpDown nudDT1Offset; public System.Windows.Forms.NumericUpDown nudDF2Offset; public System.Windows.Forms.NumericUpDown nudDF1Offset; - public System.Windows.Forms.TextBox txtCoefB; - private System.Windows.Forms.Label lblCoefB; - public System.Windows.Forms.TextBox txtCoefA; - private System.Windows.Forms.Label lblCoefA; private System.Windows.Forms.Panel pnlAtuadores; public System.Windows.Forms.CheckBox chbRaque; private System.Windows.Forms.GroupBox gpbRaque; @@ -2301,10 +2281,7 @@ public System.Windows.Forms.NumericUpDown nudOdometroInspecao; private System.Windows.Forms.Label lblOdometroInspecao; private System.Windows.Forms.Label lblOdometroInspecaoHrs; - public System.Windows.Forms.TextBox txtMultiplicadorCorrente; - private System.Windows.Forms.GroupBox gpbTipoPerfil; - public System.Windows.Forms.RadioButton rdbF4; - public System.Windows.Forms.RadioButton rdbF3; + private System.Windows.Forms.GroupBox gpbVersaoProtocolo; public System.Windows.Forms.NumericUpDown nudCanalReleGeral; private System.Windows.Forms.Label lblCanalReleGeral; public System.Windows.Forms.NumericUpDown nudEnderecoReleGeral; @@ -2320,5 +2297,12 @@ private System.Windows.Forms.Label lblAnguloMedicaoIdeal; public System.Windows.Forms.NumericUpDown nudAnguloCarimboHorizontal; private System.Windows.Forms.Label lblAnguloCarimboHorizontal; + public System.Windows.Forms.ComboBox cmbProtocoloVersao; + public System.Windows.Forms.CheckBox chbInfDebug; + public System.Windows.Forms.CheckBox chbInf; + private System.Windows.Forms.Label lblMultiplicadorCorrente; + private System.Windows.Forms.Label lblOffsetVelocidade; + public System.Windows.Forms.TextBox txtMultiplicadorCorrente; + public System.Windows.Forms.TextBox txtOffsetvelocidade; } } \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/Forms/frmParametros.cs b/OrionTardPro/OrionTardPro/Forms/frmParametros.cs index 7ad14db..9c66494 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmParametros.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmParametros.cs @@ -7,6 +7,8 @@ namespace OrionTardPro { public partial class frmParametros : Form { + private bool isAdm => Variaveis.licencaModel.CPF == "425.855.768-41"; + public frmParametros() { InitializeComponent(); @@ -14,6 +16,10 @@ namespace OrionTardPro private void frmParametros_Load(object sender, EventArgs e) { + cmbProtocoloVersao.Items.Clear(); + cmbProtocoloVersao.Items.AddRange(Enum.GetNames(typeof(VersaoProtocolo))); + cmbProtocoloVersao.SelectedItem = Variaveis.PerfilVigente.VersaoProtocolo.ToString(); + nudOdometroInspecao.Value = Variaveis.PerfilVigente.OdometroInspecao; txtPings.Value = Variaveis.PerfilVigente.NumeroPingsMaximo; nudRPM.Value = Convert.ToInt32(Variaveis.PerfilVigente.RPM); @@ -24,8 +30,6 @@ namespace OrionTardPro txtTensaominima.Text = Variaveis.PerfilVigente.TensaoMinimaBateria.ToString(); txtResolucao.Value = Variaveis.PerfilVigente.ResolucaoLeitura; txtCorrentebateria.Text = Variaveis.PerfilVigente.CorrenteMaximaBateria.ToString(); - txtCoefA.Text = Variaveis.PerfilVigente.COR_coeficienteA.ToString(); - txtCoefB.Text = Variaveis.PerfilVigente.COR_coeficienteB.ToString(); txtMultiplicadorCorrente.Text = Variaveis.PerfilVigente.COR_multiplicador.ToString(); txtComprimentocabo.Value = Convert.ToInt32(Variaveis.PerfilVigente.ComprimentoCabo); txtPotenciamaxima.Text = Variaveis.PerfilVigente.PotenciaMaxima.ToString(); @@ -40,8 +44,6 @@ namespace OrionTardPro nudDT2Offset.Value = Variaveis.PerfilVigente.Offsett_Driver2_Tras; nudOffsetEnrolar.Value = Variaveis.PerfilVigente.Offsett_Raque_Enrolar; nudOffsetDesenrolar.Value = Variaveis.PerfilVigente.Offsett_Raque_Desenrolar; - rdbF3.Checked = !Variaveis.PerfilVigente.FirmwareF4; - rdbF4.Checked = Variaveis.PerfilVigente.FirmwareF4; nudEnderecoReleGeral.Value = Variaveis.PerfilVigente.EnderecoReleGeral; nudCanalReleGeral.Value = Variaveis.PerfilVigente.CanalReleGeral; nudAnguloCarimboHorizontal.Value = Variaveis.PerfilVigente.AnguloCarimboHorizontal; @@ -68,7 +70,6 @@ namespace OrionTardPro chbBrc.Checked = Variaveis.PerfilVigente.Atuador_Ativado_BRC == Estado.Ligado; chbMep.Checked = Variaveis.PerfilVigente.Atuador_Ativado_MEP == Estado.Ligado; - bool isAdm = Variaveis.licencaModel.CPF == "425.855.768-41"; btnZerar.Enabled = isAdm; btnDefinir.Enabled = isAdm; @@ -141,8 +142,6 @@ namespace OrionTardPro Variaveis.PerfilVigente.ResolucaoLeitura = Convert.ToInt32(txtResolucao.Value); Variaveis.PerfilVigente.ValorMaximoAnalogico = Convert.ToInt32(Math.Pow(2, Variaveis.PerfilVigente.ResolucaoLeitura)); Variaveis.PerfilVigente.CorrenteMaximaBateria = Convert.ToDouble(txtCorrentebateria.Text); - Variaveis.PerfilVigente.COR_coeficienteA = Convert.ToDouble(txtCoefA.Text); - Variaveis.PerfilVigente.COR_coeficienteB = Convert.ToDouble(txtCoefB.Text); Variaveis.PerfilVigente.COR_multiplicador = Convert.ToDouble(txtMultiplicadorCorrente.Text); Variaveis.PerfilVigente.ComprimentoCabo = Convert.ToInt32(txtComprimentocabo.Value); Variaveis.PerfilVigente.PotenciaMaxima = Convert.ToInt32(txtPotenciamaxima.Value); @@ -163,7 +162,11 @@ namespace OrionTardPro Variaveis.PerfilVigente.InverterRotacaoEsquerda = chbInvEsquerda.Checked; Variaveis.PerfilVigente.OdometroInspecao = Convert.ToInt32(nudOdometroInspecao.Value); - Variaveis.PerfilVigente.FirmwareF4 = rdbF4.Checked; + Variaveis.PerfilVigente.VersaoProtocolo = + (VersaoProtocolo)Enum.Parse( + typeof(VersaoProtocolo), + cmbProtocoloVersao.SelectedItem.ToString() + ); Variaveis.PerfilVigente.EnderecoReleGeral = Convert.ToByte(nudEnderecoReleGeral.Value); Variaveis.PerfilVigente.CanalReleGeral = Convert.ToInt32(nudCanalReleGeral.Value); @@ -178,6 +181,7 @@ namespace OrionTardPro private void btnZerar_Click(object sender, EventArgs e) { + if (!isAdm) return; if (MessageBox.Show("Tem certeza que deseja resetar todos os odômetros?", "Confirmação", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) == DialogResult.Yes) { Variaveis.FuncaoOdometro = Estado.Reset; @@ -253,6 +257,7 @@ namespace OrionTardPro private void btnDefinir_Click(object sender, EventArgs e) { + if (!isAdm) return; if (MessageBox.Show("Tem certeza que deseja redefinir o valor atual do odometro " + cmbTipoodometro.Text + " para " + nudOdoValor.Value.ToString() + "?", "Redefinir valor do odometro", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { Variaveis.FuncaoOdometro = Estado.Desligado; diff --git a/OrionTardPro/OrionTardPro/Forms/frmPrincipal.Designer.cs b/OrionTardPro/OrionTardPro/Forms/frmPrincipal.Designer.cs index 768ebf8..77265a8 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmPrincipal.Designer.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmPrincipal.Designer.cs @@ -60,6 +60,9 @@ this.pnlEsquerdo = new System.Windows.Forms.Panel(); this.tabControl1 = new System.Windows.Forms.TabControl(); this.tabSensores = new System.Windows.Forms.TabPage(); + this.gpbInfiltracao = new System.Windows.Forms.GroupBox(); + this.lblInfiltracaoEstado = new System.Windows.Forms.Label(); + this.lblInfiltracaoPercentual = new System.Windows.Forms.Label(); this.gpbOdometro = new System.Windows.Forms.GroupBox(); this.lblOdometroTempo = new System.Windows.Forms.Label(); this.imgOdometro = new System.Windows.Forms.PictureBox(); @@ -92,6 +95,7 @@ this.imgLampGasVerm = new System.Windows.Forms.PictureBox(); this.tabControle = new System.Windows.Forms.TabPage(); this.gpbMedidorEspessura = new System.Windows.Forms.GroupBox(); + this.btnMedir = new System.Windows.Forms.Button(); this.lblMepStatus = new System.Windows.Forms.Label(); this.imgMepContato = new System.Windows.Forms.PictureBox(); this.btnCarimbar = new System.Windows.Forms.Button(); @@ -154,7 +158,6 @@ this.contextMenuStrip1 = new System.Windows.Forms.ContextMenuStrip(this.components); this.tmrTempoAtivo = new System.Windows.Forms.Timer(this.components); this.tmrVelocidade = new System.Windows.Forms.Timer(this.components); - this.btnMedir = new System.Windows.Forms.Button(); this.lblCarroconexao = new System.Windows.Forms.ToolStripStatusLabel(); this.lblJoystickconexao = new System.Windows.Forms.ToolStripStatusLabel(); this.lblCameraconexao = new System.Windows.Forms.ToolStripStatusLabel(); @@ -173,6 +176,7 @@ this.pnlEsquerdo.SuspendLayout(); this.tabControl1.SuspendLayout(); this.tabSensores.SuspendLayout(); + this.gpbInfiltracao.SuspendLayout(); this.gpbOdometro.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.imgOdometro)).BeginInit(); this.gpbAltitude.SuspendLayout(); @@ -246,7 +250,7 @@ this.menuStrip1.Location = new System.Drawing.Point(0, 0); this.menuStrip1.Name = "menuStrip1"; this.menuStrip1.Padding = new System.Windows.Forms.Padding(4, 2, 0, 2); - this.menuStrip1.Size = new System.Drawing.Size(1294, 24); + this.menuStrip1.Size = new System.Drawing.Size(1358, 24); this.menuStrip1.TabIndex = 0; this.menuStrip1.Text = "menuStrip1"; // @@ -488,6 +492,7 @@ // tabSensores // this.tabSensores.AutoScroll = true; + this.tabSensores.Controls.Add(this.gpbInfiltracao); this.tabSensores.Controls.Add(this.gpbOdometro); this.tabSensores.Controls.Add(this.gpbAltitude); this.tabSensores.Controls.Add(this.gpbInclinacao); @@ -503,12 +508,43 @@ this.tabSensores.Text = "Monitoramento"; this.tabSensores.UseVisualStyleBackColor = true; // + // gpbInfiltracao + // + this.gpbInfiltracao.Controls.Add(this.lblInfiltracaoEstado); + this.gpbInfiltracao.Controls.Add(this.lblInfiltracaoPercentual); + this.gpbInfiltracao.Location = new System.Drawing.Point(4, 576); + this.gpbInfiltracao.Name = "gpbInfiltracao"; + this.gpbInfiltracao.Size = new System.Drawing.Size(178, 80); + this.gpbInfiltracao.TabIndex = 4; + this.gpbInfiltracao.TabStop = false; + this.gpbInfiltracao.Text = "Infiltração"; + // + // lblInfiltracaoEstado + // + this.lblInfiltracaoEstado.AutoSize = true; + this.lblInfiltracaoEstado.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblInfiltracaoEstado.Location = new System.Drawing.Point(9, 45); + this.lblInfiltracaoEstado.Name = "lblInfiltracaoEstado"; + this.lblInfiltracaoEstado.Size = new System.Drawing.Size(105, 20); + this.lblInfiltracaoEstado.TabIndex = 30; + this.lblInfiltracaoEstado.Text = "Estado: Seco"; + // + // lblInfiltracaoPercentual + // + this.lblInfiltracaoPercentual.AutoSize = true; + this.lblInfiltracaoPercentual.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.lblInfiltracaoPercentual.Location = new System.Drawing.Point(9, 21); + this.lblInfiltracaoPercentual.Name = "lblInfiltracaoPercentual"; + this.lblInfiltracaoPercentual.Size = new System.Drawing.Size(102, 20); + this.lblInfiltracaoPercentual.TabIndex = 29; + this.lblInfiltracaoPercentual.Text = "Risco: 0,00%"; + // // gpbOdometro // this.gpbOdometro.Controls.Add(this.lblOdometroTempo); this.gpbOdometro.Controls.Add(this.imgOdometro); this.gpbOdometro.Controls.Add(this.lblOdometroMetros); - this.gpbOdometro.Location = new System.Drawing.Point(4, 576); + this.gpbOdometro.Location = new System.Drawing.Point(4, 662); this.gpbOdometro.Name = "gpbOdometro"; this.gpbOdometro.Size = new System.Drawing.Size(178, 90); this.gpbOdometro.TabIndex = 29; @@ -631,6 +667,7 @@ this.lblInclinacaox.Size = new System.Drawing.Size(50, 13); this.lblInclinacaox.TabIndex = 0; this.lblInclinacaox.Text = "L: 0.000°"; + this.lblInclinacaox.DoubleClick += new System.EventHandler(this.pnlInclinacaoX_DoubleClick); // // lblInclinacaoy // @@ -640,6 +677,7 @@ this.lblInclinacaoy.Size = new System.Drawing.Size(50, 13); this.lblInclinacaoy.TabIndex = 1; this.lblInclinacaoy.Text = "F: 0.000°"; + this.lblInclinacaoy.DoubleClick += new System.EventHandler(this.pnlInclinacaoY_DoubleClick); // // gpbNivelbateria // @@ -879,6 +917,16 @@ this.gpbMedidorEspessura.TabStop = false; this.gpbMedidorEspessura.Text = "Medidor de Espessura"; // + // btnMedir + // + this.btnMedir.Location = new System.Drawing.Point(6, 49); + this.btnMedir.Name = "btnMedir"; + this.btnMedir.Size = new System.Drawing.Size(69, 23); + this.btnMedir.TabIndex = 33; + this.btnMedir.Text = "Medir"; + this.btnMedir.UseVisualStyleBackColor = true; + this.btnMedir.Click += new System.EventHandler(this.btnMedir_Click); + // // lblMepStatus // this.lblMepStatus.AutoSize = true; @@ -1243,7 +1291,7 @@ this.pnlDireito.Controls.Add(this.lblDeslocamentovalor); this.pnlDireito.Controls.Add(this.lblInicio); this.pnlDireito.Dock = System.Windows.Forms.DockStyle.Right; - this.pnlDireito.Location = new System.Drawing.Point(1086, 24); + this.pnlDireito.Location = new System.Drawing.Point(1150, 24); this.pnlDireito.Name = "pnlDireito"; this.pnlDireito.Size = new System.Drawing.Size(208, 621); this.pnlDireito.TabIndex = 2; @@ -1299,7 +1347,7 @@ this.pnlCentro.Dock = System.Windows.Forms.DockStyle.Fill; this.pnlCentro.Location = new System.Drawing.Point(208, 24); this.pnlCentro.Name = "pnlCentro"; - this.pnlCentro.Size = new System.Drawing.Size(878, 530); + this.pnlCentro.Size = new System.Drawing.Size(942, 530); this.pnlCentro.TabIndex = 3; // // lblMensagemaviso @@ -1338,7 +1386,7 @@ this.imgCamera.Location = new System.Drawing.Point(0, 0); this.imgCamera.Margin = new System.Windows.Forms.Padding(20); this.imgCamera.Name = "imgCamera"; - this.imgCamera.Size = new System.Drawing.Size(878, 530); + this.imgCamera.Size = new System.Drawing.Size(942, 530); this.imgCamera.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.imgCamera.TabIndex = 0; this.imgCamera.TabStop = false; @@ -1355,7 +1403,7 @@ this.pnlCameraBottom.Dock = System.Windows.Forms.DockStyle.Bottom; this.pnlCameraBottom.Location = new System.Drawing.Point(208, 554); this.pnlCameraBottom.Name = "pnlCameraBottom"; - this.pnlCameraBottom.Size = new System.Drawing.Size(878, 91); + this.pnlCameraBottom.Size = new System.Drawing.Size(942, 91); this.pnlCameraBottom.TabIndex = 1; // // gpbGravacao @@ -1594,16 +1642,6 @@ this.tmrVelocidade.Interval = 10; this.tmrVelocidade.Tick += new System.EventHandler(this.tmrVelocidade_Tick); // - // btnMedir - // - this.btnMedir.Location = new System.Drawing.Point(6, 49); - this.btnMedir.Name = "btnMedir"; - this.btnMedir.Size = new System.Drawing.Size(69, 23); - this.btnMedir.TabIndex = 33; - this.btnMedir.Text = "Medir"; - this.btnMedir.UseVisualStyleBackColor = true; - this.btnMedir.Click += new System.EventHandler(this.btnMedir_Click); - // // lblCarroconexao // this.lblCarroconexao.Name = "lblCarroconexao"; @@ -1703,7 +1741,7 @@ this.lblTempo}); this.statusStrip1.Location = new System.Drawing.Point(0, 645); this.statusStrip1.Name = "statusStrip1"; - this.statusStrip1.Size = new System.Drawing.Size(1294, 22); + this.statusStrip1.Size = new System.Drawing.Size(1358, 22); this.statusStrip1.TabIndex = 1; this.statusStrip1.Text = "statusStrip1"; // @@ -1711,7 +1749,7 @@ // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1294, 667); + this.ClientSize = new System.Drawing.Size(1358, 667); this.Controls.Add(this.pnlCentro); this.Controls.Add(this.pnlCameraBottom); this.Controls.Add(this.pnlDireito); @@ -1732,6 +1770,8 @@ this.pnlEsquerdo.ResumeLayout(false); this.tabControl1.ResumeLayout(false); this.tabSensores.ResumeLayout(false); + this.gpbInfiltracao.ResumeLayout(false); + this.gpbInfiltracao.PerformLayout(); this.gpbOdometro.ResumeLayout(false); this.gpbOdometro.PerformLayout(); ((System.ComponentModel.ISupportInitialize)(this.imgOdometro)).EndInit(); @@ -1951,6 +1991,9 @@ private System.Windows.Forms.ToolStripStatusLabel lblDivisor4; private System.Windows.Forms.ToolStripStatusLabel lblTempo; private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.GroupBox gpbInfiltracao; + public System.Windows.Forms.Label lblInfiltracaoEstado; + public System.Windows.Forms.Label lblInfiltracaoPercentual; } } diff --git a/OrionTardPro/OrionTardPro/Forms/frmPrincipal.cs b/OrionTardPro/OrionTardPro/Forms/frmPrincipal.cs index 211c9e0..2c69b11 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmPrincipal.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmPrincipal.cs @@ -2,6 +2,7 @@ using OrionTardPro.Services; using System; using System.Data; +using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; @@ -15,7 +16,7 @@ namespace OrionTardPro { #region VARIAVEIS - + private int LarguraCano = 9; private int AlturaCano; private int OffSetCano = 10; @@ -23,7 +24,15 @@ namespace OrionTardPro public System.Drawing.Point P1Odometro; public System.Drawing.Point P2Odometro; Timer tmrAtualizacao; - + private bool _estadoConexaoAnterior; + private bool _alarmeInfiltracaoAnterior; + private byte? _mascaraSaudeAnterior; + private bool _encerramentoConfirmado; + private readonly Stopwatch _relogioTempoAtivo = Stopwatch.StartNew(); + private readonly Stopwatch _relogioTempoConectado = new Stopwatch(); + private DateTime _ultimaAtualizacaoPainelDeslocamentoUtc = DateTime.MinValue; + private const int IntervaloPainelDeslocamentoMs = 50; + #endregion #region EVENTOS DO FORMULARIO @@ -41,8 +50,12 @@ namespace OrionTardPro pnlDireito.GetType().GetMethod("SetStyle", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).Invoke(pnlDireito, new object[] { ControlStyles.UserPaint | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true }); } - private void frmPrincipal_Load(object sender, EventArgs e) + private async void frmPrincipal_Load(object sender, EventArgs e) { + // Monitor leve de campo: mede stalls da UI, filas, memória/GDI e + // operações lentas sem bloquear a interface para gravar diagnóstico. + PerformanceMonitorService.Inicializar(this); + AtualizaDadosProjeto(); imgEspelharY.BackgroundImage.RotateFlip(RotateFlipType.Rotate90FlipY); @@ -67,14 +80,22 @@ namespace OrionTardPro Dicas.SetToolTip(imgPausarGravacao, "Pausa a gravação do vídeo"); Dicas.SetToolTip(imgPararGravacao, "Finaliza a gravação do vídeo"); - GeneralJoystick.InstanciaJoystick(); - - Variaveis.frmCamera.AtualizaListaCameras(); + // Descobertas de hardware podem demorar em drivers problemáticos. + // Elas agora rodam fora da UI thread, mantendo o formulário responsivo. + await GeneralJoystick.InstanciaJoystickAsync(); + await Variaveis.frmCamera.AtualizaListaCamerasAsync(); tmrAtualizacao = new Timer(); tmrAtualizacao.Interval = 500; tmrAtualizacao.Tick += tmrAtualizacao_Tick; tmrAtualizacao.Start(); + + _estadoConexaoAnterior = SerialService.Conectado; + if (_estadoConexaoAnterior) + _relogioTempoConectado.Start(); + _alarmeInfiltracaoAnterior = SerialService.AlarmeInfiltracaoV6; + LogsFuncoes.IniciarGeoWatcher(); + _ = LogsFuncoes.TentarEnviarPendentesAsync(); } private void tmrAtualizacao_Tick(object sender, EventArgs e) @@ -85,24 +106,40 @@ namespace OrionTardPro "Parado"; } - private void frmPrincipal_FormClosed(object sender, FormClosedEventArgs e) - { - Environment.Exit(0); - } - private void frmPrincipal_FormClosing(object sender, FormClosingEventArgs e) { - LogsFuncoes.RegistrarLog(); + if (_encerramentoConfirmado) + return; if (MessageBox.Show("Tem certeza que deseja sair?", "Sair", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No) { e.Cancel = true; + return; } - else + + GeneralJoystick.ParadaSegura("encerramento da aplicação"); + + // Finaliza captura/gravação com espera limitada apenas no encerramento. + VideoService.Encerrar(); + + // Garante que odômetro/configurações operacionais pendentes + // sejam persistidos antes de encerrar o processo. + Variaveis.FlushPersistenciaPendente(); + + _encerramentoConfirmado = true; + if (LogsFuncoes.TemAtividadeNoPeriodo || SerialService.TempoConectado > 0) { - try { Environment.Exit(0); } catch { } + LogsFuncoes.RegistrarLog( + TipoEventoLog.Encerramento, + "Aplicação encerrada pelo usuário.", + false); } + LogsFuncoes.EncerrarGeoWatcher(); + + // Mantém o monitor ativo durante toda a sequência de encerramento + // para registrar qualquer operação lenta antes de finalizar. + PerformanceMonitorService.Encerrar(); } #endregion @@ -127,9 +164,9 @@ namespace OrionTardPro } } - private void desconectarToolStripMenuItem_Click(object sender, EventArgs e) + private async void desconectarToolStripMenuItem_Click(object sender, EventArgs e) { - SerialService.Desconectar().ConfigureAwait(false); + await SerialService.Desconectar(); } private void btnConfcamera_Click(object sender, EventArgs e) @@ -230,7 +267,7 @@ namespace OrionTardPro MessageBox.Show("O serviço ainda não foi definido, configure para visualizar a galeria!", "Serviço não definido", MessageBoxButtons.OK, MessageBoxIcon.Warning); Variaveis.frmConfiguracoes.ShowDialog(); } - else + else Variaveis.frmGaleria.ShowDialog(); } @@ -368,19 +405,37 @@ namespace OrionTardPro private void pnlInclinacaoX_DoubleClick(object sender, EventArgs e) { - if (SerialService.Conectado && Variaveis.PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado) + if (SerialService.Conectado && + Variaveis.PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado) { - Variaveis.Sensoriamento.Sensor_InclinacaoLateral_Offset = Variaveis.Sensoriamento.InclinacaoLateral; - Variaveis.Sensoriamento.Sensor_InclinacaoLateral_Offset = Variaveis.Sensoriamento.Sensor_InclinacaoLateral_Offset * -1; + // Zera exatamente a grandeza exibida neste lado da tela. + // O método cuida internamente da diferença histórica V1/V2. + Variaveis.Sensoriamento.ZerarInclinacaoLateralAtual(); + + lblInclinacaox.Text = + "F: " + + Variaveis.Sensoriamento.InclinacaoLateral.ToString("0.00") + + "°"; + + lblDebugcontrole.Text = + "Zero de inclinação frontal definido e salvo."; } } private void pnlInclinacaoY_DoubleClick(object sender, EventArgs e) { - if (SerialService.Conectado && Variaveis.PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado) + if (SerialService.Conectado && + Variaveis.PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado) { - Variaveis.Sensoriamento.Sensor_InclinacaoFrontal_Offset = Variaveis.Sensoriamento.InclinacaoFrontal; - Variaveis.Sensoriamento.Sensor_InclinacaoFrontal_Offset = Variaveis.Sensoriamento.Sensor_InclinacaoFrontal_Offset * -1; + Variaveis.Sensoriamento.ZerarInclinacaoFrontalAtual(); + + lblInclinacaoy.Text = + "L: " + + Variaveis.Sensoriamento.InclinacaoFrontal.ToString("0.00") + + "°"; + + lblDebugcontrole.Text = + "Zero de inclinação lateral definido e salvo."; } } @@ -402,16 +457,13 @@ namespace OrionTardPro public void AtualizaDadosIniciais(bool CarregaPerfil) { - trkVelocidade.Maximum = Variaveis.PerfilVigente.PotenciaMaxima; - trkVelocidade.Minimum = Variaveis.PerfilVigente.PotenciaMinima; + Variaveis.ConfigurarTrackBarsVelocidade(); trkMedidorEspessura.Maximum = Variaveis.PerfilVigente.AnguloMedicaoIdeal + Variaveis.PerfilVigente.AnguloMedicaoMargemLimite; Variaveis.frmControle.trkRampaaceleracao.Maximum = Variaveis.PerfilVigente.RampaMaxima; Variaveis.frmControle.trkRampaaceleracao.Minimum = Variaveis.PerfilVigente.RampaMinima; Variaveis.frmControle.trkPrecisaorampa.Maximum = Variaveis.PerfilVigente.PrecisaoMaxima; Variaveis.frmControle.trkPrecisaorampa.Minimum = Variaveis.PerfilVigente.PrecisaoMinima; - Variaveis.frmControle.trkVelocidade.Maximum = Variaveis.PerfilVigente.PotenciaMaxima; - Variaveis.frmControle.trkVelocidade.Minimum = Variaveis.PerfilVigente.PotenciaMinima; - GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value * 10); + GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value); GeneralJoystick.AtualizaRampaAtual(Variaveis.frmControle.trkRampaaceleracao.Value); Variaveis.PerfilVigente.PrecisaoAtual = Variaveis.frmControle.trkPrecisaorampa.Value; @@ -470,7 +522,7 @@ namespace OrionTardPro this.Text = VariaveisGerais.NomeProjeto + " v" + VariaveisGerais.Versao; if (Variaveis.Cliente != null) { - this.Text += " - " + Variaveis.Cliente.Servico + " (" + Variaveis.Cliente.Nome + ")"; + this.Text += " - " + Variaveis.Cliente.Servico + " (" + Variaveis.Cliente.Nome + ")"; DirectoryInfo Path = new DirectoryInfo(Variaveis.Cliente.Path); Variaveis.IndexFotosIndentificacao = Path.GetFiles().Count(x => x.Extension == Variaveis.ExtIdentificacao); @@ -495,14 +547,28 @@ namespace OrionTardPro return ValorPreenchido; } + private double CalcularPosicaoVerticalTubo(double deslocamentoMetros) + { + double comprimentoCabo = + Math.Max(0.001, Variaveis.PerfilVigente.ComprimentoCabo); + + return AlturaCano - + ((AlturaCano * deslocamentoMetros) / comprimentoCabo); + } + public void AdicionarMarcadorTubo(Label Marcador) { - Label label = new Label(); - label = Marcador; + Label label = Marcador; label.Parent = pnlDireito; pnlDireito.Controls.Add(label); - int Percorrido = Convert.ToInt32(AlturaCano - Convert.ToDouble(decimal.Divide(Convert.ToDecimal(AlturaCano * Variaveis.Sensoriamento.DeslocamentoCarro), Convert.ToDecimal(Variaveis.PerfilVigente.ComprimentoCabo)))); - label.Location = new System.Drawing.Point((pnlDireito.Width / 2) + 17, (Percorrido - 4)); + + int percorrido = Convert.ToInt32( + CalcularPosicaoVerticalTubo( + Variaveis.Sensoriamento.DeslocamentoCarro)); + + label.Location = new System.Drawing.Point( + (pnlDireito.Width / 2) + 17, + percorrido - 4); } public void ZerarEncoder() @@ -523,31 +589,56 @@ namespace OrionTardPro private void pnlDireito_Paint(object sender, PaintEventArgs e) { - //int MedidaCarroX = 30; - //int MedidaCarroY = 30; - double Percorrido = AlturaCano - Convert.ToDouble(decimal.Divide(Convert.ToDecimal(AlturaCano * Variaveis.Sensoriamento.DeslocamentoCarro), Convert.ToDecimal(Variaveis.PerfilVigente.ComprimentoCabo))); + double percorrido = + CalcularPosicaoVerticalTubo( + Variaveis.Sensoriamento.DeslocamentoCarro); - int MedidaCarroX = picRoboSuperior.Width; - int MedidaCarroY = picRoboSuperior.Height; - int xCarro = (pnlDireito.Width / 2) - (MedidaCarroX / 2); - int yCarro = Convert.ToInt32(Percorrido) + OffSetCano - (MedidaCarroX / 2); - picRoboSuperior.Location = new System.Drawing.Point(xCarro, yCarro); + int medidaCarroX = picRoboSuperior.Width; + int medidaCarroY = picRoboSuperior.Height; + int xCarro = + (pnlDireito.Width / 2) - (medidaCarroX / 2); + int yCarro = + Convert.ToInt32(percorrido) + + OffSetCano - + (medidaCarroY / 2); - lblDeslocamentovalor.Location = new System.Drawing.Point((pnlDireito.Width / 2) - 69, Convert.ToInt32(Percorrido) + (MedidaCarroX / 4)); + picRoboSuperior.Location = + new System.Drawing.Point(xCarro, yCarro); - //Rectangle Carro = new Rectangle((pnlDireito.Width / 2) - (MedidaCarroX / 2) - 1, Convert.ToInt32(Percorrido) + OffSetCano - (MedidaCarroX / 2), MedidaCarroX, MedidaCarroY); - //Graphics g = pnlDireito.CreateGraphics(); - //g.DrawEllipse(new Pen(Color.Red, 2), Carro); + lblDeslocamentovalor.Location = + new System.Drawing.Point( + (pnlDireito.Width / 2) - 69, + Convert.ToInt32(percorrido) + + (medidaCarroY / 4)); - Rectangle Cano = new Rectangle((pnlDireito.Width / 2) - LarguraCano, OffSetCano, LarguraCano * 2, AlturaCano); - e.Graphics.DrawRectangle(new Pen(Color.Black, 2), Cano); + Rectangle cano = new Rectangle( + (pnlDireito.Width / 2) - LarguraCano, + OffSetCano, + LarguraCano * 2, + AlturaCano); + + using (Pen canoPen = new Pen(Color.Black, 2)) + e.Graphics.DrawRectangle(canoPen, cano); if (Variaveis.Sensoriamento.OdometroParcialIniciado) { - P2Odometro = new System.Drawing.Point(lblDeslocamentovalor.Location.X - 20, lblDeslocamentovalor.Location.Y); - e.Graphics.DrawLine(new Pen(Color.Blue, 2), P1Odometro, P2Odometro); - lblOdometroparcial.Text = Variaveis.OdometroParcial + " m"; - lblOdometroparcial.Location = new System.Drawing.Point(P2Odometro.X - 13, P2Odometro.Y - 13); + P2Odometro = new System.Drawing.Point( + lblDeslocamentovalor.Location.X - 20, + lblDeslocamentovalor.Location.Y); + + using (Pen parcialPen = new Pen(Color.Blue, 2)) + e.Graphics.DrawLine( + parcialPen, + P1Odometro, + P2Odometro); + + lblOdometroparcial.Text = + Variaveis.OdometroParcial.ToString("0.00") + " m"; + + lblOdometroparcial.Location = + new System.Drawing.Point( + P2Odometro.X - 13, + P2Odometro.Y - 13); } } @@ -556,7 +647,7 @@ namespace OrionTardPro private void trkVelocidade_Scroll(object sender, EventArgs e) { Variaveis.frmControle.trkVelocidade.Value = trkVelocidade.Value; - GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value * 10); + GeneralJoystick.AtualizaPotenciaAtual(trkVelocidade.Value); } private void tkbBraco_Scroll(object sender, EventArgs e) @@ -618,14 +709,23 @@ namespace OrionTardPro string Pontos = ""; private void tmrTempoAtivo_Tick(object sender, EventArgs e) { - SerialService.TempoAtivo++; - LogsFuncoes.TimerLog += 1000; - if (SerialService.Conectado) - SerialService.TempoConectado++; + if (SerialService.Conectado && !_relogioTempoConectado.IsRunning) + _relogioTempoConectado.Start(); + else if (!SerialService.Conectado && _relogioTempoConectado.IsRunning) + _relogioTempoConectado.Stop(); + + SerialService.TempoAtivo = (int)_relogioTempoAtivo.Elapsed.TotalSeconds; + SerialService.TempoConectado = (int)_relogioTempoConectado.Elapsed.TotalSeconds; + LogsFuncoes.TimerLog += 1000; // mantido apenas para compatibilidade visual/legada lblTempo.Text = "Tempo Ativo: " + TimeSpan.FromSeconds(SerialService.TempoAtivo).ToString(@"hh\:mm\:ss") + " - Tempo Conectado: " + TimeSpan.FromSeconds(SerialService.TempoConectado).ToString(@"hh\:mm\:ss"); - if (VideoService.GravacaoIniciada) + if (VideoService.GravacaoFinalizando) + { + lblStatusCamera.Text = "Finalizando..."; + lblStatusCamera.ForeColor = Color.DarkOrange; + } + else if (VideoService.GravacaoIniciada) { if (VideoService.GravacaoPausada) { @@ -653,59 +753,206 @@ namespace OrionTardPro lblStatusCamera.ForeColor = Color.Black; } - if (LogsFuncoes.TimerLog > LogsFuncoes.TempoLog) + // Estado dos botões acompanha também a fase assíncrona de finalização. + imgIniciarGravacao.Enabled = + Variaveis.VideoIsRuning && + !VideoService.GravacaoIniciada && + !VideoService.GravacaoFinalizando; + imgPausarGravacao.Enabled = VideoService.GravacaoIniciada; + imgPararGravacao.Enabled = VideoService.GravacaoIniciada; + + RegistrarEventosOperacionais(); + + if (LogsFuncoes.DeveRegistrarLogPeriodico()) { - LogsFuncoes.TimerLog = 0; - LogsFuncoes.RegistrarLog(); + LogsFuncoes.RegistrarLog( + TipoEventoLog.Periodico, + "Snapshot periódico da operação."); + } + } + + private void RegistrarEventosOperacionais() + { + bool conectadoAgora = SerialService.Conectado; + + if (conectadoAgora != _estadoConexaoAnterior) + { + LogsFuncoes.RegistrarLog( + conectadoAgora + ? TipoEventoLog.Conectado + : TipoEventoLog.Desconectado, + conectadoAgora + ? "Sessão serial estabelecida e validada." + : "Sessão serial encerrada ou perdida."); + + _estadoConexaoAnterior = conectadoAgora; + _mascaraSaudeAnterior = null; + _alarmeInfiltracaoAnterior = false; + } + + if (!conectadoAgora || + Variaveis.PerfilVigente.VersaoProtocolo != VersaoProtocolo.v2) + return; + + bool infiltracaoAgora = SerialService.AlarmeInfiltracaoV6; + if (infiltracaoAgora && !_alarmeInfiltracaoAnterior) + { + LogsFuncoes.RegistrarLog( + TipoEventoLog.Infiltracao, + "Alarme de infiltração ativado."); + } + _alarmeInfiltracaoAnterior = infiltracaoAgora; + + // Só avalia saúde depois que pelo menos um frame válido chegou. + if (SerialService.ProtocoloEntrada.Momento == DateTime.MinValue) + return; + + byte saudeAgora = SerialService.MascaraSaudeV6; + if (!_mascaraSaudeAnterior.HasValue) + { + _mascaraSaudeAnterior = saudeAgora; + return; + } + + if (_mascaraSaudeAnterior.Value != saudeAgora) + { + LogsFuncoes.RegistrarLog( + TipoEventoLog.SaudeSensores, + $"Saúde dos sensores alterada de 0x{_mascaraSaudeAnterior.Value:X2} para 0x{saudeAgora:X2}."); + _mascaraSaudeAnterior = saudeAgora; } } double indVelocidade = 0.0; private void tmrVelocidade_Tick(object sender, EventArgs e) { - tmrVelocidade.Interval = Variaveis.PerfilVigente.RampaAtual * 10; + tmrVelocidade.Interval = + Math.Max( + 10, + Variaveis.PerfilVigente.RampaAtual * 10); - if (GeneralJoystick.UltimaDirecao != Direcao.Parado && indVelocidade < Variaveis.PerfilVigente.PrecisaoAtual) - { - indVelocidade += 1.0; - } - else if (GeneralJoystick.UltimaDirecao == Direcao.Parado && indVelocidade > 0) - { - indVelocidade -= 1.0; - } - double Porcentagem = indVelocidade / Variaveis.PerfilVigente.PrecisaoAtual; - Variaveis.Sensoriamento.VelocidadeAtual = Variaveis.Sensoriamento.VelocidadeMedia * Porcentagem; - lblVelocimetro.Text = Variaveis.Sensoriamento.VelocidadeAtual.ToString("0.00") + " m/s"; + // Atualiza a velocidade-alvo instantânea usando a potência atual + // de cada lado. A integração do deslocamento usa, logo abaixo, + // exatamente a mesma VelocidadeAtual mostrada no velocímetro. + Variaveis.Sensoriamento.AtualizaVelocidadeMedia(); - if (GeneralJoystick.UltimaDirecao == Direcao.Parado && indVelocidade == 0) + int precisaoRampa = + Math.Max( + 1, + Variaveis.PerfilVigente.PrecisaoAtual); + + if (SerialService.ProtocoloV2Ativo) { + // A V6 já informa na telemetria o PWM realmente aplicado pela + // rampa da firmware. Portanto não aplicamos uma segunda rampa + // no C#. + if (GeneralJoystick.UltimaDirecao == Direcao.Parado) + { + indVelocidade = 0; + Variaveis.Sensoriamento.VelocidadeAtual = 0; + } + else + { + indVelocidade = precisaoRampa; + Variaveis.Sensoriamento.VelocidadeAtual = + Variaveis.Sensoriamento.VelocidadeMedia; + } + } + else + { + // Legado: mantém a rampa visual/estimada original do Orion, + // mas sobre uma velocidade-alvo instantânea, sem média + // acumulada de comandos anteriores. + if (GeneralJoystick.UltimaDirecao != Direcao.Parado && + indVelocidade < precisaoRampa) + { + indVelocidade += 1.0; + } + else if (GeneralJoystick.UltimaDirecao == Direcao.Parado && + indVelocidade > 0) + { + indVelocidade -= 1.0; + } + + indVelocidade = + Math.Max( + 0, + Math.Min(precisaoRampa, indVelocidade)); + + double porcentagemRampa = + indVelocidade / precisaoRampa; + + Variaveis.Sensoriamento.VelocidadeAtual = + Variaveis.Sensoriamento.VelocidadeMedia * + porcentagemRampa; + } + + Variaveis.Sensoriamento.AtualizaDeslocamentoTubo(); + + lblVelocimetro.Text = + Variaveis.Sensoriamento.VelocidadeAtual + .ToString("0.00") + + " m/s"; + + lblDeslocamentovalor.Text = + Variaveis.Sensoriamento.DeslocamentoCarro + .ToString("0.00") + + " m"; + + DateTime agoraUtc = DateTime.UtcNow; + bool atualizarPainel = + _ultimaAtualizacaoPainelDeslocamentoUtc == + DateTime.MinValue || + (agoraUtc - + _ultimaAtualizacaoPainelDeslocamentoUtc) + .TotalMilliseconds >= + IntervaloPainelDeslocamentoMs; + + if (atualizarPainel) + { + _ultimaAtualizacaoPainelDeslocamentoUtc = + agoraUtc; + + Variaveis.Sensoriamento + .AtualizarOdometroGeral(); + + pnlDireito.Invalidate(); + } + + if (GeneralJoystick.UltimaDirecao == Direcao.Parado && + indVelocidade == 0) + { + // Força a posição final antes de dormir o timer. + Variaveis.Sensoriamento + .AtualizarOdometroGeral(); + pnlDireito.Invalidate(); + tmrVelocidade.Enabled = false; - return; } } private void teclasDeAtalhoToolStripMenuItem_Click(object sender, EventArgs e) { - string Mensagem = - "Para Frente: Seta Cima" + System.Environment.NewLine + - "Para Trás: Seta Baixo" + System.Environment.NewLine + - "Para Esquerda: Seta Esquerda" + System.Environment.NewLine + - "Para Direita: Seta Direita" + System.Environment.NewLine + - System.Environment.NewLine + - "Câmera Cima: W" + System.Environment.NewLine + - "Câmera Baixo: S" + System.Environment.NewLine + - "Câmera Esquerda: A" + System.Environment.NewLine + - "Câmera Direita: D" + System.Environment.NewLine + - "Câmera Aumentar Iluminação: +" + System.Environment.NewLine + - "Câmera Aiminuir Iluminação: -" + System.Environment.NewLine + - System.Environment.NewLine + - "Trocar Câmera: T" + System.Environment.NewLine + - "Ligar/Desligar Laser: L" + System.Environment.NewLine + - "Ligar/Desligar Lluminação: I" + System.Environment.NewLine + - "Ligar/Desligar Relé Geral: G" + System.Environment.NewLine + - System.Environment.NewLine + - "Capturar Imagem: C" + System.Environment.NewLine + - "Adicionar Marcador: M" + System.Environment.NewLine + + string Mensagem = + "Para Frente: Seta Cima" + System.Environment.NewLine + + "Para Trás: Seta Baixo" + System.Environment.NewLine + + "Para Esquerda: Seta Esquerda" + System.Environment.NewLine + + "Para Direita: Seta Direita" + System.Environment.NewLine + + System.Environment.NewLine + + "Câmera Cima: W" + System.Environment.NewLine + + "Câmera Baixo: S" + System.Environment.NewLine + + "Câmera Esquerda: A" + System.Environment.NewLine + + "Câmera Direita: D" + System.Environment.NewLine + + "Câmera Aumentar Iluminação: +" + System.Environment.NewLine + + "Câmera Aiminuir Iluminação: -" + System.Environment.NewLine + + System.Environment.NewLine + + "Trocar Câmera: T" + System.Environment.NewLine + + "Ligar/Desligar Laser: L" + System.Environment.NewLine + + "Ligar/Desligar Lluminação: I" + System.Environment.NewLine + + "Ligar/Desligar Relé Geral: G" + System.Environment.NewLine + + System.Environment.NewLine + + "Capturar Imagem: C" + System.Environment.NewLine + + "Adicionar Marcador: M" + System.Environment.NewLine + "Iniciar/Parar Odômetro Parcial: O" + System.Environment.NewLine + System.Environment.NewLine + "Carimbar Medidor de Espessura: Q" + System.Environment.NewLine + @@ -721,7 +968,7 @@ namespace OrionTardPro MessageBox.Show(VersionamentoService.ResultadoAtualizacao); } - + private void trkMedidorEspessura_Scroll(object sender, EventArgs e) { diff --git a/OrionTardPro/OrionTardPro/Forms/frmSensoriamento.cs b/OrionTardPro/OrionTardPro/Forms/frmSensoriamento.cs index c499c48..2563db2 100644 --- a/OrionTardPro/OrionTardPro/Forms/frmSensoriamento.cs +++ b/OrionTardPro/OrionTardPro/Forms/frmSensoriamento.cs @@ -14,6 +14,14 @@ namespace OrionTardPro { public partial class frmSensoriamento : Form { + private const int MaximoLinhasConsole = 500; + private const int IntervaloAtualizacaoTelaMs = 200; + private readonly Queue _linhasConsole = + new Queue(); + private int _linhasDesdeCompactacao; + private DateTime _ultimaAtualizacaoTelaUtc = + DateTime.MinValue; + public frmSensoriamento() { InitializeComponent(); @@ -54,72 +62,295 @@ namespace OrionTardPro dtSensores.Rows.Add("BMP Altitude", Variaveis.Sensoriamento.Altitude.ToString("0.00"), "m"); dtSensores.Rows.Add("Tensão", Variaveis.Sensoriamento.Tensao.ToString("0.00"), ""); dtSensores.Rows.Add("Corrente", Variaveis.Sensoriamento.Corrente.ToString("0.00"), ""); + dtSensores.Rows.Add("Infiltração - leitura bruta", Variaveis.Sensoriamento.InfiltracaoRaw, "ADC"); + dtSensores.Rows.Add("Infiltração - nível de risco", Variaveis.Sensoriamento.NivelRiscoInfiltracao.ToString("0.0"), "%"); + dtSensores.Rows.Add("Infiltração - estado", Variaveis.Sensoriamento.EstadoInfiltracao, ""); } public void AtualizaDados() { - string MensagemConsole = ""; + if (IsDisposed || !Visible) + return; + + DateTime agoraUtc = DateTime.UtcNow; + if ((agoraUtc - _ultimaAtualizacaoTelaUtc) + .TotalMilliseconds < + IntervaloAtualizacaoTelaMs) + { + return; + } + + _ultimaAtualizacaoTelaUtc = agoraUtc; + + string mensagemConsole = ""; + try { - if (string.IsNullOrEmpty(SerialService.ProtocoloEntrada.Dados)) - { + string frame = SerialService.ProtocoloEntrada.Dados; + + if (string.IsNullOrWhiteSpace(frame)) return; - } - string[] ProtocoloRecebido = SerialService.ProtocoloEntrada.Dados.Split(Convert.ToChar(SerialService.InicioSensores)); - string[] ParametrosSensor = ProtocoloRecebido[ProtocoloRecebido.Length - 1].Split(';'); - TipoSensor Tipo = (TipoSensor)Convert.ToInt32(ParametrosSensor[0]); - if ( - Tipo == TipoSensor.Tod || - (Tipo == TipoSensor.Cor && Variaveis.frmParametros.chbCorDebug.Checked) || - (Tipo == TipoSensor.Gas && Variaveis.frmParametros.chbGasDebug.Checked) || - (Tipo == TipoSensor.Mpu && Variaveis.frmParametros.chbMpuDebug.Checked) || - (Tipo == TipoSensor.Ten && Variaveis.frmParametros.chbTenDebug.Checked) || - (Tipo == TipoSensor.Tmp && Variaveis.frmParametros.chbTmpDebug.Checked) || - (Tipo == TipoSensor.Ult && Variaveis.frmParametros.chbUltDebug.Checked) || - (Tipo == TipoSensor.Umi && Variaveis.frmParametros.chbUmiDebug.Checked) || - (Tipo == TipoSensor.Vel && Variaveis.frmParametros.chbVelDebug.Checked) - ) + bool firmwareV6 = + Variaveis.PerfilVigente != null && + Variaveis.PerfilVigente.VersaoProtocolo == VersaoProtocolo.v2; + + if (firmwareV6) { - MensagemConsole = SerialService.ProtocoloEntrada.Dados; + // Na V6 o SerialService já verificou CRC, sequência, + // tipo do frame e atualizou as variáveis. + if (frame.StartsWith("!V6;", StringComparison.Ordinal)) + mensagemConsole = frame.TrimEnd('\r', '\n'); + } + else + { + // Parser visual legado. + string[] protocoloRecebido = + frame.Split(Convert.ToChar(SerialService.InicioSensores)); + + if (protocoloRecebido.Length > 0) + { + string[] parametrosSensor = + protocoloRecebido[protocoloRecebido.Length - 1].Split(';'); + + int tipoNumerico; + + if (parametrosSensor.Length > 0 && + int.TryParse(parametrosSensor[0], out tipoNumerico)) + { + TipoSensor tipo = (TipoSensor)tipoNumerico; + + if ( + tipo == TipoSensor.Tod || + (tipo == TipoSensor.Cor && Variaveis.frmParametros.chbCorDebug.Checked) || + (tipo == TipoSensor.Gas && Variaveis.frmParametros.chbGasDebug.Checked) || + (tipo == TipoSensor.Mpu && Variaveis.frmParametros.chbMpuDebug.Checked) || + (tipo == TipoSensor.Ten && Variaveis.frmParametros.chbTenDebug.Checked) || + (tipo == TipoSensor.Tmp && Variaveis.frmParametros.chbTmpDebug.Checked) || + (tipo == TipoSensor.Ult && Variaveis.frmParametros.chbUltDebug.Checked) || + (tipo == TipoSensor.Umi && Variaveis.frmParametros.chbUmiDebug.Checked) || + (tipo == TipoSensor.Vel && Variaveis.frmParametros.chbVelDebug.Checked) + ) + { + mensagemConsole = frame; + } + } + } } } catch (Exception ex) { - + Console.WriteLine( + "[SENSORIAMENTO] Erro preparando console: " + ex.Message); } - + try { - txtConsole.Text += MensagemConsole + "\r\n"; - txtConsole.SelectionStart = txtConsole.Text.Length; - txtConsole.ScrollToCaret(); + if (!string.IsNullOrWhiteSpace(mensagemConsole)) + { + _linhasConsole.Enqueue(mensagemConsole); + + while (_linhasConsole.Count > MaximoLinhasConsole) + _linhasConsole.Dequeue(); + + txtConsole.AppendText( + mensagemConsole + + Environment.NewLine); + + _linhasDesdeCompactacao++; + + // Reescreve o TextBox apenas em blocos. Assim o controle + // nunca cresce indefinidamente e também não reconstruímos + // 500 linhas a cada frame. + if (_linhasDesdeCompactacao >= 100 && + txtConsole.Lines.Length > + MaximoLinhasConsole) + { + txtConsole.Lines = + _linhasConsole.ToArray(); + + _linhasDesdeCompactacao = 0; + } + + txtConsole.SelectionStart = + txtConsole.Text.Length; + + txtConsole.ScrollToCaret(); + } + + AtualizaTabelaSensores(); } catch (Exception ex) { + Console.WriteLine( + "[SENSORIAMENTO] Erro atualizando tela: " + ex.Message); + } + } + private void AtualizaTabelaSensores() + { + if (dtSensores.Rows.Count < 17) + return; + + bool firmwareV6 = + Variaveis.PerfilVigente != null && + Variaveis.PerfilVigente.VersaoProtocolo == VersaoProtocolo.v2; + + if (firmwareV6) + { + dtSensores.Rows[0].SetValues( + "SHT31 Temperatura", + Variaveis.Sensoriamento.ShtTmp.ToString("0.00"), + "°C"); + + dtSensores.Rows[1].SetValues( + "SHT31 Umidade", + Variaveis.Sensoriamento.ShtUmd.ToString("0.00"), + "%"); + + dtSensores.Rows[2].SetValues( + "Inclinação Roll", + SerialService.RollDeg_V6.ToString("0.00"), + "°"); + + dtSensores.Rows[3].SetValues( + "ACE X", + Variaveis.Sensoriamento.AceX.ToString("0.000"), + "m/s²"); + + dtSensores.Rows[4].SetValues( + "ACE Y", + Variaveis.Sensoriamento.AceY.ToString("0.000"), + "m/s²"); + + dtSensores.Rows[5].SetValues( + "ACE Z", + Variaveis.Sensoriamento.AceZ.ToString("0.000"), + "m/s²"); + + dtSensores.Rows[6].SetValues( + "GYR X", + Variaveis.Sensoriamento.GyrX.ToString("0.000"), + "°/s"); + + dtSensores.Rows[7].SetValues( + "GYR Y", + Variaveis.Sensoriamento.GyrY.ToString("0.000"), + "°/s"); + + dtSensores.Rows[8].SetValues( + "GYR Z", + Variaveis.Sensoriamento.GyrZ.ToString("0.000"), + "°/s"); + + dtSensores.Rows[9].SetValues( + "Inclinação Pitch", + SerialService.PitchDeg_V6.ToString("0.00"), + "°"); + + dtSensores.Rows[10].SetValues( + "Infiltração", + SerialService.InfiltracaoRawV6, + "ADC"); + + dtSensores.Rows[11].SetValues( + "Alarme de infiltração", + SerialService.AlarmeInfiltracaoV6 ? "ATIVO" : "Normal", + ""); + + dtSensores.Rows[12].SetValues( + "Saúde I²C", + "0x" + SerialService.MascaraSaudeV6.ToString("X2"), + ""); + + dtSensores.Rows[13].SetValues( + "INA228", + Variaveis.Sensoriamento.INA_Iniciado ? "OK" : "Falha", + ""); + + dtSensores.Rows[14].SetValues( + "BMI270", + Variaveis.Sensoriamento.MPU_Iniciado ? "OK" : "Falha", + ""); + + dtSensores.Rows[15].SetValues( + "Tensão", + Variaveis.Sensoriamento.InaTen.ToString("0.000"), + "V"); + + dtSensores.Rows[16].SetValues( + "Corrente", + Variaveis.Sensoriamento.InaCor.ToString("0.0"), + "mA"); + + dtSensores.Rows[17].SetValues( + "Infiltração - leitura bruta", + Variaveis.Sensoriamento.InfiltracaoRaw, + "ADC"); + + dtSensores.Rows[18].SetValues( + "Infiltração - nível de risco", + Variaveis.Sensoriamento.NivelRiscoInfiltracao.ToString("0.0"), + "%"); + + dtSensores.Rows[19].SetValues( + "Infiltração - estado", + Variaveis.Sensoriamento.EstadoInfiltracao, + ""); + + return; } - dtSensores.Rows[0].Cells[0].Value = "Gás e Fumaça"; dtSensores.Rows[0].Cells[1].Value = Variaveis.Sensoriamento.NivelGas.ToString("0.00"); dtSensores.Rows[0].Cells[2].Value = ""; - dtSensores.Rows[1].Cells[0].Value = "DHT Temperatura"; dtSensores.Rows[1].Cells[1].Value = Variaveis.Sensoriamento.Temperatura.ToString("0.00"); dtSensores.Rows[1].Cells[2].Value = "°C"; - dtSensores.Rows[2].Cells[0].Value = "DHT Umidade"; dtSensores.Rows[2].Cells[1].Value = Variaveis.Sensoriamento.Umidade.ToString("0.00"); dtSensores.Rows[2].Cells[2].Value = "%"; - //dtSensores.Rows[3].Cells[0].Value = "DHT Índice de calor"; dtSensores.Rows[3].Cells[1].Value = Variaveis.Sensor_DHT_IndiceCalor; dtSensores.Rows[3].Cells[2].Value = "%"; - //dtSensores.Rows[4].Cells[0].Value = "DHT Ponto condensação da água"; dtSensores.Rows[4].Cells[1].Value = Variaveis.Sensor_DHT_Condensacao; dtSensores.Rows[4].Cells[2].Value = "%"; - //dtSensores.Rows[5].Cells[0].Value = "DHT Status"; dtSensores.Rows[5].Cells[1].Value = Variaveis.Sensor_DHT_Status; dtSensores.Rows[5].Cells[2].Value = ""; - dtSensores.Rows[3].Cells[0].Value = "ACE X"; dtSensores.Rows[3].Cells[1].Value = Variaveis.Sensoriamento.AceX; dtSensores.Rows[3].Cells[2].Value = "°"; - dtSensores.Rows[4].Cells[0].Value = "ACE Y"; dtSensores.Rows[4].Cells[1].Value = Variaveis.Sensoriamento.AceY; dtSensores.Rows[4].Cells[2].Value = "°"; - dtSensores.Rows[5].Cells[0].Value = "ACE Z"; dtSensores.Rows[5].Cells[1].Value = Variaveis.Sensoriamento.AceZ; dtSensores.Rows[5].Cells[2].Value = "°"; - dtSensores.Rows[6].Cells[0].Value = "GYR X"; dtSensores.Rows[6].Cells[1].Value = Variaveis.Sensoriamento.GyrX; dtSensores.Rows[6].Cells[2].Value = "°"; - dtSensores.Rows[7].Cells[0].Value = "GYR Y"; dtSensores.Rows[7].Cells[1].Value = Variaveis.Sensoriamento.GyrY; dtSensores.Rows[7].Cells[2].Value = "°"; - dtSensores.Rows[8].Cells[0].Value = "GYR Z"; dtSensores.Rows[8].Cells[1].Value = Variaveis.Sensoriamento.GyrZ; dtSensores.Rows[8].Cells[2].Value = "°"; - dtSensores.Rows[9].Cells[0].Value = "MAG X"; dtSensores.Rows[9].Cells[1].Value = Variaveis.Sensoriamento.MagX; dtSensores.Rows[9].Cells[2].Value = "°"; - dtSensores.Rows[10].Cells[0].Value = "MAG Y"; dtSensores.Rows[10].Cells[1].Value = Variaveis.Sensoriamento.MagY; dtSensores.Rows[10].Cells[2].Value = "°"; - dtSensores.Rows[11].Cells[0].Value = "MAG Z"; dtSensores.Rows[11].Cells[1].Value = Variaveis.Sensoriamento.MagZ; dtSensores.Rows[11].Cells[2].Value = "°"; - dtSensores.Rows[12].Cells[0].Value = "BMP Temperatura"; dtSensores.Rows[12].Cells[1].Value = Variaveis.Sensoriamento.BmpTmp.ToString("0.00"); dtSensores.Rows[12].Cells[2].Value = "°C"; - dtSensores.Rows[13].Cells[0].Value = "BMP Pressão"; dtSensores.Rows[13].Cells[1].Value = Variaveis.Sensoriamento.Pressao.ToString("0.00"); dtSensores.Rows[13].Cells[2].Value = "Kpa"; - dtSensores.Rows[14].Cells[0].Value = "BMP Altitude"; dtSensores.Rows[14].Cells[1].Value = Variaveis.Sensoriamento.Altitude.ToString("0.00"); dtSensores.Rows[14].Cells[2].Value = "m"; - dtSensores.Rows[15].Cells[0].Value = "Leitura Tensão"; dtSensores.Rows[15].Cells[1].Value = Variaveis.Sensoriamento.Tensao.ToString("0.00"); dtSensores.Rows[15].Cells[2].Value = ""; - dtSensores.Rows[16].Cells[0].Value = "Leitura Corrente"; dtSensores.Rows[16].Cells[1].Value = Variaveis.Sensoriamento.Corrente.ToString("0.00"); dtSensores.Rows[16].Cells[2].Value = ""; + // Tabela antiga: permanece exatamente como já funcionava. + dtSensores.Rows[0].SetValues( + "Gás e Fumaça", + Variaveis.Sensoriamento.NivelGas.ToString("0.00"), + "%"); + + dtSensores.Rows[1].SetValues( + "DHT Temperatura", + Variaveis.Sensoriamento.Temperatura.ToString("0.00"), + "°C"); + + dtSensores.Rows[2].SetValues( + "DHT Umidade", + Variaveis.Sensoriamento.Umidade.ToString("0.00"), + "%"); + + dtSensores.Rows[3].SetValues("ACE X", Variaveis.Sensoriamento.AceX, "°"); + dtSensores.Rows[4].SetValues("ACE Y", Variaveis.Sensoriamento.AceY, "°"); + dtSensores.Rows[5].SetValues("ACE Z", Variaveis.Sensoriamento.AceZ, "°"); + dtSensores.Rows[6].SetValues("GYR X", Variaveis.Sensoriamento.GyrX, "°"); + dtSensores.Rows[7].SetValues("GYR Y", Variaveis.Sensoriamento.GyrY, "°"); + dtSensores.Rows[8].SetValues("GYR Z", Variaveis.Sensoriamento.GyrZ, "°"); + dtSensores.Rows[9].SetValues("MAG X", Variaveis.Sensoriamento.MagX, "°"); + dtSensores.Rows[10].SetValues("MAG Y", Variaveis.Sensoriamento.MagY, "°"); + dtSensores.Rows[11].SetValues("MAG Z", Variaveis.Sensoriamento.MagZ, "°"); + + dtSensores.Rows[12].SetValues( + "BMP Temperatura", + Variaveis.Sensoriamento.BmpTmp.ToString("0.00"), + "°C"); + + dtSensores.Rows[13].SetValues( + "BMP Pressão", + Variaveis.Sensoriamento.Pressao.ToString("0.00"), + "kPa"); + + dtSensores.Rows[14].SetValues( + "BMP Altitude", + Variaveis.Sensoriamento.Altitude.ToString("0.00"), + "m"); + + dtSensores.Rows[15].SetValues( + "Leitura Tensão", + Variaveis.Sensoriamento.Tensao.ToString("0.00"), + ""); + + dtSensores.Rows[16].SetValues( + "Leitura Corrente", + Variaveis.Sensoriamento.Corrente.ToString("0.00"), + ""); } private void frmSensoriamento_FormClosing(object sender, FormClosingEventArgs e) diff --git a/OrionTardPro/OrionTardPro/FuncoesGlobais.cs b/OrionTardPro/OrionTardPro/FuncoesGlobais.cs index 1a82d02..e3be434 100644 --- a/OrionTardPro/OrionTardPro/FuncoesGlobais.cs +++ b/OrionTardPro/OrionTardPro/FuncoesGlobais.cs @@ -20,49 +20,69 @@ namespace OrionTardPro { public static void EnviarEmail() { - if (HttpService.ServerUp) - { - string Config = File.ReadAllText(VariaveisGerais.CaminhoArquivoLicenca, Encoding.UTF8); - Config = Config.Replace("\r\n", "
").Replace(";", ": "); - var values = new NameValueCollection(); - values["Para"] = VariaveisGerais.EmailFabricante; - values["Assunto"] = "OrionTard Pro"; - values["Conteudo"] = "O Software OrionTard Pro acaba se ser iniciado

" + Config + "
Odômetro Geral: " + Variaveis.PerfilVigente.Odometro; - EnviaRequisisaoPost("/email/enviarEmail", values); - } + // Fire-and-forget deliberado: envio de e-mail nunca deve segurar a UI + // durante a inicialização. A rotina interna já trata timeout e exceções. + _ = EnviarEmailAsync(); } - public static void EnviaRequisisaoPost(string endpoint, NameValueCollection body, int timeoutInSeconds = 5) + public static async Task EnviarEmailAsync() { try { - using (var client = new WebClient()) - using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(timeoutInSeconds))) + if (!await HttpService.ServerUpAsync().ConfigureAwait(false)) + return; + + string config = await Task.Run(() => + File.ReadAllText( + VariaveisGerais.CaminhoArquivoLicenca, + Encoding.UTF8)).ConfigureAwait(false); + + config = config.Replace("\r\n", "
").Replace(";", ": "); + + var values = new NameValueCollection { - // Adicionando headers para a requisição - client.Headers.Add("Content-Type", "application/x-www-form-urlencoded"); + ["Para"] = VariaveisGerais.EmailFabricante, + ["Assunto"] = "OrionTard Pro", + ["Conteudo"] = + "O Software OrionTard Pro acaba se ser iniciado

" + + config + + "
Odômetro Geral: " + + Variaveis.PerfilVigente.Odometro + }; - // Configurando o CancellationToken - var task = Task.Run(() => client.UploadValues(VariaveisGerais.LinkSite + "/api/" + endpoint, body), cts.Token); - - // Aguarda a conclusão da tarefa com timeout - if (task.Wait(TimeSpan.FromSeconds(timeoutInSeconds))) - { - var responseString = Encoding.Default.GetString(task.Result); - Console.WriteLine("Resposta da API: " + responseString); - } - else - { - Console.WriteLine("Erro: Timeout ao enviar a requisição para o endpoint " + endpoint); - } - } + await EnviaRequisisaoPostAsync( + "/email/enviarEmail", + values).ConfigureAwait(false); } - catch (Exception e) + catch (Exception ex) { - Console.WriteLine("Erro ao enviar email: " + e.Message); + Console.WriteLine("Erro ao enviar email: " + ex.Message); } } + public static Task EnviaRequisisaoPostAsync( + string endpoint, + NameValueCollection body, + int timeoutInSeconds = 5) + { + return HttpService.PostFormAsync( + endpoint, + body, + timeoutInSeconds); + } + + // Compatibilidade com chamadas legadas: não bloqueia mais o chamador. + public static void EnviaRequisisaoPost( + string endpoint, + NameValueCollection body, + int timeoutInSeconds = 5) + { + _ = EnviaRequisisaoPostAsync( + endpoint, + body, + timeoutInSeconds); + } + public static IEnumerable GetAllControls(Control control, Type type) { var controls = control.Controls.Cast(); @@ -188,42 +208,50 @@ namespace OrionTardPro public static double CalcularModa(List lista) { - double moda = 0; + if (lista == null || lista.Count == 0) + return 0; + Dictionary valores = new Dictionary(); - foreach (var valor in lista) + foreach (double valor in lista) { - if (valores.Keys.Contains(valor)) - { - valores[valor] = valores[valor] + 1; - } - else - { - valores.Add(valor, 1); - } + if (double.IsNaN(valor) || double.IsInfinity(valor)) + continue; + + int quantidade; + valores.TryGetValue(valor, out quantidade); + valores[valor] = quantidade + 1; } - moda = valores.OrderByDescending(x => x.Value).First().Key; + if (valores.Count == 0) + return 0; - return moda; + return valores + .OrderByDescending(x => x.Value) + .ThenBy(x => x.Key) + .First() + .Key; } public static double CalcularMediana(List lista) { - double mediana = 0; + if (lista == null || lista.Count == 0) + return 0; - if (lista.Count() % 2 == 0) - { - double val1 = lista.Skip((lista.Count() / 2) - 1).First(); - double val2 = lista.Skip((lista.Count() / 2)).First(); - mediana = (val1 + val2) / 2; - } - else - { - int meio = Convert.ToInt32(lista.Count() / 2); - mediana = lista.Skip(meio).First(); - } + // A implementação antiga pegava os elementos centrais sem ordenar. + // A mediana estatística exige ordenação. + List ordenada = lista + .Where(x => !double.IsNaN(x) && !double.IsInfinity(x)) + .OrderBy(x => x) + .ToList(); - return mediana; + if (ordenada.Count == 0) + return 0; + + int meio = ordenada.Count / 2; + if ((ordenada.Count % 2) == 0) + return (ordenada[meio - 1] + ordenada[meio]) / 2.0; + + return ordenada[meio]; } diff --git a/OrionTardPro/OrionTardPro/GeneralJoystick.cs b/OrionTardPro/OrionTardPro/GeneralJoystick.cs index 2e93c51..b146b92 100644 --- a/OrionTardPro/OrionTardPro/GeneralJoystick.cs +++ b/OrionTardPro/OrionTardPro/GeneralJoystick.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Media; +using System.Threading; +using System.Threading.Tasks; using System.Windows.Forms; using static OrionTardPro.Enuns; @@ -26,12 +28,40 @@ namespace OrionTardPro private static TipoAtuador UltimoTipoAtuador; private static int EnviosUltimoProtocolo = 0; + private enum FonteControle { Nenhuma, Teclado, Joystick } + private const int IntervaloSupervisorMs = 100; + private const int ReenvioMotV2Ms = 1000; + private const int ZonaMortaAnalogico = 6500; + private const int JoystickExpiradoMs = 500; + private const int TecladoExpiradoMs = 400; + + private static System.Windows.Forms.Timer supervisorMovimentoTimer; + + // Movimento do rover: a fonte ativa precisa continuar renovando sua intenção. + // Se o teclado/joystick deixar de atualizar, o supervisor converte isso em STOP. + private static FonteControle fonteMovimento = FonteControle.Nenhuma; + private static DateTime ultimaEntradaMovimento = DateTime.MinValue; + private static DateTime ultimoMotV2 = DateTime.MinValue; + + // PTZ: vertical e horizontal são estados independentes. Isso evita o bug antigo + // em que W/S compartilhavam um bool e A/D compartilhavam outro. + private static FonteControle fonteCamera = FonteControle.Nenhuma; + private static DateTime ultimaEntradaCamera = DateTime.MinValue; + private static Direcao cameraVertical = Direcao.Parado; + private static Direcao cameraHorizontal = Direcao.Parado; + + public static bool CameraEmMovimento => + cameraVertical != Direcao.Parado || cameraHorizontal != Direcao.Parado; + public static List ListaTeclasPressionadas = new List() { "Stop" }; - static bool UltimoDireita = false; - static bool UltimoCima = false; static int PulsosCameraLedMais = 0; + private static readonly SemaphoreSlim _joystickConexaoLock = + new SemaphoreSlim(1, 1); + + private static int _pollJoystickEmAndamento; + public static void Inicializar() { joystickTimer = new System.Windows.Forms.Timer() @@ -39,76 +69,223 @@ namespace OrionTardPro Interval = 100, }; joystickTimer.Tick += JoystickTimer_Tick; + supervisorMovimentoTimer = new System.Windows.Forms.Timer() { Interval = IntervaloSupervisorMs }; + supervisorMovimentoTimer.Tick += SupervisorMovimentoTimer_Tick; + supervisorMovimentoTimer.Start(); } - private static void JoystickTimer_Tick(object sender, EventArgs e) + private static void SupervisorMovimentoTimer_Tick(object sender, EventArgs e) { - if (joystickButtons == null) + if (!SerialService.Conectado) { return; } + DateTime agora = DateTime.Now; + + if (UltimaDirecao != Direcao.Parado) + { + int timeoutMovimento = fonteMovimento == FonteControle.Joystick + ? JoystickExpiradoMs + : TecladoExpiradoMs; + + if (fonteMovimento != FonteControle.Nenhuma && + (agora - ultimaEntradaMovimento).TotalMilliseconds > timeoutMovimento) + { + ParadaSegura(fonteMovimento == FonteControle.Joystick + ? "joystick sem atualização" + : "teclado sem atualização"); + return; + } + + // A V2 renova o lease de movimento somente enquanto a fonte de controle + // continua viva. Um KeyUp perdido não pode perpetuar um MOT antigo. + if (SerialService.ProtocoloV2Ativo && + (agora - ultimoMotV2).TotalMilliseconds >= ReenvioMotV2Ms) + { + AtualizaProtocoloMotor(true); + } + } + + if (CameraEmMovimento) + { + int timeoutCamera = fonteCamera == FonteControle.Joystick + ? JoystickExpiradoMs + : TecladoExpiradoMs; + + if (fonteCamera != FonteControle.Nenhuma && + (agora - ultimaEntradaCamera).TotalMilliseconds > timeoutCamera) + { + PararCameraSegura(fonteCamera == FonteControle.Joystick + ? "joystick da câmera sem atualização" + : "teclado da câmera sem atualização"); + } + } + } + + private static Direcao DirecaoDoAnalogico(int x, int y, int centroX, int centroY) + { + int dx = x - centroX, dy = y - centroY; + bool esquerda = dx < -ZonaMortaAnalogico, direita = dx > ZonaMortaAnalogico; + bool cima = dy < -ZonaMortaAnalogico, baixo = dy > ZonaMortaAnalogico; + if (esquerda && cima) return Direcao.EsquerdaCima; + if (esquerda && baixo) return Direcao.EsquerdaBaixo; + if (direita && cima) return Direcao.DireitaCima; + if (direita && baixo) return Direcao.DireitaBaixo; + if (esquerda) return Direcao.Esquerda; + if (direita) return Direcao.Direita; + if (cima) return Direcao.Cima; + if (baixo) return Direcao.Baixo; + return Direcao.Parado; + } + + private static string PrimeiraTeclaPressionada() + { + return ListaTeclasPressionadas != null && ListaTeclasPressionadas.Count > 0 + ? ListaTeclasPressionadas[0] + : "Stop"; + } + + private static void AplicarIntencaoMovimento(Direcao direcao, FonteControle fonte) + { + ultimaEntradaMovimento = DateTime.Now; + + if (direcao == Direcao.Parado) + { + if (fonteMovimento == fonte) + ParadaSegura("comando de parada"); + return; + } + + bool mudou = UltimaDirecao != direcao || fonteMovimento != fonte; + fonteMovimento = fonte; + UltimaDirecao = direcao; + + if (fonte == FonteControle.Joystick) + UltimaDirecaoJoystick = direcao; + + if (mudou) + AtualizaProtocoloMotor(); + } + + // Chamado pelo supervisor físico do formulário enquanto pelo menos uma seta + // continua realmente pressionada. Não envia tráfego; apenas renova o deadman. + public static void RenovarHeartbeatTecladoMovimento() + { + if (fonteMovimento == FonteControle.Teclado && UltimaDirecao != Direcao.Parado) + ultimaEntradaMovimento = DateTime.Now; + } + + public static void RenovarHeartbeatTecladoCamera() + { + if (fonteCamera == FonteControle.Teclado && CameraEmMovimento) + ultimaEntradaCamera = DateTime.Now; + } + + public static void ParadaSegura(string motivo = "parada solicitada") + { + UltimaDirecao = Direcao.Parado; + UltimaDirecaoJoystick = Direcao.Parado; + fonteMovimento = FonteControle.Nenhuma; + ultimaEntradaMovimento = DateTime.MinValue; + Variaveis.Sentido_Driver1 = Sentido.Parado; + Variaveis.Sentido_Driver2 = Sentido.Parado; + SerialService.ParadaSeguraMotores(motivo); + } + + private static async void JoystickTimer_Tick(object sender, EventArgs e) + { + if (Interlocked.Exchange(ref _pollJoystickEmAndamento, 1) == 1) + return; + try { + if (joystickButtons == null || joystick == null) + return; + bool Apertou = false; bool ApertouDirecao = false; bool ApertouDirecaoCamera = false; int X0 = Variaveis.PerfilJoystick.AnalogicoZeroX; int Y0 = Variaveis.PerfilJoystick.AnalogicoZeroY; - joystickButtonsAnterior = joystick.buttons; - joystick.UpdateStatus(); - joystickButtons = joystick.buttons; + Joystick joystickAtual = joystick; + joystickButtonsAnterior = joystickAtual.buttons == null + ? null + : (bool[])joystickAtual.buttons.Clone(); - var LXaxis = (Variaveis.PerfilJoystick.InverteAnalogicoLX) ? joystick.LYaxis : joystick.LXaxis; - var LYaxis = (Variaveis.PerfilJoystick.InverteAnalogicoLY) ? joystick.LXaxis : joystick.LYaxis; - var RXaxis = (Variaveis.PerfilJoystick.InverteAnalogicoRX) ? joystick.RYaxis : joystick.RXaxis; - var RYaxis = (Variaveis.PerfilJoystick.InverteAnalogicoRY) ? joystick.RXaxis : joystick.RYaxis; + // Poll de DirectInput pode atrasar quando o dispositivo/driver está + // instável. Ele roda fora da UI e nunca possui duas leituras simultâneas. + long inicioPollPerf = + PerformanceMonitorService.MarcarInicio(); + + await Task.Run(() => joystickAtual.UpdateStatus()); + + PerformanceMonitorService.ReportarSeLento( + "Joystick.UpdateStatus", + inicioPollPerf, + 120); + + // O joystick pode ter sido substituído enquanto aguardávamos o driver. + if (!ReferenceEquals(joystickAtual, joystick)) + return; + + joystickButtons = joystickAtual.buttons == null + ? null + : (bool[])joystickAtual.buttons.Clone(); + + if (joystickButtons == null) + return; + + var LXaxis = (Variaveis.PerfilJoystick.InverteAnalogicoLX) ? joystickAtual.LYaxis : joystickAtual.LXaxis; + var LYaxis = (Variaveis.PerfilJoystick.InverteAnalogicoLY) ? joystickAtual.LXaxis : joystickAtual.LYaxis; + var RXaxis = (Variaveis.PerfilJoystick.InverteAnalogicoRX) ? joystickAtual.RYaxis : joystickAtual.RXaxis; + var RYaxis = (Variaveis.PerfilJoystick.InverteAnalogicoRY) ? joystickAtual.RXaxis : joystickAtual.RYaxis; // ANALOGICO ESQUERDO //Console.WriteLine("X: " + joystick.LXaxis + "; Y: " + joystick.LYaxis); Variaveis.frmControle.lblAnalogicod.Text = "X: " + RXaxis + "; Y: " + RYaxis; Variaveis.frmControle.lblAnalogicoe.Text = "X: " + LXaxis + "; Y: " + LYaxis; - // DIAGONAIS - if (LXaxis < X0 && LYaxis < Y0) + // DIAGONAIS (com zona morta; evita movimento por drift do analógico) + if (LXaxis < X0 - ZonaMortaAnalogico && LYaxis < Y0 - ZonaMortaAnalogico) { ApertouDirecao = true; - if (UltimaDirecaoJoystick != Direcao.Esquerda) + if (UltimaDirecaoJoystick != Direcao.EsquerdaCima) { UltimaDirecaoJoystick = Direcao.EsquerdaCima; AtualziaConsoleControle("Esquerda/Cima"); } } - else if (LXaxis < X0 && LYaxis > Y0) + else if (LXaxis < X0 - ZonaMortaAnalogico && LYaxis > Y0 + ZonaMortaAnalogico) { ApertouDirecao = true; - if (UltimaDirecaoJoystick != Direcao.Esquerda) + if (UltimaDirecaoJoystick != Direcao.EsquerdaBaixo) { UltimaDirecaoJoystick = Direcao.EsquerdaBaixo; AtualziaConsoleControle("Esquerda/Baixo"); } } - else if (LXaxis > X0 && LYaxis < Y0) + else if (LXaxis > X0 + ZonaMortaAnalogico && LYaxis < Y0 - ZonaMortaAnalogico) { ApertouDirecao = true; - if (UltimaDirecaoJoystick != Direcao.Esquerda) + if (UltimaDirecaoJoystick != Direcao.DireitaCima) { UltimaDirecaoJoystick = Direcao.DireitaCima; AtualziaConsoleControle("Direita/Cima"); } } - else if (LXaxis > X0 && LYaxis > Y0) + else if (LXaxis > X0 + ZonaMortaAnalogico && LYaxis > Y0 + ZonaMortaAnalogico) { ApertouDirecao = true; - if (UltimaDirecaoJoystick != Direcao.Esquerda) + if (UltimaDirecaoJoystick != Direcao.DireitaBaixo) { UltimaDirecaoJoystick = Direcao.DireitaBaixo; AtualziaConsoleControle("Direita/Baixo"); } } // DIRECIONAIS - else if (LXaxis == 0) + else if (LXaxis < X0 - ZonaMortaAnalogico) { ApertouDirecao = true; if (UltimaDirecaoJoystick != Direcao.Esquerda) @@ -117,7 +294,7 @@ namespace OrionTardPro AtualziaConsoleControle("Esquerda"); } } - else if (LXaxis > 50000) + else if (LXaxis > X0 + ZonaMortaAnalogico) { ApertouDirecao = true; if (UltimaDirecaoJoystick != Direcao.Direita) @@ -126,7 +303,7 @@ namespace OrionTardPro AtualziaConsoleControle("Direita"); } } - else if (LYaxis < 500) + else if (LYaxis < Y0 - ZonaMortaAnalogico) { ApertouDirecao = true; if (UltimaDirecaoJoystick != Direcao.Cima) @@ -135,7 +312,7 @@ namespace OrionTardPro AtualziaConsoleControle("Cima"); } } - else if (LYaxis > 50000) + else if (LYaxis > Y0 + ZonaMortaAnalogico) { ApertouDirecao = true; if (UltimaDirecaoJoystick != Direcao.Baixo) @@ -144,7 +321,7 @@ namespace OrionTardPro AtualziaConsoleControle("Baixo"); } } - else if (LXaxis == X0 && joystick.LYaxis == Y0) + else { if (UltimaDirecaoJoystick != Direcao.Parado) { @@ -153,67 +330,30 @@ namespace OrionTardPro } } + AplicarIntencaoMovimento(UltimaDirecaoJoystick, FonteControle.Joystick); - // ANALOGICO DIREITO - //Console.WriteLine("X: " + joystick.RXaxis + "; Y: " + joystick.RYaxis); - if (RYaxis == 0) - { - ApertouDirecaoCamera = true; - if (UltimaDirecaoCamera != Direcao.Cima) - { - AtualziaConsoleControle("Câmera para cima"); - UltimaDirecaoCamera = Direcao.Cima; - ProcessaComandoCamera(Keys.W); - } - } - if (RYaxis > (X0 * 2)) - { - ApertouDirecaoCamera = true; - if (UltimaDirecaoCamera != Direcao.Baixo) - { - AtualziaConsoleControle("Câmera para baixo"); - UltimaDirecaoCamera = Direcao.Baixo; - ProcessaComandoCamera(Keys.S); - } - } - if (RXaxis == 0) - { - ApertouDirecaoCamera = true; - if (UltimaDirecaoCamera != Direcao.Esquerda) - { - AtualziaConsoleControle("Câmera para esquerda"); - UltimaDirecaoCamera = Direcao.Esquerda; - ProcessaComandoCamera(Keys.A); - } - } - if (RXaxis > (X0 * 2)) - { - ApertouDirecaoCamera = true; - if (UltimaDirecaoCamera != Direcao.Direita) - { - AtualziaConsoleControle("Câmera para direita"); - UltimaDirecaoCamera = Direcao.Direita; - ProcessaComandoCamera(Keys.D); - } - } - else if (RXaxis == X0 && RYaxis == Y0) - { - ApertouDirecaoCamera = true; - if (UltimaDirecaoCamera != Direcao.Parado) - { - AtualziaConsoleControle("Câmera parada"); - UltimaDirecaoCamera = Direcao.Parado; - ProcessaComandoCamera(Keys.Escape); - } - } + // ANALOGICO DIREITO - PTZ com zona morta e eixos independentes. + // Igualdade exata com o centro foi removida porque drift analógico podia + // impedir o STOP. Um joystick centralizado só para a câmera se ele for + // a fonte que atualmente a controla; teclado e joystick não brigam entre si. + bool cameraCima = RYaxis < Y0 - ZonaMortaAnalogico; + bool cameraBaixo = RYaxis > Y0 + ZonaMortaAnalogico; + bool cameraEsquerda = RXaxis < X0 - ZonaMortaAnalogico; + bool cameraDireita = RXaxis > X0 + ZonaMortaAnalogico; + + AtualizarEstadoCameraJoystick( + cameraCima, + cameraBaixo, + cameraEsquerda, + cameraDireita); //Console.WriteLine("RX: " + joystick.RXaxis + "; RY: " + joystick.RYaxis); for (int i = 0; i < joystickButtons.Length; i++) { bool statusAtual = joystickButtons[i]; - bool statusAnterior = joystickButtonsAnterior[i]; + bool statusAnterior = joystickButtonsAnterior != null && i < joystickButtonsAnterior.Length && joystickButtonsAnterior[i]; if (statusAtual) { Apertou = true; @@ -228,10 +368,16 @@ namespace OrionTardPro } } - catch + catch (Exception ex) { + ParadaSegura("falha do joystick"); joystickTimer.Enabled = false; - connectToJoystick(joystick); + JoystickExist = false; + Console.WriteLine("[CONTROLE] Falha do joystick: " + ex.Message); + } + finally + { + Interlocked.Exchange(ref _pollJoystickEmAndamento, 0); } if (Variaveis.frmControle.Visible) @@ -243,42 +389,181 @@ namespace OrionTardPro Variaveis.frmControle.txtConsole.Text += TextoConsole + "\r\n"; Variaveis.frmControle.txtConsole.SelectionStart = Variaveis.frmControle.txtConsole.Text.Length; Variaveis.frmControle.txtConsole.ScrollToCaret(); - UltimaDirecao = UltimaDirecaoJoystick; - if (!TextoConsole.Contains("Câmera") && !TextoConsole.Contains("pressionado")) - AtualizaProtocoloMotor(); } - public static void InstanciaJoystick(bool ConexaoAutomatica = true, int IndexJoystick = 0) + // Compatibilidade com chamadas antigas. O fluxo novo deve preferir + // InstanciaJoystickAsync para poder aguardar o resultado sem bloquear a UI. + public static void InstanciaJoystick( + bool ConexaoAutomatica = true, + int IndexJoystick = 0) { - JoystickExist = true; - joystick = new Joystick(Variaveis.frmPrincipal.Handle); - connectToJoystick(joystick, ConexaoAutomatica, IndexJoystick); + _ = InstanciaJoystickAsync( + ConexaoAutomatica, + IndexJoystick); } - private static void connectToJoystick(Joystick joystick, bool ConexaoAutomatica = true, int IndexJoystick = 0) + public static async Task InstanciaJoystickAsync( + bool ConexaoAutomatica = true, + int IndexJoystick = 0, + int timeoutMs = 3500) { - while (JoystickExist) + if (!await _joystickConexaoLock.WaitAsync(0)) + return JoystickExist; + + long inicioDiscoveryPerf = + PerformanceMonitorService.MarcarInicio(); + + bool liberarLockNoFinally = true; + + try { - string sticks = ""; - if (ConexaoAutomatica) - sticks = joystick.FindJoysticks(); - else if (ListaDeJoysticksNomes.Count > 0) - sticks = ListaDeJoysticksNomes[IndexJoystick]; + IntPtr handle = Variaveis.frmPrincipal.Handle; - if (sticks != null) + if (Variaveis.frmPrincipal != null && + !Variaveis.frmPrincipal.IsDisposed) { - if (joystick.AcquireJoystick(sticks)) - { - joystickTimer.Start(); - Variaveis.frmPrincipal.lblJoystickconexao.Text = "Joystick: " + sticks + " Conectado"; - break; - } + Variaveis.frmPrincipal.lblJoystickconexao.Text = + "Joystick: Procurando..."; } - else + + joystickTimer?.Stop(); + + if (UltimaDirecaoJoystick != Direcao.Parado) + ParadaSegura("reconfiguração do joystick"); + + if (fonteCamera == FonteControle.Joystick && CameraEmMovimento) + PararCameraSegura("reconfiguração do joystick"); + + Joystick joystickAnterior = joystick; + joystick = null; + JoystickExist = false; + + if (joystickAnterior != null) + { + await Task.Run(() => joystickAnterior.ReleaseJoystick()); + } + + Task> tarefaDescoberta = Task.Run(() => + { + var novoJoystick = new Joystick(handle); + string nome = null; + + if (ConexaoAutomatica) + { + nome = novoJoystick.FindJoysticks(); + } + else + { + List nomes = ListaDeJoysticksNomes == null + ? new List() + : new List(ListaDeJoysticksNomes); + + if (IndexJoystick >= 0 && IndexJoystick < nomes.Count) + nome = nomes[IndexJoystick]; + } + + if (string.IsNullOrWhiteSpace(nome) || + !novoJoystick.AcquireJoystick(nome)) + { + novoJoystick.ReleaseJoystick(); + return Tuple.Create(null, null); + } + + return Tuple.Create(novoJoystick, nome); + }); + + Task concluida = await Task.WhenAny( + tarefaDescoberta, + Task.Delay(Math.Max(1000, timeoutMs))); + + if (concluida != tarefaDescoberta) { JoystickExist = false; + Variaveis.frmPrincipal.lblJoystickconexao.Text = + "Joystick: Timeout na descoberta"; + + // Não permitimos uma segunda descoberta enquanto o driver antigo + // ainda está preso. Quando ele finalmente retornar, descartamos + // o dispositivo encontrado e liberamos a trava. + liberarLockNoFinally = false; + _ = tarefaDescoberta.ContinueWith(t => + { + try + { + if (t.Status == TaskStatus.RanToCompletion && + t.Result?.Item1 != null) + { + t.Result.Item1.ReleaseJoystick(); + } + } + catch + { + } + finally + { + _joystickConexaoLock.Release(); + } + }); + + Console.WriteLine( + "[CONTROLE] Timeout na descoberta do joystick."); + return false; } + + Tuple resultado = + await tarefaDescoberta; + + if (resultado.Item1 == null) + { + JoystickExist = false; + joystickButtons = null; + joystickButtonsAnterior = null; + Variaveis.frmPrincipal.lblJoystickconexao.Text = + "Joystick: Desconectado"; + return false; + } + + joystick = resultado.Item1; + JoystickExist = true; + joystickButtons = joystick.buttons == null + ? null + : (bool[])joystick.buttons.Clone(); + joystickButtonsAnterior = null; + + joystickTimer?.Start(); + Variaveis.frmPrincipal.lblJoystickconexao.Text = + "Joystick: " + resultado.Item2 + " Conectado"; + + return true; + } + catch (Exception ex) + { + JoystickExist = false; + joystickButtons = null; + joystickButtonsAnterior = null; + + if (Variaveis.frmPrincipal != null && + !Variaveis.frmPrincipal.IsDisposed) + { + Variaveis.frmPrincipal.lblJoystickconexao.Text = + "Joystick: Desconectado"; + } + + Console.WriteLine( + "[CONTROLE] Falha ao descobrir joystick: " + ex.Message); + return false; + } + finally + { + PerformanceMonitorService.ReportarSeLento( + "GeneralJoystick.Discovery", + inicioDiscoveryPerf, + 1200, + JoystickExist ? "conectado" : "sem joystick/timeout"); + + if (liberarLockNoFinally) + _joystickConexaoLock.Release(); } } @@ -289,7 +574,6 @@ namespace OrionTardPro switch (Comando) { case Comando.Vazio: - SerialService.EnvioTimer_Tick(sender, e); break; case Comando.Motor: break; @@ -326,18 +610,20 @@ namespace OrionTardPro IniciarOdometroParcial(); break; case Comando.VelocidadeMais: - if (Variaveis.frmPrincipal.trkVelocidade.Value < Variaveis.PerfilVigente.PotenciaMaxima) + if (Variaveis.frmPrincipal.trkVelocidade.Value < Variaveis.ObterPotenciaMaximaPercentual()) { Variaveis.frmPrincipal.trkVelocidade.Value++; - AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value * 10); + Variaveis.frmControle.trkVelocidade.Value = Variaveis.frmPrincipal.trkVelocidade.Value; + AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value); AtualizaProtocoloMotor(); } break; case Comando.VelocidadeMenos: - if (Variaveis.frmPrincipal.trkVelocidade.Value > Variaveis.PerfilVigente.PotenciaMinima) + if (Variaveis.frmPrincipal.trkVelocidade.Value > Variaveis.ObterPotenciaMinimaPercentual()) { Variaveis.frmPrincipal.trkVelocidade.Value--; - AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value * 10); + Variaveis.frmControle.trkVelocidade.Value = Variaveis.frmPrincipal.trkVelocidade.Value; + AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value); AtualizaProtocoloMotor(); } break; @@ -453,68 +739,86 @@ namespace OrionTardPro else if (UltimaDirecaoJoystick != Direcao.Parado) DirecaoControle = UltimaDirecaoJoystick; Variaveis.frmPrincipal.lblDebugcontrole.Text = "Carro " + DirecaoControle.ToString(); - if (Forcar) - { - EnviosUltimoProtocolo = 0; - SerialService.EnviaProtocolo(TipoProtocolo.Carro, TipoAtuador.Mot); + if (Forcar && (!SerialService.ProtocoloV2Ativo || DirecaoControle == Direcao.Parado)) return; - } bool Enviar = false; + bool inverterEsquerda = Variaveis.PerfilVigente.InverterRotacaoEsquerda; + bool inverterDireita = Variaveis.PerfilVigente.InverterRotacaoDireita; + int potenciaAtual = Variaveis.frmPrincipal.trkVelocidade.Value; + int rampaAtual = Variaveis.frmControle.trkRampaaceleracao.Value; + int precisaoAtual = Variaveis.frmControle.trkPrecisaorampa.Value; + int potenciaMaxima = Variaveis.ObterPotenciaMaximaPercentual(); + if (DirecaoControle == Direcao.Parado) { - AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value * 10); - AtualizaRampaAtual(Variaveis.frmControle.trkRampaaceleracao.Value); - Variaveis.PerfilVigente.PrecisaoAtual = Variaveis.frmControle.trkPrecisaorampa.Value; + AtualizaPotenciaAtual(potenciaAtual); + AtualizaRampaAtual(rampaAtual); + Variaveis.PerfilVigente.PrecisaoAtual = precisaoAtual; Variaveis.Sentido_Driver1 = Sentido.Parado; Variaveis.Sentido_Driver2 = Sentido.Parado; Enviar = true; } - else if (DirecaoControle == Direcao.Cima || (DirecaoControle == Direcao.EsquerdaCima && !Variaveis.PerfilVigente.InverterRotacaoEsquerda) || (DirecaoControle == Direcao.DireitaCima && !Variaveis.PerfilVigente.InverterRotacaoDireita)) + else if (DirecaoControle == Direcao.Cima || (DirecaoControle == Direcao.EsquerdaCima && !inverterEsquerda) || (DirecaoControle == Direcao.DireitaCima && !inverterDireita)) { - AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value * 10); - AtualizaRampaAtual(Variaveis.frmControle.trkRampaaceleracao.Value); - Variaveis.PerfilVigente.PrecisaoAtual = Variaveis.frmControle.trkPrecisaorampa.Value; + AtualizaPotenciaAtual(potenciaAtual); + AtualizaRampaAtual(rampaAtual); + Variaveis.PerfilVigente.PrecisaoAtual = precisaoAtual; Variaveis.Sentido_Driver1 = Variaveis.PerfilVigente.Driver1_Frente; Variaveis.Sentido_Driver2 = Variaveis.PerfilVigente.Driver2_Frente; Enviar = true; } - else if (DirecaoControle == Direcao.Baixo || (DirecaoControle == Direcao.EsquerdaBaixo && !Variaveis.PerfilVigente.InverterRotacaoEsquerda) || (DirecaoControle == Direcao.DireitaBaixo && !Variaveis.PerfilVigente.InverterRotacaoDireita)) + else if (DirecaoControle == Direcao.Baixo || (DirecaoControle == Direcao.EsquerdaBaixo && !inverterEsquerda) || (DirecaoControle == Direcao.DireitaBaixo && !inverterDireita)) { - AtualizaPotenciaAtual(Variaveis.frmPrincipal.trkVelocidade.Value * 10); - AtualizaRampaAtual(Variaveis.frmControle.trkRampaaceleracao.Value); - Variaveis.PerfilVigente.PrecisaoAtual = Variaveis.frmControle.trkPrecisaorampa.Value; + AtualizaPotenciaAtual(potenciaAtual); + AtualizaRampaAtual(rampaAtual); + Variaveis.PerfilVigente.PrecisaoAtual = precisaoAtual; Variaveis.Sentido_Driver1 = Variaveis.PerfilVigente.Driver1_Tras; Variaveis.Sentido_Driver2 = Variaveis.PerfilVigente.Driver2_Tras; Enviar = true; } - else if (DirecaoControle == Direcao.Esquerda || ((DirecaoControle == Direcao.EsquerdaCima || DirecaoControle == Direcao.EsquerdaBaixo) && Variaveis.PerfilVigente.InverterRotacaoEsquerda)) + else if (DirecaoControle == Direcao.Esquerda || ((DirecaoControle == Direcao.EsquerdaCima || DirecaoControle == Direcao.EsquerdaBaixo) && inverterEsquerda)) { - AtualizaPotenciaAtual(Variaveis.PerfilVigente.PotenciaMaxima * 10); - AtualizaRampaAtual(((DirecaoControle == Direcao.EsquerdaCima || DirecaoControle == Direcao.EsquerdaBaixo) && Variaveis.PerfilVigente.InverterRotacaoEsquerda) ? Variaveis.PerfilVigente.RampaMaxima : Variaveis.PerfilVigente.RampaMinima); - Variaveis.PerfilVigente.PrecisaoAtual = ((DirecaoControle == Direcao.EsquerdaCima || DirecaoControle == Direcao.EsquerdaBaixo) && Variaveis.PerfilVigente.InverterRotacaoEsquerda) ? Variaveis.PerfilVigente.PrecisaoMinima : Variaveis.PerfilVigente.PrecisaoMaxima; + bool deveInverter = ((DirecaoControle == Direcao.EsquerdaCima || DirecaoControle == Direcao.EsquerdaBaixo) && inverterEsquerda); + int rampa = deveInverter ? Variaveis.PerfilVigente.RampaMaxima : Variaveis.PerfilVigente.RampaMinima; + int precisao = deveInverter ? Variaveis.PerfilVigente.PrecisaoMinima : Variaveis.PerfilVigente.PrecisaoMaxima; + AtualizaPotenciaAtual(potenciaMaxima); + AtualizaRampaAtual(rampa); + Variaveis.PerfilVigente.PrecisaoAtual = precisao; Variaveis.Sentido_Driver1 = Variaveis.PerfilVigente.Driver1_Esquerda; Variaveis.Sentido_Driver2 = Variaveis.PerfilVigente.Driver2_Esquerda; Enviar = true; } - else if (DirecaoControle == Direcao.Direita || ((DirecaoControle == Direcao.DireitaCima || DirecaoControle == Direcao.DireitaBaixo) && Variaveis.PerfilVigente.InverterRotacaoDireita)) + else if (DirecaoControle == Direcao.Direita || ((DirecaoControle == Direcao.DireitaCima || DirecaoControle == Direcao.DireitaBaixo) && inverterDireita)) { - AtualizaPotenciaAtual(Variaveis.PerfilVigente.PotenciaMaxima * 10); - AtualizaRampaAtual(((DirecaoControle == Direcao.DireitaCima || DirecaoControle == Direcao.DireitaBaixo) && Variaveis.PerfilVigente.InverterRotacaoDireita) ? Variaveis.PerfilVigente.RampaMaxima : Variaveis.PerfilVigente.RampaMinima); - Variaveis.PerfilVigente.PrecisaoAtual = ((DirecaoControle == Direcao.DireitaCima || DirecaoControle == Direcao.DireitaBaixo) && Variaveis.PerfilVigente.InverterRotacaoDireita) ? Variaveis.PerfilVigente.PrecisaoMinima : Variaveis.PerfilVigente.PrecisaoMaxima; + bool deveInverter = ((DirecaoControle == Direcao.DireitaCima || DirecaoControle == Direcao.DireitaBaixo) && inverterDireita); + int rampa = deveInverter ? Variaveis.PerfilVigente.RampaMaxima : Variaveis.PerfilVigente.RampaMinima; + int precisao = deveInverter ? Variaveis.PerfilVigente.PrecisaoMinima : Variaveis.PerfilVigente.PrecisaoMaxima; + AtualizaPotenciaAtual(potenciaMaxima); + AtualizaRampaAtual(rampa); + Variaveis.PerfilVigente.PrecisaoAtual = precisao; Variaveis.Sentido_Driver1 = Variaveis.PerfilVigente.Driver1_Direita; Variaveis.Sentido_Driver2 = Variaveis.PerfilVigente.Driver2_Direita; Enviar = true; } - if (Enviar && SerialService.Conectado && !( - Variaveis.PerfilVigente.ListaAlertas.Any(x => x.PararCarro && x.AlarmeAcionado) && - Variaveis.Sentido_Driver1 == Variaveis.PerfilVigente.Driver1_Frente && Variaveis.Sentido_Driver2 == Variaveis.PerfilVigente.Driver2_Frente - ) - ) + bool bloqueadoPorAlerta = + Variaveis.PerfilVigente.ListaAlertas.Any(x => x.PararCarro && x.AlarmeAcionado) && + Variaveis.Sentido_Driver1 == Variaveis.PerfilVigente.Driver1_Frente && + Variaveis.Sentido_Driver2 == Variaveis.PerfilVigente.Driver2_Frente; + + if (bloqueadoPorAlerta) + { + ParadaSegura("movimento bloqueado por alerta"); + return; + } + + if (Enviar && SerialService.Conectado) { EnviosUltimoProtocolo = 0; SerialService.EnviaProtocolo(TipoProtocolo.Carro, TipoAtuador.Mot); + if (SerialService.ProtocoloV2Ativo && DirecaoControle != Direcao.Parado) + ultimoMotV2 = DateTime.Now; if (Variaveis.PerfilVigente.Atuador_Ativado_Raque == Estado.Ligado) { @@ -523,8 +827,8 @@ namespace OrionTardPro } } - - + + public static void AlternarCamera() { @@ -651,7 +955,7 @@ namespace OrionTardPro public static void EnviarPingCamera() { - if (Variaveis.frmConexao.chbPingCamera.Checked && PulsosCameraLedMais > 0) + if (Variaveis.PerfilVigente.EnviarPingCamera && PulsosCameraLedMais > 0) { for (int i = 0; i < PulsosCameraLedMais; i++) { @@ -668,8 +972,7 @@ namespace OrionTardPro if (Variaveis.frmConexao.chbCamera.Checked) { PulsosCameraLedMais = 0; - SerialService.ControleCamera(0xbb); - SerialService.ControleCamera(0x66); + SerialService.ControleCameraSequencia(false, 0xbb, 0x66); } else { @@ -689,8 +992,7 @@ namespace OrionTardPro { if (!ping) PulsosCameraLedMais = Math.Min(PulsosCameraLedMais + 1, 5); - SerialService.ControleCamera(0xbb); - SerialService.ControleCamera(0x55); + SerialService.ControleCameraSequencia(false, 0xbb, 0x55); } else { @@ -837,122 +1139,248 @@ namespace OrionTardPro return TipoControle; } + public static void AtualizarEstadoCameraTeclado( + bool cima, + bool baixo, + bool esquerda, + bool direita) + { + AtualizarEstadoCamera(cima, baixo, esquerda, direita, FonteControle.Teclado); + } + + private static void AtualizarEstadoCameraJoystick( + bool cima, + bool baixo, + bool esquerda, + bool direita) + { + AtualizarEstadoCamera(cima, baixo, esquerda, direita, FonteControle.Joystick); + } + + private static void AtualizarEstadoCamera( + bool cima, + bool baixo, + bool esquerda, + bool direita, + FonteControle fonte) + { + // Teclas/direções opostas se anulam. + Direcao novoVertical = cima == baixo + ? Direcao.Parado + : (cima ? Direcao.Cima : Direcao.Baixo); + + Direcao novoHorizontal = esquerda == direita + ? Direcao.Parado + : (esquerda ? Direcao.Esquerda : Direcao.Direita); + + bool existeIntencao = + novoVertical != Direcao.Parado || + novoHorizontal != Direcao.Parado; + + if (!existeIntencao) + { + // Uma fonte neutra não cancela a outra. Ex.: joystick centralizado não + // interrompe AWSD; KeyUp só encerra o controle se o teclado era a fonte. + if (fonteCamera == fonte) + AplicarEstadoCamera(Direcao.Parado, Direcao.Parado, FonteControle.Nenhuma); + return; + } + + AplicarEstadoCamera(novoVertical, novoHorizontal, fonte); + } + + private static void AplicarEstadoCamera( + Direcao novoVertical, + Direcao novoHorizontal, + FonteControle fonte) + { + ultimaEntradaCamera = DateTime.Now; + + Direcao verticalAnterior = cameraVertical; + Direcao horizontalAnterior = cameraHorizontal; + + bool mudouVertical = verticalAnterior != novoVertical; + bool mudouHorizontal = horizontalAnterior != novoHorizontal; + bool mudouFonte = fonte != FonteControle.Nenhuma && fonteCamera != fonte; + + if (!mudouVertical && !mudouHorizontal && !mudouFonte) + return; + + cameraVertical = novoVertical; + cameraHorizontal = novoHorizontal; + fonteCamera = fonte; + + if (!SerialService.Conectado) + { + if (!CameraEmMovimento) + fonteCamera = FonteControle.Nenhuma; + return; + } + + bool cameraDireta = Variaveis.frmConexao.chbCamera.Checked; + + if (cameraDireta) + { + if (mudouVertical || mudouFonte) + { + if (verticalAnterior != Direcao.Parado) + EnviarStopCameraVertical(); + + if (novoVertical != Direcao.Parado) + EnviarMovimentoCameraVertical(novoVertical); + } + + if (mudouHorizontal || mudouFonte) + { + if (horizontalAnterior != Direcao.Parado) + EnviarStopCameraHorizontal(); + + if (novoHorizontal != Direcao.Parado) + EnviarMovimentoCameraHorizontal(novoHorizontal); + } + } + else + { + // A ponte textual legada não possui STOP por eixo. Primeiro zera tudo e + // depois reaplica os eixos ainda ativos. Isso garante que soltar D em W+D + // realmente mate o horizontal sem perder definitivamente o vertical. + SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Parado)); + + if (novoVertical != Direcao.Parado) + SerialService.EnviarProtocoloCamera(Convert.ToInt32(novoVertical)); + + if (novoHorizontal != Direcao.Parado) + SerialService.EnviarProtocoloCamera(Convert.ToInt32(novoHorizontal)); + } + + UltimaDirecaoCamera = novoVertical != Direcao.Parado + ? novoVertical + : novoHorizontal; + + AtualizarImagemCamera(); + } + + private static void EnviarMovimentoCameraVertical(Direcao direcao) + { + if (direcao == Direcao.Cima) + SerialService.ControleCameraSequencia(false, 0xaa, 0x11); + else if (direcao == Direcao.Baixo) + SerialService.ControleCameraSequencia(false, 0xaa, 0x22); + } + + private static void EnviarMovimentoCameraHorizontal(Direcao direcao) + { + if (direcao == Direcao.Esquerda) + SerialService.ControleCameraSequencia(false, 0xaa, 0x44); + else if (direcao == Direcao.Direita) + SerialService.ControleCameraSequencia(false, 0xaa, 0x33); + } + + private static void EnviarStopCameraVertical() + { + SerialService.ControleCameraSequencia(true, 0xaa, 0x01); + } + + private static void EnviarStopCameraHorizontal() + { + SerialService.ControleCameraSequencia(true, 0xaa, 0x02); + } + + private static void AtualizarImagemCamera() + { + if (Variaveis.frmControlecarro == null || Variaveis.frmControlecarro.IsDisposed) + return; + + if (cameraVertical == Direcao.Cima) + Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_up; + else if (cameraVertical == Direcao.Baixo) + Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_down; + else if (cameraHorizontal == Direcao.Esquerda) + Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_left; + else if (cameraHorizontal == Direcao.Direita) + Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_right; + else + Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_stop; + } + + public static void PararCameraSegura(string motivo = "parada da câmera") + { + Direcao verticalAnterior = cameraVertical; + Direcao horizontalAnterior = cameraHorizontal; + + cameraVertical = Direcao.Parado; + cameraHorizontal = Direcao.Parado; + UltimaDirecaoCamera = Direcao.Parado; + fonteCamera = FonteControle.Nenhuma; + ultimaEntradaCamera = DateTime.MinValue; + + if (SerialService.Conectado) + { + if (Variaveis.frmConexao.chbCamera.Checked) + { + if (verticalAnterior != Direcao.Parado) + EnviarStopCameraVertical(); + + if (horizontalAnterior != Direcao.Parado) + EnviarStopCameraHorizontal(); + } + else if (verticalAnterior != Direcao.Parado || horizontalAnterior != Direcao.Parado) + { + SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Parado)); + } + } + + AtualizarImagemCamera(); + Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera parada - " + motivo; + } + + // Wrapper mantido para compatibilidade com chamadas antigas. Novos controles de + // teclado usam AtualizarEstadoCameraTeclado para conseguir liberar um eixo só. public static void ProcessaComandoCamera(Keys Tecla) { - if (!SerialService.Conectado) + if (Tecla == Keys.Escape) + { + PararCameraSegura("comando de parada"); return; + } + + bool cima = cameraVertical == Direcao.Cima; + bool baixo = cameraVertical == Direcao.Baixo; + bool esquerda = cameraHorizontal == Direcao.Esquerda; + bool direita = cameraHorizontal == Direcao.Direita; switch (Tecla) { case Keys.W: - if (UltimoCima) - return; - UltimoCima = true; - Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_up; - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x11); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Cima)); - } - Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera Cima"; + cima = true; baixo = false; break; case Keys.S: - if (UltimoCima) - return; - UltimoCima = true; - Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_down; - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x22); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Baixo)); - } - Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera Baixo"; + baixo = true; cima = false; break; case Keys.A: - if (UltimoDireita) - return; - UltimoDireita = true; - Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_left; - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x44); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Esquerda)); - } - Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera Esquerda"; + esquerda = true; direita = false; break; case Keys.D: - if (UltimoDireita) - return; - UltimoDireita = true; - Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_right; - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x33); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Direita)); - } - Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera Direita"; - break; - case Keys.Escape: - Variaveis.frmControlecarro.imgCamera.Image = OrionTardPro.Properties.Resources.analogico_stop; - if (UltimoCima) - { - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x01); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Parado)); - } - UltimoCima = false; - } - if (UltimoDireita) - { - if (Variaveis.frmConexao.chbCamera.Checked) - { - SerialService.ControleCamera(0xaa); - SerialService.ControleCamera(0x02); - } - else - { - SerialService.EnviarProtocoloCamera(Convert.ToInt32(Direcao.Parado)); - } - UltimoDireita = false; - } - Variaveis.frmPrincipal.lblDebugcontrole.Text = "Câmera Parado"; + direita = true; esquerda = false; break; + default: + return; } - } + AtualizarEstadoCamera(cima, baixo, esquerda, direita, FonteControle.Teclado); + } public static void ProcessaComandoCarro(string Tecla) { switch (Tecla) { case "Up": - if (ListaTeclasPressionadas[0] == "Right") + if (PrimeiraTeclaPressionada() == "Right") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_right_up; UltimaDirecao = Direcao.DireitaCima; } - else if (ListaTeclasPressionadas[0] == "Left") + else if (PrimeiraTeclaPressionada() == "Left") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_left_up; UltimaDirecao = Direcao.EsquerdaCima; @@ -964,12 +1392,12 @@ namespace OrionTardPro } break; case "Down": - if (ListaTeclasPressionadas[0] == "Right") + if (PrimeiraTeclaPressionada() == "Right") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_right_down; UltimaDirecao = Direcao.DireitaBaixo; } - else if (ListaTeclasPressionadas[0] == "Left") + else if (PrimeiraTeclaPressionada() == "Left") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_left_down; UltimaDirecao = Direcao.EsquerdaBaixo; @@ -981,12 +1409,12 @@ namespace OrionTardPro } break; case "Left": - if (ListaTeclasPressionadas[0] == "Up") + if (PrimeiraTeclaPressionada() == "Up") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_left_up; UltimaDirecao = Direcao.EsquerdaCima; } - else if (ListaTeclasPressionadas[0] == "Down") + else if (PrimeiraTeclaPressionada() == "Down") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_left_down; UltimaDirecao = Direcao.EsquerdaBaixo; @@ -998,12 +1426,12 @@ namespace OrionTardPro } break; case "Right": - if (ListaTeclasPressionadas[0] == "Up") + if (PrimeiraTeclaPressionada() == "Up") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_right_up; UltimaDirecao = Direcao.DireitaCima; } - else if (ListaTeclasPressionadas[0] == "Down") + else if (PrimeiraTeclaPressionada() == "Down") { Variaveis.frmControlecarro.imgCarro.Image = OrionTardPro.Properties.Resources.analogico_right_down; UltimaDirecao = Direcao.DireitaBaixo; @@ -1020,7 +1448,10 @@ namespace OrionTardPro break; } - AtualizaProtocoloMotor(); + if (Tecla == "Stop") + ParadaSegura("tecla liberada"); + else + AplicarIntencaoMovimento(UltimaDirecao, FonteControle.Teclado); } diff --git a/OrionTardPro/OrionTardPro/Models/Alertas.cs b/OrionTardPro/OrionTardPro/Models/Alertas.cs index f46458b..7e131ec 100644 --- a/OrionTardPro/OrionTardPro/Models/Alertas.cs +++ b/OrionTardPro/OrionTardPro/Models/Alertas.cs @@ -1,4 +1,5 @@ -using System; +using Newtonsoft.Json; +using System; using System.Collections.Generic; using System.Windows.Forms; using static OrionTardPro.Enuns; @@ -18,7 +19,7 @@ namespace OrionTardPro.Models { case TipoSensor.Ten: return Variaveis.Sensoriamento.PercentualTensaoBateria; case TipoSensor.Gas: return Variaveis.Sensoriamento.NivelGas; - case TipoSensor.Ods: return Variaveis.Sensoriamento.OdometroValorConectado; + case TipoSensor.Ods: return (Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial / 60.0 / 60.0); // horas case TipoSensor.Umi: return Variaveis.Sensoriamento.Umidade; case TipoSensor.Tmp: return Variaveis.Sensoriamento.Temperatura; case TipoSensor.Cor: return Variaveis.Sensoriamento.Corrente; @@ -29,6 +30,7 @@ namespace OrionTardPro.Models public double LimiteMinimo { get; set; } public double LimiteMaximo { get; set; } public bool AlertaSonoro { get; set; } + [JsonIgnore] public Control LabelSensor { get; set; } public string LabelSensorNome { get; set; } public bool ComparacaoMaxima { get; set; } @@ -48,7 +50,8 @@ namespace OrionTardPro.Models public TipoSensor TipoSensor { get; set; } public Estado SensorAtivado { get; set; } public string Variavel { get; set; } - public Control LabelSensor { get; set; } + [JsonIgnore] + public Control LabelSensor { get; set; } public double ValorSensor { get diff --git a/OrionTardPro/OrionTardPro/Models/Joystick.cs b/OrionTardPro/OrionTardPro/Models/Joystick.cs index 21a8c5b..94fdabe 100644 --- a/OrionTardPro/OrionTardPro/Models/Joystick.cs +++ b/OrionTardPro/OrionTardPro/Models/Joystick.cs @@ -22,6 +22,7 @@ namespace OrionTardPro private IntPtr hWnd; public bool[] buttons; private string systemJoysticks; + private readonly object _deviceLock = new object(); #endregion @@ -67,19 +68,6 @@ namespace OrionTardPro return null; } - if (systemJoysticks != null) - { - GeneralJoystick.JoystickExist = true; - //if (Variaveis.frmPrincipal != null) - // Variaveis.frmPrincipal.lblJoystickconexao.Text = "Joystick: Conectado"; - } - else - { - GeneralJoystick.JoystickExist = false; - if (Variaveis.frmPrincipal != null) - Variaveis.frmPrincipal.lblJoystickconexao.Text = "Joystick: Desconectado"; - } - string Selecionado = null; if (GeneralJoystick.ListaDeJoysticksNomes.Count > 0) Selecionado = GeneralJoystick.ListaDeJoysticksNomes[0]; @@ -126,35 +114,46 @@ namespace OrionTardPro public void ReleaseJoystick() { - joystickDevice.Unacquire(); + lock (_deviceLock) + { + try + { + joystickDevice?.Unacquire(); + } + catch + { + // O dispositivo pode já ter sido removido fisicamente. + } + } } public void UpdateStatus() { - Poll(); - - int[] extraAxis = state.GetSlider(); - - LXaxis = state.X; - LYaxis = state.Y; - - RXaxis = state.Z; - RYaxis = state.Rz; - - byte[] jsButtons = state.GetButtons(); - - if (jsButtons == null) + lock (_deviceLock) { - return; - } + Poll(); - buttons = new bool[jsButtons.Length]; + int[] extraAxis = state.GetSlider(); - int i = 0; - foreach (byte button in jsButtons) - { - buttons[i] = button >= 128; - i++; + LXaxis = state.X; + LYaxis = state.Y; + + RXaxis = state.Z; + RYaxis = state.Rz; + + byte[] jsButtons = state.GetButtons(); + + if (jsButtons == null) + return; + + buttons = new bool[jsButtons.Length]; + + int i = 0; + foreach (byte button in jsButtons) + { + buttons[i] = button >= 128; + i++; + } } } @@ -173,7 +172,7 @@ namespace OrionTardPro } catch { - throw (null); + throw; } } } diff --git a/OrionTardPro/OrionTardPro/Models/LogsModel.cs b/OrionTardPro/OrionTardPro/Models/LogsModel.cs index f48f074..cee351e 100644 --- a/OrionTardPro/OrionTardPro/Models/LogsModel.cs +++ b/OrionTardPro/OrionTardPro/Models/LogsModel.cs @@ -4,12 +4,27 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Device.Location; +using System.Globalization; using System.IO; using System.Linq; +using System.Threading; +using System.Threading.Tasks; using static OrionTardPro.Enuns; namespace OrionTardPro.Models { + public enum TipoEventoLog + { + Periodico = 0, + Conectado = 1, + Desconectado = 2, + Infiltracao = 3, + SaudeSensores = 4, + TrocaPerfil = 5, + Encerramento = 6, + Manual = 7 + } + public class LogRequestModel { public string cpf { get; set; } @@ -21,8 +36,27 @@ namespace OrionTardPro.Models public class LogModel { + // Campos novos. Os campos antigos foram mantidos para preservar os relatórios. + public int schemaVersao { get; set; } = 2; + public string logId { get; set; } + public string tipoEvento { get; set; } + public string mensagemEvento { get; set; } + public DateTime periodoInicio { get; set; } + public DateTime periodoFim { get; set; } + public int protocoloVersao { get; set; } + public string revisaoHardware { get; set; } + public string capacidades { get; set; } + public byte mascaraSaude { get; set; } + public bool alarmeInfiltracao { get; set; } + public int infiltracaoRaw { get; set; } + public double potenciaW { get; set; } + public double rollDeg { get; set; } + public double pitchDeg { get; set; } + public DateTime data { get; set; } public int firmwareVersao { get; set; } + public string firmwareVersaoStr { get; set; } + public long idCliente { get; set; } public int robotNs { get; set; } public string modelo { get; set; } public string versao { get; set; } @@ -56,116 +90,543 @@ namespace OrionTardPro.Models public double media { get; set; } public double moda { get; set; } public double mediana { get; set; } - public List lista { get; set; } + public double ultimo { get; set; } + public int quantidadeAmostras { get; set; } + + // Acumulador de runtime para média O(1). Não faz parte do arquivo de log. + [JsonIgnore] + public double somaAmostrasInterna { get; set; } + + public List lista { get; set; } = new List(); } public static class LogsFuncoes { - public static int TempoLog { get; set; } = 1000 * 60 * 5; // 5 minutos - public static int TimerLog { get; set; } = 0; - public static string CaminhoArquivoLogs = Variaveis.CaminhoProjeto + "\\Logs" + Variaveis.ExtLog; + public static int TempoLog { get; set; } = 1000 * 60 * 5; + public static int TimerLog { get; set; } = 0; // compatibilidade com código legado + public static string CaminhoArquivoLogs = + Variaveis.CaminhoProjeto + "\\Logs" + Variaveis.ExtLog; - public static void RegistrarLog() + private static readonly object _arquivoLock = new object(); + private static readonly SemaphoreSlim _envioLock = new SemaphoreSlim(1, 1); + + private sealed class LogPendentePersistencia { - Variaveis.LogsSensoresDinamicos.ForEach(x => x.lista.Clear()); + public LogModel Log { get; set; } + public bool TentarEnviar { get; set; } + } - int baud = 0; - int.TryParse(Variaveis.frmConexao.cmbBaud.Text, out baud); - if (baud == 0) + private static readonly object _filaPersistenciaLock = new object(); + private static readonly Queue _filaPersistencia = + new Queue(); + private static int _persistenciaLogsEmAndamento; + + public static int FilaPersistenciaPendentePerformance + { + get { - baud = 38400; - } - - LogModel Log = new LogModel() - { - data = DateTime.Now, - firmwareVersao = Variaveis._FirmwareVersion, - robotNs = Variaveis._RobotNS, - modelo = Variaveis._Model.ToString(), - versao = VariaveisGerais.Versao, - servico = Variaveis.Cliente, - camera = Variaveis.frmPrincipal.lblCameraconexao.Text, - altitude = Variaveis.Altitude, - latitude = Variaveis.Latitude, - longitude = Variaveis.Longitude, - baudRate = baud, - cpf = Variaveis.frmLicenca.txtCNPJ.Text, - usuario = Variaveis.frmLicenca.txtLicenciadopara.Text, - perfil = Variaveis.PerfilSelecionado, - portaCOM = Variaveis.frmConexao.cmbPorta.Text, - tempoTotal = SerialService.TempoAtivo, - tempoConectado = SerialService.TempoConectado, - odometroTotal = Variaveis.Sensoriamento.OdometroValorTotal, - odometroConectado = Variaveis.Sensoriamento.OdometroValorConectado, - odometroMotores = Variaveis.Sensoriamento.OdometroValorMotorLigado, - odometroMotoresParcial = Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial, - conectado = SerialService.Conectado, - sensores = Variaveis.LogsSensoresDinamicos, - distancia = Variaveis.Sensoriamento.DeslocamentoCarro, - }; - - List Logs = new List(); - if (File.Exists(CaminhoArquivoLogs)) - Logs = File.ReadAllLines(CaminhoArquivoLogs).ToList(); - - string LogStr = JsonConvert.SerializeObject(Log); - Logs.Add(LogStr); - File.WriteAllLines(CaminhoArquivoLogs, Logs); - - if (HttpService.ServerUp) - { - foreach (var LogItem in Logs) - { - LogRequestModel LogRequest = new LogRequestModel() - { - cpf = Log.cpf, - perfil = Log.perfil, - latitude = Log.latitude, - longitude = Log.longitude, - log = LogItem - }; - EnviarLog(LogRequest); - } - Logs.Clear(); - File.WriteAllLines(CaminhoArquivoLogs, Logs); + lock (_filaPersistenciaLock) + return _filaPersistencia.Count; } } - private static void EnviarLog(LogRequestModel Log) + public static bool PersistenciaLogsEmAndamentoPerformance { - if (HttpService.ServerUp) + get { - var body = new NameValueCollection(); - body["cpf"] = Log.cpf; - body["perfil"] = Log.perfil; - body["latitude"] = Log.latitude.ToString(); - body["longitude"] = Log.longitude.ToString(); - body["log"] = Log.log; - - FuncoesGlobais.EnviaRequisisaoPost("/otp/insertLog", body); + return Interlocked.CompareExchange( + ref _persistenciaLogsEmAndamento, + 0, + 0) != 0; } } + private static DateTime _periodoInicio = DateTime.Now; + private static DateTime _ultimoLogPeriodico = DateTime.Now; + private static GeoCoordinateWatcher _geoWatcher; + + public static bool TemAtividadeNoPeriodo + { + get + { + return SerialService.Conectado || + Variaveis.LogsSensoresDinamicos.Any( + x => x.lista != null && x.lista.Count > 0); + } + } + + public static bool DeveRegistrarLogPeriodico() + { + return TemAtividadeNoPeriodo && + (DateTime.Now - _ultimoLogPeriodico).TotalMilliseconds >= TempoLog; + } + + // Mantém compatibilidade com as chamadas existentes. + public static void RegistrarLog() + { + RegistrarLog(TipoEventoLog.Manual, null, true); + } + + public static void RegistrarLog( + TipoEventoLog tipoEvento, + string mensagem = null, + bool tentarEnviar = true) + { + try + { + long inicioSnapshotPerf = + PerformanceMonitorService.MarcarInicio(); + + // O snapshot é montado no chamador (normalmente UI), pois + // algumas informações ainda vêm dos Forms. A parte cara + // de serialização + disco acontece em background. + LogModel log = + CriarSnapshot( + tipoEvento, + mensagem); + + PerformanceMonitorService.ReportarSeLento( + "LogsFuncoes.CriarSnapshot", + inicioSnapshotPerf, + 120, + tipoEvento.ToString()); + + ReiniciarJanelaSensores(); + _periodoInicio = log.periodoFim; + _ultimoLogPeriodico = log.periodoFim; + TimerLog = 0; + + if (tipoEvento == TipoEventoLog.Encerramento) + { + // No encerramento não podemos depender de uma Task que + // pode ser abortada pelo fim do processo. + string json = + JsonConvert.SerializeObject(log); + + lock (_arquivoLock) + { + GarantirDiretorio(); + File.AppendAllText( + CaminhoArquivoLogs, + json + Environment.NewLine, + System.Text.Encoding.UTF8); + } + + return; + } + + EnfileirarPersistenciaLog( + log, + tentarEnviar); + } + catch (Exception ex) + { + Console.WriteLine( + "[LOG] Falha ao preparar snapshot: " + + ex.Message); + } + } + + private static void EnfileirarPersistenciaLog( + LogModel log, + bool tentarEnviar) + { + lock (_filaPersistenciaLock) + { + _filaPersistencia.Enqueue( + new LogPendentePersistencia + { + Log = log, + TentarEnviar = tentarEnviar + }); + } + + IniciarWorkerPersistenciaLogs(); + } + + private static void IniciarWorkerPersistenciaLogs() + { + if (Interlocked.Exchange( + ref _persistenciaLogsEmAndamento, + 1) == 1) + { + return; + } + + Task.Run( + (Action)ProcessarFilaPersistenciaLogs); + } + + private static void ProcessarFilaPersistenciaLogs() + { + bool deveTentarEnviar = false; + + try + { + while (true) + { + LogPendentePersistencia item; + + lock (_filaPersistenciaLock) + { + if (_filaPersistencia.Count == 0) + break; + + item = + _filaPersistencia.Dequeue(); + } + + try + { + long inicioPerf = + PerformanceMonitorService.MarcarInicio(); + + string json = + JsonConvert.SerializeObject( + item.Log); + + lock (_arquivoLock) + { + GarantirDiretorio(); + + File.AppendAllText( + CaminhoArquivoLogs, + json + Environment.NewLine, + System.Text.Encoding.UTF8); + } + + PerformanceMonitorService.ReportarSeLento( + "PersistenciaLogBackground", + inicioPerf, + 200); + + deveTentarEnviar |= + item.TentarEnviar; + } + catch (Exception ex) + { + Console.WriteLine( + "[LOG] Falha ao persistir em background: " + + ex.Message); + + // Não perde o snapshot em memória. Recoloca no fim + // da fila e encerra esta rodada para evitar loop quente. + lock (_filaPersistenciaLock) + { + _filaPersistencia.Enqueue( + item); + } + + break; + } + } + } + finally + { + Interlocked.Exchange( + ref _persistenciaLogsEmAndamento, + 0); + + bool aindaHaPendentes; + lock (_filaPersistenciaLock) + { + aindaHaPendentes = + _filaPersistencia.Count > 0; + } + + if (aindaHaPendentes) + { + // Pequena folga evita retry agressivo se o disco + // estiver temporariamente indisponível. + Task.Delay(1000) + .ContinueWith( + _ => IniciarWorkerPersistenciaLogs()); + } + } + + if (deveTentarEnviar) + _ = TentarEnviarPendentesAsync(); + } + + + private static LogModel CriarSnapshot( + TipoEventoLog tipoEvento, + string mensagem) + { + DateTime agora = DateTime.Now; + bool v2 = Variaveis.PerfilVigente != null && + Variaveis.PerfilVigente.VersaoProtocolo == VersaoProtocolo.v2; + + int baud = v2 ? 9600 : + (Variaveis.PerfilVigente != null + ? Variaveis.PerfilVigente.BaudRate + : 38400); + try + { + if (SerialService._PortaRS485 != null && SerialService._PortaRS485.IsOpen) + baud = SerialService._PortaRS485.BaudRate; + } + catch { } + + return new LogModel + { + logId = Guid.NewGuid().ToString("N"), + tipoEvento = tipoEvento.ToString(), + mensagemEvento = mensagem, + periodoInicio = _periodoInicio, + periodoFim = agora, + data = agora, + protocoloVersao = Variaveis.PerfilVigente == null + ? (int)VersaoProtocolo.v1 + : (int)Variaveis.PerfilVigente.VersaoProtocolo, + firmwareVersao = Variaveis._FirmwareVersion, + firmwareVersaoStr = v2 && !string.IsNullOrWhiteSpace(SerialService.FirmwareVersionTextoV6) + ? SerialService.FirmwareVersionTextoV6 + : Variaveis._FirmwareVersion.ToString(CultureInfo.InvariantCulture), + idCliente = v2 ? SerialService.ClienteIdV6 : 0, + robotNs = Variaveis._RobotNS, + modelo = Variaveis._Model.ToString(), + revisaoHardware = v2 ? SerialService.RevisaoHardwareV6 : null, + capacidades = v2 ? SerialService.CapacidadesV6 : null, + mascaraSaude = v2 ? SerialService.MascaraSaudeV6 : (byte)0, + alarmeInfiltracao = v2 && SerialService.AlarmeInfiltracaoV6, + infiltracaoRaw = v2 ? SerialService.InfiltracaoRawV6 : 0, + potenciaW = v2 ? SerialService.PotenciaW_V6 : 0, + rollDeg = v2 ? SerialService.RollDeg_V6 : 0, + pitchDeg = v2 ? SerialService.PitchDeg_V6 : 0, + versao = VariaveisGerais.Versao, + servico = Variaveis.Cliente, + camera = Variaveis.frmPrincipal?.lblCameraconexao?.Text, + altitude = Variaveis.Altitude, + latitude = Variaveis.Latitude, + longitude = Variaveis.Longitude, + baudRate = baud, + cpf = Variaveis.frmLicenca?.txtCNPJ?.Text, + usuario = Variaveis.frmLicenca?.txtLicenciadopara?.Text, + perfil = Variaveis.PerfilSelecionado, + portaCOM = SerialService.PortaCom, + tempoTotal = SerialService.TempoAtivo, + tempoConectado = SerialService.TempoConectado, + odometroTotal = Variaveis.Sensoriamento.OdometroValorTotal, + odometroConectado = Variaveis.Sensoriamento.OdometroValorConectado, + odometroMotores = Variaveis.Sensoriamento.OdometroValorMotorLigado, + odometroMotoresParcial = Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial, + conectado = SerialService.Conectado, + sensores = CriarSnapshotSensores(), + distancia = Variaveis.Sensoriamento.DeslocamentoCarro + }; + } + + private static List CriarSnapshotSensores() + { + return Variaveis.LogsSensoresDinamicos.Select(sensor => + { + List valores = sensor.lista == null + ? new List() + : sensor.lista.Where(NumeroValido).ToList(); + + return new LogSensorModel + { + sensor = sensor.sensor, + descricao = sensor.descricao, + quantidadeAmostras = valores.Count, + ultimo = valores.Count == 0 ? 0 : valores[valores.Count - 1], + minimo = valores.Count == 0 ? 0 : valores.Min(), + maximo = valores.Count == 0 ? 0 : valores.Max(), + media = valores.Count == 0 ? 0 : valores.Average(), + moda = valores.Count == 0 ? 0 : FuncoesGlobais.CalcularModa(valores), + mediana = valores.Count == 0 ? 0 : FuncoesGlobais.CalcularMediana(valores), + lista = valores + }; + }).ToList(); + } + + private static bool NumeroValido(double valor) + { + return !double.IsNaN(valor) && !double.IsInfinity(valor); + } + + private static void ReiniciarJanelaSensores() + { + foreach (LogSensorModel sensor in Variaveis.LogsSensoresDinamicos) + { + sensor.lista?.Clear(); + sensor.minimo = 0; + sensor.maximo = 0; + sensor.media = 0; + sensor.moda = 0; + sensor.mediana = 0; + sensor.ultimo = 0; + sensor.quantidadeAmostras = 0; + sensor.somaAmostrasInterna = 0; + } + } + + public static async Task TentarEnviarPendentesAsync() + { + // Garante que o envio legado síncrono nunca bloqueie a thread da interface. + await Task.Delay(1).ConfigureAwait(false); + + if (!File.Exists(CaminhoArquivoLogs)) + return; + + if (!await HttpService.ServerUpAsync().ConfigureAwait(false)) + return; + if (!await _envioLock.WaitAsync(0).ConfigureAwait(false)) + return; + + try + { + List pendentes; + lock (_arquivoLock) + { + pendentes = File.ReadAllLines(CaminhoArquivoLogs) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToList(); + } + + int enviados = 0; + foreach (string item in pendentes) + { + if (!await EnviarLogComConfirmacaoAsync(item).ConfigureAwait(false)) + break; + enviados++; + } + + if (enviados > 0) + { + lock (_arquivoLock) + { + // Releitura preserva registros anexados enquanto o lote era enviado. + List atuais = File.ReadAllLines(CaminhoArquivoLogs) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .ToList(); + int remover = Math.Min(enviados, atuais.Count); + GravarArquivoAtomicamente(atuais.Skip(remover)); + } + } + } + catch (Exception ex) + { + Console.WriteLine("[LOG] Falha ao enviar pendências: " + ex.Message); + } + finally + { + _envioLock.Release(); + } + } + + private static async Task EnviarLogComConfirmacaoAsync( + string logJson) + { + try + { + LogModel log = + JsonConvert.DeserializeObject(logJson); + + if (log == null) + return false; + + var body = new NameValueCollection + { + ["cpf"] = log.cpf ?? "", + ["perfil"] = log.perfil ?? "", + ["latitude"] = log.latitude.ToString(CultureInfo.InvariantCulture), + ["longitude"] = log.longitude.ToString(CultureInfo.InvariantCulture), + ["log"] = logJson, + ["logId"] = log.logId ?? "" + }; + + // Confirmação real por status HTTP 2xx. A rede nunca bloqueia + // a thread da interface e possui timeout no HttpService. + return await FuncoesGlobais + .EnviaRequisisaoPostAsync("/otp/insertLog", body) + .ConfigureAwait(false); + } + catch (Exception ex) + { + Console.WriteLine( + "[LOG] Envio não confirmado: " + ex.Message); + return false; + } + } + + private static void GravarArquivoAtomicamente(IEnumerable linhas) + { + GarantirDiretorio(); + string temporario = CaminhoArquivoLogs + ".tmp"; + File.WriteAllLines(temporario, linhas, System.Text.Encoding.UTF8); + + if (File.Exists(CaminhoArquivoLogs)) + { + string backup = CaminhoArquivoLogs + ".bak"; + try + { + File.Replace(temporario, CaminhoArquivoLogs, backup, true); + } + catch + { + if (File.Exists(backup)) File.Delete(backup); + File.Copy(temporario, CaminhoArquivoLogs, true); + File.Delete(temporario); + return; + } + + try { if (File.Exists(backup)) File.Delete(backup); } catch { } + return; + } + + File.Copy(temporario, CaminhoArquivoLogs, true); + File.Delete(temporario); + } + + private static void GarantirDiretorio() + { + string diretorio = Path.GetDirectoryName(CaminhoArquivoLogs); + if (!string.IsNullOrWhiteSpace(diretorio)) + Directory.CreateDirectory(diretorio); + } + public static void IniciarGeoWatcher() { try { - GeoCoordinateWatcher GeoWatcher = new GeoCoordinateWatcher(); - GeoWatcher.PositionChanged += GeoWatcher_PositionChanged; ; - GeoWatcher.Start(); + if (_geoWatcher != null) + return; + + _geoWatcher = new GeoCoordinateWatcher(); + _geoWatcher.PositionChanged += GeoWatcher_PositionChanged; + _geoWatcher.Start(); } catch (Exception ex) { - + Console.WriteLine("[LOG] Falha ao iniciar localização: " + ex.Message); } } - private static void GeoWatcher_PositionChanged(object sender, GeoPositionChangedEventArgs e) + public static void EncerrarGeoWatcher() + { + try + { + if (_geoWatcher == null) return; + _geoWatcher.PositionChanged -= GeoWatcher_PositionChanged; + _geoWatcher.Stop(); + _geoWatcher.Dispose(); + _geoWatcher = null; + } + catch (Exception ex) + { + Console.WriteLine("[LOG] Falha ao encerrar localização: " + ex.Message); + } + } + + private static void GeoWatcher_PositionChanged( + object sender, + GeoPositionChangedEventArgs e) { Variaveis.Latitude = e.Position.Location.Latitude; Variaveis.Longitude = e.Position.Location.Longitude; Variaveis.Altitude = e.Position.Location.Altitude; } } - } diff --git a/OrionTardPro/OrionTardPro/Models/PerfilModel.cs b/OrionTardPro/OrionTardPro/Models/PerfilModel.cs index e75dca2..9277555 100644 --- a/OrionTardPro/OrionTardPro/Models/PerfilModel.cs +++ b/OrionTardPro/OrionTardPro/Models/PerfilModel.cs @@ -17,6 +17,10 @@ namespace OrionTardPro.Models public double RPM { get; set; } = 30; public double DiametroRoda { get; set; } = 130; public double OffsetOdometro { get; set; } = 0.01; + // Calibração persistente do inclinômetro. + // Esses offsets pertencem ao perfil/instalação e são restaurados ao abrir o Orion. + public double Sensor_InclinacaoLateral_Offset { get; set; } = 0; + public double Sensor_InclinacaoFrontal_Offset { get; set; } = 0; public bool InverterRotacaoEsquerda { get; set; } = false; public bool InverterRotacaoDireita { get; set; } = false; public int Offsett_Driver1_Frente { get; set; } = 0; @@ -38,9 +42,8 @@ namespace OrionTardPro.Models public int TempoEntrePings { get; set; } = 5; public int QuantidadeEnvios { get; set; } = 2; public bool ControleIndividualCamera { get; set; } = false; + public bool EnviarPingCamera { get; set; } = false; public bool ReceberProtocoloCompleto { get; set; } = true; - public double COR_coeficienteA { get; set; } = 0.097; - public double COR_coeficienteB { get; set; } = -0.170; public double COR_multiplicador { get; set; } = 1; // DADOS DO PERFIL public string PerfilSelecionado { get; set; } = "Perfis\\default" + Variaveis.ExtPerfil; @@ -56,6 +59,7 @@ namespace OrionTardPro.Models public Estado Sensor_Ativado_COR { get; set; } = Estado.Desligado; public Estado Sensor_Ativado_VEL { get; set; } = Estado.Desligado; public Estado Sensor_Ativado_TMP { get; set; } = Estado.Desligado; + public Estado Sensor_Ativado_INF { get; set; } = Estado.Desligado; public Estado Sensor_Ativado_Watchdog { get; set; } = Estado.Desligado; public Estado Atuador_Ativado_MOT { get; set; } = Estado.Ligado; public Estado Atuador_Ativado_ILU { get; set; } = Estado.Desligado; @@ -78,7 +82,8 @@ namespace OrionTardPro.Models public Sentido Raque_Desenrolar { get; set; } = Sentido.Antihorario; public int BaudRate { get; set; } = 38400; public bool RequisitarDados { get; set; } = false; - public bool FirmwareF4 { get; set; } = false; + //public bool FirmwareF4 { get; set; } = false; + public VersaoProtocolo VersaoProtocolo { get; set; } = VersaoProtocolo.v1; public byte EnderecoReleGeral { get; set; } = 0x00; public int CanalReleGeral { get; set; } = 1; public int AnguloCarimboHorizontal { get; set; } = 67; @@ -141,7 +146,9 @@ namespace OrionTardPro.Models public double GetVelocidadeMaxima() { - return Math.Round((Convert.ToDouble(RPM * (DiametroRoda / 1000)) * Math.PI) / 60, 2); + // Mantém a precisão do cálculo físico durante a integração. + // O arredondamento fica apenas para exibição na interface. + return (RPM * (DiametroRoda / 1000.0) * Math.PI) / 60.0; } } diff --git a/OrionTardPro/OrionTardPro/Models/SensoresModel.cs b/OrionTardPro/OrionTardPro/Models/SensoresModel.cs index 766b88d..97339a5 100644 --- a/OrionTardPro/OrionTardPro/Models/SensoresModel.cs +++ b/OrionTardPro/OrionTardPro/Models/SensoresModel.cs @@ -1,8 +1,10 @@ -using System; +using OrionTardPro.Services; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Drawing; using System.Linq; +using System.Windows.Forms; using static OrionTardPro.Enuns; namespace OrionTardPro.Models @@ -36,29 +38,89 @@ namespace OrionTardPro.Models public bool ReleGeralConectado { get; set; } public bool MepCarimbando { get; set; } public bool MepContato { get; set; } - + public int InfiltracaoRaw { get; set; } + public bool InfiltracaoDetectada { get; set; } + #endregion public double Sensor_InclinacaoLateral_Offset { get; set; } = 0; public double Sensor_InclinacaoFrontal_Offset { get; set; } = 0; + private bool FirmwareV6 + { + get + { + return SerialService.Conectado && + SerialService.ProtocoloSessao == VersaoProtocolo.v2; + } + } #region GETs - + + /// + /// Normaliza um angulo para o intervalo [-180, +180). + /// Evita descontinuidades como +179 -> -179 virarem aproximadamente -359 + /// depois da aplicacao de um offset de zero. + /// + private static double NormalizarAngulo180(double angulo) + { + if (double.IsNaN(angulo) || double.IsInfinity(angulo)) + return angulo; + + angulo %= 360.0; + if (angulo >= 180.0) + angulo -= 360.0; + else if (angulo < -180.0) + angulo += 360.0; + + return angulo; + } + public double InclinacaoLateral { get { - double angulo = Math.Round(((Math.Atan2((AceX), Math.Sqrt(AceY * AceY + AceZ * AceZ)) * 180.0 / Math.PI)) * 2.0, 3); + if (FirmwareV6) + { + return Math.Round( + NormalizarAngulo180( + SerialService.RollDeg_V6 + + Sensor_InclinacaoLateral_Offset), + 2); + } + + double angulo = + Math.Atan2( + AceX, + Math.Sqrt(AceY * AceY + AceZ * AceZ) + ) * 180.0 / Math.PI; + + angulo *= 2.0; angulo += Sensor_InclinacaoFrontal_Offset; + return Math.Round(angulo, 2); } } + public double InclinacaoFrontal { get { - double angulo = Math.Round(((Math.Atan2(AceY, AceZ) * 180.0 / Math.PI)) * 2.0, 3); + if (FirmwareV6) + { + return Math.Round( + NormalizarAngulo180( + SerialService.PitchDeg_V6 + + Sensor_InclinacaoFrontal_Offset), + 2); + } + + double angulo = + Math.Atan2(AceY, AceZ) * + 180.0 / Math.PI; + + angulo *= 2.0; angulo += Sensor_InclinacaoLateral_Offset; + return Math.Round(angulo, 2); } } @@ -148,18 +210,20 @@ namespace OrionTardPro.Models { get { - double valor = Math.Round(((float)InaCor) / (float)Variaveis.PerfilVigente.CorrenteMaximaBateria, 2) / 10.0; + double correnteMaximaA = + Variaveis.PerfilVigente.CorrenteMaximaBateria; - if (valor < 0) - { - valor = 0; - } - else if (valor > 100) - { - valor = 100; - } + if (correnteMaximaA <= 0) + return 0; - return valor; + double correnteAtualA = Math.Abs(InaCor) / 1000.0; + + double percentual = + correnteAtualA / correnteMaximaA * 100.0; + + return Math.Round( + Math.Max(0, Math.Min(100, percentual)), + 2); } } public double Tensao @@ -173,31 +237,53 @@ namespace OrionTardPro.Models { get { - double valor = Math.Round(InaTen / Variaveis.PerfilVigente.TensaoMaximaBateria, 2) * 100.0; + double minima = Variaveis.PerfilVigente.TensaoMinimaBateria; + double maxima = Variaveis.PerfilVigente.TensaoMaximaBateria; - if (valor < 0) - { - valor = 0; - } - else if (valor > 100) - { - valor = 100; - } + if (maxima <= minima) + return 0; - return valor; + double percentual = + (InaTen - minima) / (maxima - minima) * 100.0; + + return Math.Round( + Math.Max(0, Math.Min(100, percentual)), + 2); } } public int TempoEstimadoBateria { get { - double PotenciaTotal = Variaveis.PerfilVigente.CorrenteMaximaBateria * Variaveis.PerfilVigente.TensaoMaximaBateria * 1.0; - double PotenciaRestante = PotenciaTotal * (PercentualTensaoBateria / 100.0); - var PotenciaAtual = ((Corrente == 0) ? 1 : (Corrente / 1000.0)) * ((Tensao == 0) ? 1 : Tensao); - double HorasRestante = PotenciaRestante / PotenciaAtual; - int SegundosRestantes = (int)(HorasRestante * 60.0 * 60.0); + double capacidadeAh = + Variaveis.PerfilVigente.CorrenteMaximaBateria; - return SegundosRestantes; + double percentualCarga = + PercentualTensaoBateria / 100.0; + + double correnteA = + Math.Abs(Corrente) / 1000.0; + + if (capacidadeAh <= 0 || + percentualCarga <= 0 || + correnteA < 0.05) + { + return 0; + } + + double capacidadeRestanteAh = + capacidadeAh * percentualCarga; + + double horasRestantes = + capacidadeRestanteAh / correnteA; + + double segundosRestantes = + horasRestantes * 3600.0; + + if (segundosRestantes >= int.MaxValue) + return int.MaxValue; + + return (int)Math.Round(segundosRestantes); } } public double Temperatura @@ -229,8 +315,84 @@ namespace OrionTardPro.Models } } - private List VelocidadesD = new List(); - private List VelocidadesE = new List(); + public double NivelRiscoInfiltracao + { + get + { + // O alarme latched da firmware sempre tem prioridade. + if (InfiltracaoDetectada) + return 100.0; + + const double limiteSeco = 700.0; + const double limiteMolhado = 1200.0; + + double normalizado = + (InfiltracaoRaw - limiteSeco) / + (limiteMolhado - limiteSeco); + + if (normalizado < 0) + normalizado = 0; + + if (normalizado > 1) + normalizado = 1; + + /* + * Smoothstep: + * reduz a relevância de pequenas oscilações perto do seco + * e cresce mais rapidamente ao se aproximar da região molhada. + */ + double nivel = + normalizado * normalizado * + (3.0 - (2.0 * normalizado)); + + return Math.Round(nivel * 100.0, 1); + } + } + + public string EstadoInfiltracao + { + get + { + if (InfiltracaoDetectada) + return "INFILTRAÇÃO DETECTADA"; + + if (NivelRiscoInfiltracao >= 75) + return "Risco elevado"; + + if (NivelRiscoInfiltracao >= 30) + return "Umidade suspeita"; + + return "Seco"; + } + } + + + // Relógio da integração de distância. O deslocamento é atualizado + // pelo timer de velocidade usando delta real de tempo. + private DateTime _ultimaAtualizacaoDeslocamentoUtc = DateTime.MinValue; + + // Calibração experimental da velocidade translacional. Os fatores foram + // obtidos em ensaios físicos realizados a 15,47 V e ficam concentrados + // aqui para serem fáceis de recalibrar sem tocar no restante do fluxo. + private const double TensaoReferenciaCalibracaoVelocidade = 15.47; + private const double FatorVelocidade60 = 2.146; + private const double FatorVelocidade80 = 2.270; + private const double FatorVelocidade100 = 1.820; + private const double ConstanteTempoFiltroTensaoSeg = 3.0; + + private double _tensaoFiltradaVelocidade = double.NaN; + private DateTime _ultimaAtualizacaoFiltroTensaoUtc = DateTime.MinValue; + + // Imagens de inclinação geradas em runtime. Mantemos as referências + // para descartar somente bitmaps criados por nós, sem tocar nos Resources. + private Image _imagemInclinacaoLateralGerada; + private Image _imagemInclinacaoFrontalGerada; + private DateTime _ultimaAtualizacaoImagemInclinacaoUtc = DateTime.MinValue; + private double _ultimoAnguloImagemLateral = double.NaN; + private double _ultimoAnguloImagemFrontal = double.NaN; + private const int IntervaloImagemInclinacaoMs = 500; + private bool _registrarAmostrasNesteCiclo; + public double VelocidadeMediaD { get; set; } = 0.0; public double VelocidadeMediaE { get; set; } = 0.0; public double VelocidadeMedia { get; set; } = 0.0; @@ -254,77 +416,295 @@ namespace OrionTardPro.Models #endregion + private double AtualizarTensaoFiltradaVelocidade() + { + double tensaoAtual = Tensao; + + // Se o INA ainda não entregou uma leitura válida, não aplica + // compensação de tensão. Isso evita zerar ou explodir a estimativa + // durante a inicialização/desconexão do sensor. + if (double.IsNaN(tensaoAtual) || + double.IsInfinity(tensaoAtual) || + tensaoAtual < 5.0) + { + return double.IsNaN(_tensaoFiltradaVelocidade) + ? TensaoReferenciaCalibracaoVelocidade + : _tensaoFiltradaVelocidade; + } + + DateTime agoraUtc = DateTime.UtcNow; + if (double.IsNaN(_tensaoFiltradaVelocidade) || + _ultimaAtualizacaoFiltroTensaoUtc == DateTime.MinValue) + { + _tensaoFiltradaVelocidade = tensaoAtual; + _ultimaAtualizacaoFiltroTensaoUtc = agoraUtc; + return _tensaoFiltradaVelocidade; + } + + double deltaSegundos = + Math.Max(0.0, + Math.Min(2.0, + (agoraUtc - _ultimaAtualizacaoFiltroTensaoUtc) + .TotalSeconds)); + + _ultimaAtualizacaoFiltroTensaoUtc = agoraUtc; + + if (deltaSegundos <= 0) + return _tensaoFiltradaVelocidade; + + // Filtro exponencial independente da frequência do timer. Com tau + // de 3 s acompanha a descarga da bateria sem fazer o velocímetro + // "respirar" a cada pequeno afundamento de tensão. + double alpha = + 1.0 - Math.Exp( + -deltaSegundos / ConstanteTempoFiltroTensaoSeg); + + _tensaoFiltradaVelocidade += + alpha * + (tensaoAtual - _tensaoFiltradaVelocidade); + + return _tensaoFiltradaVelocidade; + } + + private static double InterpolarLinear( + double x, + double x0, + double y0, + double x1, + double y1) + { + if (Math.Abs(x1 - x0) < 0.000001) + return y0; + + double t = (x - x0) / (x1 - x0); + return y0 + ((y1 - y0) * t); + } + + private static double ObterFatorCalibracaoPotencia( + double potenciaPercentual) + { + potenciaPercentual = + Math.Max(0.0, Math.Min(100.0, potenciaPercentual)); + + if (potenciaPercentual <= 0) + return 0; + + // O ensaio de 40% apresentou um fator muito discrepante (4,70x). + // Até repetirmos esse ponto, abaixo de 60% mantemos o fator de 60% + // em vez de extrapolar um valor potencialmente incorreto. + if (potenciaPercentual <= 60.0) + return FatorVelocidade60; + + if (potenciaPercentual <= 80.0) + { + return InterpolarLinear( + potenciaPercentual, + 60.0, + FatorVelocidade60, + 80.0, + FatorVelocidade80); + } + + return InterpolarLinear( + potenciaPercentual, + 80.0, + FatorVelocidade80, + 100.0, + FatorVelocidade100); + } + + private double CalcularVelocidadeCalibrada( + double potenciaPercentual, + double velocidadeMaxima) + { + potenciaPercentual = + Math.Max(0.0, Math.Min(100.0, potenciaPercentual)); + + if (potenciaPercentual <= 0 || velocidadeMaxima <= 0) + return 0; + + double velocidadeTeorica = + velocidadeMaxima * + (potenciaPercentual / 100.0); + + double fatorPotencia = + ObterFatorCalibracaoPotencia(potenciaPercentual); + + double tensaoFiltrada = + AtualizarTensaoFiltradaVelocidade(); + + double fatorTensao = + tensaoFiltrada / + TensaoReferenciaCalibracaoVelocidade; + + // Limite de sanidade para perfis/sensores anormais. Para um pacote + // 4S normal essa faixa é mais larga do que a variação esperada, mas + // impede uma leitura elétrica ruim de corromper o odômetro. + fatorTensao = + Math.Max(0.80, Math.Min(1.15, fatorTensao)); + + return velocidadeTeorica * + fatorPotencia * + fatorTensao; + } + public void AtualizaVelocidadeMedia() { - double Vmax = Variaveis.PerfilVigente.GetVelocidadeMaxima(); - if (GeneralJoystick.UltimaDirecao != Direcao.Parado) - { - double PotenciaAtual = Variaveis.PerfilVigente.GetPotenciaAtual(1); - double VelAdd = PotenciaAtual * Vmax / 100.0; + Direcao direcao = GeneralJoystick.UltimaDirecao; - VelocidadesD.Add(VelAdd); - double Soma = VelocidadesD.Sum(); - VelocidadeMediaD = Math.Round(Soma / VelocidadesD.Count() * 1.0, 2); - } - else if (GeneralJoystick.UltimaDirecao == Direcao.Parado) + if (direcao == Direcao.Parado) { - VelocidadesD = new List(); VelocidadeMediaD = 0; - } - if (GeneralJoystick.UltimaDirecao != Direcao.Parado) - { - double PotenciaAtual = Variaveis.PerfilVigente.GetPotenciaAtual(2); - double VelAdd = PotenciaAtual * Vmax / 100.0; - - VelocidadesE.Add(VelAdd); - double Soma = VelocidadesE.Sum(); - VelocidadeMediaE = Math.Round(Soma / VelocidadesE.Count() * 1.0, 2); - } - else if (GeneralJoystick.UltimaDirecao == Direcao.Parado) - { - VelocidadesE = new List(); VelocidadeMediaE = 0; + VelocidadeMedia = 0; + return; } - VelocidadeMedia = Math.Round((VelocidadeMediaD + VelocidadeMediaE) / 2.0, 2); + double velocidadeMaxima = + Variaveis.PerfilVigente.GetVelocidadeMaxima(); + + if (SerialService.ProtocoloV2Ativo) + { + // Na V6 a própria telemetria devolve o PWM efetivamente aplicado + // após a rampa da firmware. A calibração é aplicada separadamente + // em cada lado para continuar correta também durante curvas. + TelemetriaV6Model telemetria = + SerialService.UltimaTelemetriaV6; + + double potenciaD = + Math.Max(0, Math.Min(1000, telemetria.Motor1Permille)) / + 10.0; + + double potenciaE = + Math.Max(0, Math.Min(1000, telemetria.Motor2Permille)) / + 10.0; + + VelocidadeMediaD = + CalcularVelocidadeCalibrada( + potenciaD, + velocidadeMaxima); + + VelocidadeMediaE = + CalcularVelocidadeCalibrada( + potenciaE, + velocidadeMaxima); + } + else + { + // No legado não há PWM aplicado na telemetria. Usa a potência + // comandada de cada lado e passa pelo mesmo modelo de calibração. + double potenciaD = + Variaveis.PerfilVigente.GetPotenciaAtual(1); + double potenciaE = + Variaveis.PerfilVigente.GetPotenciaAtual(2); + + VelocidadeMediaD = + CalcularVelocidadeCalibrada( + potenciaD, + velocidadeMaxima); + + VelocidadeMediaE = + CalcularVelocidadeCalibrada( + potenciaE, + velocidadeMaxima); + } + + // Para o corredor interessa a velocidade translacional do centro + // do robô. Em giro puro o centro não avança, embora as rodas girem. + VelocidadeMedia = + ObterSentidoDeslocamentoLinear() == 0 + ? 0 + : (VelocidadeMediaD + VelocidadeMediaE) / 2.0; + } + + private int ObterSentidoDeslocamentoLinear() + { + Direcao direcao = GeneralJoystick.UltimaDirecao; + bool inverterEsquerda = + Variaveis.PerfilVigente.InverterRotacaoEsquerda; + bool inverterDireita = + Variaveis.PerfilVigente.InverterRotacaoDireita; + + bool frente = + direcao == Direcao.Cima || + (direcao == Direcao.EsquerdaCima && !inverterEsquerda) || + (direcao == Direcao.DireitaCima && !inverterDireita); + + if (frente) + return 1; + + bool tras = + direcao == Direcao.Baixo || + (direcao == Direcao.EsquerdaBaixo && !inverterEsquerda) || + (direcao == Direcao.DireitaBaixo && !inverterDireita); + + if (tras) + return -1; + + // Esquerda/Direita puros (e diagonais configuradas como inversão) + // giram o robô sem representar avanço no corredor. + return 0; } public void AtualizaDeslocamentoTubo() { - AtualizaVelocidadeMedia(); + DateTime agoraUtc = DateTime.UtcNow; + int sentido = ObterSentidoDeslocamentoLinear(); - double Posicao = 0; - - if (GeneralJoystick.UltimaDirecao == Direcao.Parado) + if (_ultimaAtualizacaoDeslocamentoUtc == DateTime.MinValue) { - UltimoDeslocamento = Posicao; + _ultimaAtualizacaoDeslocamentoUtc = agoraUtc; + UltimoDeslocamento = 0; return; } - double TempoPercorrido = (VelocidadesD.Count() * (double)Variaveis.frmConexao.txtDelay.Value) / 1000.0; + double deltaSegundos = + (agoraUtc - _ultimaAtualizacaoDeslocamentoUtc) + .TotalSeconds; - Posicao = VelocidadeMedia * (double)TempoPercorrido; + _ultimaAtualizacaoDeslocamentoUtc = agoraUtc; - //Console.WriteLine("DESLOCAMENTO: " + Posicao + " metros"); - double VariacaoDistancia = Posicao - UltimoDeslocamento; - if (GeneralJoystick.UltimaDirecao == Direcao.Cima) + // Evita saltos absurdos depois de pausa do debugger, + // suspensão do Windows ou retenção longa da UI. + deltaSegundos = Math.Max( + 0, + Math.Min(2.0, deltaSegundos)); + + if (sentido == 0 || deltaSegundos <= 0) { - DeslocamentoCarro += VariacaoDistancia; - if (OdometroParcialIniciado) - Variaveis.OdometroParcial += VariacaoDistancia; - } - else if (GeneralJoystick.UltimaDirecao == Direcao.Baixo) - { - DeslocamentoCarro -= VariacaoDistancia; - if (OdometroParcialIniciado) - Variaveis.OdometroParcial -= VariacaoDistancia; + UltimoDeslocamento = 0; + return; } - Variaveis.PerfilVigente.Odometro += VariacaoDistancia; - UltimoDeslocamento = Posicao; + // Uma única fonte de verdade: + // o mesmo VelocidadeAtual exibido no velocímetro alimenta + // a integração de distância. + double variacaoDistancia = + Math.Abs(VelocidadeAtual) * + deltaSegundos; + + if (variacaoDistancia <= 0) + return; + + DeslocamentoCarro += + sentido * variacaoDistancia; + + if (OdometroParcialIniciado) + Variaveis.OdometroParcial += + sentido * variacaoDistancia; + + // O odômetro geral representa distância efetivamente percorrida, + // portanto cresce tanto para frente quanto para trás, mas não em + // giro no próprio eixo. + Variaveis.PerfilVigente.Odometro += + variacaoDistancia; + + UltimoDeslocamento += + variacaoDistancia; } + public string GetEnderecoOdometro() { string Endereco = "000"; @@ -374,8 +754,141 @@ namespace OrionTardPro.Models Sensor_InclinacaoFrontal_Offset = 0; } + /// + /// Restaura os offsets persistidos no perfil atualmente carregado. + /// Não grava em disco; apenas sincroniza o estado runtime do sensor. + /// + public void CarregarOffsetsDoPerfil() + { + if (Variaveis.PerfilVigente == null) + return; + + Sensor_InclinacaoLateral_Offset = + Variaveis.PerfilVigente.Sensor_InclinacaoLateral_Offset; + + Sensor_InclinacaoFrontal_Offset = + Variaveis.PerfilVigente.Sensor_InclinacaoFrontal_Offset; + } + + /// + /// Define como zero o valor atualmente exposto por InclinacaoLateral. + /// O firmware V1 historicamente usa o offset de nome "Frontal" nessa + /// grandeza, enquanto o V2 usa o offset "Lateral". Mantemos essa + /// compatibilidade aqui para que o usuário zere exatamente o valor + /// que está vendo na tela. + /// + public void ZerarInclinacaoLateralAtual() + { + double valorAtual = InclinacaoLateral; + + if (FirmwareV6) + Sensor_InclinacaoLateral_Offset -= valorAtual; + else + Sensor_InclinacaoFrontal_Offset -= valorAtual; + + PersistirOffsetsInclinacao(); + } + + /// + /// Define como zero o valor atualmente exposto por InclinacaoFrontal. + /// Veja ZerarInclinacaoLateralAtual para a compatibilidade V1/V2. + /// + public void ZerarInclinacaoFrontalAtual() + { + double valorAtual = InclinacaoFrontal; + + if (FirmwareV6) + Sensor_InclinacaoFrontal_Offset -= valorAtual; + else + Sensor_InclinacaoLateral_Offset -= valorAtual; + + PersistirOffsetsInclinacao(); + } + + private void PersistirOffsetsInclinacao() + { + Variaveis.PersistirCalibracaoInclinacao( + Sensor_InclinacaoLateral_Offset, + Sensor_InclinacaoFrontal_Offset); + } + + private void AtualizarImagensInclinacaoSeNecessario() + { + DateTime agoraUtc = DateTime.UtcNow; + double lateral = Variaveis.Sensoriamento.InclinacaoLateral; + double frontal = Variaveis.Sensoriamento.InclinacaoFrontal; + + bool intervaloAtingido = + (agoraUtc - _ultimaAtualizacaoImagemInclinacaoUtc) + .TotalMilliseconds >= IntervaloImagemInclinacaoMs; + + bool mudouAngulo = + double.IsNaN(_ultimoAnguloImagemLateral) || + double.IsNaN(_ultimoAnguloImagemFrontal) || + Math.Abs(lateral - _ultimoAnguloImagemLateral) >= 0.5 || + Math.Abs(frontal - _ultimoAnguloImagemFrontal) >= 0.5; + + if (!intervaloAtingido || !mudouAngulo) + return; + + Image novaLateral = + FuncoesGlobais.RotacionarImagem( + Properties.Resources.F3_lateral, + lateral); + + Image novaFrontal = + FuncoesGlobais.RotacionarImagem( + Properties.Resources.F3_frontal, + frontal); + + if (novaLateral != null) + { + Image anterior = _imagemInclinacaoLateralGerada; + _imagemInclinacaoLateralGerada = novaLateral; + Variaveis.frmPrincipal.picRoboLateral.Image = novaLateral; + if (anterior != null) + anterior.Dispose(); + } + + if (novaFrontal != null) + { + Image anterior = _imagemInclinacaoFrontalGerada; + _imagemInclinacaoFrontalGerada = novaFrontal; + Variaveis.frmPrincipal.picRoboFrontal.Image = novaFrontal; + if (anterior != null) + anterior.Dispose(); + } + + _ultimoAnguloImagemLateral = lateral; + _ultimoAnguloImagemFrontal = frontal; + _ultimaAtualizacaoImagemInclinacaoUtc = agoraUtc; + } + public void AtualizaSaidasSensores(bool reset, bool protocoloNovo) { + // Toda renderização WinForms ocorre em uma única passagem na UI. + // Nunca usamos Invoke síncrono dentro da telemetria. + if (Variaveis.frmPrincipal != null && + !Variaveis.frmPrincipal.IsDisposed && + Variaveis.frmPrincipal.IsHandleCreated && + Variaveis.frmPrincipal.InvokeRequired) + { + try + { + Variaveis.frmPrincipal.BeginInvoke( + (MethodInvoker)(() => + AtualizaSaidasSensores(reset, protocoloNovo))); + } + catch (InvalidOperationException) + { + } + return; + } + + long inicioPerf = + PerformanceMonitorService.MarcarInicio(); + + _registrarAmostrasNesteCiclo = protocoloNovo; if (reset) { AceX = 0; @@ -412,11 +925,7 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.lblTemperaturavalor.Text = Variaveis.Sensoriamento.Temperatura.ToString("0.00") + "°C"; Variaveis.frmPrincipal.imgTermometro.Invalidate(); }); - - if (Variaveis.frmPrincipal.pnlBarratermometro.InvokeRequired) - Variaveis.frmPrincipal.pnlBarratermometro.Invoke(atualizarTemperatura); - else - atualizarTemperatura(); + atualizarTemperatura(); AtualizarDadosSensorDinamico(Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Tmp), Variaveis.Sensoriamento.Temperatura); } @@ -428,29 +937,16 @@ namespace OrionTardPro.Models { Variaveis.frmPrincipal.pgbUmidade.Text = Variaveis.Sensoriamento.Umidade.ToString("0.00") + "%"; Variaveis.frmPrincipal.pgbUmidade.Value = Convert.ToInt32(Math.Min(Variaveis.Sensoriamento.Umidade, Variaveis.frmPrincipal.pgbUmidade.Maximum)); - Variaveis.frmPrincipal.lblDHTTemperatura.Text = Variaveis.Sensoriamento.BmpTmp.ToString("0.00") + "°C"; + Variaveis.frmPrincipal.lblDHTTemperatura.Text = Variaveis.Sensoriamento.Temperatura.ToString("0.00") + "°C"; }); - - if (Variaveis.frmPrincipal.pgbUmidade.InvokeRequired) - Variaveis.frmPrincipal.pgbUmidade.Invoke(atualizarUmidade); - else - atualizarUmidade(); + atualizarUmidade(); AtualizarDadosSensorDinamico(Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Umi), Variaveis.Sensoriamento.Umidade); } // DESLOCAMENTO - if (Variaveis.frmPrincipal.lblDeslocamentovalor.InvokeRequired) - { - Variaveis.frmPrincipal.lblDeslocamentovalor.Invoke(new Action(() => - { - Variaveis.frmPrincipal.lblDeslocamentovalor.Text = DeslocamentoCarro.ToString("0.00") + " m"; - })); - } - else - { - Variaveis.frmPrincipal.lblDeslocamentovalor.Text = DeslocamentoCarro.ToString("0.00") + " m"; - } + Variaveis.frmPrincipal.lblDeslocamentovalor.Text = + DeslocamentoCarro.ToString("0.00") + " m"; // INCLINACAO if (Variaveis.PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado) @@ -461,19 +957,14 @@ namespace OrionTardPro.Models Action atualizarInclinacao = new Action(() => { - Variaveis.frmPrincipal.lblInclinacaox.Text = "L: " + Variaveis.Sensoriamento.InclinacaoLateral.ToString("0.00") + "°"; - Variaveis.frmPrincipal.picRoboLateral.Image = FuncoesGlobais.RotacionarImagem(Properties.Resources.F3_lateral, Variaveis.Sensoriamento.InclinacaoLateral); - Variaveis.frmPrincipal.lblInclinacaoy.Text = "F: " + Variaveis.Sensoriamento.InclinacaoFrontal.ToString("0.00") + "°"; - Variaveis.frmPrincipal.picRoboFrontal.Image = FuncoesGlobais.RotacionarImagem(Properties.Resources.F3_frontal, Variaveis.Sensoriamento.InclinacaoFrontal); + Variaveis.frmPrincipal.lblInclinacaox.Text = "F: " + Variaveis.Sensoriamento.InclinacaoLateral.ToString("0.00") + "°"; + Variaveis.frmPrincipal.lblInclinacaoy.Text = "L: " + Variaveis.Sensoriamento.InclinacaoFrontal.ToString("0.00") + "°"; + AtualizarImagensInclinacaoSeNecessario(); Variaveis.frmPrincipal.lblAltitude.Text = Variaveis.Sensoriamento.Altitude.ToString("0.00") + " m"; Variaveis.frmPrincipal.lblPressao.Text = (Variaveis.Sensoriamento.Pressao / 1000.0).ToString("0.00") + " MPa"; Variaveis.frmPrincipal.lblVelocidadeMPU.Text = Variaveis.Sensoriamento.VelocidadeMPU.ToString("0.00") + " m/s"; }); - - if (Variaveis.frmPrincipal.lblInclinacaox.InvokeRequired) - Variaveis.frmPrincipal.lblInclinacaox.Invoke(atualizarInclinacao); - else - atualizarInclinacao(); + atualizarInclinacao(); var SensorV = Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Vel && x.descricao.Contains("MPU")); var SensorA = Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Mpu && x.descricao.Contains("ACE")); @@ -507,11 +998,7 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.imgLampGasVerd.Image = OrionTardPro.Properties.Resources.led_verde_on; } }); - - if (Variaveis.frmPrincipal.lblNivelgas.InvokeRequired) - Variaveis.frmPrincipal.lblNivelgas.Invoke(atualizarGas); - else - atualizarGas(); + atualizarGas(); AtualizarDadosSensorDinamico(Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Gas), Variaveis.Sensoriamento.NivelGas); } @@ -539,11 +1026,7 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.gpbNivelbateria.Text = "Bateria " + (Variaveis.Sensoriamento.Tensao.ToString("0.00") + " v " + Variaveis.Sensoriamento.PercentualTensaoBateria.ToString("0.00") + "%"); }); - - if (Variaveis.frmPrincipal.gpbNivelbateria.InvokeRequired) - Variaveis.frmPrincipal.gpbNivelbateria.Invoke(atualizarTensao); - else - atualizarTensao(); + atualizarTensao(); } // CORRENTE @@ -558,28 +1041,15 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.lblINATmp.Text = Variaveis.Sensoriamento.InaTmp + "°C"; Variaveis.frmPrincipal.pgbCorrente.Value = Math.Min((int)Variaveis.Sensoriamento.PercentualCorrenteBateria, Variaveis.frmPrincipal.pgbCorrente.Maximum); }); - - if (Variaveis.frmPrincipal.lblPorcentagemCorrente.InvokeRequired) - Variaveis.frmPrincipal.lblPorcentagemCorrente.Invoke(atualizarCorrente); - else - atualizarCorrente(); + atualizarCorrente(); } // TEMPO ESTIMADO BATERIA if (Variaveis.PerfilVigente.Sensor_Ativado_TEN == Estado.Ligado && Variaveis.PerfilVigente.Sensor_Ativado_COR == Estado.Ligado) { TimeSpan ts = new TimeSpan(0, 0, Variaveis.Sensoriamento.TempoEstimadoBateria); - if (Variaveis.frmPrincipal.lblTempoBateria.InvokeRequired) - { - Variaveis.frmPrincipal.lblTempoBateria.Invoke(new Action(() => - { - Variaveis.frmPrincipal.lblTempoBateria.Text = "Restante " + ts.ToString(@"hh\:mm\:ss") + " hrs"; - })); - } - else - { - Variaveis.frmPrincipal.lblTempoBateria.Text = "Restante " + ts.ToString(@"hh\:mm\:ss") + " hrs"; - } + Variaveis.frmPrincipal.lblTempoBateria.Text = + "Restante " + ts.ToString(@"hh\:mm\:ss") + " hrs"; } // STATUS DA EMBREAGEM @@ -598,11 +1068,7 @@ namespace OrionTardPro.Models else if (Variaveis.Sensoriamento.StatusEmbreagem == TipoEmbreagem.Indefinido) Variaveis.frmPrincipal.imgComandoembreagem.Image = OrionTardPro.Properties.Resources.switch_1; }); - - if (Variaveis.frmPrincipal.gpbControlemotores.InvokeRequired) - Variaveis.frmPrincipal.gpbControlemotores.Invoke(atualizarEmbreagem); - else - atualizarEmbreagem(); + atualizarEmbreagem(); } // RELE GERAL @@ -613,10 +1079,7 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.lblReleGeralConexao.Text = Variaveis.Sensoriamento.ReleGeralConectado ? "Conectado" : "Desconectado"; Variaveis.frmPrincipal.lblReleGeralStatus.Text = Variaveis.EstadoReleGeral.ToString(); }); - if (Variaveis.frmPrincipal.gpbReleGeral.InvokeRequired) - Variaveis.frmPrincipal.gpbReleGeral.Invoke(atualiaReleGeral); - else - atualiaReleGeral(); + atualiaReleGeral(); } // STATUS DO MEDIDOR DE ESPESSURA @@ -627,26 +1090,34 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.lblMepStatus.Text = Variaveis.Sensoriamento.MepCarimbando ? "Carimbando" : "Parado"; Variaveis.frmPrincipal.imgMepContato.Image = Variaveis.Sensoriamento.MepContato ? OrionTardPro.Properties.Resources.led_vermelho_on : OrionTardPro.Properties.Resources.led_vermelho_off; }); - if (Variaveis.frmPrincipal.gpbMedidorEspessura.InvokeRequired) - Variaveis.frmPrincipal.gpbMedidorEspessura.Invoke(atualizaMep); - else - atualizaMep(); + atualizaMep(); + } + + // INFILTRAÇÃO + if (Variaveis.PerfilVigente.Sensor_Ativado_INF == Estado.Ligado) + { + Action atualizarInfiltracao = new Action(() => + { + Variaveis.frmPrincipal.lblInfiltracaoPercentual.Text = $"Risco: {NivelRiscoInfiltracao:F2}%"; + Variaveis.frmPrincipal.lblInfiltracaoEstado.Text = $"Estado: {EstadoInfiltracao}"; + }); + atualizarInfiltracao(); + + AtualizarDadosSensorDinamico(Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Inf && x.descricao.Contains("risco")), Variaveis.Sensoriamento.NivelRiscoInfiltracao); + AtualizarDadosSensorDinamico(Variaveis.LogsSensoresDinamicos.First(x => x.sensor == TipoSensor.Inf && x.descricao.Contains("bruta")), Variaveis.Sensoriamento.InfiltracaoRaw); } // ODOMETROS Action atualizarOdometros = new Action(() => { - Variaveis.frmParametros.lblOdoTotal.Text = "Total: " + FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorTotal) + " hrs"; - Variaveis.frmParametros.lblOdoConectado.Text = "Conectado: " + FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorConectado) + " hrs"; - Variaveis.frmParametros.lblOdoMovimento.Text = "Movimento: " + FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorMotorLigado) + " hrs"; - Variaveis.frmParametros.lblOdoParcial.Text = "Parcial: " + FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial) + " hrs"; + Variaveis.frmParametros.lblOdoTotal.Text = $"Total: {FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorTotal)} hrs ({Variaveis.Sensoriamento.OdometroValorTotal} s)"; + Variaveis.frmParametros.lblOdoConectado.Text = $"Conectado: {FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorConectado)} hrs ({Variaveis.Sensoriamento.OdometroValorConectado} s)"; + Variaveis.frmParametros.lblOdoMovimento.Text = $"Movimento: {FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorMotorLigado)} hrs ({Variaveis.Sensoriamento.OdometroValorMotorLigado} s)"; + Variaveis.frmParametros.lblOdoParcial.Text = $"Parcial: {FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial)} hrs ({Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial} s)"; }); // Verifica se é necessário usar Invoke para atualizar os labels - if (Variaveis.frmParametros.lblOdoTotal.InvokeRequired) - Variaveis.frmParametros.lblOdoTotal.Invoke(atualizarOdometros); - else - atualizarOdometros(); + atualizarOdometros(); if (protocoloNovo) { @@ -662,25 +1133,27 @@ namespace OrionTardPro.Models { Variaveis.frmPrincipal.pnlDireito.Invalidate(); AtualizarOdometroGeral(); - AtualizaDeslocamentoTubo(); if (GeneralJoystick.UltimaDirecao != Direcao.Parado) - Variaveis.SetVariaveisGerais(false, true); + Variaveis.SolicitarPersistenciaOperacional(); Variaveis.frmSensoriamento.AtualizaDados(); AtualizaAlertas(); }); // Verifica se é necessário usar Invoke para atualizar a tela - if (Variaveis.frmPrincipal.pnlDireito.InvokeRequired) - Variaveis.frmPrincipal.pnlDireito.Invoke(atualizarTela); - else - atualizarTela(); + atualizarTela(); } catch (Exception e) { Console.WriteLine("Erro ao atribuir valores aos labels: " + e.Message); } + + PerformanceMonitorService.ReportarSeLento( + "SensoresModel.AtualizaSaidasSensores", + inicioPerf, + 80, + protocoloNovo ? "protocoloNovo=1" : "protocoloNovo=0"); } @@ -690,20 +1163,70 @@ namespace OrionTardPro.Models Variaveis.frmPrincipal.lblOdometroTempo.Text = FuncoesGlobais.SegundosParaTempo(Variaveis.Sensoriamento.OdometroValorMotorLigadoParcial); } - private void AtualizarDadosSensorDinamico(LogSensorModel LogSensor, double atual) + private void AtualizarDadosSensorDinamico( + LogSensorModel LogSensor, + double atual) { - LogSensor.maximo = atual > LogSensor.maximo ? atual : LogSensor.maximo; - LogSensor.minimo = atual < LogSensor.minimo ? atual : LogSensor.minimo; + if (!_registrarAmostrasNesteCiclo || + LogSensor == null || + double.IsNaN(atual) || + double.IsInfinity(atual)) + { + return; + } + + if (LogSensor.quantidadeAmostras == 0) + { + LogSensor.minimo = atual; + LogSensor.maximo = atual; + LogSensor.somaAmostrasInterna = 0; + } + else + { + if (atual < LogSensor.minimo) + LogSensor.minimo = atual; + + if (atual > LogSensor.maximo) + LogSensor.maximo = atual; + } + + LogSensor.quantidadeAmostras++; + LogSensor.ultimo = atual; + LogSensor.somaAmostrasInterna += atual; + LogSensor.media = + LogSensor.somaAmostrasInterna / + LogSensor.quantidadeAmostras; + + // Mantemos os valores brutos para o snapshot de 5 minutos, + // onde moda e mediana são calculadas uma única vez. LogSensor.lista.Add(atual); - LogSensor.media = LogSensor.lista.Sum() / LogSensor.lista.Count(); - LogSensor.moda = FuncoesGlobais.CalcularModa(LogSensor.lista); - LogSensor.mediana = FuncoesGlobais.CalcularMediana(LogSensor.lista); + + // Para evitar crescimento ilimitado caso o log periódico falhe, + // preservamos as 5000 amostras mais recentes. + const int maximoAmostrasBuffer = 5000; + if (LogSensor.lista.Count > maximoAmostrasBuffer) + { + int remover = + LogSensor.lista.Count - + maximoAmostrasBuffer; + + LogSensor.lista.RemoveRange( + 0, + remover); + } } + private void AtualizaAlertas() { string MensagemAviso = "CUIDADO: "; - Variaveis.frmAlarmes.LimpaTabela(); + bool atualizarTabelaAlarmes = + Variaveis.frmAlarmes != null && + !Variaveis.frmAlarmes.IsDisposed && + Variaveis.frmAlarmes.Visible; + + if (atualizarTabelaAlarmes) + Variaveis.frmAlarmes.LimpaTabela(); foreach (var Alerta in Variaveis.PerfilVigente.ListaAlertas) { @@ -714,9 +1237,9 @@ namespace OrionTardPro.Models if ((AlertaMinimo || AlertaMaximo) && Alerta.AlarmeAtivado == Estado.Ligado) { Alerta.AlarmeAcionado = true; - if (AlertaMinimo) + if (atualizarTabelaAlarmes && AlertaMinimo) Variaveis.frmAlarmes.dtAlarmes.Rows.Add(Alerta.Titulo, Alerta.LimiteMinimo, ValorComparar, Alerta.Mensagem); - if (AlertaMaximo) + if (atualizarTabelaAlarmes && AlertaMaximo) Variaveis.frmAlarmes.dtAlarmes.Rows.Add(Alerta.Titulo, Alerta.LimiteMaximo, ValorComparar, Alerta.Mensagem); MensagemAviso += Alerta.Mensagem + " "; Alerta.LabelSensor.ForeColor = Color.Red; diff --git a/OrionTardPro/OrionTardPro/Properties/AssemblyInfo.cs b/OrionTardPro/OrionTardPro/Properties/AssemblyInfo.cs index 939cb87..8927db8 100644 --- a/OrionTardPro/OrionTardPro/Properties/AssemblyInfo.cs +++ b/OrionTardPro/OrionTardPro/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // É possível especificar todos os valores ou usar como padrão os Números de Build e da Revisão // utilizando o "*" como mostrado abaixo: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.1.8")] -[assembly: AssemblyFileVersion("1.0.1.8")] +[assembly: AssemblyVersion("1.1.0.0")] +[assembly: AssemblyFileVersion("1.1.0.0")] diff --git a/OrionTardPro/OrionTardPro/Services/HttpService.cs b/OrionTardPro/OrionTardPro/Services/HttpService.cs index 3b2180c..78cef2b 100644 --- a/OrionTardPro/OrionTardPro/Services/HttpService.cs +++ b/OrionTardPro/OrionTardPro/Services/HttpService.cs @@ -2,63 +2,168 @@ using OrionTardPro.Models; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.IO; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text; +using System.Threading; using System.Threading.Tasks; namespace OrionTardPro.Services { + /// + /// Camada HTTP não bloqueante. + /// + /// Regras de campo: + /// - nenhuma operação de rede deve bloquear a thread do WinForms; + /// - todos os acessos possuem timeout/cancelamento; + /// - HttpClient é reutilizado para evitar exaustão de sockets; + /// - probes de servidor são cacheados por poucos segundos para não fazer + /// uma conexão nova a cada log/consulta. + /// class HttpService { - private static string UrlDownloadArquivos { get; set; } = $"https://zendioninc.com.br/sistemas/{VariaveisGerais.IDSistema}/downloads"; + private static readonly string UrlDownloadArquivos = + $"https://zendioninc.com.br/sistemas/{VariaveisGerais.IDSistema}/downloads"; - public static bool HasInternet + private static readonly HttpClient _httpClient; + private static readonly SemaphoreSlim _probeServidorLock = new SemaphoreSlim(1, 1); + private static readonly object _cacheServidorLock = new object(); + + private static DateTime _ultimoProbeServidorUtc = DateTime.MinValue; + private static bool _ultimoEstadoServidor; + private static readonly TimeSpan _validadeCacheServidor = TimeSpan.FromSeconds(5); + + static HttpService() { - get + ServicePointManager.SecurityProtocol = + SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; + + _httpClient = new HttpClient { - try - { - using (var client = new WebClient()) - { - using (client.OpenRead("http://google.com")) - { - return true; - } - } - } - catch - { - return false; - } - } + // O timeout real de cada operação é controlado por CTS. + Timeout = Timeout.InfiniteTimeSpan + }; } + /// + /// Último estado conhecido do servidor. Esta propriedade NÃO faz rede. + /// Para uma verificação real use ServerUpAsync(). + /// public static bool ServerUp { get { + lock (_cacheServidorLock) + return _ultimoEstadoServidor; + } + } + + public static async Task HasInternetAsync(int timeoutMs = 2500) + { + try + { + using (var cts = new CancellationTokenSource(Math.Max(500, timeoutMs))) + using (var request = new HttpRequestMessage(HttpMethod.Head, "https://www.google.com/generate_204")) + using (HttpResponseMessage response = await _httpClient + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token) + .ConfigureAwait(false)) + { + return response.IsSuccessStatusCode; + } + } + catch + { + return false; + } + } + + public static async Task ServerUpAsync( + int timeoutMs = 3000, + bool forceRefresh = false) + { + DateTime agoraUtc = DateTime.UtcNow; + + lock (_cacheServidorLock) + { + if (!forceRefresh && + agoraUtc - _ultimoProbeServidorUtc < _validadeCacheServidor) + { + return _ultimoEstadoServidor; + } + } + + await _probeServidorLock.WaitAsync().ConfigureAwait(false); + try + { + agoraUtc = DateTime.UtcNow; + lock (_cacheServidorLock) + { + if (!forceRefresh && + agoraUtc - _ultimoProbeServidorUtc < _validadeCacheServidor) + { + return _ultimoEstadoServidor; + } + } + + long inicioPerf = + PerformanceMonitorService.MarcarInicio(); + + bool disponivel = false; try { - using (var client = new WebClient()) + using (var cts = new CancellationTokenSource(Math.Max(500, timeoutMs))) + using (var request = new HttpRequestMessage(HttpMethod.Head, VariaveisGerais.LinkSite)) + using (HttpResponseMessage response = await _httpClient + .SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cts.Token) + .ConfigureAwait(false)) { - using (client.OpenRead(VariaveisGerais.LinkSite)) + // Alguns servidores não aceitam HEAD. Se responder 405, + // fazemos um GET mínimo apenas para confirmar conectividade. + if (response.StatusCode == HttpStatusCode.MethodNotAllowed) { - return true; + using (var get = new HttpRequestMessage(HttpMethod.Get, VariaveisGerais.LinkSite)) + using (HttpResponseMessage getResponse = await _httpClient + .SendAsync(get, HttpCompletionOption.ResponseHeadersRead, cts.Token) + .ConfigureAwait(false)) + { + disponivel = (int)getResponse.StatusCode < 500; + } + } + else + { + disponivel = (int)response.StatusCode < 500; } } } catch { - return false; + disponivel = false; } + + lock (_cacheServidorLock) + { + _ultimoEstadoServidor = disponivel; + _ultimoProbeServidorUtc = DateTime.UtcNow; + } + + PerformanceMonitorService.ReportarSeLento( + "HttpService.ServerUpAsync", + inicioPerf, + 2000, + disponivel ? "online" : "offline/timeout"); + + return disponivel; + } + finally + { + _probeServidorLock.Release(); } } - - #region MODELS public class LicencaRequestModel @@ -79,143 +184,215 @@ namespace OrionTardPro.Services public string error { get; set; } } - #endregion - public LicencaResponse ValidaLicenca(string Chave) + public async Task ValidaLicencaAsync( + string chave, + CancellationToken cancellationToken = default(CancellationToken)) { - LicencaResponse Retorno = new LicencaResponse(); + var request = new LicencaRequestModel { Chave = chave }; - LicencaRequestModel request = new LicencaRequestModel() { Chave = Chave }; - - var response = HttpRequest(new HttpRequestData() - { - FullAddress = new Uri(VariaveisGerais.LinkSite + "/api/users/validaLicencaByChave/" + VariaveisGerais.IDSistema.ToString()), - MediaType = "application/json", - InputJson = request, - }, HttpMethod.Post); - - string json = response.Content.ReadAsStringAsync().Result; - - if (!response.IsSuccessStatusCode) - { - var error = response.Content.ReadAsStringAsync(); - } - else - { - Retorno = JsonConvert.DeserializeObject(json); - } - - return Retorno; - } - - public SetUsadaResponse SetChaveUsada(string Chave) - { - SetUsadaResponse Retorno = new SetUsadaResponse(); - - LicencaRequestModel request = new LicencaRequestModel() { Chave = Chave }; - - var response = HttpRequest(new HttpRequestData() - { - FullAddress = new Uri(VariaveisGerais.LinkSite + "/api/users/setUsada"), - MediaType = "application/json", - InputJson = request, - }, HttpMethod.Post); - - string json = response.Content.ReadAsStringAsync().Result; - - if (!response.IsSuccessStatusCode) - { - var error = response.Content.ReadAsStringAsync(); - } - else - { - Retorno = JsonConvert.DeserializeObject(json); - } - - return Retorno; - } - - private HttpResponseMessage HttpRequest(HttpRequestData dataRequest, HttpMethod Metodo) - { - using (var httpClient = new HttpClient()) - { - System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12; - - httpClient.BaseAddress = new Uri(VariaveisGerais.LinkSite); - httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue(dataRequest.MediaType)); - - //httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes($"{AdminUser}:{AdminPassword}"))); - - if (Metodo == HttpMethod.Post) + using (HttpResponseMessage response = await HttpRequestAsync( + new HttpRequestData { - var json = JsonConvert.SerializeObject(dataRequest.InputJson); - var content = new StringContent(json, Encoding.UTF8, dataRequest.MediaType); - if (!string.IsNullOrEmpty(dataRequest.Body)) content = new StringContent(dataRequest.Body, Encoding.UTF8, dataRequest.MediaType); + FullAddress = new Uri( + VariaveisGerais.LinkSite + + "/api/users/validaLicencaByChave/" + + VariaveisGerais.IDSistema), + MediaType = "application/json", + InputJson = request, + }, + HttpMethod.Post, + cancellationToken).ConfigureAwait(false)) + { + string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - return httpClient.PostAsync(dataRequest.FullAddress, content).ConfigureAwait(true).GetAwaiter().GetResult(); - } - else if (Metodo == HttpMethod.Put) - { - var json = JsonConvert.SerializeObject(dataRequest.InputJson); - var content = new StringContent(json, Encoding.UTF8, dataRequest.MediaType); - if (!string.IsNullOrEmpty(dataRequest.Body)) content = new StringContent(dataRequest.Body, Encoding.UTF8, dataRequest.MediaType); - - return httpClient.PutAsync(dataRequest.FullAddress, content).ConfigureAwait(true).GetAwaiter().GetResult(); - } - else - { - return httpClient.GetAsync(dataRequest.FullAddress).ConfigureAwait(true).GetAwaiter().GetResult(); - } + if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(json)) + return new LicencaResponse(); + return JsonConvert.DeserializeObject(json) + ?? new LicencaResponse(); } } - // Método para baixar arquivos - public static async Task DownloadFileAsync(string fileName, string savePath, Action reportProgress = null) + public async Task SetChaveUsadaAsync( + string chave, + CancellationToken cancellationToken = default(CancellationToken)) { - using (var client = new HttpClient()) - { - client.BaseAddress = new Uri(VariaveisGerais.LinkSite); - string url = $"{UrlDownloadArquivos}/{fileName}"; + var request = new LicencaRequestModel { Chave = chave }; - HttpResponseMessage response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead); - if (response.IsSuccessStatusCode) + using (HttpResponseMessage response = await HttpRequestAsync( + new HttpRequestData { - var totalBytes = response.Content.Headers.ContentLength ?? -1L; - var downloadedBytes = 0L; + FullAddress = new Uri(VariaveisGerais.LinkSite + "/api/users/setUsada"), + MediaType = "application/json", + InputJson = request, + }, + HttpMethod.Post, + cancellationToken).ConfigureAwait(false)) + { + string json = await response.Content.ReadAsStringAsync().ConfigureAwait(false); - using (var fileStream = new FileStream(savePath, FileMode.Create, FileAccess.Write, FileShare.None)) - using (var contentStream = await response.Content.ReadAsStreamAsync()) + if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(json)) + return new SetUsadaResponse(); + + return JsonConvert.DeserializeObject(json) + ?? new SetUsadaResponse(); + } + } + + private static async Task HttpRequestAsync( + HttpRequestData dataRequest, + HttpMethod metodo, + CancellationToken cancellationToken = default(CancellationToken), + int timeoutMs = 6000) + { + if (dataRequest == null || dataRequest.FullAddress == null) + throw new ArgumentException("Requisição HTTP inválida."); + + using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + using (var request = new HttpRequestMessage(metodo, dataRequest.FullAddress)) + { + timeoutCts.CancelAfter(Math.Max(500, timeoutMs)); + + request.Headers.Accept.Add( + new MediaTypeWithQualityHeaderValue( + string.IsNullOrWhiteSpace(dataRequest.MediaType) + ? "application/json" + : dataRequest.MediaType)); + + if (metodo == HttpMethod.Post || metodo == HttpMethod.Put) + { + string json = !string.IsNullOrEmpty(dataRequest.Body) + ? dataRequest.Body + : JsonConvert.SerializeObject(dataRequest.InputJson); + + request.Content = new StringContent( + json ?? string.Empty, + Encoding.UTF8, + string.IsNullOrWhiteSpace(dataRequest.MediaType) + ? "application/json" + : dataRequest.MediaType); + } + + long inicioPerf = + PerformanceMonitorService.MarcarInicio(); + + HttpResponseMessage response = await _httpClient + .SendAsync(request, HttpCompletionOption.ResponseContentRead, timeoutCts.Token) + .ConfigureAwait(false); + + PerformanceMonitorService.ReportarSeLento( + "HttpService.HttpRequestAsync", + inicioPerf, + 2000, + metodo + " " + dataRequest.FullAddress.Host); + + return response; + } + } + + public static async Task PostFormAsync( + string endpoint, + NameValueCollection body, + int timeoutInSeconds = 5, + CancellationToken cancellationToken = default(CancellationToken)) + { + try + { + var pares = new List>(); + if (body != null) + { + foreach (string chave in body.AllKeys.Where(x => x != null)) + pares.Add(new KeyValuePair(chave, body[chave] ?? string.Empty)); + } + + string endereco = endpoint != null && endpoint.StartsWith("http", StringComparison.OrdinalIgnoreCase) + ? endpoint + : VariaveisGerais.LinkSite.TrimEnd('/') + "/api/" + (endpoint ?? string.Empty).TrimStart('/'); + + using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + using (var conteudo = new FormUrlEncodedContent(pares)) + { + timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Max(1, timeoutInSeconds))); + + using (HttpResponseMessage response = await _httpClient + .PostAsync(endereco, conteudo, timeoutCts.Token) + .ConfigureAwait(false)) { - var buffer = new byte[81920]; // 80 KB buffer + return response.IsSuccessStatusCode; + } + } + } + catch (Exception ex) + { + Console.WriteLine("[HTTP] Falha no POST: " + ex.Message); + return false; + } + } + + public static async Task DownloadFileAsync( + string fileName, + string savePath, + Action reportProgress = null, + CancellationToken cancellationToken = default(CancellationToken)) + { + string url = $"{UrlDownloadArquivos}/{fileName}"; + + using (var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) + { + // Download pode ser grande, então damos uma janela generosa sem + // deixar a operação presa para sempre. + timeoutCts.CancelAfter(TimeSpan.FromMinutes(5)); + + using (HttpResponseMessage response = await _httpClient + .GetAsync(url, HttpCompletionOption.ResponseHeadersRead, timeoutCts.Token) + .ConfigureAwait(false)) + { + if (!response.IsSuccessStatusCode) + { + throw new Exception( + $"Erro ao baixar o arquivo: {response.StatusCode} - {response.ReasonPhrase}"); + } + + long totalBytes = response.Content.Headers.ContentLength ?? -1L; + long downloadedBytes = 0L; + + using (var fileStream = new FileStream( + savePath, + FileMode.Create, + FileAccess.Write, + FileShare.None, + 81920, + true)) + using (var contentStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false)) + { + var buffer = new byte[81920]; int bytesRead; - while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0) + while ((bytesRead = await contentStream + .ReadAsync(buffer, 0, buffer.Length, timeoutCts.Token) + .ConfigureAwait(false)) > 0) { - fileStream.Write(buffer, 0, bytesRead); + await fileStream + .WriteAsync(buffer, 0, bytesRead, timeoutCts.Token) + .ConfigureAwait(false); + downloadedBytes += bytesRead; - // Reporta o progresso if (reportProgress != null && totalBytes > 0) { - double progress = (double)downloadedBytes / totalBytes * 100; - reportProgress?.Invoke(progress); + double progress = (double)downloadedBytes / totalBytes * 100.0; + reportProgress(progress); } } } return true; } - else - { - throw new Exception($"Erro ao baixar o arquivo: {response.StatusCode} - {response.ReasonPhrase}"); - } } } - } - } public class HttpRequestData diff --git a/OrionTardPro/OrionTardPro/Services/SerialService.cs b/OrionTardPro/OrionTardPro/Services/SerialService.cs index 27e76f9..377a308 100644 --- a/OrionTardPro/OrionTardPro/Services/SerialService.cs +++ b/OrionTardPro/OrionTardPro/Services/SerialService.cs @@ -1,6 +1,11 @@ +using OrionTardPro.Models; using System; +using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; +using System.IO; +using System.Runtime.InteropServices; using System.IO.Ports; using System.Linq; using System.Text; @@ -36,6 +41,37 @@ namespace OrionTardPro.Services // Não use o mesmo valor de PortaCom aqui. Windows normalmente não permite abrir a mesma COM duas vezes. public static string PortaComCamera { get; set; } = ""; public static int BaudRateCamera { get; set; } = 9600; + // Baud padrão/preferencial. Tanto V1 quanto V2 fazem discovery antes do handshake + // e podem encontrar uma firmware que tenha persistido outro baud. + private const int BaudRateV2 = 9600; + private const int BaudRateBootstrapV1 = 9600; + + // Lista comum de baud rates que o software sabe procurar. Deve permanecer alinhada + // com os baud rates aceitos pelas firmwares que implementarem persistência. + // A ordem funciona também como prioridade de fallback da descoberta. + private static readonly int[] BaudRatesSuportadosSerial = + { + 9600, + 38400, + 19200, + 57600, + 115200, + 4800, + 34800 + }; + + // Compatibilidade de API: 9600 continua sendo o baud padrão/preferencial da V2. + // O baud efetivo da sessão pode ser outro caso o discovery encontre a firmware + // persistida em outra velocidade. + public static int BaudRateV6 + { + get { return BaudRateV2; } + set + { + if (value != BaudRateV2) + throw new ArgumentOutOfRangeException(nameof(value), "O baud padrão/preferencial do protocolo V2 é 9600."); + } + } // Estratégia padrão: // 1) se houver PortaComCamera diferente, usa porta separada; @@ -44,19 +80,95 @@ namespace OrionTardPro.Services public static EstrategiaCameraSerial EstrategiaCamera { get; set; } = EstrategiaCameraSerial.Auto; // Só habilite para teste de bancada. Em campo, prefira PortaSeparada ou mesmo baud. - public static bool PermitirTrocaBaudCameraNaMesmaPorta { get; set; } = true; + public static bool PermitirTrocaBaudCameraNaMesmaPorta { get; set; } = false; + + private enum EstadoSessaoSerial + { + Desconectada, + Conectando, + Operacional, + Desconectando + } + + private static EstadoSessaoSerial _EstadoSessao = EstadoSessaoSerial.Desconectada; + private static VersaoProtocolo _ProtocoloSessao = VersaoProtocolo.v1; + private static int _BaudOperacaoSessao = BaudRateBootstrapV1; + private static int _BaudRecepcaoEsperado = BaudRateBootstrapV1; + private static volatile bool _HandshakeV1Confirmado; + private static volatile bool _HandshakeV2Confirmado; + + // Discovery V1: o C# procura o baud em que a firmware está ouvindo sem alterar + // nenhuma configuração do robô. A confirmação é um PNS válido em resposta ao PING. + private static volatile bool _DescobertaV1Ativa; + private static volatile bool _DescobertaV1Confirmada; + private static volatile int _BaudTentativaDescobertaV1; + public static int UltimoBaudDescobertoV1 { get; private set; } + + // Discovery V2: HEL é seguro para descoberta porque não altera baud nem parâmetros + // operacionais. Um MOD V6 válido, com CRC e baud coerente com a tentativa atual, + // confirma que a firmware V2 está presente naquela velocidade. + private static volatile bool _DescobertaV2Ativa; + private static volatile bool _DescobertaV2Confirmada; + private static volatile int _BaudTentativaDescobertaV2; + public static int UltimoBaudDescobertoV2 { get; private set; } + + // Negociação de baud V2. Firmwares novos anunciam BAUDCFG nas capacidades + // e aceitam CFG;BAUD;. O ACK ainda sai no baud antigo e o C# + // confirma a migração com um novo HEL/MOD no baud preferencial. + private static volatile bool _ConfigBaudV2Aguardando; + private static volatile bool _ConfigBaudV2Confirmada; + private static volatile bool _ConfigBaudV2Rejeitada; + private static volatile ushort _SequenciaConfigBaudV2; + private static volatile int _BaudConfigV2Solicitado; + + private static int _VersaoFirmwareLegada; + + private static bool EhProtocoloV2 => _ProtocoloSessao == VersaoProtocolo.v2; + public static bool ProtocoloV2Ativo => EhProtocoloV2; + private static bool SessaoOperacional => _EstadoSessao == EstadoSessaoSerial.Operacional; + public static VersaoProtocolo ProtocoloSessao => _ProtocoloSessao; private static readonly object _FilaLock = new object(); private static readonly object _CameraLock = new object(); private static readonly SemaphoreSlim _SerialLock = new SemaphoreSlim(1, 1); private static readonly StringBuilder _RxFrame = new StringBuilder(); + // TX da câmera em porta separada: um único worker FIFO preserva a ordem real + // dos comandos. STOP pode eliminar movimentos ainda pendentes antes de entrar. + private static readonly LinkedList _FilaCameraTx = new LinkedList(); + private static readonly SemaphoreSlim _CameraTxSignal = new SemaphoreSlim(0); + private static bool _CameraTxWorkerIniciado; + private static readonly List FilaComandos = new List(); private static System.Timers.Timer tmrLoopComandos; private static volatile bool _IgnorarRxSerial = false; private static DateTime _IgnorarRxAte = DateTime.MinValue; private static int _RxEmAndamento = 0; + private static readonly object _SequenciaV6Lock = new object(); + private static ushort _ProximaSequenciaV6 = 1; + private static uint _UltimoTelemetryIdV6 = 0; + private static uint _HistoricoPendenteV6 = 0; + + // Identidade e diagnóstico recebidos da firmware V6. + public static uint ClienteIdV6 { get; private set; } + public static string FirmwareVersionTextoV6 { get; private set; } = ""; + public static string ModeloNomeV6 { get; private set; } = ""; + public static string RevisaoHardwareV6 { get; private set; } = ""; + public static string CapacidadesV6 { get; private set; } = ""; + + // Grandezas novas que ainda não existem no modelo legado de Sensoriamento. + public static double PotenciaW_V6 { get; private set; } + public static double RollDeg_V6 { get; private set; } + public static double PitchDeg_V6 { get; private set; } + public static int InfiltracaoRawV6 { get; private set; } + public static bool AlarmeInfiltracaoV6 { get; private set; } + public static byte MascaraSaudeV6 { get; private set; } + public static TelemetriaV6Model UltimaTelemetriaV6 { get; private set; } = new TelemetriaV6Model(); + public static int RampaPeriodoMsV6 { get; set; } = 20; + public static int RampaPassoPermilleV6 { get; set; } = 25; + private static DateTime _UltimaConsultaSensoresV2 = DateTime.MinValue; + private static DateTime _UltimaConsultaOdometroV2 = DateTime.MinValue; #region VARIAVEIS @@ -65,7 +177,23 @@ namespace OrionTardPro.Services public static int BaudRateFirmware { - get { return Variaveis.PerfilVigente.FirmwareF4 ? 38400 : 4800; } + get + { + switch (_ProtocoloSessao) + { + case VersaoProtocolo.v1: return BaudRateBootstrapV1; + case VersaoProtocolo.v2: return BaudRateV2; + default: return BaudRateBootstrapV1; + } + } + } + + private static int BaudRateOperacaoRover + { + get + { + return _BaudOperacaoSessao; + } } public static string InicioRaque { get; } = "!"; @@ -88,6 +216,94 @@ namespace OrionTardPro.Services public static System.Windows.Forms.Timer EnvioTimer = null; public static System.Windows.Forms.Timer SensoresTimer = null; + // Diagnóstico de performance. Apenas leitura, sem expor as coleções internas. + public static int FilaComandosPendentesPerformance + { + get + { + lock (_FilaLock) + return FilaComandos.Count(x => !x.Cancelado && !x.Enviado); + } + } + + public static int FilaAguardandoRespostaPerformance + { + get + { + lock (_FilaLock) + return FilaComandos.Count(x => !x.Cancelado && x.Enviado && !x.Respondido && x.AguardarResposta); + } + } + + public static int FilaCameraPendentePerformance + { + get + { + lock (_CameraLock) + return _FilaCameraTx.Count; + } + } + + public static bool RxEmAndamentoPerformance + { + get { return Interlocked.CompareExchange(ref _RxEmAndamento, 0, 0) != 0; } + } + + public static int BaudAtualPerformance + { + get + { + try + { + return _PortaRS485 != null && _PortaRS485.IsOpen + ? _PortaRS485.BaudRate + : 0; + } + catch + { + return 0; + } + } + } + + public static int IdadeUltimoProtocoloMsPerformance + { + get + { + DateTime momento = ProtocoloEntrada != null + ? ProtocoloEntrada.Momento + : DateTime.MinValue; + + if (momento == DateTime.MinValue) + return -1; + + double ms = (DateTime.Now - momento).TotalMilliseconds; + if (ms < 0) + return 0; + if (ms > int.MaxValue) + return int.MaxValue; + + return (int)ms; + } + } + + // Coalescência de telemetria -> UI. + // Nunca deixamos vários BeginInvoke de sensores se acumularem. + private static int _AtualizacaoSaidasSensoresPendente; + private static int _AtualizacaoSaidasSensoresTemDadosNovos; + private static long _AtualizacoesUiSensoresCoalescidas; + private static long _AtualizacoesUiSensoresExecutadas; + + public static long AtualizacoesUiSensoresCoalescidasPerformance + { + get { return Interlocked.Read(ref _AtualizacoesUiSensoresCoalescidas); } + } + + public static long AtualizacoesUiSensoresExecutadasPerformance + { + get { return Interlocked.Read(ref _AtualizacoesUiSensoresExecutadas); } + } + #endregion public static void Inicializar() @@ -138,6 +354,28 @@ namespace OrionTardPro.Services Console.WriteLine("[SERIAL] " + msg); } + private static void PostarNaUi(Action acao) + { + if (acao == null) + return; + + try + { + Form form = Variaveis.frmPrincipal; + if (form == null || form.IsDisposed || !form.IsHandleCreated) + return; + + if (form.InvokeRequired) + form.BeginInvoke((MethodInvoker)(() => acao())); + else + acao(); + } + catch (InvalidOperationException) + { + // Formulário pode estar sendo destruído durante shutdown. + } + } + private static bool MesmaPorta(string a, string b) { if (string.IsNullOrWhiteSpace(a) || string.IsNullOrWhiteSpace(b)) @@ -146,6 +384,43 @@ namespace OrionTardPro.Services return string.Equals(a.Trim(), b.Trim(), StringComparison.OrdinalIgnoreCase); } + private static bool BaudSerialSuportado(int baud) + { + return BaudRatesSuportadosSerial.Contains(baud); + } + + private static int[] ObterBaudsDescobertaV1() + { + // Regra: + // 1) começa pelo baud padrão da firmware (9600); + // 2) tenta cedo o baud operacional escolhido pelo usuário; + // 3) percorre os demais baud rates suportados sem duplicar. + return new[] { BaudRateBootstrapV1, _BaudOperacaoSessao } + .Concat(BaudRatesSuportadosSerial) + .Where(BaudSerialSuportado) + .Distinct() + .ToArray(); + } + + private static string CriarPingDescobertaV1() + { + return InicioComandos + + FuncoesGlobais.CorrigeProtocoloTipoAtuador(TipoAtuador.Png) + ";" + + DateTime.Now.Second.ToString(CultureInfo.InvariantCulture) + ";" + + FimProtocolo; + } + + private static int[] ObterBaudsDescobertaV2() + { + // V2 continua preferindo 9600, mas procura os demais para suportar + // firmware com baud persistido e também recuperação de campo. + return new[] { BaudRateV2, _BaudOperacaoSessao } + .Concat(BaudRatesSuportadosSerial) + .Where(BaudSerialSuportado) + .Distinct() + .ToArray(); + } + private static bool TemPortaCameraSeparadaConfigurada() { return !string.IsNullOrWhiteSpace(PortaComCamera) && !MesmaPorta(PortaComCamera, PortaCom); @@ -159,7 +434,7 @@ namespace OrionTardPro.Services if (TemPortaCameraSeparadaConfigurada()) return EstrategiaCameraSerial.PortaSeparada; - if (Variaveis.PerfilVigente.BaudRate == BaudRateCamera) + if (BaudRateOperacaoRover == BaudRateCamera) return EstrategiaCameraSerial.MesmaPortaMesmoBaud; if (PermitirTrocaBaudCameraNaMesmaPorta) @@ -176,6 +451,28 @@ namespace OrionTardPro.Services { if (Conectado) { + if (EhProtocoloV2) + { + if (Variaveis.PerfilVigente.RequisitarDados) + { + AdicionarRequisicaoV6("GET", false, 500, "TEL"); + + if ((DateTime.Now - _UltimaConsultaSensoresV2).TotalMilliseconds >= 5000) + { + _UltimaConsultaSensoresV2 = DateTime.Now; + AdicionarRequisicaoV6("GET", false, 500, "SEN"); + } + } + + // Odômetros são operacionais e independem da telemetria estar habilitada. + if ((DateTime.Now - _UltimaConsultaOdometroV2).TotalMilliseconds >= 5000) + { + _UltimaConsultaOdometroV2 = DateTime.Now; + AdicionarRequisicaoV6("GET", false, 500, "ODO"); + } + return; + } + int delay = 0; if (Variaveis.PerfilVigente.RequisitarDados) { @@ -188,12 +485,20 @@ namespace OrionTardPro.Services { await Task.Delay(delay); + DateTime momentoRecebido = + ProtocoloEntrada.Momento; + bool protocoloNovo = !string.IsNullOrEmpty(ProtocoloEntrada.Dados) && ProtocoloEntrada.Sensores && - ProtocoloEntrada.Completo; + ProtocoloEntrada.Completo && + momentoRecebido > UltimoProtocoloProcessado; - Variaveis.Sensoriamento.AtualizaSaidasSensores(false, protocoloNovo); + if (protocoloNovo) + UltimoProtocoloProcessado = momentoRecebido; + + AgendarAtualizacaoSaidasSensores( + protocoloNovo); }); } } @@ -203,13 +508,20 @@ namespace OrionTardPro.Services } } - private static async Task EnviarDadosConfiguracao(bool conectar) + private static async Task EnviarDadosConfiguracao(bool conectar, int? baudInicialV1 = null) { + if (EhProtocoloV2) + { + string frame = CriarFrameV6(ProximaSequenciaV6(), conectar ? "HEL" : "BYE"); + await EnviarDadosSerialDiretoRover(frame, _BaudOperacaoSessao, true); + return; + } + string protocoloConfiguracao = InicioConfig + FuncoesGlobais.CorrigeProtocoloEstado(conectar ? Estado.Ligado : Estado.Desligado) + ";" + Variaveis.PerfilVigente.NumeroPingsMaximo.ToString("00") + ";" + Variaveis.PerfilVigente.TempoRecepcaoSensores.ToString("0000") + ";" + - Variaveis.PerfilVigente.BaudRate.ToString("00000000") + ";" + + _BaudOperacaoSessao.ToString("00000000") + ";" + FuncoesGlobais.CorrigeProtocoloEstado(Variaveis.ZerarTempopercorrido) + ";" + FuncoesGlobais.CorrigeProtocoloEstado(Variaveis.PerfilVigente.Sensor_Ativado_GAS) + ";" + FuncoesGlobais.CorrigeProtocoloEstado(Variaveis.PerfilVigente.Sensor_Ativado_UMI) + ";" + @@ -233,57 +545,472 @@ namespace OrionTardPro.Services FuncoesGlobais.CorrigeProtocoloEstado(Variaveis.PerfilVigente.Atuador_Ativado_MEP) + ";" + FimProtocolo; - await EnviarDadosSerialDiretoRover(protocoloConfiguracao, conectar ? BaudRateFirmware : Variaveis.PerfilVigente.BaudRate, true); + int baudEnvio = conectar ? (baudInicialV1 ?? BaudRateBootstrapV1) : _BaudOperacaoSessao; + await EnviarDadosSerialDiretoRover(protocoloConfiguracao, baudEnvio, true); Console.WriteLine(protocoloConfiguracao); - await Task.Delay(1000); - Variaveis.Sensoriamento.LimparOffsetSensores(); + if (conectar && _PortaRS485?.IsOpen == true) + { + await Task.Delay(150); + await _SerialLock.WaitAsync(); + try + { + _PortaRS485.BaudRate = _BaudOperacaoSessao; + _BaudRecepcaoEsperado = _BaudOperacaoSessao; + } + finally + { + _SerialLock.Release(); + } + } + + } + + private static void ReiniciarEstadoSessao() + { + Conectado = false; + _HandshakeV1Confirmado = false; + _HandshakeV2Confirmado = false; + _DescobertaV1Ativa = false; + _DescobertaV1Confirmada = false; + _BaudTentativaDescobertaV1 = 0; + UltimoBaudDescobertoV1 = 0; + _DescobertaV2Ativa = false; + _DescobertaV2Confirmada = false; + _BaudTentativaDescobertaV2 = 0; + UltimoBaudDescobertoV2 = 0; + _ConfigBaudV2Aguardando = false; + _ConfigBaudV2Confirmada = false; + _ConfigBaudV2Rejeitada = false; + _SequenciaConfigBaudV2 = 0; + _BaudConfigV2Solicitado = 0; + _VersaoFirmwareLegada = 0; + FirmwareVersionTextoV6 = ""; + ClienteIdV6 = 0; + ModeloNomeV6 = ""; + RevisaoHardwareV6 = ""; + CapacidadesV6 = ""; + ProtocoloEntrada = new ProtocoloModel(); + ProtocoloEntrada2 = new ProtocoloModel(); + Pings = 0; + EnviosUltimoProtocolo = 0; + _UltimoTelemetryIdV6 = 0; + _HistoricoPendenteV6 = 0; + _UltimaConsultaSensoresV2 = DateTime.MinValue; + _UltimaConsultaOdometroV2 = DateTime.MinValue; + UltimoProtocoloProcessado = DateTime.MinValue; + Interlocked.Exchange(ref _AtualizacaoSaidasSensoresPendente, 0); + Interlocked.Exchange(ref _AtualizacaoSaidasSensoresTemDadosNovos, 0); + _RxFrame.Clear(); + lock (_FilaLock) FilaComandos.Clear(); + } + + private static async Task AguardarHandshakeAsync(Func confirmado, int timeoutMs) + { + DateTime limite = DateTime.UtcNow.AddMilliseconds(timeoutMs); + while (!confirmado() && DateTime.UtcNow < limite) + await Task.Delay(25); + return confirmado(); + } + + private static async Task DescobrirBaudV1Async() + { + int[] candidatos = ObterBaudsDescobertaV1(); + _DescobertaV1Ativa = true; + + try + { + // Duas rodadas protegem contra o caso em que o usuário clica em + // Conectar exatamente enquanto o ESP32 ainda está reiniciando. + for (int rodada = 1; rodada <= 2; rodada++) + { + foreach (int baud in candidatos) + { + _DescobertaV1Confirmada = false; + _BaudTentativaDescobertaV1 = baud; + UltimoBaudDescobertoV1 = 0; + + LogSerial( + $"Discovery V1: rodada {rodada}/2, testando {baud} baud."); + + // Sempre gera um frame novo, limpa o RX e posiciona a UART no baud + // candidato antes de perguntar. O PING não altera configuração do robô. + string ping = CriarPingDescobertaV1(); + bool enviado = await EnviarDadosSerialDiretoRover(ping, baud, true); + if (!enviado) + { + LogSerial($"Discovery V1: falha ao transmitir em {baud} baud."); + continue; + } + + bool respondeu = await AguardarHandshakeAsync( + () => _DescobertaV1Confirmada && + UltimoBaudDescobertoV1 == baud, + 650); + + if (respondeu) + { + LogSerial($"Discovery V1: robô encontrado em {baud} baud."); + return baud; + } + } + } + + return null; + } + finally + { + _DescobertaV1Ativa = false; + _DescobertaV1Confirmada = false; + _BaudTentativaDescobertaV1 = 0; + } + } + + private static async Task ExecutarHandshakeV1Async() + { + int? baudDescoberto = await DescobrirBaudV1Async(); + if (!baudDescoberto.HasValue) + { + LogSerial("Discovery V1 encerrado sem resposta válida."); + return false; + } + + _HandshakeV1Confirmado = false; + + LogSerial( + $"Handshake V1: encontrado em {baudDescoberto.Value}; " + + $"configurando operação em {_BaudOperacaoSessao}."); + + // A CONFIG é enviada no baud em que o ESP32 foi encontrado. Ela informa + // o baud operacional desejado. Em seguida, EnviarDadosConfiguracao move + // a UART local para o mesmo baud operacional e aguardamos o MOD. + await EnviarDadosConfiguracao(true, baudDescoberto.Value); + + if (await AguardarHandshakeAsync(() => _HandshakeV1Confirmado, 2300)) + return true; + + // Recuperação de campo: a CONFIG pode ter sido recebida e o ESP32 já ter + // migrado para o baud operacional, mas o primeiro MOD pode ter se perdido. + // Reenviar a mesma CONFIG no baud operacional é idempotente para esse fluxo + // e força uma nova resposta MOD sem voltar ao baud anterior. + LogSerial( + $"Handshake V1: MOD não chegou após a troca. " + + $"Reconfirmando em {_BaudOperacaoSessao} baud."); + + _HandshakeV1Confirmado = false; + await EnviarDadosConfiguracao(true, _BaudOperacaoSessao); + + return await AguardarHandshakeAsync( + () => _HandshakeV1Confirmado, + 2300); + } + + private static async Task DescobrirBaudV2Async() + { + int[] candidatos = ObterBaudsDescobertaV2(); + _DescobertaV2Ativa = true; + + try + { + // Assim como na V1, duas rodadas cobrem conexão durante boot/reset. + for (int rodada = 1; rodada <= 2; rodada++) + { + foreach (int baud in candidatos) + { + _DescobertaV2Confirmada = false; + _HandshakeV2Confirmado = false; + _BaudTentativaDescobertaV2 = baud; + UltimoBaudDescobertoV2 = 0; + + LogSerial( + $"Discovery V2: rodada {rodada}/2, testando {baud} baud."); + + // HEL é também o handshake natural da V2. Durante o discovery ele + // é enviado diretamente no candidato e aguardamos um MOD V6 válido. + string frame = CriarFrameV6(ProximaSequenciaV6(), "HEL"); + bool enviado = await EnviarDadosSerialDiretoRover(frame, baud, true); + if (!enviado) + { + LogSerial($"Discovery V2: falha ao transmitir em {baud} baud."); + continue; + } + + bool respondeu = await AguardarHandshakeAsync( + () => _DescobertaV2Confirmada && + _HandshakeV2Confirmado && + UltimoBaudDescobertoV2 == baud, + 750); + + if (respondeu) + { + LogSerial($"Discovery V2: robô encontrado em {baud} baud."); + return baud; + } + } + } + + return null; + } + finally + { + _DescobertaV2Ativa = false; + _DescobertaV2Confirmada = false; + _BaudTentativaDescobertaV2 = 0; + } + } + + private static bool FirmwareV2SuportaConfigBaud() + { + if (string.IsNullOrWhiteSpace(CapacidadesV6)) + return false; + + return CapacidadesV6 + .Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Any(x => string.Equals(x.Trim(), "BAUDCFG", StringComparison.OrdinalIgnoreCase)); + } + + private static async Task AjustarBaudPortaLocalV2Async(int baud) + { + await _SerialLock.WaitAsync(); + try + { + if (_PortaRS485 == null || !_PortaRS485.IsOpen) + return; + + try { _PortaRS485.DiscardInBuffer(); } catch { } + _RxFrame.Clear(); + + if (_PortaRS485.BaudRate != baud) + { + _PortaRS485.BaudRate = baud; + await Task.Delay(120); + } + + _BaudRecepcaoEsperado = baud; + try { _PortaRS485.DiscardInBuffer(); } catch { } + _RxFrame.Clear(); + } + finally + { + _SerialLock.Release(); + } + } + + private static async Task ConfirmarV2NoBaudAsync(int baud, int tentativas = 2) + { + _BaudOperacaoSessao = baud; + _BaudRecepcaoEsperado = baud; + await AjustarBaudPortaLocalV2Async(baud); + + for (int tentativa = 1; tentativa <= tentativas; tentativa++) + { + _HandshakeV2Confirmado = false; + string frame = CriarFrameV6(ProximaSequenciaV6(), "HEL"); + + if (!await EnviarDadosSerialDiretoRover(frame, baud, true)) + continue; + + if (await AguardarHandshakeAsync(() => _HandshakeV2Confirmado, 850)) + return true; + + LogSerial($"Confirmação V2 em {baud} sem MOD válido; tentativa {tentativa}/{tentativas}."); + } + + return false; + } + + private static async Task TentarNormalizarBaudV2Async(int baudDescoberto) + { + // O baud preferencial/normalizado da V2 continua 9600. Discovery e + // persistência permitem recuperar uma unidade que tenha ficado salva em + // outra velocidade sem exigir intervenção manual. + int baudPreferencial = BaudRateV2; + + _BaudOperacaoSessao = baudDescoberto; + _BaudRecepcaoEsperado = baudDescoberto; + await AjustarBaudPortaLocalV2Async(baudDescoberto); + + if (baudDescoberto == baudPreferencial) + return _HandshakeV2Confirmado; + + if (!FirmwareV2SuportaConfigBaud()) + { + LogSerial( + $"Firmware V2 encontrada em {baudDescoberto}, sem BAUDCFG; " + + "mantendo a sessão no baud descoberto por compatibilidade."); + return _HandshakeV2Confirmado; + } + + ushort seq = ProximaSequenciaV6(); + _ConfigBaudV2Aguardando = true; + _ConfigBaudV2Confirmada = false; + _ConfigBaudV2Rejeitada = false; + _SequenciaConfigBaudV2 = seq; + _BaudConfigV2Solicitado = baudPreferencial; + + string cfg = CriarFrameV6( + seq, + "CFG", + "BAUD", + baudPreferencial.ToString(CultureInfo.InvariantCulture)); + + LogSerial( + $"V2 encontrada em {baudDescoberto}; solicitando persistência em {baudPreferencial} baud."); + + bool enviado = await EnviarDadosSerialDiretoRover(cfg, baudDescoberto, true); + if (!enviado) + { + _ConfigBaudV2Aguardando = false; + return _HandshakeV2Confirmado; + } + + await AguardarHandshakeAsync( + () => _ConfigBaudV2Confirmada || _ConfigBaudV2Rejeitada, + 900); + + bool rejeitada = _ConfigBaudV2Rejeitada; + bool ackRecebido = _ConfigBaudV2Confirmada; + _ConfigBaudV2Aguardando = false; + + if (rejeitada) + { + LogSerial( + $"Firmware V2 recusou mudança para {baudPreferencial}; " + + $"mantendo operação em {baudDescoberto}."); + _BaudOperacaoSessao = baudDescoberto; + _BaudRecepcaoEsperado = baudDescoberto; + return await ConfirmarV2NoBaudAsync(baudDescoberto, 1); + } + + // Mesmo se o ACK tiver se perdido, a firmware pode já ter persistido e + // agendado a troca. Por isso tentamos o baud novo antes de desistir. + await Task.Delay(500); + + if (await ConfirmarV2NoBaudAsync(baudPreferencial, 2)) + { + LogSerial( + $"V2 normalizada e confirmada em {baudPreferencial} baud" + + (ackRecebido ? "." : " (ACK da migração não foi visto, mas HEL/MOD confirmou).")); + return true; + } + + // Fallback para firmware que não aplicou a troca apesar de anunciar + // capacidade, ou para qualquer falha transitória durante a negociação. + LogSerial( + $"Não foi possível confirmar {baudPreferencial}; tentando recuperar {baudDescoberto}."); + + return await ConfirmarV2NoBaudAsync(baudDescoberto, 2); + } + + private static async Task ExecutarHandshakeV2Async() + { + int? baudDescoberto = await DescobrirBaudV2Async(); + if (!baudDescoberto.HasValue) + { + LogSerial("Discovery V2 encerrado sem MOD válido."); + return false; + } + + bool confirmado = await TentarNormalizarBaudV2Async(baudDescoberto.Value); + return confirmado; } public static async Task ConectaPortaSerial() { - Conectado = false; + if (_EstadoSessao != EstadoSessaoSerial.Desconectada) + return Conectado; - if (_PortaRS485 == null) - Inicializar(); + _EstadoSessao = EstadoSessaoSerial.Conectando; + _ProtocoloSessao = Variaveis.PerfilVigente.VersaoProtocolo; + _BaudOperacaoSessao = EhProtocoloV2 ? BaudRateV2 : Variaveis.PerfilVigente.BaudRate; + _BaudRecepcaoEsperado = _BaudOperacaoSessao; + ReiniciarEstadoSessao(); - if (!_PortaRS485.IsOpen) + try { - _PortaRS485.BaudRate = Variaveis.PerfilVigente.BaudRate; - _PortaRS485.PortName = PortaCom; + if (_ProtocoloSessao != VersaoProtocolo.v1 && _ProtocoloSessao != VersaoProtocolo.v2) + throw new InvalidOperationException("Versão de protocolo não suportada pelo SerialService."); + if (_BaudOperacaoSessao <= 0) + throw new InvalidOperationException("Baud rate operacional inválido no perfil."); + if (!EhProtocoloV2 && !BaudSerialSuportado(_BaudOperacaoSessao)) + throw new InvalidOperationException( + $"Baud rate V1 não suportado: {_BaudOperacaoSessao}. " + + $"Permitidos: {string.Join(", ", BaudRatesSuportadosSerial)}."); + if (string.IsNullOrWhiteSpace(PortaCom)) + throw new InvalidOperationException("A porta serial do robô não foi configurada."); + + if (_PortaRS485 == null) + Inicializar(); + if (_PortaRS485.IsOpen) + _PortaRS485.Close(); ConfigurarPortaSerialPadrao(_PortaRS485); + _PortaRS485.PortName = PortaCom; + + // Ambas as versões abrem no baud preferencial e deixam o discovery + // assumir a busca caso a firmware esteja persistida em outra velocidade. + _BaudRecepcaoEsperado = EhProtocoloV2 + ? BaudRateV2 + : BaudRateBootstrapV1; + + _PortaRS485.BaudRate = _BaudRecepcaoEsperado; _PortaRS485.Open(); - if (_PortaRS485.IsOpen) - { - Variaveis.frmPrincipal.imgLampGasVerd.Image = OrionTardPro.Properties.Resources.led_verde_on; - Variaveis.frmPrincipal.imgLampGasVerm.Image = OrionTardPro.Properties.Resources.led_vermelho_off; - Variaveis.frmPrincipal.btnConectaraocarro.Enabled = false; - Variaveis.frmPrincipal.btnDesconectar.Enabled = true; - Variaveis.frmPrincipal.btnCalibragem.Enabled = true; + // Pequena janela para o conversor USB/RS485 e o driver estabilizarem + // antes do primeiro frame de discovery/handshake. + await Task.Delay(120); - await EnviarDadosConfiguracao(true); + bool handshakeOk = EhProtocoloV2 + ? await ExecutarHandshakeV2Async() + : await ExecutarHandshakeV1Async(); - if (Variaveis.PerfilVigente.EnderecoReleGeral > 0x00) - Variaveis.Sensoriamento.ReleGeralConectado = await ConectaReleGeral(); + if (!handshakeOk) + throw new TimeoutException(EhProtocoloV2 + ? "O robô V2 não respondeu ao HEL/MOD válido nos baud rates tentados." + : "O robô legado não respondeu com um MOD válido nos baud rates tentados."); - await ConectarCameraSeNecessario(); + EnvioTimer.Interval = EhProtocoloV2 + ? ObterIntervaloHeartbeatV2() + : Math.Max( + 1000, + Variaveis.PerfilVigente.TempoEntrePings * 1000); + SensoresTimer.Interval = EhProtocoloV2 + ? ObterIntervaloTelemetriaV2() + : Math.Max( + 100, + Variaveis.PerfilVigente.TempoRecepcaoSensores); - Conectado = true; + if (Variaveis.PerfilVigente.EnderecoReleGeral > 0x00) + Variaveis.Sensoriamento.ReleGeralConectado = await ConectaReleGeral(); + await ConectarCameraSeNecessario(); - object sender = new object(); - EventArgs e = new EventArgs(); - if (!Variaveis.frmControlecarro.Visible) - Variaveis.frmPrincipal.imgAnalogicocamera_Click(sender, e); - } - else - { - MessageBox.Show("Erro ao conectar-se ao carro", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error); - } + _EstadoSessao = EstadoSessaoSerial.Operacional; + Conectado = true; + Variaveis.frmPrincipal.imgLampGasVerd.Image = OrionTardPro.Properties.Resources.led_verde_on; + Variaveis.frmPrincipal.imgLampGasVerm.Image = OrionTardPro.Properties.Resources.led_vermelho_off; + Variaveis.frmPrincipal.btnConectaraocarro.Enabled = false; + Variaveis.frmPrincipal.btnDesconectar.Enabled = true; + Variaveis.frmPrincipal.btnCalibragem.Enabled = true; + + object sender = new object(); + EventArgs e = new EventArgs(); + if (!Variaveis.frmControlecarro.Visible) + Variaveis.frmPrincipal.imgAnalogicocamera_Click(sender, e); + return true; + } + catch (Exception ex) + { + LogSerial("Falha ao conectar: " + ex.Message); + try { if (_PortaRS485?.IsOpen == true) _PortaRS485.Close(); } catch { } + _EstadoSessao = EstadoSessaoSerial.Desconectada; + Conectado = false; + Variaveis.frmPrincipal.btnConectaraocarro.Enabled = true; + Variaveis.frmPrincipal.btnDesconectar.Enabled = false; + Variaveis.frmPrincipal.btnCalibragem.Enabled = false; + Variaveis.frmPrincipal.imgLampGasVerd.Image = OrionTardPro.Properties.Resources.led_verde_off; + MessageBox.Show(ex.Message, "Falha de comunicação", MessageBoxButtons.OK, MessageBoxIcon.Error); + return false; } - - return Conectado; } private static async Task ConectarCameraSeNecessario() @@ -359,6 +1086,12 @@ namespace OrionTardPro.Services Variaveis.frmPrincipal.lblCarroconexao.Text = "Carro: Desconectado"; Variaveis.ZerarTempopercorrido = Estado.Desligado; Conectado = false; + + // Persistência explícita no encerramento da sessão. + // É uma operação rara e garante que o último odômetro + // não dependa do próximo tick de background. + Variaveis.FlushPersistenciaPendente(); + Variaveis.Sensoriamento.AtualizaSaidasSensores(true, false); } } @@ -376,6 +1109,7 @@ namespace OrionTardPro.Services } catch { } + _FilaCameraTx.Clear(); CameraConectada = false; } }); @@ -384,6 +1118,10 @@ namespace OrionTardPro.Services private static async Task DesconectaPortaSerial(bool proposital = true) { bool conectado = true; + _EstadoSessao = EstadoSessaoSerial.Desconectando; + Conectado = false; + EnvioTimer.Enabled = false; + SensoresTimer.Enabled = false; if (_PortaRS485.IsOpen) { @@ -406,13 +1144,59 @@ namespace OrionTardPro.Services lock (_FilaLock) FilaComandos.ForEach(x => x.Enviado = true); + + _EstadoSessao = EstadoSessaoSerial.Desconectada; + ReiniciarEstadoSessao(); } return conectado; } + private static int ObterIntervaloTelemetriaV2() + { + int intervalo = + Variaveis.PerfilVigente.TempoRecepcaoSensores; + + return Math.Max(250, Math.Min(10000, intervalo)); + } + private static int ObterIntervaloHeartbeatV2() + { + int segundos = Variaveis.PerfilVigente.TempoEntrePings; + + segundos = Math.Max(1, Math.Min(4, segundos)); + + return segundos * 1000; + } + private static int ObterTimeoutConexaoV2() + { + int heartbeatMs = ObterIntervaloHeartbeatV2(); + + return (heartbeatMs * 3) + 500; + } + public static void EnvioTimer_Tick(object sender, EventArgs e) { + if (EhProtocoloV2) + { + EnvioTimer.Interval = ObterIntervaloHeartbeatV2(); + GeneralJoystick.EnviarPingCamera(); + AdicionarRequisicaoV6("HBT", true, 300); + + double semRespostaMs = (DateTime.Now - ProtocoloEntrada.Momento).TotalMilliseconds; + if (ProtocoloEntrada.Momento == DateTime.MinValue || semRespostaMs > ObterTimeoutConexaoV2()) + { + Pings++; + Variaveis.frmPrincipal.lblCarroconexao.Text = "Carro V6: aguardando resposta"; + } + else + { + Pings = 0; + Variaveis.frmPrincipal.lblCarroconexao.Text = + $"Carro: Conectado (Cliente {ClienteIdV6} | {ModeloNomeV6} | NS {Variaveis._RobotNS} | FV {FirmwareVersionTextoV6})"; + } + return; + } + bool enviaPing = true; if (EnviosUltimoProtocolo > 0 && EnviosUltimoProtocolo < Variaveis.PerfilVigente.QuantidadeEnvios) @@ -481,9 +1265,17 @@ namespace OrionTardPro.Services resultado = 0; if (string.IsNullOrWhiteSpace(valor)) return false; valor = valor.Trim().Replace(',', '.'); + if (string.Equals(valor, "nan", StringComparison.OrdinalIgnoreCase)) { resultado = double.NaN; return true; } + if (string.Equals(valor, "inf", StringComparison.OrdinalIgnoreCase) || string.Equals(valor, "+inf", StringComparison.OrdinalIgnoreCase)) { resultado = double.PositiveInfinity; return true; } + if (string.Equals(valor, "-inf", StringComparison.OrdinalIgnoreCase)) { resultado = double.NegativeInfinity; return true; } return double.TryParse(valor, NumberStyles.Float, CultureInfo.InvariantCulture, out resultado); } + private static bool NumeroFinito(double valor) + { + return !double.IsNaN(valor) && !double.IsInfinity(valor); + } + private static bool TryBool01(string valor, out bool resultado) { resultado = false; @@ -492,6 +1284,114 @@ namespace OrionTardPro.Services return false; } + private static ushort ProximaSequenciaV6() + { + lock (_SequenciaV6Lock) + { + ushort atual = _ProximaSequenciaV6++; + if (_ProximaSequenciaV6 == 0) + _ProximaSequenciaV6 = 1; + return atual; + } + } + + private static ushort CalcularCrc16Modbus(byte[] dados) + { + ushort crc = 0xFFFF; + for (int i = 0; i < dados.Length; i++) + { + crc ^= dados[i]; + for (int bit = 0; bit < 8; bit++) + crc = (ushort)(((crc & 1) != 0) ? ((crc >> 1) ^ 0xA001) : (crc >> 1)); + } + return crc; + } + + private static string CriarFrameV6(ushort sequencia, string tipo, params string[] campos) + { + string corpo = "V6;" + sequencia.ToString(CultureInfo.InvariantCulture) + ";" + tipo; + if (campos != null && campos.Length > 0) + corpo += ";" + string.Join(";", campos); + + ushort crc = CalcularCrc16Modbus(Encoding.ASCII.GetBytes(corpo)); + return "!" + corpo + "*" + crc.ToString("X4", CultureInfo.InvariantCulture) + "\r\n"; + } + + private static bool TryDecodificarFrameV6(string frame, out ushort sequencia, out string tipo, out string[] campos) + { + sequencia = 0; + tipo = ""; + campos = new string[0]; + if (string.IsNullOrWhiteSpace(frame)) return false; + + string limpo = frame.TrimEnd('\r', '\n'); + int asterisco = limpo.LastIndexOf('*'); + if (!limpo.StartsWith("!V6;") || asterisco < 4 || limpo.Length - asterisco != 5) + return false; + + ushort crcRecebido; + if (!ushort.TryParse(limpo.Substring(asterisco + 1), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out crcRecebido)) + return false; + + string corpo = limpo.Substring(1, asterisco - 1); + if (CalcularCrc16Modbus(Encoding.ASCII.GetBytes(corpo)) != crcRecebido) + return false; + + string[] partes = corpo.Split(new[] { ';' }, StringSplitOptions.None); + if (partes.Length < 3 || partes[0] != "V6" || !ushort.TryParse(partes[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out sequencia)) + return false; + + tipo = partes[2].Trim().ToUpperInvariant(); + campos = partes.Skip(3).ToArray(); + return tipo.Length > 0; + } + + private static void AdicionarRequisicaoV6(string tipo, bool prioritario, int timeout, params string[] campos) + { + string chave = tipo + ";" + string.Join(";", campos ?? new string[0]); + lock (_FilaLock) + { + if (tipo == "MOT" || tipo == "CAM") + FilaComandos.Where(x => !x.Enviado && x.ChaveV6.StartsWith(tipo + ";", StringComparison.Ordinal)) + .ToList().ForEach(x => x.Cancelado = true); + + // Uma parada nunca pode ficar atrás de um movimento ainda não enviado. + if (tipo == "STP") + FilaComandos.Where(x => !x.Enviado && x.ChaveV6.StartsWith("MOT;", StringComparison.Ordinal)) + .ToList().ForEach(x => x.Cancelado = true); + + if (FilaComandos.Any(x => !x.Cancelado && !x.Respondido && x.ChaveV6 == chave)) + return; + } + + ushort sequencia = ProximaSequenciaV6(); + AdicionarMensagemFila(new ProtocoloMensagemModel + { + Momento = DateTime.Now, + Dados = CriarFrameV6(sequencia, tipo, campos), + SequenciaV6 = sequencia, + ChaveV6 = chave, + AguardarResposta = true, + Timeout = timeout, + Prioritario = prioritario, + BaudRate = BaudRateOperacaoRover, + MaxTentativas = 2 + }); + } + + private static void MarcarRequisicaoV6Respondida(ushort sequencia) + { + lock (_FilaLock) + { + ProtocoloMensagemModel req = FilaComandos.FirstOrDefault(x => x.SequenciaV6 == sequencia && !x.Respondido); + if (req != null) + { + req.Respondido = true; + req.RespondidoEm = DateTime.Now; + } + } + } + private static void LimparRxSeguro(string motivo = "") { try @@ -506,6 +1406,42 @@ namespace OrionTardPro.Services catch { } } + private static string LerFrameV6ComTimeout(int timeoutMs) + { + DateTime fim = DateTime.Now.AddMilliseconds(timeoutMs); + while (DateTime.Now < fim) + { + try + { + if (_PortaRS485 == null || !_PortaRS485.IsOpen) { _RxFrame.Clear(); return ""; } + if (_PortaRS485.BytesToRead <= 0) { Thread.Sleep(1); continue; } + int b = _PortaRS485.ReadByte(); + if (b < 0) continue; + char c = (char)b; + + if (c == '!') { _RxFrame.Clear(); _RxFrame.Append(c); continue; } + if (_RxFrame.Length == 0) continue; + if (c != '\r' && c != '\n' && (c < 0x20 || c > 0x7E)) { _RxFrame.Clear(); continue; } + + _RxFrame.Append(c); + if (_RxFrame.Length > 256) { LogSerial("Frame V6 excedeu 256 bytes."); _RxFrame.Clear(); continue; } + if (c == '\n') + { + string frame = _RxFrame.ToString(); + _RxFrame.Clear(); + return frame; + } + } + catch (Exception ex) + { + _RxFrame.Clear(); + LogSerial("Erro lendo frame V6: " + ex.Message); + return ""; + } + } + return ""; + } + private static string LerFrameSensorComTimeout(int timeoutMs) { DateTime fim = DateTime.Now.AddMilliseconds(timeoutMs); @@ -674,7 +1610,7 @@ namespace OrionTardPro.Services } acumuladorCorrente = Math.Sqrt(acumuladorCorrente) * Variaveis.PerfilVigente.COR_multiplicador; - acumuladorCorrente = (acumuladorCorrente * Variaveis.PerfilVigente.COR_coeficienteA) + Variaveis.PerfilVigente.COR_coeficienteB; + //acumuladorCorrente = (acumuladorCorrente * Variaveis.PerfilVigente.COR_coeficienteA) + Variaveis.PerfilVigente.COR_coeficienteB; double porcentagemBateriaTensao = (leituraTensao / Variaveis.PerfilVigente.ValorMaximoAnalogico) * 100.0; double tensaoConvertida = (porcentagemBateriaTensao / 100.0) * Variaveis.PerfilVigente.TensaoMaximaBateria; @@ -718,7 +1654,11 @@ namespace OrionTardPro.Services Variaveis._FirmwareVersion = firmwareVersion; Variaveis._RobotNS = robotNs; Variaveis._Model = (ModeloEquipamento)modelInt; - return true; + _VersaoFirmwareLegada = firmwareVersion; + _HandshakeV1Confirmado = firmwareVersion > 0 && firmwareVersion < 6; + if (!_HandshakeV1Confirmado) + LogSerial("MOD legado rejeitado: versão incompatível " + firmwareVersion + "."); + return _HandshakeV1Confirmado; } private static bool ProcessarFrameOds(string frame, string[] p) @@ -736,7 +1676,12 @@ namespace OrionTardPro.Services return false; } - MessageBox.Show(FuncoesGlobais.SegundosParaTempo(segundosOdometro), "Odometro", MessageBoxButtons.OK, MessageBoxIcon.Information); + PostarNaUi(() => + MessageBox.Show( + FuncoesGlobais.SegundosParaTempo(segundosOdometro), + "Odometro", + MessageBoxButtons.OK, + MessageBoxIcon.Information)); return true; } @@ -759,6 +1704,18 @@ namespace OrionTardPro.Services Variaveis.Sensoriamento.SetValorVariavelOdometro(conectado, Variaveis.OdometroEnderecoConectado); Variaveis.Sensoriamento.SetValorVariavelOdometro(motoresLigados, Variaveis.OdometroEnderecoMotorLigado); Variaveis.Sensoriamento.SetValorVariavelOdometro(motoresLigadosParcial, Variaveis.OdometroEnderecoMotorLigadoParcial); + + // Durante o discovery, apenas um PNS estruturalmente válido confirma + // que existe uma firmware V1 respondendo no baud candidato. + if (_DescobertaV1Ativa && + _EstadoSessao == EstadoSessaoSerial.Conectando && + _PortaRS485?.IsOpen == true && + _PortaRS485.BaudRate == _BaudTentativaDescobertaV1) + { + UltimoBaudDescobertoV1 = _BaudTentativaDescobertaV1; + _DescobertaV1Confirmada = true; + } + return true; } @@ -785,7 +1742,7 @@ namespace OrionTardPro.Services switch (tipo) { case TipoSensor.Tod: - return Variaveis.PerfilVigente.FirmwareF4 + return _VersaoFirmwareLegada >= 4 ? ProcessarFrameTodF4(frame, p) : ProcessarFrameTodLegado(frame, p); @@ -804,6 +1761,320 @@ namespace OrionTardPro.Services } } + private static bool ProcessarFrameV6(string frame) + { + ushort sequencia; + string tipo; + string[] p; + if (!TryDecodificarFrameV6(frame, out sequencia, out tipo, out p)) + { + LogSerial("Frame V6 rejeitado (formato ou CRC): " + frame.Trim()); + return false; + } + + bool processado = false; + try + { + switch (tipo) + { + case "ACK": + if (_ConfigBaudV2Aguardando && + sequencia == _SequenciaConfigBaudV2 && + p.Length >= 2 && + string.Equals(p[0], "BAUD", StringComparison.OrdinalIgnoreCase) && + int.TryParse(p[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out int baudAck) && + baudAck == _BaudConfigV2Solicitado) + { + _ConfigBaudV2Confirmada = true; + } + processado = true; + break; + + case "HBA": + processado = true; + break; + + case "NAK": + if (_ConfigBaudV2Aguardando && sequencia == _SequenciaConfigBaudV2) + _ConfigBaudV2Rejeitada = true; + LogSerial("NAK V6 seq=" + sequencia + ": " + string.Join(";", p)); + processado = true; + break; + + case "MOD": + processado = ProcessarIdentidadeV6(p); + break; + + case "SEN": + processado = ProcessarSensoresV6(p); + break; + + case "TEL": + processado = ProcessarTelemetriaV6(p); + break; + + case "ODO": + processado = ProcessarOdometroV6(p); + break; + + case "STA": + case "DIA": + processado = true; // CRC e sequência já foram validados; exposição detalhada entra na próxima tela de diagnóstico. + break; + + default: + LogSerial("Tipo V6 desconhecido: " + tipo); + break; + } + } + catch (Exception ex) + { + LogSerial("Payload V6 inválido em " + tipo + ": " + ex.Message); + processado = false; + } + + if (processado) + MarcarRequisicaoV6Respondida(sequencia); + return processado; + } + + private static bool ProcessarIdentidadeV6(string[] p) + { + if (p.Length < 10) return false; + uint cliente; + int protocolo, modelo, ns, baud; + if (!int.TryParse(p[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out protocolo) || + !uint.TryParse(p[2], NumberStyles.Integer, CultureInfo.InvariantCulture, out cliente) || + !int.TryParse(p[3], NumberStyles.Integer, CultureInfo.InvariantCulture, out modelo) || + !int.TryParse(p[5], NumberStyles.Integer, CultureInfo.InvariantCulture, out ns) || + !int.TryParse(p[8], NumberStyles.Integer, CultureInfo.InvariantCulture, out baud)) return false; + + if (protocolo != 6 || !BaudSerialSuportado(baud) || string.IsNullOrWhiteSpace(p[0])) + { + LogSerial($"MOD V2 rejeitado: protocolo={protocolo}, baud={baud}, firmware='{p[0]}'."); + return false; + } + + // Durante discovery, o MOD precisa declarar exatamente o baud candidato + // no qual a UART local está ouvindo. Fora do discovery, precisa coincidir + // com o baud efetivo da sessão. Isso evita aceitar frame residual de outra + // velocidade ou identidade incoerente. + int baudEsperado = _DescobertaV2Ativa + ? _BaudTentativaDescobertaV2 + : _BaudOperacaoSessao; + + if (baud != baudEsperado || + _PortaRS485 == null || + !_PortaRS485.IsOpen || + _PortaRS485.BaudRate != baudEsperado) + { + LogSerial( + $"MOD V2 rejeitado por baud incoerente: recebido={baud}, esperado={baudEsperado}."); + return false; + } + + string firmware = p[0]; + string modeloNome = p[4]; + string revisao = p[6]; + string capacidades = p[9]; + + FirmwareVersionTextoV6 = firmware; + ClienteIdV6 = cliente; + ModeloNomeV6 = modeloNome; + RevisaoHardwareV6 = revisao; + CapacidadesV6 = capacidades; + Variaveis._FirmwareVersion = 6; + Variaveis._RobotNS = ns; + Variaveis._Model = (ModeloEquipamento)modelo; + _HandshakeV2Confirmado = true; + + if (_DescobertaV2Ativa && + _EstadoSessao == EstadoSessaoSerial.Conectando && + baud == _BaudTentativaDescobertaV2) + { + UltimoBaudDescobertoV2 = baud; + _DescobertaV2Confirmada = true; + } + + LogSerial($"V6 identificada: cliente={cliente}, modelo={ModeloNomeV6}, NS={ns}, firmware={FirmwareVersionTextoV6}, protocolo={protocolo}, baud={baud}"); + return true; + } + + private static bool ProcessarSensoresV6(string[] p) + { + if (p.Length < 16) return false; + double tensao, correnteA, potencia, temperatura, umidade, ax, ay, az, gx, gy, gz, roll, pitch; + int infiltracao, alarme, saude; + if (!TryDouble(p[0], out tensao) || !TryDouble(p[1], out correnteA) || !TryDouble(p[2], out potencia) || + !TryDouble(p[3], out temperatura) || !TryDouble(p[4], out umidade) || + !TryDouble(p[5], out ax) || !TryDouble(p[6], out ay) || !TryDouble(p[7], out az) || + !TryDouble(p[8], out gx) || !TryDouble(p[9], out gy) || !TryDouble(p[10], out gz) || + !TryDouble(p[11], out roll) || !TryDouble(p[12], out pitch) || + !TryInt(p[13], out infiltracao) || !TryInt(p[14], out alarme) || !TryInt(p[15], out saude)) return false; + + if (NumeroFinito(tensao)) Variaveis.Sensoriamento.InaTen = tensao; + if (NumeroFinito(correnteA)) Variaveis.Sensoriamento.InaCor = correnteA * 1000.0; // modelo legado exibe mA + if (NumeroFinito(temperatura)) Variaveis.Sensoriamento.ShtTmp = temperatura; + if (NumeroFinito(umidade)) Variaveis.Sensoriamento.ShtUmd = umidade; + if (NumeroFinito(ax)) Variaveis.Sensoriamento.AceX = ax; if (NumeroFinito(ay)) Variaveis.Sensoriamento.AceY = ay; if (NumeroFinito(az)) Variaveis.Sensoriamento.AceZ = az; + if (NumeroFinito(gx)) Variaveis.Sensoriamento.GyrX = gx; if (NumeroFinito(gy)) Variaveis.Sensoriamento.GyrY = gy; if (NumeroFinito(gz)) Variaveis.Sensoriamento.GyrZ = gz; + if (NumeroFinito(potencia)) PotenciaW_V6 = potencia; if (NumeroFinito(roll)) RollDeg_V6 = roll; if (NumeroFinito(pitch)) PitchDeg_V6 = pitch; + InfiltracaoRawV6 = infiltracao; + AlarmeInfiltracaoV6 = alarme != 0; + MascaraSaudeV6 = (byte)saude; + Variaveis.Sensoriamento.INA_Iniciado = (saude & 0x01) != 0; + Variaveis.Sensoriamento.MPU_Iniciado = (saude & 0x02) != 0; + Variaveis.Sensoriamento.SHT_Iniciado = (saude & 0x04) != 0; + Variaveis.Sensoriamento.InfiltracaoRaw = InfiltracaoRawV6; + Variaveis.Sensoriamento.InfiltracaoDetectada = AlarmeInfiltracaoV6; + AtualizarSaidasSensoresV6(); + return true; + } + + private static void AtualizarSaidasSensoresV6() + { + AgendarAtualizacaoSaidasSensores(true); + } + + private static void AgendarAtualizacaoSaidasSensores( + bool protocoloNovo) + { + if (protocoloNovo) + { + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresTemDadosNovos, + 1); + } + + if (Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresPendente, + 1) == 1) + { + if (protocoloNovo) + Interlocked.Increment( + ref _AtualizacoesUiSensoresCoalescidas); + + return; + } + + try + { + if (Variaveis.frmPrincipal == null || + Variaveis.frmPrincipal.IsDisposed || + !Variaveis.frmPrincipal.IsHandleCreated) + { + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresPendente, + 0); + + return; + } + + Variaveis.frmPrincipal.BeginInvoke( + (MethodInvoker)delegate + { + try + { + bool dadosNovos = + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresTemDadosNovos, + 0) == 1; + + Interlocked.Increment( + ref _AtualizacoesUiSensoresExecutadas); + + Variaveis.Sensoriamento + .AtualizaSaidasSensores( + false, + dadosNovos); + } + finally + { + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresPendente, + 0); + + // Se chegou outro frame enquanto a UI estava + // renderizando, agenda apenas mais um refresh. + if (Conectado && + Interlocked.CompareExchange( + ref _AtualizacaoSaidasSensoresTemDadosNovos, + 0, + 0) == 1) + { + AgendarAtualizacaoSaidasSensores( + true); + } + } + }); + } + catch (ObjectDisposedException) + { + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresPendente, + 0); + } + catch (InvalidOperationException) + { + Interlocked.Exchange( + ref _AtualizacaoSaidasSensoresPendente, + 0); + } + } + + + private static bool ProcessarTelemetriaV6(string[] p) + { + if (p.Length < 24) return false; + TelemetriaV6Model t = new TelemetriaV6Model(); + uint id, oldest, newest, uptime; + int vAvg, vMin, iAvg, iMax, pAvg, pMax, temp, hum, rMin, rMax, piMin, piMax, inf, flags, health, m1, d1, m2, d2, stop; + if (!uint.TryParse(p[0], out id) || !uint.TryParse(p[1], out oldest) || !uint.TryParse(p[2], out newest) || !uint.TryParse(p[3], out uptime) || + !TryInt(p[4], out vAvg) || !TryInt(p[5], out vMin) || !TryInt(p[6], out iAvg) || !TryInt(p[7], out iMax) || + !TryInt(p[8], out pAvg) || !TryInt(p[9], out pMax) || !TryInt(p[10], out temp) || !TryInt(p[11], out hum) || + !TryInt(p[12], out rMin) || !TryInt(p[13], out rMax) || !TryInt(p[14], out piMin) || !TryInt(p[15], out piMax) || + !TryInt(p[16], out inf) || !TryInt(p[17], out flags) || !TryInt(p[18], out health) || !TryInt(p[19], out m1) || + !TryInt(p[20], out d1) || !TryInt(p[21], out m2) || !TryInt(p[22], out d2) || !TryInt(p[23], out stop)) return false; + + t.Id = id; t.OldestId = oldest; t.NewestId = newest; t.UptimeSeconds = uptime; + t.VoltageAvgV = vAvg / 1000.0; t.VoltageMinV = vMin / 1000.0; t.CurrentAvgMa = iAvg; t.CurrentMaxMa = iMax; + t.PowerAvgW = pAvg / 1000.0; t.PowerMaxW = pMax / 1000.0; t.TemperatureC = temp / 100.0; t.HumidityPct = hum / 100.0; + t.RollMinDeg = rMin / 100.0; t.RollMaxDeg = rMax / 100.0; t.PitchMinDeg = piMin / 100.0; t.PitchMaxDeg = piMax / 100.0; + t.InfiltrationRaw = inf; t.Flags = flags; t.HealthyMask = health; t.Motor1Permille = m1; t.Direction1 = d1; + t.Motor2Permille = m2; t.Direction2 = d2; t.StopReason = stop; + UltimaTelemetriaV6 = t; + + if (vAvg != int.MinValue) Variaveis.Sensoriamento.InaTen = t.VoltageAvgV; + if (iAvg != int.MinValue) Variaveis.Sensoriamento.InaCor = t.CurrentAvgMa; + if (temp != int.MinValue) Variaveis.Sensoriamento.ShtTmp = t.TemperatureC; + if (hum != int.MinValue) Variaveis.Sensoriamento.ShtUmd = t.HumidityPct; + if (pAvg != int.MinValue) PotenciaW_V6 = t.PowerAvgW; + InfiltracaoRawV6 = inf; AlarmeInfiltracaoV6 = (flags & 0x04) != 0; MascaraSaudeV6 = (byte)health; + AtualizarSaidasSensoresV6(); + + if (_HistoricoPendenteV6 == id) _HistoricoPendenteV6 = 0; + if (_UltimoTelemetryIdV6 == 0) _UltimoTelemetryIdV6 = id; + else if (id == _UltimoTelemetryIdV6 + 1) _UltimoTelemetryIdV6 = id; + if (newest > _UltimoTelemetryIdV6 && _HistoricoPendenteV6 == 0) + { + _HistoricoPendenteV6 = _UltimoTelemetryIdV6 + 1; + AdicionarRequisicaoV6("GET", false, 500, "HIS", _HistoricoPendenteV6.ToString(CultureInfo.InvariantCulture)); + } + return true; + } + + private static bool ProcessarOdometroV6(string[] p) + { + if (p.Length < 4) return false; + int total, conectado, movimento, parcial; + if (!TryInt(p[0], out total) || !TryInt(p[1], out conectado) || !TryInt(p[2], out movimento) || !TryInt(p[3], out parcial)) return false; + Variaveis.Sensoriamento.SetValorVariavelOdometro(total, Variaveis.OdometroEnderecoTotal); + Variaveis.Sensoriamento.SetValorVariavelOdometro(conectado, Variaveis.OdometroEnderecoConectado); + Variaveis.Sensoriamento.SetValorVariavelOdometro(movimento, Variaveis.OdometroEnderecoMotorLigado); + Variaveis.Sensoriamento.SetValorVariavelOdometro(parcial, Variaveis.OdometroEnderecoMotorLigadoParcial); + return true; + } + private static void _PortaRS485_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (_IgnorarRxSerial || DateTime.Now < _IgnorarRxAte) @@ -815,7 +2086,7 @@ namespace OrionTardPro.Services if (_PortaRS485 == null || !_PortaRS485.IsOpen) return; - if (_PortaRS485.BaudRate != Variaveis.PerfilVigente.BaudRate) + if (_PortaRS485.BaudRate != _BaudRecepcaoEsperado) { LimparRxSeguro("baud atual diferente do baud do firmware"); return; @@ -824,22 +2095,29 @@ namespace OrionTardPro.Services if (Interlocked.Exchange(ref _RxEmAndamento, 1) == 1) return; + long inicioRxPerf = + PerformanceMonitorService.MarcarInicio(); + try { - string frame = LerFrameSensorComTimeout(350); + string frame = EhProtocoloV2 ? LerFrameV6ComTimeout(350) : LerFrameSensorComTimeout(350); if (string.IsNullOrWhiteSpace(frame)) { - LogSerial("Frame descartado: vazio/incompleto"); + // Só é realmente incompleto se já existia um começo de frame. + if (_RxFrame.Length > 0) + LogSerial($"Frame parcial aguardando continuação: {_RxFrame}"); + return; } - if (!ProcessarFrameSensor(frame)) + if (EhProtocoloV2 ? !ProcessarFrameV6(frame) : !ProcessarFrameSensor(frame)) return; ProtocoloEntrada.Dados = frame; ProtocoloEntrada.Momento = DateTime.Now; - MarcarUltimaRequisicaoRespondida(); + if (!EhProtocoloV2) + MarcarUltimaRequisicaoRespondida(); Console.WriteLine(frame); } catch (Exception ex) @@ -849,6 +2127,12 @@ namespace OrionTardPro.Services finally { Interlocked.Exchange(ref _RxEmAndamento, 0); + + PerformanceMonitorService.ReportarSeLento( + "SerialService.DataReceived", + inicioRxPerf, + 200, + EhProtocoloV2 ? "V2" : "V1"); } } @@ -856,14 +2140,18 @@ namespace OrionTardPro.Services public static void EnviaProtocolo(TipoProtocolo tipo, TipoAtuador atuador) { - Variaveis.frmPrincipal.Invoke((MethodInvoker)delegate - { - Variaveis.frmPrincipal.pgbEnvio.Value = 0; - }); + PostarNaUi(() => + Variaveis.frmPrincipal.pgbEnvio.Value = 0); - if (!Conectado) + if (!SessaoOperacional) return; + if (EhProtocoloV2 && tipo == TipoProtocolo.Carro) + { + EnviaProtocoloCarroV6(atuador); + return; + } + ProtocoloMensagemModel protocoloEnvio = new ProtocoloMensagemModel(); if (tipo == TipoProtocolo.Carro) @@ -952,6 +2240,20 @@ namespace OrionTardPro.Services } protocoloEnvio.Dados = Variaveis.ProtocoloCarro; + + if (atuador == TipoAtuador.Mot) + { + bool paradaMotor = + Variaveis.Sentido_Driver1 == Sentido.Parado && + Variaveis.Sentido_Driver2 == Sentido.Parado; + + protocoloEnvio.EhComandoMotor = true; + protocoloEnvio.EhParadaMotor = paradaMotor; + protocoloEnvio.Prioritario = paradaMotor; + + if (paradaMotor) + protocoloEnvio.Momento = DateTime.Now.AddMilliseconds(-500); + } } else if (tipo == TipoProtocolo.Raque) { @@ -997,97 +2299,298 @@ namespace OrionTardPro.Services AdicionarMensagemFila(protocoloEnvio); } + public static void ParadaSeguraMotores(string motivo = "parada solicitada") + { + Variaveis.Sentido_Driver1 = Sentido.Parado; + Variaveis.Sentido_Driver2 = Sentido.Parado; + + if (!SessaoOperacional) + return; + + LogSerial("Parada segura dos motores: " + motivo); + if (EhProtocoloV2) + { + AdicionarRequisicaoV6("STP", true, 250); + return; + } + + // O legado não possui STP dedicado: mantém exatamente o protocolo MOT + // já usado em campo, agora com os dois sentidos explicitamente parados. + EnviaProtocolo(TipoProtocolo.Carro, TipoAtuador.Mot); + } + + private static int PotenciaParaPermilleV6(object valor) + { + double potencia = Convert.ToDouble(valor, CultureInfo.InvariantCulture); + if (potencia >= 0 && potencia <= 100) potencia *= 10.0; + return Math.Max(0, Math.Min(1000, (int)Math.Round(potencia))); + } + + private static string ObterCampoOdometroV6() + { + string enderecoTexto = Variaveis.Sensoriamento.GetEnderecoOdometro(); + int endereco; + if (!int.TryParse(enderecoTexto, NumberStyles.Integer, CultureInfo.InvariantCulture, out endereco)) + return null; + + if (endereco == Variaveis.OdometroEnderecoTotal) return "TOTAL"; + if (endereco == Variaveis.OdometroEnderecoConectado) return "CONNECTED"; + if (endereco == Variaveis.OdometroEnderecoMotorLigado) return "MOVING"; + if (endereco == Variaveis.OdometroEnderecoMotorLigadoParcial) return "PARTIAL"; + return null; + } + + private static void EnviaProtocoloCarroV6(TipoAtuador atuador) + { + switch (atuador) + { + case TipoAtuador.Mot: + AdicionarRequisicaoV6("MOT", true, 300, + PotenciaParaPermilleV6(Variaveis.PerfilVigente.GetPotenciaAtual(1)).ToString(CultureInfo.InvariantCulture), + Convert.ToInt32(Variaveis.Sentido_Driver1).ToString(CultureInfo.InvariantCulture), + PotenciaParaPermilleV6(Variaveis.PerfilVigente.GetPotenciaAtual(2)).ToString(CultureInfo.InvariantCulture), + Convert.ToInt32(Variaveis.Sentido_Driver2).ToString(CultureInfo.InvariantCulture), + Math.Max(1, RampaPeriodoMsV6).ToString(CultureInfo.InvariantCulture), + Math.Max(1, Math.Min(1000, RampaPassoPermilleV6)).ToString(CultureInfo.InvariantCulture)); + break; + + case TipoAtuador.Cam: + AdicionarRequisicaoV6("CAM", true, 300, + Convert.ToInt32(Variaveis.CameraAtual).ToString(CultureInfo.InvariantCulture)); + break; + + case TipoAtuador.Png: + AdicionarRequisicaoV6("HBT", true, 300); + break; + + case TipoAtuador.Sen: + AdicionarRequisicaoV6("GET", false, 500, "SEN"); + break; + + case TipoAtuador.Odo: + if (Variaveis.FuncaoOdometro == Estado.Reset) + { + AdicionarRequisicaoV6("ODO", true, 700, "RESET"); + } + else if (Variaveis.FuncaoOdometro == Estado.Desligado) + { + string campo = ObterCampoOdometroV6(); + if (string.IsNullOrWhiteSpace(campo)) + { + LogSerial("V6 ODO SET cancelado: tipo de odômetro não reconhecido."); + break; + } + + int valor = Math.Max(0, Variaveis.ValorOdometroEnviar); + LogSerial($"V6 ODO SET: campo={campo}, valor={valor}."); + AdicionarRequisicaoV6( + "ODO", + true, + 800, + "SET", + campo, + valor.ToString(CultureInfo.InvariantCulture)); + } + else + { + AdicionarRequisicaoV6("GET", false, 500, "ODO"); + } + break; + + default: + LogSerial("Atuador ainda não existe no hardware/protocolo V6: " + atuador); + break; + } + } + public static void EnviarProtocoloCamera(int comando) { if (!Conectado) return; string protocoloEnvio = InicioCamera + comando + ";" + FimProtocolo; + bool parada = comando == Convert.ToInt32(Direcao.Parado); AdicionarMensagemFila(new ProtocoloMensagemModel() { - Momento = DateTime.Now, + Momento = parada ? DateTime.Now.AddMilliseconds(-500) : DateTime.Now, Dados = protocoloEnvio, AguardarResposta = false, PermitirDuplicado = true, + Prioritario = parada, + EhComandoCamera = true, + EhParadaCamera = parada, }); } + // Compatibilidade com chamadas antigas de um único byte. Para PTZ use + // ControleCameraSequencia para que prefixo + comando sejam transmitidos + // como uma unidade atômica e nunca invertam de ordem. public static void ControleCamera(byte comando) { - if (!Conectado) + ControleCameraSequencia(comando == 0x00, comando); + } + + public static void ControleCameraSequencia(bool parada, params byte[] comandos) + { + if (!Conectado || comandos == null || comandos.Length == 0) return; + byte[] dados = (byte[])comandos.Clone(); EstrategiaCameraSerial estrategia = ResolverEstrategiaCamera(); - bool comandoParar = comando == 0x00; switch (estrategia) { case EstrategiaCameraSerial.PortaSeparada: - EnviarComandoCameraPortaSeparada(comando); + EnfileirarCameraPortaSeparada(dados, parada); return; case EstrategiaCameraSerial.MesmaPortaMesmoBaud: AdicionarMensagemFila(new ProtocoloMensagemModel() { - Momento = comandoParar ? DateTime.Now.AddMilliseconds(-500) : DateTime.Now, - Bytes = new[] { comando }, - BaudRate = Variaveis.PerfilVigente.BaudRate, + Momento = parada ? DateTime.Now.AddMilliseconds(-500) : DateTime.Now, + Bytes = dados, + BaudRate = BaudRateOperacaoRover, AguardarResposta = false, PermitirDuplicado = true, - Prioritario = comandoParar, + Prioritario = parada, + EhComandoCamera = true, + EhParadaCamera = parada, }); return; case EstrategiaCameraSerial.PonteFirmwareTexto: - EnviarProtocoloCamera(comando); + LogSerial("Sequência binária da câmera ignorada: estratégia atual usa ponte textual."); return; case EstrategiaCameraSerial.TrocaBaudMesmaPortaExperimental: AdicionarMensagemFila(new ProtocoloMensagemModel() { - Momento = comandoParar ? DateTime.Now.AddMilliseconds(-500) : DateTime.Now, - Bytes = new[] { comando }, + Momento = parada ? DateTime.Now.AddMilliseconds(-500) : DateTime.Now, + Bytes = dados, BaudRate = BaudRateCamera, AguardarResposta = false, PermitirDuplicado = true, - Prioritario = comandoParar, + Prioritario = parada, EhCameraBaudTemporario = true, + EhComandoCamera = true, + EhParadaCamera = parada, }); return; case EstrategiaCameraSerial.Desabilitada: default: - LogSerial($"Comando câmera ignorado: rover @ {Variaveis.PerfilVigente.BaudRate}, câmera @ {BaudRateCamera}, mesma COM sem troca de baud permitida."); + LogSerial($"Comando câmera ignorado: rover @ {BaudRateOperacaoRover}, câmera @ {BaudRateCamera}, mesma COM sem troca de baud permitida."); return; } } - private static void EnviarComandoCameraPortaSeparada(byte comando) + private static void GarantirCameraTxWorker() { - Task.Run(() => + lock (_CameraLock) { + if (_CameraTxWorkerIniciado) + return; + + _CameraTxWorkerIniciado = true; + Task.Run(ProcessarFilaCameraPortaSeparadaAsync); + } + } + + private static void EnfileirarCameraPortaSeparada(byte[] dados, bool parada) + { + if (dados == null || dados.Length == 0) + return; + + GarantirCameraTxWorker(); + + lock (_CameraLock) + { + if (parada) + { + // STOP ganha prioridade e invalida movimentos que ainda nem saíram + // pela serial. Comandos não relacionados a movimento são preservados. + LinkedListNode no = _FilaCameraTx.First; + while (no != null) + { + LinkedListNode proximo = no.Next; + if (no.Value.EhMovimento && !no.Value.Parada) + _FilaCameraTx.Remove(no); + no = proximo; + } + + _FilaCameraTx.AddFirst(new CameraTxItem + { + Dados = dados, + Parada = true, + EhMovimento = true + }); + } + else + { + _FilaCameraTx.AddLast(new CameraTxItem + { + Dados = dados, + Parada = false, + EhMovimento = EhSequenciaMovimentoCamera(dados) + }); + } + } + + _CameraTxSignal.Release(); + } + + private static bool EhSequenciaMovimentoCamera(byte[] dados) + { + if (dados == null || dados.Length == 0) + return false; + + byte comando = dados[dados.Length - 1]; + return comando == 0x11 || comando == 0x22 || comando == 0x33 || comando == 0x44; + } + + private static async Task ProcessarFilaCameraPortaSeparadaAsync() + { + while (true) + { + await _CameraTxSignal.WaitAsync(); + + CameraTxItem item = null; lock (_CameraLock) { - try + if (_FilaCameraTx.Count > 0) + { + item = _FilaCameraTx.First.Value; + _FilaCameraTx.RemoveFirst(); + } + } + + // O semáforo pode possuir créditos antigos depois de uma coalescência + // de STOP. Nesse caso simplesmente consumimos o crédito e seguimos. + if (item == null) + continue; + + try + { + lock (_CameraLock) { if (_PortaCamera == null || !_PortaCamera.IsOpen) { CameraConectada = false; LogSerial("Comando câmera não enviado: porta da câmera não está aberta."); - return; + continue; } - _PortaCamera.Write(new[] { comando }, 0, 1); + _PortaCamera.Write(item.Dados, 0, item.Dados.Length); CameraConectada = true; } - catch (Exception ex) - { - CameraConectada = false; - LogSerial("Erro enviando comando para câmera: " + ex.Message); - } } - }); + catch (Exception ex) + { + CameraConectada = false; + LogSerial("Erro enviando comando para câmera: " + ex.Message); + } + } } public static async Task ConectaReleGeral() @@ -1156,7 +2659,7 @@ namespace OrionTardPro.Services { Momento = DateTime.Now, Bytes = comando, - BaudRate = Variaveis.PerfilVigente.BaudRate, + BaudRate = BaudRateOperacaoRover, }); } @@ -1186,8 +2689,14 @@ namespace OrionTardPro.Services private static async void TmrLoopComandos_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { + long inicioLoopPerf = + PerformanceMonitorService.MarcarInicio(); + try { + if (!SessaoOperacional) + return; + List fila; lock (_FilaLock) @@ -1201,12 +2710,15 @@ namespace OrionTardPro.Services foreach (var protocolo in fila) { + if (protocolo.Cancelado) + continue; + bool sucesso = await EnviarDadosSerial(protocolo); if (sucesso) { if (protocolo.AguardarResposta) - await VerificaRespostaRequisicao(protocolo.Timeout); + await VerificaRespostaRequisicao(protocolo, protocolo.Timeout); else { protocolo.Respondido = true; @@ -1215,10 +2727,9 @@ namespace OrionTardPro.Services } else { - Variaveis.frmPrincipal.Invoke((MethodInvoker)delegate - { - Variaveis.frmPrincipal.lblCarroconexao.Text = "Carro: Desconectado"; - }); + PostarNaUi(() => + Variaveis.frmPrincipal.lblCarroconexao.Text = + "Carro: Desconectado"); } } @@ -1230,6 +2741,13 @@ namespace OrionTardPro.Services } finally { + PerformanceMonitorService.ReportarSeLento( + "SerialService.LoopComandos", + inicioLoopPerf, + 1200, + "pend=" + FilaComandosPendentesPerformance + + " aguardando=" + FilaAguardandoRespostaPerformance); + if (tmrLoopComandos != null) tmrLoopComandos.Start(); } @@ -1275,6 +2793,8 @@ namespace OrionTardPro.Services if (_PortaRS485.BaudRate != baudDesejado) { _PortaRS485.BaudRate = baudDesejado; + if (!ehBaudTemporario) + _BaudRecepcaoEsperado = baudDesejado; await Task.Delay(120); } @@ -1283,12 +2803,12 @@ namespace OrionTardPro.Services private static async Task EnviarDadosSerial(ProtocoloMensagemModel protocolo) { - if (!Conectado || _PortaRS485 == null || !_PortaRS485.IsOpen) + if (!SessaoOperacional || _PortaRS485 == null || !_PortaRS485.IsOpen) return false; await _SerialLock.WaitAsync(); - int baudFirmware = Variaveis.PerfilVigente.BaudRate; + int baudFirmware = BaudRateOperacaoRover; int baudDesejado = protocolo.BaudRate ?? baudFirmware; bool baudTemporario = protocolo.EhCameraBaudTemporario || baudDesejado != baudFirmware; @@ -1310,17 +2830,25 @@ namespace OrionTardPro.Services try { _PortaRS485.DiscardOutBuffer(); } catch { } } - if (!string.IsNullOrEmpty(protocolo.Dados)) - _PortaRS485.Write(protocolo.Dados); - else if (protocolo.Bytes != null && protocolo.Bytes.Length > 0) - _PortaRS485.Write(protocolo.Bytes, 0, protocolo.Bytes.Length); - - await Task.Delay(protocolo.DelayAposEnvioMs); - protocolo.Enviado = true; protocolo.EnviadoEm = DateTime.Now; - return true; + try + { + if (!string.IsNullOrEmpty(protocolo.Dados)) + _PortaRS485.Write(protocolo.Dados); + else if (protocolo.Bytes != null && protocolo.Bytes.Length > 0) + _PortaRS485.Write(protocolo.Bytes, 0, protocolo.Bytes.Length); + + await Task.Delay(protocolo.DelayAposEnvioMs); + return true; + } + catch + { + protocolo.Enviado = false; + protocolo.EnviadoEm = DateTime.MinValue; + throw; + } } catch (Exception ex) { @@ -1336,6 +2864,7 @@ namespace OrionTardPro.Services if (_PortaRS485.BaudRate != baudFirmware) { _PortaRS485.BaudRate = baudFirmware; + _BaudRecepcaoEsperado = baudFirmware; await Task.Delay(150); } @@ -1348,34 +2877,26 @@ namespace OrionTardPro.Services } } - private static async Task VerificaRespostaRequisicao(int timeoutMs = 1000) + private static async Task VerificaRespostaRequisicao(ProtocoloMensagemModel protocolo, int timeoutMs = 1000) { DateTime limite = DateTime.Now.AddMilliseconds(timeoutMs); - bool reqRespondido = false; - DateTime ultimoEnvio = DateTime.MinValue; - - lock (_FilaLock) + while (!protocolo.Respondido && DateTime.Now < limite) { - var p = FilaComandos.Where(x => x.Enviado && !x.Respondido && x.AguardarResposta).OrderByDescending(x => x.EnviadoEm).FirstOrDefault(); - if (p != null) - ultimoEnvio = p.EnviadoEm; - } - - while (!reqRespondido && DateTime.Now < limite) - { - lock (_FilaLock) - { - reqRespondido = FilaComandos.Any(x => - x.EnviadoEm >= ultimoEnvio && - x.Respondido && - x.RespondidoEm.AddMilliseconds(150) > DateTime.Now); - } - await Task.Delay(50); } - if (!reqRespondido) - LimparRxSeguro("timeout aguardando resposta"); + if (!protocolo.Respondido) + { + if (EhProtocoloV2 && protocolo.Tentativas < protocolo.MaxTentativas) + { + protocolo.Tentativas++; + protocolo.Enviado = false; // reenvia o mesmo frame e a mesma sequência; a firmware trata duplicatas. + protocolo.EnviadoEm = DateTime.MinValue; + LogSerial("Timeout V6; reagendando seq=" + protocolo.SequenciaV6 + " tentativa=" + protocolo.Tentativas); + } + else + LogSerial("Timeout aguardando resposta"); + } } private static void AdicionarMensagemFila(ProtocoloMensagemModel protocolo) @@ -1388,7 +2909,25 @@ namespace OrionTardPro.Services lock (_FilaLock) { - bool jaExiste = FilaComandos.Any(x => !x.Enviado && MensagensIguais(x, protocolo)); + // STOP sempre invalida movimentos ainda não transmitidos do mesmo + // subsistema. Isso impede um MOVE antigo de sair depois da parada. + if (protocolo.EhParadaMotor) + { + FilaComandos + .Where(x => !x.Enviado && x.EhComandoMotor && !x.EhParadaMotor) + .ToList() + .ForEach(x => x.Cancelado = true); + } + + if (protocolo.EhParadaCamera) + { + FilaComandos + .Where(x => !x.Enviado && x.EhComandoCamera && !x.EhParadaCamera) + .ToList() + .ForEach(x => x.Cancelado = true); + } + + bool jaExiste = FilaComandos.Any(x => !x.Cancelado && !x.Enviado && MensagensIguais(x, protocolo)); if (protocolo.PermitirDuplicado || !jaExiste) FilaComandos.Add(protocolo); @@ -1415,11 +2954,537 @@ namespace OrionTardPro.Services lock (_FilaLock) { - FilaComandos.RemoveAll(x => x.Enviado && x.Momento < limite); + FilaComandos.RemoveAll(x => x.Cancelado || (x.Enviado && x.Momento < limite)); } } } + public static class PerformanceMonitorService + { + private const int IntervaloAmostragemMs = 5000; + private const int IntervaloProbeUiMs = 500; + private const int IntervaloResumoMs = 60000; + private const int UiWarnMs = 250; + private const int UiCriticalMs = 1000; + + private static readonly ConcurrentQueue _filaLog = new ConcurrentQueue(); + private static readonly AutoResetEvent _logWake = new AutoResetEvent(false); + private static readonly object _estadoLock = new object(); + + private static System.Threading.Timer _sampleTimer; + private static System.Threading.Timer _uiProbeTimer; + private static Thread _writerThread; + private static volatile bool _encerrando; + private static Control _uiRoot; + private static Process _processo; + + private static DateTime _ultimaAmostraUtc = DateTime.MinValue; + private static TimeSpan _cpuAnterior = TimeSpan.Zero; + private static DateTime _ultimoResumoUtc = DateTime.MinValue; + private static long _uiProbeInicioTicks; + private static int _uiProbePendente; + private static int _uiProbeAlertaEmitido; + private static long _maiorUiStallIntervaloMs; + private static long _maiorUiStallSessaoMs; + private static long _quantidadeUiStalls; + private static long _quantidadeOperacoesLentas; + private static long _ultimoWorkingSet; + + public static bool Ativo { get; private set; } + public static double UltimaCpuPercent { get; private set; } + public static long UltimoWorkingSetMb { get; private set; } + public static long UltimaMemoriaGerenciadaMb { get; private set; } + public static int UltimosGdiHandles { get; private set; } + public static int UltimosUserHandles { get; private set; } + public static long MaiorUiStallSessaoMs { get { return Interlocked.Read(ref _maiorUiStallSessaoMs); } } + public static long QuantidadeUiStalls { get { return Interlocked.Read(ref _quantidadeUiStalls); } } + public static long QuantidadeOperacoesLentas { get { return Interlocked.Read(ref _quantidadeOperacoesLentas); } } + + public static string CaminhoLog + { + get + { + string basePath = Variaveis.CaminhoProjeto; + if (string.IsNullOrWhiteSpace(basePath)) + basePath = AppDomain.CurrentDomain.BaseDirectory; + + return Path.Combine(basePath, "Logs", "performance.log"); + } + } + + public static void Inicializar(Control uiRoot) + { + lock (_estadoLock) + { + if (Ativo) + return; + + _uiRoot = uiRoot; + _encerrando = false; + _processo = Process.GetCurrentProcess(); + _processo.Refresh(); + _ultimaAmostraUtc = DateTime.UtcNow; + _cpuAnterior = _processo.TotalProcessorTime; + _ultimoResumoUtc = DateTime.UtcNow; + _ultimoWorkingSet = _processo.WorkingSet64; + + _writerThread = new Thread(LogWriterLoop) + { + IsBackground = true, + Name = "Orion.PerformanceLog" + }; + _writerThread.Start(); + + _sampleTimer = new System.Threading.Timer( + SampleTimerCallback, + null, + IntervaloAmostragemMs, + IntervaloAmostragemMs); + + _uiProbeTimer = new System.Threading.Timer( + UiProbeTimerCallback, + null, + IntervaloProbeUiMs, + IntervaloProbeUiMs); + + Application.ThreadException += Application_ThreadException; + AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; + + Ativo = true; + } + + SampleTimerCallback(null); + Registrar( + "INFO", + "Performance monitor iniciado | versão=" + + VariaveisGerais.Versao); + RegistrarResumo("START"); + } + + public static void Encerrar() + { + lock (_estadoLock) + { + if (!Ativo) + return; + + Ativo = false; + + try { _sampleTimer?.Dispose(); } catch { } + try { _uiProbeTimer?.Dispose(); } catch { } + _sampleTimer = null; + _uiProbeTimer = null; + + Application.ThreadException -= Application_ThreadException; + AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException; + } + + try + { + RegistrarResumo("FINAL"); + } + catch { } + + _encerrando = true; + _logWake.Set(); + try + { + if (_writerThread != null && _writerThread.IsAlive) + _writerThread.Join(500); + } + catch { } + } + + /// + /// Timestamp barato para instrumentar métodos sem alocar Stopwatch. + /// + public static long MarcarInicio() + { + return Stopwatch.GetTimestamp(); + } + + public static long DuracaoMs(long inicioTimestamp) + { + long delta = Stopwatch.GetTimestamp() - inicioTimestamp; + return (long)Math.Round(delta * 1000.0 / Stopwatch.Frequency); + } + + public static void ReportarSeLento( + string operacao, + long inicioTimestamp, + int limiteMs, + string detalhe = null) + { + long ms = DuracaoMs(inicioTimestamp); + if (ms < limiteMs) + return; + + Interlocked.Increment(ref _quantidadeOperacoesLentas); + Registrar( + ms >= Math.Max(1000, limiteMs * 4) ? "CRIT" : "WARN", + "Operação lenta: " + operacao + " levou " + ms + " ms" + + (string.IsNullOrWhiteSpace(detalhe) ? "" : " | " + detalhe)); + } + + public static void RegistrarAviso(string mensagem) + { + Registrar("WARN", mensagem); + } + + public static void RegistrarInfo(string mensagem) + { + Registrar("INFO", mensagem); + } + + public static void RegistrarErro(string mensagem) + { + Registrar("ERROR", mensagem); + } + + private static void UiProbeTimerCallback(object state) + { + if (!Ativo || _encerrando) + return; + + Control root = _uiRoot; + if (root == null || root.IsDisposed || !root.IsHandleCreated) + return; + + if (Interlocked.CompareExchange(ref _uiProbePendente, 1, 0) == 0) + { + Interlocked.Exchange(ref _uiProbeInicioTicks, Stopwatch.GetTimestamp()); + Interlocked.Exchange(ref _uiProbeAlertaEmitido, 0); + + try + { + root.BeginInvoke((MethodInvoker)delegate + { + long inicio = Interlocked.Read(ref _uiProbeInicioTicks); + long ms = DuracaoMs(inicio); + Interlocked.Exchange(ref _uiProbePendente, 0); + + if (ms >= UiWarnMs) + { + Interlocked.Increment(ref _quantidadeUiStalls); + AtualizarMaximo(ref _maiorUiStallIntervaloMs, ms); + AtualizarMaximo(ref _maiorUiStallSessaoMs, ms); + + Registrar( + ms >= UiCriticalMs ? "CRIT" : "WARN", + "UI stall concluído: " + ms + " ms" + MontarResumoCurto()); + } + }); + } + catch + { + Interlocked.Exchange(ref _uiProbePendente, 0); + } + + return; + } + + // Já existe um probe aguardando a UI. Podemos detectar o travamento + // enquanto ele ainda está acontecendo, sem esperar o WinForms voltar. + long inicioPendente = Interlocked.Read(ref _uiProbeInicioTicks); + long idadeMs = DuracaoMs(inicioPendente); + if (idadeMs >= UiCriticalMs && + Interlocked.CompareExchange(ref _uiProbeAlertaEmitido, 1, 0) == 0) + { + Registrar("CRIT", "UI sem responder há pelo menos " + idadeMs + " ms" + MontarResumoCurto()); + + // Fail-safe de campo: em V1 um comando de movimento pode permanecer + // latched se a UI travar antes do KeyUp. O watchdog roda fora da UI + // e injeta uma parada prioritária uma única vez por stall crítico. + try + { + bool carroEmMovimento = + GeneralJoystick.UltimaDirecao != Direcao.Parado || + Variaveis.Sentido_Driver1 != Sentido.Parado || + Variaveis.Sentido_Driver2 != Sentido.Parado; + + if (SerialService.Conectado && carroEmMovimento) + { + GeneralJoystick.ParadaSegura( + "watchdog: UI sem resposta"); + + Registrar( + "CRIT", + "FAIL-SAFE: parada de motores solicitada por stall crítico da UI."); + } + } + catch (Exception ex) + { + Registrar( + "ERROR", + "Falha ao aplicar fail-safe de UI: " + ex.Message); + } + } + } + + private static void SampleTimerCallback(object state) + { + if (!Ativo || _encerrando) + return; + + try + { + Process processo = _processo ?? Process.GetCurrentProcess(); + processo.Refresh(); + + DateTime agoraUtc = DateTime.UtcNow; + TimeSpan cpuAtual = processo.TotalProcessorTime; + double elapsedMs = Math.Max(1.0, (agoraUtc - _ultimaAmostraUtc).TotalMilliseconds); + double cpuDeltaMs = Math.Max(0.0, (cpuAtual - _cpuAnterior).TotalMilliseconds); + double cpu = cpuDeltaMs / elapsedMs / Math.Max(1, Environment.ProcessorCount) * 100.0; + + _ultimaAmostraUtc = agoraUtc; + _cpuAnterior = cpuAtual; + UltimaCpuPercent = cpu; + UltimoWorkingSetMb = processo.WorkingSet64 / (1024L * 1024L); + UltimaMemoriaGerenciadaMb = GC.GetTotalMemory(false) / (1024L * 1024L); + UltimosGdiHandles = ObterGuiResourcesSeguro(processo.Handle, 0); + UltimosUserHandles = ObterGuiResourcesSeguro(processo.Handle, 1); + + long crescimentoUltimaAmostraMb = (processo.WorkingSet64 - _ultimoWorkingSet) / (1024L * 1024L); + _ultimoWorkingSet = processo.WorkingSet64; + + if (UltimosGdiHandles >= 4000) + Registrar("WARN", "GDI handles altos: " + UltimosGdiHandles + "."); + if (UltimosUserHandles >= 4000) + Registrar("WARN", "USER handles altos: " + UltimosUserHandles + "."); + if (UltimoWorkingSetMb >= 1200) + Registrar("WARN", "Working set alto: " + UltimoWorkingSetMb + " MB."); + if (crescimentoUltimaAmostraMb >= 150) + Registrar("WARN", "Memória cresceu " + crescimentoUltimaAmostraMb + " MB em ~5 s."); + if (SerialService.FilaComandosPendentesPerformance >= 100) + Registrar("WARN", "Fila serial pendente alta: " + SerialService.FilaComandosPendentesPerformance + "."); + if (LogsFuncoes.FilaPersistenciaPendentePerformance >= 50) + Registrar("WARN", "Fila de persistência de logs alta: " + LogsFuncoes.FilaPersistenciaPendentePerformance + "."); + + if ((agoraUtc - _ultimoResumoUtc).TotalMilliseconds >= IntervaloResumoMs) + { + RegistrarResumo("SUMMARY"); + _ultimoResumoUtc = agoraUtc; + Interlocked.Exchange(ref _maiorUiStallIntervaloMs, 0); + } + } + catch (Exception ex) + { + Registrar("ERROR", "Falha na amostragem de performance: " + ex.Message); + } + } + + private static string MontarResumoCurto() + { + try + { + return string.Format( + CultureInfo.InvariantCulture, + " | cpu={0:F1}% ws={1}MB gdi={2} serialPend={3} camQ={4} logQ={5}", + UltimaCpuPercent, + UltimoWorkingSetMb, + UltimosGdiHandles, + SerialService.FilaComandosPendentesPerformance, + SerialService.FilaCameraPendentePerformance, + LogsFuncoes.FilaPersistenciaPendentePerformance); + } + catch + { + return ""; + } + } + + private static void RegistrarResumo(string tipo) + { + Process processo = _processo; + int threads = 0; + int handles = 0; + try + { + if (processo != null) + { + processo.Refresh(); + threads = processo.Threads.Count; + handles = processo.HandleCount; + } + } + catch { } + + int workerDisponivel = 0; + int ioDisponivel = 0; + try { ThreadPool.GetAvailableThreads(out workerDisponivel, out ioDisponivel); } catch { } + + string linha = string.Format( + CultureInfo.InvariantCulture, + "cpu={0:F1}% ws={1}MB managed={2}MB gdi={3} user={4} handles={5} threads={6} " + + "uiMax60s={7}ms uiMaxSessao={8}ms stalls={9} slowOps={10} " + + "serial[p={11},wait={12},rx={13},cam={14},baud={15},age={16}ms] " + + "video[cam={17},rec={18},writer={19},seq={20},age={21}ms] " + + "uiSensors[exec={22},coal={23}] persist[pending={24},busy={25}] logQ={26} tpAvail={27}/{28}", + UltimaCpuPercent, + UltimoWorkingSetMb, + UltimaMemoriaGerenciadaMb, + UltimosGdiHandles, + UltimosUserHandles, + handles, + threads, + Interlocked.Read(ref _maiorUiStallIntervaloMs), + Interlocked.Read(ref _maiorUiStallSessaoMs), + Interlocked.Read(ref _quantidadeUiStalls), + Interlocked.Read(ref _quantidadeOperacoesLentas), + SerialService.FilaComandosPendentesPerformance, + SerialService.FilaAguardandoRespostaPerformance, + SerialService.RxEmAndamentoPerformance ? 1 : 0, + SerialService.FilaCameraPendentePerformance, + SerialService.BaudAtualPerformance, + SerialService.IdadeUltimoProtocoloMsPerformance, + VideoService.CameraDisponivel ? 1 : 0, + VideoService.GravacaoIniciada ? 1 : 0, + VideoService.WriterAtivoPerformance ? 1 : 0, + VideoService.SequenciaFramePerformance, + VideoService.IdadeUltimoFrameMsPerformance, + SerialService.AtualizacoesUiSensoresExecutadasPerformance, + SerialService.AtualizacoesUiSensoresCoalescidasPerformance, + Variaveis.PersistenciaPendentePerformance ? 1 : 0, + Variaveis.PersistenciaEmAndamentoPerformance ? 1 : 0, + LogsFuncoes.FilaPersistenciaPendentePerformance, + workerDisponivel, + ioDisponivel); + + Registrar(tipo, linha); + } + + private static void Registrar(string nivel, string mensagem) + { + string linha = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture) + + " [PERF][" + nivel + "] " + mensagem; + + try { Console.WriteLine(linha); } catch { } + _filaLog.Enqueue(linha); + _logWake.Set(); + } + + private static void LogWriterLoop() + { + while (!_encerrando || !_filaLog.IsEmpty) + { + _logWake.WaitOne(1000); + DrenarFilaLog(); + } + + DrenarFilaLog(); + } + + private static void DrenarFilaLog() + { + if (_filaLog.IsEmpty) + return; + + try + { + string caminho = CaminhoLog; + string diretorio = Path.GetDirectoryName(caminho); + if (!string.IsNullOrWhiteSpace(diretorio)) + Directory.CreateDirectory(diretorio); + + // Evita crescimento ilimitado em notebooks que passam meses no campo. + if (File.Exists(caminho)) + { + FileInfo info = new FileInfo(caminho); + if (info.Length > 5L * 1024L * 1024L) + { + string arquivoAnterior = + Path.Combine( + info.DirectoryName ?? "", + "performance.previous.log"); + + try + { + if (File.Exists(arquivoAnterior)) + File.Delete(arquivoAnterior); + + File.Move(caminho, arquivoAnterior); + } + catch + { + } + } + } + + using (StreamWriter sw = new StreamWriter(caminho, true)) + { + string linha; + int escritos = 0; + while (escritos < 500 && _filaLog.TryDequeue(out linha)) + { + sw.WriteLine(linha); + escritos++; + } + } + } + catch + { + // O monitor nunca pode comprometer a aplicação monitorada. + } + } + + private static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) + { + try + { + Registrar("ERROR", "Exceção não tratada na UI: " + e.Exception); + } + catch { } + } + + private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) + { + try + { + Registrar("ERROR", "Exceção não tratada no AppDomain: " + Convert.ToString(e.ExceptionObject) + + " | terminating=" + e.IsTerminating); + + if (e.IsTerminating) + DrenarFilaLog(); + } + catch { } + } + + private static void AtualizarMaximo(ref long alvo, long valor) + { + while (true) + { + long atual = Interlocked.Read(ref alvo); + if (valor <= atual) + return; + if (Interlocked.CompareExchange(ref alvo, valor, atual) == atual) + return; + } + } + + private static int ObterGuiResourcesSeguro(IntPtr handleProcesso, int flag) + { + try + { + return GetGuiResources(handleProcesso, flag); + } + catch + { + return -1; + } + } + + [DllImport("user32.dll")] + private static extern int GetGuiResources(IntPtr hProcess, int uiFlags); + } + + internal class CameraTxItem + { + public byte[] Dados { get; set; } = new byte[0]; + public bool Parada { get; set; } + public bool EhMovimento { get; set; } + } + public class ProtocoloModel { public DateTime Momento { get; set; } = DateTime.MinValue; @@ -1463,5 +3528,42 @@ namespace OrionTardPro.Services public bool PermitirDuplicado { get; set; } = false; public bool Prioritario { get; set; } = false; public bool EhCameraBaudTemporario { get; set; } = false; + public bool EhComandoMotor { get; set; } = false; + public bool EhParadaMotor { get; set; } = false; + public bool EhComandoCamera { get; set; } = false; + public bool EhParadaCamera { get; set; } = false; + public ushort? SequenciaV6 { get; set; } = null; + public string ChaveV6 { get; set; } = ""; + public int Tentativas { get; set; } = 0; + public int MaxTentativas { get; set; } = 0; + public bool Cancelado { get; set; } = false; + } + + public class TelemetriaV6Model + { + public uint Id { get; set; } + public uint OldestId { get; set; } + public uint NewestId { get; set; } + public uint UptimeSeconds { get; set; } + public double VoltageAvgV { get; set; } + public double VoltageMinV { get; set; } + public int CurrentAvgMa { get; set; } + public int CurrentMaxMa { get; set; } + public double PowerAvgW { get; set; } + public double PowerMaxW { get; set; } + public double TemperatureC { get; set; } + public double HumidityPct { get; set; } + public double RollMinDeg { get; set; } + public double RollMaxDeg { get; set; } + public double PitchMinDeg { get; set; } + public double PitchMaxDeg { get; set; } + public int InfiltrationRaw { get; set; } + public int Flags { get; set; } + public int HealthyMask { get; set; } + public int Motor1Permille { get; set; } + public int Direction1 { get; set; } + public int Motor2Permille { get; set; } + public int Direction2 { get; set; } + public int StopReason { get; set; } } } diff --git a/OrionTardPro/OrionTardPro/Services/VersionamentoService.cs b/OrionTardPro/OrionTardPro/Services/VersionamentoService.cs index d500596..722f549 100644 --- a/OrionTardPro/OrionTardPro/Services/VersionamentoService.cs +++ b/OrionTardPro/OrionTardPro/Services/VersionamentoService.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using static OrionTardPro.Enuns; @@ -12,6 +13,7 @@ namespace OrionTardPro.Services public class VersionamentoService { private static readonly object _ArquivoLock = new object(); + private static readonly SemaphoreSlim _atualizacaoLock = new SemaphoreSlim(1, 1); private static string _ArquivoVersionamento { get; set; } = "version_files.vsf"; public static bool AtualizandoArquivos { get; set; } = false; public static double ProgressoAtualizacao { get; set; } = 0.0; @@ -97,68 +99,81 @@ namespace OrionTardPro.Services public static async Task AtualizarArquivoVersionamento(bool BaixarArquivo) { - // Nao executa caso ja esteja em andamento - if (AtualizandoArquivos) - { + // Evita duas verificações/downloads simultâneos vindos de timers, + // fechamento da licença ou ação manual do usuário. + if (!await _atualizacaoLock.WaitAsync(0).ConfigureAwait(false)) return; - } - // Nao executa caso seja verificacao periodica, e nao tenha dado erro anteriormente - if (!BaixarArquivo && !ResultadoAtualizacao.Contains("Erro")) + try { - return; - } + // Não executa caso seja verificação periódica e a última + // verificação já tenha terminado sem erro. + if (!BaixarArquivo && !ResultadoAtualizacao.Contains("Erro")) + return; - AtualizandoArquivos = true; - ProgressoAtualizacao = 0.0; - ResultadoAtualizacao = "Verificando atualizações..."; - string caminhoArquivo = _ArquivoVersionamento; + AtualizandoArquivos = true; + ProgressoAtualizacao = 0.0; + ResultadoAtualizacao = "Verificando atualizações..."; + string caminhoArquivo = _ArquivoVersionamento; - if (!File.Exists(caminhoArquivo)) - { - List arquivos = new List(); - lock (_ArquivoLock) + if (!File.Exists(caminhoArquivo)) { - arquivos = new List(_ArquivosVersionados); + List arquivos; + lock (_ArquivoLock) + { + arquivos = new List(_ArquivosVersionados); + } + + await SalvarArquivoVersionamento(arquivos).ConfigureAwait(false); } - await SalvarArquivoVersionamento(arquivos); - } - await CarregarArquivoVersionamento(caminhoArquivo); - ResultadoAtualizacao = "Arquivos carregados!"; + await CarregarArquivoVersionamento(caminhoArquivo).ConfigureAwait(false); + ResultadoAtualizacao = "Arquivos carregados!"; + + if (!await HttpService.ServerUpAsync().ConfigureAwait(false)) + { + ResultadoAtualizacao = "Equipamento desconectado!"; + return; + } - if (HttpService.ServerUp) - { File.Copy(caminhoArquivo, caminhoArquivo + "_old", true); bool baixado = !BaixarArquivo; if (!baixado) { - baixado = await HttpService.DownloadFileAsync(_ArquivoVersionamento, caminhoArquivo); + baixado = await HttpService + .DownloadFileAsync(_ArquivoVersionamento, caminhoArquivo) + .ConfigureAwait(false); } - if (baixado) + + if (!baixado) + return; + + bool carregado = await CarregarArquivoVersionamento(caminhoArquivo) + .ConfigureAwait(false); + + if (!carregado) { - bool carregado = await CarregarArquivoVersionamento(caminhoArquivo); - - if (!carregado) - { - File.Copy(caminhoArquivo + "_old", caminhoArquivo, true); - ResultadoAtualizacao = "Erro ao atualizar arquivos!"; - } - else - { - LimparArquivosAntigos(); - ResultadoAtualizacao = "Arquivos atualizados!"; - } + File.Copy(caminhoArquivo + "_old", caminhoArquivo, true); + ResultadoAtualizacao = "Erro ao atualizar arquivos!"; + } + else + { + LimparArquivosAntigos(); + ResultadoAtualizacao = "Arquivos atualizados!"; } } - else + catch (Exception ex) { - ResultadoAtualizacao = "Equipamento desconectado!"; + ResultadoAtualizacao = "Erro ao atualizar arquivos: " + ex.Message; + Console.WriteLine("[VERSAO] " + ResultadoAtualizacao); + } + finally + { + ForcarDownloadArquivos = false; + AtualizandoArquivos = false; + _atualizacaoLock.Release(); } - - ForcarDownloadArquivos = false; - AtualizandoArquivos = false; } private static async Task SalvarArquivoVersionamento(List arquivos) diff --git a/OrionTardPro/OrionTardPro/Services/VideoService.cs b/OrionTardPro/OrionTardPro/Services/VideoService.cs index e842b0d..bd3b4db 100644 --- a/OrionTardPro/OrionTardPro/Services/VideoService.cs +++ b/OrionTardPro/OrionTardPro/Services/VideoService.cs @@ -3,178 +3,829 @@ using OpenCvSharp.Extensions; using OrionTardPro.Models; using OrionTardPro.Services; using System; -using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using System.Windows.Forms; namespace OrionTardPro { + /// + /// Pipeline de vídeo do Orion. + /// + /// Princípios da implementação: + /// - a thread de captura nunca toca diretamente em controles WinForms; + /// - preview usa latest-frame: se a UI atrasar, quadros antigos são descartados; + /// - gravação possui worker próprio e nunca faz busy-loop; + /// - captura, preview e gravação não compartilham um Mat mutável sem sincronização; + /// - nenhuma operação normal usa Thread.Abort ou Join bloqueante na UI; + /// - reinício/desconexão da câmera é cooperativo e auto-recuperável. + /// public class VideoService { + private static readonly object _estadoLock = new object(); + private static readonly object _frameLock = new object(); + private static readonly object _previewLock = new object(); + private static readonly object _writerLock = new object(); + + private static readonly AutoResetEvent _cameraWake = new AutoResetEvent(false); + private static readonly AutoResetEvent _novoFrameWake = new AutoResetEvent(false); + private static readonly AutoResetEvent _writerWake = new AutoResetEvent(false); + + private static volatile bool _encerrarWorkerCamera; + private static volatile bool _cameraSolicitada; + private static volatile bool _cameraAberta; + private static int _versaoConfiguracaoCamera; + private static RotateFlipType OrientacaoCamera = RotateFlipType.RotateNoneFlipNone; - private static bool FlipX = false; - private static bool FlipY = false; - private static int Rotacao = 0; + private static bool FlipX; + private static bool FlipY; + private static int Rotacao; - public static bool GravacaoIniciada = false; - public static bool GravacaoPausada = false; - static string NomeVideoEmGravacao = ""; + public static volatile bool GravacaoIniciada; + public static volatile bool GravacaoPausada; + private static volatile bool _gravacaoFinalizando; + public static bool GravacaoFinalizando + { + get { return _gravacaoFinalizando; } + } - // Create class-level accesible variables - //VideoCapture capture; - static Mat _capturedFrame; - static Bitmap image; + private static string NomeVideoEmGravacao = ""; + + // Mantidos públicos por compatibilidade com código legado/diagnóstico. + // Não devem mais ser manipulados externamente. public static Thread camera; + public static VideoCapture _videoCapture; public static int IndexCamera = 0; - //private readonly VideoCaptureAPIs _videoCaptureApi = VideoCaptureAPIs.DSHOW; - private static readonly ManualResetEventSlim _writerReset = new ManualResetEventSlim(false); - public static VideoCapture _videoCapture; + private static Mat _ultimoFrame; + private static long _sequenciaFrame; + private static DateTime _ultimoFrameUtc = DateTime.MinValue; + private static double _fpsCaptura = 30.0; + private static int _larguraCaptura; + private static int _alturaCaptura; + + private static Bitmap _previewPendente; + private static Bitmap _previewExibidoPertencenteAoVideoService; + private static DateTime _ultimoPreviewGerado = DateTime.MinValue; + private static System.Windows.Forms.Timer _previewTimer; + private static VideoWriter _videoWriter; private static Thread _writerThread; + private static volatile bool _writerSolicitado; + + private const int PreviewMinimoMs = 33; // ~30 FPS máximos na UI. + private const int RetryCameraMs = 600; + private const int FalhasLeituraAntesReabrir = 30; + + public static bool CameraDisponivel + { + get { return _cameraAberta; } + } + + public static long SequenciaFramePerformance + { + get + { + lock (_frameLock) + return _sequenciaFrame; + } + } + + public static int IdadeUltimoFrameMsPerformance + { + get + { + DateTime ultimo; + lock (_frameLock) + ultimo = _ultimoFrameUtc; + + if (ultimo == DateTime.MinValue) + return -1; + + double ms = (DateTime.UtcNow - ultimo).TotalMilliseconds; + if (ms < 0) + return 0; + if (ms > int.MaxValue) + return int.MaxValue; + + return (int)ms; + } + } + + public static bool WriterAtivoPerformance + { + get + { + Thread writer = _writerThread; + return writer != null && writer.IsAlive; + } + } + + public static double FpsCapturaPerformance + { + get + { + lock (_estadoLock) + return _fpsCaptura; + } + } + + #region CAMERA / PREVIEW public static void LigarCamera() { - if (_videoCapture != null && _videoCapture.IsOpened()) + GarantirPreviewTimer(); + GarantirWorkerCamera(); + + lock (_estadoLock) { - camera.Abort(); - Thread.Sleep(300); - _videoCapture.Release(); + _cameraSolicitada = true; + _versaoConfiguracaoCamera++; + Variaveis.VideoIsRuning = true; } - Variaveis.VideoIsRuning = true; - camera = new Thread(new ThreadStart(CaptureCameraCallback)); - camera.Start(); - GravacaoPausada = false; + + // Mantém o comportamento anterior ao retornar da edição de foto. + if (GravacaoIniciada) + { + GravacaoPausada = false; + _writerWake.Set(); + } + + AtualizarIntervaloPreview(); + if (_previewTimer != null && !_previewTimer.Enabled) + _previewTimer.Start(); + + _cameraWake.Set(); } - private static void CaptureCameraCallback() + public static void DesligarCamera() { - //int indexCamera = Variaveis.frmCamera.cmbCamera.SelectedIndex; - _capturedFrame = new Mat(); - _videoCapture = new VideoCapture(IndexCamera); + DesligarCameraInterno(true, true); + } - _videoCapture.Open(IndexCamera); - _videoCapture.Fps = 60; + private static void PausarCameraParaEdicao() + { + DesligarCameraInterno(false, false); + } - if (_videoCapture.IsOpened()) + private static void DesligarCameraInterno(bool pararGravacao, bool limparFrames) + { + if (pararGravacao && GravacaoIniciada) + PararGravacao(); + + lock (_estadoLock) { - while (Variaveis.VideoIsRuning) + _cameraSolicitada = false; + _versaoConfiguracaoCamera++; + Variaveis.VideoIsRuning = false; + } + + _cameraWake.Set(); + + if (_previewTimer != null) + _previewTimer.Stop(); + + if (limparFrames) + LimparFramesPendentes(); + } + + private static void GarantirWorkerCamera() + { + lock (_estadoLock) + { + if (camera != null && camera.IsAlive) + return; + + _encerrarWorkerCamera = false; + camera = new Thread(CameraWorkerLoop) { - try + IsBackground = true, + Name = "Orion.VideoCapture" + }; + camera.Start(); + } + } + + private static void CameraWorkerLoop() + { + while (!_encerrarWorkerCamera) + { + if (!_cameraSolicitada) + { + FecharCapturaAtual(); + _cameraWake.WaitOne(250); + continue; + } + + int index; + int versaoLocal; + lock (_estadoLock) + { + index = IndexCamera; + versaoLocal = _versaoConfiguracaoCamera; + } + + VideoCapture capturaLocal = null; + + try + { + capturaLocal = new VideoCapture(); + capturaLocal.Open(index); + + try { capturaLocal.Fps = 60; } catch { } + + if (!capturaLocal.IsOpened()) { - _videoCapture.Read(_capturedFrame); - image = BitmapConverter.ToBitmap(_capturedFrame); - if (Variaveis.frmPrincipal.imgCamera.Image != null) + _cameraAberta = false; + capturaLocal.Release(); + capturaLocal.Dispose(); + capturaLocal = null; + _cameraWake.WaitOne(RetryCameraMs); + continue; + } + + lock (_estadoLock) + { + _videoCapture = capturaLocal; + _cameraAberta = true; + + double fps = capturaLocal.Fps; + if (double.IsNaN(fps) || double.IsInfinity(fps) || fps < 1 || fps > 120) + fps = 30.0; + + _fpsCaptura = fps; + _larguraCaptura = Math.Max(1, (int)Math.Round((double)capturaLocal.FrameWidth)); + _alturaCaptura = Math.Max(1, (int)Math.Round((double)capturaLocal.FrameHeight)); + } + + int falhasConsecutivas = 0; + using (Mat frame = new Mat()) + { + while (!_encerrarWorkerCamera && + _cameraSolicitada && + versaoLocal == _versaoConfiguracaoCamera) { - Variaveis.frmPrincipal.imgCamera.Image.Dispose(); + bool leu = false; + long inicioLeituraPerf = + PerformanceMonitorService.MarcarInicio(); + + try + { + leu = capturaLocal.Read(frame) && !frame.Empty(); + } + catch (Exception ex) + { + LogVideo("Falha lendo câmera: " + ex.Message); + } + + PerformanceMonitorService.ReportarSeLento( + "VideoCapture.Read", + inicioLeituraPerf, + 150, + "camera=" + index); + + if (!leu) + { + falhasConsecutivas++; + if (falhasConsecutivas >= FalhasLeituraAntesReabrir) + break; + + Thread.Sleep(10); + continue; + } + + falhasConsecutivas = 0; + + // A leitura pode ter começado antes de um Stop/Restart. + // Não publica um quadro obsoleto depois da mudança de geração. + if (!_cameraSolicitada || + versaoLocal != _versaoConfiguracaoCamera || + _encerrarWorkerCamera) + { + break; + } + + PublicarFrame(frame); + GerarPreviewSeNecessario(frame); } - Variaveis.frmPrincipal.imgCamera.Image = image; - Variaveis.frmPrincipal.imgCamera.Image.RotateFlip(OrientacaoCamera); } - catch (Exception ex) + } + catch (Exception ex) + { + LogVideo("Erro no worker da câmera: " + ex.Message); + } + finally + { + lock (_estadoLock) { - camera.Abort(); - LigarCamera(); + if (ReferenceEquals(_videoCapture, capturaLocal)) + _videoCapture = null; + _cameraAberta = false; } + + if (capturaLocal != null) + { + try { capturaLocal.Release(); } catch { } + try { capturaLocal.Dispose(); } catch { } + } + } + + if (_cameraSolicitada && !_encerrarWorkerCamera) + _cameraWake.WaitOne(RetryCameraMs); + } + + FecharCapturaAtual(); + } + + private static void FecharCapturaAtual() + { + VideoCapture captura = null; + lock (_estadoLock) + { + captura = _videoCapture; + _videoCapture = null; + _cameraAberta = false; + } + + if (captura != null) + { + try { captura.Release(); } catch { } + try { captura.Dispose(); } catch { } + } + } + + private static void PublicarFrame(Mat frame) + { + lock (_frameLock) + { + if (_ultimoFrame == null) + _ultimoFrame = new Mat(); + + frame.CopyTo(_ultimoFrame); + _sequenciaFrame++; + _ultimoFrameUtc = DateTime.UtcNow; + } + + _novoFrameWake.Set(); + } + + private static void GerarPreviewSeNecessario(Mat frame) + { + int intervalo = ObterIntervaloPreviewMs(); + DateTime agora = DateTime.UtcNow; + if ((agora - _ultimoPreviewGerado).TotalMilliseconds < intervalo) + return; + + _ultimoPreviewGerado = agora; + + Bitmap bitmap = null; + try + { + bitmap = BitmapConverter.ToBitmap(frame); + bitmap.RotateFlip(ObterOrientacaoAtual()); + + lock (_previewLock) + { + Bitmap anterior = _previewPendente; + _previewPendente = bitmap; + bitmap = null; + if (anterior != null) + anterior.Dispose(); + } + } + catch (Exception ex) + { + LogVideo("Falha criando preview: " + ex.Message); + } + finally + { + if (bitmap != null) + bitmap.Dispose(); + } + } + + private static void GarantirPreviewTimer() + { + if (_previewTimer != null) + return; + + _previewTimer = new System.Windows.Forms.Timer(); + _previewTimer.Interval = ObterIntervaloPreviewMs(); + _previewTimer.Tick += PreviewTimer_Tick; + } + + private static void AtualizarIntervaloPreview() + { + if (_previewTimer == null) + return; + + int intervalo = ObterIntervaloPreviewMs(); + if (_previewTimer.Interval != intervalo) + _previewTimer.Interval = intervalo; + } + + private static int ObterIntervaloPreviewMs() + { + return Math.Max(PreviewMinimoMs, Math.Min(250, Variaveis.DelayQuadros)); + } + + private static void PreviewTimer_Tick(object sender, EventArgs e) + { + long inicioPerf = + PerformanceMonitorService.MarcarInicio(); + + Bitmap novo = null; + lock (_previewLock) + { + novo = _previewPendente; + _previewPendente = null; + } + + if (novo == null) + return; + + try + { + if (Variaveis.frmPrincipal == null || + Variaveis.frmPrincipal.IsDisposed || + !Variaveis.frmPrincipal.IsHandleCreated) + { + novo.Dispose(); + return; + } + + PictureBox picture = Variaveis.frmPrincipal.imgCamera; + Bitmap antigoPertencente = _previewExibidoPertencenteAoVideoService; + + picture.Image = novo; + _previewExibidoPertencenteAoVideoService = novo; + + if (antigoPertencente != null && + !ReferenceEquals(antigoPertencente, novo)) + { + try { antigoPertencente.Dispose(); } catch { } + } + } + catch (Exception ex) + { + novo.Dispose(); + LogVideo("Falha atualizando preview: " + ex.Message); + } + + PerformanceMonitorService.ReportarSeLento( + "VideoService.PreviewTimer", + inicioPerf, + 50); + } + + private static RotateFlipType ObterOrientacaoAtual() + { + lock (_estadoLock) + return OrientacaoCamera; + } + + private static void LimparFramesPendentes() + { + lock (_frameLock) + { + if (_ultimoFrame != null) + { + _ultimoFrame.Dispose(); + _ultimoFrame = null; + } + _sequenciaFrame = 0; + _ultimoFrameUtc = DateTime.MinValue; + } + + lock (_previewLock) + { + if (_previewPendente != null) + { + _previewPendente.Dispose(); + _previewPendente = null; } } } + /// + /// Substitui o preview por uma imagem estática na UI e libera somente + /// bitmaps cujo ownership pertence ao VideoService. + /// + public static void ExibirImagemEstatica(Bitmap imagem) + { + if (imagem == null) + return; + try + { + PictureBox picture = Variaveis.frmPrincipal.imgCamera; + Bitmap antigoPertencente = _previewExibidoPertencenteAoVideoService; + + picture.Image = imagem; + _previewExibidoPertencenteAoVideoService = null; + + if (antigoPertencente != null && + !ReferenceEquals(antigoPertencente, imagem)) + { + try { antigoPertencente.Dispose(); } catch { } + } + } + catch + { + try { imagem.Dispose(); } catch { } + } + } + + #endregion + + #region GRAVACAO public static void IniciarGravacao() { - if (_writerThread != null) - return; + lock (_writerLock) + { + if (GravacaoIniciada || GravacaoFinalizando || (_writerThread != null && _writerThread.IsAlive)) + return; - GravacaoIniciada = true; - DirectoryInfo Path = new DirectoryInfo(Variaveis.Cliente.Path); - int QtdVideos = Path.GetFiles().Where(x => x.Extension == Variaveis.ExtVideo).Count() + 1; - string NomeVideo = "Video_" + QtdVideos + Variaveis.ExtVideo; - NomeVideoEmGravacao = Variaveis.Cliente.Path + "\\" + NomeVideo; + if (!_cameraAberta || !ExisteFrameValido()) + { + MessageBox.Show( + "A câmera ainda não possui um quadro válido para iniciar a gravação.", + "Vídeo indisponível", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + return; + } - AtualizaDetalhesFotos(NomeVideo, NomeVideoEmGravacao, 2, ""); + if (!Directory.Exists(Variaveis.Cliente.Path)) + Directory.CreateDirectory(Variaveis.Cliente.Path); - _videoWriter = new VideoWriter(NomeVideoEmGravacao, FourCC.XVID, _videoCapture.Fps, new OpenCvSharp.Size(_videoCapture.FrameWidth, _videoCapture.FrameHeight), true); + DirectoryInfo path = new DirectoryInfo(Variaveis.Cliente.Path); + int qtdVideos = path.GetFiles().Count(x => x.Extension == Variaveis.ExtVideo) + 1; + string nomeVideo = "Video_" + qtdVideos + Variaveis.ExtVideo; + NomeVideoEmGravacao = Path.Combine(Variaveis.Cliente.Path, nomeVideo); - _writerReset.Reset(); - _writerThread = new Thread(AddCameraFrameToRecordingThread); - _writerThread.Start(); + AtualizaDetalhesFotos(nomeVideo, NomeVideoEmGravacao, 2, ""); + + GravacaoPausada = false; + _gravacaoFinalizando = false; + GravacaoIniciada = true; + _writerSolicitado = true; + + _writerThread = new Thread(WriterWorkerLoop) + { + IsBackground = true, + Name = "Orion.VideoWriter" + }; + _writerThread.Start(); + } } public static void PausarGravacao() { - if (GravacaoIniciada) - GravacaoPausada = !GravacaoPausada; + if (!GravacaoIniciada) + return; + + GravacaoPausada = !GravacaoPausada; + _writerWake.Set(); } public static void PararGravacao() { - GravacaoIniciada = false; - - if (_writerThread != null) + lock (_writerLock) { - _writerReset.Set(); - _writerThread.Join(); - _writerThread = null; - _writerReset.Reset(); + if (!GravacaoIniciada && !(_writerThread != null && _writerThread.IsAlive)) + return; + + GravacaoIniciada = false; + GravacaoPausada = false; + _gravacaoFinalizando = true; + _writerSolicitado = false; } - _videoWriter?.Release(); - _videoWriter?.Dispose(); - _videoWriter = null; + _writerWake.Set(); + _novoFrameWake.Set(); } - private static void AddCameraFrameToRecordingThread() + private static void WriterWorkerLoop() { - var waitTimeBetweenFrames = 1_000 / _videoCapture.Fps; - var lastWrite = DateTime.Now; + VideoWriter writerLocal = null; + Mat frameEscrita = new Mat(); - while (GravacaoIniciada) + try { - if (DateTime.Now.Subtract(lastWrite).TotalMilliseconds < waitTimeBetweenFrames || GravacaoPausada) - continue; - lastWrite = DateTime.Now; - _videoWriter.Write(_capturedFrame); + double fps; + lock (_estadoLock) + fps = _fpsCaptura; + + int largura = 0; + int altura = 0; + lock (_frameLock) + { + if (_ultimoFrame != null && !_ultimoFrame.Empty()) + { + largura = _ultimoFrame.Cols; + altura = _ultimoFrame.Rows; + } + } + + if (double.IsNaN(fps) || double.IsInfinity(fps) || fps < 1 || fps > 120) + fps = 30.0; + if (largura <= 0 || altura <= 0) + throw new InvalidOperationException("Dimensões da câmera ainda não estão disponíveis."); + + writerLocal = new VideoWriter( + NomeVideoEmGravacao, + FourCC.XVID, + fps, + new OpenCvSharp.Size(largura, altura), + true); + + if (!writerLocal.IsOpened()) + throw new InvalidOperationException("Não foi possível abrir o arquivo de vídeo para gravação."); + + lock (_writerLock) + _videoWriter = writerLocal; + + int intervaloFrameMs = Math.Max(1, (int)Math.Round(1000.0 / fps)); + DateTime proximoFrame = DateTime.UtcNow; + long ultimaSequenciaGravada = -1; + + while (_writerSolicitado) + { + if (GravacaoPausada) + { + _writerWake.WaitOne(100); + proximoFrame = DateTime.UtcNow; + continue; + } + + int espera = Math.Max(1, (int)(proximoFrame - DateTime.UtcNow).TotalMilliseconds); + if (espera > 50) + espera = 50; + + _novoFrameWake.WaitOne(espera); + + if (!_writerSolicitado) + break; + if (GravacaoPausada) + continue; + if (DateTime.UtcNow < proximoFrame) + continue; + + long sequencia; + if (!CopiarUltimoFrame(frameEscrita, out sequencia)) + { + proximoFrame = DateTime.UtcNow.AddMilliseconds(intervaloFrameMs); + continue; + } + + // Não duplica o mesmo frame se a câmera estiver momentaneamente parada. + if (sequencia == ultimaSequenciaGravada) + { + proximoFrame = DateTime.UtcNow.AddMilliseconds(intervaloFrameMs); + continue; + } + + if (frameEscrita.Cols != largura || frameEscrita.Rows != altura) + throw new InvalidOperationException("A resolução da câmera mudou durante a gravação. Finalize a gravação antes de trocar de dispositivo/resolução."); + + long inicioWritePerf = + PerformanceMonitorService.MarcarInicio(); + + writerLocal.Write(frameEscrita); + + PerformanceMonitorService.ReportarSeLento( + "VideoWriter.Write", + inicioWritePerf, + 100); + + ultimaSequenciaGravada = sequencia; + proximoFrame = DateTime.UtcNow.AddMilliseconds(intervaloFrameMs); + } + } + catch (Exception ex) + { + LogVideo("Falha na gravação: " + ex.Message); + PostUi(delegate + { + try + { + MessageBox.Show( + "A gravação de vídeo foi interrompida.\r\n\r\n" + ex.Message, + "Erro de gravação", + MessageBoxButtons.OK, + MessageBoxIcon.Error); + } + catch { } + }); + } + finally + { + try { frameEscrita.Dispose(); } catch { } + if (writerLocal != null) + { + try { writerLocal.Release(); } catch { } + try { writerLocal.Dispose(); } catch { } + } + + lock (_writerLock) + { + if (ReferenceEquals(_videoWriter, writerLocal)) + _videoWriter = null; + + _writerSolicitado = false; + GravacaoIniciada = false; + GravacaoPausada = false; + _gravacaoFinalizando = false; + _writerThread = null; + } } } + private static bool ExisteFrameValido() + { + lock (_frameLock) + return _ultimoFrame != null && !_ultimoFrame.Empty(); + } + + private static bool CopiarUltimoFrame(Mat destino, out long sequencia) + { + lock (_frameLock) + { + sequencia = _sequenciaFrame; + if (_ultimoFrame == null || _ultimoFrame.Empty()) + return false; + + _ultimoFrame.CopyTo(destino); + return true; + } + } + + #endregion + + #region FOTO + public static void SalvaImagem() { if (Variaveis.Cliente == null) { - MessageBox.Show("O serviço ainda não foi definido, configure para salvar as fotos!", "Serviço não definido", MessageBoxButtons.OK, MessageBoxIcon.Warning); + MessageBox.Show( + "O serviço ainda não foi definido, configure para salvar as fotos!", + "Serviço não definido", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); Variaveis.frmConfiguracoes.ShowDialog(); return; } - if (!Variaveis.EditandoFoto && _videoCapture.IsOpened()) + if (Variaveis.EditandoFoto) { - Variaveis.VideoIsRuning = false; - GravacaoPausada = true; + Variaveis.frmImagemEditor.CarregaImagem(Variaveis.frmPrincipal.imgCamera, null); + Variaveis.frmImagemEditor.Show(); + Variaveis.frmImagemEditor.TopMost = true; + return; + } + Bitmap snapshot = ObterSnapshotPreviewAtual(); + if (snapshot == null) + { + MessageBox.Show( + "Ainda não há uma imagem válida da câmera para capturar.", + "Imagem indisponível", + MessageBoxButtons.OK, + MessageBoxIcon.Warning); + return; + } + + // Congela a câmera durante a edição, sem destruir a sessão de gravação. + PausarCameraParaEdicao(); + if (GravacaoIniciada) + { + GravacaoPausada = true; + _writerWake.Set(); + } + + try + { if (!Directory.Exists(Variaveis.Cliente.Path)) Directory.CreateDirectory(Variaveis.Cliente.Path); - string Hora = DateTime.Now.Hour.ToString(); - string Minuto = DateTime.Now.Minute.ToString(); - string Segundo = DateTime.Now.Second.ToString(); - string NomeFoto = ""; - if (DateTime.Now.Hour < 10) - Hora = "0" + DateTime.Now.Hour.ToString(); - if (DateTime.Now.Minute < 10) - Minuto = "0" + DateTime.Now.Minute.ToString(); - if (DateTime.Now.Second < 10) - Segundo = "0" + DateTime.Now.Second.ToString(); - NomeFoto = "Foto_" + Hora + "." + Minuto + "." + Segundo + Variaveis.ExtFoto; - Variaveis.frmPrincipal.imgCamera.Image.Save(Variaveis.Cliente.Path + "\\" + NomeFoto, System.Drawing.Imaging.ImageFormat.Jpeg); + string nomeFoto = "Foto_" + DateTime.Now.ToString("HH.mm.ss") + Variaveis.ExtFoto; + string pathSalvar = Path.Combine(Variaveis.Cliente.Path, nomeFoto); + snapshot.Save(pathSalvar, System.Drawing.Imaging.ImageFormat.Jpeg); - AtualizaDetalhesFotos(NomeFoto, Variaveis.Cliente.Path + "\\" + NomeFoto, 1, ""); + // Mantém na tela exatamente o quadro capturado enquanto o editor está aberto. + Bitmap congelado = (Bitmap)snapshot.Clone(); + Bitmap antigoPertencente = _previewExibidoPertencenteAoVideoService; + Variaveis.frmPrincipal.imgCamera.Image = congelado; + _previewExibidoPertencenteAoVideoService = congelado; + if (antigoPertencente != null && !ReferenceEquals(antigoPertencente, congelado)) + antigoPertencente.Dispose(); + + AtualizaDetalhesFotos(nomeFoto, pathSalvar, 1, ""); Variaveis.EditandoFoto = true; Variaveis.frmImagemEditor.txtObservacao.Text = ""; @@ -182,20 +833,45 @@ namespace OrionTardPro Variaveis.frmImagemEditor.Show(); Variaveis.frmImagemEditor.TopMost = true; } - else if (Variaveis.EditandoFoto) + finally { - Variaveis.frmImagemEditor.CarregaImagem(Variaveis.frmPrincipal.imgCamera, null); - Variaveis.frmImagemEditor.Show(); - Variaveis.frmImagemEditor.TopMost = true; + snapshot.Dispose(); } } - private static void AtualizaDetalhesFotos(string NomeArquivo, string Path, int Tipo, string ObservacaoFoto) + private static Bitmap ObterSnapshotPreviewAtual() { - RegistrosServicoModel Registro = new RegistrosServicoModel() + // Salva sempre o preview orientado, preservando o comportamento anterior. + try + { + Image imagemAtual = Variaveis.frmPrincipal != null + ? Variaveis.frmPrincipal.imgCamera.Image + : null; + + if (imagemAtual != null) + return new Bitmap(imagemAtual); + } + catch { } + + lock (_previewLock) + { + if (_previewPendente != null) + return new Bitmap(_previewPendente); + } + + return null; + } + + #endregion + + #region METADADOS / ORIENTACAO + + private static void AtualizaDetalhesFotos(string NomeArquivo, string PathArquivo, int Tipo, string ObservacaoFoto) + { + RegistrosServicoModel registro = new RegistrosServicoModel() { Nome = NomeArquivo, - Path = Path, + Path = PathArquivo, Data = DateTime.Now, TempoServico = TimeSpan.FromSeconds(SerialService.TempoConectado).ToString(@"hh\:mm\:ss"), InclinacaoX = Variaveis.Sensoriamento.InclinacaoLateral, @@ -208,99 +884,137 @@ namespace OrionTardPro Observacao = ObservacaoFoto, PosicaoTubo = Variaveis.Sensoriamento.DeslocamentoCarro }; + if (Tipo == 1) - Variaveis.ServicoAtual.Fotos.Add(Registro); + Variaveis.ServicoAtual.Fotos.Add(registro); else if (Tipo == 2) - Variaveis.ServicoAtual.Videos.Add(Registro); + Variaveis.ServicoAtual.Videos.Add(registro); Variaveis.AtualizarDadosServico(); } - public static void RotacionarVideo() { - if (Rotacao == 0) + lock (_estadoLock) { - if (FlipY) - OrientacaoCamera = RotateFlipType.Rotate90FlipY; - else if (FlipX) - OrientacaoCamera = RotateFlipType.Rotate90FlipX; - else if (FlipX && FlipY) - OrientacaoCamera = RotateFlipType.Rotate90FlipXY; - else - OrientacaoCamera = RotateFlipType.Rotate90FlipNone; - Rotacao = 90; - } - else if (Rotacao == 90) - { - if (FlipY) - OrientacaoCamera = RotateFlipType.Rotate180FlipY; - else if (FlipX) - OrientacaoCamera = RotateFlipType.Rotate180FlipX; - else if (FlipX && FlipY) - OrientacaoCamera = RotateFlipType.Rotate180FlipXY; - else - OrientacaoCamera = RotateFlipType.Rotate180FlipNone; - Rotacao = 180; - } - else if (Rotacao == 180) - { - if (FlipY) - OrientacaoCamera = RotateFlipType.Rotate270FlipY; - else if (FlipX) - OrientacaoCamera = RotateFlipType.Rotate270FlipX; - else if (FlipX && FlipY) - OrientacaoCamera = RotateFlipType.Rotate270FlipXY; - else - OrientacaoCamera = RotateFlipType.Rotate270FlipNone; - Rotacao = 270; - } - else if (Rotacao == 270) - { - if (FlipY) - OrientacaoCamera = RotateFlipType.RotateNoneFlipY; - else if (FlipX) - OrientacaoCamera = RotateFlipType.RotateNoneFlipX; - else if (FlipX && FlipY) - OrientacaoCamera = RotateFlipType.RotateNoneFlipXY; - else - OrientacaoCamera = RotateFlipType.RotateNoneFlipNone; - Rotacao = 0; + Rotacao = (Rotacao + 90) % 360; + RecalcularOrientacaoSemLock(); } } public static void EspelharVideoEmY() { - if (FlipY) - if (FlipX) - OrientacaoCamera = RotateFlipType.RotateNoneFlipX; - else - OrientacaoCamera = RotateFlipType.RotateNoneFlipNone; - else + lock (_estadoLock) { - if (FlipX) - OrientacaoCamera = RotateFlipType.RotateNoneFlipXY; - else - OrientacaoCamera = RotateFlipType.RotateNoneFlipY; + FlipY = !FlipY; + RecalcularOrientacaoSemLock(); } - FlipY = !FlipY; } public static void EspelharVideoEmX() { - if (FlipX) - if (FlipY) - OrientacaoCamera = RotateFlipType.RotateNoneFlipY; - else - OrientacaoCamera = RotateFlipType.RotateNoneFlipNone; - else - if (FlipY) - OrientacaoCamera = RotateFlipType.RotateNoneFlipXY; - else - OrientacaoCamera = RotateFlipType.RotateNoneFlipX; - FlipX = !FlipX; + lock (_estadoLock) + { + FlipX = !FlipX; + RecalcularOrientacaoSemLock(); + } } + private static void RecalcularOrientacaoSemLock() + { + if (Rotacao == 0) + OrientacaoCamera = ObterFlip(RotateFlipType.RotateNoneFlipNone, RotateFlipType.RotateNoneFlipX, RotateFlipType.RotateNoneFlipY, RotateFlipType.RotateNoneFlipXY); + else if (Rotacao == 90) + OrientacaoCamera = ObterFlip(RotateFlipType.Rotate90FlipNone, RotateFlipType.Rotate90FlipX, RotateFlipType.Rotate90FlipY, RotateFlipType.Rotate90FlipXY); + else if (Rotacao == 180) + OrientacaoCamera = ObterFlip(RotateFlipType.Rotate180FlipNone, RotateFlipType.Rotate180FlipX, RotateFlipType.Rotate180FlipY, RotateFlipType.Rotate180FlipXY); + else + OrientacaoCamera = ObterFlip(RotateFlipType.Rotate270FlipNone, RotateFlipType.Rotate270FlipX, RotateFlipType.Rotate270FlipY, RotateFlipType.Rotate270FlipXY); + } + private static RotateFlipType ObterFlip( + RotateFlipType nenhum, + RotateFlipType x, + RotateFlipType y, + RotateFlipType xy) + { + if (FlipX && FlipY) return xy; + if (FlipX) return x; + if (FlipY) return y; + return nenhum; + } + + #endregion + + #region ENCERRAMENTO / UTILITARIOS + + public static void Encerrar() + { + try { PararGravacao(); } catch { } + + lock (_estadoLock) + { + _cameraSolicitada = false; + _encerrarWorkerCamera = true; + _versaoConfiguracaoCamera++; + Variaveis.VideoIsRuning = false; + } + + if (_previewTimer != null) + _previewTimer.Stop(); + + _cameraWake.Set(); + _writerWake.Set(); + _novoFrameWake.Set(); + + // Somente no encerramento da aplicação usamos espera limitada para + // dar chance ao codec de finalizar o arquivo corretamente. + try + { + Thread writer = _writerThread; + if (writer != null && writer.IsAlive) + writer.Join(1200); + } + catch { } + + try + { + Thread captura = camera; + if (captura != null && captura.IsAlive) + captura.Join(500); + } + catch { } + + LimparFramesPendentes(); + + lock (_previewLock) + { + if (_previewExibidoPertencenteAoVideoService != null) + { + try { _previewExibidoPertencenteAoVideoService.Dispose(); } catch { } + _previewExibidoPertencenteAoVideoService = null; + } + } + } + + private static void PostUi(MethodInvoker acao) + { + try + { + Form form = Variaveis.frmPrincipal; + if (form == null || form.IsDisposed || !form.IsHandleCreated) + return; + + form.BeginInvoke(acao); + } + catch { } + } + + private static void LogVideo(string mensagem) + { + Console.WriteLine("[VIDEO] " + mensagem); + } + + #endregion } } diff --git a/OrionTardPro/OrionTardPro/Variaveis.cs b/OrionTardPro/OrionTardPro/Variaveis.cs index 04607ee..40e1f8c 100644 --- a/OrionTardPro/OrionTardPro/Variaveis.cs +++ b/OrionTardPro/OrionTardPro/Variaveis.cs @@ -140,10 +140,9 @@ namespace OrionTardPro public static Estado Com_BuzzerRaque { get; set; } = Estado.Desligado; // Protocolo de entrada - public static string[] TiposOdometro = new string[] { "Total", "Conectado", "Movimento", "Parcial" }; - public static int OdometroEnderecoTotal { get; set; } = 110; + public static int OdometroEnderecoTotal { get; set; } = 110; public static int OdometroEnderecoConectado { get; set; } = 130; public static int OdometroEnderecoMotorLigado { get; set; } = 150; public static int OdometroEnderecoMotorLigadoParcial { get; set; } = 170; @@ -158,37 +157,355 @@ namespace OrionTardPro // DADOS SENSORES public static List LogsSensoresDinamicos { get; set; } = new List() { - new LogSensorModel() { sensor = TipoSensor.Cor, descricao = "Corrente", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Cor, descricao = "Porcentagem", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Gas, descricao = "Gás", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Inclinação Frontal", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Inclinação Lateral", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Altitude", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "ACE", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Ten, descricao = "Tensão", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Ten, descricao = "Porcentagem", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Tmp, descricao = "Temperatura", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Ult, descricao = "Ultrassom", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Umi, descricao = "Umidade", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Vel, descricao = "Velocidade", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Cor, descricao = "Corrente", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Cor, descricao = "Porcentagem", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Gas, descricao = "Gás", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Inclinação Frontal", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Inclinação Lateral", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "Altitude", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Mpu, descricao = "ACE", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Ten, descricao = "Tensão", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Ten, descricao = "Porcentagem", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Tmp, descricao = "Temperatura", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Ult, descricao = "Ultrassom", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Umi, descricao = "Umidade", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Vel, descricao = "Velocidade", lista = new List() }, new LogSensorModel() { sensor = TipoSensor.Vel, descricao = "Porcentagem", lista = new List() }, new LogSensorModel() { sensor = TipoSensor.Vel, descricao = "Velocidade MPU", lista = new List() }, - new LogSensorModel() { sensor = TipoSensor.Ods, descricao = "Distância", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Ods, descricao = "Distância", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Inf, descricao = "Infiltração - nível de risco", lista = new List() }, + new LogSensorModel() { sensor = TipoSensor.Inf, descricao = "Infiltração - leitura bruta", lista = new List() } }; - - + + public static int EncoderVoltasPorMetro { get; set; } = 15; - - + // Persistência operacional desacoplada da UI. + // Durante movimento apenas marcamos o perfil como "sujo". + // Um timer de background grava no máximo uma vez a cada 5 s. + private const int IntervaloPersistenciaOperacionalMs = 5000; + private static readonly object _persistenciaPerfilLock = new object(); + private static readonly object _escritaPerfilLock = new object(); + private static System.Threading.Timer _persistenciaPerfilTimer; + private static int _persistenciaPerfilPendente; + private static int _persistenciaPerfilEmAndamento; - - public static void SetVariaveisGerais(bool DeFabrica, bool Odometro = false) + public static bool PersistenciaPendentePerformance + { + get + { + return System.Threading.Interlocked.CompareExchange( + ref _persistenciaPerfilPendente, + 0, + 0) != 0; + } + } + + public static bool PersistenciaEmAndamentoPerformance + { + get + { + return System.Threading.Interlocked.CompareExchange( + ref _persistenciaPerfilEmAndamento, + 0, + 0) != 0; + } + } + + public static void SolicitarPersistenciaOperacional() + { + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 1); + + lock (_persistenciaPerfilLock) + { + if (_persistenciaPerfilTimer == null) + { + _persistenciaPerfilTimer = + new System.Threading.Timer( + PersistenciaPerfilTimerCallback, + null, + IntervaloPersistenciaOperacionalMs, + IntervaloPersistenciaOperacionalMs); + } + } + } + + private static void PersistenciaPerfilTimerCallback(object state) + { + if (System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 0) == 0) + { + return; + } + + if (System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilEmAndamento, + 1) == 1) + { + // Já há uma gravação em curso. Mantém a solicitação viva + // para o próximo ciclo sem empilhar Tasks. + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 1); + return; + } + + try + { + PersistirPerfilEmDisco(); + } + catch (Exception ex) + { + Console.WriteLine( + "[PERSISTENCIA] Falha ao salvar perfil operacional: " + + ex.Message); + + // Tenta novamente no próximo ciclo. + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 1); + } + finally + { + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilEmAndamento, + 0); + } + } + + public static void FlushPersistenciaPendente() + { + try + { + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 0); + + PersistirPerfilEmDisco(); + } + catch (Exception ex) + { + Console.WriteLine( + "[PERSISTENCIA] Falha no flush final do perfil: " + + ex.Message); + } + } + + private static void PersistirPerfilEmDisco() + { + long inicioPerf = PerformanceMonitorService.MarcarInicio(); + + lock (_escritaPerfilLock) + { + if (PerfilVigente == null) + return; + + string jsonPerfil = + JsonConvert.SerializeObject(PerfilVigente); + + GravarTextoAtomicamente( + "install" + ExtInstall, + jsonPerfil); + } + + PerformanceMonitorService.ReportarSeLento( + "PersistirPerfilEmDisco", + inicioPerf, + 150); + } + + private static void GravarTextoAtomicamente( + string caminhoDestino, + string conteudo) + { + string caminhoTemporario = + caminhoDestino + ".tmp"; + + File.WriteAllText( + caminhoTemporario, + conteudo, + Encoding.UTF8); + + if (!File.Exists(caminhoDestino)) + { + File.Move( + caminhoTemporario, + caminhoDestino); + + return; + } + + string caminhoBackup = + caminhoDestino + ".bak"; + + try + { + File.Replace( + caminhoTemporario, + caminhoDestino, + caminhoBackup, + true); + + try + { + if (File.Exists(caminhoBackup)) + File.Delete(caminhoBackup); + } + catch + { + } + } + catch + { + // Fallback para sistemas/volumes onde File.Replace + // não estiver disponível. + File.Copy( + caminhoTemporario, + caminhoDestino, + true); + + File.Delete(caminhoTemporario); + } + } + + /// + /// Persiste a calibração de inclinação no install e no perfil + /// selecionado. A operação é rara (duplo clique de calibração), então + /// usamos gravação atômica imediata para que o zero sobreviva inclusive + /// a uma reinicialização logo após a calibração. + /// + public static void PersistirCalibracaoInclinacao( + double offsetLateral, + double offsetFrontal) + { + if (PerfilVigente == null) + return; + + long inicioPerf = PerformanceMonitorService.MarcarInicio(); + + lock (_escritaPerfilLock) + { + PerfilVigente.Sensor_InclinacaoLateral_Offset = offsetLateral; + PerfilVigente.Sensor_InclinacaoFrontal_Offset = offsetFrontal; + + // O install guarda o estado operacional atual. + string jsonInstall = JsonConvert.SerializeObject(PerfilVigente); + GravarTextoAtomicamente( + "install" + ExtInstall, + jsonInstall); + + // Mantém também o perfil selecionado coerente. Atualizamos + // somente os campos de calibração para não transportar + // odômetros/estado operacional para o arquivo de perfil. + string caminhoPerfil = PerfilVigente.PerfilSelecionado; + if (!string.IsNullOrWhiteSpace(caminhoPerfil) && + File.Exists(caminhoPerfil)) + { + try + { + string jsonPerfil = File.ReadAllText(caminhoPerfil); + PerfilModel perfilSalvo = + JsonConvert.DeserializeObject(jsonPerfil); + + if (perfilSalvo != null) + { + perfilSalvo.Sensor_InclinacaoLateral_Offset = offsetLateral; + perfilSalvo.Sensor_InclinacaoFrontal_Offset = offsetFrontal; + + GravarTextoAtomicamente( + caminhoPerfil, + JsonConvert.SerializeObject(perfilSalvo)); + } + } + catch (Exception ex) + { + Console.WriteLine( + "[CALIBRACAO] Falha ao atualizar offsets no perfil: " + + ex.Message); + } + } + } + + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 0); + + PerformanceMonitorService.ReportarSeLento( + "PersistirCalibracaoInclinacao", + inicioPerf, + 150); + } + + public static int ObterPotenciaMinimaPercentual() + { + if (PerfilVigente == null) + return 0; + + return Math.Max(0, Math.Min(100, PerfilVigente.PotenciaMinima * 10)); + } + + public static int ObterPotenciaMaximaPercentual() + { + if (PerfilVigente == null) + return 100; + + int minimo = ObterPotenciaMinimaPercentual(); + int maximo = Math.Max(0, Math.Min(100, PerfilVigente.PotenciaMaxima * 10)); + return Math.Max(minimo, maximo); + } + + public static int NormalizarPotenciaAtualPercentual(int valor) + { + // Compatibilidade com os perfis legados: historicamente o TrackBar + // trabalhava em unidades de dezenas (6 = 60%). A partir daqui + // PotenciaAtual passa a representar diretamente o percentual. + if (valor >= 0 && valor <= 10) + valor *= 10; + + int minimo = ObterPotenciaMinimaPercentual(); + int maximo = ObterPotenciaMaximaPercentual(); + return Math.Max(minimo, Math.Min(maximo, valor)); + } + + public static void ConfigurarTrackBarsVelocidade() + { + if (PerfilVigente == null || frmPrincipal == null || frmControle == null) + return; + + int minimo = ObterPotenciaMinimaPercentual(); + int maximo = ObterPotenciaMaximaPercentual(); + int atual = NormalizarPotenciaAtualPercentual(PerfilVigente.PotenciaAtual); + + PerfilVigente.PotenciaAtual = atual; + + TrackBar[] controles = + { + frmPrincipal.trkVelocidade, + frmControle.trkVelocidade + }; + + foreach (TrackBar controle in controles) + { + controle.Maximum = maximo; + controle.Minimum = minimo; + controle.SmallChange = 1; + controle.LargeChange = 5; + controle.TickFrequency = 1; + controle.Value = atual; + } + } + + public static void SetVariaveisGerais( + bool DeFabrica, + bool Odometro = false) { if (DeFabrica) { @@ -197,26 +514,21 @@ namespace OrionTardPro if (!Odometro) { - PerfilVigente.PotenciaAtual = Convert.ToInt32(frmPrincipal.trkVelocidade.Value); + PerfilVigente.PotenciaAtual = + Convert.ToInt32( + frmPrincipal.trkVelocidade.Value); } - PerfilVigente.ListaAlertas.ForEach(alerta => - { - alerta.LabelSensor = null; - //alerta.ValorComparar = null; - }); - - string JsonPerfil = JsonConvert.SerializeObject(PerfilVigente); - File.WriteAllText("install" + ExtInstall, JsonPerfil); - - PerfilVigente.ListaAlertas.ForEach(alerta => - { - alerta.LabelSensor = frmPrincipal.Controls.Find(alerta.LabelSensorNome, true).First(); - //alerta.ValorComparar = ListaMetodosSensores.First(x => x.Method.Name == alerta.ValorCompararNome); - }); + // LabelSensor é runtime-only e agora possui [JsonIgnore]. + // Portanto não precisamos mais desmontar/reconstruir + // referências de Controls ao salvar o perfil. + PersistirPerfilEmDisco(); + System.Threading.Interlocked.Exchange( + ref _persistenciaPerfilPendente, + 0); } - + public static void SalvarPerfil(string Diretorio) { string NomePerfil = Diretorio.Split('\\')[Diretorio.Split('\\').Length - 1].Replace(ExtPerfil, ""); @@ -230,20 +542,22 @@ namespace OrionTardPro PerfilVigente = new PerfilModel() { - Odometro = 0, - OdometroInspecao = Convert.ToInt32(frmParametros.nudOdometroInspecao.Value), + Odometro = 0, + OdometroInspecao = Convert.ToInt32(frmParametros.nudOdometroInspecao.Value), TensaoMinimaBateria = double.Parse(frmParametros.txtTensaominima.Text), TensaoMaximaBateria = double.Parse(frmParametros.txtTensaomaxima.Text), CorrenteMaximaBateria = double.Parse(frmParametros.txtCorrentebateria.Text), ComprimentoCabo = Convert.ToInt32(frmParametros.txtComprimentocabo.Value), - RPM = Convert.ToDouble(frmParametros.nudRPM.Value), + RPM = Convert.ToDouble(frmParametros.nudRPM.Value), DiametroRoda = Convert.ToDouble(frmParametros.nudDiametroRoda.Value), OffsetOdometro = double.Parse(string.IsNullOrEmpty(frmParametros.txtOffsetvelocidade.Text) ? "0" : frmParametros.txtOffsetvelocidade.Text), + Sensor_InclinacaoLateral_Offset = Sensoriamento.Sensor_InclinacaoLateral_Offset, + Sensor_InclinacaoFrontal_Offset = Sensoriamento.Sensor_InclinacaoFrontal_Offset, Offsett_Driver1_Frente = Convert.ToInt32(frmParametros.nudDF1Offset.Value), Offsett_Driver2_Frente = Convert.ToInt32(frmParametros.nudDF2Offset.Value), Offsett_Driver1_Tras = Convert.ToInt32(frmParametros.nudDT1Offset.Value), Offsett_Driver2_Tras = Convert.ToInt32(frmParametros.nudDT2Offset.Value), - Offsett_Raque_Enrolar = Convert.ToInt32(frmParametros.nudOffsetEnrolar.Value), + Offsett_Raque_Enrolar = Convert.ToInt32(frmParametros.nudOffsetEnrolar.Value), Offsett_Raque_Desenrolar = Convert.ToInt32(frmParametros.nudOffsetDesenrolar.Value), NumeroPingsMaximo = Convert.ToInt32(frmParametros.txtPings.Value), ResolucaoLeitura = Convert.ToInt32(frmParametros.txtResolucao.Value), @@ -256,52 +570,55 @@ namespace OrionTardPro TempoRecepcaoSensores = Convert.ToInt32(frmConexao.txtDelay.Value), TempoEntrePings = Convert.ToInt32(frmConexao.txtTempoPings.Value), QuantidadeEnvios = Convert.ToInt32(frmConexao.txtEnvios.Value), - ControleIndividualCamera = frmConexao.chbCamera.Checked, - ReceberProtocoloCompleto = frmConexao.chbProtocolocompleto.Checked, - PerfilSelecionado = PerfilSelecionado, - RampaAtual = Convert.ToInt32(frmControle.trkRampaaceleracao.Value), - PotenciaAtual = Convert.ToInt32(frmControle.trkVelocidade.Value), + ControleIndividualCamera = frmConexao.chbCamera.Checked, + EnviarPingCamera = frmConexao.chbPingCamera.Checked, + ReceberProtocoloCompleto = frmConexao.chbProtocolocompleto.Checked, + PerfilSelecionado = PerfilSelecionado, + RampaAtual = Convert.ToInt32(frmControle.trkRampaaceleracao.Value), + PotenciaAtual = Convert.ToInt32(frmControle.trkVelocidade.Value), PrecisaoAtual = Convert.ToInt32(frmControle.trkPrecisaorampa.Value), - Sensor_Ativado_GAS = frmParametros.chbGas.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_UMI = frmParametros.chbUmi.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_MPU = frmParametros.chbMpu.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_ULT = frmParametros.chbUlt.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_TEN = frmParametros.chbTen.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_COR = frmParametros.chbCor.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_VEL = frmParametros.chbVel.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_TMP = frmParametros.chbTmp.Checked ? Estado.Ligado : Estado.Desligado, - Sensor_Ativado_Watchdog = frmParametros.chbWatchdog.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_MOT = frmParametros.chbMot.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_ILU = frmParametros.chbIlu.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_LAS = frmParametros.chbLas.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_CAM = frmParametros.chbCam.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_EMB = frmParametros.chbEmb.Checked ? Estado.Ligado : Estado.Desligado, - Atuador_Ativado_Transmissor = frmParametros.chbTransmissor.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_GAS = frmParametros.chbGas.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_UMI = frmParametros.chbUmi.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_MPU = frmParametros.chbMpu.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_ULT = frmParametros.chbUlt.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_TEN = frmParametros.chbTen.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_COR = frmParametros.chbCor.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_VEL = frmParametros.chbVel.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_TMP = frmParametros.chbTmp.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_INF = frmParametros.chbInf.Checked ? Estado.Ligado : Estado.Desligado, + Sensor_Ativado_Watchdog = frmParametros.chbWatchdog.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_MOT = frmParametros.chbMot.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_ILU = frmParametros.chbIlu.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_LAS = frmParametros.chbLas.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_CAM = frmParametros.chbCam.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_EMB = frmParametros.chbEmb.Checked ? Estado.Ligado : Estado.Desligado, + Atuador_Ativado_Transmissor = frmParametros.chbTransmissor.Checked ? Estado.Ligado : Estado.Desligado, Atuador_Ativado_Raque = frmParametros.chbRaque.Checked ? Estado.Ligado : Estado.Desligado, Atuador_Ativado_BRC = frmParametros.chbBrc.Checked ? Estado.Ligado : Estado.Desligado, Atuador_Ativado_MEP = frmParametros.chbMep.Checked ? Estado.Ligado : Estado.Desligado, - Driver1_Frente = (Sentido)frmParametros.cmbF1.SelectedIndex, - Driver2_Frente = (Sentido)frmParametros.cmbF2.SelectedIndex, - Driver1_Tras = (Sentido)frmParametros.cmbT1.SelectedIndex, - Driver2_Tras = (Sentido)frmParametros.cmbT2.SelectedIndex, - Driver1_Esquerda = (Sentido)frmParametros.cmbE1.SelectedIndex, - Driver2_Esquerda = (Sentido)frmParametros.cmbE2.SelectedIndex, - Driver1_Direita = (Sentido)frmParametros.cmbD1.SelectedIndex, - Driver2_Direita = (Sentido)frmParametros.cmbD2.SelectedIndex, - Raque_Enrolar = (Sentido)frmParametros.cmbEnrolar.SelectedIndex, - Raque_Desenrolar = (Sentido)frmParametros.cmbDesenrolar.SelectedIndex, - BaudRate = int.Parse(string.IsNullOrEmpty(frmConexao.cmbBaud.Text) ? SerialService.BaudRateFirmware.ToString() : frmConexao.cmbBaud.Text), + Driver1_Frente = (Sentido)frmParametros.cmbF1.SelectedIndex, + Driver2_Frente = (Sentido)frmParametros.cmbF2.SelectedIndex, + Driver1_Tras = (Sentido)frmParametros.cmbT1.SelectedIndex, + Driver2_Tras = (Sentido)frmParametros.cmbT2.SelectedIndex, + Driver1_Esquerda = (Sentido)frmParametros.cmbE1.SelectedIndex, + Driver2_Esquerda = (Sentido)frmParametros.cmbE2.SelectedIndex, + Driver1_Direita = (Sentido)frmParametros.cmbD1.SelectedIndex, + Driver2_Direita = (Sentido)frmParametros.cmbD2.SelectedIndex, + Raque_Enrolar = (Sentido)frmParametros.cmbEnrolar.SelectedIndex, + Raque_Desenrolar = (Sentido)frmParametros.cmbDesenrolar.SelectedIndex, + BaudRate = int.Parse(string.IsNullOrEmpty(frmConexao.cmbBaud.Text) ? SerialService.BaudRateFirmware.ToString() : frmConexao.cmbBaud.Text), ValorMaximoAnalogico = Convert.ToInt32(Math.Pow(2, PerfilVigente.ResolucaoLeitura)), - PerfilJoystick = PerfilVigente.PerfilJoystick, + PerfilJoystick = PerfilVigente.PerfilJoystick, ListaAlertas = PerfilVigente.ListaAlertas, - COR_coeficienteA = Double.Parse(frmParametros.txtCoefA.Text), - COR_coeficienteB = Double.Parse(frmParametros.txtCoefB.Text), - COR_multiplicador = Double.Parse(frmParametros.txtMultiplicadorCorrente.Text), - InverterRotacaoDireita = frmParametros.chbInvDireita.Checked, + COR_multiplicador = Double.Parse(frmParametros.txtMultiplicadorCorrente.Text), + InverterRotacaoDireita = frmParametros.chbInvDireita.Checked, InverterRotacaoEsquerda = frmParametros.chbInvEsquerda.Checked, - FirmwareF4 = frmParametros.rdbF4.Checked, - RequisitarDados = frmConexao.chbRequisitarDados.Checked, - EnderecoReleGeral = Convert.ToByte(frmParametros.nudEnderecoReleGeral.Value), + //FirmwareF4 = true, // frmParametros.rdbF4.Checked, + VersaoProtocolo = (VersaoProtocolo)Enum.Parse( + typeof(VersaoProtocolo), + frmParametros.cmbProtocoloVersao.SelectedItem.ToString()), + RequisitarDados = frmConexao.chbRequisitarDados.Checked, + EnderecoReleGeral = Convert.ToByte(frmParametros.nudEnderecoReleGeral.Value), CanalReleGeral = Convert.ToInt32(frmParametros.nudCanalReleGeral.Value), AnguloCarimboHorizontal = Convert.ToInt32(frmParametros.nudAnguloCarimboHorizontal.Value), AnguloCarimboVertical = Convert.ToInt32(frmParametros.nudAnguloCarimboVertical.Value), @@ -309,7 +626,7 @@ namespace OrionTardPro AnguloMedicaoMargemLimite = Convert.ToInt32(frmParametros.nudAnguloMedicaoMargemLimite.Value), }; string PerfilJson = JsonConvert.SerializeObject(PerfilVigente); - File.WriteAllText(Diretorio, PerfilJson); + GravarTextoAtomicamente(Diretorio, PerfilJson); //File.WriteAllLines(Diretorio, DadosPerfil); @@ -318,6 +635,13 @@ namespace OrionTardPro public static void CarregarPerfil(string Diretorio = null) { + if (Diretorio != null && LogsFuncoes.TemAtividadeNoPeriodo) + { + LogsFuncoes.RegistrarLog( + TipoEventoLog.TrocaPerfil, + "Encerramento da janela antes da troca de perfil."); + } + string pathLeitura = "install" + ExtInstall; if (Diretorio != null) pathLeitura = Diretorio; @@ -325,6 +649,19 @@ namespace OrionTardPro string JsonPerfil = File.ReadAllText(pathLeitura); PerfilVigente = JsonConvert.DeserializeObject(JsonPerfil); + if (PerfilVigente == null) + throw new InvalidDataException("O arquivo de perfil não contém um perfil válido."); + + // Sincroniza imediatamente a calibração persistida com o modelo + // runtime. Assim conexão/reconexão não apaga o zero do usuário. + if (Sensoriamento != null) + Sensoriamento.CarregarOffsetsDoPerfil(); + + if (!Enum.IsDefined(typeof(VersaoProtocolo), PerfilVigente.VersaoProtocolo)) + PerfilVigente.VersaoProtocolo = VersaoProtocolo.v1; + if (PerfilVigente.ListaAlertas == null) + PerfilVigente.ListaAlertas = new List(); + if (pathLeitura.Contains("install")) { OdometroAtual = PerfilVigente.Odometro; @@ -350,7 +687,7 @@ namespace OrionTardPro AlertaSonoro = false, ComparacaoMaxima = true, ComparacaoMinima = false, - LabelSensorNome = "lblOdometroTempo", + LabelSensorNome = "lblOdometroTempo", LimiteMaximo = PerfilVigente.OdometroInspecao, LimiteMinimo = 0, Mensagem = "O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!", @@ -366,16 +703,11 @@ namespace OrionTardPro }); PerfilVigente.ValorMaximoAnalogico = Convert.ToInt32(Math.Pow(2, PerfilVigente.ResolucaoLeitura)); - frmControle.trkVelocidade.Maximum = PerfilVigente.PotenciaMaxima; - frmPrincipal.trkVelocidade.Maximum = PerfilVigente.PotenciaMaxima; - frmControle.trkVelocidade.Minimum = PerfilVigente.PotenciaMinima; - frmPrincipal.trkVelocidade.Minimum = PerfilVigente.PotenciaMinima; + ConfigurarTrackBarsVelocidade(); frmControle.trkRampaaceleracao.Maximum = PerfilVigente.RampaMaxima; frmControle.trkRampaaceleracao.Minimum = PerfilVigente.RampaMinima; frmControle.trkPrecisaorampa.Maximum = PerfilVigente.PrecisaoMaxima; frmControle.trkPrecisaorampa.Minimum = PerfilVigente.PrecisaoMinima; - frmPrincipal.trkVelocidade.Value = PerfilVigente.PotenciaAtual; - frmControle.trkVelocidade.Value = PerfilVigente.PotenciaAtual; frmParametros.chbGas.Checked = (PerfilVigente.Sensor_Ativado_GAS == Estado.Ligado); frmParametros.chbTmp.Checked = (PerfilVigente.Sensor_Ativado_TMP == Estado.Ligado); frmParametros.chbMpu.Checked = (PerfilVigente.Sensor_Ativado_MPU == Estado.Ligado); @@ -384,9 +716,10 @@ namespace OrionTardPro frmParametros.chbVel.Checked = (PerfilVigente.Sensor_Ativado_VEL == Estado.Ligado); frmParametros.chbCor.Checked = (PerfilVigente.Sensor_Ativado_COR == Estado.Ligado); frmParametros.chbUmi.Checked = (PerfilVigente.Sensor_Ativado_UMI == Estado.Ligado); + frmParametros.chbInf.Checked = (PerfilVigente.Sensor_Ativado_INF == Estado.Ligado); frmParametros.chbMot.Checked = (PerfilVigente.Atuador_Ativado_MOT == Estado.Ligado); frmParametros.chbIlu.Checked = (PerfilVigente.Atuador_Ativado_ILU == Estado.Ligado); - frmParametros.chbLas.Checked = (PerfilVigente.Atuador_Ativado_LAS == Estado.Ligado); + frmParametros.chbLas.Checked = (PerfilVigente.Atuador_Ativado_LAS == Estado.Ligado); frmParametros.chbCam.Checked = (PerfilVigente.Atuador_Ativado_CAM == Estado.Ligado); frmParametros.chbEmb.Checked = (PerfilVigente.Atuador_Ativado_EMB == Estado.Ligado); frmParametros.chbRaque.Checked = (PerfilVigente.Atuador_Ativado_Raque == Estado.Ligado); @@ -411,12 +744,14 @@ namespace OrionTardPro frmConexao.txtTempoPings.Value = PerfilVigente.TempoEntrePings; frmConexao.txtEnvios.Value = PerfilVigente.QuantidadeEnvios; frmConexao.chbCamera.Checked = PerfilVigente.ControleIndividualCamera; + frmConexao.chbPingCamera.Checked = PerfilVigente.EnviarPingCamera; frmConexao.chbProtocolocompleto.Checked = PerfilVigente.ReceberProtocoloCompleto; frmConexao.chbRequisitarDados.Checked = PerfilVigente.RequisitarDados; frmParametros.chbInvDireita.Checked = PerfilVigente.InverterRotacaoDireita; frmParametros.chbInvEsquerda.Checked = PerfilVigente.InverterRotacaoEsquerda; - frmParametros.rdbF3.Checked = !PerfilVigente.FirmwareF4; - frmParametros.rdbF4.Checked = PerfilVigente.FirmwareF4; + //frmParametros.rdbF3.Checked = !PerfilVigente.FirmwareF4; + //frmParametros.rdbF4.Checked = PerfilVigente.FirmwareF4; + frmParametros.cmbProtocoloVersao.SelectedItem = PerfilVigente.VersaoProtocolo.ToString(); frmParametros.nudEnderecoReleGeral.Value = PerfilVigente.EnderecoReleGeral; frmParametros.nudCanalReleGeral.Value = PerfilVigente.CanalReleGeral; frmPrincipal.tkbBraco.Value = 0; @@ -432,7 +767,6 @@ namespace OrionTardPro frmPrincipal.lblPerfil.Text = "Perfil: " + NomePerfil; - LogsFuncoes.RegistrarLog(); } public static void AtualizarDadosServico() diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Layouts/PS2.joy b/OrionTardPro/OrionTardPro/bin/Debug/Layouts/PS2.joy index 992f9df..3de1ba4 100644 --- a/OrionTardPro/OrionTardPro/bin/Debug/Layouts/PS2.joy +++ b/OrionTardPro/OrionTardPro/bin/Debug/Layouts/PS2.joy @@ -1 +1 @@ -{"PerfilNome":"PS2","Layout":"Layouts\\PS2.jpg","DataCriacao":"2024-08-26T14:17:17.6131205-03:00","Botoes":[{"Indice":0,"Descricao":"Triangulo","Posicao":"378, 149","Botao":0,"Comando":11,"Definido":true},{"Indice":1,"Descricao":"Bolinha","Posicao":"412, 181","Botao":1,"Comando":10,"Definido":true},{"Indice":2,"Descricao":"Xis","Posicao":"377, 214","Botao":2,"Comando":9,"Definido":true},{"Indice":3,"Descricao":"Quadrado","Posicao":"347, 181","Botao":3,"Comando":0,"Definido":true},{"Indice":4,"Descricao":"L1","Posicao":"122, 101","Botao":4,"Comando":6,"Definido":true},{"Indice":5,"Descricao":"R1","Posicao":"376, 103","Botao":5,"Comando":5,"Definido":true},{"Indice":6,"Descricao":"L2","Posicao":"123, 84","Botao":6,"Comando":13,"Definido":true},{"Indice":7,"Descricao":"R2","Posicao":"375, 83","Botao":7,"Comando":12,"Definido":true},{"Indice":8,"Descricao":"Select","Posicao":"210, 182","Botao":8,"Comando":0,"Definido":true},{"Indice":9,"Descricao":"Start","Posicao":"285, 182","Botao":9,"Comando":0,"Definido":true},{"Indice":10,"Descricao":"L3","Posicao":"183, 245","Botao":10,"Comando":8,"Definido":true},{"Indice":11,"Descricao":"R3","Posicao":"313, 246","Botao":11,"Comando":0,"Definido":true}],"QuantidadeBotoes":12,"AnalogicoZeroX":32767,"AnalogicoZeroY":32767,"InverteAnalogicoLX":false,"InverteAnalogicoLY":false,"InverteAnalogicoRX":true,"InverteAnalogicoRY":true} \ No newline at end of file +{"PerfilNome":"PS2","Layout":"Layouts\\PS2.jpg","DataCriacao":"2026-05-12T13:04:20.8474749-03:00","Botoes":[{"Indice":0,"Descricao":"Triangulo","Posicao":"378, 149","Botao":0,"Comando":11,"Definido":true},{"Indice":1,"Descricao":"Bolinha","Posicao":"412, 181","Botao":1,"Comando":10,"Definido":true},{"Indice":2,"Descricao":"Xis","Posicao":"377, 214","Botao":2,"Comando":9,"Definido":true},{"Indice":3,"Descricao":"Quadrado","Posicao":"347, 181","Botao":3,"Comando":0,"Definido":true},{"Indice":4,"Descricao":"L1","Posicao":"122, 101","Botao":4,"Comando":6,"Definido":true},{"Indice":5,"Descricao":"R1","Posicao":"376, 103","Botao":5,"Comando":5,"Definido":true},{"Indice":6,"Descricao":"L2","Posicao":"123, 84","Botao":6,"Comando":13,"Definido":true},{"Indice":7,"Descricao":"R2","Posicao":"375, 83","Botao":7,"Comando":12,"Definido":true},{"Indice":8,"Descricao":"Select","Posicao":"210, 182","Botao":8,"Comando":0,"Definido":true},{"Indice":9,"Descricao":"Start","Posicao":"285, 182","Botao":9,"Comando":0,"Definido":true},{"Indice":10,"Descricao":"L3","Posicao":"183, 245","Botao":10,"Comando":8,"Definido":true},{"Indice":11,"Descricao":"R3","Posicao":"313, 246","Botao":11,"Comando":0,"Definido":true}],"QuantidadeBotoes":12,"AnalogicoZeroX":32767,"AnalogicoZeroY":32767,"InverteAnalogicoLX":false,"InverteAnalogicoLY":false,"InverteAnalogicoRX":true,"InverteAnalogicoRY":true} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2.otpp b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2.otpp index 3b35d60..2ba06ab 100644 --- a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2.otpp +++ b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2.otpp @@ -1 +1 @@ -{"Odometro":0.0,"OdometroInspecao":10,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":66.0,"DiametroRoda":120.0,"OffsetOdometro":0.01030125,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":5,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":1000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor2.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":675.0,"LimiteMinimo":0.0,"LimiteMaximo":36000.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":91.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":23.06,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":47.23,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file +{"Odometro":0.0,"OdometroInspecao":36000,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":66.0,"DiametroRoda":120.0,"OffsetOdometro":0.01030125,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":5000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor2.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":675.0,"LimiteMinimo":0.0,"LimiteMaximo":36000.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":91.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":23.06,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":47.23,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2_MedidorEspessura.otpp b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2_MedidorEspessura.otpp new file mode 100644 index 0000000..02c92f7 --- /dev/null +++ b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor2_MedidorEspessura.otpp @@ -0,0 +1 @@ +{"Odometro":0.0,"OdometroInspecao":36000,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":66.0,"DiametroRoda":120.0,"OffsetOdometro":0.01030125,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":5000,"TempoEntrePings":10,"QuantidadeEnvios":1,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor2_MedidorEspessura.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":1,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"AnguloCarimboHorizontal":63,"AnguloCarimboVertical":63,"AnguloMedicaoIdeal":132,"AnguloMedicaoMargemLimite":20,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":16583.0,"LimiteMinimo":0.0,"LimiteMaximo":36000.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":94.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":30.06,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":36.53,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor3.1.otpp b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor3.1.otpp index 8e560ce..52bc7f8 100644 --- a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor3.1.otpp +++ b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor3.1.otpp @@ -1 +1 @@ -{"Odometro":0.0,"OdometroInspecao":10,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":54.0,"DiametroRoda":170.0,"OffsetOdometro":0.01206,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":5,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":1000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor3.1.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":190.0,"LimiteMinimo":0.0,"LimiteMaximo":10.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":0.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":0.0,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file +{"Odometro":0.0,"OdometroInspecao":36000,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":54.0,"DiametroRoda":170.0,"OffsetOdometro":0.01206,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":5000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor3.1.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":9600,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":190.0,"LimiteMinimo":0.0,"LimiteMaximo":10.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":0.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":0.0,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor4.otpp b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor4.otpp index c9a7bdd..10610d5 100644 --- a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor4.otpp +++ b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Factor4.otpp @@ -1 +1 @@ -{"Odometro":0.0,"OdometroInspecao":10,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":14.4,"CorrenteMaximaBateria":15.0,"ComprimentoCabo":150,"RPM":54.0,"DiametroRoda":170.0,"OffsetOdometro":0.01206,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":5,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":1000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor4.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":10.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":0.91,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":4.0,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":17.4,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file +{"Odometro":0.0,"OdometroInspecao":10,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":8.0,"ComprimentoCabo":150,"RPM":54.0,"DiametroRoda":170.0,"OffsetOdometro":0.01206,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":4,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":10,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":10,"PrecisaoMinima":1,"TempoRecepcaoSensores":1000,"TempoEntrePings":4,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"EnviarPingCamera":false,"ReceberProtocoloCompleto":true,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Factor4.otpp","PerfilJoystick":"Layouts\\default.joy","RampaAtual":2,"PotenciaAtual":6,"PrecisaoAtual":10,"Sensor_Ativado_GAS":0,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_INF":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":1,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":2,"Driver2_Frente":1,"Driver1_Tras":1,"Driver2_Tras":2,"Driver1_Esquerda":2,"Driver2_Esquerda":2,"Driver1_Direita":1,"Driver2_Direita":1,"Raque_Enrolar":0,"Raque_Desenrolar":0,"BaudRate":38400,"RequisitarDados":true,"VersaoProtocolo":2,"EnderecoReleGeral":0,"CanalReleGeral":1,"AnguloCarimboHorizontal":67,"AnguloCarimboVertical":63,"AnguloMedicaoIdeal":132,"AnguloMedicaoMargemLimite":20,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":10.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":0.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":0.0,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file diff --git a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Tardigrado.otpp b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Tardigrado.otpp index acd7bb8..372c5a7 100644 --- a/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Tardigrado.otpp +++ b/OrionTardPro/OrionTardPro/bin/Debug/Perfis/Tardigrado.otpp @@ -1 +1 @@ -{"Odometro":0.0,"OdometroInspecao":10,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":15.0,"ComprimentoCabo":150,"RPM":50.0,"DiametroRoda":205.0,"OffsetOdometro":0.0135675,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":7,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":20,"PrecisaoMinima":10,"TempoRecepcaoSensores":1000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Tardigrado.otpp","PerfilJoystick":"Layouts\\PS2.joy","RampaAtual":2,"PotenciaAtual":3,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":0,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":1,"Driver2_Frente":2,"Driver1_Tras":2,"Driver2_Tras":1,"Driver1_Esquerda":1,"Driver2_Esquerda":1,"Driver1_Direita":2,"Driver2_Direita":2,"Raque_Enrolar":1,"Raque_Desenrolar":2,"BaudRate":38400,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":1,"CanalReleGeral":1,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":675.0,"LimiteMinimo":0.0,"LimiteMaximo":36000.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":91.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":23.06,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":47.23,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file +{"Odometro":0.0,"OdometroInspecao":36000,"TensaoMinimaBateria":12.0,"TensaoMaximaBateria":16.8,"CorrenteMaximaBateria":15.0,"ComprimentoCabo":150,"RPM":50.0,"DiametroRoda":205.0,"OffsetOdometro":0.0135675,"InverterRotacaoEsquerda":false,"InverterRotacaoDireita":false,"Offsett_Driver1_Frente":0,"Offsett_Driver2_Frente":0,"Offsett_Driver1_Tras":0,"Offsett_Driver2_Tras":0,"Offsett_Raque_Enrolar":0,"Offsett_Raque_Desenrolar":0,"NumeroPingsMaximo":10,"ResolucaoLeitura":10,"ValorMaximoAnalogico":1024,"PotenciaMaxima":7,"PotenciaMinima":3,"RampaMaxima":5,"RampaMinima":1,"PrecisaoMaxima":20,"PrecisaoMinima":10,"TempoRecepcaoSensores":5000,"TempoEntrePings":10,"QuantidadeEnvios":2,"ControleIndividualCamera":true,"ReceberProtocoloCompleto":true,"COR_coeficienteA":0.097,"COR_coeficienteB":-0.17,"COR_multiplicador":1.0,"PerfilSelecionado":"Perfis\\Tardigrado.otpp","PerfilJoystick":"Layouts\\PS2.joy","RampaAtual":2,"PotenciaAtual":5,"PrecisaoAtual":10,"Sensor_Ativado_GAS":1,"Sensor_Ativado_UMI":1,"Sensor_Ativado_MPU":1,"Sensor_Ativado_ULT":0,"Sensor_Ativado_TEN":1,"Sensor_Ativado_COR":1,"Sensor_Ativado_VEL":0,"Sensor_Ativado_TMP":1,"Sensor_Ativado_Watchdog":0,"Atuador_Ativado_MOT":1,"Atuador_Ativado_ILU":0,"Atuador_Ativado_LAS":0,"Atuador_Ativado_CAM":0,"Atuador_Ativado_EMB":0,"Atuador_Ativado_Transmissor":1,"Atuador_Ativado_Raque":0,"Atuador_Ativado_BRC":0,"Atuador_Ativado_MEP":0,"Driver1_Frente":1,"Driver2_Frente":2,"Driver1_Tras":2,"Driver2_Tras":1,"Driver1_Esquerda":1,"Driver2_Esquerda":1,"Driver1_Direita":2,"Driver2_Direita":2,"Raque_Enrolar":1,"Raque_Desenrolar":2,"BaudRate":9600,"RequisitarDados":true,"FirmwareF4":true,"EnderecoReleGeral":0,"CanalReleGeral":1,"AnguloCarimboHorizontal":67,"AnguloCarimboVertical":63,"AnguloMedicaoIdeal":132,"AnguloMedicaoMargemLimite":20,"ListaAlertas":[{"TipoSensor":9,"Titulo":"Manutenção preventiva necessária","Mensagem":"O tempo de trabalho já atingiu o limite recomendado para manutenção preventiva. Planeje essa ação!","ValorComparar":15227.0,"LimiteMinimo":0.0,"LimiteMaximo":36000.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblOdometroTempo","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":0,"Editando":false},{"TipoSensor":5,"Titulo":"Bateria baixa","Mensagem":"Risco de parada!","ValorComparar":0.0,"LimiteMinimo":20.0,"LimiteMaximo":0.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"gpbNivelbateria","ComparacaoMaxima":false,"ComparacaoMinima":true,"AlarmeAcionado":true,"PararCarro":false,"AlarmeAtivado":1,"Variavel":2,"Editando":false},{"TipoSensor":8,"Titulo":"Temperatura excedida","Mensagem":"Risco de parada","ValorComparar":0.0,"LimiteMinimo":-5.0,"LimiteMaximo":85.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblTemperaturavalor","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":6,"Editando":false},{"TipoSensor":2,"Titulo":"Umidade alta","Mensagem":"Risco de infiltração","ValorComparar":0.0,"LimiteMinimo":0.0,"LimiteMaximo":40.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblStatusUmidade","ComparacaoMaxima":true,"ComparacaoMinima":false,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":7,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação lateral","Mensagem":"Risco de queda!","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblInclinacaoy","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":10,"Editando":false},{"TipoSensor":3,"Titulo":"Inclinação frontal","Mensagem":"Risco de queda!\n","ValorComparar":0.0,"LimiteMinimo":-45.0,"LimiteMaximo":45.0,"AlertaSonoro":false,"LabelSensor":null,"LabelSensorNome":"lblAltitude","ComparacaoMaxima":true,"ComparacaoMinima":true,"AlarmeAcionado":false,"PararCarro":false,"AlarmeAtivado":1,"Variavel":11,"Editando":false}]} \ No newline at end of file