Add CW keying, function key messages and the documentation

The function keys send through cwdaemon or a WinKeyer, with N1MM's message
macros. Escape stops sending.

Settings are read with the reflection serializer rather than a generated one:
the generated one hands back null for every property the file leaves out
instead of the value the property is declared with, which crashed the program
the first time a new setting was added.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik
2026-08-27 11:23:30 +00:00
co-authored by Claude Opus 5
parent d96cbe146b
commit f5aa77ece3
25 changed files with 1002 additions and 26 deletions
+3 -1
View File
@@ -178,7 +178,9 @@ public sealed class LoggingSession
return qso;
}
private string DefaultReport() => Mode.Category switch
/// The report an operator sends without thinking about it: 599 on CW and
/// digital modes, 59 on phone.
public string DefaultReport() => Mode.Category switch
{
ModeCategory.Cw => "599",
ModeCategory.Phone => "59",
+63
View File
@@ -0,0 +1,63 @@
using System.Text;
namespace Nonemm.Session;
/// Fills in a function key message. The macro names are N1MM's, so a message
/// file written for either program says the same thing.
public static class MessageExpander
{
/// Cut numbers: a contest operator sends T for zero and N for nine because
/// they are shorter.
private const string CutDigits = "T12345678N";
public static string Expand(string template, LoggingSession session)
{
StringBuilder text = new();
int at = 0;
while (at < template.Length)
{
char c = template[at];
if (c == '{')
{
int close = template.IndexOf('}', at + 1);
if (close < 0)
{
text.Append(template[at..]);
break;
}
text.Append(Macro(template[(at + 1)..close], session));
at = close + 1;
continue;
}
if (c == '#')
{
text.Append(session.SentNumber);
at++;
continue;
}
text.Append(c);
at++;
}
return text.ToString();
}
private static string Macro(string name, LoggingSession session) => name.ToUpperInvariant() switch
{
"MYCALL" => session.Me.Callsign,
"CALL" => session.Entry.Call.Trim(),
"LOGGEDCALL" => session.Log.Qsos.Count > 0 ? session.Log.Qsos[^1].Call.Text : "",
"EXCH" => session.Instance.SentExchange,
"SENTRST" => session.DefaultReport(),
"SENTRSTCUT" => Cut(session.DefaultReport()),
"SENTNR" => session.SentNumber.ToString(),
"SENTNRCUT" => Cut(session.SentNumber.ToString()),
"NAME" => session.Me.Name,
"GRIDSQUARE" => session.Me.GridSquare,
"MYZONE" => session.Me.CqZone.ToString(),
"OTHERMHZ" => (session.Frequency.Megahertz).ToString("0.###"),
_ => "",
};
private static string Cut(string digits) =>
new(digits.Select(d => char.IsAsciiDigit(d) ? CutDigits[d - '0'] : d).ToArray());
}