diff --git a/docs/unfinished.md b/docs/unfinished.md index c2aaa11..61853e1 100644 --- a/docs/unfinished.md +++ b/docs/unfinished.md @@ -382,6 +382,26 @@ Where a station is goes out once a second when it has changed, rather than from each of the dozen places it can change from. In a multi-single entry that is what the other operators watch. +**The status message.** `STATUS` goes out every five seconds whether anything +has changed or not, and it is what fills the other operators' windows: the pass +frequency, the frequency, run or search and pounce, the operator, the rates over +the last ten and the last hundred contacts, the mode, the categories and the +version. `Rates.PerHour` reads the two rates out of the log the way N1MM does — +counting back over the last N contacts, stopping at a gap longer than the +contest's minimum off time, and treating a span under a minute as a minute so a +burst of three does not read as thousands an hour. The five transmit-interlock +flags in the message are sent as false: this program does not block a +transmitter. + +**The network status window** has N1MM's Stations, Actions and Messages tabs and +its columns — Computer, IP Address, Pass, Run, 10, 100, Freq, Op, Msg, Send, +Receive, Band, R/M, Mode. Right-click a row to pass the contact being worked to +that station, to talk, or to close and open its connection; a connection closed +by hand stays closed until it is opened again, because the next beacon would +otherwise open it within ten seconds. Left out: N1MM's Resynchronize and Rescore +tabs, the transmit interlocks of its Options tab, the Master column and the +call-stacking target tab. There is nothing behind any of them here. + **The ten-minute rule.** `BandChangeRules` counts band changes and the stay on a band, and the entry window shows the countdown. What was missing was the rule itself: only a user-defined contest carried one, so every built-in contest diff --git a/src/Nonemm.App/AppSession.cs b/src/Nonemm.App/AppSession.cs index 0474233..6aec4e7 100644 --- a/src/Nonemm.App/AppSession.cs +++ b/src/Nonemm.App/AppSession.cs @@ -19,6 +19,11 @@ namespace Nonemm.App; /// `Changed` event tells them all to redraw. public sealed class AppSession : IDisposable { + /// How often this station tells the others how it is doing. N1MM sends its + /// status on the same sort of interval; every second would read the log + /// sixty times a minute for two numbers that barely move. + private static readonly TimeSpan StatusInterval = TimeSpan.FromSeconds(5); + private LogStore? store; private readonly List radios = []; private readonly List positions = []; @@ -30,6 +35,9 @@ public sealed class AppSession : IDisposable /// What was last said to the other stations about where this one is, so it /// is said again only when it changes. private (Frequency Where, string Mode, bool Running, int Radio) announced; + /// When the last `STATUS` went out. It is not sent every second: it carries + /// the rates, which are read out of the log. + private DateTime statusSent; private DispatcherTimer? announcing; private MessageSender? keyer; private AlternatingCq? alternating; @@ -310,8 +318,13 @@ public sealed class AppSession : IDisposable // changed, rather than announced from each of them announcing?.Stop(); announced = default; + statusSent = default; announcing = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) }; - announcing.Tick += (_, _) => AnnounceWhereIAm(); + announcing.Tick += (_, _) => + { + AnnounceWhereIAm(); + SayHowIAmDoing(); + }; announcing.Start(); Changed?.Invoke(this, EventArgs.Empty); } @@ -335,6 +348,35 @@ public sealed class AppSession : IDisposable _ = link.SendBandAsync(position.Frequency, position.Mode, position.IsRunning, position.RadioNumber); } + /// The `STATUS` message: everything the other operators' network status + /// windows show about this station, whether anything has changed or not. It + /// goes out on N1MM's own interval, and the rates in it are read out of the + /// log each time. + private void SayHowIAmDoing() + { + if (link is null || Position is not { } position + || DateTime.UtcNow - statusSent < StatusInterval) + { + return; + } + statusSent = DateTime.UtcNow; + TimeSpan off = Logging?.Contest.MinimumOffTime ?? TimeSpan.FromMinutes(30); + IReadOnlyList qsos = Logging?.Log.Qsos ?? []; + _ = link.SendStatusAsync(new StationStatus( + link.Me.PassFrequency, + position.Frequency, + position.IsRunning, + link.Operator, + position.Mode, + Rates.PerHour(qsos, 10, DateTime.UtcNow, off), + Rates.PerHour(qsos, 100, DateTime.UtcNow, off), + Logging?.Instance.OperatorCategory ?? "", + Logging?.Instance.TransmitterCategory ?? "", + // the second radio of a two-radio position is N1MM's Run2, which is + // what its R/M column reads + position.RadioNumber == 2)); + } + /// A station named by hand, written `RUN-PC@192.168.1.5` with `:port` on /// the end when that station is not on the usual one. Null for anything /// else, because a line the operator has half typed is not an address. diff --git a/src/Nonemm.App/Windows/NetworkStatusWindow.axaml b/src/Nonemm.App/Windows/NetworkStatusWindow.axaml index 8295d75..1980fbf 100644 --- a/src/Nonemm.App/Windows/NetworkStatusWindow.axaml +++ b/src/Nonemm.App/Windows/NetworkStatusWindow.axaml @@ -2,22 +2,85 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Nonemm.App.Windows" x:Class="Nonemm.App.Windows.NetworkStatusWindow" - Title="Network status" Width="820" Height="320"> - - - - -