Take serial numbers from a pool the whole entry shares
Each station worked out its next number as the highest in its log plus one, so two operators typing at the same moment both sent the same number and one of the two contacts was wrong in the sponsor's log. SerialNumberPool holds the highest number taken per band, which is N1MM's QSONumbers array. A number is taken at the first character of the callsign, as N1MM takes it, and ReserveNr tells the other stations before the contact is worked. A number at or below one this station has used is answered with the whole pool, N1MM's QSONRS, and the station that is behind counts on from there. The pool also goes out when a contest is opened and when the numbers are started again. Contest.NumbersByBand says whether the numbers run per band or as one sequence for the station. CQ WPX runs one per band for a multi-two and a multi-multi; every other contest here runs one sequence. The band index in the message is N1MM's, so the array lines up with theirs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015NZkFxHde1iifAQemFTHxf
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
using Nonemm.Core;
|
||||
|
||||
namespace Nonemm.Network;
|
||||
|
||||
/// A serial number another station has taken, and where it was working when it
|
||||
/// took it. The band decides which of the pool's numbers it belongs to.
|
||||
public sealed record ReservedNumberMessage(int Number, Frequency Frequency);
|
||||
@@ -131,6 +131,14 @@ public sealed class StationLink : IDisposable
|
||||
/// owner is told and does it.
|
||||
public event EventHandler? SerialNumbersReset;
|
||||
|
||||
/// A serial number another station has taken, with the frequency it took
|
||||
/// it on. The pool is not the link's, so the owner is told and notes it.
|
||||
public event EventHandler<ReservedNumberMessage>? NumberReserved;
|
||||
|
||||
/// The whole serial number pool from another station, one number per band
|
||||
/// in N1MM's band order.
|
||||
public event EventHandler<IReadOnlyList<int>>? SerialNumbersArrived;
|
||||
|
||||
/// A callsign another station handed to this one's call stack, with the
|
||||
/// contact behind it when there was one.
|
||||
public event EventHandler<StackedCall>? CallStacked;
|
||||
@@ -424,6 +432,30 @@ public sealed class StationLink : IDisposable
|
||||
StationsChanged?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
/// Says which serial number this station has taken for the contact it is
|
||||
/// about to work. It goes out at the first character of the callsign, so
|
||||
/// the other operators' next number is above this one.
|
||||
public Task<int> SendReservedNumberAsync(
|
||||
int number,
|
||||
Frequency frequency,
|
||||
CancellationToken cancellation = default) =>
|
||||
SendAsync(
|
||||
StationMessages.ReserveNumber(number, frequency, StationNumber, computerName),
|
||||
cancellation);
|
||||
|
||||
/// Sends the whole pool, which is what a station answers with when the
|
||||
/// number another one has taken is one this station has already used.
|
||||
public Task<int> SendSerialNumbersAsync(
|
||||
IReadOnlyList<int> numbers,
|
||||
CancellationToken cancellation = default) =>
|
||||
SendAsync(
|
||||
StationMessages.SerialNumbers(
|
||||
Log?.ContestName ?? "",
|
||||
numbers,
|
||||
StationNumber,
|
||||
computerName),
|
||||
cancellation);
|
||||
|
||||
/// Tells every station to start its serial numbers again at one.
|
||||
public Task<int> SendResetSerialNumbersAsync(CancellationToken cancellation = default) =>
|
||||
SendAsync(StationMessages.ResetSerialNumbers(StationNumber, computerName), cancellation);
|
||||
@@ -893,6 +925,21 @@ public sealed class StationLink : IDisposable
|
||||
case "RESETQSONRS":
|
||||
SerialNumbersReset?.Invoke(this, EventArgs.Empty);
|
||||
break;
|
||||
case "RESERVENR":
|
||||
NumberReserved?.Invoke(
|
||||
this,
|
||||
new ReservedNumberMessage(
|
||||
record.Number(0),
|
||||
Frequency.FromKilohertz(record.Decimal(1) / 100)));
|
||||
break;
|
||||
case "QSONRS":
|
||||
// the pool is per contest, so a station logging another one is
|
||||
// counting something else
|
||||
if (Log is null || record.Field(0).Equals(Log.ContestName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
SerialNumbersArrived?.Invoke(this, StationMessages.ReadSerialNumbers(record));
|
||||
}
|
||||
break;
|
||||
case "STACKANOTHERCALL":
|
||||
CallStacked?.Invoke(this, new StackedCall(record.Field(0), null, station.ComputerName));
|
||||
break;
|
||||
|
||||
@@ -27,6 +27,8 @@ namespace Nonemm.Network;
|
||||
/// | `TIME` | the sender's clock | yes | yes |
|
||||
/// | `MASTER` | which station is the master for the cluster and the time | yes | yes |
|
||||
/// | `RESETQSONRS` | start the serial numbers again at one | yes | yes |
|
||||
/// | `ReserveNr` | a serial number the sender has taken for a contact | yes | yes |
|
||||
/// | `QSONRS` | the highest serial number taken on each band | yes | yes |
|
||||
/// | `STACKCALL` | a contact for another station's call stack | yes | yes |
|
||||
/// | `STACKANOTHERCALL` | a callsign for another station's call stack | yes | yes |
|
||||
/// | `STATUS` | everything the network status window shows about a station | yes | yes |
|
||||
@@ -34,8 +36,8 @@ namespace Nonemm.Network;
|
||||
/// | `CONFIRMED` | the answer to a checksum, and what moves a resync on | yes | yes |
|
||||
/// | `DELETEQS` | drop your copies of one station's contacts in this block | yes | yes |
|
||||
///
|
||||
/// The rest — the score and sked windows, the log check, the spot lists, the
|
||||
/// serial-number pool — are passed over. An unknown type is not an error: N1MM
|
||||
/// The rest — the score and sked windows, the log check, the spot lists — are
|
||||
/// passed over. An unknown type is not an error: N1MM
|
||||
/// adds them between versions, and a station that cannot read one is no worse
|
||||
/// off than a station that has not been told.
|
||||
public static class StationMessages
|
||||
@@ -307,6 +309,30 @@ public static class StationMessages
|
||||
public static StationRecord ResetSerialNumbers(int stationNumber, string computerName) =>
|
||||
Record("RESETQSONRS", stationNumber, computerName, []);
|
||||
|
||||
/// A serial number this station has taken for a contact it is about to
|
||||
/// work, with the frequency it was taken on. The other stations count past
|
||||
/// it, so nobody sends the same number twice.
|
||||
public static StationRecord ReserveNumber(
|
||||
int number,
|
||||
Frequency frequency,
|
||||
int stationNumber,
|
||||
string computerName) =>
|
||||
Record("ReserveNr", stationNumber, computerName, [Text(number), Hundredths(frequency)]);
|
||||
|
||||
/// The highest serial number taken on each band, in N1MM's band order. It
|
||||
/// is the answer to a `ReserveNr` that has fallen behind: every station
|
||||
/// keeps the higher of the two for each band, so the sender catches up.
|
||||
public static StationRecord SerialNumbers(
|
||||
string contestName,
|
||||
IReadOnlyList<int> numbers,
|
||||
int stationNumber,
|
||||
string computerName) =>
|
||||
Record("QSONRS", stationNumber, computerName, [contestName, .. numbers.Select(Text)]);
|
||||
|
||||
/// The numbers out of a `QSONRS` message, one per band.
|
||||
public static IReadOnlyList<int> ReadSerialNumbers(StationRecord record) =>
|
||||
[.. Enumerable.Range(1, record.Fields.Count - 1).Select(record.Number)];
|
||||
|
||||
/// A contact for another station's call stack: a station this one has heard
|
||||
/// but is not going to work, handed to the position that will.
|
||||
public static StationRecord StackContact(Qso qso, int stationNumber, string computerName) =>
|
||||
|
||||
Reference in New Issue
Block a user