mirror of
https://github.com/lupettohf/kv4p-sharp.git
synced 2025-01-19 01:36:30 +08:00
Aggiungere i file di progetto.
This commit is contained in:
parent
6c675ed097
commit
cbfafc4252
19
RadioGUI/Program.cs
Normal file
19
RadioGUI/Program.cs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
using RadioControllerApp;
|
||||||
|
|
||||||
|
namespace RadioGUI
|
||||||
|
{
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The main entry point for the application.
|
||||||
|
/// </summary>
|
||||||
|
[STAThread]
|
||||||
|
static void Main()
|
||||||
|
{
|
||||||
|
// To customize application configuration such as set high DPI settings or default font,
|
||||||
|
// see https://aka.ms/applicationconfiguration.
|
||||||
|
ApplicationConfiguration.Initialize();
|
||||||
|
Application.Run(new FormRadio());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
615
RadioGUI/Radio.cs
Normal file
615
RadioGUI/Radio.cs
Normal file
|
@ -0,0 +1,615 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using NAudio.Wave;
|
||||||
|
|
||||||
|
namespace RadioControllerApp
|
||||||
|
{
|
||||||
|
public class FormRadio : Form
|
||||||
|
{
|
||||||
|
private RadioController radioController;
|
||||||
|
private WaveInEvent waveIn;
|
||||||
|
private BufferedWaveProvider receivedAudioBuffer;
|
||||||
|
private WaveOutEvent waveOut;
|
||||||
|
private bool isRecording = false;
|
||||||
|
|
||||||
|
// UI Controls
|
||||||
|
private TextBox txtPortName;
|
||||||
|
private Button btnOpenConnection;
|
||||||
|
private Button btnCloseConnection;
|
||||||
|
private Button btnInitialize;
|
||||||
|
|
||||||
|
private TextBox txtTXFrequency;
|
||||||
|
private TextBox txtRXFrequency;
|
||||||
|
private TextBox txtTone;
|
||||||
|
private TextBox txtSquelchLevel;
|
||||||
|
private Button btnTune;
|
||||||
|
|
||||||
|
private CheckBox chkEmphasis;
|
||||||
|
private CheckBox chkHighpass;
|
||||||
|
private CheckBox chkLowpass;
|
||||||
|
private Button btnSetFilters;
|
||||||
|
|
||||||
|
private Button btnStartRX;
|
||||||
|
private Button btnStartTX;
|
||||||
|
private Button btnEndTX;
|
||||||
|
private Button btnStop;
|
||||||
|
|
||||||
|
private Button btnStartRecording;
|
||||||
|
private Button btnStopRecording;
|
||||||
|
private Button btnPlayReceivedAudio;
|
||||||
|
|
||||||
|
private TextBox txtStatus;
|
||||||
|
|
||||||
|
public FormRadio()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
InitializeRadioController();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.AutoScaleMode = AutoScaleMode.Dpi; // Enable DPI scaling
|
||||||
|
|
||||||
|
// Form properties
|
||||||
|
this.Text = "Radio Controller";
|
||||||
|
this.Size = new System.Drawing.Size(800, 700);
|
||||||
|
this.MinimumSize = new System.Drawing.Size(600, 6<s00);
|
||||||
|
this.FormClosing += FormRadio_FormClosing;
|
||||||
|
|
||||||
|
// Initialize controls
|
||||||
|
InitializeControls();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeControls()
|
||||||
|
{
|
||||||
|
// Main TableLayoutPanel
|
||||||
|
TableLayoutPanel mainLayout = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ColumnCount = 1,
|
||||||
|
RowCount = 6,
|
||||||
|
AutoSize = true,
|
||||||
|
};
|
||||||
|
this.Controls.Add(mainLayout);
|
||||||
|
|
||||||
|
// Adjust row styles
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Connection Controls
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Frequency Controls
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Filter Controls
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Mode Controls
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); // Audio Controls
|
||||||
|
mainLayout.RowStyles.Add(new RowStyle(SizeType.Percent, 100F)); // Status TextBox
|
||||||
|
|
||||||
|
// Connection Controls GroupBox
|
||||||
|
GroupBox grpConnection = CreateConnectionControls();
|
||||||
|
grpConnection.Dock = DockStyle.Fill;
|
||||||
|
mainLayout.Controls.Add(grpConnection, 0, 0);
|
||||||
|
|
||||||
|
// Frequency Controls GroupBox
|
||||||
|
GroupBox grpFrequency = CreateFrequencyControls();
|
||||||
|
grpFrequency.Dock = DockStyle.Fill;
|
||||||
|
mainLayout.Controls.Add(grpFrequency, 0, 1);
|
||||||
|
|
||||||
|
// Filter Controls GroupBox
|
||||||
|
GroupBox grpFilters = CreateFilterControls();
|
||||||
|
grpFilters.Dock = DockStyle.Fill;
|
||||||
|
mainLayout.Controls.Add(grpFilters, 0, 2);
|
||||||
|
|
||||||
|
// Mode Controls GroupBox
|
||||||
|
GroupBox grpModes = CreateModeControls();
|
||||||
|
grpModes.Dock = DockStyle.Fill;
|
||||||
|
mainLayout.Controls.Add(grpModes, 0, 3);
|
||||||
|
|
||||||
|
// Audio Controls GroupBox
|
||||||
|
GroupBox grpAudio = CreateAudioControls();
|
||||||
|
grpAudio.Dock = DockStyle.Fill;
|
||||||
|
mainLayout.Controls.Add(grpAudio, 0, 4);
|
||||||
|
|
||||||
|
// Status TextBox
|
||||||
|
txtStatus = new TextBox
|
||||||
|
{
|
||||||
|
Multiline = true,
|
||||||
|
ReadOnly = true,
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ScrollBars = ScrollBars.Vertical
|
||||||
|
};
|
||||||
|
mainLayout.Controls.Add(txtStatus, 0, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupBox CreateConnectionControls()
|
||||||
|
{
|
||||||
|
GroupBox grpConnection = new GroupBox
|
||||||
|
{
|
||||||
|
Text = "Connection Controls",
|
||||||
|
AutoSize = true,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
TableLayoutPanel layout = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ColumnCount = 5,
|
||||||
|
AutoSize = true
|
||||||
|
};
|
||||||
|
grpConnection.Controls.Add(layout);
|
||||||
|
|
||||||
|
Label lblPortName = new Label { Text = "Port Name:", Anchor = AnchorStyles.Right };
|
||||||
|
txtPortName = new TextBox { Text = "COM3", Anchor = AnchorStyles.Left };
|
||||||
|
btnOpenConnection = new Button { Text = "Open Connection", AutoSize = true };
|
||||||
|
btnCloseConnection = new Button { Text = "Close Connection", AutoSize = true };
|
||||||
|
btnInitialize = new Button { Text = "Initialize", AutoSize = true };
|
||||||
|
|
||||||
|
btnOpenConnection.Click += btnOpenConnection_Click;
|
||||||
|
btnCloseConnection.Click += btnCloseConnection_Click;
|
||||||
|
btnInitialize.Click += btnInitialize_Click;
|
||||||
|
|
||||||
|
layout.Controls.Add(lblPortName, 0, 0);
|
||||||
|
layout.Controls.Add(txtPortName, 1, 0);
|
||||||
|
layout.Controls.Add(btnOpenConnection, 2, 0);
|
||||||
|
layout.Controls.Add(btnCloseConnection, 3, 0);
|
||||||
|
layout.Controls.Add(btnInitialize, 4, 0);
|
||||||
|
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); // Label
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); // TextBox
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33F)); // Button
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 33F)); // Button
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 34F)); // Button
|
||||||
|
|
||||||
|
return grpConnection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupBox CreateFrequencyControls()
|
||||||
|
{
|
||||||
|
GroupBox grpFrequency = new GroupBox
|
||||||
|
{
|
||||||
|
Text = "Frequency Controls",
|
||||||
|
AutoSize = true,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
TableLayoutPanel layout = new TableLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
ColumnCount = 6,
|
||||||
|
AutoSize = true
|
||||||
|
};
|
||||||
|
grpFrequency.Controls.Add(layout);
|
||||||
|
|
||||||
|
Label lblTXFrequency = new Label { Text = "TX Frequency:", Anchor = AnchorStyles.Right };
|
||||||
|
txtTXFrequency = new TextBox { Text = "146.520", Anchor = AnchorStyles.Left };
|
||||||
|
Label lblRXFrequency = new Label { Text = "RX Frequency:", Anchor = AnchorStyles.Right };
|
||||||
|
txtRXFrequency = new TextBox { Text = "146.520", Anchor = AnchorStyles.Left };
|
||||||
|
Label lblTone = new Label { Text = "Tone:", Anchor = AnchorStyles.Right };
|
||||||
|
txtTone = new TextBox { Text = "00", Anchor = AnchorStyles.Left };
|
||||||
|
Label lblSquelchLevel = new Label { Text = "Squelch Level:", Anchor = AnchorStyles.Right };
|
||||||
|
txtSquelchLevel = new TextBox { Text = "0", Anchor = AnchorStyles.Left };
|
||||||
|
btnTune = new Button { Text = "Tune to Frequency", AutoSize = true };
|
||||||
|
|
||||||
|
btnTune.Click += btnTune_Click;
|
||||||
|
|
||||||
|
layout.Controls.Add(lblTXFrequency, 0, 0);
|
||||||
|
layout.Controls.Add(txtTXFrequency, 1, 0);
|
||||||
|
layout.Controls.Add(lblRXFrequency, 2, 0);
|
||||||
|
layout.Controls.Add(txtRXFrequency, 3, 0);
|
||||||
|
layout.Controls.Add(lblTone, 4, 0);
|
||||||
|
layout.Controls.Add(txtTone, 5, 0);
|
||||||
|
|
||||||
|
layout.Controls.Add(lblSquelchLevel, 0, 1);
|
||||||
|
layout.Controls.Add(txtSquelchLevel, 1, 1);
|
||||||
|
layout.SetColumnSpan(btnTune, 4);
|
||||||
|
layout.Controls.Add(btnTune, 2, 1);
|
||||||
|
|
||||||
|
// Adjust column styles
|
||||||
|
for (int i = 0; i < 6; i += 2)
|
||||||
|
{
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.AutoSize)); // Label
|
||||||
|
layout.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 16.66F)); // TextBox
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpFrequency;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupBox CreateFilterControls()
|
||||||
|
{
|
||||||
|
GroupBox grpFilters = new GroupBox
|
||||||
|
{
|
||||||
|
Text = "Filter Controls",
|
||||||
|
AutoSize = true,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
FlowLayoutPanel layout = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
AutoSize = true,
|
||||||
|
WrapContents = false,
|
||||||
|
};
|
||||||
|
grpFilters.Controls.Add(layout);
|
||||||
|
|
||||||
|
chkEmphasis = new CheckBox { Text = "Emphasis", AutoSize = true };
|
||||||
|
chkHighpass = new CheckBox { Text = "Highpass", AutoSize = true };
|
||||||
|
chkLowpass = new CheckBox { Text = "Lowpass", AutoSize = true };
|
||||||
|
btnSetFilters = new Button { Text = "Set Filters", AutoSize = true };
|
||||||
|
|
||||||
|
btnSetFilters.Click += btnSetFilters_Click;
|
||||||
|
|
||||||
|
layout.Controls.Add(chkEmphasis);
|
||||||
|
layout.Controls.Add(chkHighpass);
|
||||||
|
layout.Controls.Add(chkLowpass);
|
||||||
|
layout.Controls.Add(btnSetFilters);
|
||||||
|
|
||||||
|
return grpFilters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupBox CreateModeControls()
|
||||||
|
{
|
||||||
|
GroupBox grpModes = new GroupBox
|
||||||
|
{
|
||||||
|
Text = "Mode Controls",
|
||||||
|
AutoSize = true,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
FlowLayoutPanel layout = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
AutoSize = true,
|
||||||
|
WrapContents = false,
|
||||||
|
};
|
||||||
|
grpModes.Controls.Add(layout);
|
||||||
|
|
||||||
|
btnStartRX = new Button { Text = "Start RX Mode", AutoSize = true };
|
||||||
|
btnStartTX = new Button { Text = "Start TX Mode", AutoSize = true };
|
||||||
|
btnEndTX = new Button { Text = "End TX Mode", AutoSize = true };
|
||||||
|
btnStop = new Button { Text = "Stop", AutoSize = true };
|
||||||
|
|
||||||
|
btnStartRX.Click += btnStartRX_Click;
|
||||||
|
btnStartTX.Click += btnStartTX_Click;
|
||||||
|
btnEndTX.Click += btnEndTX_Click;
|
||||||
|
btnStop.Click += btnStop_Click;
|
||||||
|
|
||||||
|
layout.Controls.Add(btnStartRX);
|
||||||
|
layout.Controls.Add(btnStartTX);
|
||||||
|
layout.Controls.Add(btnEndTX);
|
||||||
|
layout.Controls.Add(btnStop);
|
||||||
|
|
||||||
|
return grpModes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupBox CreateAudioControls()
|
||||||
|
{
|
||||||
|
GroupBox grpAudio = new GroupBox
|
||||||
|
{
|
||||||
|
Text = "Audio Controls",
|
||||||
|
AutoSize = true,
|
||||||
|
Dock = DockStyle.Fill
|
||||||
|
};
|
||||||
|
|
||||||
|
FlowLayoutPanel layout = new FlowLayoutPanel
|
||||||
|
{
|
||||||
|
Dock = DockStyle.Fill,
|
||||||
|
AutoSize = true,
|
||||||
|
WrapContents = false,
|
||||||
|
};
|
||||||
|
grpAudio.Controls.Add(layout);
|
||||||
|
|
||||||
|
btnStartRecording = new Button { Text = "Start Recording", AutoSize = true };
|
||||||
|
btnStopRecording = new Button { Text = "Stop Recording", AutoSize = true };
|
||||||
|
btnPlayReceivedAudio = new Button { Text = "Play Received Audio", AutoSize = true };
|
||||||
|
|
||||||
|
btnStartRecording.Click += btnStartRecording_Click;
|
||||||
|
btnStopRecording.Click += btnStopRecording_Click;
|
||||||
|
btnPlayReceivedAudio.Click += btnPlayReceivedAudio_Click;
|
||||||
|
|
||||||
|
layout.Controls.Add(btnStartRecording);
|
||||||
|
layout.Controls.Add(btnStopRecording);
|
||||||
|
layout.Controls.Add(btnPlayReceivedAudio);
|
||||||
|
|
||||||
|
return grpAudio;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeRadioController()
|
||||||
|
{
|
||||||
|
// Disable controls that require an open connection
|
||||||
|
ToggleControls(false);
|
||||||
|
|
||||||
|
receivedAudioBuffer = new BufferedWaveProvider(new WaveFormat(44100, 16, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnOpenConnection_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string portName = txtPortName.Text.Trim();
|
||||||
|
if (string.IsNullOrEmpty(portName))
|
||||||
|
{
|
||||||
|
AppendStatus("Please enter a valid port name.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
radioController = new RadioController(portName);
|
||||||
|
radioController.ErrorOccurred += RadioController_ErrorOccurred;
|
||||||
|
radioController.AudioDataReceived += RadioController_AudioDataReceived;
|
||||||
|
radioController.OpenConnection();
|
||||||
|
|
||||||
|
AppendStatus($"Connection opened on {portName}.");
|
||||||
|
ToggleControls(true);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error opening connection: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnCloseConnection_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (radioController != null)
|
||||||
|
{
|
||||||
|
radioController.CloseConnection();
|
||||||
|
radioController.Dispose();
|
||||||
|
radioController = null;
|
||||||
|
AppendStatus("Connection closed.");
|
||||||
|
ToggleControls(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error closing connection: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnInitialize_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
radioController.Initialize();
|
||||||
|
AppendStatus("Radio initialized.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error initializing radio: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnTune_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string txFreq = txtTXFrequency.Text.Trim();
|
||||||
|
string rxFreq = txtRXFrequency.Text.Trim();
|
||||||
|
int tone = int.Parse(txtTone.Text.Trim());
|
||||||
|
int squelch = int.Parse(txtSquelchLevel.Text.Trim());
|
||||||
|
|
||||||
|
radioController.TuneToFrequency(txFreq, rxFreq, tone, squelch);
|
||||||
|
AppendStatus($"Tuned to TX: {txFreq}, RX: {rxFreq}, Tone: {tone}, Squelch Level: {squelch}");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error tuning frequency: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnSetFilters_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool emphasis = chkEmphasis.Checked;
|
||||||
|
bool highpass = chkHighpass.Checked;
|
||||||
|
bool lowpass = chkLowpass.Checked;
|
||||||
|
|
||||||
|
radioController.SetFilters(emphasis, highpass, lowpass);
|
||||||
|
AppendStatus("Filters set.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error setting filters: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStartRX_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
radioController.StartRXMode();
|
||||||
|
AppendStatus("Started RX mode.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error starting RX mode: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStartTX_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
radioController.StartTXMode();
|
||||||
|
AppendStatus("Started TX mode.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error starting TX mode: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnEndTX_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
radioController.EndTXMode();
|
||||||
|
AppendStatus("Ended TX mode.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error ending TX mode: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStop_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
radioController.Stop();
|
||||||
|
AppendStatus("Radio stopped.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error stopping radio: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStartRecording_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (radioController == null)
|
||||||
|
{
|
||||||
|
AppendStatus("Please open the connection first.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
radioController.StartTXMode();
|
||||||
|
isRecording = true;
|
||||||
|
|
||||||
|
waveIn = new WaveInEvent();
|
||||||
|
waveIn.WaveFormat = new WaveFormat(44100, 16, 1);
|
||||||
|
waveIn.DataAvailable += WaveIn_DataAvailable;
|
||||||
|
waveIn.StartRecording();
|
||||||
|
|
||||||
|
AppendStatus("Recording started.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error starting recording: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnStopRecording_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isRecording && waveIn != null)
|
||||||
|
{
|
||||||
|
waveIn.StopRecording();
|
||||||
|
waveIn.Dispose();
|
||||||
|
waveIn = null;
|
||||||
|
isRecording = false;
|
||||||
|
|
||||||
|
radioController.EndTXMode();
|
||||||
|
AppendStatus("Recording stopped.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error stopping recording: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void WaveIn_DataAvailable(object sender, WaveInEventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isRecording && radioController != null)
|
||||||
|
{
|
||||||
|
byte[] buffer = new byte[e.BytesRecorded];
|
||||||
|
Array.Copy(e.Buffer, buffer, e.BytesRecorded);
|
||||||
|
|
||||||
|
// Convert audio data to the required format if necessary
|
||||||
|
await radioController.SendAudioDataAsync(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error sending audio data: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnPlayReceivedAudio_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (waveOut == null)
|
||||||
|
{
|
||||||
|
waveOut = new WaveOutEvent();
|
||||||
|
waveOut.Init(receivedAudioBuffer);
|
||||||
|
}
|
||||||
|
waveOut.Play();
|
||||||
|
|
||||||
|
AppendStatus("Playing received audio.");
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error playing received audio: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RadioController_ErrorOccurred(object sender, ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
AppendStatus($"Error: {e.GetException().Message}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RadioController_AudioDataReceived(object sender, byte[] data)
|
||||||
|
{
|
||||||
|
// Buffer the received audio data
|
||||||
|
receivedAudioBuffer.AddSamples(data, 0, data.Length);
|
||||||
|
AppendStatus($"Audio data received. Length: {data.Length} bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendStatus(string message)
|
||||||
|
{
|
||||||
|
if (txtStatus.InvokeRequired)
|
||||||
|
{
|
||||||
|
txtStatus.Invoke(new Action(() => AppendStatus(message)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
txtStatus.AppendText($"{DateTime.Now}: {message}{Environment.NewLine}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ToggleControls(bool isEnabled)
|
||||||
|
{
|
||||||
|
btnInitialize.Enabled = isEnabled;
|
||||||
|
btnTune.Enabled = isEnabled;
|
||||||
|
btnSetFilters.Enabled = isEnabled;
|
||||||
|
btnStartRX.Enabled = isEnabled;
|
||||||
|
btnStartTX.Enabled = isEnabled;
|
||||||
|
btnEndTX.Enabled = isEnabled;
|
||||||
|
btnStop.Enabled = isEnabled;
|
||||||
|
|
||||||
|
btnStartRecording.Enabled = isEnabled;
|
||||||
|
btnStopRecording.Enabled = isEnabled;
|
||||||
|
btnPlayReceivedAudio.Enabled = isEnabled;
|
||||||
|
|
||||||
|
// Disable connection buttons appropriately
|
||||||
|
btnOpenConnection.Enabled = !isEnabled;
|
||||||
|
btnCloseConnection.Enabled = isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FormRadio_FormClosing(object sender, FormClosingEventArgs e)
|
||||||
|
{
|
||||||
|
// Clean up resources
|
||||||
|
if (radioController != null)
|
||||||
|
{
|
||||||
|
radioController.Dispose();
|
||||||
|
radioController = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waveIn != null)
|
||||||
|
{
|
||||||
|
waveIn.Dispose();
|
||||||
|
waveIn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (waveOut != null)
|
||||||
|
{
|
||||||
|
waveOut.Dispose();
|
||||||
|
waveOut = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
120
RadioGUI/Radio.resx
Normal file
120
RadioGUI/Radio.resx
Normal file
|
@ -0,0 +1,120 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<root>
|
||||||
|
<!--
|
||||||
|
Microsoft ResX Schema
|
||||||
|
|
||||||
|
Version 2.0
|
||||||
|
|
||||||
|
The primary goals of this format is to allow a simple XML format
|
||||||
|
that is mostly human readable. The generation and parsing of the
|
||||||
|
various data types are done through the TypeConverter classes
|
||||||
|
associated with the data types.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
... ado.net/XML headers & schema ...
|
||||||
|
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||||
|
<resheader name="version">2.0</resheader>
|
||||||
|
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||||
|
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||||
|
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||||
|
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||||
|
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||||
|
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||||
|
</data>
|
||||||
|
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||||
|
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||||
|
<comment>This is a comment</comment>
|
||||||
|
</data>
|
||||||
|
|
||||||
|
There are any number of "resheader" rows that contain simple
|
||||||
|
name/value pairs.
|
||||||
|
|
||||||
|
Each data row contains a name, and value. The row also contains a
|
||||||
|
type or mimetype. Type corresponds to a .NET class that support
|
||||||
|
text/value conversion through the TypeConverter architecture.
|
||||||
|
Classes that don't support this are serialized and stored with the
|
||||||
|
mimetype set.
|
||||||
|
|
||||||
|
The mimetype is used for serialized objects, and tells the
|
||||||
|
ResXResourceReader how to depersist the object. This is currently not
|
||||||
|
extensible. For a given mimetype the value must be set accordingly:
|
||||||
|
|
||||||
|
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||||
|
that the ResXResourceWriter will generate, however the reader can
|
||||||
|
read any of the formats listed below.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.binary.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.soap.base64
|
||||||
|
value : The object must be serialized with
|
||||||
|
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
|
||||||
|
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||||
|
value : The object must be serialized into a byte array
|
||||||
|
: using a System.ComponentModel.TypeConverter
|
||||||
|
: and then encoded with base64 encoding.
|
||||||
|
-->
|
||||||
|
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||||
|
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||||
|
<xsd:element name="root" msdata:IsDataSet="true">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:choice maxOccurs="unbounded">
|
||||||
|
<xsd:element name="metadata">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="assembly">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:attribute name="alias" type="xsd:string" />
|
||||||
|
<xsd:attribute name="name" type="xsd:string" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="data">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||||
|
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||||
|
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||||
|
<xsd:attribute ref="xml:space" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
<xsd:element name="resheader">
|
||||||
|
<xsd:complexType>
|
||||||
|
<xsd:sequence>
|
||||||
|
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||||
|
</xsd:sequence>
|
||||||
|
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:choice>
|
||||||
|
</xsd:complexType>
|
||||||
|
</xsd:element>
|
||||||
|
</xsd:schema>
|
||||||
|
<resheader name="resmimetype">
|
||||||
|
<value>text/microsoft-resx</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="version">
|
||||||
|
<value>2.0</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="reader">
|
||||||
|
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
<resheader name="writer">
|
||||||
|
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||||
|
</resheader>
|
||||||
|
</root>
|
19
RadioGUI/RadioGUI.csproj
Normal file
19
RadioGUI/RadioGUI.csproj
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="NAudio" Version="2.2.1" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\kv4p-sharp\kv4p-sharp-lib.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
31
kv4p-sharp.sln
Normal file
31
kv4p-sharp.sln
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.11.35312.102
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kv4p-sharp-lib", "kv4p-sharp\kv4p-sharp-lib.csproj", "{321A4BEA-94AB-4345-91EA-A603180603D4}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RadioGUI", "RadioGUI\RadioGUI.csproj", "{833C55A9-5DE4-4B46-8F4A-63EA59325E17}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{321A4BEA-94AB-4345-91EA-A603180603D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{321A4BEA-94AB-4345-91EA-A603180603D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{321A4BEA-94AB-4345-91EA-A603180603D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{321A4BEA-94AB-4345-91EA-A603180603D4}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{833C55A9-5DE4-4B46-8F4A-63EA59325E17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{833C55A9-5DE4-4B46-8F4A-63EA59325E17}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{833C55A9-5DE4-4B46-8F4A-63EA59325E17}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{833C55A9-5DE4-4B46-8F4A-63EA59325E17}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {19C17713-1168-468F-9761-FAC3257E0E68}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
397
kv4p-sharp/RadioController.cs
Normal file
397
kv4p-sharp/RadioController.cs
Normal file
|
@ -0,0 +1,397 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
public class RadioController : IDisposable
|
||||||
|
{
|
||||||
|
private SerialPort serialPort;
|
||||||
|
private const int baudRate = 921600;
|
||||||
|
private const int dataBits = 8;
|
||||||
|
private const Parity parity = Parity.None;
|
||||||
|
private const StopBits stopBits = StopBits.One;
|
||||||
|
private const int readTimeout = 1000;
|
||||||
|
private const int writeTimeout = 1000;
|
||||||
|
|
||||||
|
// Delimiter must match ESP32 code
|
||||||
|
private static readonly byte[] COMMAND_DELIMITER = new byte[]
|
||||||
|
{
|
||||||
|
0xFF, 0x00, 0xFF, 0x00,
|
||||||
|
0xFF, 0x00, 0xFF, 0x00
|
||||||
|
};
|
||||||
|
|
||||||
|
// Command bytes
|
||||||
|
private enum ESP32Command : byte
|
||||||
|
{
|
||||||
|
PTT_DOWN = 1,
|
||||||
|
PTT_UP = 2,
|
||||||
|
TUNE_TO = 3,
|
||||||
|
FILTERS = 4,
|
||||||
|
STOP = 5,
|
||||||
|
GET_FIRMWARE_VER = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum RadioMode
|
||||||
|
{
|
||||||
|
STARTUP,
|
||||||
|
RX,
|
||||||
|
TX,
|
||||||
|
SCAN
|
||||||
|
}
|
||||||
|
|
||||||
|
private RadioMode currentMode = RadioMode.STARTUP;
|
||||||
|
private const int MIN_FIRMWARE_VER = 1;
|
||||||
|
private string versionStrBuffer = "";
|
||||||
|
|
||||||
|
private const int AUDIO_SAMPLE_RATE = 44100;
|
||||||
|
|
||||||
|
// Synchronization locks
|
||||||
|
private readonly object _syncLock = new object();
|
||||||
|
private readonly object _versionStrBufferLock = new object();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when an error is encountered.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<ErrorEventArgs> ErrorOccurred;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Occurs when audio data is received in RX mode.
|
||||||
|
/// </summary>
|
||||||
|
public event EventHandler<byte[]> AudioDataReceived;
|
||||||
|
|
||||||
|
public RadioController(string portName)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(portName))
|
||||||
|
throw new ArgumentException("Port name cannot be null or empty.", nameof(portName));
|
||||||
|
|
||||||
|
serialPort = new SerialPort(portName, baudRate, parity, dataBits, stopBits)
|
||||||
|
{
|
||||||
|
ReadTimeout = readTimeout,
|
||||||
|
WriteTimeout = writeTimeout
|
||||||
|
};
|
||||||
|
serialPort.DataReceived += SerialPort_DataReceived;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenConnection()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (!serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
serialPort.Open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CloseConnection()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
serialPort.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
currentMode = RadioMode.STARTUP;
|
||||||
|
}
|
||||||
|
SendCommand(ESP32Command.STOP);
|
||||||
|
SendCommand(ESP32Command.GET_FIRMWARE_VER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartRXMode()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
currentMode = RadioMode.RX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void StartTXMode()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
currentMode = RadioMode.TX;
|
||||||
|
SendCommand(ESP32Command.PTT_DOWN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EndTXMode()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (currentMode == RadioMode.TX)
|
||||||
|
{
|
||||||
|
SendCommand(ESP32Command.PTT_UP);
|
||||||
|
currentMode = RadioMode.RX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
currentMode = RadioMode.RX;
|
||||||
|
}
|
||||||
|
SendCommand(ESP32Command.STOP);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Tunes the radio to the specified frequencies with tone and squelch level.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="txFrequencyStr">Transmit frequency as a string (e.g., "146.520").</param>
|
||||||
|
/// <param name="rxFrequencyStr">Receive frequency as a string (e.g., "146.520").</param>
|
||||||
|
/// <param name="tone">Tone value as an integer (00 to 99).</param>
|
||||||
|
/// <param name="squelchLevel">Squelch level as an integer (0 to 9).</param>
|
||||||
|
public void TuneToFrequency(string txFrequencyStr, string rxFrequencyStr, int tone, int squelchLevel)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(txFrequencyStr))
|
||||||
|
throw new ArgumentException("Transmit frequency cannot be null or empty.", nameof(txFrequencyStr));
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(rxFrequencyStr))
|
||||||
|
throw new ArgumentException("Receive frequency cannot be null or empty.", nameof(rxFrequencyStr));
|
||||||
|
|
||||||
|
txFrequencyStr = MakeSafe2MFreq(txFrequencyStr);
|
||||||
|
rxFrequencyStr = MakeSafe2MFreq(rxFrequencyStr);
|
||||||
|
|
||||||
|
string toneStr = tone.ToString("00");
|
||||||
|
string squelchStr = squelchLevel.ToString();
|
||||||
|
|
||||||
|
// Ensure squelch level is a single digit
|
||||||
|
if (squelchStr.Length != 1)
|
||||||
|
throw new ArgumentException("Squelch level must be a single digit (0-9).", nameof(squelchLevel));
|
||||||
|
|
||||||
|
// Build parameters string
|
||||||
|
string paramsStr = txFrequencyStr + rxFrequencyStr + toneStr + squelchStr;
|
||||||
|
SendCommand(ESP32Command.TUNE_TO, paramsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetFilters(bool emphasis, bool highpass, bool lowpass)
|
||||||
|
{
|
||||||
|
string paramsStr = (emphasis ? "1" : "0") + (highpass ? "1" : "0") + (lowpass ? "1" : "0");
|
||||||
|
SendCommand(ESP32Command.FILTERS, paramsStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendAudioDataAsync(byte[] audioData, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (currentMode != RadioMode.TX)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int chunkSize = 512;
|
||||||
|
Stopwatch stopwatch = new Stopwatch();
|
||||||
|
|
||||||
|
for (int i = 0; i < audioData.Length; i += chunkSize)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
int remaining = audioData.Length - i;
|
||||||
|
int size = remaining > chunkSize ? chunkSize : remaining;
|
||||||
|
byte[] chunk = new byte[size];
|
||||||
|
Array.Copy(audioData, i, chunk, 0, size);
|
||||||
|
|
||||||
|
stopwatch.Restart();
|
||||||
|
SendBytesToESP32(chunk);
|
||||||
|
stopwatch.Stop();
|
||||||
|
|
||||||
|
// Calculate the expected delay
|
||||||
|
double expectedDelay = (double)size / AUDIO_SAMPLE_RATE * 1000.0; // in milliseconds
|
||||||
|
double elapsedTime = stopwatch.Elapsed.TotalMilliseconds;
|
||||||
|
int delay = (int)(expectedDelay - elapsedTime);
|
||||||
|
|
||||||
|
if (delay > 0)
|
||||||
|
{
|
||||||
|
await Task.Delay(delay, cancellationToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
|
||||||
|
{
|
||||||
|
byte[] receivedData = null;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
int bytesToRead = serialPort.BytesToRead;
|
||||||
|
receivedData = new byte[bytesToRead];
|
||||||
|
serialPort.Read(receivedData, 0, bytesToRead);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OnErrorOccurred(new ErrorEventArgs(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (receivedData != null && receivedData.Length > 0)
|
||||||
|
{
|
||||||
|
HandleData(receivedData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleData(byte[] data)
|
||||||
|
{
|
||||||
|
RadioMode mode;
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
mode = currentMode;
|
||||||
|
}
|
||||||
|
if (mode == RadioMode.RX)
|
||||||
|
{
|
||||||
|
// Raise an event with the received audio data
|
||||||
|
OnAudioDataReceived(data);
|
||||||
|
}
|
||||||
|
else if (mode == RadioMode.STARTUP)
|
||||||
|
{
|
||||||
|
// Handle firmware version check
|
||||||
|
string dataStr = System.Text.Encoding.UTF8.GetString(data);
|
||||||
|
lock (_versionStrBufferLock)
|
||||||
|
{
|
||||||
|
versionStrBuffer += dataStr;
|
||||||
|
if (versionStrBuffer.Contains("VERSION"))
|
||||||
|
{
|
||||||
|
int startIdx = versionStrBuffer.IndexOf("VERSION") + "VERSION".Length;
|
||||||
|
if (versionStrBuffer.Length >= startIdx + 8)
|
||||||
|
{
|
||||||
|
string verStr = versionStrBuffer.Substring(startIdx, 8);
|
||||||
|
if (int.TryParse(verStr, out int verInt))
|
||||||
|
{
|
||||||
|
if (verInt < MIN_FIRMWARE_VER)
|
||||||
|
{
|
||||||
|
OnErrorOccurred(new ErrorEventArgs(new InvalidOperationException("Unsupported firmware version.")));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
currentMode = RadioMode.RX;
|
||||||
|
}
|
||||||
|
// No need to initialize audio playback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnErrorOccurred(new ErrorEventArgs(new FormatException("Invalid firmware version format.")));
|
||||||
|
}
|
||||||
|
versionStrBuffer = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCommand(ESP32Command command)
|
||||||
|
{
|
||||||
|
byte[] commandArray = new byte[COMMAND_DELIMITER.Length + 1];
|
||||||
|
Array.Copy(COMMAND_DELIMITER, commandArray, COMMAND_DELIMITER.Length);
|
||||||
|
commandArray[COMMAND_DELIMITER.Length] = (byte)command;
|
||||||
|
SendBytesToESP32(commandArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendCommand(ESP32Command command, string paramsStr)
|
||||||
|
{
|
||||||
|
byte[] paramsBytes = System.Text.Encoding.ASCII.GetBytes(paramsStr);
|
||||||
|
byte[] commandArray = new byte[COMMAND_DELIMITER.Length + 1 + paramsBytes.Length];
|
||||||
|
Array.Copy(COMMAND_DELIMITER, commandArray, COMMAND_DELIMITER.Length);
|
||||||
|
commandArray[COMMAND_DELIMITER.Length] = (byte)command;
|
||||||
|
Array.Copy(paramsBytes, 0, commandArray, COMMAND_DELIMITER.Length + 1, paramsBytes.Length);
|
||||||
|
SendBytesToESP32(commandArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SendBytesToESP32(byte[] data)
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
if (serialPort.IsOpen)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
serialPort.Write(data, 0, data.Length);
|
||||||
|
}
|
||||||
|
catch (TimeoutException ex)
|
||||||
|
{
|
||||||
|
OnErrorOccurred(new ErrorEventArgs(ex));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
OnErrorOccurred(new ErrorEventArgs(ex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string MakeSafe2MFreq(string strFreq)
|
||||||
|
{
|
||||||
|
// Implement frequency validation and formatting as needed
|
||||||
|
if (!float.TryParse(strFreq, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float freq))
|
||||||
|
{
|
||||||
|
freq = 146.520f; // Default frequency
|
||||||
|
}
|
||||||
|
|
||||||
|
while (freq > 148.0f)
|
||||||
|
{
|
||||||
|
freq /= 10f;
|
||||||
|
}
|
||||||
|
|
||||||
|
freq = Math.Min(freq, 148.0f);
|
||||||
|
freq = Math.Max(freq, 144.0f);
|
||||||
|
|
||||||
|
string formattedFreq = freq.ToString("000.000", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
return formattedFreq;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnErrorOccurred(ErrorEventArgs e)
|
||||||
|
{
|
||||||
|
ErrorOccurred?.Invoke(this, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnAudioDataReceived(byte[] data)
|
||||||
|
{
|
||||||
|
AudioDataReceived?.Invoke(this, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region IDisposable Support
|
||||||
|
|
||||||
|
private bool disposedValue = false; // To detect redundant calls
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (!disposedValue)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
lock (_syncLock)
|
||||||
|
{
|
||||||
|
CloseConnection();
|
||||||
|
serialPort?.Dispose();
|
||||||
|
serialPort = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
disposedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~RadioController()
|
||||||
|
{
|
||||||
|
Dispose(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
14
kv4p-sharp/kv4p-sharp-lib.csproj
Normal file
14
kv4p-sharp/kv4p-sharp-lib.csproj
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<RootNamespace>kv4p_sharp</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.IO.Ports" Version="8.0.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
Loading…
Reference in New Issue
Block a user