Mark the CQ frequency on the bandmap and leave Run when the radio moves off it
Starting Run stores the frequency in the position. The bandmap draws a purple, bold "CQ Frequency" pseudospot there, which is not added to the shared bandmap, and a click on it tunes back and sets Run. A move of more than 3 kHz (a setting on the telnet Filters tab) while running switches to search and pounce. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -313,7 +313,7 @@ public sealed partial class AppSession : IDisposable
|
|||||||
positions.Clear();
|
positions.Clear();
|
||||||
for (int number = 1; number <= PositionCount; number++)
|
for (int number = 1; number <= PositionCount; number++)
|
||||||
{
|
{
|
||||||
positions.Add(new OperatingPosition(Logging, number));
|
positions.Add(new OperatingPosition(Logging, number) { CqLeaveHertz = Settings.CqLeaveHertz });
|
||||||
}
|
}
|
||||||
activeRadio = Math.Min(activeRadio, positions.Count - 1);
|
activeRadio = Math.Min(activeRadio, positions.Count - 1);
|
||||||
// the other stations refuse to resynchronise with a station on another
|
// the other stations refuse to resynchronise with a station on another
|
||||||
@@ -995,6 +995,10 @@ public sealed partial class AppSession : IDisposable
|
|||||||
{
|
{
|
||||||
spotFilter = Settings.SpotFilter.ToFilter(BandPlan, Settings.Station, Countries, Calls, History);
|
spotFilter = Settings.SpotFilter.ToFilter(BandPlan, Settings.Station, Countries, Calls, History);
|
||||||
Bandmap.Lifetime = TimeSpan.FromMinutes(Math.Max(1, Settings.SpotTimeoutMinutes));
|
Bandmap.Lifetime = TimeSpan.FromMinutes(Math.Max(1, Settings.SpotTimeoutMinutes));
|
||||||
|
foreach (OperatingPosition position in positions)
|
||||||
|
{
|
||||||
|
position.CqLeaveHertz = Settings.CqLeaveHertz;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ReloadSupportFiles()
|
public void ReloadSupportFiles()
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ public sealed record Settings
|
|||||||
/// boxes are wiped. N1MM uses 2 kHz.
|
/// boxes are wiped. N1MM uses 2 kHz.
|
||||||
public int QsySpotHertz { get; init; } = 2000;
|
public int QsySpotHertz { get; init; } = 2000;
|
||||||
|
|
||||||
|
/// How far the radio can move off the CQ frequency, in hertz, before a
|
||||||
|
/// running position goes back to searching.
|
||||||
|
public int CqLeaveHertz { get; init; } = 3000;
|
||||||
|
|
||||||
public int TuningToleranceFor(ModeCategory mode) => mode switch
|
public int TuningToleranceFor(ModeCategory mode) => mode switch
|
||||||
{
|
{
|
||||||
ModeCategory.Phone => PhoneTuningToleranceHertz,
|
ModeCategory.Phone => PhoneTuningToleranceHertz,
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ public sealed class BandmapView : Control
|
|||||||
new Point(ScaleX + 2, label.MarkY),
|
new Point(ScaleX + 2, label.MarkY),
|
||||||
new Point(LabelX - 2, label.LabelY + (LabelHeight / 2)));
|
new Point(LabelX - 2, label.LabelY + (LabelHeight / 2)));
|
||||||
context.DrawText(
|
context.DrawText(
|
||||||
Text(label.Spot.Call.Text, colour, TextSize, label.Spot.IsLocal),
|
Text(label.Spot.Call.Text, colour, TextSize, label.Spot.IsLocal || label.Spot.IsCqFrequency),
|
||||||
new Point(LabelX, label.LabelY + (LabelPadding / 2)));
|
new Point(LabelX, label.LabelY + (LabelPadding / 2)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,15 +34,19 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
|
|
||||||
private readonly AppSession session;
|
private readonly AppSession session;
|
||||||
private readonly Action<Frequency, string> tune;
|
private readonly Action<Frequency, string> tune;
|
||||||
|
private readonly Action<Frequency> returnToCq;
|
||||||
|
|
||||||
/// Where the slice was last drawn from, so it stays put until the receiver
|
/// Where the slice was last drawn from, so it stays put until the receiver
|
||||||
/// nears an edge.
|
/// nears an edge.
|
||||||
private Frequency? sliceLow;
|
private Frequency? sliceLow;
|
||||||
|
|
||||||
public BandmapWindow(AppSession session, Action<Frequency, string> tune)
|
private static readonly IBrush CqBrush = new SolidColorBrush(Color.FromRgb(0x9B, 0x59, 0xB6));
|
||||||
|
|
||||||
|
public BandmapWindow(AppSession session, Action<Frequency, string> tune, Action<Frequency> returnToCq)
|
||||||
{
|
{
|
||||||
this.session = session;
|
this.session = session;
|
||||||
this.tune = tune;
|
this.tune = tune;
|
||||||
|
this.returnToCq = returnToCq;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
SpanBox.ItemsSource = Spans.Select(s => s.Label).ToList();
|
SpanBox.ItemsSource = Spans.Select(s => s.Label).ToList();
|
||||||
SpanBox.SelectedIndex = Math.Max(
|
SpanBox.SelectedIndex = Math.Max(
|
||||||
@@ -59,7 +63,17 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
CentreBox.IsCheckedChanged += (_, _) => SaveView();
|
CentreBox.IsCheckedChanged += (_, _) => SaveView();
|
||||||
View.Zoomed += (_, zoom) => Zoom(zoom);
|
View.Zoomed += (_, zoom) => Zoom(zoom);
|
||||||
View.ColourOf = Colour;
|
View.ColourOf = Colour;
|
||||||
View.SpotPicked += (_, spot) => tune(spot.Frequency, spot.Call.Text);
|
View.SpotPicked += (_, spot) =>
|
||||||
|
{
|
||||||
|
if (spot.IsCqFrequency)
|
||||||
|
{
|
||||||
|
returnToCq(spot.Frequency);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
tune(spot.Frequency, spot.Call.Text);
|
||||||
|
}
|
||||||
|
};
|
||||||
View.FrequencyPicked += (_, frequency) => tune(frequency, "");
|
View.FrequencyPicked += (_, frequency) => tune(frequency, "");
|
||||||
session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh);
|
session.Bandmap.Changed += (_, _) => Dispatcher.UIThread.Post(Refresh);
|
||||||
Refresh();
|
Refresh();
|
||||||
@@ -95,12 +109,24 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
long shown = high.Hertz - low.Hertz;
|
long shown = high.Hertz - low.Hertz;
|
||||||
View.TickStep = StepFor(shown);
|
View.TickStep = StepFor(shown);
|
||||||
View.DenseTicks = shown is > 40_000 and <= 200_000;
|
View.DenseTicks = shown is > 40_000 and <= 200_000;
|
||||||
View.Spots = session.Bandmap.On(band);
|
View.Spots = WithCqFrequency(session.Bandmap.On(band), band);
|
||||||
View.Vfos = Vfos();
|
View.Vfos = Vfos();
|
||||||
View.Segments = session.BandPlan.For(band);
|
View.Segments = session.BandPlan.For(band);
|
||||||
View.InvalidateVisual();
|
View.InvalidateVisual();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The bandmap's spots with the pseudospot for the CQ frequency, when the
|
||||||
|
/// position has one on this band. It is added here and not to the bandmap,
|
||||||
|
/// so it does not age out and the other windows do not list it.
|
||||||
|
private IReadOnlyList<Spot> WithCqFrequency(IReadOnlyList<Spot> spots, Band band)
|
||||||
|
{
|
||||||
|
if (session.Position?.CqFrequency is not { } cq || Bands.ForFrequency(cq) != band)
|
||||||
|
{
|
||||||
|
return spots;
|
||||||
|
}
|
||||||
|
return [.. spots.Append(Spot.CqFrequency(cq, DateTime.UtcNow)).OrderBy(s => s.Frequency.Hertz)];
|
||||||
|
}
|
||||||
|
|
||||||
/// Green for where the operator is listening, red for where the radio
|
/// Green for where the operator is listening, red for where the radio
|
||||||
/// transmits when working split, orange for the other radio of a two-radio
|
/// transmits when working split, orange for the other radio of a two-radio
|
||||||
/// station.
|
/// station.
|
||||||
@@ -165,7 +191,8 @@ public sealed partial class BandmapWindow : RefreshableWindow
|
|||||||
_ => Frequency.FromKilohertz(50),
|
_ => Frequency.FromKilohertz(50),
|
||||||
};
|
};
|
||||||
|
|
||||||
private IBrush Colour(Spot spot) => Verdicts.Colour(VerdictFor(spot));
|
private IBrush Colour(Spot spot) =>
|
||||||
|
spot.IsCqFrequency ? CqBrush : Verdicts.Colour(VerdictFor(spot));
|
||||||
|
|
||||||
private Verdict? VerdictFor(Spot spot)
|
private Verdict? VerdictFor(Spot spot)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,10 +12,6 @@ namespace Nonemm.App.Windows;
|
|||||||
/// which is how a station is worked without typing its call.
|
/// which is how a station is worked without typing its call.
|
||||||
public sealed partial class EntryWindow
|
public sealed partial class EntryWindow
|
||||||
{
|
{
|
||||||
/// Where we last called CQ, so the frame can say so when the radio comes
|
|
||||||
/// back to it.
|
|
||||||
private Frequency cqFrequency;
|
|
||||||
|
|
||||||
/// What the frame holds, when it holds a callsign. `CQ-Frequency` is not
|
/// What the frame holds, when it holds a callsign. `CQ-Frequency` is not
|
||||||
/// one, and neither is nothing.
|
/// one, and neither is nothing.
|
||||||
private string framedCall = "";
|
private string framedCall = "";
|
||||||
@@ -35,7 +31,7 @@ public sealed partial class EntryWindow
|
|||||||
CallFrameText.Text = "";
|
CallFrameText.Text = "";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cqFrequency.Hertz > 0
|
if (Logging.CqFrequency is { } cqFrequency
|
||||||
&& Math.Abs(Logging.Frequency.Hertz - cqFrequency.Hertz) <= TuningTolerance.Hertz)
|
&& Math.Abs(Logging.Frequency.Hertz - cqFrequency.Hertz) <= TuningTolerance.Hertz)
|
||||||
{
|
{
|
||||||
CallFrameText.Text = "CQ-Frequency";
|
CallFrameText.Text = "CQ-Frequency";
|
||||||
@@ -86,7 +82,7 @@ public sealed partial class EntryWindow
|
|||||||
{
|
{
|
||||||
if (Logging is not null)
|
if (Logging is not null)
|
||||||
{
|
{
|
||||||
cqFrequency = Logging.Frequency;
|
Logging.CqFrequency = Logging.Frequency;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -462,7 +462,7 @@ public sealed partial class EntryWindow
|
|||||||
|
|
||||||
private void OnShowCheck(object? sender, RoutedEventArgs e) => Show(() => new CheckWindow(session));
|
private void OnShowCheck(object? sender, RoutedEventArgs e) => Show(() => new CheckWindow(session));
|
||||||
|
|
||||||
private void OnShowBandmap(object? sender, RoutedEventArgs e) => Show(() => new BandmapWindow(session, Tune));
|
private void OnShowBandmap(object? sender, RoutedEventArgs e) => Show(() => new BandmapWindow(session, Tune, ReturnToCq));
|
||||||
|
|
||||||
private void OnShowAvailable(object? sender, RoutedEventArgs e) =>
|
private void OnShowAvailable(object? sender, RoutedEventArgs e) =>
|
||||||
Show(() => new AvailableWindow(session, Tune));
|
Show(() => new AvailableWindow(session, Tune));
|
||||||
@@ -712,6 +712,14 @@ public sealed partial class EntryWindow
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A click on the CQ frequency on the bandmap: back to where the CQs went
|
||||||
|
/// out, and running again.
|
||||||
|
private void ReturnToCq(Frequency frequency)
|
||||||
|
{
|
||||||
|
Tune(frequency, "");
|
||||||
|
SetRunning(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void Tune(Frequency frequency, string call)
|
private void Tune(Frequency frequency, string call)
|
||||||
{
|
{
|
||||||
if (Logging is null)
|
if (Logging is null)
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public sealed partial class EntryWindow
|
|||||||
[
|
[
|
||||||
(nameof(LogWindow), () => _ = Show(() => new LogWindow(session), activate: false)),
|
(nameof(LogWindow), () => _ = Show(() => new LogWindow(session), activate: false)),
|
||||||
(nameof(CheckWindow), () => _ = Show(() => new CheckWindow(session), activate: false)),
|
(nameof(CheckWindow), () => _ = Show(() => new CheckWindow(session), activate: false)),
|
||||||
(nameof(BandmapWindow), () => _ = Show(() => new BandmapWindow(session, Tune), activate: false)),
|
(nameof(BandmapWindow), () => _ = Show(() => new BandmapWindow(session, Tune, ReturnToCq), activate: false)),
|
||||||
(nameof(AvailableWindow), () => _ = Show(() => new AvailableWindow(session, Tune), activate: false)),
|
(nameof(AvailableWindow), () => _ = Show(() => new AvailableWindow(session, Tune), activate: false)),
|
||||||
(nameof(ScoreWindow), () => _ = Show(() => new ScoreWindow(session), activate: false)),
|
(nameof(ScoreWindow), () => _ = Show(() => new ScoreWindow(session), activate: false)),
|
||||||
(nameof(StatisticsWindow), () => _ = Show(() => new StatisticsWindow(session), activate: false)),
|
(nameof(StatisticsWindow), () => _ = Show(() => new StatisticsWindow(session), activate: false)),
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ public sealed partial class TelnetWindow
|
|||||||
BlockedCallsBox.Text = string.Join(' ', filter.BlockedCalls);
|
BlockedCallsBox.Text = string.Join(' ', filter.BlockedCalls);
|
||||||
TimeoutBox.Text = session.Settings.SpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture);
|
TimeoutBox.Text = session.Settings.SpotTimeoutMinutes.ToString(CultureInfo.InvariantCulture);
|
||||||
QsySpotBox.Text = session.Settings.QsySpotHertz.ToString(CultureInfo.InvariantCulture);
|
QsySpotBox.Text = session.Settings.QsySpotHertz.ToString(CultureInfo.InvariantCulture);
|
||||||
|
CqLeaveBox.Text = session.Settings.CqLeaveHertz.ToString(CultureInfo.InvariantCulture);
|
||||||
SpotCommentBox.Text = session.Settings.SpotComment;
|
SpotCommentBox.Text = session.Settings.SpotComment;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,6 +75,7 @@ public sealed partial class TelnetWindow
|
|||||||
RandomizeSpots = RandomizeBox.IsChecked == true,
|
RandomizeSpots = RandomizeBox.IsChecked == true,
|
||||||
SpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.SpotTimeoutMinutes),
|
SpotTimeoutMinutes = Minutes(TimeoutBox.Text, session.Settings.SpotTimeoutMinutes),
|
||||||
QsySpotHertz = Minutes(QsySpotBox.Text, session.Settings.QsySpotHertz),
|
QsySpotHertz = Minutes(QsySpotBox.Text, session.Settings.QsySpotHertz),
|
||||||
|
CqLeaveHertz = Minutes(CqLeaveBox.Text, session.Settings.CqLeaveHertz),
|
||||||
});
|
});
|
||||||
Show("*** filters applied — they hold for the spots that arrive from now on", NoticeColour);
|
Show("*** filters applied — they hold for the spots that arrive from now on", NoticeColour);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -124,6 +124,8 @@
|
|||||||
<TextBox Name="TimeoutBox" Width="60" HorizontalAlignment="Left" />
|
<TextBox Name="TimeoutBox" Width="60" HorizontalAlignment="Left" />
|
||||||
<TextBlock Classes="label" Text="Spot a call locally when the radio moves more than (Hz)" />
|
<TextBlock Classes="label" Text="Spot a call locally when the radio moves more than (Hz)" />
|
||||||
<TextBox Name="QsySpotBox" Width="60" HorizontalAlignment="Left" />
|
<TextBox Name="QsySpotBox" Width="60" HorizontalAlignment="Left" />
|
||||||
|
<TextBlock Classes="label" Text="Go back to search and pounce when the radio moves off the CQ frequency by more than (Hz)" TextWrapping="Wrap" />
|
||||||
|
<TextBox Name="CqLeaveBox" Width="60" HorizontalAlignment="Left" />
|
||||||
<Button Content="Apply" Click="OnApplyFilters" Margin="0,10,0,0" />
|
<Button Content="Apply" Click="OnApplyFilters" Margin="0,10,0,0" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|||||||
@@ -46,7 +46,28 @@ public sealed class OperatingPosition
|
|||||||
|
|
||||||
/// True while the operator is calling CQ on this radio rather than
|
/// True while the operator is calling CQ on this radio rather than
|
||||||
/// searching. Each radio runs or searches on its own.
|
/// searching. Each radio runs or searches on its own.
|
||||||
public bool IsRunning { get; set; }
|
public bool IsRunning
|
||||||
|
{
|
||||||
|
get => running;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value && !running)
|
||||||
|
{
|
||||||
|
CqFrequency = Frequency;
|
||||||
|
}
|
||||||
|
running = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool running;
|
||||||
|
|
||||||
|
/// Where this radio last started running or sent a CQ. It stays after the
|
||||||
|
/// radio goes back to searching, so the operator can return to it.
|
||||||
|
public Frequency? CqFrequency { get; set; }
|
||||||
|
|
||||||
|
/// How far, in hertz, the radio can move off the CQ frequency before the
|
||||||
|
/// position goes back to searching.
|
||||||
|
public long CqLeaveHertz { get; set; } = 3_000;
|
||||||
|
|
||||||
/// The other operator's name and a note about the contact. Neither is part
|
/// The other operator's name and a note about the contact. Neither is part
|
||||||
/// of any exchange: the name is what a contact being quick edited already
|
/// of any exchange: the name is what a contact being quick edited already
|
||||||
@@ -110,6 +131,10 @@ public sealed class OperatingPosition
|
|||||||
FillSentExchange();
|
FillSentExchange();
|
||||||
}
|
}
|
||||||
TransmitFrequency = transmitFrequency ?? TransmitFrequency;
|
TransmitFrequency = transmitFrequency ?? TransmitFrequency;
|
||||||
|
if (running && CqFrequency is { } cq && Math.Abs(frequency.Hertz - cq.Hertz) > CqLeaveHertz)
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
Session.NotifyChanged();
|
Session.NotifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,17 @@ public sealed record Spot(
|
|||||||
/// opposed to one from the cluster.
|
/// opposed to one from the cluster.
|
||||||
public bool IsLocal => Source == SpotSource.Operator && IsStation;
|
public bool IsLocal => Source == SpotSource.Operator && IsStation;
|
||||||
|
|
||||||
|
/// The pseudospot that marks where the operator is running. It is drawn on
|
||||||
|
/// the bandmap and is never added to it.
|
||||||
|
public bool IsCqFrequency { get; init; }
|
||||||
|
|
||||||
|
public static Spot CqFrequency(Frequency where, DateTime atUtc) =>
|
||||||
|
new(Callsign.Parse("CQ Frequency"), where, atUtc, SpotSource.Operator)
|
||||||
|
{
|
||||||
|
IsStation = false,
|
||||||
|
IsCqFrequency = true,
|
||||||
|
};
|
||||||
|
|
||||||
public bool IsSplit => Qsx.Hertz > 0;
|
public bool IsSplit => Qsx.Hertz > 0;
|
||||||
|
|
||||||
public Band? Band => Bands.ForFrequency(Frequency);
|
public Band? Band => Bands.ForFrequency(Frequency);
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ public static partial class SpotDescription
|
|||||||
{
|
{
|
||||||
public static string Of(Spot spot, DateTime? nowUtc = null)
|
public static string Of(Spot spot, DateTime? nowUtc = null)
|
||||||
{
|
{
|
||||||
|
if (spot.IsCqFrequency)
|
||||||
|
{
|
||||||
|
return $"{spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture)}, your CQ frequency";
|
||||||
|
}
|
||||||
string age = Age((nowUtc ?? DateTime.UtcNow) - spot.AtUtc);
|
string age = Age((nowUtc ?? DateTime.UtcNow) - spot.AtUtc);
|
||||||
string kilohertz = spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture);
|
string kilohertz = spot.Frequency.Kilohertz.ToString("0.0", CultureInfo.InvariantCulture);
|
||||||
string who = spot.IsLocal && spot.Computer.Length > 0 ? spot.Computer : spot.Spotter;
|
string who = spot.IsLocal && spot.Computer.Length > 0 ? spot.Computer : spot.Spotter;
|
||||||
|
|||||||
@@ -25,6 +25,45 @@ public class OperatingPositionTests
|
|||||||
CountryPrefix = "DL",
|
CountryPrefix = "DL",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RunningRemembersTheFrequencyItStartedOn()
|
||||||
|
{
|
||||||
|
OperatingPosition position = Session();
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_025));
|
||||||
|
|
||||||
|
position.IsRunning = true;
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_026));
|
||||||
|
|
||||||
|
Assert.Equal(Frequency.FromKilohertz(14_025), position.CqFrequency);
|
||||||
|
Assert.True(position.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MovingFarOffTheCqFrequencyGoesBackToSearching()
|
||||||
|
{
|
||||||
|
OperatingPosition position = Session();
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_025));
|
||||||
|
position.IsRunning = true;
|
||||||
|
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_028.5));
|
||||||
|
|
||||||
|
Assert.False(position.IsRunning);
|
||||||
|
Assert.Equal(Frequency.FromKilohertz(14_025), position.CqFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TheDistanceThatEndsRunningIsSet()
|
||||||
|
{
|
||||||
|
OperatingPosition position = Session();
|
||||||
|
position.CqLeaveHertz = 1_000;
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_025));
|
||||||
|
position.IsRunning = true;
|
||||||
|
|
||||||
|
position.Tune(Frequency.FromKilohertz(14_026.5));
|
||||||
|
|
||||||
|
Assert.False(position.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
/// N1MM's `{FORCELOG}`: a contact the checks refuse still goes in the log,
|
/// N1MM's `{FORCELOG}`: a contact the checks refuse still goes in the log,
|
||||||
/// because one that was never logged cannot be fixed afterwards.
|
/// because one that was never logged cannot be fixed afterwards.
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -73,4 +73,21 @@ public class SpotDescriptionTests
|
|||||||
Assert.Equal("14025.0 by SHACK-2, 41m ago", SpotDescription.Of(spot, Now));
|
Assert.Equal("14025.0 by SHACK-2, 41m ago", SpotDescription.Of(spot, Now));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TheCqFrequencyPseudospotSaysSo() =>
|
||||||
|
Assert.Equal("14025.0, your CQ frequency", SpotDescription.Of(Spot.CqFrequency(Frequency.FromKilohertz(14_025), At), Now));
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("CW 26 dB 1205Z", "CW 26 dB")]
|
||||||
|
[InlineData("1205Z", "")]
|
||||||
|
[InlineData("QRP 5W1205Z", "QRP 5W1205Z")]
|
||||||
|
public void TheTimeAtTheEndOfAClusterLineIsLeftOut(string comment, string expected)
|
||||||
|
{
|
||||||
|
Spot spot = new(
|
||||||
|
Callsign.Parse("JA1XYZ"), Frequency.FromKilohertz(14_025), At, SpotSource.Cluster,
|
||||||
|
"K1TTT", comment);
|
||||||
|
|
||||||
|
string second = SpotDescription.Of(spot, Now).Split('\n').ElementAtOrDefault(1) ?? "";
|
||||||
|
Assert.Equal(expected, second);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user