mirror of
https://github.com/adrigongv23/G26---Telemetry-Software.git
synced 2026-08-25 11:33:17 +02:00
458 lines
13 KiB
C#
458 lines
13 KiB
C#
using System.IO.Ports;
|
|
|
|
namespace App_radio;
|
|
|
|
public partial class RadioMain : Form
|
|
{
|
|
private readonly RadioController _radioController = new();
|
|
|
|
private bool _pilotConfirmed;
|
|
private bool _disconnectingAfterFault;
|
|
private bool _closing;
|
|
private bool _closeReady;
|
|
private bool _pttTransitionInProgress;
|
|
|
|
public RadioMain()
|
|
{
|
|
InitializeComponent();
|
|
ConfigureControls();
|
|
SubscribeEvents();
|
|
LoadPorts();
|
|
ApplyVolumes();
|
|
ShowDisconnectedState();
|
|
AddLog("Aplicación iniciada.");
|
|
}
|
|
|
|
private void ConfigureControls()
|
|
{
|
|
PuertoBox.DropDownStyle = ComboBoxStyle.DropDownList;
|
|
|
|
VolumenBoxUpdown.Minimum = 0;
|
|
VolumenBoxUpdown.Maximum = 100;
|
|
VolumenBoxUpdown.Increment = 1;
|
|
|
|
VolumenPilotoUpdown.Minimum = 0;
|
|
VolumenPilotoUpdown.Maximum = 100;
|
|
VolumenPilotoUpdown.Increment = 1;
|
|
|
|
PttButton.Enabled = false;
|
|
PttButton.Cursor = Cursors.Hand;
|
|
}
|
|
|
|
private void SubscribeEvents()
|
|
{
|
|
ActualizarButton.Click += ActualizarButton_Click;
|
|
ConectarButton.Click += ConectarButton_Click;
|
|
|
|
PttButton.MouseDown += PttButton_MouseDown;
|
|
PttButton.MouseUp += PttButton_MouseUp;
|
|
PttButton.MouseCaptureChanged += PttButton_MouseCaptureChanged;
|
|
|
|
VolumenBoxUpdown.ValueChanged += VolumeControl_Changed;
|
|
VolumenPilotoUpdown.ValueChanged += VolumeControl_Changed;
|
|
SilenciarBoxCheck.CheckedChanged += VolumeControl_Changed;
|
|
SilenciarPilotoCheck.CheckedChanged += VolumeControl_Changed;
|
|
|
|
Deactivate += RadioMain_Deactivate;
|
|
FormClosing += RadioMain_FormClosing;
|
|
|
|
_radioController.Error += RadioController_Error;
|
|
_radioController.PilotAudioReceived += RadioController_PilotAudioReceived;
|
|
_radioController.ConnectionFaulted += RadioController_ConnectionFaulted;
|
|
}
|
|
|
|
private void LoadPorts()
|
|
{
|
|
string? previousSelection = PuertoBox.SelectedItem?.ToString();
|
|
|
|
PuertoBox.Items.Clear();
|
|
string[] availablePorts = SerialPort.GetPortNames();
|
|
Array.Sort(availablePorts, ComparePortNames);
|
|
PuertoBox.Items.AddRange(availablePorts);
|
|
|
|
if (previousSelection is not null && PuertoBox.Items.Contains(previousSelection))
|
|
{
|
|
PuertoBox.SelectedItem = previousSelection;
|
|
}
|
|
else if (PuertoBox.Items.Count > 0)
|
|
{
|
|
PuertoBox.SelectedIndex = 0;
|
|
}
|
|
|
|
if (PuertoBox.Items.Count == 0)
|
|
{
|
|
AddLog("No se ha encontrado ningún puerto COM.");
|
|
}
|
|
}
|
|
|
|
private static int ComparePortNames(string? left, string? right)
|
|
{
|
|
int leftNumber = ExtractPortNumber(left);
|
|
int rightNumber = ExtractPortNumber(right);
|
|
|
|
int numberComparison = leftNumber.CompareTo(rightNumber);
|
|
return numberComparison != 0
|
|
? numberComparison
|
|
: string.Compare(left, right, StringComparison.OrdinalIgnoreCase);
|
|
}
|
|
|
|
private static int ExtractPortNumber(string? portName)
|
|
{
|
|
if (portName is not null
|
|
&& portName.StartsWith("COM", StringComparison.OrdinalIgnoreCase)
|
|
&& int.TryParse(portName.AsSpan(3), out int number))
|
|
{
|
|
return number;
|
|
}
|
|
|
|
return int.MaxValue;
|
|
}
|
|
|
|
private void ActualizarButton_Click(object? sender, EventArgs e)
|
|
{
|
|
LoadPorts();
|
|
AddLog($"Puertos actualizados: {PuertoBox.Items.Count} encontrado(s).");
|
|
}
|
|
|
|
private async void ConectarButton_Click(object? sender, EventArgs e)
|
|
{
|
|
if (_radioController.IsConnected)
|
|
{
|
|
await DisconnectRadioAsync("Desconectado por el usuario.");
|
|
return;
|
|
}
|
|
|
|
string? selectedPort = PuertoBox.SelectedItem?.ToString();
|
|
if (string.IsNullOrWhiteSpace(selectedPort))
|
|
{
|
|
MessageBox.Show(
|
|
this,
|
|
"Selecciona un puerto COM antes de conectar.",
|
|
"Puerto no seleccionado",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Warning);
|
|
return;
|
|
}
|
|
|
|
SetConnectionControlsEnabled(false);
|
|
SetStatus(ConectadoBoxLabel, "● Conectando...", Color.DarkOrange);
|
|
SetGeneralRadioStatus("● Conectando...", Color.DarkOrange);
|
|
AddLog($"Abriendo {selectedPort} a 115200 baudios...");
|
|
|
|
try
|
|
{
|
|
await Task.Run(() => _radioController.Connect(selectedPort));
|
|
|
|
_pilotConfirmed = false;
|
|
SetStatus(ConectadoBoxLabel, "● Conectado", Color.Green);
|
|
SetStatus(ConectadoPilotoLabel, "● Sin confirmar", Color.Gray);
|
|
SetGeneralRadioStatus("● Escuchando", Color.Green);
|
|
|
|
ConectarButton.Text = "Desconectar";
|
|
PttButton.Enabled = true;
|
|
PuertoBox.Enabled = false;
|
|
ActualizarButton.Enabled = false;
|
|
ConectarButton.Enabled = true;
|
|
|
|
ApplyVolumes();
|
|
AddLog($"Radio del box conectada en {selectedPort}.");
|
|
AddLog("Mantén pulsado el botón PTT para hablar.");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
ShowDisconnectedState();
|
|
AddLog($"No se pudo conectar: {ex.Message}");
|
|
|
|
MessageBox.Show(
|
|
this,
|
|
$"No se pudo iniciar la radio.\n\n{ex.Message}",
|
|
"Error de conexión",
|
|
MessageBoxButtons.OK,
|
|
MessageBoxIcon.Error);
|
|
}
|
|
}
|
|
|
|
private void PttButton_MouseDown(object? sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button != MouseButtons.Left || !_radioController.IsConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
PttButton.Capture = true;
|
|
|
|
if (_radioController.SetPtt(true))
|
|
{
|
|
PttButton.Text = "HABLANDO...\nEscucha simultánea activa";
|
|
PttButton.UseVisualStyleBackColor = false;
|
|
PttButton.BackColor = Color.IndianRed;
|
|
SetGeneralRadioStatus("● Transmitiendo y escuchando", Color.Red);
|
|
AddLog("PTT activado: transmitiendo y manteniendo activa la escucha.");
|
|
}
|
|
}
|
|
|
|
private void PttButton_MouseUp(object? sender, MouseEventArgs e)
|
|
{
|
|
if (e.Button == MouseButtons.Left)
|
|
{
|
|
StopPtt();
|
|
}
|
|
}
|
|
|
|
private void PttButton_MouseCaptureChanged(object? sender, EventArgs e)
|
|
{
|
|
if (!PttButton.Capture && _radioController.IsPttActive)
|
|
{
|
|
StopPtt();
|
|
}
|
|
}
|
|
|
|
private void RadioMain_Deactivate(object? sender, EventArgs e)
|
|
{
|
|
StopPtt();
|
|
}
|
|
|
|
private void StopPtt()
|
|
{
|
|
if (_pttTransitionInProgress)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_pttTransitionInProgress = true;
|
|
|
|
try
|
|
{
|
|
bool wasActive = _radioController.IsPttActive;
|
|
|
|
// El controlador corta la transmisión de forma inmediata y el
|
|
// servicio serie deja de enviar al terminar como máximo el fragmento
|
|
// actual de 200 bytes.
|
|
_radioController.SetPtt(false);
|
|
|
|
PttButton.Capture = false;
|
|
PttButton.Text = "Pulsar para hablar";
|
|
PttButton.UseVisualStyleBackColor = true;
|
|
PttButton.BackColor = SystemColors.Control;
|
|
|
|
if (_radioController.IsConnected)
|
|
{
|
|
SetGeneralRadioStatus("● Escuchando", Color.Green);
|
|
}
|
|
else
|
|
{
|
|
SetGeneralRadioStatus("● Desconectada", Color.Gray);
|
|
}
|
|
|
|
// Fuerza el repintado antes de añadir el registro, para que el botón
|
|
// nunca parezca quedarse pulsado mientras se actualiza el RichTextBox.
|
|
PttButton.Invalidate();
|
|
PttButton.Update();
|
|
|
|
if (wasActive)
|
|
{
|
|
AddLog("PTT desactivado: escuchando la radio del piloto.");
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_pttTransitionInProgress = false;
|
|
}
|
|
}
|
|
|
|
private void VolumeControl_Changed(object? sender, EventArgs e)
|
|
{
|
|
ApplyVolumes();
|
|
}
|
|
|
|
private void ApplyVolumes()
|
|
{
|
|
int boxPercent = Decimal.ToInt32(VolumenBoxUpdown.Value);
|
|
int pilotPercent = Decimal.ToInt32(VolumenPilotoUpdown.Value);
|
|
|
|
_radioController.SetBoxVolume(boxPercent, SilenciarBoxCheck.Checked);
|
|
_radioController.SetPilotVolume(pilotPercent, SilenciarPilotoCheck.Checked);
|
|
}
|
|
|
|
private void RadioController_PilotAudioReceived(object? sender, EventArgs e)
|
|
{
|
|
RunOnUiThread(() =>
|
|
{
|
|
if (_pilotConfirmed)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_pilotConfirmed = true;
|
|
SetStatus(ConectadoPilotoLabel, "● Conectado", Color.Green);
|
|
AddLog("Se ha recibido un frame de audio válido de la radio del piloto.");
|
|
});
|
|
}
|
|
|
|
private void RadioController_Error(object? sender, string message)
|
|
{
|
|
AddLog(message);
|
|
}
|
|
|
|
private void RadioController_ConnectionFaulted(object? sender, EventArgs e)
|
|
{
|
|
RunOnUiThread(async () =>
|
|
{
|
|
if (_disconnectingAfterFault || !_radioController.IsConnected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_disconnectingAfterFault = true;
|
|
try
|
|
{
|
|
await DisconnectRadioAsync("La conexión se ha cerrado por un error.");
|
|
}
|
|
finally
|
|
{
|
|
_disconnectingAfterFault = false;
|
|
}
|
|
});
|
|
}
|
|
|
|
private async Task DisconnectRadioAsync(string logMessage)
|
|
{
|
|
StopPtt();
|
|
SetConnectionControlsEnabled(false);
|
|
|
|
try
|
|
{
|
|
await _radioController.DisconnectAsync();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AddLog($"Error al cerrar la conexión: {ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
ShowDisconnectedState();
|
|
AddLog(logMessage);
|
|
}
|
|
}
|
|
|
|
private void ShowDisconnectedState()
|
|
{
|
|
_pilotConfirmed = false;
|
|
SetStatus(ConectadoBoxLabel, "● Desconectado", Color.Gray);
|
|
SetStatus(ConectadoPilotoLabel, "● Desconectado", Color.Gray);
|
|
SetGeneralRadioStatus("● Desconectada", Color.Gray);
|
|
|
|
ConectarButton.Text = "Conectar";
|
|
ConectarButton.Enabled = true;
|
|
PuertoBox.Enabled = true;
|
|
ActualizarButton.Enabled = true;
|
|
PttButton.Enabled = false;
|
|
PttButton.Text = "Pulsar para hablar";
|
|
PttButton.UseVisualStyleBackColor = true;
|
|
PttButton.BackColor = SystemColors.Control;
|
|
}
|
|
|
|
private void SetConnectionControlsEnabled(bool enabled)
|
|
{
|
|
ConectarButton.Enabled = enabled;
|
|
PuertoBox.Enabled = enabled;
|
|
ActualizarButton.Enabled = enabled;
|
|
PttButton.Enabled = enabled && _radioController.IsConnected;
|
|
}
|
|
|
|
private static void SetStatus(Label label, string text, Color color)
|
|
{
|
|
label.Text = text;
|
|
label.ForeColor = color;
|
|
}
|
|
|
|
private void SetGeneralRadioStatus(string text, Color color)
|
|
{
|
|
label2.Text = text;
|
|
label2.ForeColor = color;
|
|
}
|
|
|
|
private void AddLog(string message)
|
|
{
|
|
RunOnUiThread(() =>
|
|
{
|
|
if (RegistrosTextBox.TextLength > 100_000)
|
|
{
|
|
RegistrosTextBox.Select(0, 20_000);
|
|
RegistrosTextBox.SelectedText = string.Empty;
|
|
}
|
|
|
|
RegistrosTextBox.AppendText(
|
|
$"[{DateTime.Now:HH:mm:ss}] {message}{Environment.NewLine}");
|
|
RegistrosTextBox.SelectionStart = RegistrosTextBox.TextLength;
|
|
RegistrosTextBox.ScrollToCaret();
|
|
});
|
|
}
|
|
|
|
private void RunOnUiThread(Action action)
|
|
{
|
|
if (IsDisposed || Disposing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (InvokeRequired)
|
|
{
|
|
try
|
|
{
|
|
BeginInvoke(action);
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// La ventana se está cerrando.
|
|
}
|
|
}
|
|
else
|
|
{
|
|
action();
|
|
}
|
|
}
|
|
|
|
private async void RadioMain_FormClosing(object? sender, FormClosingEventArgs e)
|
|
{
|
|
if (_closeReady)
|
|
{
|
|
return;
|
|
}
|
|
|
|
e.Cancel = true;
|
|
|
|
if (_closing)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_closing = true;
|
|
|
|
// La ventana desaparece al instante. La limpieza del audio y del puerto
|
|
// se realiza después, con un límite de tiempo para que la X nunca deje la
|
|
// aplicación bloqueada en pantalla.
|
|
StopPtt();
|
|
Enabled = false;
|
|
Hide();
|
|
|
|
Task disconnectTask = Task.Run(async () => await _radioController.DisconnectAsync());
|
|
|
|
try
|
|
{
|
|
await disconnectTask.WaitAsync(TimeSpan.FromSeconds(2));
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
// Cerramos igualmente la interfaz. La tarea de limpieza podrá acabar
|
|
// en segundo plano sin impedir que termine la aplicación.
|
|
}
|
|
catch (Exception)
|
|
{
|
|
// Durante el cierre no mostramos cuadros de error que puedan volver a
|
|
// bloquear la salida de la aplicación.
|
|
}
|
|
_closeReady = true;
|
|
Close();
|
|
}
|
|
}
|