From 9d0de3b1292e2588fa20a07b8e0be011e1b883be Mon Sep 17 00:00:00 2001 From: ericek111 Date: Sat, 24 Oct 2026 12:27:55 +0000 Subject: [PATCH] Set the bandmap spot timeout separately for CW/digital and phone CW and digital spots time out after 10 minutes and phone spots after 30 by default, both settings on the telnet Filters tab. The mode comes from the band plan; a frequency it has no boundaries for counts as phone. Co-Authored-By: Claude Sonnet 5.5 --- src/Nonemm.App/AppSession.cs | 6 +++++- src/Nonemm.App/Configuration/Settings.cs | 8 ++++++-- src/Nonemm.App/Windows/TelnetWindow.Filters.cs | 6 ++++-- src/Nonemm.App/Windows/TelnetWindow.axaml | 4 +++- src/Nonemm.Spotting/Bandmap.cs | 11 ++++++++--- tests/Nonemm.Spotting.Tests/BandmapTests.cs | 15 +++++++++++++++ 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/src/Nonemm.App/AppSession.cs b/src/Nonemm.App/AppSession.cs index c592131..ae24741 100644 --- a/src/Nonemm.App/AppSession.cs +++ b/src/Nonemm.App/AppSession.cs @@ -1039,7 +1039,11 @@ public sealed partial class AppSession : IDisposable private void ApplySpotSettings() { spotFilter = Settings.SpotFilter.ToFilter(BandPlan, Settings.Station, Countries, Calls, History); - Bandmap.Lifetime = TimeSpan.FromMinutes(Math.Max(1, Settings.SpotTimeoutMinutes)); + Bandmap.LifetimeFor = spot => TimeSpan.FromMinutes(Math.Max( + 1, + BandPlan.ModeAt(spot.Frequency) is ModeCategory.Cw or ModeCategory.Digital + ? Settings.CwDigitalSpotTimeoutMinutes + : Settings.PhoneSpotTimeoutMinutes)); foreach (OperatingPosition position in positions) { position.CqLeaveHertz = Settings.CqLeaveHertz; diff --git a/src/Nonemm.App/Configuration/Settings.cs b/src/Nonemm.App/Configuration/Settings.cs index b955c1f..d375ee2 100644 --- a/src/Nonemm.App/Configuration/Settings.cs +++ b/src/Nonemm.App/Configuration/Settings.cs @@ -50,8 +50,12 @@ public sealed record Settings /// Which cluster spots reach the bandmap. public StoredSpotFilter SpotFilter { get; init; } = new(); - /// How long a spot stays on the bandmap. N1MM's own default is 60. - public int SpotTimeoutMinutes { get; init; } = 60; + /// How long a CW or digital spot stays on the bandmap. + public int CwDigitalSpotTimeoutMinutes { get; init; } = 10; + + /// How long a phone spot stays on the bandmap. A frequency with no + /// boundaries in the band plan counts as phone. + public int PhoneSpotTimeoutMinutes { get; init; } = 30; /// The width of the slice the bandmap shows, in hertz. Zero is the whole band /// and minus one is the CW, digital or phone part the receiver is in. diff --git a/src/Nonemm.App/Windows/TelnetWindow.Filters.cs b/src/Nonemm.App/Windows/TelnetWindow.Filters.cs index 20e784f..898778c 100644 --- a/src/Nonemm.App/Windows/TelnetWindow.Filters.cs +++ b/src/Nonemm.App/Windows/TelnetWindow.Filters.cs @@ -48,7 +48,8 @@ public sealed partial class TelnetWindow CallAreasBox.Text = string.Join(' ', filter.CallAreas); BlockedSpottersBox.Text = string.Join(' ', filter.BlockedSpotters); BlockedCallsBox.Text = string.Join(' ', filter.BlockedCalls); - TimeoutBox.Text = session.Settings.SpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture); + TimeoutBox.Text = session.Settings.CwDigitalSpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture); + PhoneTimeoutBox.Text = session.Settings.PhoneSpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture); QsySpotBox.Text = session.Settings.QsySpotHertz.ToString(CultureInfo.InvariantCulture); RecentSpotBox.Text = session.Settings.RecentContactSpotMinutes.ToString(CultureInfo.InvariantCulture); CqLeaveBox.Text = session.Settings.CqLeaveHertz.ToString(CultureInfo.InvariantCulture); @@ -74,7 +75,8 @@ public sealed partial class TelnetWindow PreferredSpotters = Words(PreferredSpottersBox.Text), }, RandomizeSpots = RandomizeBox.IsChecked == true, - SpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.SpotTimeoutMinutes), + CwDigitalSpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.CwDigitalSpotTimeoutMinutes), + PhoneSpotTimeoutMinutes = Minutes(PhoneTimeoutBox.Text, session.Settings.PhoneSpotTimeoutMinutes), QsySpotHertz = Minutes(QsySpotBox.Text, session.Settings.QsySpotHertz), RecentContactSpotMinutes = int.TryParse(RecentSpotBox.Text, out int recent) && recent >= 0 ? recent diff --git a/src/Nonemm.App/Windows/TelnetWindow.axaml b/src/Nonemm.App/Windows/TelnetWindow.axaml index 349e90a..1419c61 100644 --- a/src/Nonemm.App/Windows/TelnetWindow.axaml +++ b/src/Nonemm.App/Windows/TelnetWindow.axaml @@ -120,8 +120,10 @@ - + + + diff --git a/src/Nonemm.Spotting/Bandmap.cs b/src/Nonemm.Spotting/Bandmap.cs index 710b1fa..ccdecb5 100644 --- a/src/Nonemm.Spotting/Bandmap.cs +++ b/src/Nonemm.Spotting/Bandmap.cs @@ -26,10 +26,14 @@ public sealed class Bandmap this.clock = clock ?? (() => DateTime.UtcNow); } - /// How long a spot stays on the map. N1MM asks for the same number in - /// minutes, on the telnet window's Filters tab. + /// How long a spot stays on the map when `LifetimeFor` is not set. public TimeSpan Lifetime { get; set; } + /// How long a given spot stays on the map, for a different time in each + /// mode. N1MM asks for one number in minutes, on the telnet window's + /// Filters tab. + public Func? LifetimeFor { get; set; } + public event EventHandler? Changed; public void Add(Spot spot) @@ -85,7 +89,8 @@ public sealed class Bandmap lock (guard) { List stale = spots - .Where(pair => nowUtc - arrived.GetValueOrDefault(pair.Key, nowUtc) > Lifetime) + .Where(pair => nowUtc - arrived.GetValueOrDefault(pair.Key, nowUtc) + > (LifetimeFor?.Invoke(pair.Value) ?? Lifetime)) .Select(pair => pair.Key) .ToList(); foreach (string key in stale) diff --git a/tests/Nonemm.Spotting.Tests/BandmapTests.cs b/tests/Nonemm.Spotting.Tests/BandmapTests.cs index af6d85a..c314e1b 100644 --- a/tests/Nonemm.Spotting.Tests/BandmapTests.cs +++ b/tests/Nonemm.Spotting.Tests/BandmapTests.cs @@ -18,6 +18,21 @@ public class BandmapTests Assert.Equal(["JA1XYZ", "DL1ABC"], map.On(Bands.Band20M).Select(s => s.Call.Text)); } + [Fact] + public void EachSpotLivesAsLongAsItsOwnLifetime() + { + Bandmap map = new(clock: () => Now) + { + LifetimeFor = spot => TimeSpan.FromMinutes(spot.Frequency.Kilohertz < 14_100 ? 10 : 30), + }; + map.Add(At("DL1ABC", 14_025)); + map.Add(At("JA1XYZ", 14_200)); + + map.DropOlderThan(Now.AddMinutes(15)); + + Assert.Equal(["JA1XYZ"], map.All().Select(s => s.Call.Text)); + } + [Fact] public void AStationThatMovesKeepsOneEntryOnItsBand() {