Two entry windows and an SO2R box
LoggingSession mixed two things: the contest, which one station has one of, and what the operator is typing, which belongs to a radio. It is now ContestSession and RadioPosition. Two positions share one log, one score and one run of serial numbers, so a station worked on radio 1 is a dupe on radio 2. Frequency, mode, split, what is typed and whether you are running belong to each radio on its own. A second radio opens a second entry window. It has no menu of its own: it is another view of the same contest, not another program. Only the window for radio 1 opens the database and the connections. Ctrl+Tab moves the operator to the other radio and the keyboard follows. Ctrl+Shift+Tab puts both radios in the headphones. OtrspBox speaks OTRSP to an SO2R box over a serial port: TX1/TX2 for the key, RX1/RX2 and RX1S for the headphones, AUXnnn for an output line. It follows the active radio, and every message points the box at this radio before keying, so a message cannot go out of the radio the operator has just left. A command that would change nothing is not sent, because the box works relays; N1MM does the same. The box takes a Stream, so what it sends is tested without a serial port. Still missing: alternating CQ, and voice keying on the second radio. Looked at under Xvfb with two fake radios, one of them split: both windows up, radio 1 showing 14008.00 into 14020.0, radio 2 on 14030.00, Ctrl+Tab moving the keyboard between them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
116
tests/Nonemm.Session.Tests/ContestSessionTests.cs
Normal file
116
tests/Nonemm.Session.Tests/ContestSessionTests.cs
Normal file
@@ -0,0 +1,116 @@
|
||||
using Nonemm.Contests;
|
||||
using Nonemm.Contests.Rules;
|
||||
using Nonemm.Core;
|
||||
using Nonemm.Core.Country;
|
||||
using Nonemm.Storage;
|
||||
|
||||
namespace Nonemm.Session.Tests;
|
||||
|
||||
/// Two radios sharing one contest.
|
||||
public class ContestSessionTests
|
||||
{
|
||||
private static readonly StationInfo Me = new()
|
||||
{
|
||||
Callsign = "DL1ABC",
|
||||
CqZone = 14,
|
||||
Continent = "EU",
|
||||
CountryPrefix = "DL",
|
||||
};
|
||||
|
||||
private static ContestSession Session()
|
||||
{
|
||||
FakeLogStore store = new();
|
||||
ContestInstance instance = store.AddContest(new ContestInstance
|
||||
{
|
||||
ContestNumber = 0,
|
||||
ContestName = "CQWPXCW",
|
||||
});
|
||||
return new ContestSession(store, new CqWpx(ModeCategory.Cw), instance, Me, null);
|
||||
}
|
||||
|
||||
private static void Type(RadioPosition radio, string call, string number)
|
||||
{
|
||||
radio.Entry.Call = call;
|
||||
radio.Entry.Set(ExchangeSlot.ReceivedReport, "599");
|
||||
radio.Entry.Set(ExchangeSlot.SerialNumber, number);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BothRadiosLogIntoTheSameLog()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
first.LogContact();
|
||||
Type(second, "W3LPL", "2");
|
||||
second.LogContact();
|
||||
|
||||
Assert.Equal(2, session.Log.Qsos.Count);
|
||||
Assert.Equal([1, 2], session.Log.Qsos.Select(q => q.RadioNumber));
|
||||
}
|
||||
|
||||
/// Serial numbers belong to the station, not to a radio, so the second
|
||||
/// radio carries on where the first left off.
|
||||
[Fact]
|
||||
public void SerialNumbersCountUpAcrossBothRadios()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
Assert.Equal(1, first.LogContact().SentNumber);
|
||||
|
||||
Type(second, "W3LPL", "2");
|
||||
Assert.Equal(2, second.LogContact().SentNumber);
|
||||
}
|
||||
|
||||
/// A station worked on radio 1 is a dupe on radio 2, or the second radio
|
||||
/// spends the contest working stations again.
|
||||
[Fact]
|
||||
public void AStationWorkedOnOneRadioIsADupeOnTheOther()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
first.LogContact();
|
||||
|
||||
Type(second, "JA1XYZ", "2");
|
||||
Assert.True(second.Verdict()?.IsDupe);
|
||||
}
|
||||
|
||||
/// The two radios are on different bands and are running or searching
|
||||
/// independently, so neither of those can be shared.
|
||||
[Fact]
|
||||
public void EachRadioKeepsItsOwnFrequencyAndRunState()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
|
||||
first.Tune(Frequency.FromKilohertz(14_025));
|
||||
first.IsRunning = true;
|
||||
second.Tune(Frequency.FromKilohertz(7_025));
|
||||
|
||||
Assert.Equal(14_025_000, first.Frequency.Hertz);
|
||||
Assert.Equal(7_025_000, second.Frequency.Hertz);
|
||||
Assert.True(first.IsRunning);
|
||||
Assert.False(second.IsRunning);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WhatIsTypedOnOneRadioStaysThere()
|
||||
{
|
||||
ContestSession session = Session();
|
||||
RadioPosition first = new(session, 1);
|
||||
RadioPosition second = new(session, 2);
|
||||
|
||||
Type(first, "JA1XYZ", "1");
|
||||
|
||||
Assert.Equal("", second.Entry.Call);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user