Mirror a radio from another computer's broadcast data
Listen for Radio broadcast data from another Nonemm or N1MM computer and record its frequency, mode and split as this computer's own radio. No radio is controlled. The settings are in the Configure Radios dialog. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -118,6 +118,48 @@ public static class BroadcastMessages
|
||||
new XElement("AuxAntSelectedName", ""),
|
||||
new XElement("IsConnected", Flag(radio.IsConnected))).ToString();
|
||||
|
||||
/// Null for anything that is not a `RadioInfo` message with a frequency, so
|
||||
/// the other data on the same port is passed over.
|
||||
public static RadioBroadcast? ReadRadio(string xml)
|
||||
{
|
||||
XElement root;
|
||||
try
|
||||
{
|
||||
root = XElement.Parse(xml);
|
||||
}
|
||||
catch (System.Xml.XmlException)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (root.Name.LocalName != "RadioInfo"
|
||||
|| !long.TryParse(root.Element("Freq")?.Value, CultureInfo.InvariantCulture, out long frequency)
|
||||
|| !long.TryParse(root.Element("TXFreq")?.Value, CultureInfo.InvariantCulture, out long transmit)
|
||||
|| !int.TryParse(root.Element("RadioNr")?.Value, CultureInfo.InvariantCulture, out int number))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
string Text(string name) => root.Element(name)?.Value ?? "";
|
||||
bool Yes(string name) => bool.TryParse(Text(name), out bool value) && value;
|
||||
int.TryParse(Text("FocusRadioNr"), CultureInfo.InvariantCulture, out int focus);
|
||||
int.TryParse(Text("ActiveRadioNr"), CultureInfo.InvariantCulture, out int active);
|
||||
return new RadioBroadcast(
|
||||
Text("StationName"),
|
||||
new RadioReport(
|
||||
number,
|
||||
frequency * FrequencyUnit,
|
||||
transmit * FrequencyUnit,
|
||||
Text("Mode"),
|
||||
Text("mycall"),
|
||||
Text("OpCall"),
|
||||
Yes("IsRunning"),
|
||||
focus,
|
||||
Yes("IsSplit"),
|
||||
active,
|
||||
Yes("IsTransmitting"),
|
||||
Text("RadioName"),
|
||||
Yes("IsConnected")));
|
||||
}
|
||||
|
||||
/// `action` is `add` or `delete`. N1MM leaves the comment and status off a
|
||||
/// delete.
|
||||
public static string Spot(SpotReport spot, string action, string stationName)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace Nonemm.Network;
|
||||
|
||||
/// A `RadioInfo` message as received: which computer sent it, and what it says.
|
||||
public sealed record RadioBroadcast(string StationName, RadioReport Radio);
|
||||
@@ -0,0 +1,57 @@
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
|
||||
namespace Nonemm.Network;
|
||||
|
||||
/// Listens for the `RadioInfo` messages other copies of this program or N1MM
|
||||
/// broadcast, so this station can copy their radio when it has no serial port of its own.
|
||||
public sealed class RadioMirror : IDisposable
|
||||
{
|
||||
private readonly UdpClient listener = new() { ExclusiveAddressUse = false };
|
||||
private readonly CancellationTokenSource stopping = new();
|
||||
|
||||
/// Throws `SocketException` when the port is taken by a program that did
|
||||
/// not allow sharing it.
|
||||
public RadioMirror(int port)
|
||||
{
|
||||
listener.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
|
||||
listener.Client.Bind(new IPEndPoint(IPAddress.Any, port));
|
||||
_ = ReadAsync(stopping.Token);
|
||||
}
|
||||
|
||||
/// Raised on a thread of the pool.
|
||||
public event EventHandler<RadioBroadcast>? Received;
|
||||
|
||||
public event EventHandler<string>? Failed;
|
||||
|
||||
private async Task ReadAsync(CancellationToken cancellation)
|
||||
{
|
||||
while (!cancellation.IsCancellationRequested)
|
||||
{
|
||||
try
|
||||
{
|
||||
UdpReceiveResult result = await listener.ReceiveAsync(cancellation).ConfigureAwait(false);
|
||||
if (BroadcastMessages.ReadRadio(Encoding.UTF8.GetString(result.Buffer)) is { } message)
|
||||
{
|
||||
Received?.Invoke(this, message);
|
||||
}
|
||||
}
|
||||
catch (Exception e) when (e is OperationCanceledException or ObjectDisposedException)
|
||||
{
|
||||
return;
|
||||
}
|
||||
catch (SocketException e)
|
||||
{
|
||||
Failed?.Invoke(this, $"could not read radio data: {e.Message}");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
stopping.Cancel();
|
||||
listener.Dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user