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() {