From 70c16a563fbb8a10779cb54316472676cda7dbac Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sat, 24 Oct 2026 11:51:05 +0000 Subject: [PATCH] Measure a QSY spot from where the call was first seen The radio reports the frequency in small steps as the knob turns, and each step was compared with the one before, so no step passed the threshold. The move is now measured from the frequency the radio had when the call appeared in the entry window. The threshold is a box on the Filters tab of the telnet window. Co-Authored-By: Claude Sonnet 5.5 --- src/Nonemm.App/AppSession.QsySpot.cs | 25 ++++++++++++++++++- .../Windows/TelnetWindow.Filters.cs | 2 ++ src/Nonemm.App/Windows/TelnetWindow.axaml | 2 ++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Nonemm.App/AppSession.QsySpot.cs b/src/Nonemm.App/AppSession.QsySpot.cs index 6ccf3f4..7b0bac1 100644 --- a/src/Nonemm.App/AppSession.QsySpot.cs +++ b/src/Nonemm.App/AppSession.QsySpot.cs @@ -28,10 +28,32 @@ public sealed partial class AppSession Dispatcher.UIThread.Post(() => SpotOnQsy(position, before)); } - private void SpotOnQsy(OperatingPosition position, Frequency before) + /// Where the radio was when the call in each entry window was first seen. + /// A radio reports every few hundred hertz as the knob turns, so the move + /// is measured from here and not from the previous report. + private readonly Dictionary heardAt = []; + + private void SpotOnQsy(OperatingPosition position, Frequency reported) { string call = position.Entry.Call.Trim().ToUpperInvariant(); Frequency now = position.Frequency; + if (call.Length == 0) + { + heardAt.Remove(position); + return; + } + if (!heardAt.TryGetValue(position, out var anchor) || anchor.Call != call) + { + anchor = (call, reported); + heardAt[position] = anchor; + } + if (Bands.ForFrequency(anchor.At) != Bands.ForFrequency(now)) + { + // a band change starts again from the new band + heardAt[position] = (call, now); + return; + } + Frequency before = anchor.At; Band? band = Bands.ForFrequency(before); if (call.Length < ShortestCallToSpot || call.All(char.IsAsciiDigit) @@ -58,6 +80,7 @@ public sealed partial class AppSession SpotSource.Operator, position.Me.Callsign, $"Local spot by {Settings.CurrentOperator}"); + heardAt.Remove(position); Bandmap.Add(spot); SendSpots([spot]); _ = link?.SendSpotAsync(new SharedSpot( diff --git a/src/Nonemm.App/Windows/TelnetWindow.Filters.cs b/src/Nonemm.App/Windows/TelnetWindow.Filters.cs index 115ef3b..ecbbfc3 100644 --- a/src/Nonemm.App/Windows/TelnetWindow.Filters.cs +++ b/src/Nonemm.App/Windows/TelnetWindow.Filters.cs @@ -49,6 +49,7 @@ public sealed partial class TelnetWindow BlockedSpottersBox.Text = string.Join(' ', filter.BlockedSpotters); BlockedCallsBox.Text = string.Join(' ', filter.BlockedCalls); TimeoutBox.Text = session.Settings.SpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture); + QsySpotBox.Text = session.Settings.QsySpotHertz.ToString(CultureInfo.InvariantCulture); SpotCommentBox.Text = session.Settings.SpotComment; } @@ -72,6 +73,7 @@ public sealed partial class TelnetWindow }, RandomizeSpots = RandomizeBox.IsChecked == true, SpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.SpotTimeoutMinutes), + QsySpotHertz = Minutes(QsySpotBox.Text, session.Settings.QsySpotHertz), }); Show("*** filters applied — they hold for the spots that arrive from now on", NoticeColour); } diff --git a/src/Nonemm.App/Windows/TelnetWindow.axaml b/src/Nonemm.App/Windows/TelnetWindow.axaml index 14f56ac..4906e4f 100644 --- a/src/Nonemm.App/Windows/TelnetWindow.axaml +++ b/src/Nonemm.App/Windows/TelnetWindow.axaml @@ -122,6 +122,8 @@ + +